#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 #include #include #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(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, event->m_key.m_mask); break; case CEventBase::kKeyRepeat: m_activeScreen->onKeyRepeat(event->m_key.m_key, event->m_key.m_mask, event->m_key.m_count); break; case CEventBase::kKeyUp: m_activeScreen->onKeyUp(event->m_key.m_key, event->m_key.m_mask); 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(0.5 + y * static_cast(hDst - 1) / (hSrc - 1)); break; case kTop: case kBottom: assert(x >= 0 && x < wSrc); x = static_cast(0.5 + x * static_cast(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(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(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; }