deskflow/src/lib/client/ServerProxy.cpp
Nick Bolton 49e6293a4d 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.
2026-09-18 20:15:14 +01:00

811 lines
21 KiB
C++

/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
* 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
*/
#include "client/ServerProxy.h"
#include "base/IEventQueue.h"
#include "base/Log.h"
#include "client/Client.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 <cstring>
//
// 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());
m_events->removeHandler(EventTypes::ClipboardSending, this);
}
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);
requestDisconnect("incomplete message from server");
return;
}
// parse message
LOG_VERBOSE("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);
requestDisconnect("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;
using enum deskflow::core::ConnectionRefusal;
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::handleMessage;
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_VERBOSE("recv close");
requestDisconnect(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);
requestRefuseConnection(IncompatibleVersion, "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());
requestRefuseConnection(AlreadyConnected, "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());
requestRefuseConnection(UnknownClient, "server refused client with our name");
return Disconnect;
}
else if (memcmp(code, kMsgEBad, 4) == 0) {
LOG_ERR("server disconnected due to a protocol error");
requestRefuseConnection(ProtocolError, "server reported a protocol error");
return Disconnect;
} else {
return Unknown;
}
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;
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, kMsgDKeyDown1_1, 4) == 0) {
uint16_t id = 0;
uint16_t mask = 0;
uint16_t button = 0;
ProtocolUtil::readf(m_stream, kMsgDKeyDown1_1 + 4, &id, &mask, &button);
LOG_VERBOSE("recv key down id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button);
keyDown(id, mask, button, "");
}
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, 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) {
// 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, kMsgCClose, 4) == 0) {
// server wants us to hangup
LOG_VERBOSE("recv close");
requestDisconnect(nullptr);
return Disconnect;
} else if (memcmp(code, kMsgEBad, 4) == 0) {
LOG_ERR("server disconnected due to a protocol error");
requestDisconnect("server reported a protocol error");
return Disconnect;
} else {
return Unknown;
}
return Okay;
}
void ServerProxy::handleKeepAliveAlarm()
{
LOG_INFO("server is dead");
requestDisconnect("server is not responding");
}
void ServerProxy::requestDisconnect(const char *message)
{
m_events->addEvent(Event(
EventTypes::ClientDisconnectRequested, m_stream->getEventTarget(),
new Client::DisconnectRequest(Client::DisconnectRequest::Kind::Disconnect, message)
));
}
void ServerProxy::requestRefuseConnection(deskflow::core::ConnectionRefusal reason, const char *message)
{
m_events->addEvent(Event(
EventTypes::ClientDisconnectRequested, m_stream->getEventTarget(), new Client::DisconnectRequest(reason, message)
));
}
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_VERBOSE("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_VERBOSE("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_VERBOSE("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;
// forward
m_client->enter(x, y, seqNum, static_cast<KeyModifierMask>(mask), false);
}
void ServerProxy::leave()
{
// parse
LOG_VERBOSE("recv leave");
// send last mouse motion
flushCompressedMouse();
// forward
m_client->leave();
}
void ServerProxy::setClipboard()
{
// parse
ClipboardID id;
uint32_t seq;
auto r = ClipboardChunk::assemble(
m_stream, m_clipboardDataCached, id, seq, m_clipboardChunkState, m_client->getMaximumClipboardReceiveSizeBytes()
);
if (r == TransferState::Started) {
size_t size = ClipboardChunk::getExpectedSize(m_clipboardChunkState);
LOG_DEBUG("receiving clipboard %d size=%zu", id, size);
} else if (r == TransferState::Finished) {
LOG_DEBUG("received clipboard %d size=%zu", id, m_clipboardDataCached.size());
// forward
Clipboard clipboard;
clipboard.unmarshall(m_clipboardDataCached, 0);
m_client->setClipboard(id, &clipboard);
m_clipboardDataCached.clear();
m_clipboardDataCached.shrink_to_fit();
LOG_INFO("clipboard was updated");
} else if (r == TransferState::Error) {
requestDisconnect("invalid clipboard data from server");
}
}
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)
{
flushCompressedMouse();
KeyID id2 = translateKey(static_cast<KeyID>(id));
KeyModifierMask mask2 = translateModifierMask(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);
m_client->keyDown(id2, mask2, button, lang);
}
void ServerProxy::keyRepeat(uint16_t id, uint16_t mask, uint16_t count, uint16_t button, const std::string &lang)
{
flushCompressedMouse();
KeyID id2 = translateKey(static_cast<KeyID>(id));
KeyModifierMask mask2 = translateModifierMask(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);
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_VERBOSE("recv key up id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button);
// translate
KeyID id2 = translateKey(static_cast<KeyID>(id));
KeyModifierMask mask2 = translateModifierMask(static_cast<KeyModifierMask>(mask));
if (id2 != static_cast<KeyID>(id) || mask2 != static_cast<KeyModifierMask>(mask))
LOG_VERBOSE("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_VERBOSE("recv mouse down id=%d", id);
// forward
m_client->mouseDown(static_cast<ButtonID>(id));
}
void ServerProxy::mouseUp()
{
// get mouse up to date
flushCompressedMouse();
// parse
int8_t id;
ProtocolUtil::readf(m_stream, kMsgDMouseUp + 4, &id);
LOG_VERBOSE("recv mouse up id=%d", id);
// forward
m_client->mouseUp(static_cast<ButtonID>(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_VERBOSE("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_VERBOSE("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_VERBOSE("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_VERBOSE("recv screen saver on=%d", on);
// forward
m_client->screensaver(on != 0);
}
void ServerProxy::resetOptions()
{
// parse
LOG_VERBOSE("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_VERBOSE("recv set options size=%d", options.size());
if (options.size() % 2 != 0) {
LOG_ERR("options are the incorrect size, can not process them");
return;
}
// 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<double>(options[i + 1]));
}
if (id != kKeyModifierIDNull) {
m_modifierTranslationTable[id] = std::clamp<KeyModifierMask>(options[i + 1], 0, kKeyModifierIDLast - 1);
LOG_VERBOSE("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_VERBOSE("recv info acknowledgment");
m_ignoreMouse = false;
}