diff --git a/src/lib/client/CMakeLists.txt b/src/lib/client/CMakeLists.txt index db03af6c9..ed5bd7ac6 100644 --- a/src/lib/client/CMakeLists.txt +++ b/src/lib/client/CMakeLists.txt @@ -8,6 +8,10 @@ add_library(client STATIC Client.h ServerProxy.cpp ServerProxy.h + ServerProxy1_7.cpp + ServerProxy1_7.h + ServerProxy1_8.cpp + ServerProxy1_8.h ) target_link_libraries(client PUBLIC common) diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp index 7fcb4f64d..30f0e85ee 100644 --- a/src/lib/client/Client.cpp +++ b/src/lib/client/Client.cpp @@ -12,6 +12,8 @@ #include "base/IEventQueue.h" #include "base/Log.h" #include "client/ServerProxy.h" +#include "client/ServerProxy1_7.h" +#include "client/ServerProxy1_8.h" #include "common/NetworkProtocol.h" #include "common/Settings.h" #include "deskflow/Clipboard.h" @@ -465,18 +467,38 @@ void Client::setupConnection() }); } -void Client::setupScreen() +bool Client::setupScreen(int16_t protocolMinor) { assert(m_server == nullptr); m_ready = false; - m_server = new ServerProxy(this, m_stream, m_events); - m_events->addHandler(EventTypes::ScreenShapeChanged, getEventTarget(), [this](const auto &) { - handleShapeChanged(); - }); - m_events->addHandler(EventTypes::ClipboardGrabbed, getEventTarget(), [this](const auto &e) { - handleClipboardGrabbed(e); - }); + + // only 1.6 and later have a proxy: the clipboard, mouse wheel and key message formats + // differ below that, and nothing older (synergy 1.4 and earlier) still needs supporting. + // a version with no case is refused by the hello handler. + switch (protocolMinor) { + case 6: + m_server = new ServerProxy(this, m_stream, m_events); + break; + case 7: + m_server = new ServerProxy1_7(this, m_stream, m_events); + break; + case 8: + m_server = new ServerProxy1_8(this, m_stream, m_events); + break; + default: + break; + } + + if (m_server != nullptr) { + m_events->addHandler(EventTypes::ScreenShapeChanged, getEventTarget(), [this](const auto &) { + handleShapeChanged(); + }); + m_events->addHandler(EventTypes::ClipboardGrabbed, getEventTarget(), [this](const auto &e) { + handleClipboardGrabbed(e); + }); + } + return m_server != nullptr; } void Client::setupTimer() @@ -683,6 +705,17 @@ void Client::handleHello() ); } + // no proxy speaks the negotiated version, so hang up as incompatible rather than + // talk a version the client does not implement, the same rule the server applies + // when it picks a client proxy + if (!setupScreen(helloBackMinor)) { + LOG_WARN("server protocol version not supported: %d.%d", serverMajor, serverMinor); + sendConnectionFailedEvent(IncompatibleClientException(serverMajor, serverMinor).what()); + cleanupTimer(); + cleanupConnection(); + return; + } + LOG_DEBUG("saying hello back with version %s %d.%d", protocolName.c_str(), kProtocolMajorVersion, helloBackMinor); // dynamically build write format for hello back since `ProtocolUtil::writef` @@ -691,7 +724,6 @@ void Client::handleHello() ProtocolUtil::writef(m_stream, helloBackMessage.c_str(), kProtocolMajorVersion, helloBackMinor, &m_name); // now connected but waiting to complete handshake - setupScreen(); cleanupTimer(); // make sure we process any remaining messages later. we won't diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h index a0cc64b95..71095625b 100644 --- a/src/lib/client/Client.h +++ b/src/lib/client/Client.h @@ -197,7 +197,7 @@ private: void sendConnectionFailedEvent(const char *msg); void setupConnecting(); void setupConnection(); - void setupScreen(); + bool setupScreen(int16_t protocolMinor); void setupTimer(); void cleanup(); void cleanupConnecting(); diff --git a/src/lib/client/ServerProxy.cpp b/src/lib/client/ServerProxy.cpp index 6f16a3c77..3204aec72 100644 --- a/src/lib/client/ServerProxy.cpp +++ b/src/lib/client/ServerProxy.cpp @@ -1,7 +1,7 @@ /* * Deskflow -- mouse and keyboard sharing utility * SPDX-FileCopyrightText: (C) 2025 Deskflow Developers - * SPDX-FileCopyrightText: (C) 2012 - 2016 Synergy App Ltd + * SPDX-FileCopyrightText: (C) 2012 - 2016, 2026 Synergy App Ltd * SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ @@ -18,7 +18,6 @@ #include "deskflow/ProtocolTypes.h" #include "deskflow/ProtocolUtil.h" #include "deskflow/StreamChunker.h" -#include "deskflow/ipc/CoreIpc.h" #include "io/IStream.h" #include @@ -140,12 +139,7 @@ ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t * setOptions(); // handshake is complete - m_parser = &ServerProxy::parseMessage; - - if (const auto missedKeyboardLayouts = m_layoutManager.getMissedLayouts(); !missedKeyboardLayouts.empty()) { - LOG_WARN("server layouts missing on this computer: %s", missedKeyboardLayouts.c_str()); - ipcSendToClient("missingKeyboardLayouts", QString::fromStdString(missedKeyboardLayouts)); - } + m_parser = &ServerProxy::handleMessage; m_client->handshakeComplete(); } @@ -196,8 +190,6 @@ ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t * LOG_ERR("server disconnected due to a protocol error"); requestRefuseConnection(ProtocolError, "server reported a protocol error"); return Disconnect; - } else if (memcmp(code, kMsgDLanguageSynchronisation, 4) == 0) { - setServerLanguages(); } else { return Unknown; } @@ -205,6 +197,22 @@ ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t * return Okay; } +ServerProxy::ConnectionResult ServerProxy::handleMessage(const uint8_t *code) +{ + const auto result = parseMessage(code); + if (result == ConnectionResult::Okay) { + // send a reply. this is intended to work around a delay when + // running a linux server and an OS X (any BSD?) client. the + // client waits to send an ACK (if the system control flag + // net.inet.tcp.delayed_ack is 1) in hopes of piggybacking it + // on a data packet. we provide that packet here. i don't + // know why a delayed ACK should cause the server to wait since + // TCP_NODELAY is enabled. + ProtocolUtil::writef(m_stream, kMsgCNoop); + } + return result; +} + ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code) { using enum ConnectionResult; @@ -231,18 +239,6 @@ ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code) keyDown(id, mask, button, ""); } - else if (memcmp(code, kMsgDKeyDown, 4) == 0) { - std::string lang; - uint16_t id = 0; - uint16_t mask = 0; - uint16_t button = 0; - - ProtocolUtil::readf(m_stream, kMsgDKeyDown + 4, &id, &mask, &button, &lang); - LOG_VERBOSE("recv key down id=0x%08x, mask=0x%04x, button=0x%04x, lang=\"%s\"", id, mask, button, lang.c_str()); - - keyDown(id, mask, button, lang); - } - else if (memcmp(code, kMsgDKeyUp, 4) == 0) { keyUp(); } @@ -255,8 +251,15 @@ ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code) mouseUp(); } - else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) { - keyRepeat(); + else if (memcmp(code, kMsgDKeyRepeat1_1, 4) == 0) { + uint16_t id = 0; + uint16_t mask = 0; + uint16_t count = 0; + uint16_t button = 0; + ProtocolUtil::readf(m_stream, kMsgDKeyRepeat1_1 + 4, &id, &mask, &count, &button); + LOG_VERBOSE("recv key repeat id=0x%08x, mask=0x%04x, count=%d, button=0x%04x", id, mask, count, button); + + keyRepeat(id, mask, count, button, ""); } else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { @@ -305,10 +308,6 @@ ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code) setOptions(); } - else if (memcmp(code, kMsgDSecureInputNotification, 4) == 0) { - secureInputNotification(); - } - else if (memcmp(code, kMsgCClose, 4) == 0) { // server wants us to hangup LOG_VERBOSE("recv close"); @@ -322,15 +321,6 @@ ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code) return Unknown; } - // send a reply. this is intended to work around a delay when - // running a linux server and an OS X (any BSD?) client. the - // client waits to send an ACK (if the system control flag - // net.inet.tcp.delayed_ack is 1) in hopes of piggybacking it - // on a data packet. we provide that packet here. i don't - // know why a delayed ACK should cause the server to wait since - // TCP_NODELAY is enabled. - ProtocolUtil::writef(m_stream, kMsgCNoop); - return Okay; } @@ -521,8 +511,6 @@ void ServerProxy::enter() m_dxMouse = 0; m_dyMouse = 0; m_seqNum = seqNum; - m_serverLayout = ""; - m_isUserNotifiedAboutLayoutSyncError = false; // forward m_client->enter(x, y, seqNum, static_cast(mask), false); @@ -588,45 +576,25 @@ void ServerProxy::grabClipboard() void ServerProxy::keyDown(uint16_t id, uint16_t mask, uint16_t button, const std::string &lang) { - // get mouse up to date flushCompressedMouse(); - setActiveServerLanguage(lang); - // translate KeyID id2 = translateKey(static_cast(id)); KeyModifierMask mask2 = translateModifierMask(static_cast(mask)); if (id2 != static_cast(id) || mask2 != static_cast(mask)) LOG_VERBOSE("key down translated to id=0x%08x, mask=0x%04x", id2, mask2); - // forward m_client->keyDown(id2, mask2, button, lang); } -void ServerProxy::keyRepeat() +void ServerProxy::keyRepeat(uint16_t id, uint16_t mask, uint16_t count, uint16_t button, const std::string &lang) { - // get mouse up to date flushCompressedMouse(); - // parse - uint16_t id; - uint16_t mask; - uint16_t count; - uint16_t button; - std::string lang; - ProtocolUtil::readf(m_stream, kMsgDKeyRepeat + 4, &id, &mask, &count, &button, &lang); - LOG( - (CLOG_VERBOSE "recv key repeat id=0x%08x, mask=0x%04x, count=%d, " - "button=0x%04x, lang=\"%s\"", - id, mask, count, button, lang.c_str()) - ); - - // translate KeyID id2 = translateKey(static_cast(id)); KeyModifierMask mask2 = translateModifierMask(static_cast(mask)); if (id2 != static_cast(id) || mask2 != static_cast(mask)) LOG_VERBOSE("key repeat translated to id=0x%08x, mask=0x%04x", id2, mask2); - // forward m_client->keyRepeat(id2, mask2, count, button, lang); } @@ -841,38 +809,3 @@ void ServerProxy::infoAcknowledgment() LOG_VERBOSE("recv info acknowledgment"); m_ignoreMouse = false; } - -void ServerProxy::secureInputNotification() -{ - std::string app; - ProtocolUtil::readf(m_stream, kMsgDSecureInputNotification + 4, &app); - LOG_INFO("application \"%s\" is blocking the keyboard", app.c_str()); -} - -void ServerProxy::setServerLanguages() -{ - std::string serverLayout; - ProtocolUtil::readf(m_stream, kMsgDLanguageSynchronisation + 4, &serverLayout); - m_layoutManager.setRemoteLayouts(serverLayout); -} - -void ServerProxy::setActiveServerLanguage(const std::string_view &language) -{ - if (!language.empty() && (language.size() > 0)) { - if (m_serverLayout != language) { - m_isUserNotifiedAboutLayoutSyncError = false; - m_serverLayout = language; - } - - if (!m_layoutManager.isLayoutInstalled(m_serverLayout)) { - if (!m_isUserNotifiedAboutLayoutSyncError) { - LOG_WARN("current server layout is not installed on client"); - m_isUserNotifiedAboutLayoutSyncError = true; - } - } else { - m_isUserNotifiedAboutLayoutSyncError = false; - } - } else { - LOG_VERBOSE("active server layout is empty"); - } -} diff --git a/src/lib/client/ServerProxy.h b/src/lib/client/ServerProxy.h index 6c3bbedc2..6291dd32b 100644 --- a/src/lib/client/ServerProxy.h +++ b/src/lib/client/ServerProxy.h @@ -1,7 +1,7 @@ /* * Deskflow -- mouse and keyboard sharing utility * SPDX-FileCopyrightText: (C) 2025 Deskflow Developers - * SPDX-FileCopyrightText: (C) 2012 - 2016 Synergy App Ltd + * SPDX-FileCopyrightText: (C) 2012 - 2016, 2026 Synergy App Ltd * SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ @@ -12,7 +12,6 @@ #include "deskflow/ClipboardChunk.h" #include "deskflow/ClipboardTypes.h" #include "deskflow/KeyTypes.h" -#include "deskflow/KeyboardLayoutManager.h" class Client; class ClientInfo; @@ -23,10 +22,12 @@ class IStream; } class IEventQueue; -//! Proxy for server +//! Proxy for server implementing protocol version 1.6 /*! This class acts a proxy for the server, converting calls into messages to the server and messages from the server to calls on the client. +It implements 1.6, the oldest version the client supports; later +versions subclass it and add their messages. */ class ServerProxy { @@ -38,7 +39,7 @@ public: ServerProxy(Client *client, deskflow::IStream *stream, IEventQueue *events); ServerProxy(ServerProxy const &) = delete; ServerProxy(ServerProxy &&) = delete; - ~ServerProxy(); + virtual ~ServerProxy(); ServerProxy &operator=(ServerProxy const &) = delete; ServerProxy &operator=(ServerProxy &&) = delete; @@ -59,8 +60,16 @@ protected: Unknown, Disconnect }; - ConnectionResult parseHandshakeMessage(const uint8_t *code); - ConnectionResult parseMessage(const uint8_t *code); + + virtual ConnectionResult parseHandshakeMessage(const uint8_t *code); + virtual ConnectionResult parseMessage(const uint8_t *code); + void keyDown(uint16_t id, uint16_t mask, uint16_t button, const std::string &lang); + void keyRepeat(uint16_t id, uint16_t mask, uint16_t count, uint16_t button, const std::string &lang); + + deskflow::IStream *getStream() const + { + return m_stream; + } private: // if compressing mouse motion then send the last motion now @@ -77,6 +86,7 @@ private: // event handlers void handleData(); + ConnectionResult handleMessage(const uint8_t *code); void handleKeepAliveAlarm(); void requestDisconnect(const char *message); void requestRefuseConnection(deskflow::core::ConnectionRefusal reason, const char *message); @@ -86,8 +96,6 @@ private: void leave(); void setClipboard(); void grabClipboard(); - void keyDown(uint16_t id, uint16_t mask, uint16_t button, const std::string &lang); - void keyRepeat(); void keyUp(); void mouseDown(); void mouseUp(); @@ -99,9 +107,6 @@ private: void setOptions(); void queryInfo(); void infoAcknowledgment(); - void secureInputNotification(); - void setServerLanguages(); - void setActiveServerLanguage(const std::string_view &language); private: using MessageParser = ConnectionResult (ServerProxy::*)(const uint8_t *); @@ -127,9 +132,6 @@ private: MessageParser m_parser = &ServerProxy::parseHandshakeMessage; IEventQueue *m_events = nullptr; - std::string m_serverLayout = ""; std::string m_clipboardDataCached; ClipboardChunkAssemblyState m_clipboardChunkState; - bool m_isUserNotifiedAboutLayoutSyncError = false; - deskflow::KeyboardLayoutManager m_layoutManager; }; diff --git a/src/lib/client/ServerProxy1_7.cpp b/src/lib/client/ServerProxy1_7.cpp new file mode 100644 index 000000000..6c12476ad --- /dev/null +++ b/src/lib/client/ServerProxy1_7.cpp @@ -0,0 +1,37 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2026 Synergy App Ltd + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include "client/ServerProxy1_7.h" + +#include "base/Log.h" +#include "deskflow/ProtocolTypes.h" +#include "deskflow/ProtocolUtil.h" + +#include +#include + +ServerProxy1_7::ServerProxy1_7(Client *client, deskflow::IStream *stream, IEventQueue *events) + : ServerProxy(client, stream, events) +{ +} + +ServerProxy::ConnectionResult ServerProxy1_7::parseMessage(const uint8_t *code) +{ + auto result = ConnectionResult::Okay; + if (memcmp(code, kMsgDSecureInputNotification, 4) == 0) { + secureInputNotification(); + } else { + result = ServerProxy::parseMessage(code); + } + return result; +} + +void ServerProxy1_7::secureInputNotification() const +{ + std::string app; + ProtocolUtil::readf(getStream(), kMsgDSecureInputNotification + 4, &app); + LOG_INFO("application \"%s\" is blocking the keyboard", app.c_str()); +} diff --git a/src/lib/client/ServerProxy1_7.h b/src/lib/client/ServerProxy1_7.h new file mode 100644 index 000000000..af1e58914 --- /dev/null +++ b/src/lib/client/ServerProxy1_7.h @@ -0,0 +1,23 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2026 Synergy App Ltd + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#pragma once + +#include "client/ServerProxy.h" + +//! Proxy for server implementing protocol version 1.7 +class ServerProxy1_7 : public ServerProxy +{ +public: + ServerProxy1_7(Client *client, deskflow::IStream *stream, IEventQueue *events); + ~ServerProxy1_7() override = default; + +protected: + ConnectionResult parseMessage(const uint8_t *code) override; + +private: + void secureInputNotification() const; +}; diff --git a/src/lib/client/ServerProxy1_8.cpp b/src/lib/client/ServerProxy1_8.cpp new file mode 100644 index 000000000..ab0ae5d3a --- /dev/null +++ b/src/lib/client/ServerProxy1_8.cpp @@ -0,0 +1,103 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2026 Synergy App Ltd + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include "client/ServerProxy1_8.h" + +#include "base/Log.h" +#include "deskflow/ProtocolTypes.h" +#include "deskflow/ProtocolUtil.h" +#include "deskflow/ipc/CoreIpc.h" + +#include +#include + +#include + +ServerProxy1_8::ServerProxy1_8(Client *client, deskflow::IStream *stream, IEventQueue *events) + : ServerProxy1_7(client, stream, events) +{ +} + +ServerProxy::ConnectionResult ServerProxy1_8::parseHandshakeMessage(const uint8_t *code) +{ + auto result = ConnectionResult::Okay; + if (memcmp(code, kMsgDLanguageSynchronisation, 4) == 0) { + setServerLayouts(); + } else { + result = ServerProxy1_7::parseHandshakeMessage(code); + } + return result; +} + +ServerProxy::ConnectionResult ServerProxy1_8::parseMessage(const uint8_t *code) +{ + auto result = ConnectionResult::Okay; + if (memcmp(code, kMsgDKeyDown, 4) == 0) { + uint16_t id = 0; + uint16_t mask = 0; + uint16_t button = 0; + std::string lang; + ProtocolUtil::readf(getStream(), kMsgDKeyDown + 4, &id, &mask, &button, &lang); + LOG_VERBOSE("recv key down id=0x%08x, mask=0x%04x, button=0x%04x, lang=\"%s\"", id, mask, button, lang.c_str()); + + setActiveServerLayout(lang); + keyDown(id, mask, button, lang); + } else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) { + uint16_t id = 0; + uint16_t mask = 0; + uint16_t count = 0; + uint16_t button = 0; + std::string lang; + ProtocolUtil::readf(getStream(), kMsgDKeyRepeat + 4, &id, &mask, &count, &button, &lang); + LOG_VERBOSE( + "recv key repeat id=0x%08x, mask=0x%04x, count=%d, button=0x%04x, lang=\"%s\"", id, mask, count, button, + lang.c_str() + ); + + keyRepeat(id, mask, count, button, lang); + } else if (memcmp(code, kMsgCEnter, 4) == 0) { + m_serverLayout.clear(); + m_isUserNotifiedAboutLayoutSyncError = false; + result = ServerProxy1_7::parseMessage(code); + } else { + result = ServerProxy1_7::parseMessage(code); + } + return result; +} + +void ServerProxy1_8::setServerLayouts() +{ + std::string serverLayouts; + ProtocolUtil::readf(getStream(), kMsgDLanguageSynchronisation + 4, &serverLayouts); + m_layoutManager.setRemoteLayouts(serverLayouts); + + if (const auto missingLayouts = m_layoutManager.getMissedLayouts(); !missingLayouts.empty()) { + LOG_WARN("server layouts missing on this computer: %s", missingLayouts.c_str()); + ipcSendToClient("missingKeyboardLayouts", QString::fromStdString(missingLayouts)); + } +} + +void ServerProxy1_8::setActiveServerLayout(const std::string_view &layout) +{ + if (layout.empty()) { + LOG_VERBOSE("active server layout is empty"); + return; + } + + if (m_serverLayout != layout) { + m_isUserNotifiedAboutLayoutSyncError = false; + m_serverLayout = layout; + } + + if (!m_layoutManager.isLayoutInstalled(m_serverLayout)) { + if (!m_isUserNotifiedAboutLayoutSyncError) { + LOG_WARN("current server layout is not installed on client"); + m_isUserNotifiedAboutLayoutSyncError = true; + } + } else { + m_isUserNotifiedAboutLayoutSyncError = false; + } +} diff --git a/src/lib/client/ServerProxy1_8.h b/src/lib/client/ServerProxy1_8.h new file mode 100644 index 000000000..74eabe7d2 --- /dev/null +++ b/src/lib/client/ServerProxy1_8.h @@ -0,0 +1,33 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2026 Synergy App Ltd + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#pragma once + +#include "client/ServerProxy1_7.h" +#include "deskflow/KeyboardLayoutManager.h" + +#include +#include + +//! Proxy for server implementing protocol version 1.8 +class ServerProxy1_8 : public ServerProxy1_7 +{ +public: + ServerProxy1_8(Client *client, deskflow::IStream *stream, IEventQueue *events); + ~ServerProxy1_8() override = default; + +protected: + ConnectionResult parseHandshakeMessage(const uint8_t *code) override; + ConnectionResult parseMessage(const uint8_t *code) override; + +private: + void setServerLayouts(); + void setActiveServerLayout(const std::string_view &layout); + + std::string m_serverLayout; + bool m_isUserNotifiedAboutLayoutSyncError = false; + deskflow::KeyboardLayoutManager m_layoutManager; +};