Files
barrier/lib/client/CServerProxy.cpp
crs 11f90022e0 Checkpointing improved key handling. This change adds non-ASCII
key handling to win32 on both client and server.  It also changes
the protocol and adds code to ensure every key pressed also gets
released and that that doesn't get confused when the KeyID for
the press is different from the KeyID of the release (or repeat).
2003-04-27 17:01:14 +00:00

757 lines
16 KiB
C++

/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "CServerProxy.h"
#include "CProtocolUtil.h"
#include "IClient.h"
#include "OptionTypes.h"
#include "ProtocolTypes.h"
#include "IInputStream.h"
#include "IOutputStream.h"
#include "CLock.h"
#include "CLog.h"
#include "CStopwatch.h"
#include "XBase.h"
#include <memory>
//
// CServerProxy
//
CServerProxy::CServerProxy(IClient* client,
IInputStream* adoptedInput, IOutputStream* adoptedOutput) :
m_client(client),
m_input(adoptedInput),
m_output(adoptedOutput),
m_seqNum(0),
m_heartRate(kHeartRate)
{
assert(m_client != NULL);
assert(m_input != NULL);
assert(m_output != NULL);
// initialize modifier translation table
for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id)
m_modifierTranslationTable[id] = id;
}
CServerProxy::~CServerProxy()
{
delete m_input;
delete m_output;
}
bool
CServerProxy::mainLoop()
{
bool failedToConnect = false;
try {
// no compressed mouse motion yet
m_compressMouse = false;
// not ignoring mouse motions
m_ignoreMouse = false;
// reset sequence number
m_seqNum = 0;
// handle messages from server
CStopwatch heartbeat;
for (;;) {
// if no input is pending then flush compressed mouse motion
if (getInputStream()->getSize() == 0) {
flushCompressedMouse();
}
// wait for a message
LOG((CLOG_DEBUG2 "waiting for message"));
UInt8 code[4];
UInt32 n = getInputStream()->read(code, 4, m_heartRate);
// check if server hungup
if (n == 0) {
LOG((CLOG_NOTE "server disconnected"));
break;
}
// check for time out
if (n == (UInt32)-1 ||
(m_heartRate >= 0.0 && heartbeat.getTime() > m_heartRate)) {
// send heartbeat
CLock lock(&m_mutex);
CProtocolUtil::writef(getOutputStream(), kMsgCNoop);
heartbeat.reset();
if (n == (UInt32)-1) {
// no message to process
continue;
}
}
// verify we got an entire code
if (n != 4) {
// client sent an incomplete message
LOG((CLOG_ERR "incomplete message from server"));
break;
}
// parse message
LOG((CLOG_DEBUG2 "msg from server: %c%c%c%c", code[0], code[1], code[2], code[3]));
if (memcmp(code, kMsgDMouseMove, 4) == 0) {
mouseMove();
}
else if (memcmp(code, kMsgDMouseWheel, 4) == 0) {
mouseWheel();
}
else if (memcmp(code, kMsgDKeyDown, 4) == 0) {
keyDown();
}
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, 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((CLOG_DEBUG1 "recv close"));
break;
}
else if (memcmp(code, kMsgEIncompatible, 4) == 0) {
SInt32 major, minor;
CProtocolUtil::readf(getInputStream(),
kMsgEIncompatible + 4, &major, &minor);
LOG((CLOG_ERR "server has incompatible version %d.%d", major, minor));
failedToConnect = true;
break;
}
else if (memcmp(code, kMsgEBusy, 4) == 0) {
LOG((CLOG_ERR "server already has a connected client with name \"%s\"", getName().c_str()));
failedToConnect = true;
break;
}
else if (memcmp(code, kMsgEUnknown, 4) == 0) {
LOG((CLOG_ERR "server refused client with name \"%s\"", getName().c_str()));
failedToConnect = true;
break;
}
else if (memcmp(code, kMsgEBad, 4) == 0) {
LOG((CLOG_ERR "server disconnected due to a protocol error"));
failedToConnect = true;
break;
}
else {
// unknown message
LOG((CLOG_ERR "unknown message from server"));
LOG((CLOG_ERR "unknown message: %d %d %d %d [%c%c%c%c]", code[0], code[1], code[2], code[3], code[0], code[1], code[2], code[3]));
failedToConnect = true;
break;
}
}
}
catch (XBase& e) {
LOG((CLOG_ERR "error: %s", e.what()));
}
catch (...) {
throw;
}
return !failedToConnect;
}
IClient*
CServerProxy::getClient() const
{
return m_client;
}
CString
CServerProxy::getName() const
{
return m_client->getName();
}
IInputStream*
CServerProxy::getInputStream() const
{
return m_input;
}
IOutputStream*
CServerProxy::getOutputStream() const
{
return m_output;
}
void
CServerProxy::onError()
{
// ignore
}
void
CServerProxy::onInfoChanged(const CClientInfo& info)
{
// ignore mouse motion until we receive acknowledgment of our info
// change message.
CLock lock(&m_mutex);
m_ignoreMouse = true;
// send info update
sendInfo(info);
}
bool
CServerProxy::onGrabClipboard(ClipboardID id)
{
LOG((CLOG_DEBUG1 "sending clipboard %d changed", id));
CLock lock(&m_mutex);
CProtocolUtil::writef(getOutputStream(), kMsgCClipboard, id, m_seqNum);
return true;
}
void
CServerProxy::onClipboardChanged(ClipboardID id, const CString& data)
{
CLock lock(&m_mutex);
LOG((CLOG_DEBUG1 "sending clipboard %d seqnum=%d, size=%d", id, m_seqNum, data.size()));
CProtocolUtil::writef(getOutputStream(), kMsgDClipboard, id, m_seqNum, &data);
}
void
CServerProxy::flushCompressedMouse()
{
bool send = false;
SInt32 x = 0, y = 0;
{
CLock lock(&m_mutex);
if (m_compressMouse) {
m_compressMouse = false;
x = m_xMouse;
y = m_yMouse;
send = true;
}
}
if (send) {
getClient()->mouseMove(x, y);
}
}
void
CServerProxy::sendInfo(const CClientInfo& info)
{
// note -- m_mutex should be locked on entry
LOG((CLOG_DEBUG1 "sending info shape=%d,%d %dx%d zone=%d pos=%d,%d", info.m_x, info.m_y, info.m_w, info.m_h, info.m_zoneSize, info.m_mx, info.m_my));
CProtocolUtil::writef(getOutputStream(), kMsgDInfo,
info.m_x, info.m_y,
info.m_w, info.m_h,
info.m_zoneSize,
info.m_mx, info.m_my);
}
KeyID
CServerProxy::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 }
};
KeyModifierID id2 = kKeyModifierIDNull;
UInt32 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 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;
}
if (id2 != kKeyModifierIDNull) {
return s_translationTable[m_modifierTranslationTable[id2]][side];
}
else {
return id;
}
}
KeyModifierMask
CServerProxy::translateModifierMask(KeyModifierMask mask) const
{
static const KeyModifierMask s_masks[kKeyModifierIDLast] = {
0x0000,
KeyModifierShift,
KeyModifierControl,
KeyModifierAlt,
KeyModifierMeta,
KeyModifierSuper
};
KeyModifierMask newMask = 0;
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 & KeyModifierMeta) != 0) {
newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDMeta]];
}
if ((mask & KeyModifierSuper) != 0) {
newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDSuper]];
}
return newMask;
}
void
CServerProxy::enter()
{
// parse
SInt16 x, y;
UInt16 mask;
UInt32 seqNum;
CProtocolUtil::readf(getInputStream(),
kMsgCEnter + 4, &x, &y, &seqNum, &mask);
LOG((CLOG_DEBUG1 "recv enter, %d,%d %d %04x", x, y, seqNum, mask));
// discard old compressed mouse motion, if any
{
CLock lock(&m_mutex);
m_compressMouse = false;
m_seqNum = seqNum;
}
// forward
getClient()->enter(x, y, seqNum, static_cast<KeyModifierMask>(mask), false);
}
void
CServerProxy::leave()
{
// parse
LOG((CLOG_DEBUG1 "recv leave"));
// send last mouse motion
flushCompressedMouse();
// forward
getClient()->leave();
}
void
CServerProxy::setClipboard()
{
// parse
ClipboardID id;
UInt32 seqNum;
CString data;
CProtocolUtil::readf(getInputStream(),
kMsgDClipboard + 4, &id, &seqNum, &data);
LOG((CLOG_DEBUG "recv clipboard %d size=%d", id, data.size()));
// validate
if (id >= kClipboardEnd) {
return;
}
// forward
getClient()->setClipboard(id, data);
}
void
CServerProxy::grabClipboard()
{
// parse
ClipboardID id;
UInt32 seqNum;
CProtocolUtil::readf(getInputStream(), kMsgCClipboard + 4, &id, &seqNum);
LOG((CLOG_DEBUG "recv grab clipboard %d", id));
// validate
if (id >= kClipboardEnd) {
return;
}
// forward
getClient()->grabClipboard(id);
}
void
CServerProxy::keyDown()
{
// get mouse up to date
flushCompressedMouse();
// parse
UInt16 id, mask, button;
CProtocolUtil::readf(getInputStream(), kMsgDKeyDown + 4,
&id, &mask, &button);
LOG((CLOG_DEBUG1 "recv key down id=%d, 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((CLOG_DEBUG1 "key down translated to id=%d, mask=0x%04x", id2, mask2));
// forward
getClient()->keyDown(id2, mask2, button);
}
void
CServerProxy::keyRepeat()
{
// get mouse up to date
flushCompressedMouse();
// parse
UInt16 id, mask, count, button;
CProtocolUtil::readf(getInputStream(), kMsgDKeyRepeat + 4,
&id, &mask, &count, &button);
LOG((CLOG_DEBUG1 "recv key repeat id=%d, mask=0x%04x, count=%d, button=0x%04x", id, mask, count, 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((CLOG_DEBUG1 "key down translated to id=%d, mask=0x%04x", id2, mask2));
// forward
getClient()->keyRepeat(id2, mask2, count, button);
}
void
CServerProxy::keyUp()
{
// get mouse up to date
flushCompressedMouse();
// parse
UInt16 id, mask, button;
CProtocolUtil::readf(getInputStream(), kMsgDKeyUp + 4, &id, &mask, &button);
LOG((CLOG_DEBUG1 "recv key up id=%d, 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((CLOG_DEBUG1 "key down translated to id=%d, mask=0x%04x", id2, mask2));
// forward
getClient()->keyUp(id2, mask2, button);
}
void
CServerProxy::mouseDown()
{
// get mouse up to date
flushCompressedMouse();
// parse
SInt8 id;
CProtocolUtil::readf(getInputStream(), kMsgDMouseDown + 4, &id);
LOG((CLOG_DEBUG1 "recv mouse down id=%d", id));
// forward
getClient()->mouseDown(static_cast<ButtonID>(id));
}
void
CServerProxy::mouseUp()
{
// get mouse up to date
flushCompressedMouse();
// parse
SInt8 id;
CProtocolUtil::readf(getInputStream(), kMsgDMouseUp + 4, &id);
LOG((CLOG_DEBUG1 "recv mouse up id=%d", id));
// forward
getClient()->mouseUp(static_cast<ButtonID>(id));
}
void
CServerProxy::mouseMove()
{
// parse
bool ignore;
SInt16 x, y;
CProtocolUtil::readf(getInputStream(), kMsgDMouseMove + 4, &x, &y);
{
// note if we should ignore the move
CLock lock(&m_mutex);
ignore = m_ignoreMouse;
// compress mouse motion events if more input follows
if (!ignore && !m_compressMouse && getInputStream()->getSize() > 0) {
m_compressMouse = true;
}
// if compressing then ignore the motion but record it
if (m_compressMouse) {
ignore = true;
m_xMouse = x;
m_yMouse = y;
}
}
LOG((CLOG_DEBUG2 "recv mouse move %d,%d", x, y));
// forward
if (!ignore) {
getClient()->mouseMove(x, y);
}
}
void
CServerProxy::mouseWheel()
{
// get mouse up to date
flushCompressedMouse();
// parse
SInt16 delta;
CProtocolUtil::readf(getInputStream(), kMsgDMouseWheel + 4, &delta);
LOG((CLOG_DEBUG2 "recv mouse wheel %+d", delta));
// forward
getClient()->mouseWheel(delta);
}
void
CServerProxy::screensaver()
{
// parse
SInt8 on;
CProtocolUtil::readf(getInputStream(), kMsgCScreenSaver + 4, &on);
LOG((CLOG_DEBUG1 "recv screen saver on=%d", on));
// forward
getClient()->screensaver(on != 0);
}
void
CServerProxy::resetOptions()
{
// parse
LOG((CLOG_DEBUG1 "recv reset options"));
// forward
getClient()->resetOptions();
CLock lock(&m_mutex);
// reset heart rate
m_heartRate = kHeartRate;
// reset modifier translation table
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
CServerProxy::setOptions()
{
// parse
COptionsList options;
CProtocolUtil::readf(getInputStream(), kMsgDSetOptions + 4, &options);
LOG((CLOG_DEBUG1 "recv set options size=%d", options.size()));
// 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;
if (options[i] == kOptionModifierMapForShift) {
id = kKeyModifierIDShift;
}
else if (options[i] == kOptionModifierMapForControl) {
id = kKeyModifierIDControl;
}
else if (options[i] == kOptionModifierMapForAlt) {
id = kKeyModifierIDAlt;
}
else if (options[i] == kOptionModifierMapForMeta) {
id = kKeyModifierIDMeta;
}
else if (options[i] == kOptionModifierMapForSuper) {
id = kKeyModifierIDSuper;
}
else if (options[i] == kOptionHeartbeat) {
// update heart rate
m_heartRate = 1.0e-3 * static_cast<double>(options[i + 1]);
// send heartbeat if necessary
if (m_heartRate >= 0.0) {
CProtocolUtil::writef(getOutputStream(), kMsgCNoop);
}
}
if (id != kKeyModifierIDNull) {
m_modifierTranslationTable[id] =
static_cast<KeyModifierID>(options[i + 1]);
LOG((CLOG_DEBUG1 "modifier %d mapped to %d", id, m_modifierTranslationTable[id]));
}
}
}
void
CServerProxy::queryInfo()
{
// get current info
CClientInfo info;
getClient()->getShape(info.m_x, info.m_y, info.m_w, info.m_h);
getClient()->getCursorPos(info.m_mx, info.m_my);
info.m_zoneSize = getClient()->getJumpZoneSize();
// send it
CLock lock(&m_mutex);
sendInfo(info);
}
void
CServerProxy::infoAcknowledgment()
{
// parse
LOG((CLOG_DEBUG1 "recv info acknowledgment"));
// now allow mouse motion
CLock lock(&m_mutex);
m_ignoreMouse = false;
}