Files
barrier/lib/server/CConfig.cpp
crs23 b728885e25 Applied patch 1547642, key broadcasting. Modified the patch to conform
to style guidelines.  Also enhanced it to allow binding hot keys to not
only toggling broadcasting but also turning it on or off, resized the
hot key dialog to accommodate the keyboard broadcasting option, changed
the configuration string for the keyboard broadcasting option, and
added documentation.

This change does not include the VS8 fix included in the patch.
2007-09-10 00:59:51 +00:00

2278 lines
52 KiB
C++

/*
* synergy -- mouse and keyboard sharing utility
* 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 COPYING 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.
*/
#include "CConfig.h"
#include "CServer.h"
#include "CKeyMap.h"
#include "KeyTypes.h"
#include "XSocket.h"
#include "stdistream.h"
#include "stdostream.h"
#include <stdlib.h>
//
// CConfig
//
CConfig::CConfig() : m_hasLockToScreenAction(false)
{
// do nothing
}
CConfig::~CConfig()
{
// do nothing
}
bool
CConfig::addScreen(const CString& 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, CCell()));
// add name
m_nameToCanonicalName.insert(std::make_pair(name, name));
return true;
}
bool
CConfig::renameScreen(const CString& oldName,
const CString& newName)
{
// get canonical name and find cell
CString oldCanonical = getCanonicalName(oldName);
CCellMap::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 (!CStringUtil::CaselessCmp::equal(oldName, newName) &&
m_nameToCanonicalName.find(newName) != m_nameToCanonicalName.end()) {
return false;
}
// update cell
CCell 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
CName oldNameObj(this, oldName);
for (index = m_map.begin(); index != m_map.end(); ++index) {
index->second.rename(oldNameObj, newName);
}
// update alias targets
if (CStringUtil::CaselessCmp::equal(oldName, oldCanonical)) {
for (CNameMap::iterator index = m_nameToCanonicalName.begin();
index != m_nameToCanonicalName.end(); ++index) {
if (CStringUtil::CaselessCmp::equal(
index->second, oldCanonical)) {
index->second = newName;
}
}
}
return true;
}
void
CConfig::removeScreen(const CString& name)
{
// get canonical name and find cell
CString canonical = getCanonicalName(name);
CCellMap::iterator index = m_map.find(canonical);
if (index == m_map.end()) {
return;
}
// remove from map
m_map.erase(index);
// disconnect
CName nameObj(this, name);
for (index = m_map.begin(); index != m_map.end(); ++index) {
index->second.remove(nameObj);
}
// remove aliases (and canonical name)
for (CNameMap::iterator index = m_nameToCanonicalName.begin();
index != m_nameToCanonicalName.end(); ) {
if (index->second == canonical) {
m_nameToCanonicalName.erase(index++);
}
else {
++index;
}
}
}
void
CConfig::removeAllScreens()
{
m_map.clear();
m_nameToCanonicalName.clear();
}
bool
CConfig::addAlias(const CString& canonical, const CString& 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
CConfig::removeAlias(const CString& alias)
{
// must not be a canonical name
if (m_map.find(alias) != m_map.end()) {
return false;
}
// find alias
CNameMap::iterator index = m_nameToCanonicalName.find(alias);
if (index == m_nameToCanonicalName.end()) {
return false;
}
// remove alias
m_nameToCanonicalName.erase(index);
return true;
}
bool
CConfig::removeAliases(const CString& canonical)
{
// must be a canonical name
if (m_map.find(canonical) == m_map.end()) {
return false;
}
// find and removing matching aliases
for (CNameMap::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
CConfig::removeAllAliases()
{
// remove all names
m_nameToCanonicalName.clear();
// put the canonical names back in
for (CCellMap::iterator index = m_map.begin();
index != m_map.end(); ++index) {
m_nameToCanonicalName.insert(
std::make_pair(index->first, index->first));
}
}
bool
CConfig::connect(const CString& srcName,
EDirection srcSide,
float srcStart, float srcEnd,
const CString& dstName,
float dstStart, float dstEnd)
{
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// find source cell
CCellMap::iterator index = m_map.find(getCanonicalName(srcName));
if (index == m_map.end()) {
return false;
}
// add link
CCellEdge srcEdge(srcSide, CInterval(srcStart, srcEnd));
CCellEdge dstEdge(dstName, srcSide, CInterval(dstStart, dstEnd));
return index->second.add(srcEdge, dstEdge);
}
bool
CConfig::disconnect(const CString& srcName, EDirection srcSide)
{
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// find source cell
CCellMap::iterator index = m_map.find(srcName);
if (index == m_map.end()) {
return false;
}
// disconnect side
index->second.remove(srcSide);
return true;
}
bool
CConfig::disconnect(const CString& srcName, EDirection srcSide, float position)
{
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// find source cell
CCellMap::iterator index = m_map.find(srcName);
if (index == m_map.end()) {
return false;
}
// disconnect side
index->second.remove(srcSide, position);
return true;
}
void
CConfig::setSynergyAddress(const CNetworkAddress& addr)
{
m_synergyAddress = addr;
}
bool
CConfig::addOption(const CString& name, OptionID option, OptionValue value)
{
// find options
CScreenOptions* options = NULL;
if (name.empty()) {
options = &m_globalOptions;
}
else {
CCellMap::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
CConfig::removeOption(const CString& name, OptionID option)
{
// find options
CScreenOptions* options = NULL;
if (name.empty()) {
options = &m_globalOptions;
}
else {
CCellMap::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
CConfig::removeOptions(const CString& name)
{
// find options
CScreenOptions* options = NULL;
if (name.empty()) {
options = &m_globalOptions;
}
else {
CCellMap::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
CConfig::isValidScreenName(const CString& 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
CString::size_type b = 0;
for (;;) {
// accept trailing .
if (b == name.size()) {
break;
}
// find end of part
CString::size_type e = name.find('.', b);
if (e == CString::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 (CString::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;
}
CConfig::const_iterator
CConfig::begin() const
{
return const_iterator(m_map.begin());
}
CConfig::const_iterator
CConfig::end() const
{
return const_iterator(m_map.end());
}
CConfig::all_const_iterator
CConfig::beginAll() const
{
return m_nameToCanonicalName.begin();
}
CConfig::all_const_iterator
CConfig::endAll() const
{
return m_nameToCanonicalName.end();
}
bool
CConfig::isScreen(const CString& name) const
{
return (m_nameToCanonicalName.count(name) > 0);
}
bool
CConfig::isCanonicalName(const CString& name) const
{
return (!name.empty() &&
CStringUtil::CaselessCmp::equal(getCanonicalName(name), name));
}
CString
CConfig::getCanonicalName(const CString& name) const
{
CNameMap::const_iterator index = m_nameToCanonicalName.find(name);
if (index == m_nameToCanonicalName.end()) {
return CString();
}
else {
return index->second;
}
}
CString
CConfig::getNeighbor(const CString& srcName, EDirection srcSide,
float position, float* positionOut) const
{
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// find source cell
CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
if (index == m_map.end()) {
return CString();
}
// find edge
const CCellEdge* 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
CConfig::hasNeighbor(const CString& srcName, EDirection srcSide) const
{
return hasNeighbor(srcName, srcSide, 0.0f, 1.0f);
}
bool
CConfig::hasNeighbor(const CString& srcName, EDirection srcSide,
float start, float end) const
{
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// find source cell
CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
if (index == m_map.end()) {
return false;
}
return index->second.overlaps(CCellEdge(srcSide, CInterval(start, end)));
}
CConfig::link_const_iterator
CConfig::beginNeighbor(const CString& srcName) const
{
CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
assert(index != m_map.end());
return index->second.begin();
}
CConfig::link_const_iterator
CConfig::endNeighbor(const CString& srcName) const
{
CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
assert(index != m_map.end());
return index->second.end();
}
const CNetworkAddress&
CConfig::getSynergyAddress() const
{
return m_synergyAddress;
}
const CConfig::CScreenOptions*
CConfig::getOptions(const CString& name) const
{
// find options
const CScreenOptions* options = NULL;
if (name.empty()) {
options = &m_globalOptions;
}
else {
CCellMap::const_iterator index = m_map.find(name);
if (index != m_map.end()) {
options = &index->second.m_options;
}
}
// return options
return options;
}
bool
CConfig::hasLockToScreenAction() const
{
return m_hasLockToScreenAction;
}
bool
CConfig::operator==(const CConfig& 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;
}
for (CCellMap::const_iterator index1 = m_map.begin(),
index2 = x.m_map.begin();
index1 != m_map.end(); ++index1, ++index2) {
// compare names
if (!CStringUtil::CaselessCmp::equal(index1->first, index2->first)) {
return false;
}
// compare cells
if (index1->second != index2->second) {
return false;
}
}
for (CNameMap::const_iterator index1 = m_nameToCanonicalName.begin(),
index2 = x.m_nameToCanonicalName.begin();
index1 != m_nameToCanonicalName.end();
++index1, ++index2) {
if (!CStringUtil::CaselessCmp::equal(index1->first, index2->first) ||
!CStringUtil::CaselessCmp::equal(index1->second, index2->second)) {
return false;
}
}
// compare input filters
if (m_inputFilter != x.m_inputFilter) {
return false;
}
return true;
}
bool
CConfig::operator!=(const CConfig& x) const
{
return !operator==(x);
}
void
CConfig::read(CConfigReadContext& context)
{
CConfig tmp;
while (context) {
tmp.readSection(context);
}
*this = tmp;
}
const char*
CConfig::dirName(EDirection dir)
{
static const char* s_name[] = { "left", "right", "up", "down" };
assert(dir >= kFirstDirection && dir <= kLastDirection);
return s_name[dir - kFirstDirection];
}
CInputFilter*
CConfig::getInputFilter()
{
return &m_inputFilter;
}
CString
CConfig::formatInterval(const CInterval& x)
{
if (x.first == 0.0f && x.second == 1.0f) {
return "";
}
return CStringUtil::print("(%d,%d)", (int)(x.first * 100.0f + 0.5f),
(int)(x.second * 100.0f + 0.5f));
}
void
CConfig::readSection(CConfigReadContext& 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";
CString 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
CString::size_type i = line.find_first_not_of(" \t", sizeof(s_section) - 1);
if (i == CString::npos) {
throw XConfigRead(s, "section name is missing");
}
CString name = line.substr(i);
i = name.find_first_of(" \t");
if (i != CString::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
CConfig::readSectionOptions(CConfigReadContext& s)
{
CString 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[,...])]
CString::size_type i = 0;
CString name, value;
CConfigReadContext::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 = CNetworkAddress(value, kDefaultPort);
m_synergyAddress.resolve();
}
catch (XSocketAddress& e) {
throw XConfigRead(s,
CString("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 == "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 {
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
CInputFilter::CRule 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 == CString::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
CConfig::readSectionScreens(CConfigReadContext& s)
{
CString line;
CString 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>'
CString::size_type i = line.find_first_of(" \t=");
if (i == 0) {
throw XConfigRead(s, "missing argument name");
}
if (i == CString::npos) {
throw XConfigRead(s, "missing =");
}
CString name = line.substr(0, i);
i = line.find_first_not_of(" \t", i);
if (i == CString::npos || line[i] != '=') {
throw XConfigRead(s, "missing =");
}
i = line.find_first_not_of(" \t", i + 1);
CString value;
if (i != CString::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 == "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 {
// unknown argument
throw XConfigRead(s, "unknown argument \"%{1}\"", name);
}
}
}
throw XConfigRead(s, "unexpected end of screens section");
}
void
CConfig::readSectionLinks(CConfigReadContext& s)
{
CString line;
CString 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).
CString::size_type i = 0;
CString side, dstScreen, srcArgString, dstArgString;
CConfigReadContext::ArgList srcArgs, dstArgs;
s.parseNameWithArgs("link", line, "=", i, side, srcArgs);
++i;
s.parseNameWithArgs("screen", line, "", i, dstScreen, dstArgs);
CInterval srcInterval(s.parseInterval(srcArgs));
CInterval 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
CConfig::readSectionAliases(CConfigReadContext& s)
{
CString line;
CString 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");
}
CInputFilter::CCondition*
CConfig::parseCondition(CConfigReadContext& s,
const CString& name, const std::vector<CString>& args)
{
if (name == "keystroke") {
if (args.size() != 1) {
throw XConfigRead(s, "syntax for condition: keystroke(modifiers+key)");
}
IPlatformScreen::CKeyInfo* keyInfo = s.parseKeystroke(args[0]);
return new CInputFilter::CKeystrokeCondition(keyInfo);
}
if (name == "mousebutton") {
if (args.size() != 1) {
throw XConfigRead(s, "syntax for condition: mousebutton(modifiers+button)");
}
IPlatformScreen::CButtonInfo* mouseInfo = s.parseMouse(args[0]);
return new CInputFilter::CMouseButtonCondition(mouseInfo);
}
if (name == "connect") {
if (args.size() != 1) {
throw XConfigRead(s, "syntax for condition: connect([screen])");
}
CString 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 CInputFilter::CScreenConnectedCondition(screen);
}
throw XConfigRead(s, "unknown argument \"%{1}\"", name);
}
void
CConfig::parseAction(CConfigReadContext& s,
const CString& name, const std::vector<CString>& args,
CInputFilter::CRule& rule, bool activate)
{
CInputFilter::CAction* 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::CKeyInfo* keyInfo;
if (args.size() == 1) {
keyInfo = s.parseKeystroke(args[0]);
}
else {
std::set<CString> screens;
parseScreens(s, args[1], screens);
keyInfo = s.parseKeystroke(args[0], screens);
}
if (name == "keystroke") {
IPlatformScreen::CKeyInfo* keyInfo2 =
IKeyState::CKeyInfo::alloc(*keyInfo);
action = new CInputFilter::CKeystrokeAction(keyInfo2, true);
rule.adoptAction(action, true);
action = new CInputFilter::CKeystrokeAction(keyInfo, false);
activate = false;
}
else if (name == "keyDown") {
action = new CInputFilter::CKeystrokeAction(keyInfo, true);
}
else {
action = new CInputFilter::CKeystrokeAction(keyInfo, false);
}
}
else if (name == "mousebutton" ||
name == "mouseDown" || name == "mouseUp") {
if (args.size() != 1) {
throw XConfigRead(s, "syntax for action: mousebutton(modifiers+button)");
}
IPlatformScreen::CButtonInfo* mouseInfo = s.parseMouse(args[0]);
if (name == "mousebutton") {
IPlatformScreen::CButtonInfo* mouseInfo2 =
IPlatformScreen::CButtonInfo::alloc(*mouseInfo);
action = new CInputFilter::CMouseButtonAction(mouseInfo2, true);
rule.adoptAction(action, true);
action = new CInputFilter::CMouseButtonAction(mouseInfo, false);
activate = false;
}
else if (name == "mouseDown") {
action = new CInputFilter::CMouseButtonAction(mouseInfo, true);
}
else {
action = new CInputFilter::CMouseButtonAction(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 CInputFilter::CModifierAction(mask, ~mask);
}
*/
else if (name == "switchToScreen") {
if (args.size() != 1) {
throw XConfigRead(s, "syntax for action: switchToScreen(name)");
}
CString screen = args[0];
if (isScreen(screen)) {
screen = getCanonicalName(screen);
}
else if (!screen.empty()) {
throw XConfigRead(s, "unknown screen name in switchToScreen");
}
action = new CInputFilter::CSwitchToScreenAction(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 CInputFilter::CSwitchInDirectionAction(direction);
}
else if (name == "lockCursorToScreen") {
if (args.size() > 1) {
throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])");
}
CInputFilter::CLockCursorToScreenAction::Mode mode =
CInputFilter::CLockCursorToScreenAction::kToggle;
if (args.size() == 1) {
if (args[0] == "off") {
mode = CInputFilter::CLockCursorToScreenAction::kOff;
}
else if (args[0] == "on") {
mode = CInputFilter::CLockCursorToScreenAction::kOn;
}
else if (args[0] == "toggle") {
mode = CInputFilter::CLockCursorToScreenAction::kToggle;
}
else {
throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])");
}
}
if (mode != CInputFilter::CLockCursorToScreenAction::kOff) {
m_hasLockToScreenAction = true;
}
action = new CInputFilter::CLockCursorToScreenAction(mode);
}
else if (name == "keyboardBroadcast") {
if (args.size() > 2) {
throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])");
}
CInputFilter::CKeyboardBroadcastAction::Mode mode =
CInputFilter::CKeyboardBroadcastAction::kToggle;
if (args.size() >= 1) {
if (args[0] == "off") {
mode = CInputFilter::CKeyboardBroadcastAction::kOff;
}
else if (args[0] == "on") {
mode = CInputFilter::CKeyboardBroadcastAction::kOn;
}
else if (args[0] == "toggle") {
mode = CInputFilter::CKeyboardBroadcastAction::kToggle;
}
else {
throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])");
}
}
std::set<CString> screens;
if (args.size() >= 2) {
parseScreens(s, args[1], screens);
}
action = new CInputFilter::CKeyboardBroadcastAction(mode, screens);
}
else {
throw XConfigRead(s, "unknown action argument \"%{1}\"", name);
}
rule.adoptAction(action, activate);
}
void
CConfig::parseScreens(CConfigReadContext& c,
const CString& s, std::set<CString>& screens) const
{
screens.clear();
CString::size_type i = 0;
while (i < s.size()) {
// find end of next screen name
CString::size_type j = s.find(':', i);
if (j == CString::npos) {
j = s.size();
}
// extract name
CString 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()) {
CString name = getCanonicalName(rawName);
if (name.empty()) {
throw XConfigRead(c, "unknown screen name \"%{1}\"", rawName);
}
screens.insert(name);
}
// next
i = j + 1;
}
}
const char*
CConfig::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 == 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 == kOptionScreenSaverSync) {
return "screenSaverSync";
}
if (id == kOptionXTestXineramaUnaware) {
return "xtestIsXineramaUnaware";
}
if (id == kOptionRelativeMouseMoves) {
return "relativeMouseMoves";
}
if (id == kOptionWin32KeepForeground) {
return "win32KeepForeground";
}
return NULL;
}
CString
CConfig::getOptionValue(OptionID id, OptionValue value)
{
if (id == kOptionHalfDuplexCapsLock ||
id == kOptionHalfDuplexNumLock ||
id == kOptionHalfDuplexScrollLock ||
id == kOptionScreenSaverSync ||
id == kOptionXTestXineramaUnaware ||
id == kOptionRelativeMouseMoves ||
id == kOptionWin32KeepForeground) {
return (value != 0) ? "true" : "false";
}
if (id == kOptionModifierMapForShift ||
id == kOptionModifierMapForControl ||
id == kOptionModifierMapForAlt ||
id == kOptionModifierMapForMeta ||
id == kOptionModifierMapForSuper) {
switch (value) {
case kKeyModifierIDShift:
return "shift";
case kKeyModifierIDControl:
return "ctrl";
case kKeyModifierIDAlt:
return "alt";
case kKeyModifierIDMeta:
return "meta";
case kKeyModifierIDSuper:
return "super";
default:
return "none";
}
}
if (id == kOptionHeartbeat ||
id == kOptionScreenSwitchCornerSize ||
id == kOptionScreenSwitchDelay ||
id == kOptionScreenSwitchTwoTap) {
return CStringUtil::print("%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 "";
}
//
// CConfig::CName
//
CConfig::CName::CName(CConfig* config, const CString& name) :
m_config(config),
m_name(config->getCanonicalName(name))
{
// do nothing
}
bool
CConfig::CName::operator==(const CString& name) const
{
CString canonical = m_config->getCanonicalName(name);
return CStringUtil::CaselessCmp::equal(canonical, m_name);
}
//
// CConfig::CCellEdge
//
CConfig::CCellEdge::CCellEdge(EDirection side, float position)
{
init("", side, CInterval(position, position));
}
CConfig::CCellEdge::CCellEdge(EDirection side, const CInterval& interval)
{
assert(interval.first >= 0.0f);
assert(interval.second <= 1.0f);
assert(interval.first < interval.second);
init("", side, interval);
}
CConfig::CCellEdge::CCellEdge(const CString& name,
EDirection side, const CInterval& interval)
{
assert(interval.first >= 0.0f);
assert(interval.second <= 1.0f);
assert(interval.first < interval.second);
init(name, side, interval);
}
CConfig::CCellEdge::~CCellEdge()
{
// do nothing
}
void
CConfig::CCellEdge::init(const CString& name, EDirection side,
const CInterval& interval)
{
assert(side != kNoDirection);
m_name = name;
m_side = side;
m_interval = interval;
}
CConfig::CInterval
CConfig::CCellEdge::getInterval() const
{
return m_interval;
}
void
CConfig::CCellEdge::setName(const CString& newName)
{
m_name = newName;
}
CString
CConfig::CCellEdge::getName() const
{
return m_name;
}
EDirection
CConfig::CCellEdge::getSide() const
{
return m_side;
}
bool
CConfig::CCellEdge::overlaps(const CCellEdge& edge) const
{
const CInterval& x = m_interval;
const CInterval& 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
CConfig::CCellEdge::isInside(float x) const
{
return (x >= m_interval.first && x < m_interval.second);
}
float
CConfig::CCellEdge::transform(float x) const
{
return (x - m_interval.first) / (m_interval.second - m_interval.first);
}
float
CConfig::CCellEdge::inverseTransform(float x) const
{
return x * (m_interval.second - m_interval.first) + m_interval.first;
}
bool
CConfig::CCellEdge::operator<(const CCellEdge& 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
CConfig::CCellEdge::operator==(const CCellEdge& x) const
{
return (m_side == x.m_side && m_interval == x.m_interval);
}
bool
CConfig::CCellEdge::operator!=(const CCellEdge& x) const
{
return !operator==(x);
}
//
// CConfig::CCell
//
bool
CConfig::CCell::add(const CCellEdge& src, const CCellEdge& 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
CConfig::CCell::remove(EDirection side)
{
for (CEdgeLinks::iterator j = m_neighbors.begin();
j != m_neighbors.end(); ) {
if (j->first.getSide() == side) {
m_neighbors.erase(j++);
}
else {
++j;
}
}
}
void
CConfig::CCell::remove(EDirection side, float position)
{
for (CEdgeLinks::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
CConfig::CCell::remove(const CName& name)
{
for (CEdgeLinks::iterator j = m_neighbors.begin();
j != m_neighbors.end(); ) {
if (name == j->second.getName()) {
m_neighbors.erase(j++);
}
else {
++j;
}
}
}
void
CConfig::CCell::rename(const CName& oldName, const CString& newName)
{
for (CEdgeLinks::iterator j = m_neighbors.begin();
j != m_neighbors.end(); ++j) {
if (oldName == j->second.getName()) {
j->second.setName(newName);
}
}
}
bool
CConfig::CCell::hasEdge(const CCellEdge& edge) const
{
CEdgeLinks::const_iterator i = m_neighbors.find(edge);
return (i != m_neighbors.end() && i->first == edge);
}
bool
CConfig::CCell::overlaps(const CCellEdge& edge) const
{
CEdgeLinks::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
CConfig::CCell::getLink(EDirection side, float position,
const CCellEdge*& src, const CCellEdge*& dst) const
{
CCellEdge edge(side, position);
CEdgeLinks::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
CConfig::CCell::operator==(const CCell& x) const
{
// compare options
if (m_options != x.m_options) {
return false;
}
// compare links
if (m_neighbors.size() != x.m_neighbors.size()) {
return false;
}
for (CEdgeLinks::const_iterator index1 = m_neighbors.begin(),
index2 = x.m_neighbors.begin();
index1 != m_neighbors.end();
++index1, ++index2) {
if (index1->first != index2->first) {
return false;
}
if (index1->second != index2->second) {
return false;
}
// operator== doesn't compare names. only compare destination
// names.
if (!CStringUtil::CaselessCmp::equal(index1->second.getName(),
index2->second.getName())) {
return false;
}
}
return true;
}
bool
CConfig::CCell::operator!=(const CCell& x) const
{
return !operator==(x);
}
CConfig::CCell::const_iterator
CConfig::CCell::begin() const
{
return m_neighbors.begin();
}
CConfig::CCell::const_iterator
CConfig::CCell::end() const
{
return m_neighbors.end();
}
//
// CConfig I/O
//
std::istream&
operator>>(std::istream& s, CConfig& config)
{
CConfigReadContext context(s);
config.read(context);
return s;
}
std::ostream&
operator<<(std::ostream& s, const CConfig& config)
{
// screens section
s << "section: screens" << std::endl;
for (CConfig::const_iterator screen = config.begin();
screen != config.end(); ++screen) {
s << "\t" << screen->c_str() << ":" << std::endl;
const CConfig::CScreenOptions* options = config.getOptions(*screen);
if (options != NULL && options->size() > 0) {
for (CConfig::CScreenOptions::const_iterator
option = options->begin();
option != options->end(); ++option) {
const char* name = CConfig::getOptionName(option->first);
CString value = CConfig::getOptionValue(option->first,
option->second);
if (name != NULL && !value.empty()) {
s << "\t\t" << name << " = " << value << std::endl;
}
}
}
}
s << "end" << std::endl;
// links section
CString neighbor;
s << "section: links" << std::endl;
for (CConfig::const_iterator screen = config.begin();
screen != config.end(); ++screen) {
s << "\t" << screen->c_str() << ":" << std::endl;
for (CConfig::link_const_iterator
link = config.beginNeighbor(*screen),
nend = config.endNeighbor(*screen); link != nend; ++link) {
s << "\t\t" << CConfig::dirName(link->first.getSide()) <<
CConfig::formatInterval(link->first.getInterval()) <<
" = " << link->second.getName().c_str() <<
CConfig::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<CString, CString,
CStringUtil::CaselessCmp> CMNameMap;
CMNameMap aliases;
for (CConfig::CNameMap::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
CString 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 CConfig::CScreenOptions* options = config.getOptions("");
if (options != NULL && options->size() > 0) {
for (CConfig::CScreenOptions::const_iterator
option = options->begin();
option != options->end(); ++option) {
const char* name = CConfig::getOptionName(option->first);
CString value = CConfig::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;
}
//
// CConfigReadContext
//
CConfigReadContext::CConfigReadContext(std::istream& s, SInt32 firstLine) :
m_stream(s),
m_line(firstLine - 1)
{
// do nothing
}
CConfigReadContext::~CConfigReadContext()
{
// do nothing
}
bool
CConfigReadContext::readLine(CString& line)
{
++m_line;
while (std::getline(m_stream, line)) {
// strip leading whitespace
CString::size_type i = line.find_first_not_of(" \t");
if (i != CString::npos) {
line.erase(0, i);
}
// strip comments and then trailing whitespace
i = line.find('#');
if (i != CString::npos) {
line.erase(i);
}
i = line.find_last_not_of(" \r\t");
if (i != CString::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}",
CStringUtil::print("%#2x", line[i]));
}
}
return true;
}
// next line
++m_line;
}
return false;
}
UInt32
CConfigReadContext::getLineNumber() const
{
return m_line;
}
CConfigReadContext::operator void*() const
{
return m_stream;
}
bool
CConfigReadContext::operator!() const
{
return !m_stream;
}
OptionValue
CConfigReadContext::parseBoolean(const CString& arg) const
{
if (CStringUtil::CaselessCmp::equal(arg, "true")) {
return static_cast<OptionValue>(true);
}
if (CStringUtil::CaselessCmp::equal(arg, "false")) {
return static_cast<OptionValue>(false);
}
throw XConfigRead(*this, "invalid boolean argument \"%{1}\"", arg);
}
OptionValue
CConfigReadContext::parseInt(const CString& 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
CConfigReadContext::parseModifierKey(const CString& arg) const
{
if (CStringUtil::CaselessCmp::equal(arg, "shift")) {
return static_cast<OptionValue>(kKeyModifierIDShift);
}
if (CStringUtil::CaselessCmp::equal(arg, "ctrl")) {
return static_cast<OptionValue>(kKeyModifierIDControl);
}
if (CStringUtil::CaselessCmp::equal(arg, "alt")) {
return static_cast<OptionValue>(kKeyModifierIDAlt);
}
if (CStringUtil::CaselessCmp::equal(arg, "meta")) {
return static_cast<OptionValue>(kKeyModifierIDMeta);
}
if (CStringUtil::CaselessCmp::equal(arg, "super")) {
return static_cast<OptionValue>(kKeyModifierIDSuper);
}
if (CStringUtil::CaselessCmp::equal(arg, "none")) {
return static_cast<OptionValue>(kKeyModifierIDNull);
}
throw XConfigRead(*this, "invalid argument \"%{1}\"", arg);
}
OptionValue
CConfigReadContext::parseCorner(const CString& arg) const
{
if (CStringUtil::CaselessCmp::equal(arg, "left")) {
return kTopLeftMask | kBottomLeftMask;
}
else if (CStringUtil::CaselessCmp::equal(arg, "right")) {
return kTopRightMask | kBottomRightMask;
}
else if (CStringUtil::CaselessCmp::equal(arg, "top")) {
return kTopLeftMask | kTopRightMask;
}
else if (CStringUtil::CaselessCmp::equal(arg, "bottom")) {
return kBottomLeftMask | kBottomRightMask;
}
else if (CStringUtil::CaselessCmp::equal(arg, "top-left")) {
return kTopLeftMask;
}
else if (CStringUtil::CaselessCmp::equal(arg, "top-right")) {
return kTopRightMask;
}
else if (CStringUtil::CaselessCmp::equal(arg, "bottom-left")) {
return kBottomLeftMask;
}
else if (CStringUtil::CaselessCmp::equal(arg, "bottom-right")) {
return kBottomRightMask;
}
else if (CStringUtil::CaselessCmp::equal(arg, "none")) {
return kNoCornerMask;
}
else if (CStringUtil::CaselessCmp::equal(arg, "all")) {
return kAllCornersMask;
}
throw XConfigRead(*this, "invalid argument \"%{1}\"", arg);
}
OptionValue
CConfigReadContext::parseCorners(const CString& args) const
{
// find first token
CString::size_type i = args.find_first_not_of(" \t", 0);
if (i == CString::npos) {
throw XConfigRead(*this, "missing corner argument");
}
CString::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 != CString::npos) {
// parse +/-
bool add;
if (args[i] == '-') {
add = false;
}
else if (args[i] == '+') {
add = true;
}
else {
throw XConfigRead(*this,
"invalid corner operator \"%{1}\"",
CString(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 == CString::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;
}
CConfig::CInterval
CConfigReadContext::parseInterval(const ArgList& args) const
{
if (args.size() == 0) {
return CConfig::CInterval(0.0f, 1.0f);
}
if (args.size() != 2 || args[0].empty() || args[1].empty()) {
throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
}
char* end;
long startValue = strtol(args[0].c_str(), &end, 10);
if (end[0] != '\0') {
throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
}
long endValue = strtol(args[1].c_str(), &end, 10);
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));
}
return CConfig::CInterval(startValue / 100.0f, endValue / 100.0f);
}
void
CConfigReadContext::parseNameWithArgs(
const CString& type, const CString& line,
const CString& delim, CString::size_type& index,
CString& name, ArgList& args) const
{
// skip leading whitespace
CString::size_type i = line.find_first_not_of(" \t", index);
if (i == CString::npos) {
throw XConfigRead(*this, CString("missing ") + type);
}
// find end of name
CString::size_type j = line.find_first_of(" \t(" + delim, i);
if (j == CString::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') == CString::npos);
// skip whitespace
i = line.find_first_not_of(" \t", j);
if (i == CString::npos && needDelim) {
// expected delimiter but didn't find it
throw XConfigRead(*this, CString("missing ") + delim[0]);
}
if (i == CString::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 != CString::npos) {
// extract arg
CString arg(line.substr(i, j - i));
i = j;
// trim whitespace
j = arg.find_first_not_of(" \t");
if (j != CString::npos) {
arg.erase(0, j);
}
j = arg.find_last_not_of(" \t");
if (j != CString::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 == CString::npos) {
// expected )
throw XConfigRead(*this, "missing )");
}
// eat ')'
++i;
// skip whitespace
j = line.find_first_not_of(" \t", i);
if (j == CString::npos && needDelim) {
// expected delimiter but didn't find it
throw XConfigRead(*this, CString("missing ") + delim[0]);
}
// verify delimiter
if (needDelim && delim.find(line[j]) == CString::npos) {
throw XConfigRead(*this, CString("expected ") + delim[0]);
}
if (j == CString::npos) {
j = line.length();
}
index = j;
return;
}
IPlatformScreen::CKeyInfo*
CConfigReadContext::parseKeystroke(const CString& keystroke) const
{
return parseKeystroke(keystroke, std::set<CString>());
}
IPlatformScreen::CKeyInfo*
CConfigReadContext::parseKeystroke(const CString& keystroke,
const std::set<CString>& screens) const
{
CString s = keystroke;
KeyModifierMask mask;
if (!CKeyMap::parseModifiers(s, mask)) {
throw XConfigRead(*this, "unable to parse key modifiers");
}
KeyID key;
if (!CKeyMap::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::CKeyInfo::alloc(key, mask, 0, 0, screens);
}
IPlatformScreen::CButtonInfo*
CConfigReadContext::parseMouse(const CString& mouse) const
{
CString s = mouse;
KeyModifierMask mask;
if (!CKeyMap::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::CButtonInfo::alloc(button, mask);
}
KeyModifierMask
CConfigReadContext::parseModifier(const CString& modifiers) const
{
CString s = modifiers;
KeyModifierMask mask;
if (!CKeyMap::parseModifiers(s, mask)) {
throw XConfigRead(*this, "unable to parse modifiers");
}
if (mask == 0) {
throw XConfigRead(*this, "no modifiers specified");
}
return mask;
}
CString
CConfigReadContext::concatArgs(const ArgList& args)
{
CString s("(");
for (size_t i = 0; i < args.size(); ++i) {
if (i != 0) {
s += ",";
}
s += args[i];
}
s += ")";
return s;
}
//
// CConfig I/O exceptions
//
XConfigRead::XConfigRead(const CConfigReadContext& context,
const CString& error) :
m_error(CStringUtil::print("line %d: %s",
context.getLineNumber(), error.c_str()))
{
// do nothing
}
XConfigRead::XConfigRead(const CConfigReadContext& context,
const char* errorFmt, const CString& arg) :
m_error(CStringUtil::print("line %d: ", context.getLineNumber()) +
CStringUtil::format(errorFmt, arg.c_str()))
{
// do nothing
}
XConfigRead::~XConfigRead()
{
// do nothing
}
CString
XConfigRead::getWhat() const throw()
{
return format("XConfigRead", "read error: %{1}", m_error.c_str());
}