/* * Deskflow -- mouse and keyboard sharing utility * SPDX-FileCopyrightText: (C) 2025 Deskflow Developers * SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd. * SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ #include "client/ServerProxy.h" #include "base/BaseException.h" #include "base/IEventQueue.h" #include "base/Log.h" #include "client/Client.h" #include "deskflow/AppUtil.h" #include "deskflow/Clipboard.h" #include "deskflow/ClipboardChunk.h" #include "deskflow/DeskflowException.h" #include "deskflow/OptionTypes.h" #include "deskflow/ProtocolTypes.h" #include "deskflow/ProtocolUtil.h" #include "deskflow/StreamChunker.h" #include "io/IStream.h" #include #include #include // // ServerProxy // ServerProxy::ServerProxy(Client *client, deskflow::IStream *stream, IEventQueue *events) : m_client(client), m_stream(stream), m_events(events) { assert(m_client != nullptr); assert(m_stream != nullptr); // initialize modifier translation table for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) m_modifierTranslationTable[id] = id; // handle data on stream m_events->addHandler(EventTypes::StreamInputReady, m_stream->getEventTarget(), [this](const auto &) { handleData(); }); m_events->addHandler(EventTypes::ClipboardSending, this, [this](const auto &e) { ClipboardChunk::send(m_stream, e.getDataObject()); }); // send heartbeat setKeepAliveRate(kKeepAliveRate); } ServerProxy::~ServerProxy() { setKeepAliveRate(-1.0); m_events->removeHandler(EventTypes::StreamInputReady, m_stream->getEventTarget()); } void ServerProxy::resetKeepAliveAlarm() { if (m_keepAliveAlarmTimer != nullptr) { m_events->removeHandler(EventTypes::Timer, m_keepAliveAlarmTimer); m_events->deleteTimer(m_keepAliveAlarmTimer); m_keepAliveAlarmTimer = nullptr; } if (m_keepAliveAlarm > 0.0) { m_keepAliveAlarmTimer = m_events->newOneShotTimer(m_keepAliveAlarm, nullptr); m_events->addHandler(EventTypes::Timer, m_keepAliveAlarmTimer, [this](const auto &) { handleKeepAliveAlarm(); }); } } void ServerProxy::setKeepAliveRate(double rate) { m_keepAliveAlarm = rate * kKeepAlivesUntilDeath; resetKeepAliveAlarm(); } void ServerProxy::handleData() { // handle messages until there are no more. first read message code. uint8_t code[4]; uint32_t n = m_stream->read(code, 4); while (n != 0) { // verify we got an entire code if (n != 4) { LOG_ERR("incomplete message from server: %d bytes", n); m_client->disconnect("incomplete message from server"); return; } // parse message LOG_DEBUG2("msg from server: %c%c%c%c", code[0], code[1], code[2], code[3]); try { switch ((this->*m_parser)(code)) { using enum ConnectionResult; case Okay: break; case Unknown: LOG_ERR("invalid message from server: %c%c%c%c", code[0], code[1], code[2], code[3]); // not possible to determine message boundaries // read the whole stream to discard unkonwn data while (m_stream->read(nullptr, 4)) ; break; case Disconnect: return; } } catch (const BadClientException &e) { LOG_ERR("protocol error from server: %s", e.what()); ProtocolUtil::writef(m_stream, kMsgEBad); m_client->disconnect("invalid message from server"); return; } // next message n = m_stream->read(code, 4); } flushCompressedMouse(); } ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t *code) { using enum ConnectionResult; if (memcmp(code, kMsgQInfo, 4) == 0) { queryInfo(); } else if (memcmp(code, kMsgCInfoAck, 4) == 0) { infoAcknowledgment(); } else if (memcmp(code, kMsgDSetOptions, 4) == 0) { setOptions(); // handshake is complete m_parser = &ServerProxy::parseMessage; checkMissedLanguages(); m_client->handshakeComplete(); } else if (memcmp(code, kMsgCResetOptions, 4) == 0) { resetOptions(); } else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { // echo keep alives and reset alarm ProtocolUtil::writef(m_stream, kMsgCKeepAlive); resetKeepAliveAlarm(); } else if (memcmp(code, kMsgCNoop, 4) == 0) { // accept and discard no-op } else if (memcmp(code, kMsgCClose, 4) == 0) { // server wants us to hangup LOG_DEBUG1("recv close"); m_client->disconnect(nullptr); return Disconnect; } else if (memcmp(code, kMsgEIncompatible, 4) == 0) { int32_t major; int32_t minor; ProtocolUtil::readf(m_stream, kMsgEIncompatible + 4, &major, &minor); LOG_ERR("server has incompatible version %d.%d", major, minor); m_client->refuseConnection("server has incompatible version"); return Disconnect; } else if (memcmp(code, kMsgEBusy, 4) == 0) { LOG_ERR("server already has a connected client with name \"%s\"", m_client->getName().c_str()); m_client->refuseConnection("server already has a connected client with our name"); return Disconnect; } else if (memcmp(code, kMsgEUnknown, 4) == 0) { LOG_ERR("server refused client with name \"%s\"", m_client->getName().c_str()); m_client->refuseConnection("server refused client with our name"); return Disconnect; } else if (memcmp(code, kMsgEBad, 4) == 0) { LOG_ERR("server disconnected due to a protocol error"); m_client->refuseConnection("server reported a protocol error"); return Disconnect; } else if (memcmp(code, kMsgDLanguageSynchronisation, 4) == 0) { setServerLanguages(); } else { return Unknown; } return Okay; } ServerProxy::ConnectionResult ServerProxy::parseMessage(const uint8_t *code) { using enum ConnectionResult; if (memcmp(code, kMsgDMouseMove, 4) == 0) { mouseMove(); } else if (memcmp(code, kMsgDMouseRelMove, 4) == 0) { mouseRelativeMove(); } else if (memcmp(code, kMsgDMouseWheel, 4) == 0) { mouseWheel(); } else if (memcmp(code, kMsgDKeyDown, 4) == 0) { uint16_t id = 0; uint16_t mask = 0; uint16_t button = 0; ProtocolUtil::readf(m_stream, kMsgDKeyDown + 4, &id, &mask, &button); LOG_DEBUG1("recv key down id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button); keyDown(id, mask, button, ""); } else if (memcmp(code, kMsgDKeyDownLang, 4) == 0) { std::string lang; uint16_t id = 0; uint16_t mask = 0; uint16_t button = 0; ProtocolUtil::readf(m_stream, kMsgDKeyDownLang + 4, &id, &mask, &button, &lang); LOG_DEBUG1("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(); } else if (memcmp(code, kMsgDMouseDown, 4) == 0) { mouseDown(); } else if (memcmp(code, kMsgDMouseUp, 4) == 0) { mouseUp(); } else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) { keyRepeat(); } else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { // echo keep alives and reset alarm ProtocolUtil::writef(m_stream, kMsgCKeepAlive); resetKeepAliveAlarm(); } else if (memcmp(code, kMsgCNoop, 4) == 0) { // accept and discard no-op } else if (memcmp(code, kMsgCEnter, 4) == 0) { enter(); } else if (memcmp(code, kMsgCLeave, 4) == 0) { leave(); } else if (memcmp(code, kMsgCClipboard, 4) == 0) { grabClipboard(); } else if (memcmp(code, kMsgCScreenSaver, 4) == 0) { screensaver(); } else if (memcmp(code, kMsgQInfo, 4) == 0) { queryInfo(); } else if (memcmp(code, kMsgCInfoAck, 4) == 0) { infoAcknowledgment(); } else if (memcmp(code, kMsgDClipboard, 4) == 0) { setClipboard(); } else if (memcmp(code, kMsgCResetOptions, 4) == 0) { resetOptions(); } else if (memcmp(code, kMsgDSetOptions, 4) == 0) { setOptions(); } else if (memcmp(code, kMsgDSecureInputNotification, 4) == 0) { secureInputNotification(); } else if (memcmp(code, kMsgCClose, 4) == 0) { // server wants us to hangup LOG_DEBUG1("recv close"); m_client->disconnect(nullptr); return Disconnect; } else if (memcmp(code, kMsgEBad, 4) == 0) { LOG_ERR("server disconnected due to a protocol error"); m_client->disconnect("server reported a protocol error"); return Disconnect; } else { 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; } void ServerProxy::handleKeepAliveAlarm() { LOG_NOTE("server is dead"); m_client->disconnect("server is not responding"); } void ServerProxy::onInfoChanged() { // ignore mouse motion until we receive acknowledgment of our info // change message. m_ignoreMouse = true; // send info update queryInfo(); } bool ServerProxy::onGrabClipboard(ClipboardID id) { LOG_DEBUG1("sending clipboard %d changed", id); ProtocolUtil::writef(m_stream, kMsgCClipboard, id, m_seqNum); return true; } void ServerProxy::onClipboardChanged(ClipboardID id, const IClipboard *clipboard) { std::string data = IClipboard::marshall(clipboard); LOG_DEBUG("sending clipboard %d seqnum=%d", id, m_seqNum); StreamChunker::sendClipboard(data, data.size(), id, m_seqNum, m_events, this); } void ServerProxy::flushCompressedMouse() { if (m_compressMouse) { m_compressMouse = false; m_client->mouseMove(m_xMouse, m_yMouse); } if (m_compressMouseRelative) { m_compressMouseRelative = false; m_client->mouseRelativeMove(m_dxMouse, m_dyMouse); m_dxMouse = 0; m_dyMouse = 0; } } void ServerProxy::sendInfo(const ClientInfo &info) { LOG_DEBUG1("sending info shape=%d,%d %dx%d", info.m_x, info.m_y, info.m_w, info.m_h); ProtocolUtil::writef(m_stream, kMsgDInfo, info.m_x, info.m_y, info.m_w, info.m_h, 0, info.m_mx, info.m_my); } KeyID ServerProxy::translateKey(KeyID id) const { static const KeyID s_translationTable[kKeyModifierIDLast][2] = { {kKeyNone, kKeyNone}, {kKeyShift_L, kKeyShift_R}, {kKeyControl_L, kKeyControl_R}, {kKeyAlt_L, kKeyAlt_R}, {kKeyMeta_L, kKeyMeta_R}, {kKeySuper_L, kKeySuper_R}, {kKeyAltGr, kKeyAltGr} }; KeyModifierID id2 = kKeyModifierIDNull; uint32_t side = 0; switch (id) { case kKeyShift_L: id2 = kKeyModifierIDShift; side = 0; break; case kKeyShift_R: id2 = kKeyModifierIDShift; side = 1; break; case kKeyControl_L: id2 = kKeyModifierIDControl; side = 0; break; case kKeyControl_R: id2 = kKeyModifierIDControl; side = 1; break; case kKeyAlt_L: id2 = kKeyModifierIDAlt; side = 0; break; case kKeyAlt_R: id2 = kKeyModifierIDAlt; side = 1; break; case kKeyAltGr: id2 = kKeyModifierIDAltGr; side = 1; // there is only one alt gr key on the right side break; case kKeyMeta_L: id2 = kKeyModifierIDMeta; side = 0; break; case kKeyMeta_R: id2 = kKeyModifierIDMeta; side = 1; break; case kKeySuper_L: id2 = kKeyModifierIDSuper; side = 0; break; case kKeySuper_R: id2 = kKeyModifierIDSuper; side = 1; break; default: break; } if (id2 != kKeyModifierIDNull) { return s_translationTable[m_modifierTranslationTable[id2]][side]; } else { return id; } } KeyModifierMask ServerProxy::translateModifierMask(KeyModifierMask mask) const { static const KeyModifierMask s_masks[kKeyModifierIDLast] = {0x0000, KeyModifierShift, KeyModifierControl, KeyModifierAlt, KeyModifierMeta, KeyModifierSuper, KeyModifierAltGr}; KeyModifierMask newMask = mask & ~(KeyModifierShift | KeyModifierControl | KeyModifierAlt | KeyModifierMeta | KeyModifierSuper | KeyModifierAltGr); if ((mask & KeyModifierShift) != 0) { newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDShift]]; } if ((mask & KeyModifierControl) != 0) { newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDControl]]; } if ((mask & KeyModifierAlt) != 0) { newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDAlt]]; } if ((mask & KeyModifierAltGr) != 0) { newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDAltGr]]; } if ((mask & KeyModifierMeta) != 0) { newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDMeta]]; } if ((mask & KeyModifierSuper) != 0) { newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDSuper]]; } return newMask; } void ServerProxy::enter() { // parse int16_t x; int16_t y; uint16_t mask; uint32_t seqNum; ProtocolUtil::readf(m_stream, kMsgCEnter + 4, &x, &y, &seqNum, &mask); LOG_DEBUG1("recv enter, %d,%d %d %04x", x, y, seqNum, mask); // discard old compressed mouse motion, if any m_compressMouse = false; m_compressMouseRelative = false; m_dxMouse = 0; m_dyMouse = 0; m_seqNum = seqNum; m_serverLanguage = ""; m_isUserNotifiedAboutLanguageSyncError = false; // forward m_client->enter(x, y, seqNum, static_cast(mask), false); } void ServerProxy::leave() { // parse LOG_DEBUG1("recv leave"); // send last mouse motion flushCompressedMouse(); // forward m_client->leave(); } void ServerProxy::setClipboard() { // parse static std::string dataCached; ClipboardID id; uint32_t seq; auto r = ClipboardChunk::assemble(m_stream, dataCached, id, seq); if (r == TransferState::Started) { size_t size = ClipboardChunk::getExpectedSize(); LOG_DEBUG("receiving clipboard %d size=%d", id, size); } else if (r == TransferState::Finished) { LOG_DEBUG("received clipboard %d size=%d", id, dataCached.size()); // forward Clipboard clipboard; clipboard.unmarshall(dataCached, 0); m_client->setClipboard(id, &clipboard); LOG_INFO("clipboard was updated"); } } void ServerProxy::grabClipboard() { // parse ClipboardID id; uint32_t seqNum; ProtocolUtil::readf(m_stream, kMsgCClipboard + 4, &id, &seqNum); LOG_DEBUG("recv grab clipboard %d", id); // validate if (id >= kClipboardEnd) { return; } // forward m_client->grabClipboard(id); } 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_DEBUG1("key down translated to id=0x%08x, mask=0x%04x", id2, mask2); // forward m_client->keyDown(id2, mask2, button, lang); } void ServerProxy::keyRepeat() { // 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_DEBUG1 "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_DEBUG1("key repeat translated to id=0x%08x, mask=0x%04x", id2, mask2); // forward m_client->keyRepeat(id2, mask2, count, button, lang); } void ServerProxy::keyUp() { // get mouse up to date flushCompressedMouse(); // parse uint16_t id; uint16_t mask; uint16_t button; ProtocolUtil::readf(m_stream, kMsgDKeyUp + 4, &id, &mask, &button); LOG_DEBUG1("recv key up id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button); // translate KeyID id2 = translateKey(static_cast(id)); KeyModifierMask mask2 = translateModifierMask(static_cast(mask)); if (id2 != static_cast(id) || mask2 != static_cast(mask)) LOG_DEBUG1("key up translated to id=0x%08x, mask=0x%04x", id2, mask2); // forward m_client->keyUp(id2, mask2, button); } void ServerProxy::mouseDown() { // get mouse up to date flushCompressedMouse(); // parse int8_t id; ProtocolUtil::readf(m_stream, kMsgDMouseDown + 4, &id); LOG_DEBUG1("recv mouse down id=%d", id); // forward m_client->mouseDown(static_cast(id)); } void ServerProxy::mouseUp() { // get mouse up to date flushCompressedMouse(); // parse int8_t id; ProtocolUtil::readf(m_stream, kMsgDMouseUp + 4, &id); LOG_DEBUG1("recv mouse up id=%d", id); // forward m_client->mouseUp(static_cast(id)); } void ServerProxy::mouseMove() { // parse bool ignore; int16_t x; int16_t y; ProtocolUtil::readf(m_stream, kMsgDMouseMove + 4, &x, &y); // note if we should ignore the move ignore = m_ignoreMouse; // compress mouse motion events if more input follows if (!ignore && !m_compressMouse && m_stream->isReady()) { m_compressMouse = true; } // if compressing then ignore the motion but record it if (m_compressMouse) { m_compressMouseRelative = false; ignore = true; m_xMouse = x; m_yMouse = y; m_dxMouse = 0; m_dyMouse = 0; } LOG_DEBUG2("recv mouse move %d,%d", x, y); // forward if (!ignore) { m_client->mouseMove(x, y); } } void ServerProxy::mouseRelativeMove() { // parse bool ignore; int16_t dx; int16_t dy; ProtocolUtil::readf(m_stream, kMsgDMouseRelMove + 4, &dx, &dy); // note if we should ignore the move ignore = m_ignoreMouse; // compress mouse motion events if more input follows if (!ignore && !m_compressMouseRelative && m_stream->isReady()) { m_compressMouseRelative = true; } // if compressing then ignore the motion but record it if (m_compressMouseRelative) { ignore = true; m_dxMouse += dx; m_dyMouse += dy; } LOG_DEBUG2("recv mouse relative move %d,%d", dx, dy); // forward if (!ignore) { m_client->mouseRelativeMove(dx, dy); } } void ServerProxy::mouseWheel() { // get mouse up to date flushCompressedMouse(); // parse int16_t xDelta; int16_t yDelta; ProtocolUtil::readf(m_stream, kMsgDMouseWheel + 4, &xDelta, &yDelta); LOG_DEBUG2("recv mouse wheel %+d,%+d", xDelta, yDelta); // forward m_client->mouseWheel(xDelta, yDelta); } void ServerProxy::screensaver() { // parse int8_t on; ProtocolUtil::readf(m_stream, kMsgCScreenSaver + 4, &on); LOG_DEBUG1("recv screen saver on=%d", on); // forward m_client->screensaver(on != 0); } void ServerProxy::resetOptions() { // parse LOG_DEBUG1("recv reset options"); // forward m_client->resetOptions(); // reset keep alive setKeepAliveRate(kKeepAliveRate); // reset modifier translation table for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) { m_modifierTranslationTable[id] = id; } } void ServerProxy::setOptions() { // parse OptionsList options; ProtocolUtil::readf(m_stream, kMsgDSetOptions + 4, &options); LOG_DEBUG1("recv set options size=%d", options.size()); // forward m_client->setOptions(options); // update modifier table for (uint32_t i = 0, n = (uint32_t)options.size(); i < n; i += 2) { KeyModifierID id = kKeyModifierIDNull; if (options[i] == kOptionModifierMapForShift) { id = kKeyModifierIDShift; } else if (options[i] == kOptionModifierMapForControl) { id = kKeyModifierIDControl; } else if (options[i] == kOptionModifierMapForAlt) { id = kKeyModifierIDAlt; } else if (options[i] == kOptionModifierMapForAltGr) { id = kKeyModifierIDAltGr; } else if (options[i] == kOptionModifierMapForMeta) { id = kKeyModifierIDMeta; } else if (options[i] == kOptionModifierMapForSuper) { id = kKeyModifierIDSuper; } else if (options[i] == kOptionHeartbeat) { // update keep alive setKeepAliveRate(1.0e-3 * static_cast(options[i + 1])); } if (id != kKeyModifierIDNull) { m_modifierTranslationTable[id] = options[i + 1]; LOG_DEBUG1("modifier %d mapped to %d", id, m_modifierTranslationTable[id]); } } } void ServerProxy::queryInfo() { ClientInfo info; m_client->getShape(info.m_x, info.m_y, info.m_w, info.m_h); m_client->getCursorPos(info.m_mx, info.m_my); sendInfo(info); } void ServerProxy::infoAcknowledgment() { LOG_DEBUG1("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 serverLanguages; ProtocolUtil::readf(m_stream, kMsgDLanguageSynchronisation + 4, &serverLanguages); m_languageManager.setRemoteLanguages(serverLanguages); } void ServerProxy::setActiveServerLanguage(const std::string_view &language) { if (!language.empty() && (language.size() > 0)) { if (m_serverLanguage != language) { m_isUserNotifiedAboutLanguageSyncError = false; m_serverLanguage = language; } if (!m_languageManager.isLanguageInstalled(m_serverLanguage)) { if (!m_isUserNotifiedAboutLanguageSyncError) { LOG_WARN("current server language is not installed on client"); m_isUserNotifiedAboutLanguageSyncError = true; } } else { m_isUserNotifiedAboutLanguageSyncError = false; } } else { LOG_DEBUG1("active server language is empty"); } } void ServerProxy::checkMissedLanguages() const { auto missedLanguages = m_languageManager.getMissedLanguages(); if (!missedLanguages.empty()) { LOG( (CLOG_WARN "You need to install these languages on this computer and restart " "Deskflow to enable support for multiple languages: %s", missedLanguages.c_str()) ); } }