This repository has been archived by the owner. It is now read-only.
Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| /* | |
| * synergy -- mouse and keyboard sharing utility | |
| * Copyright (C) 2012-2016 Symless Ltd. | |
| * Copyright (C) 2002 Chris Schoeneman | |
| * | |
| * This package is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU General Public License | |
| * found in the file LICENSE that should have accompanied this file. | |
| * | |
| * This package is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | |
| #include "server/Config.h" | |
| #include "server/Server.h" | |
| #include "synergy/KeyMap.h" | |
| #include "synergy/key_types.h" | |
| #include "net/XSocket.h" | |
| #include "base/IEventQueue.h" | |
| #include "common/stdistream.h" | |
| #include "common/stdostream.h" | |
| #include <cstdlib> | |
| using namespace synergy::string; | |
| // | |
| // Config | |
| // | |
| Config::Config(IEventQueue* events) : | |
| m_inputFilter(events), | |
| m_hasLockToScreenAction(false), | |
| m_events(events) | |
| { | |
| // do nothing | |
| } | |
| Config::~Config() | |
| { | |
| // do nothing | |
| } | |
| bool | |
| Config::addScreen(const String& name) | |
| { | |
| // alias name must not exist | |
| if (m_nameToCanonicalName.find(name) != m_nameToCanonicalName.end()) { | |
| return false; | |
| } | |
| // add cell | |
| m_map.insert(std::make_pair(name, Cell())); | |
| // add name | |
| m_nameToCanonicalName.insert(std::make_pair(name, name)); | |
| return true; | |
| } | |
| bool | |
| Config::renameScreen(const String& oldName, | |
| const String& newName) | |
| { | |
| // get canonical name and find cell | |
| String oldCanonical = getCanonicalName(oldName); | |
| CellMap::iterator index = m_map.find(oldCanonical); | |
| if (index == m_map.end()) { | |
| return false; | |
| } | |
| // accept if names are equal but replace with new name to maintain | |
| // case. otherwise, the new name must not exist. | |
| if (!CaselessCmp::equal(oldName, newName) && | |
| m_nameToCanonicalName.find(newName) != m_nameToCanonicalName.end()) { | |
| return false; | |
| } | |
| // update cell | |
| Cell tmpCell = index->second; | |
| m_map.erase(index); | |
| m_map.insert(std::make_pair(newName, tmpCell)); | |
| // update name | |
| m_nameToCanonicalName.erase(oldCanonical); | |
| m_nameToCanonicalName.insert(std::make_pair(newName, newName)); | |
| // update connections | |
| Name oldNameObj(this, oldName); | |
| for (index = m_map.begin(); index != m_map.end(); ++index) { | |
| index->second.rename(oldNameObj, newName); | |
| } | |
| // update alias targets | |
| if (CaselessCmp::equal(oldName, oldCanonical)) { | |
| for (NameMap::iterator iter = m_nameToCanonicalName.begin(); | |
| iter != m_nameToCanonicalName.end(); ++iter) { | |
| if (CaselessCmp::equal( | |
| iter->second, oldCanonical)) { | |
| iter->second = newName; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| void | |
| Config::removeScreen(const String& name) | |
| { | |
| // get canonical name and find cell | |
| String canonical = getCanonicalName(name); | |
| CellMap::iterator index = m_map.find(canonical); | |
| if (index == m_map.end()) { | |
| return; | |
| } | |
| // remove from map | |
| m_map.erase(index); | |
| // disconnect | |
| Name nameObj(this, name); | |
| for (index = m_map.begin(); index != m_map.end(); ++index) { | |
| index->second.remove(nameObj); | |
| } | |
| // remove aliases (and canonical name) | |
| for (NameMap::iterator iter = m_nameToCanonicalName.begin(); | |
| iter != m_nameToCanonicalName.end(); ) { | |
| if (iter->second == canonical) { | |
| m_nameToCanonicalName.erase(iter++); | |
| } | |
| else { | |
| ++index; | |
| } | |
| } | |
| } | |
| void | |
| Config::removeAllScreens() | |
| { | |
| m_map.clear(); | |
| m_nameToCanonicalName.clear(); | |
| } | |
| bool | |
| Config::addAlias(const String& canonical, const String& alias) | |
| { | |
| // alias name must not exist | |
| if (m_nameToCanonicalName.find(alias) != m_nameToCanonicalName.end()) { | |
| return false; | |
| } | |
| // canonical name must be known | |
| if (m_map.find(canonical) == m_map.end()) { | |
| return false; | |
| } | |
| // insert alias | |
| m_nameToCanonicalName.insert(std::make_pair(alias, canonical)); | |
| return true; | |
| } | |
| bool | |
| Config::removeAlias(const String& alias) | |
| { | |
| // must not be a canonical name | |
| if (m_map.find(alias) != m_map.end()) { | |
| return false; | |
| } | |
| // find alias | |
| NameMap::iterator index = m_nameToCanonicalName.find(alias); | |
| if (index == m_nameToCanonicalName.end()) { | |
| return false; | |
| } | |
| // remove alias | |
| m_nameToCanonicalName.erase(index); | |
| return true; | |
| } | |
| bool | |
| Config::removeAliases(const String& canonical) | |
| { | |
| // must be a canonical name | |
| if (m_map.find(canonical) == m_map.end()) { | |
| return false; | |
| } | |
| // find and removing matching aliases | |
| for (NameMap::iterator index = m_nameToCanonicalName.begin(); | |
| index != m_nameToCanonicalName.end(); ) { | |
| if (index->second == canonical && index->first != canonical) { | |
| m_nameToCanonicalName.erase(index++); | |
| } | |
| else { | |
| ++index; | |
| } | |
| } | |
| return true; | |
| } | |
| void | |
| Config::removeAllAliases() | |
| { | |
| // remove all names | |
| m_nameToCanonicalName.clear(); | |
| // put the canonical names back in | |
| for (CellMap::iterator index = m_map.begin(); | |
| index != m_map.end(); ++index) { | |
| m_nameToCanonicalName.insert( | |
| std::make_pair(index->first, index->first)); | |
| } | |
| } | |
| bool | |
| Config::connect(const String& srcName, | |
| EDirection srcSide, | |
| float srcStart, float srcEnd, | |
| const String& dstName, | |
| float dstStart, float dstEnd) | |
| { | |
| assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); | |
| // find source cell | |
| CellMap::iterator index = m_map.find(getCanonicalName(srcName)); | |
| if (index == m_map.end()) { | |
| return false; | |
| } | |
| // add link | |
| CellEdge srcEdge(srcSide, Interval(srcStart, srcEnd)); | |
| CellEdge dstEdge(dstName, srcSide, Interval(dstStart, dstEnd)); | |
| return index->second.add(srcEdge, dstEdge); | |
| } | |
| bool | |
| Config::disconnect(const String& srcName, EDirection srcSide) | |
| { | |
| assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); | |
| // find source cell | |
| CellMap::iterator index = m_map.find(srcName); | |
| if (index == m_map.end()) { | |
| return false; | |
| } | |
| // disconnect side | |
| index->second.remove(srcSide); | |
| return true; | |
| } | |
| bool | |
| Config::disconnect(const String& srcName, EDirection srcSide, float position) | |
| { | |
| assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); | |
| // find source cell | |
| CellMap::iterator index = m_map.find(srcName); | |
| if (index == m_map.end()) { | |
| return false; | |
| } | |
| // disconnect side | |
| index->second.remove(srcSide, position); | |
| return true; | |
| } | |
| void | |
| Config::setSynergyAddress(const NetworkAddress& addr) | |
| { | |
| m_synergyAddress = addr; | |
| } | |
| bool | |
| Config::addOption(const String& name, OptionID option, OptionValue value) | |
| { | |
| // find options | |
| ScreenOptions* options = NULL; | |
| if (name.empty()) { | |
| options = &m_globalOptions; | |
| } | |
| else { | |
| CellMap::iterator index = m_map.find(name); | |
| if (index != m_map.end()) { | |
| options = &index->second.m_options; | |
| } | |
| } | |
| if (options == NULL) { | |
| return false; | |
| } | |
| // add option | |
| options->insert(std::make_pair(option, value)); | |
| return true; | |
| } | |
| bool | |
| Config::removeOption(const String& name, OptionID option) | |
| { | |
| // find options | |
| ScreenOptions* options = NULL; | |
| if (name.empty()) { | |
| options = &m_globalOptions; | |
| } | |
| else { | |
| CellMap::iterator index = m_map.find(name); | |
| if (index != m_map.end()) { | |
| options = &index->second.m_options; | |
| } | |
| } | |
| if (options == NULL) { | |
| return false; | |
| } | |
| // remove option | |
| options->erase(option); | |
| return true; | |
| } | |
| bool | |
| Config::removeOptions(const String& name) | |
| { | |
| // find options | |
| ScreenOptions* options = NULL; | |
| if (name.empty()) { | |
| options = &m_globalOptions; | |
| } | |
| else { | |
| CellMap::iterator index = m_map.find(name); | |
| if (index != m_map.end()) { | |
| options = &index->second.m_options; | |
| } | |
| } | |
| if (options == NULL) { | |
| return false; | |
| } | |
| // remove options | |
| options->clear(); | |
| return true; | |
| } | |
| bool | |
| Config::isValidScreenName(const String& name) const | |
| { | |
| // name is valid if matches validname | |
| // name ::= [_A-Za-z0-9] | [_A-Za-z0-9][-_A-Za-z0-9]*[_A-Za-z0-9] | |
| // domain ::= . name | |
| // validname ::= name domain* | |
| // we also accept names ending in . because many OS X users have | |
| // so misconfigured their systems. | |
| // empty name is invalid | |
| if (name.empty()) { | |
| return false; | |
| } | |
| // check each dot separated part | |
| String::size_type b = 0; | |
| for (;;) { | |
| // accept trailing . | |
| if (b == name.size()) { | |
| break; | |
| } | |
| // find end of part | |
| String::size_type e = name.find('.', b); | |
| if (e == String::npos) { | |
| e = name.size(); | |
| } | |
| // part may not be empty | |
| if (e - b < 1) { | |
| return false; | |
| } | |
| // check first and last characters | |
| if (!(isalnum(name[b]) || name[b] == '_') || | |
| !(isalnum(name[e - 1]) || name[e - 1] == '_')) { | |
| return false; | |
| } | |
| // check interior characters | |
| for (String::size_type i = b; i < e; ++i) { | |
| if (!isalnum(name[i]) && name[i] != '_' && name[i] != '-') { | |
| return false; | |
| } | |
| } | |
| // next part | |
| if (e == name.size()) { | |
| // no more parts | |
| break; | |
| } | |
| b = e + 1; | |
| } | |
| return true; | |
| } | |
| Config::const_iterator | |
| Config::begin() const | |
| { | |
| return const_iterator(m_map.begin()); | |
| } | |
| Config::const_iterator | |
| Config::end() const | |
| { | |
| return const_iterator(m_map.end()); | |
| } | |
| Config::all_const_iterator | |
| Config::beginAll() const | |
| { | |
| return m_nameToCanonicalName.begin(); | |
| } | |
| Config::all_const_iterator | |
| Config::endAll() const | |
| { | |
| return m_nameToCanonicalName.end(); | |
| } | |
| bool | |
| Config::isScreen(const String& name) const | |
| { | |
| return (m_nameToCanonicalName.count(name) > 0); | |
| } | |
| bool | |
| Config::isCanonicalName(const String& name) const | |
| { | |
| return (!name.empty() && | |
| CaselessCmp::equal(getCanonicalName(name), name)); | |
| } | |
| String | |
| Config::getCanonicalName(const String& name) const | |
| { | |
| NameMap::const_iterator index = m_nameToCanonicalName.find(name); | |
| if (index == m_nameToCanonicalName.end()) { | |
| return String(); | |
| } | |
| else { | |
| return index->second; | |
| } | |
| } | |
| String | |
| Config::getNeighbor(const String& srcName, EDirection srcSide, | |
| float position, float* positionOut) const | |
| { | |
| assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); | |
| // find source cell | |
| CellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); | |
| if (index == m_map.end()) { | |
| return String(); | |
| } | |
| // find edge | |
| const CellEdge* srcEdge, *dstEdge; | |
| if (!index->second.getLink(srcSide, position, srcEdge, dstEdge)) { | |
| // no neighbor | |
| return ""; | |
| } | |
| else { | |
| // compute position on neighbor | |
| if (positionOut != NULL) { | |
| *positionOut = | |
| dstEdge->inverseTransform(srcEdge->transform(position)); | |
| } | |
| // return neighbor's name | |
| return getCanonicalName(dstEdge->getName()); | |
| } | |
| } | |
| bool | |
| Config::hasNeighbor(const String& srcName, EDirection srcSide) const | |
| { | |
| return hasNeighbor(srcName, srcSide, 0.0f, 1.0f); | |
| } | |
| bool | |
| Config::hasNeighbor(const String& srcName, EDirection srcSide, | |
| float start, float end) const | |
| { | |
| assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); | |
| // find source cell | |
| CellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); | |
| if (index == m_map.end()) { | |
| return false; | |
| } | |
| return index->second.overlaps(CellEdge(srcSide, Interval(start, end))); | |
| } | |
| Config::link_const_iterator | |
| Config::beginNeighbor(const String& srcName) const | |
| { | |
| CellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); | |
| assert(index != m_map.end()); | |
| return index->second.begin(); | |
| } | |
| Config::link_const_iterator | |
| Config::endNeighbor(const String& srcName) const | |
| { | |
| CellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); | |
| assert(index != m_map.end()); | |
| return index->second.end(); | |
| } | |
| const NetworkAddress& | |
| Config::getSynergyAddress() const | |
| { | |
| return m_synergyAddress; | |
| } | |
| const Config::ScreenOptions* | |
| Config::getOptions(const String& name) const | |
| { | |
| // find options | |
| const ScreenOptions* options = NULL; | |
| if (name.empty()) { | |
| options = &m_globalOptions; | |
| } | |
| else { | |
| CellMap::const_iterator index = m_map.find(name); | |
| if (index != m_map.end()) { | |
| options = &index->second.m_options; | |
| } | |
| } | |
| // return options | |
| return options; | |
| } | |
| bool | |
| Config::hasLockToScreenAction() const | |
| { | |
| return m_hasLockToScreenAction; | |
| } | |
| bool | |
| Config::operator==(const Config& x) const | |
| { | |
| if (m_synergyAddress != x.m_synergyAddress) { | |
| return false; | |
| } | |
| if (m_map.size() != x.m_map.size()) { | |
| return false; | |
| } | |
| if (m_nameToCanonicalName.size() != x.m_nameToCanonicalName.size()) { | |
| return false; | |
| } | |
| // compare global options | |
| if (m_globalOptions != x.m_globalOptions) { | |
| return false; | |
| } | |
| 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 synergy::string::sprintf("(%d,%d)", (int)(x.first * 100.0f + 0.5f), | |
| (int)(x.second * 100.0f + 0.5f)); | |
| } | |
| void | |
| Config::readSection(ConfigReadContext& s) | |
| { | |
| static const char s_section[] = "section:"; | |
| static const char s_options[] = "options"; | |
| static const char s_screens[] = "screens"; | |
| static const char s_links[] = "links"; | |
| static const char s_aliases[] = "aliases"; | |
| String line; | |
| if (!s.readLine(line)) { | |
| // no more sections | |
| return; | |
| } | |
| // should be a section header | |
| if (line.find(s_section) != 0) { | |
| throw XConfigRead(s, "found data outside section"); | |
| } | |
| // get section name | |
| String::size_type i = line.find_first_not_of(" \t", sizeof(s_section) - 1); | |
| if (i == String::npos) { | |
| throw XConfigRead(s, "section name is missing"); | |
| } | |
| String name = line.substr(i); | |
| i = name.find_first_of(" \t"); | |
| if (i != String::npos) { | |
| throw XConfigRead(s, "unexpected data after section name"); | |
| } | |
| // read section | |
| if (name == s_options) { | |
| readSectionOptions(s); | |
| } | |
| else if (name == s_screens) { | |
| readSectionScreens(s); | |
| } | |
| else if (name == s_links) { | |
| readSectionLinks(s); | |
| } | |
| else if (name == s_aliases) { | |
| readSectionAliases(s); | |
| } | |
| else { | |
| throw XConfigRead(s, "unknown section name \"%{1}\"", name); | |
| } | |
| } | |
| void | |
| Config::readSectionOptions(ConfigReadContext& s) | |
| { | |
| String line; | |
| while (s.readLine(line)) { | |
| // check for end of section | |
| if (line == "end") { | |
| return; | |
| } | |
| // parse argument: `nameAndArgs = [values][;[values]]' | |
| // nameAndArgs := <name>[(arg[,...])] | |
| // values := valueAndArgs[,valueAndArgs]... | |
| // valueAndArgs := <value>[(arg[,...])] | |
| String::size_type i = 0; | |
| String name, value; | |
| ConfigReadContext::ArgList nameArgs, valueArgs; | |
| s.parseNameWithArgs("name", line, "=", i, name, nameArgs); | |
| ++i; | |
| s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs); | |
| bool handled = true; | |
| if (name == "address") { | |
| try { | |
| m_synergyAddress = NetworkAddress(value, kDefaultPort); | |
| m_synergyAddress.resolve(); | |
| } | |
| catch (XSocketAddress& e) { | |
| throw XConfigRead(s, | |
| String("invalid address argument ") + e.what()); | |
| } | |
| } | |
| else if (name == "heartbeat") { | |
| addOption("", kOptionHeartbeat, s.parseInt(value)); | |
| } | |
| else if (name == "switchCorners") { | |
| addOption("", kOptionScreenSwitchCorners, s.parseCorners(value)); | |
| } | |
| else if (name == "switchCornerSize") { | |
| addOption("", kOptionScreenSwitchCornerSize, s.parseInt(value)); | |
| } | |
| else if (name == "switchDelay") { | |
| addOption("", kOptionScreenSwitchDelay, s.parseInt(value)); | |
| } | |
| else if (name == "switchDoubleTap") { | |
| addOption("", kOptionScreenSwitchTwoTap, s.parseInt(value)); | |
| } | |
| else if (name == "switchNeedsShift") { | |
| addOption("", kOptionScreenSwitchNeedsShift, s.parseBoolean(value)); | |
| } | |
| else if (name == "switchNeedsControl") { | |
| addOption("", kOptionScreenSwitchNeedsControl, s.parseBoolean(value)); | |
| } | |
| else if (name == "switchNeedsAlt") { | |
| addOption("", kOptionScreenSwitchNeedsAlt, s.parseBoolean(value)); | |
| } | |
| else if (name == "screenSaverSync") { | |
| addOption("", kOptionScreenSaverSync, s.parseBoolean(value)); | |
| } | |
| else if (name == "relativeMouseMoves") { | |
| addOption("", kOptionRelativeMouseMoves, s.parseBoolean(value)); | |
| } | |
| else if (name == "win32KeepForeground") { | |
| addOption("", kOptionWin32KeepForeground, s.parseBoolean(value)); | |
| } | |
| else if (name == "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 synergy::string::sprintf("%d", value); | |
| } | |
| if (id == kOptionScreenSwitchCorners) { | |
| std::string result("none"); | |
| if ((value & kTopLeftMask) != 0) { | |
| result += " +top-left"; | |
| } | |
| if ((value & kTopRightMask) != 0) { | |
| result += " +top-right"; | |
| } | |
| if ((value & kBottomLeftMask) != 0) { | |
| result += " +bottom-left"; | |
| } | |
| if ((value & kBottomRightMask) != 0) { | |
| result += " +bottom-right"; | |
| } | |
| return result; | |
| } | |
| return ""; | |
| } | |
| // | |
| // Config::Name | |
| // | |
| Config::Name::Name(Config* config, const String& name) : | |
| m_config(config), | |
| m_name(config->getCanonicalName(name)) | |
| { | |
| // do nothing | |
| } | |
| bool | |
| Config::Name::operator==(const String& name) const | |
| { | |
| String canonical = m_config->getCanonicalName(name); | |
| return CaselessCmp::equal(canonical, m_name); | |
| } | |
| // | |
| // Config::CellEdge | |
| // | |
| Config::CellEdge::CellEdge(EDirection side, float position) | |
| { | |
| init("", side, Interval(position, position)); | |
| } | |
| Config::CellEdge::CellEdge(EDirection side, const Interval& interval) | |
| { | |
| assert(interval.first >= 0.0f); | |
| assert(interval.second <= 1.0f); | |
| assert(interval.first < interval.second); | |
| init("", side, interval); | |
| } | |
| Config::CellEdge::CellEdge(const String& name, | |
| EDirection side, const Interval& interval) | |
| { | |
| assert(interval.first >= 0.0f); | |
| assert(interval.second <= 1.0f); | |
| assert(interval.first < interval.second); | |
| init(name, side, interval); | |
| } | |
| Config::CellEdge::~CellEdge() | |
| { | |
| // do nothing | |
| } | |
| void | |
| Config::CellEdge::init(const String& name, EDirection side, | |
| const Interval& interval) | |
| { | |
| assert(side != kNoDirection); | |
| m_name = name; | |
| m_side = side; | |
| m_interval = interval; | |
| } | |
| Config::Interval | |
| Config::CellEdge::getInterval() const | |
| { | |
| return m_interval; | |
| } | |
| void | |
| Config::CellEdge::setName(const String& newName) | |
| { | |
| m_name = newName; | |
| } | |
| String | |
| Config::CellEdge::getName() const | |
| { | |
| return m_name; | |
| } | |
| EDirection | |
| Config::CellEdge::getSide() const | |
| { | |
| return m_side; | |
| } | |
| bool | |
| Config::CellEdge::overlaps(const CellEdge& edge) const | |
| { | |
| const Interval& x = m_interval; | |
| const Interval& y = edge.m_interval; | |
| if (m_side != edge.m_side) { | |
| return false; | |
| } | |
| return (x.first >= y.first && x.first < y.second) || | |
| (x.second > y.first && x.second <= y.second) || | |
| (y.first >= x.first && y.first < x.second) || | |
| (y.second > x.first && y.second <= x.second); | |
| } | |
| bool | |
| Config::CellEdge::isInside(float x) const | |
| { | |
| return (x >= m_interval.first && x < m_interval.second); | |
| } | |
| float | |
| Config::CellEdge::transform(float x) const | |
| { | |
| return (x - m_interval.first) / (m_interval.second - m_interval.first); | |
| } | |
| float | |
| Config::CellEdge::inverseTransform(float x) const | |
| { | |
| return x * (m_interval.second - m_interval.first) + m_interval.first; | |
| } | |
| bool | |
| Config::CellEdge::operator<(const CellEdge& o) const | |
| { | |
| if (static_cast<int>(m_side) < static_cast<int>(o.m_side)) { | |
| return true; | |
| } | |
| else if (static_cast<int>(m_side) > static_cast<int>(o.m_side)) { | |
| return false; | |
| } | |
| return (m_interval.first < o.m_interval.first); | |
| } | |
| bool | |
| Config::CellEdge::operator==(const CellEdge& x) const | |
| { | |
| return (m_side == x.m_side && m_interval == x.m_interval); | |
| } | |
| bool | |
| Config::CellEdge::operator!=(const CellEdge& x) const | |
| { | |
| return !operator==(x); | |
| } | |
| // | |
| // Config::Cell | |
| // | |
| bool | |
| Config::Cell::add(const CellEdge& src, const CellEdge& dst) | |
| { | |
| // cannot add an edge that overlaps other existing edges but we | |
| // can exactly replace an edge. | |
| if (!hasEdge(src) && overlaps(src)) { | |
| return false; | |
| } | |
| m_neighbors.erase(src); | |
| m_neighbors.insert(std::make_pair(src, dst)); | |
| return true; | |
| } | |
| void | |
| Config::Cell::remove(EDirection side) | |
| { | |
| for (EdgeLinks::iterator j = m_neighbors.begin(); | |
| j != m_neighbors.end(); ) { | |
| if (j->first.getSide() == side) { | |
| m_neighbors.erase(j++); | |
| } | |
| else { | |
| ++j; | |
| } | |
| } | |
| } | |
| void | |
| Config::Cell::remove(EDirection side, float position) | |
| { | |
| for (EdgeLinks::iterator j = m_neighbors.begin(); | |
| j != m_neighbors.end(); ++j) { | |
| if (j->first.getSide() == side && j->first.isInside(position)) { | |
| m_neighbors.erase(j); | |
| break; | |
| } | |
| } | |
| } | |
| void | |
| Config::Cell::remove(const Name& name) | |
| { | |
| for (EdgeLinks::iterator j = m_neighbors.begin(); | |
| j != m_neighbors.end(); ) { | |
| if (name == j->second.getName()) { | |
| m_neighbors.erase(j++); | |
| } | |
| else { | |
| ++j; | |
| } | |
| } | |
| } | |
| void | |
| Config::Cell::rename(const Name& oldName, const String& newName) | |
| { | |
| for (EdgeLinks::iterator j = m_neighbors.begin(); | |
| j != m_neighbors.end(); ++j) { | |
| if (oldName == j->second.getName()) { | |
| j->second.setName(newName); | |
| } | |
| } | |
| } | |
| bool | |
| Config::Cell::hasEdge(const CellEdge& edge) const | |
| { | |
| EdgeLinks::const_iterator i = m_neighbors.find(edge); | |
| return (i != m_neighbors.end() && i->first == edge); | |
| } | |
| bool | |
| Config::Cell::overlaps(const CellEdge& edge) const | |
| { | |
| EdgeLinks::const_iterator i = m_neighbors.upper_bound(edge); | |
| if (i != m_neighbors.end() && i->first.overlaps(edge)) { | |
| return true; | |
| } | |
| if (i != m_neighbors.begin() && (--i)->first.overlaps(edge)) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool | |
| Config::Cell::getLink(EDirection side, float position, | |
| const CellEdge*& src, const CellEdge*& dst) const | |
| { | |
| CellEdge edge(side, position); | |
| EdgeLinks::const_iterator i = m_neighbors.upper_bound(edge); | |
| if (i == m_neighbors.begin()) { | |
| return false; | |
| } | |
| --i; | |
| if (i->first.getSide() == side && i->first.isInside(position)) { | |
| src = &i->first; | |
| dst = &i->second; | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool | |
| Config::Cell::operator==(const Cell& x) const | |
| { | |
| // compare options | |
| if (m_options != x.m_options) { | |
| return false; | |
| } | |
| // compare links | |
| if (m_neighbors.size() != x.m_neighbors.size()) { | |
| return false; | |
| } | |
| 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_synergyAddress.isValid()) { | |
| s << "\taddress = " << | |
| config.m_synergyAddress.getHostname().c_str() << std::endl; | |
| } | |
| s << config.m_inputFilter.format("\t"); | |
| s << "end" << std::endl; | |
| return s; | |
| } | |
| // | |
| // ConfigReadContext | |
| // | |
| ConfigReadContext::ConfigReadContext(std::istream& s, SInt32 firstLine) : | |
| m_stream(s), | |
| m_line(firstLine - 1) | |
| { | |
| // do nothing | |
| } | |
| ConfigReadContext::~ConfigReadContext() | |
| { | |
| // do nothing | |
| } | |
| bool | |
| ConfigReadContext::readLine(String& line) | |
| { | |
| ++m_line; | |
| while (std::getline(m_stream, line)) { | |
| // strip leading whitespace | |
| String::size_type i = line.find_first_not_of(" \t"); | |
| if (i != String::npos) { | |
| line.erase(0, i); | |
| } | |
| // strip comments and then trailing whitespace | |
| i = line.find('#'); | |
| if (i != String::npos) { | |
| line.erase(i); | |
| } | |
| i = line.find_last_not_of(" \r\t"); | |
| if (i != String::npos) { | |
| line.erase(i + 1); | |
| } | |
| // return non empty line | |
| if (!line.empty()) { | |
| // make sure there are no invalid characters | |
| for (i = 0; i < line.length(); ++i) { | |
| if (!isgraph(line[i]) && line[i] != ' ' && line[i] != '\t') { | |
| throw XConfigRead(*this, | |
| "invalid character %{1}", | |
| synergy::string::sprintf("%#2x", line[i])); | |
| } | |
| } | |
| return true; | |
| } | |
| // next line | |
| ++m_line; | |
| } | |
| return false; | |
| } | |
| UInt32 | |
| ConfigReadContext::getLineNumber() const | |
| { | |
| return m_line; | |
| } | |
| bool | |
| ConfigReadContext::operator!() const | |
| { | |
| return !m_stream; | |
| } | |
| OptionValue | |
| ConfigReadContext::parseBoolean(const String& arg) const | |
| { | |
| if (CaselessCmp::equal(arg, "true")) { | |
| return static_cast<OptionValue>(true); | |
| } | |
| if (CaselessCmp::equal(arg, "false")) { | |
| return static_cast<OptionValue>(false); | |
| } | |
| throw XConfigRead(*this, "invalid boolean argument \"%{1}\"", arg); | |
| } | |
| OptionValue | |
| ConfigReadContext::parseInt(const String& arg) const | |
| { | |
| const char* s = arg.c_str(); | |
| char* end; | |
| long tmp = strtol(s, &end, 10); | |
| if (*end != '\0') { | |
| // invalid characters | |
| throw XConfigRead(*this, "invalid integer argument \"%{1}\"", arg); | |
| } | |
| OptionValue value = static_cast<OptionValue>(tmp); | |
| if (value != tmp) { | |
| // out of range | |
| throw XConfigRead(*this, "integer argument \"%{1}\" out of range", arg); | |
| } | |
| return value; | |
| } | |
| OptionValue | |
| ConfigReadContext::parseModifierKey(const String& arg) const | |
| { | |
| if (CaselessCmp::equal(arg, "shift")) { | |
| return static_cast<OptionValue>(kKeyModifierIDShift); | |
| } | |
| if (CaselessCmp::equal(arg, "ctrl")) { | |
| return static_cast<OptionValue>(kKeyModifierIDControl); | |
| } | |
| if (CaselessCmp::equal(arg, "alt")) { | |
| return static_cast<OptionValue>(kKeyModifierIDAlt); | |
| } | |
| if (CaselessCmp::equal(arg, "altgr")) { | |
| return static_cast<OptionValue>(kKeyModifierIDAltGr); | |
| } | |
| if (CaselessCmp::equal(arg, "meta")) { | |
| return static_cast<OptionValue>(kKeyModifierIDMeta); | |
| } | |
| if (CaselessCmp::equal(arg, "super")) { | |
| return static_cast<OptionValue>(kKeyModifierIDSuper); | |
| } | |
| if (CaselessCmp::equal(arg, "none")) { | |
| return static_cast<OptionValue>(kKeyModifierIDNull); | |
| } | |
| throw XConfigRead(*this, "invalid argument \"%{1}\"", arg); | |
| } | |
| OptionValue | |
| ConfigReadContext::parseCorner(const String& arg) const | |
| { | |
| if (CaselessCmp::equal(arg, "left")) { | |
| return kTopLeftMask | kBottomLeftMask; | |
| } | |
| else if (CaselessCmp::equal(arg, "right")) { | |
| return kTopRightMask | kBottomRightMask; | |
| } | |
| else if (CaselessCmp::equal(arg, "top")) { | |
| return kTopLeftMask | kTopRightMask; | |
| } | |
| else if (CaselessCmp::equal(arg, "bottom")) { | |
| return kBottomLeftMask | kBottomRightMask; | |
| } | |
| else if (CaselessCmp::equal(arg, "top-left")) { | |
| return kTopLeftMask; | |
| } | |
| else if (CaselessCmp::equal(arg, "top-right")) { | |
| return kTopRightMask; | |
| } | |
| else if (CaselessCmp::equal(arg, "bottom-left")) { | |
| return kBottomLeftMask; | |
| } | |
| else if (CaselessCmp::equal(arg, "bottom-right")) { | |
| return kBottomRightMask; | |
| } | |
| else if (CaselessCmp::equal(arg, "none")) { | |
| return kNoCornerMask; | |
| } | |
| else if (CaselessCmp::equal(arg, "all")) { | |
| return kAllCornersMask; | |
| } | |
| throw XConfigRead(*this, "invalid argument \"%{1}\"", arg); | |
| } | |
| OptionValue | |
| ConfigReadContext::parseCorners(const String& args) const | |
| { | |
| // find first token | |
| String::size_type i = args.find_first_not_of(" \t", 0); | |
| if (i == String::npos) { | |
| throw XConfigRead(*this, "missing corner argument"); | |
| } | |
| String::size_type j = args.find_first_of(" \t", i); | |
| // parse first corner token | |
| OptionValue corners = parseCorner(args.substr(i, j - i)); | |
| // get +/- | |
| i = args.find_first_not_of(" \t", j); | |
| while (i != String::npos) { | |
| // parse +/- | |
| bool add; | |
| if (args[i] == '-') { | |
| add = false; | |
| } | |
| else if (args[i] == '+') { | |
| add = true; | |
| } | |
| else { | |
| throw XConfigRead(*this, | |
| "invalid corner operator \"%{1}\"", | |
| String(args.c_str() + i, 1)); | |
| } | |
| // get next corner token | |
| i = args.find_first_not_of(" \t", i + 1); | |
| j = args.find_first_of(" \t", i); | |
| if (i == String::npos) { | |
| throw XConfigRead(*this, "missing corner argument"); | |
| } | |
| // parse next corner token | |
| if (add) { | |
| corners |= parseCorner(args.substr(i, j - i)); | |
| } | |
| else { | |
| corners &= ~parseCorner(args.substr(i, j - i)); | |
| } | |
| i = args.find_first_not_of(" \t", j); | |
| } | |
| return corners; | |
| } | |
| Config::Interval | |
| ConfigReadContext::parseInterval(const ArgList& args) const | |
| { | |
| if (args.size() == 0) { | |
| return Config::Interval(0.0f, 1.0f); | |
| } | |
| if (args.size() != 2 || args[0].empty() || args[1].empty()) { | |
| throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); | |
| } | |
| char* end; | |
| double startValue = strtod(args[0].c_str(), &end); | |
| if (end[0] != '\0') { | |
| throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); | |
| } | |
| double endValue = strtod(args[1].c_str(), &end); | |
| if (end[0] != '\0') { | |
| throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); | |
| } | |
| if (startValue < 0 || startValue > 100 || | |
| endValue < 0 || endValue > 100 || | |
| startValue >= endValue) { | |
| throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); | |
| } | |
| 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 (!synergy::KeyMap::parseModifiers(s, mask)) { | |
| throw XConfigRead(*this, "unable to parse key modifiers"); | |
| } | |
| KeyID key; | |
| if (!synergy::KeyMap::parseKey(s, key)) { | |
| throw XConfigRead(*this, "unable to parse key"); | |
| } | |
| if (key == kKeyNone && mask == 0) { | |
| throw XConfigRead(*this, "missing key and/or modifiers in keystroke"); | |
| } | |
| return IPlatformScreen::KeyInfo::alloc(key, mask, 0, 0, screens); | |
| } | |
| IPlatformScreen::ButtonInfo* | |
| ConfigReadContext::parseMouse(const String& mouse) const | |
| { | |
| String s = mouse; | |
| KeyModifierMask mask; | |
| if (!synergy::KeyMap::parseModifiers(s, mask)) { | |
| throw XConfigRead(*this, "unable to parse button modifiers"); | |
| } | |
| char* end; | |
| ButtonID button = (ButtonID)strtol(s.c_str(), &end, 10); | |
| if (*end != '\0') { | |
| throw XConfigRead(*this, "unable to parse button"); | |
| } | |
| if (s.empty() || button <= 0) { | |
| throw XConfigRead(*this, "invalid button"); | |
| } | |
| return IPlatformScreen::ButtonInfo::alloc(button, mask); | |
| } | |
| KeyModifierMask | |
| ConfigReadContext::parseModifier(const String& modifiers) const | |
| { | |
| String s = modifiers; | |
| KeyModifierMask mask; | |
| if (!synergy::KeyMap::parseModifiers(s, mask)) { | |
| throw XConfigRead(*this, "unable to parse modifiers"); | |
| } | |
| if (mask == 0) { | |
| throw XConfigRead(*this, "no modifiers specified"); | |
| } | |
| return mask; | |
| } | |
| String | |
| ConfigReadContext::concatArgs(const ArgList& args) | |
| { | |
| String s("("); | |
| for (size_t i = 0; i < args.size(); ++i) { | |
| if (i != 0) { | |
| s += ","; | |
| } | |
| s += args[i]; | |
| } | |
| s += ")"; | |
| return s; | |
| } | |
| // | |
| // Config I/O exceptions | |
| // | |
| XConfigRead::XConfigRead(const ConfigReadContext& context, | |
| const String& error) : | |
| m_error(synergy::string::sprintf("line %d: %s", | |
| context.getLineNumber(), error.c_str())) | |
| { | |
| // do nothing | |
| } | |
| XConfigRead::XConfigRead(const ConfigReadContext& context, | |
| const char* errorFmt, const String& arg) : | |
| m_error(synergy::string::sprintf("line %d: ", context.getLineNumber()) + | |
| synergy::string::format(errorFmt, arg.c_str())) | |
| { | |
| // do nothing | |
| } | |
| XConfigRead::~XConfigRead() _NOEXCEPT | |
| { | |
| // do nothing | |
| } | |
| String | |
| XConfigRead::getWhat() const throw() | |
| { | |
| return format("XConfigRead", "read error: %{1}", m_error.c_str()); | |
| } |