aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/server/Config.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/server/Config.cpp')
-rw-r--r--src/lib/server/Config.cpp2335
1 files changed, 2335 insertions, 0 deletions
diff --git a/src/lib/server/Config.cpp b/src/lib/server/Config.cpp
new file mode 100644
index 0000000..3cf60a5
--- /dev/null
+++ b/src/lib/server/Config.cpp
@@ -0,0 +1,2335 @@
+/*
+ * barrier -- 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 "barrier/KeyMap.h"
+#include "barrier/key_types.h"
+#include "net/XSocket.h"
+#include "base/IEventQueue.h"
+#include "common/stdistream.h"
+#include "common/stdostream.h"
+
+#include <cstdlib>
+
+using namespace barrier::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::setBarrierAddress(const NetworkAddress& addr)
+{
+ m_barrierAddress = 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::getBarrierAddress() const
+{
+ return m_barrierAddress;
+}
+
+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_barrierAddress != x.m_barrierAddress) {
+ 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 (CellMap::const_iterator index1 = m_map.begin(),
+ index2 = x.m_map.begin();
+ index1 != m_map.end(); ++index1, ++index2) {
+ // compare names
+ if (!CaselessCmp::equal(index1->first, index2->first)) {
+ return false;
+ }
+
+ // compare cells
+ if (index1->second != index2->second) {
+ return false;
+ }
+ }
+
+ for (NameMap::const_iterator index1 = m_nameToCanonicalName.begin(),
+ index2 = x.m_nameToCanonicalName.begin();
+ index1 != m_nameToCanonicalName.end();
+ ++index1, ++index2) {
+ if (!CaselessCmp::equal(index1->first, index2->first) ||
+ !CaselessCmp::equal(index1->second, index2->second)) {
+ return false;
+ }
+ }
+
+ // 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 barrier::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_barrierAddress = NetworkAddress(value, kDefaultPort);
+ m_barrierAddress.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 == "clipboardSharing") {
+ addOption("", kOptionClipboardSharing, 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
+ 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 == "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 == kOptionClipboardSharing) {
+ return "clipboardSharing";
+ }
+ 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) {
+ 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 barrier::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;
+ }
+ for (EdgeLinks::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 (!CaselessCmp::equal(index1->second.getName(),
+ index2->second.getName())) {
+ return false;
+ }
+ }
+ 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_barrierAddress.isValid()) {
+ s << "\taddress = " <<
+ config.m_barrierAddress.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}",
+ barrier::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));
+ }
+
+ return Config::Interval(startValue / 100.0f, endValue / 100.0f);
+}
+
+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 (!barrier::KeyMap::parseModifiers(s, mask)) {
+ throw XConfigRead(*this, "unable to parse key modifiers");
+ }
+
+ KeyID key;
+ if (!barrier::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 (!barrier::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 (!barrier::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(barrier::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(barrier::string::sprintf("line %d: ", context.getLineNumber()) +
+ barrier::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());
+}