diff --git a/lib/client/CServerProxy.cpp b/lib/client/CServerProxy.cpp index 5b3bdf81..ce0db115 100644 --- a/lib/client/CServerProxy.cpp +++ b/lib/client/CServerProxy.cpp @@ -34,7 +34,8 @@ CServerProxy::CServerProxy(IClient* client, m_client(client), m_input(adoptedInput), m_output(adoptedOutput), - m_seqNum(0) + m_seqNum(0), + m_heartRate(kHeartRate) { assert(m_client != NULL); assert(m_input != NULL); @@ -76,7 +77,7 @@ CServerProxy::mainLoop() // wait for a message LOG((CLOG_DEBUG2 "waiting for message")); UInt8 code[4]; - UInt32 n = getInputStream()->read(code, 4, kHeartRate); + UInt32 n = getInputStream()->read(code, 4, m_heartRate); // check if server hungup if (n == 0) { @@ -86,7 +87,7 @@ CServerProxy::mainLoop() // check for time out if (n == (UInt32)-1 || - (kHeartRate >= 0.0 && heartbeat.getTime() > kHeartRate)) { + (m_heartRate >= 0.0 && heartbeat.getTime() > m_heartRate)) { // send heartbeat CLock lock(&m_mutex); CProtocolUtil::writef(getOutputStream(), kMsgCNoop); @@ -663,9 +664,20 @@ CServerProxy::resetOptions() // forward getClient()->resetOptions(); + CLock lock(&m_mutex); + + // reset heart rate + m_heartRate = kHeartRate; + // reset modifier translation table - for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) + for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) { m_modifierTranslationTable[id] = id; + } + + // send heartbeat if necessary + if (m_heartRate >= 0.0) { + CProtocolUtil::writef(getOutputStream(), kMsgCNoop); + } } void @@ -679,6 +691,8 @@ CServerProxy::setOptions() // forward getClient()->setOptions(options); + CLock lock(&m_mutex); + // update modifier table for (UInt32 i = 0, n = options.size(); i < n; i += 2) { KeyModifierID id = kKeyModifierIDNull; @@ -697,6 +711,15 @@ CServerProxy::setOptions() else if (options[i] == kOptionModifierMapForSuper) { id = kKeyModifierIDSuper; } + else if (options[i] == kOptionHeartbeat) { + // update heart rate + m_heartRate = 1.0e-3 * static_cast(options[i + 1]); + + // send heartbeat if necessary + if (m_heartRate >= 0.0) { + CProtocolUtil::writef(getOutputStream(), kMsgCNoop); + } + } if (id != kKeyModifierIDNull) { m_modifierTranslationTable[id] = static_cast(options[i + 1]); diff --git a/lib/client/CServerProxy.h b/lib/client/CServerProxy.h index 7d7eb2dd..cbb58f1b 100644 --- a/lib/client/CServerProxy.h +++ b/lib/client/CServerProxy.h @@ -130,6 +130,7 @@ private: bool m_ignoreMouse; KeyModifierID m_modifierTranslationTable[kKeyModifierIDLast]; + double m_heartRate; }; #endif diff --git a/lib/server/CClientProxy.cpp b/lib/server/CClientProxy.cpp index 938fe89d..55e940f7 100644 --- a/lib/server/CClientProxy.cpp +++ b/lib/server/CClientProxy.cpp @@ -59,3 +59,9 @@ CClientProxy::getName() const { return m_name; } + +const CMutex* +CClientProxy::getMutex() const +{ + return &m_mutex; +} diff --git a/lib/server/CClientProxy.h b/lib/server/CClientProxy.h index 40447a6b..43585001 100644 --- a/lib/server/CClientProxy.h +++ b/lib/server/CClientProxy.h @@ -16,6 +16,7 @@ #define CCLIENTPROXY_H #include "IClient.h" +#include "CMutex.h" #include "CString.h" class IInputStream; @@ -84,7 +85,16 @@ public: virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; +protected: + //! Get mutex + /*! + Returns the mutex for this object. Subclasses should use this + mutex to protect their data. + */ + const CMutex* getMutex() const; + private: + CMutex m_mutex; IServer* m_server; CString m_name; IInputStream* m_input; diff --git a/lib/server/CClientProxy1_0.cpp b/lib/server/CClientProxy1_0.cpp index 6ce07a43..be0effca 100644 --- a/lib/server/CClientProxy1_0.cpp +++ b/lib/server/CClientProxy1_0.cpp @@ -31,7 +31,9 @@ CClientProxy1_0::CClientProxy1_0(IServer* server, const CString& name, IInputStream* input, IOutputStream* output) : - CClientProxy(server, name, input, output) + CClientProxy(server, name, input, output), + m_heartRate(kHeartRate), + m_heartDeath(kHeartRate * kHeartBeatsUntilDeath) { for (UInt32 i = 0; i < kClipboardEnd; ++i) { m_clipboardDirty[i] = true; @@ -81,7 +83,7 @@ CClientProxy1_0::mainLoop() // wait for a message UInt8 code[4]; - UInt32 n = getInputStream()->read(code, 4, kHeartRate); + UInt32 n = getInputStream()->read(code, 4, m_heartRate); CThread::testCancel(); // check if client hungup @@ -92,7 +94,7 @@ CClientProxy1_0::mainLoop() // check if client has stopped sending heartbeats if (n == (UInt32)-1) { - if (kHeartDeath >= 0.0 && heartTimer.getTime() > kHeartDeath) { + if (m_heartDeath >= 0.0 && heartTimer.getTime() > m_heartDeath) { LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str())); return; } @@ -117,6 +119,7 @@ CClientProxy1_0::mainLoop() } else if (memcmp(code, kMsgCNoop, 4) == 0) { // discard no-ops + LOG((CLOG_DEBUG2 "no-op from", getName().c_str())); continue; } else if (memcmp(code, kMsgCClipboard, 4) == 0) { @@ -168,7 +171,7 @@ void CClientProxy1_0::setClipboard(ClipboardID id, const CString& data) { // ignore if this clipboard is already clean - CLock lock(&m_mutex); + CLock lock(getMutex()); if (m_clipboardDirty[id]) { // this clipboard is now clean m_clipboardDirty[id] = false; @@ -185,14 +188,14 @@ CClientProxy1_0::grabClipboard(ClipboardID id) CProtocolUtil::writef(getOutputStream(), kMsgCClipboard, id, 0); // this clipboard is now dirty - CLock lock(&m_mutex); + CLock lock(getMutex()); m_clipboardDirty[id] = true; } void CClientProxy1_0::setClipboardDirty(ClipboardID id, bool dirty) { - CLock lock(&m_mutex); + CLock lock(getMutex()); m_clipboardDirty[id] = dirty; } @@ -257,6 +260,11 @@ CClientProxy1_0::resetOptions() { LOG((CLOG_DEBUG1 "send reset options to \"%s\"", getName().c_str())); CProtocolUtil::writef(getOutputStream(), kMsgCResetOptions); + + // reset heart rate and death + CLock lock(getMutex()); + m_heartRate = kHeartRate; + m_heartDeath = kHeartRate * kHeartBeatsUntilDeath; } void @@ -264,19 +272,31 @@ CClientProxy1_0::setOptions(const COptionsList& options) { LOG((CLOG_DEBUG1 "send set options to \"%s\" size=%d", getName().c_str(), options.size())); CProtocolUtil::writef(getOutputStream(), kMsgDSetOptions, &options); + + // check options + CLock lock(getMutex()); + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionHeartbeat) { + m_heartRate = 1.0e-3 * static_cast(options[i + 1]); + if (m_heartRate <= 0.0) { + m_heartRate = -1.0; + } + m_heartDeath = m_heartRate * kHeartBeatsUntilDeath; + } + } } SInt32 CClientProxy1_0::getJumpZoneSize() const { - CLock lock(&m_mutex); + CLock lock(getMutex()); return m_info.m_zoneSize; } void CClientProxy1_0::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const { - CLock lock(&m_mutex); + CLock lock(getMutex()); x = m_info.m_x; y = m_info.m_y; w = m_info.m_w; @@ -292,7 +312,7 @@ CClientProxy1_0::getCursorPos(SInt32&, SInt32&) const void CClientProxy1_0::getCursorCenter(SInt32& x, SInt32& y) const { - CLock lock(&m_mutex); + CLock lock(getMutex()); x = m_info.m_mx; y = m_info.m_my; } @@ -301,7 +321,7 @@ void CClientProxy1_0::recvInfo(bool notify) { { - CLock lock(&m_mutex); + CLock lock(getMutex()); // parse the message SInt16 x, y, w, h, zoneSize, mx, my; diff --git a/lib/server/CClientProxy1_0.h b/lib/server/CClientProxy1_0.h index ac94f0f5..6725e5be 100644 --- a/lib/server/CClientProxy1_0.h +++ b/lib/server/CClientProxy1_0.h @@ -17,7 +17,6 @@ #include "CClientProxy.h" #include "ProtocolTypes.h" -#include "CMutex.h" //! Proxy for client implementing protocol version 1.0 class CClientProxy1_0 : public CClientProxy { @@ -60,9 +59,10 @@ private: void recvGrabClipboard(); private: - CMutex m_mutex; CClientInfo m_info; bool m_clipboardDirty[kClipboardEnd]; + double m_heartRate; + double m_heartDeath; }; #endif diff --git a/lib/server/CConfig.cpp b/lib/server/CConfig.cpp index 5728bd05..3359d9f1 100644 --- a/lib/server/CConfig.cpp +++ b/lib/server/CConfig.cpp @@ -17,6 +17,7 @@ #include "XSocket.h" #include "stdistream.h" #include "stdostream.h" +#include // // CConfig @@ -504,31 +505,57 @@ CConfig::readLine(std::istream& s, CString& line) return false; } -bool +OptionValue CConfig::parseBoolean(const CString& arg) { - if (CStringUtil::CaselessCmp::equal(arg, "true")) - return true; - if (CStringUtil::CaselessCmp::equal(arg, "false")) - return false; + if (CStringUtil::CaselessCmp::equal(arg, "true")) { + return static_cast(true); + } + if (CStringUtil::CaselessCmp::equal(arg, "false")) { + return static_cast(false); + } throw XConfigRead("invalid argument"); } +OptionValue +CConfig::parseInt(const CString& arg) +{ + const char* s = arg.c_str(); + char* end; + long tmp = strtol(s, &end, 10); + if (*end != '\0') { + // invalid characters + throw XConfigRead("invalid argument"); + } + OptionValue value = static_cast(tmp); + if (value != tmp) { + // out of range + throw XConfigRead("argument out of range"); + } + return value; +} + OptionValue CConfig::parseModifierKey(const CString& arg) { - if (CStringUtil::CaselessCmp::equal(arg, "shift")) + if (CStringUtil::CaselessCmp::equal(arg, "shift")) { return static_cast(kKeyModifierIDShift); - if (CStringUtil::CaselessCmp::equal(arg, "ctrl")) + } + if (CStringUtil::CaselessCmp::equal(arg, "ctrl")) { return static_cast(kKeyModifierIDControl); - if (CStringUtil::CaselessCmp::equal(arg, "alt")) + } + if (CStringUtil::CaselessCmp::equal(arg, "alt")) { return static_cast(kKeyModifierIDAlt); - if (CStringUtil::CaselessCmp::equal(arg, "meta")) + } + if (CStringUtil::CaselessCmp::equal(arg, "meta")) { return static_cast(kKeyModifierIDMeta); - if (CStringUtil::CaselessCmp::equal(arg, "super")) + } + if (CStringUtil::CaselessCmp::equal(arg, "super")) { return static_cast(kKeyModifierIDSuper); - if (CStringUtil::CaselessCmp::equal(arg, "none")) + } + if (CStringUtil::CaselessCmp::equal(arg, "none")) { return static_cast(kKeyModifierIDNull); + } throw XConfigRead("invalid argument"); } @@ -556,10 +583,13 @@ CConfig::getOptionName(OptionID id) if (id == kOptionModifierMapForSuper) { return "super"; } + if (id == kOptionHeartbeat) { + return "heartbeat"; + } return NULL; } -const char* +CString CConfig::getOptionValue(OptionID id, OptionValue value) { if (id == kOptionHalfDuplexCapsLock || @@ -591,6 +621,9 @@ CConfig::getOptionValue(OptionID id, OptionValue value) return "none"; } } + if (id == kOptionHeartbeat) { + return CStringUtil::print("%d", value); + } return ""; } @@ -599,7 +632,7 @@ void CConfig::readSection(std::istream& s) { static const char s_section[] = "section:"; - static const char s_network[] = "network"; + static const char s_options[] = "options"; static const char s_screens[] = "screens"; static const char s_links[] = "links"; static const char s_aliases[] = "aliases"; @@ -627,8 +660,8 @@ CConfig::readSection(std::istream& s) } // read section - if (name == s_network) { - readSectionNetwork(s); + if (name == s_options) { + readSectionOptions(s); } else if (name == s_screens) { readSectionScreens(s); @@ -645,7 +678,7 @@ CConfig::readSection(std::istream& s) } void -CConfig::readSectionNetwork(std::istream& s) +CConfig::readSectionOptions(std::istream& s) { CString line; CString name; @@ -693,6 +726,9 @@ CConfig::readSectionNetwork(std::istream& s) throw XConfigRead("invalid http argument"); } } + else if (name == "heartbeat") { + addOption("", kOptionHeartbeat, parseInt(value)); + } else { throw XConfigRead("unknown argument"); } @@ -931,15 +967,28 @@ operator>>(std::istream& s, CConfig& config) std::ostream& operator<<(std::ostream& s, const CConfig& config) { - // network section - s << "section: network" << std::endl; + // options section + s << "section: options" << std::endl; + const CConfig::CScreenOptions* options = config.getOptions(""); + if (options != NULL && options->size() > 0) { + for (CConfig::CScreenOptions::const_iterator + option = options->begin(); + option != options->end(); ++option) { + const char* name = CConfig::getOptionName(option->first); + CString value = CConfig::getOptionValue(option->first, + option->second); + if (name != NULL && !value.empty()) { + s << "\t" << name << " = " << value << std::endl; + } + } + } if (config.m_synergyAddress.isValid()) { - s << "\taddress=" << config.m_synergyAddress.getHostname().c_str() << - std::endl; + s << "\taddress = " << + config.m_synergyAddress.getHostname().c_str() << std::endl; } if (config.m_httpAddress.isValid()) { - s << "\thttp=" << config.m_httpAddress.getHostname().c_str() << - std::endl; + s << "\thttp = " << + config.m_httpAddress.getHostname().c_str() << std::endl; } s << "end" << std::endl; @@ -953,10 +1002,10 @@ operator<<(std::ostream& s, const CConfig& config) for (CConfig::CScreenOptions::const_iterator option = options->begin(); option != options->end(); ++option) { - const char* name = CConfig::getOptionName(option->first); - const char* value = CConfig::getOptionValue(option->first, + const char* name = CConfig::getOptionName(option->first); + CString value = CConfig::getOptionValue(option->first, option->second); - if (name != NULL && value != NULL) { + if (name != NULL && !value.empty()) { s << "\t\t" << name << " = " << value << std::endl; } } diff --git a/lib/server/CConfig.h b/lib/server/CConfig.h index 599e436e..a2fd31b3 100644 --- a/lib/server/CConfig.h +++ b/lib/server/CConfig.h @@ -292,12 +292,13 @@ public: private: static bool readLine(std::istream&, CString&); - static bool parseBoolean(const CString&); + static OptionValue parseBoolean(const CString&); + static OptionValue parseInt(const CString&); static OptionValue parseModifierKey(const CString&); static const char* getOptionName(OptionID); - static const char* getOptionValue(OptionID, OptionValue); + static CString getOptionValue(OptionID, OptionValue); void readSection(std::istream&); - void readSectionNetwork(std::istream&); + void readSectionOptions(std::istream&); void readSectionScreens(std::istream&); void readSectionLinks(std::istream&); void readSectionAliases(std::istream&); diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp index 9ae41237..cb636300 100644 --- a/lib/server/CServer.cpp +++ b/lib/server/CServer.cpp @@ -217,12 +217,29 @@ CServer::setConfig(const CConfig& config) CLock lock(&m_mutex); m_config = config; + // process global options + const CConfig::CScreenOptions* options = m_config.getOptions(""); + if (options != NULL && options->size() > 0) { +/* + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + const OptionID id = index->first; + const OptionValue value = index->second; + } +*/ + } + // tell primary screen about reconfiguration if (m_primaryClient != NULL) { m_primaryClient->reconfigure(getActivePrimarySides()); } - // FIXME -- tell all (connected) clients about current options + // tell all (connected) clients about current options + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + IClient* client = index->second; + sendOptions(client); + } return true; } @@ -1622,20 +1639,31 @@ CServer::sendOptions(IClient* client) const { // note -- must be locked on entry - // look up options for client. we're done if there aren't any. + COptionsList optionsList; + + // look up options for client const CConfig::CScreenOptions* options = m_config.getOptions(client->getName()); - if (options == NULL || options->size() == 0) { - return; + if (options != NULL && options->size() > 0) { + // convert options to a more convenient form for sending + optionsList.reserve(2 * options->size()); + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + optionsList.push_back(index->first); + optionsList.push_back(static_cast(index->second)); + } } - // convert options to a more convenient form for sending - COptionsList optionsList; - optionsList.reserve(2 * options->size()); - for (CConfig::CScreenOptions::const_iterator index = options->begin(); - index != options->end(); ++index) { - optionsList.push_back(index->first); - optionsList.push_back(static_cast(index->second)); + // look up global options + options = m_config.getOptions(""); + if (options != NULL && options->size() > 0) { + // convert options to a more convenient form for sending + optionsList.reserve(optionsList.size() + 2 * options->size()); + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + optionsList.push_back(index->first); + optionsList.push_back(static_cast(index->second)); + } } // send the options diff --git a/lib/synergy/OptionTypes.h b/lib/synergy/OptionTypes.h index 31c72185..5a9ebf10 100644 --- a/lib/synergy/OptionTypes.h +++ b/lib/synergy/OptionTypes.h @@ -50,6 +50,7 @@ static const OptionID kOptionModifierMapForControl = OPTION_CODE("MMFC"); static const OptionID kOptionModifierMapForAlt = OPTION_CODE("MMFA"); static const OptionID kOptionModifierMapForMeta = OPTION_CODE("MMFM"); static const OptionID kOptionModifierMapForSuper = OPTION_CODE("MMFR"); +static const OptionID kOptionHeartbeat = OPTION_CODE("HART"); //@} #undef OPTION_CODE diff --git a/lib/synergy/ProtocolTypes.h b/lib/synergy/ProtocolTypes.h index 52201320..b37d1265 100644 --- a/lib/synergy/ProtocolTypes.h +++ b/lib/synergy/ProtocolTypes.h @@ -31,8 +31,8 @@ static const UInt32 kMaxHelloLength = 1024; // heartbeat. static const double kHeartRate = -1.0; -// time without a heartbeat that constitutes death -static const double kHeartDeath = 3.0 * kHeartRate; +// number of skipped heartbeats that constitutes death +static const double kHeartBeatsUntilDeath = 3.0; // direction constants enum EDirection {