barrier/CServer.cpp

812 lines
18 KiB
C++

#include "CServer.h"
#include "CEvent.h"
#include "IEventQueue.h"
#include "IScreen.h"
#include "CScreenProxy.h"
#include "ISocket.h"
#include "CSocketFactory.h"
#include "CMessageSocket.h"
#include "TMethodJob.h"
#include "CTrace.h"
#include <assert.h>
#include <string.h>
#include <ctype.h>
#if !defined(NDEBUG)
static const char* s_dirName[] = { "left", "right", "top", "bottom" };
#endif
//
// CServerSocketJob
//
class CServerSocketJob : public IJob {
public:
typedef void (CServer::*ServerMethod)(ISocket*);
CServerSocketJob(CServer*, ServerMethod, ISocket*);
virtual ~CServerSocketJob();
// IJob overrides
virtual void run();
private:
CServer* m_server;
ServerMethod m_method;
ISocket* m_socket;
};
CServerSocketJob::CServerSocketJob(CServer* server,
ServerMethod method, ISocket* socket) :
m_server(server),
m_method(method),
m_socket(socket)
{
// do nothing
}
CServerSocketJob::~CServerSocketJob()
{
// do nothing
}
void CServerSocketJob::run()
{
(m_server->*m_method)(m_socket);
}
//
// CServer
//
class XServerScreenExists { // FIXME
public:
XServerScreenExists(const CString&) { }
};
// the width/height of the zone on the edge of the local screen that
// will provoke a switch to a neighboring screen. this generally
// shouldn't be changed because it effectively reduces the size of
// the local screen's screen.
// FIXME -- should get this from the local screen itself. it may
// need a slightly larger zone (to avoid virtual screens) or it may
// be able to generate off-screen coordinates to provoke the switch
// in which case the size can be zero.
const SInt32 CServer::s_zoneSize = 1;
CServer::CServer() : m_running(false), m_done(false),
m_localScreen(NULL),
m_activeScreen(NULL),
m_listenHost(),
// FIXME -- define kDefaultPort
m_listenPort(40001/*CProtocol::kDefaultPort*/),
m_listenSocket(NULL)
{
// FIXME
}
CServer::~CServer()
{
assert(m_listenSocket == NULL);
// FIXME
}
void CServer::setListenPort(
const CString& hostname, UInt16 port)
{
m_listenHost = hostname;
m_listenPort = port;
}
void CServer::addLocalScreen(IScreen* screen)
{
assert(screen != NULL);
assert(m_running == false);
assert(m_localScreen == NULL);
addScreen(screen->getName(), screen);
m_localScreen = screen;
m_activeScreen = screen;
// open the screen as primary
screen->open(true);
}
void CServer::addRemoteScreen(const CString& name)
{
addScreen(name, NULL);
}
void CServer::addScreen(const CString& name, IScreen* screen)
{
assert(!name.empty());
// cannot add a screen multiple times
if (m_map.count(name) != 0)
throw XServerScreenExists(name);
// add entry for screen in the map
ScreenCell& cell = m_map[name];
// set the cell's screen
cell.m_screen = screen;
}
void CServer::removeScreen(const CString& name)
{
// screen must in map
assert(!name.empty());
assert(m_map.count(name) == 1);
// look up cell
ScreenCell& cell = m_map[name];
// if this is the local screen then there must not be any other
// screens and we must not be running.
assert(cell.m_screen != m_localScreen || (m_map.size() == 1 && !m_running));
// if this is the active screen then warp to the local screen, or
// set no active screen if this is the local screen.
if (cell.m_screen == m_localScreen) {
m_activeScreen = NULL;
m_localScreen = NULL;
}
else if (cell.m_screen == m_activeScreen) {
setActiveScreen(m_localScreen);
}
// close the screen
if (cell.m_screen)
cell.m_screen->close();
// fix up map
if (!cell.m_neighbor[kLeft].empty()) {
assert(m_map.count(cell.m_neighbor[kLeft]) == 1);
m_map[cell.m_neighbor[kLeft]].m_neighbor[kRight] =
cell.m_neighbor[kRight];
}
if (!cell.m_neighbor[kRight].empty()) {
assert(m_map.count(cell.m_neighbor[kRight]) == 1);
m_map[cell.m_neighbor[kRight]].m_neighbor[kLeft] =
cell.m_neighbor[kLeft];
}
if (!cell.m_neighbor[kTop].empty()) {
assert(m_map.count(cell.m_neighbor[kTop]) == 1);
m_map[cell.m_neighbor[kTop]].m_neighbor[kBottom] =
cell.m_neighbor[kBottom];
}
if (!cell.m_neighbor[kBottom].empty()) {
assert(m_map.count(cell.m_neighbor[kBottom]) == 1);
m_map[cell.m_neighbor[kBottom]].m_neighbor[kTop] =
cell.m_neighbor[kTop];
}
}
void CServer::connectEdge(
const CString& src, EDirection srcSide,
const CString& dst)
{
// check input
assert(!src.empty());
assert(!dst.empty());
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// both screens must exist in map
assert(m_map.count(src) == 1);
assert(m_map.count(dst) == 1);
// look up map entry
ScreenCell& cell = m_map[src];
// set edge
cell.m_neighbor[srcSide] = dst;
TRACE(("connect %s:%s to %s", src.c_str(),
s_dirName[srcSide],
cell.m_neighbor[srcSide].c_str()));
}
void CServer::disconnectEdge(
const CString& src, EDirection srcSide)
{
// check input
assert(!src.empty());
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
assert(m_map.count(src) == 1);
TRACE(("disconnect %s:%s from %s", src.c_str(),
s_dirName[srcSide],
m_map[src].m_neighbor[srcSide].c_str()));
// look up map entry
ScreenCell& cell = m_map[src];
// set edge
cell.m_neighbor[srcSide] = CString();
}
void CServer::run()
{
assert(m_running == false);
assert(m_activeScreen != NULL);
assert(m_activeScreen == m_localScreen);
// prepare socket to listen for remote screens
// FIXME -- need m_socketFactory (creates sockets of desired type)
// m_listenSocket = m_socketFactory->createSocket();
m_listenSocket = CSOCKETFACTORY->create();
m_listenSocket->setReadJob(new TMethodJob<CServer>(this,
&CServer::newConnectionCB));
// FIXME -- keep retrying until this works (in case of FIN_WAIT).
// also, must clean up m_listenSocket if this method throws anywhere.
m_listenSocket->listen(m_listenHost, m_listenPort);
// now running
m_running = true;
// event loop
IEventQueue* queue = CEQ;
while (!m_done) {
// wait for new connections, network messages, and user events
queue->wait(-1.0);
// handle events
while (!queue->isEmpty()) {
// get the next event
CEvent event;
queue->pop(&event);
// handle it
switch (event.m_any.m_type) {
case CEventBase::kNull:
// do nothing
break;
case CEventBase::kKeyDown:
case CEventBase::kKeyRepeat:
case CEventBase::kKeyUp:
if (!onCommandKey(&event.m_key))
relayEvent(&event);
break;
case CEventBase::kMouseDown:
case CEventBase::kMouseUp:
case CEventBase::kMouseWheel:
relayEvent(&event);
break;
case CEventBase::kMouseMove:
if (m_localScreen == m_activeScreen)
onLocalMouseMove(event.m_mouse.m_x, event.m_mouse.m_y);
else
onRemoteMouseMove(event.m_mouse.m_x, event.m_mouse.m_y);
break;
case CEventBase::kScreenSize:
// FIXME
break;
}
}
}
// reset
m_running = false;
m_done = false;
// tell screens to shutdown
// FIXME
// close our socket
delete m_listenSocket;
m_listenSocket = NULL;
}
void CServer::onClipboardChanged(IScreen*)
{
// FIXME -- should take screen name not screen pointer
// FIXME
}
void CServer::setActiveScreen(IScreen* screen)
{
// FIXME -- should take screen name not screen pointer
assert(screen != NULL);
assert(m_map.count(screen->getName()) == 1);
// ignore if no change
if (m_activeScreen == screen)
return;
// get center of screen
SInt32 w, h;
screen->getSize(&w, &h);
w >>= 1;
h >>= 1;
// switch
switchScreen(screen, w, h);
}
IScreen* CServer::getActiveScreen() const
{
return m_activeScreen;
}
void CServer::relayEvent(const CEvent* event)
{
assert(event != NULL);
assert(m_activeScreen != NULL);
// ignore attempts to relay to the local screen
if (m_activeScreen == m_localScreen)
return;
// relay the event
switch (event->m_any.m_type) {
case CEventBase::kNull:
// do nothing
break;
case CEventBase::kKeyDown:
m_activeScreen->onKeyDown(event->m_key.m_key);
break;
case CEventBase::kKeyRepeat:
m_activeScreen->onKeyRepeat(event->m_key.m_key, event->m_key.m_count);
break;
case CEventBase::kKeyUp:
m_activeScreen->onKeyUp(event->m_key.m_key);
break;
case CEventBase::kMouseDown:
m_activeScreen->onMouseDown(event->m_mouse.m_button);
break;
case CEventBase::kMouseUp:
m_activeScreen->onMouseUp(event->m_mouse.m_button);
break;
case CEventBase::kMouseWheel:
m_activeScreen->onMouseWheel(event->m_mouse.m_x);
break;
case CEventBase::kMouseMove:
assert(0 && "kMouseMove relayed");
break;
default:
assert(0 && "invalid event relayed");
break;
}
}
bool CServer::onCommandKey(const CEventKey* /*keyEvent*/)
{
// FIXME -- strip out command keys (e.g. lock to screen, warp, etc.)
return false;
}
void CServer::onLocalMouseMove(SInt32 x, SInt32 y)
{
assert(m_activeScreen == m_localScreen);
// ignore if locked to screen
if (isLockedToScreen())
return;
// get local screen's size
SInt32 w, h;
m_activeScreen->getSize(&w, &h);
// see if we should change screens
EDirection dir;
if (x < s_zoneSize) {
x -= s_zoneSize;
dir = kLeft;
}
else if (x >= w - s_zoneSize) {
x += s_zoneSize;
dir = kRight;
}
else if (y < s_zoneSize) {
y -= s_zoneSize;
dir = kTop;
}
else if (y >= h - s_zoneSize) {
y += s_zoneSize;
dir = kBottom;
}
else {
// still on local screen
return;
}
TRACE(("leave %s on %s", m_activeScreen->getName().c_str(), s_dirName[dir]));
// get new screen. if no screen in that direction then ignore move.
IScreen* newScreen = getNeighbor(m_activeScreen, dir, x, y);
if (newScreen == NULL)
return;
// remap position to account for resolution differences between screens
mapPosition(m_activeScreen, dir, newScreen, x, y);
// switch screen
switchScreen(newScreen, x, y);
}
void CServer::onRemoteMouseMove(SInt32 dx, SInt32 dy)
{
assert(m_activeScreen != NULL);
assert(m_activeScreen != m_localScreen);
// put mouse back in center of local screen's grab area
// XXX m_localScreen->warpToCenter();
// save old position
const SInt32 xOld = m_x;
const SInt32 yOld = m_y;
// accumulate mouse position
m_x += dx;
m_y += dy;
// get active screen's size
SInt32 w, h;
m_activeScreen->getSize(&w, &h);
// switch screens if mouse is outside screen and not locked to screen
IScreen* newScreen = NULL;
if (!isLockedToScreen()) {
// find direction of neighbor
EDirection dir;
if (m_x < 0)
dir = kLeft;
else if (m_x > w - 1)
dir = kRight;
else if (m_y < 0)
dir = kTop;
else if (m_y > h - 1)
dir = kBottom;
else
newScreen = m_activeScreen;
// get neighbor if we should switch
if (newScreen == NULL) {
TRACE(("leave %s on %s", m_activeScreen->getName().c_str(),
s_dirName[dir]));
SInt32 x = m_x, y = m_y;
newScreen = getNeighbor(m_activeScreen, dir, x, y);
// remap position to account for resolution differences
if (newScreen != NULL) {
m_x = x;
m_y = y;
mapPosition(m_activeScreen, dir, newScreen, m_x, m_y);
}
else {
if (m_x < 0)
m_x = 0;
else if (m_x > w - 1)
m_x = w - 1;
if (m_y < 0)
m_y = 0;
else if (m_y > h - 1)
m_y = h - 1;
}
}
}
// clamp mouse position if locked to screen
else {
TRACE(("clamp to %s", m_activeScreen->getName().c_str()));
if (m_x < 0)
m_x = 0;
else if (m_x > w - 1)
m_x = w - 1;
if (m_y < 0)
m_y = 0;
else if (m_y > h - 1)
m_y = h - 1;
}
// if on same screen then warp cursor
if (newScreen == NULL || newScreen == m_activeScreen) {
// ignore if clamped mouse didn't move
if (m_x != xOld || m_y != yOld) {
TRACE(("move on %s to %d,%d",
m_activeScreen->getName().c_str(), m_x, m_y));
m_activeScreen->onMouseMove(m_x, m_y);
}
}
// otherwise switch the screen
else {
switchScreen(newScreen, m_x, m_y);
}
}
bool CServer::isLockedToScreen() const
{
// FIXME
return false;
}
void CServer::mapPosition(
const IScreen* src, EDirection srcSide,
const IScreen* dst, SInt32& x, SInt32& y) const
{
assert(src != NULL);
assert(dst != NULL);
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// get sizes
SInt32 wSrc, hSrc, wDst, hDst;
src->getSize(&wSrc, &hSrc);
dst->getSize(&wDst, &hDst);
// remap
switch (srcSide) {
case kLeft:
case kRight:
assert(y >= 0 && y < hSrc);
y = static_cast<SInt32>(0.5 + y *
static_cast<double>(hDst - 1) / (hSrc - 1));
break;
case kTop:
case kBottom:
assert(x >= 0 && x < wSrc);
x = static_cast<SInt32>(0.5 + x *
static_cast<double>(wSrc - 1) / (wSrc - 1));
break;
}
}
IScreen* CServer::getNeighbor(
const IScreen* src, EDirection dir) const
{
// check input
assert(src != NULL);
assert(dir >= kFirstDirection && dir <= kLastDirection);
assert(m_map.count(src->getName()) == 1);
// look up source cell
ScreenMap::const_iterator index = m_map.find(src->getName());
do {
// look up name of neighbor
const ScreenCell& cell = index->second;
const CString dstName(cell.m_neighbor[dir]);
// if nothing in that direction then return NULL
if (dstName.empty())
return NULL;
// look up neighbor cell
assert(m_map.count(dstName) == 1);
index = m_map.find(dstName);
// if no screen pointer then can't go to that neighbor so keep
// searching in the same direction.
#ifndef NDEBUG
if (index->second.m_screen == NULL)
TRACE(("skipping over unconnected screen %s", dstName.c_str()));
#endif
} while (index->second.m_screen == NULL);
return index->second.m_screen;
}
IScreen* CServer::getNeighbor(
const IScreen* src, EDirection srcSide,
SInt32& x, SInt32& y) const
{
// given a position relative to src and which side of the screen we
// left, find the screen we should move onto and where. if the
// position is sufficiently far from src then we may cross multiple
// screens.
// check input
assert(src != NULL);
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// get the first neighbor
IScreen* dst = getNeighbor(src, srcSide);
IScreen* lastGoodScreen = dst;
// get the original screen's size (needed for kRight and kBottom)
SInt32 w, h;
src->getSize(&w, &h);
// find destination screen, adjusting x or y (but not both)
switch (srcSide) {
case kLeft:
while (dst) {
lastGoodScreen = dst;
lastGoodScreen->getSize(&w, &h);
x += w;
if (x >= 0)
break;
TRACE(("skipping over screen %s", dst->getName().c_str()));
dst = getNeighbor(lastGoodScreen, srcSide);
}
break;
case kRight:
while (dst) {
lastGoodScreen = dst;
x -= w;
lastGoodScreen->getSize(&w, &h);
if (x < w)
break;
TRACE(("skipping over screen %s", dst->getName().c_str()));
dst = getNeighbor(lastGoodScreen, srcSide);
}
break;
case kTop:
while (dst) {
lastGoodScreen = dst;
lastGoodScreen->getSize(&w, &h);
y += h;
if (y >= 0)
break;
TRACE(("skipping over screen %s", dst->getName().c_str()));
dst = getNeighbor(lastGoodScreen, srcSide);
}
break;
case kBottom:
while (dst) {
lastGoodScreen = dst;
y -= h;
lastGoodScreen->getSize(&w, &h);
if (y < h)
break;
TRACE(("skipping over screen %s", dst->getName().c_str()));
dst = getNeighbor(lastGoodScreen, srcSide);
}
break;
}
// if entering local screen then be sure to move in far enough to
// avoid the switching 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 switch.
if (lastGoodScreen == m_localScreen) {
ScreenMap::const_iterator index = m_map.find(m_localScreen->getName());
const ScreenCell& cell = index->second;
switch (srcSide) {
case kLeft:
if (!cell.m_neighbor[kRight].empty() && x > w - 1 - s_zoneSize)
x = w - 1 - s_zoneSize;
break;
case kRight:
if (!cell.m_neighbor[kLeft].empty() && x < s_zoneSize)
x = s_zoneSize;
break;
case kTop:
if (!cell.m_neighbor[kBottom].empty() && y > h - 1 - s_zoneSize)
y = h - 1 - s_zoneSize;
break;
case kBottom:
if (!cell.m_neighbor[kTop].empty() && y < s_zoneSize)
y = s_zoneSize;
break;
}
}
return lastGoodScreen;
}
void CServer::switchScreen(
IScreen* screen, SInt32 x, SInt32 y)
{
assert(screen != NULL);
assert(m_running == true);
assert(m_activeScreen != NULL);
#ifndef NDEBUG
{
SInt32 w, h;
screen->getSize(&w, &h);
assert(x >= 0 && y >= 0 && x < w && y < h);
}
#endif
TRACE(("switch %s to %s at %d,%d", m_activeScreen->getName().c_str(),
screen->getName().c_str(), x, 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_activeScreen != screen) {
// leave active screen
m_activeScreen->leaveScreen();
// cut over
m_activeScreen = screen;
// enter new screen
m_activeScreen->enterScreen(x, y);
}
else {
m_activeScreen->warpCursor(x, y);
}
// record new position
m_x = x;
m_y = y;
}
void CServer::newConnectionCB()
{
ISocket* socket = m_listenSocket->accept();
TRACE(("accepted socket %p", socket));
socket->setReadJob(new CServerSocketJob(this, &CServer::loginCB, socket));
m_logins.insert(socket);
}
void CServer::loginCB(ISocket* socket)
{
// FIXME -- no fixed size buffers
UInt8 buffer[512];
SInt32 n = socket->read(buffer, sizeof(buffer));
if (n == -1) {
TRACE(("socket %p disconnected", socket));
goto fail;
}
TRACE(("read %d bytes from socket %p", n, socket));
if (n <= 10) {
TRACE(("socket %p: bogus %d byte message; hanging up", socket, n));
goto fail;
}
if (n > 10) {
if (::memcmp(buffer, "SYNERGY\000\001", 9) != 0) {
TRACE(("socket %p: bad login", socket));
goto fail;
}
const SInt32 nameLen = static_cast<SInt32>(buffer[9]);
if (nameLen < 1 || nameLen > 64) {
TRACE(("socket %p: bad login name length %d", socket, nameLen));
goto fail;
}
for (SInt32 i = 0; i < nameLen; ++i)
if (!isalnum(buffer[10 + i])) {
TRACE(("socket %p: bad login name", socket));
goto fail;
}
CString name(reinterpret_cast<char*>(buffer + 10), nameLen);
const ScreenMap::iterator index = m_map.find(name);
if (index == m_map.end()) {
TRACE(("socket %p: unknown screen %s", socket, name.c_str()));
goto fail;
}
if (index->second.m_screen != NULL) {
TRACE(("socket %p: screen %s already connected",
socket, name.c_str()));
goto fail;
}
TRACE(("socket %p: login %s", socket, name.c_str()));
CScreenProxy* screen = new CScreenProxy(name, socket);
m_logins.erase(socket);
index->second.m_screen = screen;
index->second.m_screen->open(false);
}
return;
fail:
m_logins.erase(socket);
delete socket;
}