barrier/server/CServer.cpp

1631 lines
39 KiB
C++

#include "CServer.h"
#include "CHTTPServer.h"
#include "CInputPacketStream.h"
#include "COutputPacketStream.h"
#include "CProtocolUtil.h"
#include "CServerProtocol.h"
#include "IPrimaryScreen.h"
#include "ProtocolTypes.h"
#include "XScreen.h"
#include "XSynergy.h"
#include "CNetworkAddress.h"
#include "IDataSocket.h"
#include "IListenSocket.h"
#include "ISocketFactory.h"
#include "XSocket.h"
#include "CLock.h"
#include "CThread.h"
#include "CTimerThread.h"
#include "XThread.h"
#include "CFunctionJob.h"
#include "CLog.h"
#include "CStopwatch.h"
#include "TMethodJob.h"
#include <memory>
// hack to work around operator=() bug in STL in g++ prior to v3
#if defined(__GNUC__) && (__GNUC__ < 3)
#define assign(_dst, _src, _type) _dst.reset(_src)
#else
#define assign(_dst, _src, _type) _dst = std::auto_ptr<_type >(_src)
#endif
//
// CServer
//
const SInt32 CServer::s_httpMaxSimultaneousRequests = 3;
CServer::CServer(const CString& serverName) :
m_name(serverName),
m_cleanupSize(&m_mutex, 0),
m_primary(NULL),
m_active(NULL),
m_primaryInfo(NULL),
m_seqNum(0),
m_httpServer(NULL),
m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests)
{
m_socketFactory = NULL;
m_securityFactory = NULL;
m_bindTimeout = 5.0 * 60.0;
}
CServer::~CServer()
{
// do nothing
}
void
CServer::run()
{
try {
log((CLOG_NOTE "starting server"));
// connect to primary screen
while (m_primary == NULL) {
try {
openPrimaryScreen();
}
catch (XScreenOpenFailure&) {
// can't open screen yet. wait a few seconds to retry.
log((CLOG_INFO "failed to open screen. waiting to retry."));
CThread::sleep(3.0);
}
catch (XUnknownClient& e) {
// can't open screen yet. wait a few seconds to retry.
log((CLOG_CRIT "unknown screen name `%s'", e.getName().c_str()));
log((CLOG_NOTE "stopping server"));
return;
}
}
// start listening for new clients
CThread(new TMethodJob<CServer>(this, &CServer::acceptClients));
// start listening for HTTP requests
if (m_config.getHTTPAddress().isValid()) {
m_httpServer = new CHTTPServer(this);
CThread(new TMethodJob<CServer>(this, &CServer::acceptHTTPClients));
}
// handle events
log((CLOG_DEBUG "starting event handling"));
m_primary->run();
// clean up
log((CLOG_NOTE "stopping server"));
cleanupThreads();
delete m_httpServer;
m_httpServer = NULL;
closePrimaryScreen();
}
catch (XBase& e) {
log((CLOG_ERR "server error: %s", e.what()));
// clean up
log((CLOG_NOTE "stopping server"));
cleanupThreads();
delete m_httpServer;
m_httpServer = NULL;
if (m_primary != NULL) {
closePrimaryScreen();
}
}
catch (XThread&) {
// clean up
log((CLOG_NOTE "stopping server"));
cleanupThreads();
delete m_httpServer;
m_httpServer = NULL;
if (m_primary != NULL) {
closePrimaryScreen();
}
throw;
}
catch (...) {
log((CLOG_DEBUG "unknown server error"));
// clean up
log((CLOG_NOTE "stopping server"));
cleanupThreads();
delete m_httpServer;
m_httpServer = NULL;
if (m_primary != NULL) {
closePrimaryScreen();
}
throw;
}
}
void
CServer::quit()
{
m_primary->stop();
}
void
CServer::shutdown()
{
// stop all running threads but don't wait too long since some
// threads may be unable to proceed until this thread returns.
cleanupThreads(3.0);
// done with the HTTP server
delete m_httpServer;
m_httpServer = NULL;
// note -- we do not attempt to close down the primary screen
}
bool
CServer::setConfig(const CConfig& config)
{
typedef std::vector<CThread> CThreads;
CThreads threads;
{
CLock lock(&m_mutex);
// refuse configuration if it doesn't include the primary screen
if (m_primaryInfo != NULL &&
!config.isScreen(m_primaryInfo->m_name)) {
return false;
}
// get the set of screens that are connected but are being
// dropped from the configuration (or who's canonical name
// is changing). don't add the primary screen. also tell
// the secondary screen to disconnect.
for (CScreenList::const_iterator index = m_screens.begin();
index != m_screens.end(); ++index) {
if (index->second != m_primaryInfo &&
!config.isCanonicalName(index->first)) {
assert(index->second->m_protocol != NULL);
index->second->m_protocol->sendClose();
threads.push_back(index->second->m_thread);
}
}
}
// wait a moment to allow each secondary screen to close
// its connection before we close it (to avoid having our
// socket enter TIME_WAIT).
if (threads.size() > 0) {
CThread::sleep(1.0);
}
// cancel the old secondary screen threads
for (CThreads::iterator index = threads.begin();
index != threads.end(); ++index) {
index->cancel();
}
// wait for old secondary screen threads to disconnect. must
// not hold lock while we do this so those threads can finish
// any calls to this object.
for (CThreads::iterator index = threads.begin();
index != threads.end(); ++index) {
index->wait();
}
// cut over
CLock lock(&m_mutex);
m_config = config;
// tell primary screen about reconfiguration
if (m_primary != NULL) {
m_primary->onConfigure();
}
return true;
}
CString
CServer::getPrimaryScreenName() const
{
return m_name;
}
void
CServer::getConfig(CConfig* config) const
{
assert(config != NULL);
CLock lock(&m_mutex);
*config = m_config;
}
UInt32
CServer::getActivePrimarySides() const
{
UInt32 sides = 0;
CLock lock(&m_mutex);
if (!m_config.getNeighbor(getPrimaryScreenName(),
CConfig::kLeft).empty()) {
sides |= CConfig::kLeftMask;
}
if (!m_config.getNeighbor(getPrimaryScreenName(),
CConfig::kRight).empty()) {
sides |= CConfig::kRightMask;
}
if (!m_config.getNeighbor(getPrimaryScreenName(),
CConfig::kTop).empty()) {
sides |= CConfig::kTopMask;
}
if (!m_config.getNeighbor(getPrimaryScreenName(),
CConfig::kBottom).empty()) {
sides |= CConfig::kBottomMask;
}
return sides;
}
void
CServer::setInfo(SInt32 w, SInt32 h, SInt32 zoneSize, SInt32 x, SInt32 y)
{
CLock lock(&m_mutex);
assert(m_primaryInfo != NULL);
setInfoNoLock(m_primaryInfo->m_name, w, h, zoneSize, x, y);
}
void
CServer::setInfo(const CString& client,
SInt32 w, SInt32 h, SInt32 zoneSize, SInt32 x, SInt32 y)
{
CLock lock(&m_mutex);
setInfoNoLock(client, w, h, zoneSize, x, y);
}
void
CServer::setInfoNoLock(const CString& screen,
SInt32 w, SInt32 h, SInt32 zoneSize, SInt32 x, SInt32 y)
{
assert(!screen.empty());
assert(w > 0);
assert(h > 0);
assert(zoneSize >= 0);
// screen must be connected
CScreenList::iterator index = m_screens.find(screen);
if (index == m_screens.end()) {
throw XBadClient();
}
// screen is now ready (i.e. available to user)
CScreenInfo* info = index->second;
info->m_ready = true;
// update screen info
if (info == m_active) {
// update the remote mouse coordinates
m_x = x;
m_y = y;
}
info->m_width = w;
info->m_height = h;
info->m_zoneSize = zoneSize;
log((CLOG_INFO "screen \"%s\" size=%dx%d zone=%d pos=%d,%d", screen.c_str(), w, h, zoneSize, x, y));
// send acknowledgement (if screen isn't the primary)
if (info->m_protocol != NULL) {
info->m_protocol->sendInfoAcknowledgment();
}
// handle resolution change to primary screen
else {
if (info == m_active) {
onMouseMovePrimaryNoLock(x, y);
}
else {
onMouseMoveSecondaryNoLock(0, 0);
}
}
}
void
CServer::grabClipboard(ClipboardID id)
{
CLock lock(&m_mutex);
assert(m_primaryInfo != NULL);
grabClipboardNoLock(id, 0, m_primaryInfo->m_name);
}
void
CServer::grabClipboard(ClipboardID id, UInt32 seqNum, const CString& client)
{
CLock lock(&m_mutex);
grabClipboardNoLock(id, seqNum, client);
}
void
CServer::grabClipboardNoLock(ClipboardID id,
UInt32 seqNum, const CString& screen)
{
// note -- must be locked on entry
CClipboardInfo& clipboard = m_clipboards[id];
// screen must be connected
CScreenList::iterator index = m_screens.find(screen);
if (index == m_screens.end()) {
throw XBadClient();
}
// ignore grab if sequence number is old. always allow primary
// screen to grab.
if (screen != m_primaryInfo->m_name &&
seqNum < clipboard.m_clipboardSeqNum) {
log((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", screen.c_str(), id));
return;
}
// mark screen as owning clipboard
log((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", screen.c_str(), id, clipboard.m_clipboardOwner.c_str()));
clipboard.m_clipboardOwner = screen;
clipboard.m_clipboardSeqNum = seqNum;
// no screens have the new clipboard except the sender
clearGotClipboard(id);
index->second->m_gotClipboard[id] = true;
// tell all other screens to take ownership of clipboard
for (index = m_screens.begin(); index != m_screens.end(); ++index) {
if (index->first != screen) {
CScreenInfo* info = index->second;
if (info->m_protocol == NULL) {
m_primary->grabClipboard(id);
}
else {
info->m_protocol->sendGrabClipboard(id);
}
}
}
// get the clipboard data if primary has it, otherwise mark the
// clipboard data as unknown.
if (m_active->m_protocol == NULL) {
// get clipboard immediately from primary screen
m_primary->getClipboard(id, &clipboard.m_clipboard);
clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
clipboard.m_clipboardReady = true;
}
else {
// clear out the clipboard since existing data is now out of date.
if (clipboard.m_clipboard.open(0)) {
clipboard.m_clipboard.close();
}
clipboard.m_clipboardReady = false;
}
}
void
CServer::setClipboard(ClipboardID id, UInt32 seqNum, const CString& data)
{
CLock lock(&m_mutex);
CClipboardInfo& clipboard = m_clipboards[id];
// ignore update if sequence number is old
if (seqNum < clipboard.m_clipboardSeqNum) {
log((CLOG_INFO "ignored screen \"%s\" update of clipboard %d", clipboard.m_clipboardOwner.c_str(), id));
return;
}
// unmarshall into our clipboard buffer
log((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id));
clipboard.m_clipboardReady = true;
clipboard.m_clipboardData = data;
clipboard.m_clipboard.unmarshall(clipboard.m_clipboardData, 0);
// all screens have an out-of-date clipboard except the sender
clearGotClipboard(id);
CScreenList::const_iterator index =
m_screens.find(clipboard.m_clipboardOwner);
if (index != m_screens.end()) {
index->second->m_gotClipboard[id] = true;
}
// send the new clipboard to the active screen (will do nothing if
// the active screen is the one sending the new clipboard)
sendClipboard(id);
}
bool
CServer::onCommandKey(KeyID /*id*/, KeyModifierMask /*mask*/, bool /*down*/)
{
return false;
}
void
CServer::onKeyDown(KeyID id, KeyModifierMask mask)
{
log((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x", id, mask));
CLock lock(&m_mutex);
assert(m_active != NULL);
// handle command keys
if (onCommandKey(id, mask, true)) {
return;
}
// relay
if (m_active->m_protocol != NULL) {
m_active->m_protocol->sendKeyDown(id, mask);
}
}
void
CServer::onKeyUp(KeyID id, KeyModifierMask mask)
{
log((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x", id, mask));
CLock lock(&m_mutex);
assert(m_active != NULL);
// handle command keys
if (onCommandKey(id, mask, false)) {
return;
}
// relay
if (m_active->m_protocol != NULL) {
m_active->m_protocol->sendKeyUp(id, mask);
}
}
void
CServer::onKeyRepeat(KeyID id, KeyModifierMask mask, SInt32 count)
{
log((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d", id, mask, count));
CLock lock(&m_mutex);
assert(m_active != NULL);
// handle command keys
if (onCommandKey(id, mask, false)) {
onCommandKey(id, mask, true);
return;
}
// relay
if (m_active->m_protocol != NULL) {
m_active->m_protocol->sendKeyRepeat(id, mask, count);
}
}
void
CServer::onMouseDown(ButtonID id)
{
log((CLOG_DEBUG1 "onMouseDown id=%d", id));
CLock lock(&m_mutex);
assert(m_active != NULL);
// relay
if (m_active->m_protocol != NULL) {
m_active->m_protocol->sendMouseDown(id);
}
}
void
CServer::onMouseUp(ButtonID id)
{
log((CLOG_DEBUG1 "onMouseUp id=%d", id));
CLock lock(&m_mutex);
assert(m_active != NULL);
// relay
if (m_active->m_protocol != NULL) {
m_active->m_protocol->sendMouseUp(id);
}
}
bool
CServer::onMouseMovePrimary(SInt32 x, SInt32 y)
{
log((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y));
CLock lock(&m_mutex);
return onMouseMovePrimaryNoLock(x, y);
}
bool
CServer::onMouseMovePrimaryNoLock(SInt32 x, SInt32 y)
{
// mouse move on primary (server's) screen
assert(m_active != NULL);
assert(m_active->m_protocol == NULL);
// ignore if mouse is locked to screen
if (isLockedToScreenNoLock()) {
return false;
}
// see if we should change screens
CConfig::EDirection dir;
if (x < m_active->m_zoneSize) {
x -= m_active->m_zoneSize;
dir = CConfig::kLeft;
log((CLOG_DEBUG1 "switch to left"));
}
else if (x >= m_active->m_width - m_active->m_zoneSize) {
x += m_active->m_zoneSize;
dir = CConfig::kRight;
log((CLOG_DEBUG1 "switch to right"));
}
else if (y < m_active->m_zoneSize) {
y -= m_active->m_zoneSize;
dir = CConfig::kTop;
log((CLOG_DEBUG1 "switch to top"));
}
else if (y >= m_active->m_height - m_active->m_zoneSize) {
y += m_active->m_zoneSize;
dir = CConfig::kBottom;
log((CLOG_DEBUG1 "switch to bottom"));
}
else {
// still on local screen
return false;
}
// get jump destination
CScreenInfo* newScreen = getNeighbor(m_active, dir, x, y);
// if no screen in jump direction then ignore the move
if (newScreen == NULL) {
return false;
}
// remap position to account for resolution differences
mapPosition(m_active, dir, newScreen, x, y);
// switch screen
switchScreen(newScreen, x, y);
return true;
}
void
CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
{
log((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy));
CLock lock(&m_mutex);
onMouseMoveSecondaryNoLock(dx, dy);
}
void
CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy)
{
// mouse move on secondary (client's) screen
assert(m_active != NULL);
if (m_active->m_protocol == NULL) {
// we're actually on the primary screen. this can happen
// when the primary screen begins processing a mouse move
// for a secondary screen, then the active (secondary)
// screen disconnects causing us to jump to the primary
// screen, and finally the primary screen finishes
// processing the mouse move, still thinking it's for
// a secondary screen. we just ignore the motion.
return;
}
// save old position
const SInt32 xOld = m_x;
const SInt32 yOld = m_y;
// accumulate motion
m_x += dx;
m_y += dy;
// switch screens if the mouse is outside the screen and not
// locked to the screen
CScreenInfo* newScreen = NULL;
if (!isLockedToScreenNoLock()) {
// find direction of neighbor
CConfig::EDirection dir;
if (m_x < 0) {
dir = CConfig::kLeft;
}
else if (m_x > m_active->m_width - 1) {
dir = CConfig::kRight;
}
else if (m_y < 0) {
dir = CConfig::kTop;
}
else if (m_y > m_active->m_height - 1) {
dir = CConfig::kBottom;
}
else {
newScreen = m_active;
// keep compiler quiet about unset variable
dir = CConfig::kLeft;
}
// get neighbor if we should switch
if (newScreen == NULL) {
log((CLOG_DEBUG1 "leave \"%s\" on %s", m_active->m_name.c_str(), CConfig::dirName(dir)));
SInt32 x = m_x, y = m_y;
newScreen = getNeighbor(m_active, dir, x, y);
// remap position to account for resolution differences
if (newScreen != NULL) {
mapPosition(m_active, dir, newScreen, x, y);
m_x = x;
m_y = y;
}
else {
log((CLOG_DEBUG1 "no neighbor; clamping"));
if (m_x < 0)
m_x = 0;
else if (m_x > m_active->m_width - 1)
m_x = m_active->m_width - 1;
if (m_y < 0)
m_y = 0;
else if (m_y > m_active->m_height - 1)
m_y = m_active->m_height - 1;
}
}
}
else {
// clamp to edge when locked
log((CLOG_DEBUG1 "clamp to \"%s\"", m_active->m_name.c_str()));
if (m_x < 0)
m_x = 0;
else if (m_x > m_active->m_width - 1)
m_x = m_active->m_width - 1;
if (m_y < 0)
m_y = 0;
else if (m_y > m_active->m_height - 1)
m_y = m_active->m_height - 1;
}
// warp cursor if on same screen
if (newScreen == NULL || newScreen == m_active) {
// do nothing if mouse didn't move
if (m_x != xOld || m_y != yOld) {
log((CLOG_DEBUG2 "move on %s to %d,%d", m_active->m_name.c_str(), m_x, m_y));
m_active->m_protocol->sendMouseMove(m_x, m_y);
}
}
// otherwise screen screens
else {
switchScreen(newScreen, m_x, m_y);
}
}
void
CServer::onMouseWheel(SInt32 delta)
{
log((CLOG_DEBUG1 "onMouseWheel %+d", delta));
CLock lock(&m_mutex);
assert(m_active != NULL);
// relay
if (m_active->m_protocol != NULL) {
m_active->m_protocol->sendMouseWheel(delta);
}
}
bool
CServer::isLockedToScreen() const
{
CLock lock(&m_mutex);
return isLockedToScreenNoLock();
}
bool
CServer::isLockedToScreenNoLock() const
{
// locked if primary says we're locked
if (m_primary->isLockedToScreen()) {
return true;
}
// locked if scroll-lock is toggled on
if ((m_primary->getToggleMask() & KeyModifierScrollLock) != 0) {
return true;
}
// not locked
return false;
}
void
CServer::switchScreen(CScreenInfo* dst, SInt32 x, SInt32 y)
{
assert(dst != NULL);
assert(x >= 0 && y >= 0 && x < dst->m_width && y < dst->m_height);
assert(m_active != NULL);
log((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", m_active->m_name.c_str(), dst->m_name.c_str(), x, y));
// FIXME -- we're not locked here but we probably should be
// record new position
m_x = x;
m_y = y;
// wrapping means leaving the active screen and entering it again.
// since that's a waste of time we skip that and just warp the
// mouse.
if (m_active != dst) {
// note if we're leaving the primary screen
const bool leavingPrimary = (m_active->m_protocol == NULL);
// leave active screen
if (leavingPrimary) {
if (!m_primary->leave()) {
// cannot leave primary screen
log((CLOG_WARN "can't leave primary screen"));
return;
}
// update the clipboards that the primary screen owns
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
updatePrimaryClipboard(id);
}
}
else {
m_active->m_protocol->sendLeave();
}
// cut over
m_active = dst;
// increment enter sequence number
++m_seqNum;
// enter new screen
if (m_active->m_protocol == NULL) {
m_primary->enter(x, y);
}
else {
m_active->m_protocol->sendEnter(x, y, m_seqNum,
m_primary->getToggleMask());
}
// send the clipboard data to new active screen
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
sendClipboard(id);
}
}
else {
if (m_active->m_protocol == NULL) {
m_primary->warpCursor(x, y);
}
else {
m_active->m_protocol->sendMouseMove(x, y);
}
}
}
CServer::CScreenInfo*
CServer::getNeighbor(CScreenInfo* src, CConfig::EDirection dir) const
{
assert(src != NULL);
CString srcName = src->m_name;
assert(!srcName.empty());
log((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str()));
for (;;) {
// look up name of neighbor
const CString dstName(m_config.getNeighbor(srcName, dir));
// if nothing in that direction then return NULL
if (dstName.empty()) {
log((CLOG_DEBUG2 "no neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str()));
return NULL;
}
// look up neighbor cell. if the screen is connected and
// ready then we can stop. otherwise we skip over an
// unconnected screen.
CScreenList::const_iterator index = m_screens.find(dstName);
if (index != m_screens.end() && index->second->m_ready) {
log((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\"", dstName.c_str(), CConfig::dirName(dir), srcName.c_str()));
return index->second;
}
log((CLOG_DEBUG2 "ignored \"%s\" on %s of \"%s\"", dstName.c_str(), CConfig::dirName(dir), srcName.c_str()));
srcName = dstName;
}
}
CServer::CScreenInfo*
CServer::getNeighbor(CScreenInfo* src,
CConfig::EDirection srcSide, SInt32& x, SInt32& y) const
{
assert(src != NULL);
// get the first neighbor
CScreenInfo* lastGoodScreen = src;
CScreenInfo* dst = getNeighbor(src, srcSide);
if (dst == NULL) {
return NULL;
}
// get the source screen's size (needed for kRight and kBottom)
SInt32 w = src->m_width, h = src->m_height;
// find destination screen, adjusting x or y (but not both)
switch (srcSide) {
case CConfig::kLeft:
while (dst != NULL && dst != lastGoodScreen) {
lastGoodScreen = dst;
w = lastGoodScreen->m_width;
h = lastGoodScreen->m_height;
x += w;
if (x >= 0) {
break;
}
log((CLOG_DEBUG2 "skipping over screen %s", dst->m_name.c_str()));
dst = getNeighbor(lastGoodScreen, srcSide);
}
break;
case CConfig::kRight:
while (dst != NULL) {
lastGoodScreen = dst;
x -= w;
w = lastGoodScreen->m_width;
h = lastGoodScreen->m_height;
if (x < w) {
break;
}
log((CLOG_DEBUG2 "skipping over screen %s", dst->m_name.c_str()));
dst = getNeighbor(lastGoodScreen, srcSide);
}
break;
case CConfig::kTop:
while (dst != NULL) {
lastGoodScreen = dst;
w = lastGoodScreen->m_width;
h = lastGoodScreen->m_height;
y += h;
if (y >= 0) {
break;
}
log((CLOG_DEBUG2 "skipping over screen %s", dst->m_name.c_str()));
dst = getNeighbor(lastGoodScreen, srcSide);
}
break;
case CConfig::kBottom:
while (dst != NULL) {
lastGoodScreen = dst;
y -= h;
w = lastGoodScreen->m_width;
h = lastGoodScreen->m_height;
if (y < h) {
break;
}
log((CLOG_DEBUG2 "skipping over screen %s", dst->m_name.c_str()));
dst = getNeighbor(lastGoodScreen, srcSide);
}
break;
}
assert(lastGoodScreen != NULL);
/* allow screen to be it's own neighbor to allow wrapping
// no neighbor if best neighbor is the source itself
if (lastGoodScreen == src)
return NULL;
*/
// if entering primary screen then be sure to move in far enough
// to avoid the jump zone. if entering a side that doesn't have
// a neighbor (i.e. an asymmetrical side) then we don't need to
// move inwards because that side can't provoke a jump.
if (lastGoodScreen->m_protocol == NULL) {
const CString dstName(lastGoodScreen->m_name);
switch (srcSide) {
case CConfig::kLeft:
if (!m_config.getNeighbor(dstName, CConfig::kRight).empty() &&
x > w - 1 - lastGoodScreen->m_zoneSize)
x = w - 1 - lastGoodScreen->m_zoneSize;
break;
case CConfig::kRight:
if (!m_config.getNeighbor(dstName, CConfig::kLeft).empty() &&
x < lastGoodScreen->m_zoneSize)
x = lastGoodScreen->m_zoneSize;
break;
case CConfig::kTop:
if (!m_config.getNeighbor(dstName, CConfig::kBottom).empty() &&
y > h - 1 - lastGoodScreen->m_zoneSize)
y = h - 1 - lastGoodScreen->m_zoneSize;
break;
case CConfig::kBottom:
if (!m_config.getNeighbor(dstName, CConfig::kTop).empty() &&
y < lastGoodScreen->m_zoneSize)
y = lastGoodScreen->m_zoneSize;
break;
}
}
return lastGoodScreen;
}
void
CServer::mapPosition(CScreenInfo* src, CConfig::EDirection srcSide,
CScreenInfo* dst, SInt32& x, SInt32& y) const
{
assert(src != NULL);
assert(dst != NULL);
assert(srcSide >= CConfig::kFirstDirection &&
srcSide <= CConfig::kLastDirection);
switch (srcSide) {
case CConfig::kLeft:
case CConfig::kRight:
if (y < 0) {
y = 0;
}
else if (y >= src->m_height) {
y = dst->m_height - 1;
}
else {
y = static_cast<SInt32>(0.5 + y *
static_cast<double>(dst->m_height - 1) /
(src->m_height - 1));
}
break;
case CConfig::kTop:
case CConfig::kBottom:
if (x < 0) {
x = 0;
}
else if (x >= src->m_width) {
x = dst->m_width - 1;
}
else {
x = static_cast<SInt32>(0.5 + x *
static_cast<double>(dst->m_width - 1) /
(src->m_width - 1));
}
break;
}
}
#include "CTCPListenSocket.h"
void
CServer::acceptClients(void*)
{
log((CLOG_DEBUG1 "starting to wait for clients"));
// add this thread to the list of threads to cancel. remove from
// list in d'tor.
CCleanupNote cleanupNote(this);
std::auto_ptr<IListenSocket> listen;
try {
// create socket listener
// listen = std::auto_ptr<IListenSocket>(m_socketFactory->createListen());
assign(listen, new CTCPListenSocket, IListenSocket); // FIXME
// bind to the desired port. keep retrying if we can't bind
// the address immediately.
CStopwatch timer;
for (;;) {
try {
log((CLOG_DEBUG1 "binding listen socket"));
listen->bind(m_config.getSynergyAddress());
break;
}
catch (XSocketAddressInUse&) {
// give up if we've waited too long
if (timer.getTime() >= m_bindTimeout) {
log((CLOG_DEBUG1 "waited too long to bind, giving up"));
throw;
}
// wait a bit before retrying
log((CLOG_DEBUG1 "bind failed; waiting to retry"));
CThread::sleep(5.0);
}
}
// accept connections and begin processing them
log((CLOG_DEBUG1 "waiting for client connections"));
for (;;) {
// accept connection
CThread::testCancel();
IDataSocket* socket = listen->accept();
log((CLOG_NOTE "accepted client connection"));
CThread::testCancel();
// start handshake thread
CThread(new TMethodJob<CServer>(
this, &CServer::handshakeClient, socket));
}
}
catch (XBase& e) {
log((CLOG_ERR "cannot listen for clients: %s", e.what()));
quit();
}
}
void
CServer::handshakeClient(void* vsocket)
{
log((CLOG_DEBUG1 "negotiating with new client"));
// get the socket pointer from the argument
assert(vsocket != NULL);
std::auto_ptr<IDataSocket> socket(reinterpret_cast<IDataSocket*>(vsocket));
// add this thread to the list of threads to cancel. remove from
// list in d'tor.
CCleanupNote cleanupNote(this);
CString name("<unknown>");
try {
// get the input and output streams
IInputStream* srcInput = socket->getInputStream();
IOutputStream* srcOutput = socket->getOutputStream();
std::auto_ptr<IInputStream> input;
std::auto_ptr<IOutputStream> output;
// attach the encryption layer
bool own = false;
if (m_securityFactory != NULL) {
/* FIXME -- implement ISecurityFactory
input.reset(m_securityFactory->createInputFilter(srcInput, own));
output.reset(m_securityFactory->createOutputFilter(srcOutput, own));
srcInput = input.get();
srcOutput = output.get();
own = true;
*/
}
// attach the packetizing filters
assign(input, new CInputPacketStream(srcInput, own), IInputStream);
assign(output, new COutputPacketStream(srcOutput, own), IOutputStream);
std::auto_ptr<IServerProtocol> protocol;
std::auto_ptr<CConnectionNote> connectedNote;
try {
{
// give the client a limited time to complete the handshake
CTimerThread timer(30.0);
// limit the maximum length of the hello
static const UInt32 maxHelloLen = 1024;
// say hello
log((CLOG_DEBUG1 "saying hello"));
CProtocolUtil::writef(output.get(), "Synergy%2i%2i",
kProtocolMajorVersion,
kProtocolMinorVersion);
output->flush();
// wait for the reply
log((CLOG_DEBUG1 "waiting for hello reply"));
UInt32 n = input->getSize();
if (n > maxHelloLen) {
throw XBadClient();
}
// get and parse the reply to hello
SInt16 major, minor;
try {
log((CLOG_DEBUG1 "parsing hello reply"));
CProtocolUtil::readf(input.get(), "Synergy%2i%2i%s",
&major, &minor, &name);
}
catch (XIO&) {
throw XBadClient();
}
// convert name to canonical form (if any)
if (m_config.isScreen(name)) {
name = m_config.getCanonicalName(name);
}
// create a protocol interpreter for the version
log((CLOG_DEBUG1 "creating interpreter for client \"%s\" version %d.%d", name.c_str(), major, minor));
assign(protocol, CServerProtocol::create(major, minor,
this, name, input.get(), output.get()),
IServerProtocol);
// client is now pending
assign(connectedNote, new CConnectionNote(this,
name, protocol.get()), CConnectionNote);
// ask and wait for the client's info
log((CLOG_DEBUG1 "waiting for info for client \"%s\"", name.c_str()));
protocol->queryInfo();
// now connected; client no longer subject to timeout.
}
// handle messages from client. returns when the client
// disconnects.
log((CLOG_NOTE "client \"%s\" has connected", name.c_str()));
protocol->run();
}
catch (XDuplicateClient& e) {
// client has duplicate name
log((CLOG_WARN "a client with name \"%s\" is already connected", e.getName().c_str()));
CProtocolUtil::writef(output.get(), kMsgEBusy);
}
catch (XUnknownClient& e) {
// client has unknown name
log((CLOG_WARN "a client with name \"%s\" is not in the map", e.getName().c_str()));
CProtocolUtil::writef(output.get(), kMsgEUnknown);
}
catch (XIncompatibleClient& e) {
// client is incompatible
// FIXME -- could print network address if socket had suitable method
log((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor()));
CProtocolUtil::writef(output.get(), kMsgEIncompatible,
kProtocolMajorVersion, kProtocolMinorVersion);
}
catch (XBadClient&) {
// client not behaving
// FIXME -- could print network address if socket had suitable method
log((CLOG_WARN "protocol error from client \"%s\"", name.c_str()));
CProtocolUtil::writef(output.get(), kMsgEBad);
}
// flush any pending output
output.get()->flush();
}
catch (XBase& e) {
// misc error
log((CLOG_WARN "error communicating with client \"%s\": %s", name.c_str(), e.what()));
// FIXME -- could print network address if socket had suitable method
}
}
void
CServer::acceptHTTPClients(void*)
{
log((CLOG_DEBUG1 "starting to wait for HTTP clients"));
// add this thread to the list of threads to cancel. remove from
// list in d'tor.
CCleanupNote cleanupNote(this);
std::auto_ptr<IListenSocket> listen;
try {
// create socket listener
// listen = std::auto_ptr<IListenSocket>(m_socketFactory->createListen());
assign(listen, new CTCPListenSocket, IListenSocket); // FIXME
// bind to the desired port. keep retrying if we can't bind
// the address immediately.
CStopwatch timer;
for (;;) {
try {
log((CLOG_DEBUG1 "binding HTTP listen socket"));
listen->bind(m_config.getHTTPAddress());
break;
}
catch (XSocketAddressInUse&) {
// give up if we've waited too long
if (timer.getTime() >= m_bindTimeout) {
log((CLOG_DEBUG1 "waited too long to bind HTTP, giving up"));
throw;
}
// wait a bit before retrying
log((CLOG_DEBUG1 "bind HTTP failed; waiting to retry"));
CThread::sleep(5.0);
}
}
// accept connections and begin processing them
log((CLOG_DEBUG1 "waiting for HTTP connections"));
for (;;) {
// limit the number of HTTP requests being handled at once
{
CLock lock(&m_httpAvailable);
while (m_httpAvailable == 0) {
m_httpAvailable.wait();
}
assert(m_httpAvailable > 0);
m_httpAvailable = m_httpAvailable - 1;
}
// accept connection
CThread::testCancel();
IDataSocket* socket = listen->accept();
log((CLOG_NOTE "accepted HTTP connection"));
CThread::testCancel();
// handle HTTP request
CThread(new TMethodJob<CServer>(
this, &CServer::processHTTPRequest, socket));
}
}
catch (XBase& e) {
log((CLOG_ERR "cannot listen for HTTP clients: %s", e.what()));
// FIXME -- quit?
quit();
}
}
void
CServer::processHTTPRequest(void* vsocket)
{
// add this thread to the list of threads to cancel. remove from
// list in d'tor.
CCleanupNote cleanupNote(this);
IDataSocket* socket = reinterpret_cast<IDataSocket*>(vsocket);
try {
// process the request and force delivery
m_httpServer->processRequest(socket);
socket->getOutputStream()->flush();
// wait a moment to give the client a chance to hangup first
CThread::sleep(3.0);
// clean up
socket->close();
delete socket;
// increment available HTTP handlers
{
CLock lock(&m_httpAvailable);
m_httpAvailable = m_httpAvailable + 1;
m_httpAvailable.signal();
}
}
catch (...) {
delete socket;
{
CLock lock(&m_httpAvailable);
m_httpAvailable = m_httpAvailable + 1;
m_httpAvailable.signal();
}
throw;
}
}
void
CServer::clearGotClipboard(ClipboardID id)
{
for (CScreenList::const_iterator index = m_screens.begin();
index != m_screens.end(); ++index) {
index->second->m_gotClipboard[id] = false;
}
}
void
CServer::sendClipboard(ClipboardID id)
{
// do nothing if clipboard was already sent
if (!m_active->m_gotClipboard[id]) {
CClipboardInfo& clipboard = m_clipboards[id];
if (clipboard.m_clipboardReady) {
// send it
if (m_active->m_protocol == NULL) {
m_primary->setClipboard(id, &clipboard.m_clipboard);
}
else {
m_active->m_protocol->sendClipboard(id,
clipboard.m_clipboardData);
}
// clipboard has been sent
m_active->m_gotClipboard[id] = true;
}
}
}
void
CServer::updatePrimaryClipboard(ClipboardID id)
{
CClipboardInfo& clipboard = m_clipboards[id];
// if leaving primary and the primary owns the clipboard
// then update it.
if (clipboard.m_clipboardOwner == m_primaryInfo->m_name) {
assert(clipboard.m_clipboardReady == true);
// save clipboard time
IClipboard::Time time = clipboard.m_clipboard.getTime();
// update
m_primary->getClipboard(id, &clipboard.m_clipboard);
// if clipboard changed then other screens have an
// out-of-date clipboard.
if (time != clipboard.m_clipboard.getTime()) {
log((CLOG_DEBUG "clipboard %d time changed (%08x to %08x)", id, time, clipboard.m_clipboard.getTime()));
// marshall data
CString newData = clipboard.m_clipboard.marshall();
// compare old and new data. if identical then the clipboard
// hasn't really changed.
if (newData != clipboard.m_clipboardData) {
log((CLOG_DEBUG "clipboard %d changed", id));
clipboard.m_clipboardData = newData;
clearGotClipboard(id);
}
else {
log((CLOG_DEBUG "clipboard %d unchanged", id));
}
m_primaryInfo->m_gotClipboard[id] = true;
}
}
}
// FIXME -- use factory to create screen
#if WINDOWS_LIKE
#include "CMSWindowsPrimaryScreen.h"
#elif UNIX_LIKE
#include "CXWindowsPrimaryScreen.h"
#endif
void
CServer::openPrimaryScreen()
{
assert(m_primary == NULL);
// reset sequence number
m_seqNum = 0;
CString primary = m_config.getCanonicalName(m_name);
if (primary.empty()) {
throw XUnknownClient(m_name);
}
try {
// add connection
m_active = addConnection(primary, NULL);
m_primaryInfo = m_active;
// open screen
log((CLOG_DEBUG1 "creating primary screen"));
#if WINDOWS_LIKE
m_primary = new CMSWindowsPrimaryScreen;
#elif UNIX_LIKE
m_primary = new CXWindowsPrimaryScreen;
#endif
log((CLOG_DEBUG1 "opening primary screen"));
m_primary->open(this);
}
catch (...) {
if (m_primary != NULL) {
removeConnection(primary);
delete m_primary;
}
m_primary = NULL;
m_primaryInfo = NULL;
m_active = NULL;
throw;
}
// set the clipboard owner to the primary screen and then get the
// current clipboard data.
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
CClipboardInfo& clipboard = m_clipboards[id];
m_primary->getClipboard(id, &clipboard.m_clipboard);
clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
clipboard.m_clipboardReady = true;
clipboard.m_clipboardOwner = m_active->m_name;
}
}
void
CServer::closePrimaryScreen()
{
assert(m_primary != NULL);
// remove connection
CString primary = m_config.getCanonicalName(m_name);
removeConnection(primary);
// close the primary screen
try {
log((CLOG_DEBUG1 "closing primary screen"));
m_primary->close();
}
catch (...) {
// ignore
}
// clean up
log((CLOG_DEBUG1 "destroying primary screen"));
delete m_primary;
m_primary = NULL;
}
void
CServer::addCleanupThread(const CThread& thread)
{
CLock lock(&m_mutex);
m_cleanupList.insert(m_cleanupList.begin(), new CThread(thread));
m_cleanupSize = m_cleanupSize + 1;
}
void
CServer::removeCleanupThread(const CThread& thread)
{
CLock lock(&m_mutex);
for (CThreadList::iterator index = m_cleanupList.begin();
index != m_cleanupList.end(); ++index) {
if (**index == thread) {
CThread* thread = *index;
m_cleanupList.erase(index);
m_cleanupSize = m_cleanupSize - 1;
if (m_cleanupSize == 0) {
m_cleanupSize.broadcast();
}
delete thread;
return;
}
}
}
void
CServer::cleanupThreads(double timeout)
{
log((CLOG_DEBUG1 "cleaning up threads"));
// first cancel every thread except the current one (with mutex
// locked so the cleanup list won't change).
CLock lock(&m_mutex);
CThread current(CThread::getCurrentThread());
SInt32 minCount = 0;
for (CThreadList::iterator index = m_cleanupList.begin();
index != m_cleanupList.end(); ++index) {
CThread* thread = *index;
if (thread != &current) {
thread->cancel();
}
else {
minCount = 1;
}
}
// now wait for the threads (with mutex unlocked as each thread
// will remove itself from the list)
CStopwatch timer(true);
while (m_cleanupSize > minCount) {
m_cleanupSize.wait(timer, timeout);
}
// delete remaining threads
for (CThreadList::iterator index = m_cleanupList.begin();
index != m_cleanupList.end(); ++index) {
CThread* thread = *index;
delete thread;
}
m_cleanupList.clear();
m_cleanupSize = 0;
log((CLOG_DEBUG1 "cleaned up threads"));
}
CServer::CScreenInfo*
CServer::addConnection(const CString& name, IServerProtocol* protocol)
{
log((CLOG_DEBUG "adding connection \"%s\"", name.c_str()));
CLock lock(&m_mutex);
// name must be in our configuration
if (!m_config.isScreen(name)) {
throw XUnknownClient(name);
}
// can only have one screen with a given name at any given time
if (m_screens.count(name) != 0) {
throw XDuplicateClient(name);
}
// save screen info
CScreenInfo* newScreen = new CScreenInfo(name, protocol);
m_screens.insert(std::make_pair(name, newScreen));
log((CLOG_DEBUG "added connection \"%s\"", name.c_str()));
return newScreen;
}
void
CServer::removeConnection(const CString& name)
{
log((CLOG_DEBUG "removing connection \"%s\"", name.c_str()));
CLock lock(&m_mutex);
// find screen info
CScreenList::iterator index = m_screens.find(name);
assert(index != m_screens.end());
// if this is active screen then we have to jump off of it
if (m_active == index->second && m_active != m_primaryInfo) {
// record new position (center of primary screen)
m_x = m_primaryInfo->m_width >> 1;
m_y = m_primaryInfo->m_height >> 1;
// don't notify active screen since it probably already disconnected
log((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", m_active->m_name.c_str(), m_primaryInfo->m_name.c_str(), m_x, m_y));
// cut over
m_active = m_primaryInfo;
// enter new screen
m_primary->enter(m_x, m_y);
}
// done with screen info
delete index->second;
m_screens.erase(index);
}
//
// CServer::CCleanupNote
//
CServer::CCleanupNote::CCleanupNote(CServer* server) :
m_server(server)
{
assert(m_server != NULL);
m_server->addCleanupThread(CThread::getCurrentThread());
}
CServer::CCleanupNote::~CCleanupNote()
{
m_server->removeCleanupThread(CThread::getCurrentThread());
}
//
// CServer::CConnectionNote
//
CServer::CConnectionNote::CConnectionNote(CServer* server,
const CString& name, IServerProtocol* protocol) :
m_server(server),
m_name(name)
{
assert(m_server != NULL);
m_server->addConnection(m_name, protocol);
}
CServer::CConnectionNote::~CConnectionNote()
{
m_server->removeConnection(m_name);
}
//
// CServer::CScreenInfo
//
CServer::CScreenInfo::CScreenInfo(const CString& name,
IServerProtocol* protocol) :
m_thread(CThread::getCurrentThread()),
m_name(name),
m_protocol(protocol),
m_ready(false),
m_width(0),
m_height(0),
m_zoneSize(0)
{
for (ClipboardID id = 0; id < kClipboardEnd; ++id)
m_gotClipboard[id] = false;
}
CServer::CScreenInfo::~CScreenInfo()
{
// do nothing
}
//
// CServer::CClipboardInfo
//
CServer::CClipboardInfo::CClipboardInfo() :
m_clipboard(),
m_clipboardData(),
m_clipboardOwner(),
m_clipboardSeqNum(0),
m_clipboardReady(false)
{
// do nothing
}