fix(client): add per-version server proxies for older servers

A client that downgrades to a 1.6 or 1.7 server parsed key repeat with the 1.8 format and swallowed the messages behind it. ServerProxy is now the 1.6 form with 1.7 and 1.8 subclasses picked by the negotiated version, mirroring the ClientProxy classes.
This commit is contained in:
Nick Bolton 2026-09-18 16:27:24 +01:00
parent 6638076b85
commit 49e6293a4d
9 changed files with 286 additions and 119 deletions

View file

@ -8,6 +8,10 @@ add_library(client STATIC
Client.h Client.h
ServerProxy.cpp ServerProxy.cpp
ServerProxy.h ServerProxy.h
ServerProxy1_7.cpp
ServerProxy1_7.h
ServerProxy1_8.cpp
ServerProxy1_8.h
) )
target_link_libraries(client PUBLIC common) target_link_libraries(client PUBLIC common)

View file

@ -12,6 +12,8 @@
#include "base/IEventQueue.h" #include "base/IEventQueue.h"
#include "base/Log.h" #include "base/Log.h"
#include "client/ServerProxy.h" #include "client/ServerProxy.h"
#include "client/ServerProxy1_7.h"
#include "client/ServerProxy1_8.h"
#include "common/NetworkProtocol.h" #include "common/NetworkProtocol.h"
#include "common/Settings.h" #include "common/Settings.h"
#include "deskflow/Clipboard.h" #include "deskflow/Clipboard.h"
@ -465,12 +467,30 @@ void Client::setupConnection()
}); });
} }
void Client::setupScreen() bool Client::setupScreen(int16_t protocolMinor)
{ {
assert(m_server == nullptr); assert(m_server == nullptr);
m_ready = false; m_ready = false;
// 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); 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 &) { m_events->addHandler(EventTypes::ScreenShapeChanged, getEventTarget(), [this](const auto &) {
handleShapeChanged(); handleShapeChanged();
}); });
@ -478,6 +498,8 @@ void Client::setupScreen()
handleClipboardGrabbed(e); handleClipboardGrabbed(e);
}); });
} }
return m_server != nullptr;
}
void Client::setupTimer() 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); 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` // 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); ProtocolUtil::writef(m_stream, helloBackMessage.c_str(), kProtocolMajorVersion, helloBackMinor, &m_name);
// now connected but waiting to complete handshake // now connected but waiting to complete handshake
setupScreen();
cleanupTimer(); cleanupTimer();
// make sure we process any remaining messages later. we won't // make sure we process any remaining messages later. we won't

View file

@ -197,7 +197,7 @@ private:
void sendConnectionFailedEvent(const char *msg); void sendConnectionFailedEvent(const char *msg);
void setupConnecting(); void setupConnecting();
void setupConnection(); void setupConnection();
void setupScreen(); bool setupScreen(int16_t protocolMinor);
void setupTimer(); void setupTimer();
void cleanup(); void cleanup();
void cleanupConnecting(); void cleanupConnecting();

View file

@ -1,7 +1,7 @@
/* /*
* Deskflow -- mouse and keyboard sharing utility * Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers * 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-FileCopyrightText: (C) 2002 Chris Schoeneman
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/ */
@ -18,7 +18,6 @@
#include "deskflow/ProtocolTypes.h" #include "deskflow/ProtocolTypes.h"
#include "deskflow/ProtocolUtil.h" #include "deskflow/ProtocolUtil.h"
#include "deskflow/StreamChunker.h" #include "deskflow/StreamChunker.h"
#include "deskflow/ipc/CoreIpc.h"
#include "io/IStream.h" #include "io/IStream.h"
#include <cstring> #include <cstring>
@ -140,12 +139,7 @@ ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t *
setOptions(); setOptions();
// handshake is complete // handshake is complete
m_parser = &ServerProxy::parseMessage; m_parser = &ServerProxy::handleMessage;
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_client->handshakeComplete(); m_client->handshakeComplete();
} }
@ -196,8 +190,6 @@ ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t *
LOG_ERR("server disconnected due to a protocol error"); LOG_ERR("server disconnected due to a protocol error");
requestRefuseConnection(ProtocolError, "server reported a protocol error"); requestRefuseConnection(ProtocolError, "server reported a protocol error");
return Disconnect; return Disconnect;
} else if (memcmp(code, kMsgDLanguageSynchronisation, 4) == 0) {
setServerLanguages();
} else { } else {
return Unknown; return Unknown;
} }
@ -205,6 +197,22 @@ ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t *
return Okay; 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) ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code)
{ {
using enum ConnectionResult; using enum ConnectionResult;
@ -231,18 +239,6 @@ ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code)
keyDown(id, mask, button, ""); 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) { else if (memcmp(code, kMsgDKeyUp, 4) == 0) {
keyUp(); keyUp();
} }
@ -255,8 +251,15 @@ ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code)
mouseUp(); mouseUp();
} }
else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) { else if (memcmp(code, kMsgDKeyRepeat1_1, 4) == 0) {
keyRepeat(); 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) { else if (memcmp(code, kMsgCKeepAlive, 4) == 0) {
@ -305,10 +308,6 @@ ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code)
setOptions(); setOptions();
} }
else if (memcmp(code, kMsgDSecureInputNotification, 4) == 0) {
secureInputNotification();
}
else if (memcmp(code, kMsgCClose, 4) == 0) { else if (memcmp(code, kMsgCClose, 4) == 0) {
// server wants us to hangup // server wants us to hangup
LOG_VERBOSE("recv close"); LOG_VERBOSE("recv close");
@ -322,15 +321,6 @@ ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code)
return Unknown; 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; return Okay;
} }
@ -521,8 +511,6 @@ void ServerProxy::enter()
m_dxMouse = 0; m_dxMouse = 0;
m_dyMouse = 0; m_dyMouse = 0;
m_seqNum = seqNum; m_seqNum = seqNum;
m_serverLayout = "";
m_isUserNotifiedAboutLayoutSyncError = false;
// forward // forward
m_client->enter(x, y, seqNum, static_cast<KeyModifierMask>(mask), false); m_client->enter(x, y, seqNum, static_cast<KeyModifierMask>(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) void ServerProxy::keyDown(uint16_t id, uint16_t mask, uint16_t button, const std::string &lang)
{ {
// get mouse up to date
flushCompressedMouse(); flushCompressedMouse();
setActiveServerLanguage(lang);
// translate
KeyID id2 = translateKey(static_cast<KeyID>(id)); KeyID id2 = translateKey(static_cast<KeyID>(id));
KeyModifierMask mask2 = translateModifierMask(static_cast<KeyModifierMask>(mask)); KeyModifierMask mask2 = translateModifierMask(static_cast<KeyModifierMask>(mask));
if (id2 != static_cast<KeyID>(id) || mask2 != static_cast<KeyModifierMask>(mask)) if (id2 != static_cast<KeyID>(id) || mask2 != static_cast<KeyModifierMask>(mask))
LOG_VERBOSE("key down translated to id=0x%08x, mask=0x%04x", id2, mask2); LOG_VERBOSE("key down translated to id=0x%08x, mask=0x%04x", id2, mask2);
// forward
m_client->keyDown(id2, mask2, button, lang); 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(); 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<KeyID>(id)); KeyID id2 = translateKey(static_cast<KeyID>(id));
KeyModifierMask mask2 = translateModifierMask(static_cast<KeyModifierMask>(mask)); KeyModifierMask mask2 = translateModifierMask(static_cast<KeyModifierMask>(mask));
if (id2 != static_cast<KeyID>(id) || mask2 != static_cast<KeyModifierMask>(mask)) if (id2 != static_cast<KeyID>(id) || mask2 != static_cast<KeyModifierMask>(mask))
LOG_VERBOSE("key repeat translated to id=0x%08x, mask=0x%04x", id2, mask2); LOG_VERBOSE("key repeat translated to id=0x%08x, mask=0x%04x", id2, mask2);
// forward
m_client->keyRepeat(id2, mask2, count, button, lang); m_client->keyRepeat(id2, mask2, count, button, lang);
} }
@ -841,38 +809,3 @@ void ServerProxy::infoAcknowledgment()
LOG_VERBOSE("recv info acknowledgment"); LOG_VERBOSE("recv info acknowledgment");
m_ignoreMouse = false; 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");
}
}

View file

@ -1,7 +1,7 @@
/* /*
* Deskflow -- mouse and keyboard sharing utility * Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers * 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-FileCopyrightText: (C) 2002 Chris Schoeneman
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/ */
@ -12,7 +12,6 @@
#include "deskflow/ClipboardChunk.h" #include "deskflow/ClipboardChunk.h"
#include "deskflow/ClipboardTypes.h" #include "deskflow/ClipboardTypes.h"
#include "deskflow/KeyTypes.h" #include "deskflow/KeyTypes.h"
#include "deskflow/KeyboardLayoutManager.h"
class Client; class Client;
class ClientInfo; class ClientInfo;
@ -23,10 +22,12 @@ class IStream;
} }
class IEventQueue; 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 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. 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 class ServerProxy
{ {
@ -38,7 +39,7 @@ public:
ServerProxy(Client *client, deskflow::IStream *stream, IEventQueue *events); ServerProxy(Client *client, deskflow::IStream *stream, IEventQueue *events);
ServerProxy(ServerProxy const &) = delete; ServerProxy(ServerProxy const &) = delete;
ServerProxy(ServerProxy &&) = delete; ServerProxy(ServerProxy &&) = delete;
~ServerProxy(); virtual ~ServerProxy();
ServerProxy &operator=(ServerProxy const &) = delete; ServerProxy &operator=(ServerProxy const &) = delete;
ServerProxy &operator=(ServerProxy &&) = delete; ServerProxy &operator=(ServerProxy &&) = delete;
@ -59,8 +60,16 @@ protected:
Unknown, Unknown,
Disconnect 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: private:
// if compressing mouse motion then send the last motion now // if compressing mouse motion then send the last motion now
@ -77,6 +86,7 @@ private:
// event handlers // event handlers
void handleData(); void handleData();
ConnectionResult handleMessage(const uint8_t *code);
void handleKeepAliveAlarm(); void handleKeepAliveAlarm();
void requestDisconnect(const char *message); void requestDisconnect(const char *message);
void requestRefuseConnection(deskflow::core::ConnectionRefusal reason, const char *message); void requestRefuseConnection(deskflow::core::ConnectionRefusal reason, const char *message);
@ -86,8 +96,6 @@ private:
void leave(); void leave();
void setClipboard(); void setClipboard();
void grabClipboard(); void grabClipboard();
void keyDown(uint16_t id, uint16_t mask, uint16_t button, const std::string &lang);
void keyRepeat();
void keyUp(); void keyUp();
void mouseDown(); void mouseDown();
void mouseUp(); void mouseUp();
@ -99,9 +107,6 @@ private:
void setOptions(); void setOptions();
void queryInfo(); void queryInfo();
void infoAcknowledgment(); void infoAcknowledgment();
void secureInputNotification();
void setServerLanguages();
void setActiveServerLanguage(const std::string_view &language);
private: private:
using MessageParser = ConnectionResult (ServerProxy::*)(const uint8_t *); using MessageParser = ConnectionResult (ServerProxy::*)(const uint8_t *);
@ -127,9 +132,6 @@ private:
MessageParser m_parser = &ServerProxy::parseHandshakeMessage; MessageParser m_parser = &ServerProxy::parseHandshakeMessage;
IEventQueue *m_events = nullptr; IEventQueue *m_events = nullptr;
std::string m_serverLayout = "";
std::string m_clipboardDataCached; std::string m_clipboardDataCached;
ClipboardChunkAssemblyState m_clipboardChunkState; ClipboardChunkAssemblyState m_clipboardChunkState;
bool m_isUserNotifiedAboutLayoutSyncError = false;
deskflow::KeyboardLayoutManager m_layoutManager;
}; };

View file

@ -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 <cstring>
#include <string>
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());
}

View file

@ -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;
};

View file

@ -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 <cstring>
#include <string>
#include <QString>
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;
}
}

View file

@ -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 <string>
#include <string_view>
//! 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;
};