* (WIP)-all issues corrected, tests started * first test passing alternative solution for SonarCloud check fixed for to while translation added changelog item adding tests and fixes more tests; avoid compiling BSD Tests on Windows attempting a BSD compile patch more tests borrowing symbol detection from another test more ambitious testing changed the order of assertions for better info more tests fixed clear errors before executing expanding transformation to use string comparison including terminator on translation count a different flag for testing MS Windows differente Windows flag more tests fixing platform encoding match Windows compiler difference on #ifdef Google Test macro usage fix test added added keymap test test and initialisation fixed ms cl issue with named init test for keymap exercising keystate more configuration exercises one more test for config added Unicode test an IPv6 test more portable networking code using our own platform switching code avoiding strcpy * Fixed typo in Changelog. * +appropriate memory handling * one more test for coverage * a tad more coverage * expanded test on Config Co-authored-by: Max <maxmommersteeg@hotmail.com> Co-authored-by: SerhiiGadzhilov <71632867+SerhiiGadzhilov@users.noreply.github.com>
2372 lines
54 KiB
C++
2372 lines
54 KiB
C++
/*
|
|
* synergy -- mouse and keyboard sharing utility
|
|
* Copyright (C) 2012-2016 Symless Ltd.
|
|
* Copyright (C) 2002 Chris Schoeneman
|
|
*
|
|
* This package is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* found in the file LICENSE that should have accompanied this file.
|
|
*
|
|
* This package is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "server/Config.h"
|
|
|
|
#include "server/Server.h"
|
|
#include "synergy/KeyMap.h"
|
|
#include "synergy/key_types.h"
|
|
#include "net/XSocket.h"
|
|
#include "base/IEventQueue.h"
|
|
#include "common/stdistream.h"
|
|
#include "common/stdostream.h"
|
|
|
|
#include <cstdlib>
|
|
|
|
using namespace synergy::string;
|
|
|
|
//
|
|
// Config
|
|
//
|
|
|
|
Config::Config(IEventQueue* events) :
|
|
m_inputFilter(events),
|
|
m_hasLockToScreenAction(false),
|
|
m_events(events)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
Config::~Config()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
bool
|
|
Config::addScreen(const String& name)
|
|
{
|
|
// alias name must not exist
|
|
if (m_nameToCanonicalName.find(name) != m_nameToCanonicalName.end()) {
|
|
return false;
|
|
}
|
|
|
|
// add cell
|
|
m_map.insert(std::make_pair(name, Cell()));
|
|
|
|
// add name
|
|
m_nameToCanonicalName.insert(std::make_pair(name, name));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Config::renameScreen(const String& oldName,
|
|
const String& newName)
|
|
{
|
|
// get canonical name and find cell
|
|
String oldCanonical = getCanonicalName(oldName);
|
|
CellMap::iterator index = m_map.find(oldCanonical);
|
|
if (index == m_map.end()) {
|
|
return false;
|
|
}
|
|
|
|
// accept if names are equal but replace with new name to maintain
|
|
// case. otherwise, the new name must not exist.
|
|
if (!CaselessCmp::equal(oldName, newName) &&
|
|
m_nameToCanonicalName.find(newName) != m_nameToCanonicalName.end()) {
|
|
return false;
|
|
}
|
|
|
|
// update cell
|
|
Cell tmpCell = index->second;
|
|
m_map.erase(index);
|
|
m_map.insert(std::make_pair(newName, tmpCell));
|
|
|
|
// update name
|
|
m_nameToCanonicalName.erase(oldCanonical);
|
|
m_nameToCanonicalName.insert(std::make_pair(newName, newName));
|
|
|
|
// update connections
|
|
Name oldNameObj(this, oldName);
|
|
for (index = m_map.begin(); index != m_map.end(); ++index) {
|
|
index->second.rename(oldNameObj, newName);
|
|
}
|
|
|
|
// update alias targets
|
|
if (CaselessCmp::equal(oldName, oldCanonical)) {
|
|
for (NameMap::iterator iter = m_nameToCanonicalName.begin();
|
|
iter != m_nameToCanonicalName.end(); ++iter) {
|
|
if (CaselessCmp::equal(
|
|
iter->second, oldCanonical)) {
|
|
iter->second = newName;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Config::removeScreen(const String& name)
|
|
{
|
|
// get canonical name and find cell
|
|
String canonical = getCanonicalName(name);
|
|
CellMap::iterator index = m_map.find(canonical);
|
|
if (index == m_map.end()) {
|
|
return;
|
|
}
|
|
|
|
// remove from map
|
|
m_map.erase(index);
|
|
|
|
// disconnect
|
|
Name nameObj(this, name);
|
|
for (index = m_map.begin(); index != m_map.end(); ++index) {
|
|
index->second.remove(nameObj);
|
|
}
|
|
|
|
// remove aliases (and canonical name)
|
|
for (NameMap::iterator iter = m_nameToCanonicalName.begin();
|
|
iter != m_nameToCanonicalName.end(); ) {
|
|
if (iter->second == canonical) {
|
|
m_nameToCanonicalName.erase(iter++);
|
|
}
|
|
else {
|
|
++index;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Config::removeAllScreens()
|
|
{
|
|
m_map.clear();
|
|
m_nameToCanonicalName.clear();
|
|
}
|
|
|
|
bool
|
|
Config::addAlias(const String& canonical, const String& alias)
|
|
{
|
|
// alias name must not exist
|
|
if (m_nameToCanonicalName.find(alias) != m_nameToCanonicalName.end()) {
|
|
return false;
|
|
}
|
|
|
|
// canonical name must be known
|
|
if (m_map.find(canonical) == m_map.end()) {
|
|
return false;
|
|
}
|
|
|
|
// insert alias
|
|
m_nameToCanonicalName.insert(std::make_pair(alias, canonical));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Config::removeAlias(const String& alias)
|
|
{
|
|
// must not be a canonical name
|
|
if (m_map.find(alias) != m_map.end()) {
|
|
return false;
|
|
}
|
|
|
|
// find alias
|
|
NameMap::iterator index = m_nameToCanonicalName.find(alias);
|
|
if (index == m_nameToCanonicalName.end()) {
|
|
return false;
|
|
}
|
|
|
|
// remove alias
|
|
m_nameToCanonicalName.erase(index);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Config::removeAliases(const String& canonical)
|
|
{
|
|
// must be a canonical name
|
|
if (m_map.find(canonical) == m_map.end()) {
|
|
return false;
|
|
}
|
|
|
|
// find and removing matching aliases
|
|
for (NameMap::iterator index = m_nameToCanonicalName.begin();
|
|
index != m_nameToCanonicalName.end(); ) {
|
|
if (index->second == canonical && index->first != canonical) {
|
|
m_nameToCanonicalName.erase(index++);
|
|
}
|
|
else {
|
|
++index;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Config::removeAllAliases()
|
|
{
|
|
// remove all names
|
|
m_nameToCanonicalName.clear();
|
|
|
|
// put the canonical names back in
|
|
for (CellMap::iterator index = m_map.begin();
|
|
index != m_map.end(); ++index) {
|
|
m_nameToCanonicalName.insert(
|
|
std::make_pair(index->first, index->first));
|
|
}
|
|
}
|
|
|
|
bool
|
|
Config::connect(const String& srcName,
|
|
EDirection srcSide,
|
|
float srcStart, float srcEnd,
|
|
const String& dstName,
|
|
float dstStart, float dstEnd)
|
|
{
|
|
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
|
|
|
|
// find source cell
|
|
CellMap::iterator index = m_map.find(getCanonicalName(srcName));
|
|
if (index == m_map.end()) {
|
|
return false;
|
|
}
|
|
|
|
// add link
|
|
CellEdge srcEdge(srcSide, Interval(srcStart, srcEnd));
|
|
CellEdge dstEdge(dstName, srcSide, Interval(dstStart, dstEnd));
|
|
return index->second.add(srcEdge, dstEdge);
|
|
}
|
|
|
|
bool
|
|
Config::disconnect(const String& srcName, EDirection srcSide)
|
|
{
|
|
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
|
|
|
|
// find source cell
|
|
CellMap::iterator index = m_map.find(srcName);
|
|
if (index == m_map.end()) {
|
|
return false;
|
|
}
|
|
|
|
// disconnect side
|
|
index->second.remove(srcSide);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Config::disconnect(const String& srcName, EDirection srcSide, float position)
|
|
{
|
|
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
|
|
|
|
// find source cell
|
|
CellMap::iterator index = m_map.find(srcName);
|
|
if (index == m_map.end()) {
|
|
return false;
|
|
}
|
|
|
|
// disconnect side
|
|
index->second.remove(srcSide, position);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Config::setSynergyAddress(const NetworkAddress& addr)
|
|
{
|
|
m_synergyAddress = addr;
|
|
}
|
|
|
|
bool
|
|
Config::addOption(const String& name, OptionID option, OptionValue value)
|
|
{
|
|
// find options
|
|
ScreenOptions* options = NULL;
|
|
if (name.empty()) {
|
|
options = &m_globalOptions;
|
|
}
|
|
else {
|
|
CellMap::iterator index = m_map.find(name);
|
|
if (index != m_map.end()) {
|
|
options = &index->second.m_options;
|
|
}
|
|
}
|
|
if (options == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// add option
|
|
options->insert(std::make_pair(option, value));
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Config::removeOption(const String& name, OptionID option)
|
|
{
|
|
// find options
|
|
ScreenOptions* options = NULL;
|
|
if (name.empty()) {
|
|
options = &m_globalOptions;
|
|
}
|
|
else {
|
|
CellMap::iterator index = m_map.find(name);
|
|
if (index != m_map.end()) {
|
|
options = &index->second.m_options;
|
|
}
|
|
}
|
|
if (options == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// remove option
|
|
options->erase(option);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Config::removeOptions(const String& name)
|
|
{
|
|
// find options
|
|
ScreenOptions* options = NULL;
|
|
if (name.empty()) {
|
|
options = &m_globalOptions;
|
|
}
|
|
else {
|
|
CellMap::iterator index = m_map.find(name);
|
|
if (index != m_map.end()) {
|
|
options = &index->second.m_options;
|
|
}
|
|
}
|
|
if (options == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// remove options
|
|
options->clear();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Config::isValidScreenName(const String& name) const
|
|
{
|
|
// name is valid if matches validname
|
|
// name ::= [_A-Za-z0-9] | [_A-Za-z0-9][-_A-Za-z0-9]*[_A-Za-z0-9]
|
|
// domain ::= . name
|
|
// validname ::= name domain*
|
|
// we also accept names ending in . because many OS X users have
|
|
// so misconfigured their systems.
|
|
|
|
// empty name is invalid
|
|
if (name.empty()) {
|
|
return false;
|
|
}
|
|
|
|
// check each dot separated part
|
|
String::size_type b = 0;
|
|
for (;;) {
|
|
// accept trailing .
|
|
if (b == name.size()) {
|
|
break;
|
|
}
|
|
|
|
// find end of part
|
|
String::size_type e = name.find('.', b);
|
|
if (e == String::npos) {
|
|
e = name.size();
|
|
}
|
|
|
|
// part may not be empty
|
|
if (e - b < 1) {
|
|
return false;
|
|
}
|
|
|
|
// check first and last characters
|
|
if (!(isalnum(name[b]) || name[b] == '_') ||
|
|
!(isalnum(name[e - 1]) || name[e - 1] == '_')) {
|
|
return false;
|
|
}
|
|
|
|
// check interior characters
|
|
for (String::size_type i = b; i < e; ++i) {
|
|
if (!isalnum(name[i]) && name[i] != '_' && name[i] != '-') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// next part
|
|
if (e == name.size()) {
|
|
// no more parts
|
|
break;
|
|
}
|
|
b = e + 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Config::const_iterator
|
|
Config::begin() const
|
|
{
|
|
return const_iterator(m_map.begin());
|
|
}
|
|
|
|
Config::const_iterator
|
|
Config::end() const
|
|
{
|
|
return const_iterator(m_map.end());
|
|
}
|
|
|
|
Config::all_const_iterator
|
|
Config::beginAll() const
|
|
{
|
|
return m_nameToCanonicalName.begin();
|
|
}
|
|
|
|
Config::all_const_iterator
|
|
Config::endAll() const
|
|
{
|
|
return m_nameToCanonicalName.end();
|
|
}
|
|
|
|
bool
|
|
Config::isScreen(const String& name) const
|
|
{
|
|
return (m_nameToCanonicalName.count(name) > 0);
|
|
}
|
|
|
|
bool
|
|
Config::isCanonicalName(const String& name) const
|
|
{
|
|
return (!name.empty() &&
|
|
CaselessCmp::equal(getCanonicalName(name), name));
|
|
}
|
|
|
|
String
|
|
Config::getCanonicalName(const String& name) const
|
|
{
|
|
NameMap::const_iterator index = m_nameToCanonicalName.find(name);
|
|
if (index == m_nameToCanonicalName.end()) {
|
|
return String();
|
|
}
|
|
else {
|
|
return index->second;
|
|
}
|
|
}
|
|
|
|
String
|
|
Config::getNeighbor(const String& srcName, EDirection srcSide,
|
|
float position, float* positionOut) const
|
|
{
|
|
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
|
|
|
|
// find source cell
|
|
CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
|
|
if (index == m_map.end()) {
|
|
return String();
|
|
}
|
|
|
|
// find edge
|
|
const CellEdge* srcEdge, *dstEdge;
|
|
if (!index->second.getLink(srcSide, position, srcEdge, dstEdge)) {
|
|
// no neighbor
|
|
return "";
|
|
}
|
|
else {
|
|
// compute position on neighbor
|
|
if (positionOut != NULL) {
|
|
*positionOut =
|
|
dstEdge->inverseTransform(srcEdge->transform(position));
|
|
}
|
|
|
|
// return neighbor's name
|
|
return getCanonicalName(dstEdge->getName());
|
|
}
|
|
}
|
|
|
|
bool
|
|
Config::hasNeighbor(const String& srcName, EDirection srcSide) const
|
|
{
|
|
return hasNeighbor(srcName, srcSide, 0.0f, 1.0f);
|
|
}
|
|
|
|
bool
|
|
Config::hasNeighbor(const String& srcName, EDirection srcSide,
|
|
float start, float end) const
|
|
{
|
|
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
|
|
|
|
// find source cell
|
|
CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
|
|
if (index == m_map.end()) {
|
|
return false;
|
|
}
|
|
|
|
return index->second.overlaps(CellEdge(srcSide, Interval(start, end)));
|
|
}
|
|
|
|
Config::link_const_iterator
|
|
Config::beginNeighbor(const String& srcName) const
|
|
{
|
|
CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
|
|
assert(index != m_map.end());
|
|
return index->second.begin();
|
|
}
|
|
|
|
Config::link_const_iterator
|
|
Config::endNeighbor(const String& srcName) const
|
|
{
|
|
CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
|
|
assert(index != m_map.end());
|
|
return index->second.end();
|
|
}
|
|
|
|
const NetworkAddress&
|
|
Config::getSynergyAddress() const
|
|
{
|
|
return m_synergyAddress;
|
|
}
|
|
|
|
const Config::ScreenOptions*
|
|
Config::getOptions(const String& name) const
|
|
{
|
|
// find options
|
|
const ScreenOptions* options = NULL;
|
|
if (name.empty()) {
|
|
options = &m_globalOptions;
|
|
}
|
|
else {
|
|
CellMap::const_iterator index = m_map.find(name);
|
|
if (index != m_map.end()) {
|
|
options = &index->second.m_options;
|
|
}
|
|
}
|
|
|
|
// return options
|
|
return options;
|
|
}
|
|
|
|
bool
|
|
Config::hasLockToScreenAction() const
|
|
{
|
|
return m_hasLockToScreenAction;
|
|
}
|
|
|
|
bool
|
|
Config::operator==(const Config& x) const
|
|
{
|
|
if (m_synergyAddress != x.m_synergyAddress) {
|
|
return false;
|
|
}
|
|
if (m_map.size() != x.m_map.size()) {
|
|
return false;
|
|
}
|
|
if (m_nameToCanonicalName.size() != x.m_nameToCanonicalName.size()) {
|
|
return false;
|
|
}
|
|
|
|
// compare global options
|
|
if (m_globalOptions != x.m_globalOptions) {
|
|
return false;
|
|
}
|
|
|
|
auto index2map = x.m_map.cbegin();
|
|
for (auto const &index1: m_map) {
|
|
// compare names
|
|
if (!CaselessCmp::equal(index1.first, index2map->first)) {
|
|
return false;
|
|
}
|
|
|
|
// compare cells
|
|
if (index1.second != index2map->second) {
|
|
return false;
|
|
}
|
|
++index2map;
|
|
}
|
|
|
|
auto index2 = x.m_nameToCanonicalName.cbegin();
|
|
for (auto const &index1: m_nameToCanonicalName) {
|
|
if (index2 == x.m_nameToCanonicalName.cend()) {
|
|
return false; // second source ended
|
|
}
|
|
if (!CaselessCmp::equal(index1.first, index2->first) ||
|
|
!CaselessCmp::equal(index1.second, index2->second)) {
|
|
return false;
|
|
}
|
|
++index2;
|
|
}
|
|
|
|
// compare input filters
|
|
if (m_inputFilter != x.m_inputFilter) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Config::operator!=(const Config& x) const
|
|
{
|
|
return !operator==(x);
|
|
}
|
|
|
|
void
|
|
Config::read(ConfigReadContext& context)
|
|
{
|
|
Config tmp(m_events);
|
|
while (context.getStream()) {
|
|
tmp.readSection(context);
|
|
}
|
|
*this = tmp;
|
|
}
|
|
|
|
const char*
|
|
Config::dirName(EDirection dir)
|
|
{
|
|
static const char* s_name[] = { "left", "right", "up", "down" };
|
|
|
|
assert(dir >= kFirstDirection && dir <= kLastDirection);
|
|
|
|
return s_name[dir - kFirstDirection];
|
|
}
|
|
|
|
InputFilter*
|
|
Config::getInputFilter()
|
|
{
|
|
return &m_inputFilter;
|
|
}
|
|
|
|
String
|
|
Config::formatInterval(const Interval& x)
|
|
{
|
|
if (x.first == 0.0f && x.second == 1.0f) {
|
|
return "";
|
|
}
|
|
return synergy::string::sprintf("(%d,%d)", (int)(x.first * 100.0f + 0.5f),
|
|
(int)(x.second * 100.0f + 0.5f));
|
|
}
|
|
|
|
void
|
|
Config::readSection(ConfigReadContext& s)
|
|
{
|
|
static const char s_section[] = "section:";
|
|
static const char s_options[] = "options";
|
|
static const char s_screens[] = "screens";
|
|
static const char s_links[] = "links";
|
|
static const char s_aliases[] = "aliases";
|
|
|
|
String line;
|
|
if (!s.readLine(line)) {
|
|
// no more sections
|
|
return;
|
|
}
|
|
|
|
// should be a section header
|
|
if (line.find(s_section) != 0) {
|
|
throw XConfigRead(s, "found data outside section");
|
|
}
|
|
|
|
// get section name
|
|
String::size_type i = line.find_first_not_of(" \t", sizeof(s_section) - 1);
|
|
if (i == String::npos) {
|
|
throw XConfigRead(s, "section name is missing");
|
|
}
|
|
String name = line.substr(i);
|
|
i = name.find_first_of(" \t");
|
|
if (i != String::npos) {
|
|
throw XConfigRead(s, "unexpected data after section name");
|
|
}
|
|
|
|
// read section
|
|
if (name == s_options) {
|
|
readSectionOptions(s);
|
|
}
|
|
else if (name == s_screens) {
|
|
readSectionScreens(s);
|
|
}
|
|
else if (name == s_links) {
|
|
readSectionLinks(s);
|
|
}
|
|
else if (name == s_aliases) {
|
|
readSectionAliases(s);
|
|
}
|
|
else {
|
|
throw XConfigRead(s, "unknown section name \"%{1}\"", name);
|
|
}
|
|
}
|
|
|
|
void
|
|
Config::readSectionOptions(ConfigReadContext& s)
|
|
{
|
|
String line;
|
|
while (s.readLine(line)) {
|
|
// check for end of section
|
|
if (line == "end") {
|
|
return;
|
|
}
|
|
|
|
// parse argument: `nameAndArgs = [values][;[values]]'
|
|
// nameAndArgs := <name>[(arg[,...])]
|
|
// values := valueAndArgs[,valueAndArgs]...
|
|
// valueAndArgs := <value>[(arg[,...])]
|
|
String::size_type i = 0;
|
|
String name, value;
|
|
ConfigReadContext::ArgList nameArgs, valueArgs;
|
|
s.parseNameWithArgs("name", line, "=", i, name, nameArgs);
|
|
++i;
|
|
s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs);
|
|
|
|
bool handled = true;
|
|
if (name == "address") {
|
|
try {
|
|
m_synergyAddress = NetworkAddress(value, kDefaultPort);
|
|
m_synergyAddress.resolve();
|
|
}
|
|
catch (XSocketAddress& e) {
|
|
throw XConfigRead(s,
|
|
String("invalid address argument ") + e.what());
|
|
}
|
|
}
|
|
else if (name == "heartbeat") {
|
|
addOption("", kOptionHeartbeat, s.parseInt(value));
|
|
}
|
|
else if (name == "switchCorners") {
|
|
addOption("", kOptionScreenSwitchCorners, s.parseCorners(value));
|
|
}
|
|
else if (name == "switchCornerSize") {
|
|
addOption("", kOptionScreenSwitchCornerSize, s.parseInt(value));
|
|
}
|
|
else if (name == "switchDelay") {
|
|
addOption("", kOptionScreenSwitchDelay, s.parseInt(value));
|
|
}
|
|
else if (name == "switchDoubleTap") {
|
|
addOption("", kOptionScreenSwitchTwoTap, s.parseInt(value));
|
|
}
|
|
else if (name == "switchNeedsShift") {
|
|
addOption("", kOptionScreenSwitchNeedsShift, s.parseBoolean(value));
|
|
}
|
|
else if (name == "switchNeedsControl") {
|
|
addOption("", kOptionScreenSwitchNeedsControl, s.parseBoolean(value));
|
|
}
|
|
else if (name == "switchNeedsAlt") {
|
|
addOption("", kOptionScreenSwitchNeedsAlt, s.parseBoolean(value));
|
|
}
|
|
else if (name == "screenSaverSync") {
|
|
addOption("", kOptionScreenSaverSync, s.parseBoolean(value));
|
|
}
|
|
else if (name == "relativeMouseMoves") {
|
|
addOption("", kOptionRelativeMouseMoves, s.parseBoolean(value));
|
|
}
|
|
else if (name == "win32KeepForeground") {
|
|
addOption("", kOptionWin32KeepForeground, s.parseBoolean(value));
|
|
}
|
|
else if (name == "disableLockToScreen") {
|
|
addOption("", kOptionDisableLockToScreen, s.parseBoolean(value));
|
|
}
|
|
else if (name == "clipboardSharing") {
|
|
addOption("", kOptionClipboardSharing, s.parseBoolean(value));
|
|
}
|
|
else if (name == "clipboardSharingSize") {
|
|
addOption("", kOptionClipboardSharingSize, s.parseInt(value));
|
|
}
|
|
else {
|
|
handled = false;
|
|
}
|
|
|
|
if (handled) {
|
|
// make sure handled options aren't followed by more values
|
|
if (i < line.size() && (line[i] == ',' || line[i] == ';')) {
|
|
throw XConfigRead(s, "to many arguments to %s", name.c_str());
|
|
}
|
|
}
|
|
else {
|
|
// make filter rule
|
|
InputFilter::Rule rule(parseCondition(s, name, nameArgs));
|
|
|
|
// save first action (if any)
|
|
if (!value.empty() || line[i] != ';') {
|
|
parseAction(s, value, valueArgs, rule, true);
|
|
}
|
|
|
|
// get remaining activate actions
|
|
while (i < line.length() && line[i] != ';') {
|
|
++i;
|
|
s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs);
|
|
parseAction(s, value, valueArgs, rule, true);
|
|
}
|
|
|
|
// get deactivate actions
|
|
if (i < line.length() && line[i] == ';') {
|
|
// allow trailing ';'
|
|
i = line.find_first_not_of(" \t", i + 1);
|
|
if (i == String::npos) {
|
|
i = line.length();
|
|
}
|
|
else {
|
|
--i;
|
|
}
|
|
|
|
// get actions
|
|
while (i < line.length()) {
|
|
++i;
|
|
s.parseNameWithArgs("value", line, ",\n",
|
|
i, value, valueArgs);
|
|
parseAction(s, value, valueArgs, rule, false);
|
|
}
|
|
}
|
|
|
|
// add rule
|
|
m_inputFilter.addFilterRule(rule);
|
|
}
|
|
}
|
|
throw XConfigRead(s, "unexpected end of options section");
|
|
}
|
|
|
|
void
|
|
Config::readSectionScreens(ConfigReadContext& s)
|
|
{
|
|
String line;
|
|
String screen;
|
|
while (s.readLine(line)) {
|
|
// check for end of section
|
|
if (line == "end") {
|
|
return;
|
|
}
|
|
|
|
// see if it's the next screen
|
|
if (line[line.size() - 1] == ':') {
|
|
// strip :
|
|
screen = line.substr(0, line.size() - 1);
|
|
|
|
// verify validity of screen name
|
|
if (!isValidScreenName(screen)) {
|
|
throw XConfigRead(s, "invalid screen name \"%{1}\"", screen);
|
|
}
|
|
|
|
// add the screen to the configuration
|
|
if (!addScreen(screen)) {
|
|
throw XConfigRead(s, "duplicate screen name \"%{1}\"", screen);
|
|
}
|
|
}
|
|
else if (screen.empty()) {
|
|
throw XConfigRead(s, "argument before first screen");
|
|
}
|
|
else {
|
|
// parse argument: `<name>=<value>'
|
|
String::size_type i = line.find_first_of(" \t=");
|
|
if (i == 0) {
|
|
throw XConfigRead(s, "missing argument name");
|
|
}
|
|
if (i == String::npos) {
|
|
throw XConfigRead(s, "missing =");
|
|
}
|
|
String name = line.substr(0, i);
|
|
i = line.find_first_not_of(" \t", i);
|
|
if (i == String::npos || line[i] != '=') {
|
|
throw XConfigRead(s, "missing =");
|
|
}
|
|
i = line.find_first_not_of(" \t", i + 1);
|
|
String value;
|
|
if (i != String::npos) {
|
|
value = line.substr(i);
|
|
}
|
|
|
|
// handle argument
|
|
if (name == "halfDuplexCapsLock") {
|
|
addOption(screen, kOptionHalfDuplexCapsLock,
|
|
s.parseBoolean(value));
|
|
}
|
|
else if (name == "halfDuplexNumLock") {
|
|
addOption(screen, kOptionHalfDuplexNumLock,
|
|
s.parseBoolean(value));
|
|
}
|
|
else if (name == "halfDuplexScrollLock") {
|
|
addOption(screen, kOptionHalfDuplexScrollLock,
|
|
s.parseBoolean(value));
|
|
}
|
|
else if (name == "shift") {
|
|
addOption(screen, kOptionModifierMapForShift,
|
|
s.parseModifierKey(value));
|
|
}
|
|
else if (name == "ctrl") {
|
|
addOption(screen, kOptionModifierMapForControl,
|
|
s.parseModifierKey(value));
|
|
}
|
|
else if (name == "alt") {
|
|
addOption(screen, kOptionModifierMapForAlt,
|
|
s.parseModifierKey(value));
|
|
}
|
|
else if (name == "altgr") {
|
|
addOption(screen, kOptionModifierMapForAltGr,
|
|
s.parseModifierKey(value));
|
|
}
|
|
else if (name == "meta") {
|
|
addOption(screen, kOptionModifierMapForMeta,
|
|
s.parseModifierKey(value));
|
|
}
|
|
else if (name == "super") {
|
|
addOption(screen, kOptionModifierMapForSuper,
|
|
s.parseModifierKey(value));
|
|
}
|
|
else if (name == "xtestIsXineramaUnaware") {
|
|
addOption(screen, kOptionXTestXineramaUnaware,
|
|
s.parseBoolean(value));
|
|
}
|
|
else if (name == "switchCorners") {
|
|
addOption(screen, kOptionScreenSwitchCorners,
|
|
s.parseCorners(value));
|
|
}
|
|
else if (name == "switchCornerSize") {
|
|
addOption(screen, kOptionScreenSwitchCornerSize,
|
|
s.parseInt(value));
|
|
}
|
|
else if (name == "preserveFocus") {
|
|
addOption(screen, kOptionScreenPreserveFocus,
|
|
s.parseBoolean(value));
|
|
}
|
|
else {
|
|
// unknown argument
|
|
throw XConfigRead(s, "unknown argument \"%{1}\"", name);
|
|
}
|
|
}
|
|
}
|
|
throw XConfigRead(s, "unexpected end of screens section");
|
|
}
|
|
|
|
void
|
|
Config::readSectionLinks(ConfigReadContext& s)
|
|
{
|
|
String line;
|
|
String screen;
|
|
while (s.readLine(line)) {
|
|
// check for end of section
|
|
if (line == "end") {
|
|
return;
|
|
}
|
|
|
|
// see if it's the next screen
|
|
if (line[line.size() - 1] == ':') {
|
|
// strip :
|
|
screen = line.substr(0, line.size() - 1);
|
|
|
|
// verify we know about the screen
|
|
if (!isScreen(screen)) {
|
|
throw XConfigRead(s, "unknown screen name \"%{1}\"", screen);
|
|
}
|
|
if (!isCanonicalName(screen)) {
|
|
throw XConfigRead(s, "cannot use screen name alias here");
|
|
}
|
|
}
|
|
else if (screen.empty()) {
|
|
throw XConfigRead(s, "argument before first screen");
|
|
}
|
|
else {
|
|
// parse argument: `<name>[(<s0>,<e0>)]=<value>[(<s1>,<e1>)]'
|
|
// the stuff in brackets is optional. interval values must be
|
|
// in the range [0,100] and start < end. if not given the
|
|
// interval is taken to be (0,100).
|
|
String::size_type i = 0;
|
|
String side, dstScreen, srcArgString, dstArgString;
|
|
ConfigReadContext::ArgList srcArgs, dstArgs;
|
|
s.parseNameWithArgs("link", line, "=", i, side, srcArgs);
|
|
++i;
|
|
s.parseNameWithArgs("screen", line, "", i, dstScreen, dstArgs);
|
|
Interval srcInterval(s.parseInterval(srcArgs));
|
|
Interval dstInterval(s.parseInterval(dstArgs));
|
|
|
|
// handle argument
|
|
EDirection dir;
|
|
if (side == "left") {
|
|
dir = kLeft;
|
|
}
|
|
else if (side == "right") {
|
|
dir = kRight;
|
|
}
|
|
else if (side == "up") {
|
|
dir = kTop;
|
|
}
|
|
else if (side == "down") {
|
|
dir = kBottom;
|
|
}
|
|
else {
|
|
// unknown argument
|
|
throw XConfigRead(s, "unknown side \"%{1}\" in link", side);
|
|
}
|
|
if (!isScreen(dstScreen)) {
|
|
throw XConfigRead(s, "unknown screen name \"%{1}\"", dstScreen);
|
|
}
|
|
if (!connect(screen, dir,
|
|
srcInterval.first, srcInterval.second,
|
|
dstScreen,
|
|
dstInterval.first, dstInterval.second)) {
|
|
throw XConfigRead(s, "overlapping range");
|
|
}
|
|
}
|
|
}
|
|
throw XConfigRead(s, "unexpected end of links section");
|
|
}
|
|
|
|
void
|
|
Config::readSectionAliases(ConfigReadContext& s)
|
|
{
|
|
String line;
|
|
String screen;
|
|
while (s.readLine(line)) {
|
|
// check for end of section
|
|
if (line == "end") {
|
|
return;
|
|
}
|
|
|
|
// see if it's the next screen
|
|
if (line[line.size() - 1] == ':') {
|
|
// strip :
|
|
screen = line.substr(0, line.size() - 1);
|
|
|
|
// verify we know about the screen
|
|
if (!isScreen(screen)) {
|
|
throw XConfigRead(s, "unknown screen name \"%{1}\"", screen);
|
|
}
|
|
if (!isCanonicalName(screen)) {
|
|
throw XConfigRead(s, "cannot use screen name alias here");
|
|
}
|
|
}
|
|
else if (screen.empty()) {
|
|
throw XConfigRead(s, "argument before first screen");
|
|
}
|
|
else {
|
|
// verify validity of screen name
|
|
if (!isValidScreenName(line)) {
|
|
throw XConfigRead(s, "invalid screen alias \"%{1}\"", line);
|
|
}
|
|
|
|
// add alias
|
|
if (!addAlias(screen, line)) {
|
|
throw XConfigRead(s, "alias \"%{1}\" is already used", line);
|
|
}
|
|
}
|
|
}
|
|
throw XConfigRead(s, "unexpected end of aliases section");
|
|
}
|
|
|
|
|
|
InputFilter::Condition*
|
|
Config::parseCondition(ConfigReadContext& s,
|
|
const String& name, const std::vector<String>& args)
|
|
{
|
|
if (name == "keystroke") {
|
|
if (args.size() != 1) {
|
|
throw XConfigRead(s, "syntax for condition: keystroke(modifiers+key)");
|
|
}
|
|
|
|
IPlatformScreen::KeyInfo* keyInfo = s.parseKeystroke(args[0]);
|
|
|
|
return new InputFilter::KeystrokeCondition(m_events, keyInfo);
|
|
}
|
|
|
|
if (name == "mousebutton") {
|
|
if (args.size() != 1) {
|
|
throw XConfigRead(s, "syntax for condition: mousebutton(modifiers+button)");
|
|
}
|
|
|
|
IPlatformScreen::ButtonInfo* mouseInfo = s.parseMouse(args[0]);
|
|
|
|
return new InputFilter::MouseButtonCondition(m_events, mouseInfo);
|
|
}
|
|
|
|
if (name == "connect") {
|
|
if (args.size() != 1) {
|
|
throw XConfigRead(s, "syntax for condition: connect([screen])");
|
|
}
|
|
|
|
String screen = args[0];
|
|
if (isScreen(screen)) {
|
|
screen = getCanonicalName(screen);
|
|
}
|
|
else if (!screen.empty()) {
|
|
throw XConfigRead(s, "unknown screen name \"%{1}\" in connect", screen);
|
|
}
|
|
|
|
return new InputFilter::ScreenConnectedCondition(m_events, screen);
|
|
}
|
|
|
|
throw XConfigRead(s, "unknown argument \"%{1}\"", name);
|
|
}
|
|
|
|
void
|
|
Config::parseAction(ConfigReadContext& s,
|
|
const String& name, const std::vector<String>& args,
|
|
InputFilter::Rule& rule, bool activate)
|
|
{
|
|
InputFilter::Action* action;
|
|
|
|
if (name == "keystroke" || name == "keyDown" || name == "keyUp") {
|
|
if (args.size() < 1 || args.size() > 2) {
|
|
throw XConfigRead(s, "syntax for action: keystroke(modifiers+key[,screens])");
|
|
}
|
|
|
|
IPlatformScreen::KeyInfo* keyInfo;
|
|
if (args.size() == 1) {
|
|
keyInfo = s.parseKeystroke(args[0]);
|
|
}
|
|
else {
|
|
std::set<String> screens;
|
|
parseScreens(s, args[1], screens);
|
|
keyInfo = s.parseKeystroke(args[0], screens);
|
|
}
|
|
|
|
if (name == "keystroke") {
|
|
IPlatformScreen::KeyInfo* keyInfo2 =
|
|
IKeyState::KeyInfo::alloc(*keyInfo);
|
|
action = new InputFilter::KeystrokeAction(m_events, keyInfo2, true);
|
|
rule.adoptAction(action, true);
|
|
action = new InputFilter::KeystrokeAction(m_events, keyInfo, false);
|
|
activate = false;
|
|
}
|
|
else if (name == "keyDown") {
|
|
action = new InputFilter::KeystrokeAction(m_events, keyInfo, true);
|
|
}
|
|
else {
|
|
action = new InputFilter::KeystrokeAction(m_events, keyInfo, false);
|
|
}
|
|
}
|
|
|
|
else if (name == "mousebutton" ||
|
|
name == "mouseDown" || name == "mouseUp") {
|
|
if (args.size() != 1) {
|
|
throw XConfigRead(s, "syntax for action: mousebutton(modifiers+button)");
|
|
}
|
|
|
|
IPlatformScreen::ButtonInfo* mouseInfo = s.parseMouse(args[0]);
|
|
|
|
if (name == "mousebutton") {
|
|
IPlatformScreen::ButtonInfo* mouseInfo2 =
|
|
IPlatformScreen::ButtonInfo::alloc(*mouseInfo);
|
|
action = new InputFilter::MouseButtonAction(m_events, mouseInfo2, true);
|
|
rule.adoptAction(action, true);
|
|
action = new InputFilter::MouseButtonAction(m_events, mouseInfo, false);
|
|
activate = false;
|
|
}
|
|
else if (name == "mouseDown") {
|
|
action = new InputFilter::MouseButtonAction(m_events, mouseInfo, true);
|
|
}
|
|
else {
|
|
action = new InputFilter::MouseButtonAction(m_events, mouseInfo, false);
|
|
}
|
|
}
|
|
|
|
/* XXX -- not supported
|
|
else if (name == "modifier") {
|
|
if (args.size() != 1) {
|
|
throw XConfigRead(s, "syntax for action: modifier(modifiers)");
|
|
}
|
|
|
|
KeyModifierMask mask = s.parseModifier(args[0]);
|
|
|
|
action = new InputFilter::ModifierAction(mask, ~mask);
|
|
}
|
|
*/
|
|
|
|
else if (name == "switchToScreen") {
|
|
if (args.size() != 1) {
|
|
throw XConfigRead(s, "syntax for action: switchToScreen(name)");
|
|
}
|
|
|
|
String screen = args[0];
|
|
if (isScreen(screen)) {
|
|
screen = getCanonicalName(screen);
|
|
}
|
|
else if (!screen.empty()) {
|
|
throw XConfigRead(s, "unknown screen name in switchToScreen");
|
|
}
|
|
|
|
action = new InputFilter::SwitchToScreenAction(m_events, screen);
|
|
}
|
|
|
|
else if (name == "switchInDirection") {
|
|
if (args.size() != 1) {
|
|
throw XConfigRead(s, "syntax for action: switchInDirection(<left|right|up|down>)");
|
|
}
|
|
|
|
EDirection direction;
|
|
if (args[0] == "left") {
|
|
direction = kLeft;
|
|
}
|
|
else if (args[0] == "right") {
|
|
direction = kRight;
|
|
}
|
|
else if (args[0] == "up") {
|
|
direction = kTop;
|
|
}
|
|
else if (args[0] == "down") {
|
|
direction = kBottom;
|
|
}
|
|
else {
|
|
throw XConfigRead(s, "unknown direction \"%{1}\" in switchToScreen", args[0]);
|
|
}
|
|
|
|
action = new InputFilter::SwitchInDirectionAction(m_events, direction);
|
|
}
|
|
|
|
else if (name == "lockCursorToScreen") {
|
|
if (args.size() > 1) {
|
|
throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])");
|
|
}
|
|
|
|
InputFilter::LockCursorToScreenAction::Mode mode =
|
|
InputFilter::LockCursorToScreenAction::kToggle;
|
|
if (args.size() == 1) {
|
|
if (args[0] == "off") {
|
|
mode = InputFilter::LockCursorToScreenAction::kOff;
|
|
}
|
|
else if (args[0] == "on") {
|
|
mode = InputFilter::LockCursorToScreenAction::kOn;
|
|
}
|
|
else if (args[0] == "toggle") {
|
|
mode = InputFilter::LockCursorToScreenAction::kToggle;
|
|
}
|
|
else {
|
|
throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])");
|
|
}
|
|
}
|
|
|
|
if (mode != InputFilter::LockCursorToScreenAction::kOff) {
|
|
m_hasLockToScreenAction = true;
|
|
}
|
|
|
|
action = new InputFilter::LockCursorToScreenAction(m_events, mode);
|
|
}
|
|
|
|
else if (name == "restartServer") {
|
|
if (args.size() > 1) {
|
|
throw XConfigRead(s, "syntax for action: restartServer([{{restart}}])");
|
|
}
|
|
|
|
InputFilter::RestartServer::Mode mode =
|
|
InputFilter::RestartServer::restart;
|
|
|
|
if (args.size() == 1) {
|
|
if (args[0] == "restart") {
|
|
mode = InputFilter::RestartServer::restart;
|
|
}
|
|
else {
|
|
throw XConfigRead(s, "syntax for action: restartServer([{restart}])");
|
|
}
|
|
}
|
|
|
|
action = new InputFilter::RestartServer(m_events, mode);
|
|
}
|
|
|
|
else if (name == "keyboardBroadcast") {
|
|
if (args.size() > 2) {
|
|
throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])");
|
|
}
|
|
|
|
InputFilter::KeyboardBroadcastAction::Mode mode =
|
|
InputFilter::KeyboardBroadcastAction::kToggle;
|
|
if (args.size() >= 1) {
|
|
if (args[0] == "off") {
|
|
mode = InputFilter::KeyboardBroadcastAction::kOff;
|
|
}
|
|
else if (args[0] == "on") {
|
|
mode = InputFilter::KeyboardBroadcastAction::kOn;
|
|
}
|
|
else if (args[0] == "toggle") {
|
|
mode = InputFilter::KeyboardBroadcastAction::kToggle;
|
|
}
|
|
else {
|
|
throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])");
|
|
}
|
|
}
|
|
|
|
std::set<String> screens;
|
|
if (args.size() >= 2) {
|
|
parseScreens(s, args[1], screens);
|
|
}
|
|
|
|
action = new InputFilter::KeyboardBroadcastAction(m_events, mode, screens);
|
|
}
|
|
|
|
else {
|
|
throw XConfigRead(s, "unknown action argument \"%{1}\"", name);
|
|
}
|
|
|
|
rule.adoptAction(action, activate);
|
|
}
|
|
|
|
void
|
|
Config::parseScreens(ConfigReadContext& c,
|
|
const String& s, std::set<String>& screens) const
|
|
{
|
|
screens.clear();
|
|
|
|
String::size_type i = 0;
|
|
while (i < s.size()) {
|
|
// find end of next screen name
|
|
String::size_type j = s.find(':', i);
|
|
if (j == String::npos) {
|
|
j = s.size();
|
|
}
|
|
|
|
// extract name
|
|
String rawName;
|
|
i = s.find_first_not_of(" \t", i);
|
|
if (i < j) {
|
|
rawName = s.substr(i, s.find_last_not_of(" \t", j - 1) - i + 1);
|
|
}
|
|
|
|
// add name
|
|
if (rawName == "*") {
|
|
screens.insert("*");
|
|
}
|
|
else if (!rawName.empty()) {
|
|
String name = getCanonicalName(rawName);
|
|
if (name.empty()) {
|
|
throw XConfigRead(c, "unknown screen name \"%{1}\"", rawName);
|
|
}
|
|
screens.insert(name);
|
|
}
|
|
|
|
// next
|
|
i = j + 1;
|
|
}
|
|
}
|
|
|
|
const char*
|
|
Config::getOptionName(OptionID id)
|
|
{
|
|
if (id == kOptionHalfDuplexCapsLock) {
|
|
return "halfDuplexCapsLock";
|
|
}
|
|
if (id == kOptionHalfDuplexNumLock) {
|
|
return "halfDuplexNumLock";
|
|
}
|
|
if (id == kOptionHalfDuplexScrollLock) {
|
|
return "halfDuplexScrollLock";
|
|
}
|
|
if (id == kOptionModifierMapForShift) {
|
|
return "shift";
|
|
}
|
|
if (id == kOptionModifierMapForControl) {
|
|
return "ctrl";
|
|
}
|
|
if (id == kOptionModifierMapForAlt) {
|
|
return "alt";
|
|
}
|
|
if (id == kOptionModifierMapForAltGr) {
|
|
return "altgr";
|
|
}
|
|
if (id == kOptionModifierMapForMeta) {
|
|
return "meta";
|
|
}
|
|
if (id == kOptionModifierMapForSuper) {
|
|
return "super";
|
|
}
|
|
if (id == kOptionHeartbeat) {
|
|
return "heartbeat";
|
|
}
|
|
if (id == kOptionScreenSwitchCorners) {
|
|
return "switchCorners";
|
|
}
|
|
if (id == kOptionScreenSwitchCornerSize) {
|
|
return "switchCornerSize";
|
|
}
|
|
if (id == kOptionScreenSwitchDelay) {
|
|
return "switchDelay";
|
|
}
|
|
if (id == kOptionScreenSwitchTwoTap) {
|
|
return "switchDoubleTap";
|
|
}
|
|
if (id == kOptionScreenSwitchNeedsShift) {
|
|
return "switchNeedsShift";
|
|
}
|
|
if (id == kOptionScreenSwitchNeedsControl) {
|
|
return "switchNeedsControl";
|
|
}
|
|
if (id == kOptionScreenSwitchNeedsAlt) {
|
|
return "switchNeedsAlt";
|
|
}
|
|
if (id == kOptionScreenSaverSync) {
|
|
return "screenSaverSync";
|
|
}
|
|
if (id == kOptionXTestXineramaUnaware) {
|
|
return "xtestIsXineramaUnaware";
|
|
}
|
|
if (id == kOptionRelativeMouseMoves) {
|
|
return "relativeMouseMoves";
|
|
}
|
|
if (id == kOptionWin32KeepForeground) {
|
|
return "win32KeepForeground";
|
|
}
|
|
if (id == kOptionScreenPreserveFocus) {
|
|
return "preserveFocus";
|
|
}
|
|
if (id == kOptionDisableLockToScreen) {
|
|
return "disableLockToScreen";
|
|
}
|
|
if (id == kOptionClipboardSharing) {
|
|
return "clipboardSharing";
|
|
}
|
|
if (id == kOptionClipboardSharingSize) {
|
|
return "clipboardSharingSize";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
String
|
|
Config::getOptionValue(OptionID id, OptionValue value)
|
|
{
|
|
if (id == kOptionHalfDuplexCapsLock ||
|
|
id == kOptionHalfDuplexNumLock ||
|
|
id == kOptionHalfDuplexScrollLock ||
|
|
id == kOptionScreenSwitchNeedsShift ||
|
|
id == kOptionScreenSwitchNeedsControl ||
|
|
id == kOptionScreenSwitchNeedsAlt ||
|
|
id == kOptionScreenSaverSync ||
|
|
id == kOptionXTestXineramaUnaware ||
|
|
id == kOptionRelativeMouseMoves ||
|
|
id == kOptionWin32KeepForeground ||
|
|
id == kOptionScreenPreserveFocus ||
|
|
id == kOptionClipboardSharing ||
|
|
id == kOptionClipboardSharingSize) {
|
|
return (value != 0) ? "true" : "false";
|
|
}
|
|
if (id == kOptionModifierMapForShift ||
|
|
id == kOptionModifierMapForControl ||
|
|
id == kOptionModifierMapForAlt ||
|
|
id == kOptionModifierMapForAltGr ||
|
|
id == kOptionModifierMapForMeta ||
|
|
id == kOptionModifierMapForSuper) {
|
|
switch (value) {
|
|
case kKeyModifierIDShift:
|
|
return "shift";
|
|
|
|
case kKeyModifierIDControl:
|
|
return "ctrl";
|
|
|
|
case kKeyModifierIDAlt:
|
|
return "alt";
|
|
|
|
case kKeyModifierIDAltGr:
|
|
return "altgr";
|
|
|
|
case kKeyModifierIDMeta:
|
|
return "meta";
|
|
|
|
case kKeyModifierIDSuper:
|
|
return "super";
|
|
|
|
default:
|
|
return "none";
|
|
}
|
|
}
|
|
if (id == kOptionHeartbeat ||
|
|
id == kOptionScreenSwitchCornerSize ||
|
|
id == kOptionScreenSwitchDelay ||
|
|
id == kOptionScreenSwitchTwoTap) {
|
|
return synergy::string::sprintf("%d", value);
|
|
}
|
|
if (id == kOptionScreenSwitchCorners) {
|
|
std::string result("none");
|
|
if ((value & kTopLeftMask) != 0) {
|
|
result += " +top-left";
|
|
}
|
|
if ((value & kTopRightMask) != 0) {
|
|
result += " +top-right";
|
|
}
|
|
if ((value & kBottomLeftMask) != 0) {
|
|
result += " +bottom-left";
|
|
}
|
|
if ((value & kBottomRightMask) != 0) {
|
|
result += " +bottom-right";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
|
|
//
|
|
// Config::Name
|
|
//
|
|
|
|
Config::Name::Name(Config* config, const String& name) :
|
|
m_config(config),
|
|
m_name(config->getCanonicalName(name))
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
bool
|
|
Config::Name::operator==(const String& name) const
|
|
{
|
|
String canonical = m_config->getCanonicalName(name);
|
|
return CaselessCmp::equal(canonical, m_name);
|
|
}
|
|
|
|
|
|
//
|
|
// Config::CellEdge
|
|
//
|
|
|
|
Config::CellEdge::CellEdge(EDirection side, float position)
|
|
{
|
|
init("", side, Interval(position, position));
|
|
}
|
|
|
|
Config::CellEdge::CellEdge(EDirection side, const Interval& interval)
|
|
{
|
|
assert(interval.first >= 0.0f);
|
|
assert(interval.second <= 1.0f);
|
|
assert(interval.first < interval.second);
|
|
|
|
init("", side, interval);
|
|
}
|
|
|
|
Config::CellEdge::CellEdge(const String& name,
|
|
EDirection side, const Interval& interval)
|
|
{
|
|
assert(interval.first >= 0.0f);
|
|
assert(interval.second <= 1.0f);
|
|
assert(interval.first < interval.second);
|
|
|
|
init(name, side, interval);
|
|
}
|
|
|
|
Config::CellEdge::~CellEdge()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void
|
|
Config::CellEdge::init(const String& name, EDirection side,
|
|
const Interval& interval)
|
|
{
|
|
assert(side != kNoDirection);
|
|
|
|
m_name = name;
|
|
m_side = side;
|
|
m_interval = interval;
|
|
}
|
|
|
|
Config::Interval
|
|
Config::CellEdge::getInterval() const
|
|
{
|
|
return m_interval;
|
|
}
|
|
|
|
void
|
|
Config::CellEdge::setName(const String& newName)
|
|
{
|
|
m_name = newName;
|
|
}
|
|
|
|
String
|
|
Config::CellEdge::getName() const
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
EDirection
|
|
Config::CellEdge::getSide() const
|
|
{
|
|
return m_side;
|
|
}
|
|
|
|
bool
|
|
Config::CellEdge::overlaps(const CellEdge& edge) const
|
|
{
|
|
const Interval& x = m_interval;
|
|
const Interval& y = edge.m_interval;
|
|
if (m_side != edge.m_side) {
|
|
return false;
|
|
}
|
|
return (x.first >= y.first && x.first < y.second) ||
|
|
(x.second > y.first && x.second <= y.second) ||
|
|
(y.first >= x.first && y.first < x.second) ||
|
|
(y.second > x.first && y.second <= x.second);
|
|
}
|
|
|
|
bool
|
|
Config::CellEdge::isInside(float x) const
|
|
{
|
|
return (x >= m_interval.first && x < m_interval.second);
|
|
}
|
|
|
|
float
|
|
Config::CellEdge::transform(float x) const
|
|
{
|
|
return (x - m_interval.first) / (m_interval.second - m_interval.first);
|
|
}
|
|
|
|
|
|
float
|
|
Config::CellEdge::inverseTransform(float x) const
|
|
{
|
|
return x * (m_interval.second - m_interval.first) + m_interval.first;
|
|
}
|
|
|
|
bool
|
|
Config::CellEdge::operator<(const CellEdge& o) const
|
|
{
|
|
if (static_cast<int>(m_side) < static_cast<int>(o.m_side)) {
|
|
return true;
|
|
}
|
|
else if (static_cast<int>(m_side) > static_cast<int>(o.m_side)) {
|
|
return false;
|
|
}
|
|
|
|
return (m_interval.first < o.m_interval.first);
|
|
}
|
|
|
|
bool
|
|
Config::CellEdge::operator==(const CellEdge& x) const
|
|
{
|
|
return (m_side == x.m_side && m_interval == x.m_interval);
|
|
}
|
|
|
|
bool
|
|
Config::CellEdge::operator!=(const CellEdge& x) const
|
|
{
|
|
return !operator==(x);
|
|
}
|
|
|
|
|
|
//
|
|
// Config::Cell
|
|
//
|
|
|
|
bool
|
|
Config::Cell::add(const CellEdge& src, const CellEdge& dst)
|
|
{
|
|
// cannot add an edge that overlaps other existing edges but we
|
|
// can exactly replace an edge.
|
|
if (!hasEdge(src) && overlaps(src)) {
|
|
return false;
|
|
}
|
|
|
|
m_neighbors.erase(src);
|
|
m_neighbors.insert(std::make_pair(src, dst));
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Config::Cell::remove(EDirection side)
|
|
{
|
|
for (EdgeLinks::iterator j = m_neighbors.begin();
|
|
j != m_neighbors.end(); ) {
|
|
if (j->first.getSide() == side) {
|
|
m_neighbors.erase(j++);
|
|
}
|
|
else {
|
|
++j;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Config::Cell::remove(EDirection side, float position)
|
|
{
|
|
for (EdgeLinks::iterator j = m_neighbors.begin();
|
|
j != m_neighbors.end(); ++j) {
|
|
if (j->first.getSide() == side && j->first.isInside(position)) {
|
|
m_neighbors.erase(j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
void
|
|
Config::Cell::remove(const Name& name)
|
|
{
|
|
for (EdgeLinks::iterator j = m_neighbors.begin();
|
|
j != m_neighbors.end(); ) {
|
|
if (name == j->second.getName()) {
|
|
m_neighbors.erase(j++);
|
|
}
|
|
else {
|
|
++j;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Config::Cell::rename(const Name& oldName, const String& newName)
|
|
{
|
|
for (EdgeLinks::iterator j = m_neighbors.begin();
|
|
j != m_neighbors.end(); ++j) {
|
|
if (oldName == j->second.getName()) {
|
|
j->second.setName(newName);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
Config::Cell::hasEdge(const CellEdge& edge) const
|
|
{
|
|
EdgeLinks::const_iterator i = m_neighbors.find(edge);
|
|
return (i != m_neighbors.end() && i->first == edge);
|
|
}
|
|
|
|
bool
|
|
Config::Cell::overlaps(const CellEdge& edge) const
|
|
{
|
|
EdgeLinks::const_iterator i = m_neighbors.upper_bound(edge);
|
|
if (i != m_neighbors.end() && i->first.overlaps(edge)) {
|
|
return true;
|
|
}
|
|
if (i != m_neighbors.begin() && (--i)->first.overlaps(edge)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Config::Cell::getLink(EDirection side, float position,
|
|
const CellEdge*& src, const CellEdge*& dst) const
|
|
{
|
|
CellEdge edge(side, position);
|
|
EdgeLinks::const_iterator i = m_neighbors.upper_bound(edge);
|
|
if (i == m_neighbors.begin()) {
|
|
return false;
|
|
}
|
|
--i;
|
|
if (i->first.getSide() == side && i->first.isInside(position)) {
|
|
src = &i->first;
|
|
dst = &i->second;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Config::Cell::operator==(const Cell& x) const
|
|
{
|
|
// compare options
|
|
if (m_options != x.m_options) {
|
|
return false;
|
|
}
|
|
|
|
// compare links
|
|
if (m_neighbors.size() != x.m_neighbors.size()) {
|
|
return false;
|
|
}
|
|
|
|
auto index2neighbors = x.m_neighbors.cbegin();
|
|
for (auto const &index1: m_neighbors) {
|
|
if (index1.first != index2neighbors->first) {
|
|
return false;
|
|
}
|
|
if (index1.second != index2neighbors->second) {
|
|
return false;
|
|
}
|
|
|
|
// operator== doesn't compare names. only compare destination
|
|
// names.
|
|
if (!CaselessCmp::equal(index1.second.getName(),
|
|
index2neighbors->second.getName())) {
|
|
return false;
|
|
}
|
|
++index2neighbors;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Config::Cell::operator!=(const Cell& x) const
|
|
{
|
|
return !operator==(x);
|
|
}
|
|
|
|
Config::Cell::const_iterator
|
|
Config::Cell::begin() const
|
|
{
|
|
return m_neighbors.begin();
|
|
}
|
|
|
|
Config::Cell::const_iterator
|
|
Config::Cell::end() const
|
|
{
|
|
return m_neighbors.end();
|
|
}
|
|
|
|
|
|
//
|
|
// Config I/O
|
|
//
|
|
|
|
std::istream&
|
|
operator>>(std::istream& s, Config& config)
|
|
{
|
|
ConfigReadContext context(s);
|
|
config.read(context);
|
|
return s;
|
|
}
|
|
|
|
std::ostream&
|
|
operator<<(std::ostream& s, const Config& config)
|
|
{
|
|
// screens section
|
|
s << "section: screens" << std::endl;
|
|
for (Config::const_iterator screen = config.begin();
|
|
screen != config.end(); ++screen) {
|
|
s << "\t" << screen->c_str() << ":" << std::endl;
|
|
const Config::ScreenOptions* options = config.getOptions(*screen);
|
|
if (options != NULL && options->size() > 0) {
|
|
for (Config::ScreenOptions::const_iterator
|
|
option = options->begin();
|
|
option != options->end(); ++option) {
|
|
const char* name = Config::getOptionName(option->first);
|
|
String value = Config::getOptionValue(option->first,
|
|
option->second);
|
|
if (name != NULL && !value.empty()) {
|
|
s << "\t\t" << name << " = " << value << std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
s << "end" << std::endl;
|
|
|
|
// links section
|
|
String neighbor;
|
|
s << "section: links" << std::endl;
|
|
for (Config::const_iterator screen = config.begin();
|
|
screen != config.end(); ++screen) {
|
|
s << "\t" << screen->c_str() << ":" << std::endl;
|
|
|
|
for (Config::link_const_iterator
|
|
link = config.beginNeighbor(*screen),
|
|
nend = config.endNeighbor(*screen); link != nend; ++link) {
|
|
s << "\t\t" << Config::dirName(link->first.getSide()) <<
|
|
Config::formatInterval(link->first.getInterval()) <<
|
|
" = " << link->second.getName().c_str() <<
|
|
Config::formatInterval(link->second.getInterval()) <<
|
|
std::endl;
|
|
}
|
|
}
|
|
s << "end" << std::endl;
|
|
|
|
// aliases section (if there are any)
|
|
if (config.m_map.size() != config.m_nameToCanonicalName.size()) {
|
|
// map canonical to alias
|
|
typedef std::multimap<String, String,
|
|
CaselessCmp> CMNameMap;
|
|
CMNameMap aliases;
|
|
for (Config::NameMap::const_iterator
|
|
index = config.m_nameToCanonicalName.begin();
|
|
index != config.m_nameToCanonicalName.end();
|
|
++index) {
|
|
if (index->first != index->second) {
|
|
aliases.insert(std::make_pair(index->second, index->first));
|
|
}
|
|
}
|
|
|
|
// dump it
|
|
String screen;
|
|
s << "section: aliases" << std::endl;
|
|
for (CMNameMap::const_iterator index = aliases.begin();
|
|
index != aliases.end(); ++index) {
|
|
if (index->first != screen) {
|
|
screen = index->first;
|
|
s << "\t" << screen.c_str() << ":" << std::endl;
|
|
}
|
|
s << "\t\t" << index->second.c_str() << std::endl;
|
|
}
|
|
s << "end" << std::endl;
|
|
}
|
|
|
|
// options section
|
|
s << "section: options" << std::endl;
|
|
const Config::ScreenOptions* options = config.getOptions("");
|
|
if (options != NULL && options->size() > 0) {
|
|
for (Config::ScreenOptions::const_iterator
|
|
option = options->begin();
|
|
option != options->end(); ++option) {
|
|
const char* name = Config::getOptionName(option->first);
|
|
String value = Config::getOptionValue(option->first,
|
|
option->second);
|
|
if (name != NULL && !value.empty()) {
|
|
s << "\t" << name << " = " << value << std::endl;
|
|
}
|
|
}
|
|
}
|
|
if (config.m_synergyAddress.isValid()) {
|
|
s << "\taddress = " <<
|
|
config.m_synergyAddress.getHostname().c_str() << std::endl;
|
|
}
|
|
s << config.m_inputFilter.format("\t");
|
|
s << "end" << std::endl;
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
//
|
|
// ConfigReadContext
|
|
//
|
|
|
|
ConfigReadContext::ConfigReadContext(std::istream& s, SInt32 firstLine) :
|
|
m_stream(s),
|
|
m_line(firstLine - 1)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
ConfigReadContext::~ConfigReadContext()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
bool
|
|
ConfigReadContext::readLine(String& line)
|
|
{
|
|
++m_line;
|
|
while (std::getline(m_stream, line)) {
|
|
// strip leading whitespace
|
|
String::size_type i = line.find_first_not_of(" \t");
|
|
if (i != String::npos) {
|
|
line.erase(0, i);
|
|
}
|
|
|
|
// strip comments and then trailing whitespace
|
|
i = line.find('#');
|
|
if (i != String::npos) {
|
|
line.erase(i);
|
|
}
|
|
i = line.find_last_not_of(" \r\t");
|
|
if (i != String::npos) {
|
|
line.erase(i + 1);
|
|
}
|
|
|
|
// return non empty line
|
|
if (!line.empty()) {
|
|
// make sure there are no invalid characters
|
|
for (i = 0; i < line.length(); ++i) {
|
|
if (!isgraph(line[i]) && line[i] != ' ' && line[i] != '\t') {
|
|
throw XConfigRead(*this,
|
|
"invalid character %{1}",
|
|
synergy::string::sprintf("%#2x", line[i]));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// next line
|
|
++m_line;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
UInt32
|
|
ConfigReadContext::getLineNumber() const
|
|
{
|
|
return m_line;
|
|
}
|
|
|
|
bool
|
|
ConfigReadContext::operator!() const
|
|
{
|
|
return !m_stream;
|
|
}
|
|
|
|
OptionValue
|
|
ConfigReadContext::parseBoolean(const String& arg) const
|
|
{
|
|
if (CaselessCmp::equal(arg, "true")) {
|
|
return static_cast<OptionValue>(true);
|
|
}
|
|
if (CaselessCmp::equal(arg, "false")) {
|
|
return static_cast<OptionValue>(false);
|
|
}
|
|
throw XConfigRead(*this, "invalid boolean argument \"%{1}\"", arg);
|
|
}
|
|
|
|
OptionValue
|
|
ConfigReadContext::parseInt(const String& arg) const
|
|
{
|
|
const char* s = arg.c_str();
|
|
char* end;
|
|
long tmp = strtol(s, &end, 10);
|
|
if (*end != '\0') {
|
|
// invalid characters
|
|
throw XConfigRead(*this, "invalid integer argument \"%{1}\"", arg);
|
|
}
|
|
OptionValue value = static_cast<OptionValue>(tmp);
|
|
if (value != tmp) {
|
|
// out of range
|
|
throw XConfigRead(*this, "integer argument \"%{1}\" out of range", arg);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
OptionValue
|
|
ConfigReadContext::parseModifierKey(const String& arg) const
|
|
{
|
|
if (CaselessCmp::equal(arg, "shift")) {
|
|
return static_cast<OptionValue>(kKeyModifierIDShift);
|
|
}
|
|
if (CaselessCmp::equal(arg, "ctrl")) {
|
|
return static_cast<OptionValue>(kKeyModifierIDControl);
|
|
}
|
|
if (CaselessCmp::equal(arg, "alt")) {
|
|
return static_cast<OptionValue>(kKeyModifierIDAlt);
|
|
}
|
|
if (CaselessCmp::equal(arg, "altgr")) {
|
|
return static_cast<OptionValue>(kKeyModifierIDAltGr);
|
|
}
|
|
if (CaselessCmp::equal(arg, "meta")) {
|
|
return static_cast<OptionValue>(kKeyModifierIDMeta);
|
|
}
|
|
if (CaselessCmp::equal(arg, "super")) {
|
|
return static_cast<OptionValue>(kKeyModifierIDSuper);
|
|
}
|
|
if (CaselessCmp::equal(arg, "none")) {
|
|
return static_cast<OptionValue>(kKeyModifierIDNull);
|
|
}
|
|
throw XConfigRead(*this, "invalid argument \"%{1}\"", arg);
|
|
}
|
|
|
|
OptionValue
|
|
ConfigReadContext::parseCorner(const String& arg) const
|
|
{
|
|
if (CaselessCmp::equal(arg, "left")) {
|
|
return kTopLeftMask | kBottomLeftMask;
|
|
}
|
|
else if (CaselessCmp::equal(arg, "right")) {
|
|
return kTopRightMask | kBottomRightMask;
|
|
}
|
|
else if (CaselessCmp::equal(arg, "top")) {
|
|
return kTopLeftMask | kTopRightMask;
|
|
}
|
|
else if (CaselessCmp::equal(arg, "bottom")) {
|
|
return kBottomLeftMask | kBottomRightMask;
|
|
}
|
|
else if (CaselessCmp::equal(arg, "top-left")) {
|
|
return kTopLeftMask;
|
|
}
|
|
else if (CaselessCmp::equal(arg, "top-right")) {
|
|
return kTopRightMask;
|
|
}
|
|
else if (CaselessCmp::equal(arg, "bottom-left")) {
|
|
return kBottomLeftMask;
|
|
}
|
|
else if (CaselessCmp::equal(arg, "bottom-right")) {
|
|
return kBottomRightMask;
|
|
}
|
|
else if (CaselessCmp::equal(arg, "none")) {
|
|
return kNoCornerMask;
|
|
}
|
|
else if (CaselessCmp::equal(arg, "all")) {
|
|
return kAllCornersMask;
|
|
}
|
|
throw XConfigRead(*this, "invalid argument \"%{1}\"", arg);
|
|
}
|
|
|
|
OptionValue
|
|
ConfigReadContext::parseCorners(const String& args) const
|
|
{
|
|
// find first token
|
|
String::size_type i = args.find_first_not_of(" \t", 0);
|
|
if (i == String::npos) {
|
|
throw XConfigRead(*this, "missing corner argument");
|
|
}
|
|
String::size_type j = args.find_first_of(" \t", i);
|
|
|
|
// parse first corner token
|
|
OptionValue corners = parseCorner(args.substr(i, j - i));
|
|
|
|
// get +/-
|
|
i = args.find_first_not_of(" \t", j);
|
|
while (i != String::npos) {
|
|
// parse +/-
|
|
bool add;
|
|
if (args[i] == '-') {
|
|
add = false;
|
|
}
|
|
else if (args[i] == '+') {
|
|
add = true;
|
|
}
|
|
else {
|
|
throw XConfigRead(*this,
|
|
"invalid corner operator \"%{1}\"",
|
|
String(args.c_str() + i, 1));
|
|
}
|
|
|
|
// get next corner token
|
|
i = args.find_first_not_of(" \t", i + 1);
|
|
j = args.find_first_of(" \t", i);
|
|
if (i == String::npos) {
|
|
throw XConfigRead(*this, "missing corner argument");
|
|
}
|
|
|
|
// parse next corner token
|
|
if (add) {
|
|
corners |= parseCorner(args.substr(i, j - i));
|
|
}
|
|
else {
|
|
corners &= ~parseCorner(args.substr(i, j - i));
|
|
}
|
|
i = args.find_first_not_of(" \t", j);
|
|
}
|
|
|
|
return corners;
|
|
}
|
|
|
|
Config::Interval
|
|
ConfigReadContext::parseInterval(const ArgList& args) const
|
|
{
|
|
if (args.size() == 0) {
|
|
return Config::Interval(0.0f, 1.0f);
|
|
}
|
|
if (args.size() != 2 || args[0].empty() || args[1].empty()) {
|
|
throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
|
|
}
|
|
|
|
char* end;
|
|
double startValue = strtod(args[0].c_str(), &end);
|
|
if (end[0] != '\0') {
|
|
throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
|
|
}
|
|
double endValue = strtod(args[1].c_str(), &end);
|
|
if (end[0] != '\0') {
|
|
throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
|
|
}
|
|
|
|
if (startValue < 0 || startValue > 100 ||
|
|
endValue < 0 || endValue > 100 ||
|
|
startValue >= endValue) {
|
|
throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
|
|
}
|
|
|
|
float startInterval = static_cast<float>(startValue / 100.0f);
|
|
float endInterval = static_cast<float>(endValue / 100.0f);
|
|
return Config::Interval(startInterval, endInterval);
|
|
}
|
|
|
|
void
|
|
ConfigReadContext::parseNameWithArgs(
|
|
const String& type, const String& line,
|
|
const String& delim, String::size_type& index,
|
|
String& name, ArgList& args) const
|
|
{
|
|
// skip leading whitespace
|
|
String::size_type i = line.find_first_not_of(" \t", index);
|
|
if (i == String::npos) {
|
|
throw XConfigRead(*this, String("missing ") + type);
|
|
}
|
|
|
|
// find end of name
|
|
String::size_type j = line.find_first_of(" \t(" + delim, i);
|
|
if (j == String::npos) {
|
|
j = line.length();
|
|
}
|
|
|
|
// save name
|
|
name = line.substr(i, j - i);
|
|
args.clear();
|
|
|
|
// is it okay to not find a delimiter?
|
|
bool needDelim = (!delim.empty() && delim.find('\n') == String::npos);
|
|
|
|
// skip whitespace
|
|
i = line.find_first_not_of(" \t", j);
|
|
if (i == String::npos && needDelim) {
|
|
// expected delimiter but didn't find it
|
|
throw XConfigRead(*this, String("missing ") + delim[0]);
|
|
}
|
|
if (i == String::npos) {
|
|
// no arguments
|
|
index = line.length();
|
|
return;
|
|
}
|
|
if (line[i] != '(') {
|
|
// no arguments
|
|
index = i;
|
|
return;
|
|
}
|
|
|
|
// eat '('
|
|
++i;
|
|
|
|
// parse arguments
|
|
j = line.find_first_of(",)", i);
|
|
while (j != String::npos) {
|
|
// extract arg
|
|
String arg(line.substr(i, j - i));
|
|
i = j;
|
|
|
|
// trim whitespace
|
|
j = arg.find_first_not_of(" \t");
|
|
if (j != String::npos) {
|
|
arg.erase(0, j);
|
|
}
|
|
j = arg.find_last_not_of(" \t");
|
|
if (j != String::npos) {
|
|
arg.erase(j + 1);
|
|
}
|
|
|
|
// save arg
|
|
args.push_back(arg);
|
|
|
|
// exit loop at end of arguments
|
|
if (line[i] == ')') {
|
|
break;
|
|
}
|
|
|
|
// eat ','
|
|
++i;
|
|
|
|
// next
|
|
j = line.find_first_of(",)", i);
|
|
}
|
|
|
|
// verify ')'
|
|
if (j == String::npos) {
|
|
// expected )
|
|
throw XConfigRead(*this, "missing )");
|
|
}
|
|
|
|
// eat ')'
|
|
++i;
|
|
|
|
// skip whitespace
|
|
j = line.find_first_not_of(" \t", i);
|
|
if (j == String::npos && needDelim) {
|
|
// expected delimiter but didn't find it
|
|
throw XConfigRead(*this, String("missing ") + delim[0]);
|
|
}
|
|
|
|
// verify delimiter
|
|
if (needDelim && delim.find(line[j]) == String::npos) {
|
|
throw XConfigRead(*this, String("expected ") + delim[0]);
|
|
}
|
|
|
|
if (j == String::npos) {
|
|
j = line.length();
|
|
}
|
|
|
|
index = j;
|
|
return;
|
|
}
|
|
|
|
IPlatformScreen::KeyInfo*
|
|
ConfigReadContext::parseKeystroke(const String& keystroke) const
|
|
{
|
|
return parseKeystroke(keystroke, std::set<String>());
|
|
}
|
|
|
|
IPlatformScreen::KeyInfo*
|
|
ConfigReadContext::parseKeystroke(const String& keystroke,
|
|
const std::set<String>& screens) const
|
|
{
|
|
String s = keystroke;
|
|
|
|
KeyModifierMask mask;
|
|
if (!synergy::KeyMap::parseModifiers(s, mask)) {
|
|
throw XConfigRead(*this, "unable to parse key modifiers");
|
|
}
|
|
|
|
KeyID key;
|
|
if (!synergy::KeyMap::parseKey(s, key)) {
|
|
throw XConfigRead(*this, "unable to parse key");
|
|
}
|
|
|
|
if (key == kKeyNone && mask == 0) {
|
|
throw XConfigRead(*this, "missing key and/or modifiers in keystroke");
|
|
}
|
|
|
|
return IPlatformScreen::KeyInfo::alloc(key, mask, 0, 0, screens);
|
|
}
|
|
|
|
IPlatformScreen::ButtonInfo*
|
|
ConfigReadContext::parseMouse(const String& mouse) const
|
|
{
|
|
String s = mouse;
|
|
|
|
KeyModifierMask mask;
|
|
if (!synergy::KeyMap::parseModifiers(s, mask)) {
|
|
throw XConfigRead(*this, "unable to parse button modifiers");
|
|
}
|
|
|
|
char* end;
|
|
ButtonID button = (ButtonID)strtol(s.c_str(), &end, 10);
|
|
if (*end != '\0') {
|
|
throw XConfigRead(*this, "unable to parse button");
|
|
}
|
|
if (s.empty() || button <= 0) {
|
|
throw XConfigRead(*this, "invalid button");
|
|
}
|
|
|
|
return IPlatformScreen::ButtonInfo::alloc(button, mask);
|
|
}
|
|
|
|
KeyModifierMask
|
|
ConfigReadContext::parseModifier(const String& modifiers) const
|
|
{
|
|
String s = modifiers;
|
|
|
|
KeyModifierMask mask;
|
|
if (!synergy::KeyMap::parseModifiers(s, mask)) {
|
|
throw XConfigRead(*this, "unable to parse modifiers");
|
|
}
|
|
|
|
if (mask == 0) {
|
|
throw XConfigRead(*this, "no modifiers specified");
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
String
|
|
ConfigReadContext::concatArgs(const ArgList& args)
|
|
{
|
|
String s("(");
|
|
for (size_t i = 0; i < args.size(); ++i) {
|
|
if (i != 0) {
|
|
s += ",";
|
|
}
|
|
s += args[i];
|
|
}
|
|
s += ")";
|
|
return s;
|
|
}
|
|
|
|
|
|
//
|
|
// Config I/O exceptions
|
|
//
|
|
|
|
XConfigRead::XConfigRead(const ConfigReadContext& context,
|
|
const String& error) :
|
|
m_error(synergy::string::sprintf("line %d: %s",
|
|
context.getLineNumber(), error.c_str()))
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
XConfigRead::XConfigRead(const ConfigReadContext& context,
|
|
const char* errorFmt, const String& arg) :
|
|
m_error(synergy::string::sprintf("line %d: ", context.getLineNumber()) +
|
|
synergy::string::format(errorFmt, arg.c_str()))
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
XConfigRead::~XConfigRead() _NOEXCEPT
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
String
|
|
XConfigRead::getWhat() const throw()
|
|
{
|
|
return format("XConfigRead", "read error: %{1}", m_error.c_str());
|
|
}
|