Files
barrier/lib/server/CClientProxy1_0.cpp
crs 16110acaa2 Added support for a global relative mouse motion option. When true
and on a secondary screen and locked to the screen (via scroll lock)
mouse motion is sent as motion deltas.  When true and scroll lock
is toggled off the mouse is warped to the secondary screen's center
so the server knows where it is.  This option is intended to support
games and other programs that repeatedly warp the mouse to the center
of the screen.  This change adds general and X11 support but not
win32.  The option name is "relativeMouseMoves".
2004-05-01 15:18:59 +00:00

469 lines
12 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 "CClientProxy1_0.h"
#include "CProtocolUtil.h"
#include "XSynergy.h"
#include "IStream.h"
#include "CLog.h"
#include "IEventQueue.h"
#include "TMethodEventJob.h"
#include <cstring>
//
// CClientProxy1_0
//
CClientProxy1_0::CClientProxy1_0(const CString& name, IStream* stream) :
CClientProxy(name, stream),
m_heartbeatAlarm(kHeartRate * kHeartBeatsUntilDeath),
m_heartbeatTimer(NULL),
m_parser(&CClientProxy1_0::parseHandshakeMessage)
{
// install event handlers
EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(),
stream->getEventTarget(),
new TMethodEventJob<CClientProxy1_0>(this,
&CClientProxy1_0::handleData, NULL));
EVENTQUEUE->adoptHandler(IStream::getOutputErrorEvent(),
stream->getEventTarget(),
new TMethodEventJob<CClientProxy1_0>(this,
&CClientProxy1_0::handleWriteError, NULL));
EVENTQUEUE->adoptHandler(IStream::getInputShutdownEvent(),
stream->getEventTarget(),
new TMethodEventJob<CClientProxy1_0>(this,
&CClientProxy1_0::handleDisconnect, NULL));
EVENTQUEUE->adoptHandler(IStream::getOutputShutdownEvent(),
stream->getEventTarget(),
new TMethodEventJob<CClientProxy1_0>(this,
&CClientProxy1_0::handleWriteError, NULL));
EVENTQUEUE->adoptHandler(CEvent::kTimer, this,
new TMethodEventJob<CClientProxy1_0>(this,
&CClientProxy1_0::handleFlatline, NULL));
LOG((CLOG_DEBUG1 "querying client \"%s\" info", getName().c_str()));
CProtocolUtil::writef(getStream(), kMsgQInfo);
}
CClientProxy1_0::~CClientProxy1_0()
{
removeHandlers();
}
void
CClientProxy1_0::disconnect()
{
removeHandlers();
getStream()->close();
EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), getEventTarget()));
}
void
CClientProxy1_0::removeHandlers()
{
// uninstall event handlers
EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(),
getStream()->getEventTarget());
EVENTQUEUE->removeHandler(IStream::getOutputErrorEvent(),
getStream()->getEventTarget());
EVENTQUEUE->removeHandler(IStream::getInputShutdownEvent(),
getStream()->getEventTarget());
EVENTQUEUE->removeHandler(IStream::getOutputShutdownEvent(),
getStream()->getEventTarget());
EVENTQUEUE->removeHandler(CEvent::kTimer, this);
// remove timer
removeHeartbeatTimer();
}
void
CClientProxy1_0::addHeartbeatTimer()
{
if (m_heartbeatAlarm > 0.0) {
m_heartbeatTimer = EVENTQUEUE->newOneShotTimer(m_heartbeatAlarm, this);
}
}
void
CClientProxy1_0::removeHeartbeatTimer()
{
if (m_heartbeatTimer != NULL) {
EVENTQUEUE->deleteTimer(m_heartbeatTimer);
m_heartbeatTimer = NULL;
}
}
void
CClientProxy1_0::handleData(const CEvent&, void*)
{
// handle messages until there are no more. first read message code.
UInt8 code[4];
UInt32 n = getStream()->read(code, 4);
while (n != 0) {
// verify we got an entire code
if (n != 4) {
LOG((CLOG_ERR "incomplete message from \"%s\": %d bytes", getName().c_str(), n));
disconnect();
return;
}
// parse message
LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3]));
if (!(this->*m_parser)(code)) {
LOG((CLOG_ERR "invalid message from client \"%s\"", getName().c_str()));
disconnect();
return;
}
// next message
n = getStream()->read(code, 4);
}
// restart heartbeat timer
removeHeartbeatTimer();
addHeartbeatTimer();
}
bool
CClientProxy1_0::parseHandshakeMessage(const UInt8* code)
{
if (memcmp(code, kMsgCNoop, 4) == 0) {
// discard no-ops
LOG((CLOG_DEBUG2 "no-op from", getName().c_str()));
return true;
}
else if (memcmp(code, kMsgDInfo, 4) == 0) {
// future messages get parsed by parseMessage
m_parser = &CClientProxy1_0::parseMessage;
if (recvInfo()) {
EVENTQUEUE->addEvent(CEvent(getReadyEvent(), getEventTarget()));
return true;
}
}
return false;
}
bool
CClientProxy1_0::parseMessage(const UInt8* code)
{
if (memcmp(code, kMsgDInfo, 4) == 0) {
if (recvInfo()) {
EVENTQUEUE->addEvent(
CEvent(getShapeChangedEvent(), getEventTarget()));
return true;
}
return false;
}
else if (memcmp(code, kMsgCNoop, 4) == 0) {
// discard no-ops
LOG((CLOG_DEBUG2 "no-op from", getName().c_str()));
return true;
}
else if (memcmp(code, kMsgCClipboard, 4) == 0) {
return recvGrabClipboard();
}
else if (memcmp(code, kMsgDClipboard, 4) == 0) {
return recvClipboard();
}
return false;
}
void
CClientProxy1_0::handleDisconnect(const CEvent&, void*)
{
LOG((CLOG_NOTE "client \"%s\" has disconnected", getName().c_str()));
disconnect();
}
void
CClientProxy1_0::handleWriteError(const CEvent&, void*)
{
LOG((CLOG_WARN "error writing to client \"%s\"", getName().c_str()));
disconnect();
}
void
CClientProxy1_0::handleFlatline(const CEvent&, void*)
{
// didn't get a heartbeat fast enough. assume client is dead.
LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str()));
disconnect();
}
bool
CClientProxy1_0::getClipboard(ClipboardID id, IClipboard* clipboard) const
{
CClipboard::copy(clipboard, &m_clipboard[id].m_clipboard);
return true;
}
void
CClientProxy1_0::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
{
x = m_info.m_x;
y = m_info.m_y;
w = m_info.m_w;
h = m_info.m_h;
}
void
CClientProxy1_0::getCursorPos(SInt32&, SInt32&) const
{
assert(0 && "shouldn't be called");
}
void
CClientProxy1_0::enter(SInt32 xAbs, SInt32 yAbs,
UInt32 seqNum, KeyModifierMask mask, bool)
{
LOG((CLOG_DEBUG1 "send enter to \"%s\", %d,%d %d %04x", getName().c_str(), xAbs, yAbs, seqNum, mask));
CProtocolUtil::writef(getStream(), kMsgCEnter,
xAbs, yAbs, seqNum, mask);
}
bool
CClientProxy1_0::leave()
{
LOG((CLOG_DEBUG1 "send leave to \"%s\"", getName().c_str()));
CProtocolUtil::writef(getStream(), kMsgCLeave);
// we can never prevent the user from leaving
return true;
}
void
CClientProxy1_0::setClipboard(ClipboardID id, const IClipboard* clipboard)
{
// ignore if this clipboard is already clean
if (m_clipboard[id].m_dirty) {
// this clipboard is now clean
m_clipboard[id].m_dirty = false;
CClipboard::copy(&m_clipboard[id].m_clipboard, clipboard);
CString data = m_clipboard[id].m_clipboard.marshall();
LOG((CLOG_DEBUG "send clipboard %d to \"%s\" size=%d", id, getName().c_str(), data.size()));
CProtocolUtil::writef(getStream(), kMsgDClipboard, id, 0, &data);
}
}
void
CClientProxy1_0::grabClipboard(ClipboardID id)
{
LOG((CLOG_DEBUG "send grab clipboard %d to \"%s\"", id, getName().c_str()));
CProtocolUtil::writef(getStream(), kMsgCClipboard, id, 0);
// this clipboard is now dirty
m_clipboard[id].m_dirty = true;
}
void
CClientProxy1_0::setClipboardDirty(ClipboardID id, bool dirty)
{
m_clipboard[id].m_dirty = dirty;
}
void
CClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton)
{
LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask));
CProtocolUtil::writef(getStream(), kMsgDKeyDown1_0, key, mask);
}
void
CClientProxy1_0::keyRepeat(KeyID key, KeyModifierMask mask,
SInt32 count, KeyButton)
{
LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d", getName().c_str(), key, mask, count));
CProtocolUtil::writef(getStream(), kMsgDKeyRepeat1_0, key, mask, count);
}
void
CClientProxy1_0::keyUp(KeyID key, KeyModifierMask mask, KeyButton)
{
LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask));
CProtocolUtil::writef(getStream(), kMsgDKeyUp1_0, key, mask);
}
void
CClientProxy1_0::mouseDown(ButtonID button)
{
LOG((CLOG_DEBUG1 "send mouse down to \"%s\" id=%d", getName().c_str(), button));
CProtocolUtil::writef(getStream(), kMsgDMouseDown, button);
}
void
CClientProxy1_0::mouseUp(ButtonID button)
{
LOG((CLOG_DEBUG1 "send mouse up to \"%s\" id=%d", getName().c_str(), button));
CProtocolUtil::writef(getStream(), kMsgDMouseUp, button);
}
void
CClientProxy1_0::mouseMove(SInt32 xAbs, SInt32 yAbs)
{
LOG((CLOG_DEBUG2 "send mouse move to \"%s\" %d,%d", getName().c_str(), xAbs, yAbs));
CProtocolUtil::writef(getStream(), kMsgDMouseMove, xAbs, yAbs);
}
void
CClientProxy1_0::mouseRelativeMove(SInt32, SInt32)
{
// ignore -- not supported in protocol 1.0
}
void
CClientProxy1_0::mouseWheel(SInt32 delta)
{
LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d", getName().c_str(), delta));
CProtocolUtil::writef(getStream(), kMsgDMouseWheel, delta);
}
void
CClientProxy1_0::screensaver(bool on)
{
LOG((CLOG_DEBUG1 "send screen saver to \"%s\" on=%d", getName().c_str(), on ? 1 : 0));
CProtocolUtil::writef(getStream(), kMsgCScreenSaver, on ? 1 : 0);
}
void
CClientProxy1_0::resetOptions()
{
LOG((CLOG_DEBUG1 "send reset options to \"%s\"", getName().c_str()));
CProtocolUtil::writef(getStream(), kMsgCResetOptions);
// reset heart rate and death
m_heartbeatAlarm = kHeartRate * kHeartBeatsUntilDeath;
removeHeartbeatTimer();
addHeartbeatTimer();
}
void
CClientProxy1_0::setOptions(const COptionsList& options)
{
LOG((CLOG_DEBUG1 "send set options to \"%s\" size=%d", getName().c_str(), options.size()));
CProtocolUtil::writef(getStream(), kMsgDSetOptions, &options);
// check options
for (UInt32 i = 0, n = options.size(); i < n; i += 2) {
if (options[i] == kOptionHeartbeat) {
double rate = 1.0e-3 * static_cast<double>(options[i + 1]);
if (rate <= 0.0) {
rate = -1.0;
}
m_heartbeatAlarm = rate * kHeartBeatsUntilDeath;
removeHeartbeatTimer();
addHeartbeatTimer();
}
}
}
bool
CClientProxy1_0::recvInfo()
{
// parse the message
SInt16 x, y, w, h, dummy1, dummy2, dummy3;
if (!CProtocolUtil::readf(getStream(), kMsgDInfo + 4,
&x, &y, &w, &h, &dummy1, &dummy2, &dummy3)) {
return false;
}
LOG((CLOG_DEBUG "received client \"%s\" info shape=%d,%d %dx%d", getName().c_str(), x, y, w, h));
// validate
if (w <= 0 || h <= 0) {
return false;
}
// save
m_info.m_x = x;
m_info.m_y = y;
m_info.m_w = w;
m_info.m_h = h;
// acknowledge receipt
LOG((CLOG_DEBUG1 "send info ack to \"%s\"", getName().c_str()));
CProtocolUtil::writef(getStream(), kMsgCInfoAck);
return true;
}
bool
CClientProxy1_0::recvClipboard()
{
// parse message
ClipboardID id;
UInt32 seqNum;
CString data;
if (!CProtocolUtil::readf(getStream(),
kMsgDClipboard + 4, &id, &seqNum, &data)) {
return false;
}
LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d", getName().c_str(), id, seqNum, data.size()));
// validate
if (id >= kClipboardEnd) {
return false;
}
// save clipboard
m_clipboard[id].m_clipboard.unmarshall(data, 0);
m_clipboard[id].m_sequenceNumber = seqNum;
// notify
CClipboardInfo* info = new CClipboardInfo;
info->m_id = id;
info->m_sequenceNumber = seqNum;
EVENTQUEUE->addEvent(CEvent(getClipboardChangedEvent(),
getEventTarget(), info));
return true;
}
bool
CClientProxy1_0::recvGrabClipboard()
{
// parse message
ClipboardID id;
UInt32 seqNum;
if (!CProtocolUtil::readf(getStream(), kMsgCClipboard + 4, &id, &seqNum)) {
return false;
}
LOG((CLOG_DEBUG "received client \"%s\" grabbed clipboard %d seqnum=%d", getName().c_str(), id, seqNum));
// validate
if (id >= kClipboardEnd) {
return false;
}
// notify
CClipboardInfo* info = new CClipboardInfo;
info->m_id = id;
info->m_sequenceNumber = seqNum;
EVENTQUEUE->addEvent(CEvent(getClipboardGrabbedEvent(),
getEventTarget(), info));
return true;
}
//
// CClientProxy1_0::CClientClipboard
//
CClientProxy1_0::CClientClipboard::CClientClipboard() :
m_clipboard(),
m_sequenceNumber(0),
m_dirty(true)
{
// do nothing
}