From 1861f21fb522d791f3e38059d86fb67962742af9 Mon Sep 17 00:00:00 2001 From: crs Date: Sat, 14 Feb 2004 14:04:36 +0000 Subject: [PATCH] Checkpoint. synergys now works. Still need to do lib/client and synergyc. --- cmd/synergys/CServerTaskBarReceiver.cpp | 140 +- cmd/synergys/CServerTaskBarReceiver.h | 76 +- .../CXWindowsServerTaskBarReceiver.cpp | 6 - cmd/synergys/CXWindowsServerTaskBarReceiver.h | 4 - cmd/synergys/synergys.cpp | 449 ++- lib/arch/CArch.cpp | 6 + lib/arch/CArch.h | 1 + lib/arch/CArchMultithreadPosix.cpp | 1 + lib/arch/CArchNetworkBSD.cpp | 8 + lib/arch/CArchNetworkBSD.h | 1 + lib/arch/IArchNetwork.h | 3 + lib/arch/vsnprintf.cpp | 1 - lib/base/CEvent.cpp | 22 +- lib/base/CEvent.h | 49 +- lib/base/CEventQueue.cpp | 115 +- lib/base/CEventQueue.h | 21 +- lib/base/CSimpleEventQueueBuffer.cpp | 97 + lib/base/CSimpleEventQueueBuffer.h | 49 + lib/base/CStringUtil.cpp | 4 +- lib/base/IEventQueue.h | 34 +- lib/base/IEventQueueBuffer.h | 94 + lib/io/IStream.cpp | 15 +- lib/net/CNetworkAddress.cpp | 12 + lib/net/CNetworkAddress.h | 12 + lib/net/CSocketMultiplexer.cpp | 5 +- lib/net/CTCPListenSocket.cpp | 5 +- lib/net/CTCPSocket.cpp | 20 +- lib/net/IDataSocket.cpp | 6 +- lib/net/IListenSocket.cpp | 3 +- lib/net/ISocket.cpp | 3 +- ...ueue.cpp => CXWindowsEventQueueBuffer.cpp} | 86 +- ...entQueue.h => CXWindowsEventQueueBuffer.h} | 40 +- lib/platform/CXWindowsScreen.cpp | 807 ++--- lib/platform/CXWindowsScreen.h | 112 +- lib/platform/CXWindowsScreenSaver.cpp | 140 +- lib/platform/CXWindowsScreenSaver.h | 58 +- lib/platform/Makefile.am | 4 +- lib/server/CClientListener.cpp | 189 ++ lib/server/CClientListener.h | 76 + lib/server/CClientProxy.cpp | 39 +- lib/server/CClientProxy.h | 60 +- lib/server/CClientProxy1_0.cpp | 238 +- lib/server/CClientProxy1_0.h | 35 +- lib/server/CClientProxy1_1.cpp | 5 +- lib/server/CClientProxy1_1.h | 3 +- lib/server/CClientProxyUnknown.cpp | 277 ++ lib/server/CClientProxyUnknown.h | 83 + lib/server/CConfig.cpp | 2 - lib/server/CPrimaryClient.cpp | 148 +- lib/server/CPrimaryClient.h | 67 +- lib/server/CServer.cpp | 2755 +++++++---------- lib/server/CServer.h | 376 +-- lib/server/Makefile.am | 6 +- lib/synergy/CProtocolUtil.cpp | 1 - lib/synergy/CScreen.cpp | 156 +- lib/synergy/CScreen.h | 87 +- lib/synergy/IClient.h | 68 +- lib/synergy/IPlatformScreen.cpp | 149 + lib/synergy/IPlatformScreen.h | 159 +- lib/synergy/IPrimaryScreen.h | 18 +- lib/synergy/IPrimaryScreenReceiver.h | 73 - lib/synergy/IScreen.cpp | 52 + lib/synergy/IScreen.h | 106 + lib/synergy/IScreenFactory.h | 41 - lib/synergy/IScreenReceiver.h | 65 - lib/synergy/IScreenSaver.h | 1 + lib/synergy/IServer.h | 75 - lib/synergy/Makefile.am | 7 +- 68 files changed, 3812 insertions(+), 4114 deletions(-) create mode 100644 lib/base/CSimpleEventQueueBuffer.cpp create mode 100644 lib/base/CSimpleEventQueueBuffer.h create mode 100644 lib/base/IEventQueueBuffer.h rename lib/platform/{CXWindowsEventQueue.cpp => CXWindowsEventQueueBuffer.cpp} (62%) rename lib/platform/{CXWindowsEventQueue.h => CXWindowsEventQueueBuffer.h} (55%) create mode 100644 lib/server/CClientListener.cpp create mode 100644 lib/server/CClientListener.h create mode 100644 lib/server/CClientProxyUnknown.cpp create mode 100644 lib/server/CClientProxyUnknown.h create mode 100644 lib/synergy/IPlatformScreen.cpp delete mode 100644 lib/synergy/IPrimaryScreenReceiver.h create mode 100644 lib/synergy/IScreen.cpp create mode 100644 lib/synergy/IScreen.h delete mode 100644 lib/synergy/IScreenFactory.h delete mode 100644 lib/synergy/IScreenReceiver.h delete mode 100644 lib/synergy/IServer.h diff --git a/cmd/synergys/CServerTaskBarReceiver.cpp b/cmd/synergys/CServerTaskBarReceiver.cpp index 8349a13c..93b182f4 100644 --- a/cmd/synergys/CServerTaskBarReceiver.cpp +++ b/cmd/synergys/CServerTaskBarReceiver.cpp @@ -14,8 +14,8 @@ #include "CServerTaskBarReceiver.h" #include "CServer.h" +#include "CEventQueue.h" #include "CLock.h" -#include "TMethodJob.h" #include "CArch.h" // @@ -23,71 +23,80 @@ // CServerTaskBarReceiver::CServerTaskBarReceiver() : - m_quit(NULL), - m_state(kNotRunning), - m_server(NULL) + m_state(kNotRunning) { - // create a job for getting notification when the server's - // status changes. - m_job = new TMethodJob(this, - &CServerTaskBarReceiver::statusChanged, NULL); + // do nothing } CServerTaskBarReceiver::~CServerTaskBarReceiver() { - if (m_server != NULL) { - m_server->removeStatusJob(m_job); - } - delete m_job; - delete m_quit; + // do nothing } +#include "CLog.h" void -CServerTaskBarReceiver::setServer(CServer* server) +CServerTaskBarReceiver::updateStatus(CServer* server, const CString& errorMsg) { { + // update our status CLock lock(&m_mutex); - if (m_server != server) { - if (m_server != NULL) { - m_server->removeStatusJob(m_job); + m_errorMessage = errorMsg; + if (server == NULL) { + if (m_errorMessage.empty()) { + m_state = kNotRunning; } - m_server = server; - if (m_server != NULL) { - m_server->addStatusJob(m_job); + else { + m_state = kNotWorking; + } + } + else { + m_clients.clear(); + server->getClients(m_clients); + if (m_clients.size() <= 1) { + m_state = kNotConnected; + } + else { + m_state = kConnected; } } - } - ARCH->updateReceiver(this); -} -void -CServerTaskBarReceiver::setState(EState state) -{ - { - CLock lock(&m_mutex); - m_state = state; + // let subclasses have a go + onStatusChanged(server); +LOG((CLOG_INFO "### status: %s", getToolTip().c_str())); } - ARCH->updateReceiver(this); -} -void -CServerTaskBarReceiver::setQuitJob(IJob* job) -{ - CLock lock(&m_mutex); - delete m_quit; - m_quit = job; + // tell task bar + ARCH->updateReceiver(this); } CServerTaskBarReceiver::EState -CServerTaskBarReceiver::getState() const +CServerTaskBarReceiver::getStatus() const { return m_state; } -CServer* -CServerTaskBarReceiver::getServer() const +const CString& +CServerTaskBarReceiver::getErrorMessage() const { - return m_server; + return m_errorMessage; +} + +const CServerTaskBarReceiver::CClients& +CServerTaskBarReceiver::getClients() const +{ + return m_clients; +} + +void +CServerTaskBarReceiver::quit() +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +void +CServerTaskBarReceiver::onStatusChanged(CServer*) +{ + // do nothing } void @@ -110,7 +119,7 @@ CServerTaskBarReceiver::getToolTip() const return "Synergy: Not running"; case kNotWorking: - return CString("Synergy: ") + m_errorMessage; + return std::string("Synergy: ") + m_errorMessage; case kNotConnected: return "Synergy: Waiting for clients"; @@ -122,50 +131,3 @@ CServerTaskBarReceiver::getToolTip() const return ""; } } - -void -CServerTaskBarReceiver::quit() -{ - if (m_quit != NULL) { - m_quit->run(); - } -} - -void -CServerTaskBarReceiver::onStatusChanged() -{ - // do nothing -} - -void -CServerTaskBarReceiver::statusChanged(void*) -{ - // update our status - switch (m_server->getStatus(&m_errorMessage)) { - case CServer::kNotRunning: - setState(kNotRunning); - break; - - case CServer::kRunning: - if (m_server->getNumClients() > 1) - setState(kConnected); - else - setState(kNotConnected); - break; - - case CServer::kServerNameUnknown: - m_errorMessage = "Server name is not in configuration"; - setState(kNotWorking); - break; - - case CServer::kError: - setState(kNotWorking); - break; - - default: - break; - } - - // let subclasses have a go - onStatusChanged(); -} diff --git a/cmd/synergys/CServerTaskBarReceiver.h b/cmd/synergys/CServerTaskBarReceiver.h index 0591a097..372d73f2 100644 --- a/cmd/synergys/CServerTaskBarReceiver.h +++ b/cmd/synergys/CServerTaskBarReceiver.h @@ -18,61 +18,24 @@ #include "CMutex.h" #include "CString.h" #include "IArchTaskBarReceiver.h" +#include "stdvector.h" class CServer; -class IJob; //! Implementation of IArchTaskBarReceiver for the synergy server class CServerTaskBarReceiver : public IArchTaskBarReceiver { public: - enum EState { - kNotRunning, - kNotWorking, - kNotConnected, - kConnected, - kMaxState - }; - CServerTaskBarReceiver(); virtual ~CServerTaskBarReceiver(); //! @name manipulators //@{ - //! Set server + //! Update status /*! - Sets the server. The receiver will query state from this server. + Determine the status and query required information from the server. */ - void setServer(CServer*); - - //! Set state - /*! - Sets the current server state. - */ - void setState(EState); - - //! Set the quit job that causes the server to quit - /*! - Set the job that causes the server to quit. - */ - void setQuitJob(IJob* adopted); - - //@} - //! @name accessors - //@{ - - //! Get state - /*! - Returns the current server state. The receiver is not locked - by this call; the caller must do the locking. - */ - EState getState() const; - - //! Get server - /*! - Returns the server set by \c setServer(). - */ - CServer* getServer() const; + void updateStatus(CServer*, const CString& errorMsg); //@} @@ -86,6 +49,28 @@ public: virtual std::string getToolTip() const; protected: + typedef std::vector CClients; + enum EState { + kNotRunning, + kNotWorking, + kNotConnected, + kConnected, + kMaxState + }; + + //! Get status + EState getStatus() const; + + //! Get error message + const CString& getErrorMessage() const; + + //! Get connected clients + const CClients& getClients() const; + + //! Quit app + /*! + Causes the application to quit gracefully + */ void quit(); //! Status change notification @@ -93,18 +78,13 @@ protected: Called when status changes. The default implementation does nothing. */ - virtual void onStatusChanged(); - -private: - void statusChanged(void*); + virtual void onStatusChanged(CServer* server); private: CMutex m_mutex; - IJob* m_quit; EState m_state; - CServer* m_server; - IJob* m_job; CString m_errorMessage; + CClients m_clients; }; #endif diff --git a/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp b/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp index 20118732..b3e683a2 100644 --- a/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp +++ b/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp @@ -53,9 +53,3 @@ CXWindowsServerTaskBarReceiver::getIcon() const { return NULL; } - -void -CXWindowsServerTaskBarReceiver::onStatusChanged() -{ - // do nothing -} diff --git a/cmd/synergys/CXWindowsServerTaskBarReceiver.h b/cmd/synergys/CXWindowsServerTaskBarReceiver.h index a05a4307..ad114145 100644 --- a/cmd/synergys/CXWindowsServerTaskBarReceiver.h +++ b/cmd/synergys/CXWindowsServerTaskBarReceiver.h @@ -28,10 +28,6 @@ public: virtual void runMenu(int x, int y); virtual void primaryAction(); virtual const Icon getIcon() const; - -protected: - // CServerTaskBarReceiver overrides - virtual void onStatusChanged(); }; #endif diff --git a/cmd/synergys/synergys.cpp b/cmd/synergys/synergys.cpp index cfa66e8a..ad5ecde7 100644 --- a/cmd/synergys/synergys.cpp +++ b/cmd/synergys/synergys.cpp @@ -12,22 +12,25 @@ * GNU General Public License for more details. */ -#include "CServer.h" +#include "CClientListener.h" +#include "CClientProxy.h" #include "CConfig.h" -#include "IScreenFactory.h" +#include "CPrimaryClient.h" +#include "CServer.h" +#include "CScreen.h" #include "ProtocolTypes.h" #include "Version.h" #include "XScreen.h" +#include "CSocketMultiplexer.h" #include "CTCPSocketFactory.h" #include "XSocket.h" -#include "CLock.h" -#include "CMutex.h" #include "CThread.h" -#include "XThread.h" -#include "CFunctionJob.h" +#include "CEventQueue.h" +#include "CFunctionEventJob.h" #include "CLog.h" #include "LogOutputters.h" #include "CArch.h" +#include "XArch.h" #include "stdfstream.h" #include @@ -98,166 +101,301 @@ CArgs* CArgs::s_instance = NULL; // platform dependent factories // -//! Factory for creating screens -/*! -Objects of this type create screens appropriate for the platform. -*/ -class CScreenFactory : public IScreenFactory { -public: - CScreenFactory() { } - virtual ~CScreenFactory() { } - - // IScreenFactory overrides - virtual IPlatformScreen* - create(IScreenReceiver*, IPrimaryScreenReceiver*); -}; - -IPlatformScreen* -CScreenFactory::create(IScreenReceiver* receiver, - IPrimaryScreenReceiver* primaryReceiver) +static +CScreen* +createScreen() { #if WINDOWS_LIKE - return new CMSWindowsScreen(receiver, primaryReceiver); + return new CScreen(new CMSWindowsScreen(true)); #elif UNIX_LIKE - return new CXWindowsScreen(receiver, primaryReceiver); + return new CScreen(new CXWindowsScreen(true)); #endif } -//! CQuitJob -/*! -A job that cancels a given thread. -*/ -class CQuitJob : public IJob { -public: - CQuitJob(const CThread& thread); - ~CQuitJob(); - - // IJob overrides - virtual void run(); - -private: - CThread m_thread; -}; - -CQuitJob::CQuitJob(const CThread& thread) : - m_thread(thread) -{ - // do nothing -} - -CQuitJob::~CQuitJob() -{ - // do nothing -} - -void -CQuitJob::run() -{ - m_thread.cancel(); -} - - // // platform independent main // static CServer* s_server = NULL; +static CPrimaryClient* s_primaryClient = NULL; +static CClientListener* s_listener = NULL; static CServerTaskBarReceiver* s_taskBarReceiver = NULL; static -int -realMain(void) +void +updateStatus() { - int result = kExitSuccess; - do { - bool opened = false; - bool locked = true; - try { - // if configuration has no screens then add this system - // as the default - if (ARG->m_config.begin() == ARG->m_config.end()) { - ARG->m_config.addScreen(ARG->m_name); - } - - // set the contact address, if provided, in the config. - // otherwise, if the config doesn't have an address, use - // the default. - if (ARG->m_synergyAddress.isValid()) { - ARG->m_config.setSynergyAddress(ARG->m_synergyAddress); - } - else if (!ARG->m_config.getSynergyAddress().isValid()) { - ARG->m_config.setSynergyAddress(CNetworkAddress(kDefaultPort)); - } - - // create server - s_server = new CServer(ARG->m_name); - s_server->setConfig(ARG->m_config); - s_server->setScreenFactory(new CScreenFactory); - s_server->setSocketFactory(new CTCPSocketFactory); - s_server->setStreamFilterFactory(NULL); - - // open server - try { - s_taskBarReceiver->setServer(s_server); - s_server->open(); - opened = true; - - // run server - DAEMON_RUNNING(true); - locked = false; - s_server->mainLoop(); - - // clean up -#define FINALLY do { \ - if (!locked) { \ - DAEMON_RUNNING(false); \ - locked = true; \ - } \ - if (opened) { \ - s_server->close(); \ - } \ - s_taskBarReceiver->setServer(NULL); \ - delete s_server; \ - s_server = NULL; \ - } while (false) - FINALLY; - } - catch (XScreenUnavailable& e) { - // wait before retrying if we're going to retry - if (ARG->m_restartable) { - ARCH->sleep(e.getRetryTime()); - } - else { - result = kExitFailed; - } - FINALLY; - } - catch (XThread&) { - FINALLY; - throw; - } - catch (...) { - // don't try to restart and fail - ARG->m_restartable = false; - result = kExitFailed; - FINALLY; - } -#undef FINALLY - } - catch (XBase& e) { - LOG((CLOG_CRIT "failed: %s", e.what())); - } - catch (XThread&) { - // terminated - ARG->m_restartable = false; - result = kExitTerminated; - } - } while (ARG->m_restartable); - - return result; + s_taskBarReceiver->updateStatus(s_server, ""); } +static +void +updateStatus(const CString& msg) +{ + s_taskBarReceiver->updateStatus(s_server, msg); +} + +static +void +handleClientConnected(const CEvent&, void* vlistener) +{ + CClientListener* listener = reinterpret_cast(vlistener); + CClientProxy* client = listener->getNextClient(); + if (client != NULL) { + s_server->adoptClient(client); + updateStatus(); + } +} + +static +CClientListener* +openClientListener(const CNetworkAddress& address) +{ + CClientListener* listen = + new CClientListener(address, new CTCPSocketFactory, NULL); + EVENTQUEUE->adoptHandler(CClientListener::getConnectedEvent(), listen, + new CFunctionEventJob( + &handleClientConnected, listen)); + return listen; +} + +static +void +closeClientListener(CClientListener* listen) +{ + if (listen != NULL) { + EVENTQUEUE->removeHandler(CClientListener::getConnectedEvent(), listen); + delete listen; + } +} + +static +void +handleScreenError(const CEvent&, void*) +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +static +CPrimaryClient* +openPrimaryClient(const CString& name) +{ + LOG((CLOG_DEBUG1 "creating primary screen")); + CScreen* screen = createScreen(); + CPrimaryClient* primaryClient = new CPrimaryClient(name, screen); + EVENTQUEUE->adoptHandler(IScreen::getErrorEvent(), + primaryClient->getEventTarget(), + new CFunctionEventJob( + &handleScreenError)); + return primaryClient; +} + +static +void +closePrimaryClient(CPrimaryClient* primaryClient) +{ + if (primaryClient != NULL) { + EVENTQUEUE->removeHandler(IScreen::getErrorEvent(), + primaryClient->getEventTarget()); + delete primaryClient; + } +} + +static +void +handleNoClients(const CEvent&, void*) +{ + updateStatus(); +} + +static +void +handleClientsDisconnected(const CEvent&, void*) +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +static +CServer* +openServer(const CConfig& config, CPrimaryClient* primaryClient) +{ + CServer* server = new CServer(config, primaryClient); + EVENTQUEUE->adoptHandler(CServer::getDisconnectedEvent(), server, + new CFunctionEventJob(handleNoClients)); + return server; +} + +static +void +closeServer(CServer* server) +{ + if (server == NULL) { + return; + } + + // tell all clients to disconnect + server->disconnect(); + + // wait for clients to disconnect for up to timeout seconds + double timeout = 3.0; + CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(timeout, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, + new CFunctionEventJob(handleClientsDisconnected)); + EVENTQUEUE->adoptHandler(CServer::getDisconnectedEvent(), server, + new CFunctionEventJob(handleClientsDisconnected)); + CEvent event; + EVENTQUEUE->getEvent(event); + while (event.getType() != CEvent::kQuit) { + EVENTQUEUE->dispatchEvent(event); + CEvent::deleteData(event); + EVENTQUEUE->getEvent(event); + } + EVENTQUEUE->removeHandler(CEvent::kTimer, timer); + EVENTQUEUE->deleteTimer(timer); + EVENTQUEUE->removeHandler(CServer::getDisconnectedEvent(), server); + + // done with server + delete server; +} + +static bool startServer(); + +static +void +retryStartHandler(const CEvent&, void* vtimer) +{ + // discard old timer + CEventQueueTimer* timer = reinterpret_cast(vtimer); + EVENTQUEUE->deleteTimer(timer); + EVENTQUEUE->removeHandler(CEvent::kTimer, NULL); + + // try starting the server again + LOG((CLOG_DEBUG1 "retry starting server")); + startServer(); +} + +static +bool +startServer() +{ + double retryTime; + CPrimaryClient* primaryClient = NULL; + CClientListener* listener = NULL; + try { + CString name = ARG->m_config.getCanonicalName(ARG->m_name); + primaryClient = openPrimaryClient(name); + listener = openClientListener(ARG->m_config.getSynergyAddress()); + s_server = openServer(ARG->m_config, primaryClient); + s_primaryClient = primaryClient; + s_listener = listener; + updateStatus(); + LOG((CLOG_NOTE "started server")); + return true; + } + catch (XScreenUnavailable& e) { + LOG((CLOG_WARN "cannot open primary screen: %s", e.what())); + closeClientListener(listener); + closePrimaryClient(primaryClient); + updateStatus(CString("cannot open primary screen: ") + e.what()); + retryTime = e.getRetryTime(); + } + catch (XSocketAddressInUse& e) { + LOG((CLOG_WARN "cannot listen for clients: %s", e.what())); + closeClientListener(listener); + closePrimaryClient(primaryClient); + updateStatus(CString("cannot listen for clients: ") + e.what()); + retryTime = 10.0; + } + catch (XScreenOpenFailure& e) { + LOG((CLOG_CRIT "cannot open primary screen: %s", e.what())); + closeClientListener(listener); + closePrimaryClient(primaryClient); + return false; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closeClientListener(listener); + closePrimaryClient(primaryClient); + return false; + } + + if (ARG->m_restartable) { + // install a timer and handler to retry later + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, + new CFunctionEventJob(&retryStartHandler, timer)); + return true; + } + else { + // don't try again + return false; + } +} + +static +int +realMain() +{ + // if configuration has no screens then add this system + // as the default + if (ARG->m_config.begin() == ARG->m_config.end()) { + ARG->m_config.addScreen(ARG->m_name); + } + + // set the contact address, if provided, in the config. + // otherwise, if the config doesn't have an address, use + // the default. + if (ARG->m_synergyAddress.isValid()) { + ARG->m_config.setSynergyAddress(ARG->m_synergyAddress); + } + else if (!ARG->m_config.getSynergyAddress().isValid()) { + ARG->m_config.setSynergyAddress(CNetworkAddress(kDefaultPort)); + } + + // canonicalize the primary screen name + CString primaryName = ARG->m_config.getCanonicalName(ARG->m_name); + if (primaryName.empty()) { + LOG((CLOG_CRIT "unknown screen name `%s'", ARG->m_name.c_str())); + return kExitFailed; + } + + // start the server. if this return false then we've failed and + // we shouldn't retry. + LOG((CLOG_DEBUG1 "starting server")); + if (!startServer()) { + return kExitFailed; + } + + // run event loop. if startServer() failed we're supposed to retry + // later. the timer installed by startServer() will take care of + // that. + DAEMON_RUNNING(true); + CEvent event; + EVENTQUEUE->getEvent(event); + while (event.getType() != CEvent::kQuit) { + EVENTQUEUE->dispatchEvent(event); + CEvent::deleteData(event); + EVENTQUEUE->getEvent(event); + } + DAEMON_RUNNING(false); + + // close down + LOG((CLOG_DEBUG1 "stopping server")); + closeClientListener(s_listener); + closeServer(s_server); + closePrimaryClient(s_primaryClient); + s_server = NULL; + s_listener = NULL; + s_primaryClient = NULL; + updateStatus(); + LOG((CLOG_NOTE "stopped server")); + + return kExitSuccess; +} + +/* XXX static void realMainEntry(void* vresult) @@ -293,7 +431,7 @@ runMainInThread(void) throw; } } - +*/ // // command line parsing @@ -666,9 +804,6 @@ daemonStartup(int argc, const char** argv) { CSystemLogger sysLogger(DAEMON_NAME); - // have to cancel this thread to quit - s_taskBarReceiver->setQuitJob(new CQuitJob(CThread::getCurrentThread())); - // catch errors that would normally exit bye = &byeThrow; @@ -768,7 +903,6 @@ WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) // through the task bar. s_taskBarReceiver = new CMSWindowsServerTaskBarReceiver(instance, &logBuffer); - s_taskBarReceiver->setQuitJob(new CQuitJob(CThread::getCurrentThread())); int result; try { @@ -817,15 +951,20 @@ main(int argc, char** argv) { CArch arch; CLOG; - CArgs args; + + // go really fast + CThread::getCurrentThread().setPriority(-14); + + CSocketMultiplexer multiplexer; + CEventQueue eventQueue; // get program name + CArgs args; ARG->m_pname = ARCH->getBasename(argv[0]); // make the task bar receiver. the user can control this app // through the task bar. s_taskBarReceiver = new CXWindowsServerTaskBarReceiver; - s_taskBarReceiver->setQuitJob(new CQuitJob(CThread::getCurrentThread())); // parse command line parse(argc, argv); diff --git a/lib/arch/CArch.cpp b/lib/arch/CArch.cpp index 4f95bc90..d07bb57e 100644 --- a/lib/arch/CArch.cpp +++ b/lib/arch/CArch.cpp @@ -555,6 +555,12 @@ CArch::isAnyAddr(CArchNetAddress addr) return m_net->isAnyAddr(addr); } +bool +CArch::isEqualAddr(CArchNetAddress a, CArchNetAddress b) +{ + return m_net->isEqualAddr(a, b); +} + void CArch::sleep(double timeout) { diff --git a/lib/arch/CArch.h b/lib/arch/CArch.h index 9e80d174..18c1cd08 100644 --- a/lib/arch/CArch.h +++ b/lib/arch/CArch.h @@ -153,6 +153,7 @@ public: virtual void setAddrPort(CArchNetAddress, int port); virtual int getAddrPort(CArchNetAddress); virtual bool isAnyAddr(CArchNetAddress); + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress); // IArchSleep overrides virtual void sleep(double timeout); diff --git a/lib/arch/CArchMultithreadPosix.cpp b/lib/arch/CArchMultithreadPosix.cpp index 707f4dfd..1d5680d6 100644 --- a/lib/arch/CArchMultithreadPosix.cpp +++ b/lib/arch/CArchMultithreadPosix.cpp @@ -575,6 +575,7 @@ CArchMultithreadPosix::interrupt() lockMutex(m_threadMutex); if (m_signalFunc != NULL) { m_signalFunc(m_signalUserData); + pthread_kill(m_mainThread->m_thread, SIGWAKEUP); } else { ARCH->cancelThread(m_mainThread); diff --git a/lib/arch/CArchNetworkBSD.cpp b/lib/arch/CArchNetworkBSD.cpp index bb8dae02..36daf926 100644 --- a/lib/arch/CArchNetworkBSD.cpp +++ b/lib/arch/CArchNetworkBSD.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #if HAVE_POLL # include @@ -750,6 +751,13 @@ CArchNetworkBSD::isAnyAddr(CArchNetAddress addr) } } +bool +CArchNetworkBSD::isEqualAddr(CArchNetAddress a, CArchNetAddress b) +{ + return (a->m_len == b->m_len && + memcmp(&a->m_addr, &b->m_addr, a->m_len) == 0); +} + void CArchNetworkBSD::throwError(int err) { diff --git a/lib/arch/CArchNetworkBSD.h b/lib/arch/CArchNetworkBSD.h index 9718d71b..18f942b8 100644 --- a/lib/arch/CArchNetworkBSD.h +++ b/lib/arch/CArchNetworkBSD.h @@ -77,6 +77,7 @@ public: virtual void setAddrPort(CArchNetAddress, int port); virtual int getAddrPort(CArchNetAddress); virtual bool isAnyAddr(CArchNetAddress); + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress); private: void throwError(int); diff --git a/lib/arch/IArchNetwork.h b/lib/arch/IArchNetwork.h index 1af8cd74..21758800 100644 --- a/lib/arch/IArchNetwork.h +++ b/lib/arch/IArchNetwork.h @@ -265,6 +265,9 @@ public: //! Get the port of an address virtual int getAddrPort(CArchNetAddress) = 0; + //! Test addresses for equality + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress) = 0; + //! Test for the "any" address /*! Returns true if \c addr is the "any" address. \c newAnyAddr() diff --git a/lib/arch/vsnprintf.cpp b/lib/arch/vsnprintf.cpp index f06c0a30..62ccd771 100644 --- a/lib/arch/vsnprintf.cpp +++ b/lib/arch/vsnprintf.cpp @@ -56,7 +56,6 @@ ARCH_STRING::vsnprintf(char* str, int size, const char* fmt, va_list ap) #else // !HAVE_VSNPRINTF && !UNIX_LIKE -// FIXME #error vsnprintf not implemented #endif // !HAVE_VSNPRINTF diff --git a/lib/base/CEvent.cpp b/lib/base/CEvent.cpp index 36f4f674..67451d6a 100644 --- a/lib/base/CEvent.cpp +++ b/lib/base/CEvent.cpp @@ -13,13 +13,12 @@ */ #include "CEvent.h" +#include "CEventQueue.h" // // CEvent // -CEvent::Type CEvent::s_nextType = kLast; - CEvent::CEvent() : m_type(kUnknown), m_target(NULL), @@ -55,20 +54,21 @@ CEvent::getData() const } CEvent::Type -CEvent::registerType() +CEvent::registerType(const char* name) { - // FIXME -- lock mutex (need a mutex) - return s_nextType++; + return EVENTQUEUE->registerType(name); } CEvent::Type -CEvent::registerTypeOnce(Type& type) +CEvent::registerTypeOnce(Type& type, const char* name) { - // FIXME -- lock mutex (need a mutex) - if (type == CEvent::kUnknown) { - type = s_nextType++; - } - return type; + return EVENTQUEUE->registerTypeOnce(type, name); +} + +const char* +CEvent::getTypeName(Type type) +{ + return EVENTQUEUE->getTypeName(type); } void diff --git a/lib/base/CEvent.h b/lib/base/CEvent.h index 8257e603..a2c44735 100644 --- a/lib/base/CEvent.h +++ b/lib/base/CEvent.h @@ -16,6 +16,7 @@ #define CEVENT_H #include "BasicTypes.h" +#include "stdmap.h" //! Event /*! @@ -46,6 +47,33 @@ public: //! @name manipulators //@{ + //! Creates a new event type + /*! + Returns a unique event type id. + */ + static Type registerType(const char* name); + + //! Creates a new event type + /*! + If \p type contains \c kUnknown then it is set to a unique event + type id otherwise it is left alone. The final value of \p type + is returned. + */ + static Type registerTypeOnce(Type& type, const char* name); + + //! Get name for event + /*! + Returns the name for the event \p type. This is primarily for + debugging. + */ + static const char* getTypeName(Type type); + + //! Release event data + /*! + Deletes event data for the given event. + */ + static void deleteData(const CEvent&); + //@} //! @name accessors //@{ @@ -68,33 +96,12 @@ public: */ void* getData() const; - //! Creates a new event type - /*! - Returns a unique event type id. - */ - static Type registerType(); - - //! Creates a new event type - /*! - If \p type contains \c kUnknown then it is set to a unique event - type id otherwise it is left alone. The final value of \p type - is returned. - */ - static Type registerTypeOnce(Type& type); - - //! Release event data - /*! - Deletes event data for the given event. - */ - static void deleteData(const CEvent&); - //@} private: Type m_type; void* m_target; void* m_data; - static Type s_nextType; }; #endif diff --git a/lib/base/CEventQueue.cpp b/lib/base/CEventQueue.cpp index 8528b0ac..879a97ce 100644 --- a/lib/base/CEventQueue.cpp +++ b/lib/base/CEventQueue.cpp @@ -13,7 +13,9 @@ */ #include "CEventQueue.h" +#include "CLog.h" #include "CSimpleEventQueueBuffer.h" +#include "CStopwatch.h" #include "IEventJob.h" #include "CArch.h" @@ -30,7 +32,8 @@ interrupt(void*) // CEventQueue // -CEventQueue::CEventQueue() +CEventQueue::CEventQueue() : + m_nextType(CEvent::kLast) { setInstance(this); m_mutex = ARCH->newMutex(); @@ -46,6 +49,54 @@ CEventQueue::~CEventQueue() setInstance(NULL); } +CEvent::Type +CEventQueue::registerType(const char* name) +{ + CArchMutexLock lock(m_mutex); + m_typeMap.insert(std::make_pair(m_nextType, name)); + LOG((CLOG_DEBUG1 "registered event type %s as %d", name, m_nextType)); + return m_nextType++; +} + +CEvent::Type +CEventQueue::registerTypeOnce(CEvent::Type& type, const char* name) +{ + CArchMutexLock lock(m_mutex); + if (type == CEvent::kUnknown) { + m_typeMap.insert(std::make_pair(m_nextType, name)); + LOG((CLOG_DEBUG1 "registered event type %s as %d", name, m_nextType)); + type = m_nextType++; + } + return type; +} + +const char* +CEventQueue::getTypeName(CEvent::Type type) +{ + switch (type) { + case CEvent::kUnknown: + return "nil"; + + case CEvent::kQuit: + return "quit"; + + case CEvent::kSystem: + return "system"; + + case CEvent::kTimer: + return "timer"; + + default: + CTypeMap::const_iterator i = m_typeMap.find(type); + if (i == m_typeMap.end()) { + return ""; + } + else { + return i->second; + } + } +} + void CEventQueue::adoptBuffer(IEventQueueBuffer* buffer) { @@ -69,45 +120,55 @@ CEventQueue::adoptBuffer(IEventQueueBuffer* buffer) bool CEventQueue::getEvent(CEvent& event, double timeout) { + CStopwatch timer(true); +retry: // if no events are waiting then handle timers and then wait - if (m_buffer->isEmpty()) { + while (m_buffer->isEmpty()) { // handle timers first if (hasTimerExpired(event)) { return true; } + // get time remaining in timeout + double timeLeft = timeout - timer.getTime(); + if (timeout >= 0.0 && timeLeft <= 0.0) { + return false; + } + // get time until next timer expires. if there is a timer // and it'll expire before the client's timeout then use // that duration for our timeout instead. double timerTimeout = getNextTimerTimeout(); - if (timerTimeout >= 0.0 && timerTimeout < timeout) { - timeout = timerTimeout; + if (timeout < 0.0 || (timerTimeout >= 0.0 && timerTimeout < timeLeft)) { + timeLeft = timerTimeout; } // wait for an event - m_buffer->waitForEvent(timeout); - } - - // if no events are pending then do the timers - if (m_buffer->isEmpty()) { - return hasTimerExpired(event); + m_buffer->waitForEvent(timeLeft); } + // get the event UInt32 dataID; IEventQueueBuffer::Type type = m_buffer->getEvent(event, dataID); switch (type) { case IEventQueueBuffer::kNone: + if (timeout < 0.0 || timeout <= timer.getTime()) { + // don't want to fail if client isn't expecting that + // so if getEvent() fails with an infinite timeout + // then just try getting another event. + goto retry; + } return false; case IEventQueueBuffer::kSystem: return true; case IEventQueueBuffer::kUser: - { - CArchMutexLock lock(m_mutex); - event = removeEvent(dataID); - return true; - } + { + CArchMutexLock lock(m_mutex); + event = removeEvent(dataID); + return true; + } } } @@ -156,9 +217,16 @@ CEventQueue::newTimer(double duration, void* target) assert(duration > 0.0); CEventQueueTimer* timer = m_buffer->newTimer(duration, false); + if (target == NULL) { + target = timer; + } CArchMutexLock lock(m_mutex); m_timers.insert(timer); - m_timerQueue.push(CTimer(timer, duration, target, false)); + // initial duration is requested duration plus whatever's on + // the clock currently because the latter will be subtracted + // the next time we check for timers. + m_timerQueue.push(CTimer(timer, duration, + duration + m_time.getTime(), target, false)); return timer; } @@ -168,9 +236,16 @@ CEventQueue::newOneShotTimer(double duration, void* target) assert(duration > 0.0); CEventQueueTimer* timer = m_buffer->newTimer(duration, true); + if (target == NULL) { + target = timer; + } CArchMutexLock lock(m_mutex); m_timers.insert(timer); - m_timerQueue.push(CTimer(timer, duration, target, true)); + // initial duration is requested duration plus whatever's on + // the clock currently because the latter will be subtracted + // the next time we check for timers. + m_timerQueue.push(CTimer(timer, duration, + duration + m_time.getTime(), target, true)); return timer; } @@ -401,13 +476,13 @@ CEventQueue::CTypeTarget::operator<(const CTypeTarget& tt) const // CEventQueue::CTimer // -CEventQueue::CTimer::CTimer(CEventQueueTimer* timer, - double timeout, void* target, bool oneShot) : +CEventQueue::CTimer::CTimer(CEventQueueTimer* timer, double timeout, + double initialTime, void* target, bool oneShot) : m_timer(timer), m_timeout(timeout), m_target(target), m_oneShot(oneShot), - m_time(timeout) + m_time(initialTime) { assert(m_timeout > 0.0); } diff --git a/lib/base/CEventQueue.h b/lib/base/CEventQueue.h index 514612bb..b61eb1e3 100644 --- a/lib/base/CEventQueue.h +++ b/lib/base/CEventQueue.h @@ -39,9 +39,9 @@ public: virtual bool dispatchEvent(const CEvent& event); virtual void addEvent(const CEvent& event); virtual CEventQueueTimer* - newTimer(double duration, void* target = NULL); + newTimer(double duration, void* target); virtual CEventQueueTimer* - newOneShotTimer(double duration, void* target = NULL); + newOneShotTimer(double duration, void* target); virtual void deleteTimer(CEventQueueTimer*); virtual void adoptHandler(void* target, IEventJob* dispatcher); virtual void adoptHandler(CEvent::Type type, @@ -50,8 +50,13 @@ public: virtual IEventJob* orphanHandler(CEvent::Type type, void* target); virtual void removeHandler(void* target); virtual void removeHandler(CEvent::Type type, void* target); + virtual CEvent::Type + registerType(const char* name); + virtual CEvent::Type + registerTypeOnce(CEvent::Type& type, const char* name); virtual bool isEmpty() const; virtual IEventJob* getHandler(CEvent::Type type, void* target) const; + virtual const char* getTypeName(CEvent::Type type); private: void doAdoptHandler(CEvent::Type type, @@ -77,7 +82,8 @@ private: }; class CTimer { public: - CTimer(CEventQueueTimer*, double timeout, void* target, bool oneShot); + CTimer(CEventQueueTimer*, double timeout, double initialTime, + void* target, bool oneShot); ~CTimer(); void reset(); @@ -106,19 +112,28 @@ private: typedef std::map CEventTable; typedef std::vector CEventIDList; typedef std::map CHandlerTable; + typedef std::map CTypeMap; CArchMutex m_mutex; + // registered events + CEvent::Type m_nextType; + CTypeMap m_typeMap; + + // buffer of events IEventQueueBuffer* m_buffer; + // saved events CEventTable m_events; CEventIDList m_oldEventIDs; + // timers CStopwatch m_time; CTimers m_timers; CTimerQueue m_timerQueue; CTimerEvent m_timerEvent; + // event handlers CHandlerTable m_handlers; }; diff --git a/lib/base/CSimpleEventQueueBuffer.cpp b/lib/base/CSimpleEventQueueBuffer.cpp new file mode 100644 index 00000000..f01d2722 --- /dev/null +++ b/lib/base/CSimpleEventQueueBuffer.cpp @@ -0,0 +1,97 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CSimpleEventQueueBuffer.h" +#include "CStopwatch.h" +#include "CArch.h" + +class CEventQueueTimer { }; + +// +// CSimpleEventQueueBuffer +// + +CSimpleEventQueueBuffer::CSimpleEventQueueBuffer() +{ + m_queueMutex = ARCH->newMutex(); + m_queueReadyCond = ARCH->newCondVar(); + m_queueReady = false; +} + +CSimpleEventQueueBuffer::~CSimpleEventQueueBuffer() +{ + ARCH->closeCondVar(m_queueReadyCond); + ARCH->closeMutex(m_queueMutex); +} + +void +CSimpleEventQueueBuffer::waitForEvent(double timeout) +{ + CArchMutexLock lock(m_queueMutex); + CStopwatch timer(true); + while (!m_queueReady) { + double timeLeft = timeout; + if (timeLeft >= 0.0) { + timeLeft -= timer.getTime(); + if (timeLeft < 0.0) { + return; + } + } + ARCH->waitCondVar(m_queueReadyCond, m_queueMutex, timeLeft); + } +} + +IEventQueueBuffer::Type +CSimpleEventQueueBuffer::getEvent(CEvent& event, UInt32& dataID) +{ + CArchMutexLock lock(m_queueMutex); + if (!m_queueReady) { + return kNone; + } + dataID = m_queue.back(); + m_queue.pop_back(); + m_queueReady = !m_queue.empty(); + return kUser; +} + +bool +CSimpleEventQueueBuffer::addEvent(UInt32 dataID) +{ + CArchMutexLock lock(m_queueMutex); + m_queue.push_front(dataID); + if (!m_queueReady) { + m_queueReady = true; + ARCH->broadcastCondVar(m_queueReadyCond); + } + return true; +} + +bool +CSimpleEventQueueBuffer::isEmpty() const +{ + CArchMutexLock lock(m_queueMutex); + return !m_queueReady; +} + +CEventQueueTimer* +CSimpleEventQueueBuffer::newTimer(double, bool) const +{ + return new CEventQueueTimer; +} + +void +CSimpleEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} diff --git a/lib/base/CSimpleEventQueueBuffer.h b/lib/base/CSimpleEventQueueBuffer.h new file mode 100644 index 00000000..c395fabd --- /dev/null +++ b/lib/base/CSimpleEventQueueBuffer.h @@ -0,0 +1,49 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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. + */ + +#ifndef CSIMPLEEVENTQUEUEBUFFER_H +#define CSIMPLEEVENTQUEUEBUFFER_H + +#include "IEventQueueBuffer.h" +#include "IArchMultithread.h" +#include "stddeque.h" + +//! In-memory event queue buffer +/*! +An event queue buffer provides a queue of events for an IEventQueue. +*/ +class CSimpleEventQueueBuffer : public IEventQueueBuffer { +public: + CSimpleEventQueueBuffer(); + ~CSimpleEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void waitForEvent(double timeout); + virtual Type getEvent(CEvent& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(CEventQueueTimer*) const; + +private: + typedef std::deque CEventDeque; + + CArchMutex m_queueMutex; + CArchCond m_queueReadyCond; + bool m_queueReady; + CEventDeque m_queue; +}; + +#endif diff --git a/lib/base/CStringUtil.cpp b/lib/base/CStringUtil.cpp index 2e61c047..08dc61c8 100644 --- a/lib/base/CStringUtil.cpp +++ b/lib/base/CStringUtil.cpp @@ -176,7 +176,7 @@ CStringUtil::CaselessCmp::cmpEqual( const CString::value_type& a, const CString::value_type& b) { - // FIXME -- use std::tolower but not in all versions of libstdc++ have it + // should use std::tolower but not in all versions of libstdc++ have it return tolower(a) == tolower(b); } @@ -185,7 +185,7 @@ CStringUtil::CaselessCmp::cmpLess( const CString::value_type& a, const CString::value_type& b) { - // FIXME -- use std::tolower but not in all versions of libstdc++ have it + // should use std::tolower but not in all versions of libstdc++ have it return tolower(a) < tolower(b); } diff --git a/lib/base/IEventQueue.h b/lib/base/IEventQueue.h index 6e649456..765d4143 100644 --- a/lib/base/IEventQueue.h +++ b/lib/base/IEventQueue.h @@ -80,7 +80,8 @@ public: is returned the data points to a \c CTimerEvent. The client must pass the returned timer to \c deleteTimer() (whether or not the timer has expired) to release the timer. The returned timer event uses the - given \p target. + given \p target. If \p target is NULL it uses the returned timer as + the target. Events for a single timer don't accumulate in the queue, even if the client reading events can't keep up. Instead, the \c m_count member @@ -89,7 +90,7 @@ public: removed (or since the timer was added). */ virtual CEventQueueTimer* - newTimer(double duration, void* target = NULL) = 0; + newTimer(double duration, void* target) = 0; //! Create a one-shot timer /*! @@ -99,11 +100,12 @@ public: The \m c_count member of the \c CTimerEvent is always 1. The client must pass the returned timer to \c deleteTimer() (whether or not the timer has expired) to release the timer. The returned timer event - uses the given \p target. + uses the given \p target. If \p target is NULL it uses the returned + timer as the target. */ virtual CEventQueueTimer* newOneShotTimer(double duration, - void* target = NULL) = 0; + void* target) = 0; //! Destroy a timer /*! @@ -160,6 +162,23 @@ public: */ virtual void removeHandler(CEvent::Type type, void* target) = 0; + //! Creates a new event type + /*! + Returns a unique event type id. + */ + virtual CEvent::Type + registerType(const char* name) = 0; + + //! Creates a new event type + /*! + If \p type contains \c kUnknown then it is set to a unique event + type id otherwise it is left alone. The final value of \p type + is returned. + */ + virtual CEvent::Type + registerTypeOnce(CEvent::Type& type, + const char* name) = 0; + //@} //! @name accessors //@{ @@ -179,6 +198,13 @@ public: */ virtual IEventJob* getHandler(CEvent::Type type, void* target) const = 0; + //! Get name for event + /*! + Returns the name for the event \p type. This is primarily for + debugging. + */ + virtual const char* getTypeName(CEvent::Type type) = 0; + //! Get the system event type target /*! Returns the target to use for dispatching \c CEvent::kSystem events. diff --git a/lib/base/IEventQueueBuffer.h b/lib/base/IEventQueueBuffer.h new file mode 100644 index 00000000..1aff51a6 --- /dev/null +++ b/lib/base/IEventQueueBuffer.h @@ -0,0 +1,94 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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. + */ + +#ifndef IEVENTQUEUEBUFFER_H +#define IEVENTQUEUEBUFFER_H + +#include "IInterface.h" +#include "BasicTypes.h" + +class CEvent; +class CEventQueueTimer; + +//! Event queue buffer interface +/*! +An event queue buffer provides a queue of events for an IEventQueue. +*/ +class IEventQueueBuffer : public IInterface { +public: + enum Type { + kNone, //!< No event is available + kSystem, //!< Event is a system event + kUser //!< Event is a user event + }; + + //! @name manipulators + //@{ + + //! Block waiting for an event + /*! + Wait for an event in the event queue buffer for up to \p timeout + seconds. + */ + virtual void waitForEvent(double timeout) = 0; + + //! Get the next event + /*! + Get the next event from the buffer. Return kNone if no event is + available. If a system event is next, return kSystem and fill in + event. The event data in a system event can point to a static + buffer (because CEvent::deleteData() will not attempt to delete + data in a kSystem event). Otherwise, return kUser and fill in + \p dataID with the value passed to \c addEvent(). + */ + virtual Type getEvent(CEvent& event, UInt32& dataID) = 0; + + //! Post an event + /*! + Add the given event to the end of the queue buffer. This is a user + event and \c getEvent() must be able to identify it as such and + return \p dataID. This method must cause \c waitForEvent() to + return at some future time if it's blocked waiting on an event. + */ + virtual bool addEvent(UInt32 dataID) = 0; + + //@} + //! @name accessors + //@{ + + //! Check if event queue buffer is empty + /*! + Return true iff the event queue buffer is empty. + */ + virtual bool isEmpty() const = 0; + + //! Create a timer object + /*! + Create and return a timer object. The object is opaque and is + used only by the buffer but it must be a valid object (i.e. + not NULL). + */ + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const = 0; + + //! Destroy a timer object + /*! + Destroy a timer object previously returned by \c newTimer(). + */ + virtual void deleteTimer(CEventQueueTimer*) const = 0; + + //@} +}; + +#endif diff --git a/lib/io/IStream.cpp b/lib/io/IStream.cpp index 42947190..aec65b61 100644 --- a/lib/io/IStream.cpp +++ b/lib/io/IStream.cpp @@ -27,29 +27,34 @@ CEvent::Type IStream::s_outputShutdownEvent = CEvent::kUnknown; CEvent::Type IStream::getInputReadyEvent() { - return CEvent::registerTypeOnce(s_inputReadyEvent); + return CEvent::registerTypeOnce(s_inputReadyEvent, + "IStream::inputReady"); } CEvent::Type IStream::getOutputFlushedEvent() { - return CEvent::registerTypeOnce(s_outputFlushedEvent); + return CEvent::registerTypeOnce(s_outputFlushedEvent, + "IStream::outputFlushed"); } CEvent::Type IStream::getOutputErrorEvent() { - return CEvent::registerTypeOnce(s_outputErrorEvent); + return CEvent::registerTypeOnce(s_outputErrorEvent, + "IStream::outputError"); } CEvent::Type IStream::getInputShutdownEvent() { - return CEvent::registerTypeOnce(s_inputShutdownEvent); + return CEvent::registerTypeOnce(s_inputShutdownEvent, + "IStream::inputShutdown"); } CEvent::Type IStream::getOutputShutdownEvent() { - return CEvent::registerTypeOnce(s_outputShutdownEvent); + return CEvent::registerTypeOnce(s_outputShutdownEvent, + "IStream::outputShutdown"); } diff --git a/lib/net/CNetworkAddress.cpp b/lib/net/CNetworkAddress.cpp index c60b96a5..3b2e3032 100644 --- a/lib/net/CNetworkAddress.cpp +++ b/lib/net/CNetworkAddress.cpp @@ -141,6 +141,18 @@ CNetworkAddress::operator=(const CNetworkAddress& addr) return *this; } +bool +CNetworkAddress::operator==(const CNetworkAddress& addr) const +{ + return ARCH->isEqualAddr(m_address, addr.m_address); +} + +bool +CNetworkAddress::operator!=(const CNetworkAddress& addr) const +{ + return !operator==(addr); +} + bool CNetworkAddress::isValid() const { diff --git a/lib/net/CNetworkAddress.h b/lib/net/CNetworkAddress.h index 02898278..f1e66bab 100644 --- a/lib/net/CNetworkAddress.h +++ b/lib/net/CNetworkAddress.h @@ -55,6 +55,18 @@ public: //! @name accessors //@{ + //! Check address equality + /*! + Returns true if this address is equal to \p address. + */ + bool operator==(const CNetworkAddress&) const; + + //! Check address inequality + /*! + Returns true if this address is not equal to \p address. + */ + bool operator!=(const CNetworkAddress&) const; + //! Check address validity /*! Returns true if this is not the invalid address. diff --git a/lib/net/CSocketMultiplexer.cpp b/lib/net/CSocketMultiplexer.cpp index 46191b3c..2ed2dc35 100644 --- a/lib/net/CSocketMultiplexer.cpp +++ b/lib/net/CSocketMultiplexer.cpp @@ -18,6 +18,7 @@ #include "CLock.h" #include "CMutex.h" #include "CThread.h" +#include "CLog.h" #include "TMethodJob.h" #include "CArch.h" #include "XArch.h" @@ -207,8 +208,8 @@ CSocketMultiplexer::serviceThread(void*) // check for status status = ARCH->pollSocket(&pfds[0], pfds.size(), -1); } - catch (XArchNetwork&) { - // FIXME -- uh oh + catch (XArchNetwork& e) { + LOG((CLOG_WARN "error in socket multiplexer: %s", e.what().c_str())); status = 0; } diff --git a/lib/net/CTCPListenSocket.cpp b/lib/net/CTCPListenSocket.cpp index 49695446..bca77cd4 100644 --- a/lib/net/CTCPListenSocket.cpp +++ b/lib/net/CTCPListenSocket.cpp @@ -62,6 +62,7 @@ CTCPListenSocket::bind(const CNetworkAddress& addr) CLock lock(m_mutex); ARCH->bindSocket(m_socket, addr.getAddress()); ARCH->listenOnSocket(m_socket); + ARCH->setBlockingOnSocket(m_socket, false); CSocketMultiplexer::getInstance()->addSocket(this, new TSocketMultiplexerMethodJob( this, &CTCPListenSocket::serviceListening, @@ -102,11 +103,13 @@ IDataSocket* CTCPListenSocket::accept() { try { + IDataSocket* socket = + new CTCPSocket(ARCH->acceptSocket(m_socket, NULL)); CSocketMultiplexer::getInstance()->addSocket(this, new TSocketMultiplexerMethodJob( this, &CTCPListenSocket::serviceListening, m_socket, true, false)); - return new CTCPSocket(ARCH->acceptSocket(m_socket, NULL)); + return socket; } catch (XArchNetwork&) { return NULL; diff --git a/lib/net/CTCPSocket.cpp b/lib/net/CTCPSocket.cpp index fd592203..0b7b6ddf 100644 --- a/lib/net/CTCPSocket.cpp +++ b/lib/net/CTCPSocket.cpp @@ -19,6 +19,7 @@ #include "XSocket.h" #include "CLock.h" #include "CEventQueue.h" +#include "CLog.h" #include "IEventJob.h" #include "CArch.h" #include "XArch.h" @@ -102,8 +103,8 @@ CTCPSocket::close() ARCH->closeSocket(socket); } catch (XArchNetwork& e) { - // FIXME -- just discard this for now - //throw XSocketIOClose(e.what()); + // ignore, there's not much we can do + LOG((CLOG_WARN "error closing socket: %s", e.what().c_str())); } } } @@ -257,7 +258,6 @@ CTCPSocket::connect(const CNetworkAddress& addr) } try { -// FIXME -- don't throw if in progress, just return that info ARCH->connectSocket(m_socket, addr.getAddress()); sendSocketEvent(getConnectedEvent()); onConnected(); @@ -281,15 +281,13 @@ CTCPSocket::init() m_readable = false; m_writable = false; - // make socket non-blocking -// FIXME -- check for error - ARCH->setBlockingOnSocket(m_socket, false); - - // turn off Nagle algorithm. we send lots of very short messages - // that should be sent without (much) delay. for example, the - // mouse motion messages are much less useful if they're delayed. -// FIXME -- the client should do this try { + // make socket non-blocking + ARCH->setBlockingOnSocket(m_socket, false); + + // turn off Nagle algorithm. we send lots of very short messages + // that should be sent without (much) delay. for example, the + // mouse motion messages are much less useful if they're delayed. ARCH->setNoDelayOnSocket(m_socket, true); } catch (XArchNetwork& e) { diff --git a/lib/net/IDataSocket.cpp b/lib/net/IDataSocket.cpp index b3f6a5e1..ff10f4ec 100644 --- a/lib/net/IDataSocket.cpp +++ b/lib/net/IDataSocket.cpp @@ -24,11 +24,13 @@ CEvent::Type IDataSocket::s_failedEvent = CEvent::kUnknown; CEvent::Type IDataSocket::getConnectedEvent() { - return CEvent::registerTypeOnce(s_connectedEvent); + return CEvent::registerTypeOnce(s_connectedEvent, + "IDataSocket::connected"); } CEvent::Type IDataSocket::getConnectionFailedEvent() { - return CEvent::registerTypeOnce(s_failedEvent); + return CEvent::registerTypeOnce(s_failedEvent, + "IDataSocket::failed"); } diff --git a/lib/net/IListenSocket.cpp b/lib/net/IListenSocket.cpp index 9c9e704c..20dbc9a4 100644 --- a/lib/net/IListenSocket.cpp +++ b/lib/net/IListenSocket.cpp @@ -23,5 +23,6 @@ CEvent::Type IListenSocket::s_connectingEvent = CEvent::kUnknown; CEvent::Type IListenSocket::getConnectingEvent() { - return CEvent::registerTypeOnce(s_connectingEvent); + return CEvent::registerTypeOnce(s_connectingEvent, + "IListenSocket::connecting"); } diff --git a/lib/net/ISocket.cpp b/lib/net/ISocket.cpp index b1adbcc3..a9aaf1ac 100644 --- a/lib/net/ISocket.cpp +++ b/lib/net/ISocket.cpp @@ -23,5 +23,6 @@ CEvent::Type ISocket::s_disconnectedEvent = CEvent::kUnknown; CEvent::Type ISocket::getDisconnectedEvent() { - return CEvent::registerTypeOnce(s_disconnectedEvent); + return CEvent::registerTypeOnce(s_disconnectedEvent, + "ISocket::disconnected"); } diff --git a/lib/platform/CXWindowsEventQueue.cpp b/lib/platform/CXWindowsEventQueueBuffer.cpp similarity index 62% rename from lib/platform/CXWindowsEventQueue.cpp rename to lib/platform/CXWindowsEventQueueBuffer.cpp index 9c51284d..7592bc89 100644 --- a/lib/platform/CXWindowsEventQueue.cpp +++ b/lib/platform/CXWindowsEventQueueBuffer.cpp @@ -12,9 +12,10 @@ * GNU General Public License for more details. */ -#include "CXWindowsEventQueue.h" -#include "CEvent.h" +#include "CXWindowsEventQueueBuffer.h" #include "CThread.h" +#include "CEvent.h" +#include "IEventQueue.h" #if UNIX_LIKE # if HAVE_POLL # include @@ -42,52 +43,27 @@ class CEventQueueTimer { }; // -// CXWindowsEventQueue +// CXWindowsEventQueueBuffer // -CXWindowsEventQueue::CXWindowsEventQueue(Display* display) : - m_display(display) +CXWindowsEventQueueBuffer::CXWindowsEventQueueBuffer( + Display* display, Window window) : + m_display(display), + m_window(window) { + assert(m_display != NULL); + assert(m_window != None); + m_userEvent = XInternAtom(m_display, "SYNERGY_USER_EVENT", False); - - XSetWindowAttributes attr; - m_window = XCreateWindow(m_display, DefaultRootWindow(m_display), - 0, 0, 1, 1, 0, 0, InputOnly, CopyFromParent, - 0, &attr); } -CXWindowsEventQueue::~CXWindowsEventQueue() +CXWindowsEventQueueBuffer::~CXWindowsEventQueueBuffer() { - XDestroyWindow(m_display, m_window); + // do nothing } void -CXWindowsEventQueue::processSystemEvent(CEvent& event) -{ - event = CEvent(CEvent::kSystem, getSystemTarget(), &m_event); -} - -void -CXWindowsEventQueue::processClientMessage(CEvent& event) -{ - assert(m_event.xany.type == ClientMessage); - - // handle user events specially - if (m_event.xclient.message_type == m_userEvent) { - // get event data - CEventData data = removeEventData(m_event.xclient.data.l[1]); - - // create event - event = CEvent(static_cast(m_event.xclient.data.l[0]), - data.first, data.second); - } - else { - processSystemEvent(event); - } -} - -void -CXWindowsEventQueue::waitForEvent(double dtimeout) +CXWindowsEventQueueBuffer::waitForEvent(double dtimeout) { // use poll() to wait for a message from the X server or for timeout. // this is a good deal more efficient than polling and sleeping. @@ -132,25 +108,27 @@ CXWindowsEventQueue::waitForEvent(double dtimeout) CThread::testCancel(); } -bool -CXWindowsEventQueue::doGetEvent(CEvent& event) +IEventQueueBuffer::Type +CXWindowsEventQueueBuffer::getEvent(CEvent& event, UInt32& dataID) { // get next event XNextEvent(m_display, &m_event); // process event - if (m_event.xany.type == ClientMessage) { - processClientMessage(event); + if (m_event.xany.type == ClientMessage && + m_event.xclient.message_type == m_userEvent) { + dataID = static_cast(m_event.xclient.data.l[0]); + return kUser; } else { - processSystemEvent(event); + event = CEvent(CEvent::kSystem, + IEventQueue::getSystemTarget(), &m_event); + return kSystem; } - - return true; } bool -CXWindowsEventQueue::doAddEvent(CEvent::Type type, UInt32 dataID) +CXWindowsEventQueueBuffer::addEvent(UInt32 dataID) { // send ourself a message XEvent xevent; @@ -158,25 +136,29 @@ CXWindowsEventQueue::doAddEvent(CEvent::Type type, UInt32 dataID) xevent.xclient.window = m_window; xevent.xclient.message_type = m_userEvent; xevent.xclient.format = 32; - xevent.xclient.data.l[0] = static_cast(type); - xevent.xclient.data.l[1] = static_cast(dataID); - return (XSendEvent(m_display, m_window, False, 0, &xevent) != 0); + xevent.xclient.data.l[0] = static_cast(dataID); + if (XSendEvent(m_display, m_window, False, 0, &xevent) == 0) { + return false; + } + + // force waitForEvent() to return + XFlush(m_display); } bool -CXWindowsEventQueue::doIsEmpty() const +CXWindowsEventQueueBuffer::isEmpty() const { return (XPending(m_display) == 0); } CEventQueueTimer* -CXWindowsEventQueue::doNewTimer(double, bool) const +CXWindowsEventQueueBuffer::newTimer(double, bool) const { return new CEventQueueTimer(); } void -CXWindowsEventQueue::doDeleteTimer(CEventQueueTimer* timer) const +CXWindowsEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const { delete timer; } diff --git a/lib/platform/CXWindowsEventQueue.h b/lib/platform/CXWindowsEventQueueBuffer.h similarity index 55% rename from lib/platform/CXWindowsEventQueue.h rename to lib/platform/CXWindowsEventQueueBuffer.h index 39d9faef..8b3d4312 100644 --- a/lib/platform/CXWindowsEventQueue.h +++ b/lib/platform/CXWindowsEventQueueBuffer.h @@ -12,44 +12,30 @@ * GNU General Public License for more details. */ -#ifndef CXWINDOWSEVENTQUEUE_H -#define CXWINDOWSEVENTQUEUE_H +#ifndef CXWINDOWSEVENTQUEUEBUFFER_H +#define CXWINDOWSEVENTQUEUEBUFFER_H -#include "CEventQueue.h" +#include "IEventQueueBuffer.h" #if defined(X_DISPLAY_MISSING) # error X11 is required to build synergy #else # include #endif -//! Event queue for X11 -class CXWindowsEventQueue : public CEventQueue { +//! Event queue buffer for X11 +class CXWindowsEventQueueBuffer : public IEventQueueBuffer { public: - CXWindowsEventQueue(Display*); - virtual ~CXWindowsEventQueue(); + CXWindowsEventQueueBuffer(Display*, Window); + virtual ~CXWindowsEventQueueBuffer(); - //! @name manipulators - //@{ - - //@} - //! @name accessors - //@{ - - //@} - -protected: - // CEventQueue overrides + // IEventQueueBuffer overrides virtual void waitForEvent(double timeout); - virtual bool doGetEvent(CEvent& event); - virtual bool doAddEvent(CEvent::Type type, UInt32 dataID); - virtual bool doIsEmpty() const; + virtual Type getEvent(CEvent& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; virtual CEventQueueTimer* - doNewTimer(double duration, bool oneShot) const; - virtual void doDeleteTimer(CEventQueueTimer*) const; - -private: - void processSystemEvent(CEvent& event); - void processClientMessage(CEvent& event); + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(CEventQueueTimer*) const; private: Display* m_display; diff --git a/lib/platform/CXWindowsScreen.cpp b/lib/platform/CXWindowsScreen.cpp index 12b1d703..c7e5f1cc 100644 --- a/lib/platform/CXWindowsScreen.cpp +++ b/lib/platform/CXWindowsScreen.cpp @@ -14,18 +14,16 @@ #include "CXWindowsScreen.h" #include "CXWindowsClipboard.h" +#include "CXWindowsEventQueueBuffer.h" #include "CXWindowsScreenSaver.h" #include "CXWindowsUtil.h" #include "CClipboard.h" -#include "IScreenReceiver.h" -#include "IPrimaryScreenReceiver.h" #include "XScreen.h" -#include "CLock.h" -#include "CThread.h" #include "CLog.h" #include "CStopwatch.h" #include "CStringUtil.h" -#include "IJob.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" #include #if defined(X_DISPLAY_MISSING) # error X11 is required to build synergy @@ -109,16 +107,24 @@ static const KeySym g_map1008FF[] = // CXWindowsScreen // +// NOTE -- the X display is shared among several objects but is owned +// by the CXWindowsScreen. Xlib is not reentrant so we must ensure +// that no two objects can simultaneously call Xlib with the display. +// this is easy since we only make X11 calls from the main thread. +// we must also ensure that these objects do not use the display in +// their destructors or, if they do, we can tell them not to. This +// is to handle unexpected disconnection of the X display, when any +// call on the display is invalid. In that situation we discard the +// display and the X11 event queue buffer, ignore any calls that try +// to use the display, and wait to be destroyed. + CXWindowsScreen* CXWindowsScreen::s_screen = NULL; -CXWindowsScreen::CXWindowsScreen(IScreenReceiver* receiver, - IPrimaryScreenReceiver* primaryReceiver) : - m_isPrimary(primaryReceiver != NULL), +CXWindowsScreen::CXWindowsScreen(bool isPrimary) : + m_isPrimary(isPrimary), m_display(NULL), m_root(None), m_window(None), - m_receiver(receiver), - m_primaryReceiver(primaryReceiver), m_isOnScreen(m_isPrimary), m_x(0), m_y(0), m_w(0), m_h(0), @@ -129,205 +135,99 @@ CXWindowsScreen::CXWindowsScreen(IScreenReceiver* receiver, m_im(NULL), m_ic(NULL), m_lastKeycode(0), - m_atomQuit(None), + m_sequenceNumber(0), m_screensaver(NULL), m_screensaverNotify(false), - m_atomScreensaver(None), - m_oneShotTimer(NULL), m_xtestIsXineramaUnaware(true) { - assert(s_screen == NULL); - assert(m_receiver != NULL); + assert(s_screen == NULL); s_screen = this; - // no clipboards to start with - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - m_clipboard[id] = NULL; + // set the X I/O error handler so we catch the display disconnecting + XSetIOErrorHandler(&CXWindowsScreen::ioErrorHandler); + + try { + m_display = openDisplay(); + m_root = DefaultRootWindow(m_display); + saveShape(); + m_window = openWindow(); + m_screensaver = new CXWindowsScreenSaver(m_display, + m_window, getEventTarget()); + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_xinerama ? "(xinerama)" : "")); + LOG((CLOG_DEBUG "window is 0x%08x", m_window)); } + catch (...) { + if (m_display != NULL) { + XCloseDisplay(m_display); + } + throw; + } + + // primary/secondary screen only initialization + if (m_isPrimary) { + // start watching for events on other windows + selectEvents(m_root); + + // prepare to use input methods + openIM(); + } + else { + // become impervious to server grabs + XTestGrabControl(m_display, True); + } + + // initialize the clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_clipboard[id] = new CXWindowsClipboard(m_display, m_window, id); + } + + // install event handlers + EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(), + new TMethodEventJob(this, + &CXWindowsScreen::handleSystemEvent)); + + // install the platform event queue + EVENTQUEUE->adoptBuffer(new CXWindowsEventQueueBuffer(m_display, m_window)); } CXWindowsScreen::~CXWindowsScreen() { assert(s_screen != NULL); - assert(m_display == NULL); + assert(m_display != NULL); + + EVENTQUEUE->adoptBuffer(NULL); + EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget()); + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + delete m_clipboard[id]; + } + delete m_screensaver; + m_screensaver = NULL; + if (m_display != NULL) { + // FIXME -- is it safe to clean up the IC and IM without a display? + if (m_ic != NULL) { + XDestroyIC(m_ic); + } + if (m_im != NULL) { + XCloseIM(m_im); + } + XDestroyWindow(m_display, m_window); + XCloseDisplay(m_display); + } + XSetIOErrorHandler(NULL); - delete m_oneShotTimer; s_screen = NULL; } void -CXWindowsScreen::open(IKeyState* keyState) +CXWindowsScreen::setKeyState(IKeyState* keyState) { - assert(m_display == NULL); - - try { - // set the X I/O error handler so we catch the display disconnecting - XSetIOErrorHandler(&CXWindowsScreen::ioErrorHandler); - - // get the DISPLAY - const char* display = getenv("DISPLAY"); - if (display == NULL) { - display = ":0.0"; - } - - // open the display - LOG((CLOG_DEBUG "XOpenDisplay(\"%s\")", display)); - m_display = XOpenDisplay(display); - if (m_display == NULL) { - throw XScreenUnavailable(60.0); - } - - // verify the availability of the XTest extension - if (!m_isPrimary) { - int majorOpcode, firstEvent, firstError; - if (!XQueryExtension(m_display, XTestExtensionName, - &majorOpcode, &firstEvent, &firstError)) { - LOG((CLOG_ERR "XTEST extension not available")); - throw XScreenOpenFailure(); - } - } - - // get root window - m_root = DefaultRootWindow(m_display); - - // get shape of default screen - m_x = 0; - m_y = 0; - m_w = WidthOfScreen(DefaultScreenOfDisplay(m_display)); - m_h = HeightOfScreen(DefaultScreenOfDisplay(m_display)); - LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d", m_x, m_y, m_w, m_h)); - - // get center of default screen - m_xCenter = m_x + (m_w >> 1); - m_yCenter = m_y + (m_h >> 1); - - // check if xinerama is enabled and there is more than one screen. - // get center of first Xinerama screen. Xinerama appears to have - // a bug when XWarpPointer() is used in combination with - // XGrabPointer(). in that case, the warp is successful but the - // next pointer motion warps the pointer again, apparently to - // constrain it to some unknown region, possibly the region from - // 0,0 to Wm,Hm where Wm (Hm) is the minimum width (height) over - // all physical screens. this warp only seems to happen if the - // pointer wasn't in that region before the XWarpPointer(). the - // second (unexpected) warp causes synergy to think the pointer - // has been moved when it hasn't. to work around the problem, - // we warp the pointer to the center of the first physical - // screen instead of the logical screen. - m_xinerama = false; -#if HAVE_X11_EXTENSIONS_XINERAMA_H - int eventBase, errorBase; - if (XineramaQueryExtension(m_display, &eventBase, &errorBase) && - XineramaIsActive(m_display)) { - int numScreens; - XineramaScreenInfo* screens; - screens = XineramaQueryScreens(m_display, &numScreens); - if (screens != NULL) { - if (numScreens > 1) { - m_xinerama = true; - m_xCenter = screens[0].x_org + (screens[0].width >> 1); - m_yCenter = screens[0].y_org + (screens[0].height >> 1); - } - XFree(screens); - } - } -#endif - - // create the window - m_window = createWindow(); - if (m_window == None) { - throw XScreenOpenFailure(); - } - LOG((CLOG_DEBUG "window is 0x%08x", m_window)); - - if (m_isPrimary) { - // start watching for events on other windows - selectEvents(m_root); - - // prepare to use input methods - openIM(); - } - - // initialize the clipboards - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - m_clipboard[id] = new CXWindowsClipboard(m_display, m_window, id); - } - - // initialize the screen saver - m_atomScreensaver = XInternAtom(m_display, - "SYNERGY_SCREENSAVER", False); - m_screensaver = new CXWindowsScreenSaver(this, m_display); - } - catch (...) { - close(); - throw; - } - - // save the IKeyState m_keyState = keyState; - - // we'll send ourself an event of this type to exit the main loop - m_atomQuit = XInternAtom(m_display, "SYNERGY_QUIT", False); - - if (!m_isPrimary) { - // become impervious to server grabs - XTestGrabControl(m_display, True); - } -} - -void -CXWindowsScreen::close() -{ - // done with m_keyState - m_keyState = NULL; - - // done with screen saver - delete m_screensaver; - - // destroy clipboards - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - delete m_clipboard[id]; - m_clipboard[id] = NULL; - } - - // done with input methods - if (m_ic != NULL) { - XDestroyIC(m_ic); - } - if (m_im != NULL) { - XCloseIM(m_im); - } - - // done with window - if (m_window != None) { - XDestroyWindow(m_display, m_window); - } - - // close the display - if (m_display != NULL) { - XCloseDisplay(m_display); - } - - // restore error handler - XSetIOErrorHandler(NULL); - - // reset state - m_atomQuit = None; - m_screensaver = NULL; - m_atomScreensaver = None; - m_ic = NULL; - m_im = NULL; - m_window = None; - m_root = None; - m_display = NULL; } void CXWindowsScreen::enable() { - CLock lock(&m_mutex); - if (!m_isPrimary) { // get the keyboard control state XKeyboardState keyControl; @@ -349,8 +249,6 @@ CXWindowsScreen::enable() void CXWindowsScreen::disable() { - CLock lock(&m_mutex); - // release input context focus if (m_ic != NULL) { XUnsetICFocus(m_ic); @@ -366,145 +264,9 @@ CXWindowsScreen::disable() } } -void -CXWindowsScreen::mainLoop() -{ - // wait for an event in a cancellable way and don't lock the - // display while we're waiting. we use CLock to ensure that - // we unlock on exit but we directly unlock/lock the mutex - // for certain sections when we mustn't hold the lock. it's - // very important that these sections not return (even by - // exception or cancellation) without first reestablishing - // the lock. - XEvent event; - CLock lock(&m_mutex); - - for (;;) { - -#if UNIX_LIKE - - // compute timeout to next timer - double dtimeout; - { - CLock timersLock(&m_timersMutex); - dtimeout = (m_timers.empty() ? -1.0 : m_timers.top()); - if (m_oneShotTimer != NULL && - (dtimeout == -1.0 || *m_oneShotTimer < dtimeout)) { - dtimeout = *m_oneShotTimer; - } - } - - // use poll() to wait for a message from the X server or for timeout. - // this is a good deal more efficient than polling and sleeping. -#if HAVE_POLL - struct pollfd pfds[1]; - pfds[0].fd = ConnectionNumber(m_display); - pfds[0].events = POLLIN; - int timeout = static_cast(1000.0 * dtimeout); -#else - struct timeval timeout; - struct timeval* timeoutPtr; - if (dtimeout < 0.0) { - timeoutPtr = NULL; - } - else { - timeout.tv_sec = static_cast(dtimeout); - timeout.tv_usec = static_cast(1.0e+6 * - (dtimeout - timeout.tv_sec)); - timeoutPtr = &timeout; - } - - // initialize file descriptor sets - fd_set rfds; - FD_ZERO(&rfds); - FD_SET(ConnectionNumber(m_display), &rfds); -#endif - - // wait for message from X server or for timeout. also check - // if the thread has been cancelled. poll() should return -1 - // with EINTR when the thread is cancelled. - CThread::testCancel(); - m_mutex.unlock(); -#if HAVE_POLL - poll(pfds, 1, timeout); -#else - select(ConnectionNumber(m_display) + 1, - SELECT_TYPE_ARG234 &rfds, - SELECT_TYPE_ARG234 NULL, - SELECT_TYPE_ARG234 NULL, - SELECT_TYPE_ARG5 timeoutPtr); -#endif - m_mutex.lock(); - CThread::testCancel(); - - // process timers - processTimers(); - -#else // !UNIX_LIKE - - // poll for pending events and process timers - while (XPending(m_display) == 0) { - // check timers - if (processTimers()) { - continue; - } - - // wait - try { - m_mutex.unlock(); - CThread::sleep(0.01); - m_mutex.lock(); - } - catch (...) { - m_mutex.lock(); - throw; - } - } - -#endif // !UNIX_LIKE - - // process events - while (XPending(m_display) > 0) { - // get the event - XNextEvent(m_display, &event); - if (isQuitEvent(&event)) { - return; - } - - // process the event - onEvent(&event); - } - } -} - -void -CXWindowsScreen::exitMainLoop() -{ - // send ourself a quit event. this will wake up the event loop - // and cause it to exit. - if (m_atomQuit != None) { - XEvent event; - event.xclient.type = ClientMessage; - event.xclient.display = m_display; - event.xclient.window = m_window; - event.xclient.message_type = m_atomQuit; - event.xclient.format = 32; - event.xclient.data.l[0] = 0; - event.xclient.data.l[1] = 0; - event.xclient.data.l[2] = 0; - event.xclient.data.l[3] = 0; - event.xclient.data.l[4] = 0; - CLock lock(&m_mutex); - CXWindowsUtil::CErrorLock errorLock(m_display); - XSendEvent(m_display, m_window, False, 0, &event); - } -} - void CXWindowsScreen::enter() { - CLock lock(&m_mutex); - // release input context focus if (m_ic != NULL) { XUnsetICFocus(m_ic); @@ -539,8 +301,6 @@ CXWindowsScreen::enter() bool CXWindowsScreen::leave() { - CLock lock(&m_mutex); - if (!m_isPrimary) { // restore the previous keyboard auto-repeat state. if the user // changed the auto-repeat configuration while on the client then @@ -590,8 +350,6 @@ CXWindowsScreen::leave() bool CXWindowsScreen::setClipboard(ClipboardID id, const IClipboard* clipboard) { - CLock lock(&m_mutex); - // fail if we don't have the requested clipboard if (m_clipboard[id] == NULL) { return false; @@ -625,14 +383,8 @@ CXWindowsScreen::checkClipboards() void CXWindowsScreen::openScreensaver(bool notify) { - CLock lock(&m_mutex); - assert(m_screensaver != NULL); - m_screensaverNotify = notify; - if (m_screensaverNotify) { - m_screensaver->setNotify(m_window); - } - else { + if (!m_screensaverNotify) { m_screensaver->disable(); } } @@ -640,23 +392,14 @@ CXWindowsScreen::openScreensaver(bool notify) void CXWindowsScreen::closeScreensaver() { - CLock lock(&m_mutex); - if (m_screensaver != NULL) { - if (m_screensaverNotify) { - m_screensaver->setNotify(None); - } - else { - m_screensaver->enable(); - } + if (!m_screensaverNotify) { + m_screensaver->enable(); } } void CXWindowsScreen::screensaver(bool activate) { - CLock lock(&m_mutex); - assert(m_screensaver != NULL); - if (activate) { m_screensaver->activate(); } @@ -685,27 +428,34 @@ CXWindowsScreen::setOptions(const COptionsList& options) void CXWindowsScreen::updateKeys() { - CLock lock(&m_mutex); - // update keyboard and mouse button mappings m_keyMapper.update(m_display, m_keyState); updateButtons(); } +void +CXWindowsScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + bool CXWindowsScreen::isPrimary() const { return m_isPrimary; } +void* +CXWindowsScreen::getEventTarget() const +{ + return const_cast(this); +} + bool CXWindowsScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const { assert(clipboard != NULL); - // block others from using the display while we get the clipboard - CLock lock(&m_mutex); - // fail if we don't have the requested clipboard if (m_clipboard[id] == NULL) { return false; @@ -731,9 +481,6 @@ CXWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const void CXWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const { - CLock lock(&m_mutex); - assert(m_display != NULL); - Window root, window; int mx, my, xWindow, yWindow; unsigned int mask; @@ -757,8 +504,6 @@ CXWindowsScreen::reconfigure(UInt32) void CXWindowsScreen::warpCursor(SInt32 x, SInt32 y) { - CLock lock(&m_mutex); - // warp mouse warpCursorNoFlush(x, y); @@ -777,15 +522,6 @@ CXWindowsScreen::warpCursor(SInt32 x, SInt32 y) m_yCursor = y; } -UInt32 -CXWindowsScreen::addOneShotTimer(double timeout) -{ - CLock lock(&m_timersMutex); - // FIXME -- support multiple one-shot timers - m_oneShotTimer = new CTimer(NULL, m_time.getTime(), timeout); - return 0; -} - SInt32 CXWindowsScreen::getJumpZoneSize() const { @@ -795,8 +531,6 @@ CXWindowsScreen::getJumpZoneSize() const bool CXWindowsScreen::isAnyMouseButtonDown() const { - CLock lock(&m_mutex); - // query the pointer to get the button state Window root, window; int xRoot, yRoot, xWindow, yWindow; @@ -810,11 +544,16 @@ CXWindowsScreen::isAnyMouseButtonDown() const return false; } +void +CXWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + const char* CXWindowsScreen::getKeyName(KeyButton keycode) const { - CLock lock(&m_mutex); - KeySym keysym = XKeycodeToKeysym(m_display, keycode, 0); char* name = XKeysymToString(keysym); if (name != NULL) { @@ -830,7 +569,6 @@ CXWindowsScreen::getKeyName(KeyButton keycode) const void CXWindowsScreen::fakeKeyEvent(KeyButton keycode, bool press) const { - CLock lock(&m_mutex); XTestFakeKeyEvent(m_display, keycode, press ? True : False, CurrentTime); XFlush(m_display); } @@ -847,7 +585,6 @@ CXWindowsScreen::fakeMouseButton(ButtonID button, bool press) const { const unsigned int xButton = mapButtonToX(button); if (xButton != 0) { - CLock lock(&m_mutex); XTestFakeButtonEvent(m_display, xButton, press ? True : False, CurrentTime); XFlush(m_display); @@ -857,7 +594,6 @@ CXWindowsScreen::fakeMouseButton(ButtonID button, bool press) const void CXWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const { - CLock lock(&m_mutex); if (m_xinerama && m_xtestIsXineramaUnaware) { XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); } @@ -884,7 +620,6 @@ CXWindowsScreen::fakeMouseWheel(SInt32 delta) const } // send as many clicks as necessary - CLock lock(&m_mutex); for (; delta >= 120; delta -= 120) { XTestFakeButtonEvent(m_display, xButton, True, CurrentTime); XTestFakeButtonEvent(m_display, xButton, False, CurrentTime); @@ -901,17 +636,84 @@ CXWindowsScreen::mapKey(IKeyState::Keystrokes& keys, return m_keyMapper.mapKey(keys, keyState, id, desiredMask, isAutoRepeat); } -bool -CXWindowsScreen::isQuitEvent(XEvent* event) const +Display* +CXWindowsScreen::openDisplay() const { - return (m_atomQuit != None && - event->type == ClientMessage && - event->xclient.window == m_window && - event->xclient.message_type == m_atomQuit); + // get the DISPLAY + const char* displayName = getenv("DISPLAY"); + if (displayName == NULL) { + displayName = ":0.0"; + } + + // open the display + LOG((CLOG_DEBUG "XOpenDisplay(\"%s\")", displayName)); + Display* display = XOpenDisplay(displayName); + if (display == NULL) { + throw XScreenUnavailable(60.0); + } + + // verify the availability of the XTest extension + if (!m_isPrimary) { + int majorOpcode, firstEvent, firstError; + if (!XQueryExtension(display, XTestExtensionName, + &majorOpcode, &firstEvent, &firstError)) { + LOG((CLOG_ERR "XTEST extension not available")); + XCloseDisplay(display); + throw XScreenOpenFailure(); + } + } + + return display; +} + +void +CXWindowsScreen::saveShape() +{ + // get shape of default screen + m_x = 0; + m_y = 0; + m_w = WidthOfScreen(DefaultScreenOfDisplay(m_display)); + m_h = HeightOfScreen(DefaultScreenOfDisplay(m_display)); + + // get center of default screen + m_xCenter = m_x + (m_w >> 1); + m_yCenter = m_y + (m_h >> 1); + + // check if xinerama is enabled and there is more than one screen. + // get center of first Xinerama screen. Xinerama appears to have + // a bug when XWarpPointer() is used in combination with + // XGrabPointer(). in that case, the warp is successful but the + // next pointer motion warps the pointer again, apparently to + // constrain it to some unknown region, possibly the region from + // 0,0 to Wm,Hm where Wm (Hm) is the minimum width (height) over + // all physical screens. this warp only seems to happen if the + // pointer wasn't in that region before the XWarpPointer(). the + // second (unexpected) warp causes synergy to think the pointer + // has been moved when it hasn't. to work around the problem, + // we warp the pointer to the center of the first physical + // screen instead of the logical screen. + m_xinerama = false; +#if HAVE_X11_EXTENSIONS_XINERAMA_H + int eventBase, errorBase; + if (XineramaQueryExtension(m_display, &eventBase, &errorBase) && + XineramaIsActive(m_display)) { + int numScreens; + XineramaScreenInfo* screens; + screens = XineramaQueryScreens(m_display, &numScreens); + if (screens != NULL) { + if (numScreens > 1) { + m_xinerama = true; + m_xCenter = screens[0].x_org + (screens[0].width >> 1); + m_yCenter = screens[0].y_org + (screens[0].height >> 1); + } + XFree(screens); + } + } +#endif } Window -CXWindowsScreen::createWindow() const +CXWindowsScreen::openWindow() const { // default window attributes. we don't want the window manager // messing with our window and we don't want the cursor to be @@ -951,11 +753,15 @@ CXWindowsScreen::createWindow() const } // create and return the window - return XCreateWindow(m_display, m_root, x, y, w, h, 0, 0, + Window window = XCreateWindow(m_display, m_root, x, y, w, h, 0, 0, InputOnly, CopyFromParent, CWDontPropagate | CWEventMask | CWOverrideRedirect | CWCursor, &attr); + if (window == None) { + throw XScreenOpenFailure(); + } + return window; } void @@ -1021,40 +827,24 @@ CXWindowsScreen::openIM() } void -CXWindowsScreen::addTimer(IJob* job, double timeout) +CXWindowsScreen::sendEvent(CEvent::Type type, void* data) { - CLock lock(&m_timersMutex); - removeTimerNoLock(job); - m_timers.push(CTimer(job, m_time.getTime(), timeout)); + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data)); } void -CXWindowsScreen::removeTimer(IJob* job) +CXWindowsScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id) { - CLock lock(&m_timersMutex); - removeTimerNoLock(job); + CClipboardInfo* info = new CClipboardInfo; + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); } void -CXWindowsScreen::removeTimerNoLock(IJob* job) -{ - // do it the hard way. first collect all jobs that are not - // the removed job. - CTimerPriorityQueue::container_type tmp; - for (CTimerPriorityQueue::iterator index = m_timers.begin(); - index != m_timers.end(); ++index) { - if (index->getJob() != job) { - tmp.push_back(*index); - } - } - - // now swap in the new list - m_timers.swap(tmp); -} - -void -CXWindowsScreen::onEvent(XEvent* xevent) +CXWindowsScreen::handleSystemEvent(const CEvent& event, void*) { + XEvent* xevent = reinterpret_cast(event.getData()); assert(xevent != NULL); // let input methods try to handle event first @@ -1082,13 +872,20 @@ CXWindowsScreen::onEvent(XEvent* xevent) } } + // let screen saver have a go + if (m_screensaver->handleXEvent(xevent)) { + // screen saver handled it + return; + } + + // handle the event ourself switch (xevent->type) { case CreateNotify: if (m_isPrimary) { // select events on new window selectEvents(xevent->xcreatewindow.window); } - return; + break; case MappingNotify: if (XPending(m_display) > 0) { @@ -1122,7 +919,7 @@ CXWindowsScreen::onEvent(XEvent* xevent) if (id != kClipboardEnd) { LOG((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); m_clipboard[id]->lost(xevent->xselectionclear.time); - m_receiver->onGrabClipboard(id); + sendClipboardEvent(getClipboardGrabbedEvent(), id); return; } } @@ -1138,7 +935,7 @@ CXWindowsScreen::onEvent(XEvent* xevent) xevent->xselection.requestor, xevent->xselection.property); } - return; + break; case SelectionRequest: { @@ -1163,17 +960,6 @@ CXWindowsScreen::onEvent(XEvent* xevent) processClipboardRequest(xevent->xproperty.window, xevent->xproperty.time, xevent->xproperty.atom); - return; - } - break; - - case ClientMessage: - if (m_isPrimary && - xevent->xclient.message_type == m_atomScreensaver && - xevent->xclient.format == 32) { - // screen saver activation/deactivation event - m_primaryReceiver->onScreensaver(xevent->xclient.data.l[0] != 0); - return; } break; @@ -1216,9 +1002,6 @@ CXWindowsScreen::onEvent(XEvent* xevent) default: break; } - - // let screen saver have a go - m_screensaver->onPreDispatch(xevent); } void @@ -1245,10 +1028,11 @@ CXWindowsScreen::onKeyPress(XKeyEvent& xkey) } // handle key - m_primaryReceiver->onKeyDown(key, mask, keycode); + sendEvent(getKeyDownEvent(), new CKeyInfo(key, mask, keycode, 1)); KeyModifierMask keyMask = m_keyState->getMaskForKey(keycode); if (m_keyState->isHalfDuplex(keyMask)) { - m_primaryReceiver->onKeyUp(key, mask | keyMask, keycode); + sendEvent(getKeyUpEvent(), + new CKeyInfo(key, mask | keyMask, keycode, 1)); } } } @@ -1304,9 +1088,10 @@ CXWindowsScreen::onKeyRelease(XKeyEvent& xkey) LOG((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", keycode, xkey.state)); KeyModifierMask keyMask = m_keyState->getMaskForKey(keycode); if (m_keyState->isHalfDuplex(keyMask)) { - m_primaryReceiver->onKeyDown(key, mask, keycode); + sendEvent(getKeyDownEvent(), + new CKeyInfo(key, mask, keycode, 1)); } - m_primaryReceiver->onKeyUp(key, mask, keycode); + sendEvent(getKeyUpEvent(), new CKeyInfo(key, mask, keycode, 1)); } else { // found a press event following so it's a repeat. @@ -1314,7 +1099,7 @@ CXWindowsScreen::onKeyRelease(XKeyEvent& xkey) // repeats but we'll just send a repeat of 1. // note that we discard the press event. LOG((CLOG_DEBUG1 "event: repeat code=%d, state=0x%04x", keycode, xkey.state)); - m_primaryReceiver->onKeyRepeat(key, mask, 1, keycode); + sendEvent(getKeyRepeatEvent(), new CKeyInfo(key, mask, keycode, 1)); } } } @@ -1325,7 +1110,7 @@ CXWindowsScreen::onMousePress(const XButtonEvent& xbutton) LOG((CLOG_DEBUG1 "event: ButtonPress button=%d", xbutton.button)); const ButtonID button = mapButtonFromX(&xbutton); if (button != kButtonNone) { - m_primaryReceiver->onMouseDown(button); + sendEvent(getButtonDownEvent(), new CButtonInfo(button)); } } @@ -1335,15 +1120,15 @@ CXWindowsScreen::onMouseRelease(const XButtonEvent& xbutton) LOG((CLOG_DEBUG1 "event: ButtonRelease button=%d", xbutton.button)); const ButtonID button = mapButtonFromX(&xbutton); if (button != kButtonNone) { - m_primaryReceiver->onMouseUp(button); + sendEvent(getButtonUpEvent(), new CButtonInfo(button)); } else if (xbutton.button == 4) { // wheel forward (away from user) - m_primaryReceiver->onMouseWheel(120); + sendEvent(getWheelEvent(), new CWheelInfo(120)); } else if (xbutton.button == 5) { // wheel backward (toward user) - m_primaryReceiver->onMouseWheel(-120); + sendEvent(getWheelEvent(), new CWheelInfo(-120)); } } @@ -1374,7 +1159,8 @@ CXWindowsScreen::onMouseMove(const XMotionEvent& xmotion) } else if (m_isOnScreen) { // motion on primary screen - m_primaryReceiver->onMouseMovePrimary(m_xCursor, m_yCursor); + sendEvent(getMotionOnPrimaryEvent(), + new CMotionInfo(m_xCursor, m_yCursor)); } else { // motion on secondary screen. warp mouse back to @@ -1404,7 +1190,7 @@ CXWindowsScreen::onMouseMove(const XMotionEvent& xmotion) // warping to the primary screen's enter position, // effectively overriding it. if (x != 0 || y != 0) { - m_primaryReceiver->onMouseMoveSecondary(x, y); + sendEvent(getMotionOnSecondaryEvent(), new CMotionInfo(x, y)); } } } @@ -1445,72 +1231,6 @@ CXWindowsScreen::createBlankCursor() const return cursor; } -bool -CXWindowsScreen::processTimers() -{ - bool oneShot = false; - std::vector jobs; - { - CLock lock(&m_timersMutex); - - // get current time - const double time = m_time.getTime(); - - // done if no timers have expired - if ((m_oneShotTimer == NULL || *m_oneShotTimer > time) && - (m_timers.empty() || m_timers.top() > time)) { - return false; - } - - // handle one shot timers - if (m_oneShotTimer != NULL) { - *m_oneShotTimer -= time; - if (*m_oneShotTimer <= 0.0) { - delete m_oneShotTimer; - m_oneShotTimer = NULL; - oneShot = true; - } - } - - // subtract current time from all timers. note that this won't - // change the order of elements in the priority queue (except - // for floating point round off which we'll ignore). - for (CTimerPriorityQueue::iterator index = m_timers.begin(); - index != m_timers.end(); ++index) { - (*index) -= time; - } - - // process all timers at or below zero, saving the jobs - if (!m_timers.empty()) { - while (m_timers.top() <= 0.0) { - CTimer timer = m_timers.top(); - jobs.push_back(timer.getJob()); - timer.reset(); - m_timers.pop(); - m_timers.push(timer); - } - } - - // reset the clock - m_time.reset(); - } - - // now notify of the one shot timers - if (oneShot) { - m_mutex.unlock(); - m_primaryReceiver->onOneShotTimerExpired(0); - m_mutex.lock(); - } - - // now run the jobs. note that if one of these jobs removes - // a timer later in the jobs list and deletes that job pointer - // then this will crash when it tries to run that job. - for (std::vector::iterator index = jobs.begin(); - index != jobs.end(); ++index) { - (*index)->run(); - } -} - ClipboardID CXWindowsScreen::getClipboardID(Atom selection) const { @@ -1548,20 +1268,38 @@ CXWindowsScreen::destroyClipboardRequest(Window requestor) } } +void +CXWindowsScreen::onError() +{ + // prevent further access to the X display + EVENTQUEUE->adoptBuffer(NULL); + m_screensaver->destroy(); + m_screensaver = NULL; + m_display = NULL; + + // notify of failure + sendEvent(getErrorEvent(), NULL); + + // FIXME -- should ensure that we ignore operations that involve + // m_display from now on. however, Xlib will simply exit the + // application in response to the X I/O error so there's no + // point in trying to really handle the error. if we did want + // to handle the error, it'd probably be easiest to delegate to + // one of two objects. one object would take the implementation + // from this class. the other object would be stub methods that + // don't use X11. on error, we'd switch to the latter. +} + int CXWindowsScreen::ioErrorHandler(Display*) { // the display has disconnected, probably because X is shutting - // down. X forces us to exit at this point. that's arguably - // a flaw in X but, realistically, it's difficult to gracefully - // handle not having a Display* anymore. we'll simply log the - // error, notify the subclass (which must not use the display - // so we set it to NULL), and exit. - LOG((CLOG_WARN "X display has unexpectedly disconnected")); - s_screen->m_display = NULL; - s_screen->m_receiver->onError(); - LOG((CLOG_CRIT "quiting due to X display disconnection")); - exit(17); + // down. X forces us to exit at this point which is annoying. + // we'll pretend as if we won't exit so we try to make sure we + // don't access the display anymore. + LOG((CLOG_CRIT "X display has unexpectedly disconnected")); + s_screen->onError(); + return 0; } void @@ -1854,56 +1592,3 @@ CXWindowsScreen::grabMouseAndKeyboard() LOG((CLOG_DEBUG1 "grabbed pointer and keyboard")); return true; } - - -// -// CXWindowsScreen::CTimer -// - -CXWindowsScreen::CTimer::CTimer(IJob* job, double startTime, double resetTime) : - m_job(job), - m_timeout(resetTime), - m_time(resetTime), - m_startTime(startTime) -{ - assert(m_timeout > 0.0); -} - -CXWindowsScreen::CTimer::~CTimer() -{ - // do nothing -} - -void -CXWindowsScreen::CTimer::run() -{ - if (m_job != NULL) { - m_job->run(); - } -} - -void -CXWindowsScreen::CTimer::reset() -{ - m_time = m_timeout; - m_startTime = 0.0; -} - -CXWindowsScreen::CTimer::CTimer& -CXWindowsScreen::CTimer::operator-=(double dt) -{ - m_time -= dt - m_startTime; - m_startTime = 0.0; - return *this; -} - -CXWindowsScreen::CTimer::operator double() const -{ - return m_time; -} - -bool -CXWindowsScreen::CTimer::operator<(const CTimer& t) const -{ - return m_time < t.m_time; -} diff --git a/lib/platform/CXWindowsScreen.h b/lib/platform/CXWindowsScreen.h index 36d5be4b..65ecfcee 100644 --- a/lib/platform/CXWindowsScreen.h +++ b/lib/platform/CXWindowsScreen.h @@ -17,9 +17,6 @@ #include "IPlatformScreen.h" #include "CXWindowsKeyMapper.h" -#include "CMutex.h" -#include "CStopwatch.h" -#include "CPriorityQueue.h" #include "stdvector.h" #if defined(X_DISPLAY_MISSING) # error X11 is required to build synergy @@ -29,43 +26,22 @@ class CXWindowsClipboard; class CXWindowsScreenSaver; -class IJob; -class IScreenReceiver; -class IPrimaryScreenReceiver; //! Implementation of IPlatformScreen for X11 class CXWindowsScreen : public IPlatformScreen { public: - CXWindowsScreen(IScreenReceiver*, IPrimaryScreenReceiver*); + CXWindowsScreen(bool isPrimary); virtual ~CXWindowsScreen(); //! @name manipulators //@{ - //! Add timer - /*! - Add a job to invoke every timeout seconds. The job is called - with the display locked. If a job timeout expires twice or - more before the job can be called then the job is called just - once. The caller retains ownership of the job. - */ - void addTimer(IJob*, double timeout); - - //! Remove timer - /*! - Remove a job. The caller retains ownership of the job. - */ - void removeTimer(IJob*); - //@} // IPlatformScreen overrides - virtual void open(IKeyState*); - virtual void close(); + virtual void setKeyState(IKeyState*); virtual void enable(); virtual void disable(); - virtual void mainLoop(); - virtual void exitMainLoop(); virtual void enter(); virtual bool leave(); virtual bool setClipboard(ClipboardID, const IClipboard*); @@ -76,17 +52,22 @@ public: virtual void resetOptions(); virtual void setOptions(const COptionsList& options); virtual void updateKeys(); + virtual void setSequenceNumber(UInt32); virtual bool isPrimary() const; - virtual bool getClipboard(ClipboardID, IClipboard*) const; - virtual void getShape(SInt32&, SInt32&, SInt32&, SInt32&) const; - virtual void getCursorPos(SInt32&, SInt32&) const; + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; // IPrimaryScreen overrides virtual void reconfigure(UInt32 activeSides); virtual void warpCursor(SInt32 x, SInt32 y); - virtual UInt32 addOneShotTimer(double timeout); virtual SInt32 getJumpZoneSize() const; virtual bool isAnyMouseButtonDown() const; + virtual void getCursorCenter(SInt32& x, SInt32& y) const; virtual const char* getKeyName(KeyButton) const; // ISecondaryScreen overrides @@ -101,18 +82,16 @@ public: bool isAutoRepeat) const; private: - // process events before dispatching to receiver - void onEvent(XEvent* event); + // event sending + void sendEvent(CEvent::Type, void* = NULL); + void sendClipboardEvent(CEvent::Type, ClipboardID); + + // event handling + void handleSystemEvent(const CEvent&, void*); // create the transparent cursor Cursor createBlankCursor() const; - // remove a timer without locking - void removeTimerNoLock(IJob*); - - // process timers - bool processTimers(); - // determine the clipboard from the X selection. returns // kClipboardEnd if no such clipboard. ClipboardID getClipboardID(Atom selection) const; @@ -125,40 +104,10 @@ private: void destroyClipboardRequest(Window window); // X I/O error handler + void onError(); static int ioErrorHandler(Display*); private: - // a timer priority queue element - class CTimer { - public: - CTimer(IJob* job, double startTime, double resetTime); - ~CTimer(); - - // manipulators - - void run(); - - void reset(); - - CTimer& operator-=(double); - - // accessors - - IJob* getJob() const - { - return m_job; - } - - operator double() const; - - bool operator<(const CTimer&) const; - - private: - IJob* m_job; - double m_timeout; - double m_time; - double m_startTime; - }; class CKeyEventInfo { public: int m_event; @@ -167,9 +116,9 @@ private: KeyCode m_keycode; }; - bool isQuitEvent(XEvent*) const; - - Window createWindow() const; + Display* openDisplay() const; + void saveShape(); + Window openWindow() const; void openIM(); bool grabMouseAndKeyboard(); @@ -193,21 +142,13 @@ private: static Bool findKeyEvent(Display*, XEvent* xevent, XPointer arg); private: - typedef CPriorityQueue CTimerPriorityQueue; - // true if screen is being used as a primary screen, false otherwise bool m_isPrimary; - // X is not thread safe - CMutex m_mutex; - Display* m_display; Window m_root; Window m_window; - IScreenReceiver* m_receiver; - IPrimaryScreenReceiver* m_primaryReceiver; - // true if mouse has entered the screen bool m_isOnScreen; @@ -230,20 +171,11 @@ private: // clipboards CXWindowsClipboard* m_clipboard[kClipboardEnd]; - - // the quit message - Atom m_atomQuit; + UInt32 m_sequenceNumber; // screen saver stuff CXWindowsScreenSaver* m_screensaver; bool m_screensaverNotify; - Atom m_atomScreensaver; - - // timers, the stopwatch used to time, and a mutex for the timers - CTimerPriorityQueue m_timers; - CStopwatch m_time; - CMutex m_timersMutex; - CTimer* m_oneShotTimer; // logical to physical button mapping. m_buttons[i] gives the // physical button for logical button i+1. diff --git a/lib/platform/CXWindowsScreenSaver.cpp b/lib/platform/CXWindowsScreenSaver.cpp index c652c3d9..a40ae7e4 100644 --- a/lib/platform/CXWindowsScreenSaver.cpp +++ b/lib/platform/CXWindowsScreenSaver.cpp @@ -13,10 +13,12 @@ */ #include "CXWindowsScreenSaver.h" -#include "CXWindowsScreen.h" #include "CXWindowsUtil.h" +#include "IPlatformScreen.h" #include "CLog.h" -#include "TMethodJob.h" +#include "CEvent.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" #include #if defined(HAVE_X11_EXTENSIONS_XTEST_H) # include @@ -29,20 +31,16 @@ // CXWindowsScreenSaver::CXWindowsScreenSaver( - CXWindowsScreen* screen, Display* display) : - m_screen(screen), + Display* display, Window window, void* eventTarget) : m_display(display), - m_notify(None), + m_xscreensaverSink(window), + m_eventTarget(eventTarget), m_xscreensaver(None), m_xscreensaverActive(false), m_disabled(false), m_suppressDisable(false), - m_disableJobInstalled(false) + m_disableTimer(NULL) { - // screen saver disable callback - m_disableJob = new TMethodJob(this, - &CXWindowsScreenSaver::disableCallback); - // get atoms m_atomScreenSaver = XInternAtom(m_display, "SCREENSAVER", False); @@ -52,24 +50,6 @@ CXWindowsScreenSaver::CXWindowsScreenSaver( "ACTIVATE", False); m_atomScreenSaverDeactivate = XInternAtom(m_display, "DEACTIVATE", False); - m_atomSynergyScreenSaver = XInternAtom(m_display, - "SYNERGY_SCREENSAVER", False); - - // create dummy window to receive xscreensaver responses. this - // shouldn't be necessary (we should be able to send responses - // to None) but it doesn't hurt. - XSetWindowAttributes attr; - attr.event_mask = 0;//PropertyChangeMask; - attr.do_not_propagate_mask = 0; - attr.override_redirect = True; - m_xscreensaverSink = XCreateWindow(m_display, - DefaultRootWindow(m_display), - 0, 0, 1, 1, 0, 0, - InputOnly, CopyFromParent, - CWDontPropagate | CWEventMask | - CWOverrideRedirect, - &attr); - LOG((CLOG_DEBUG "xscreensaver sink window is 0x%08x", m_xscreensaverSink)); // watch top-level windows for changes { @@ -94,28 +74,39 @@ CXWindowsScreenSaver::CXWindowsScreenSaver( // get the built-in settings XGetScreenSaver(m_display, &m_timeout, &m_interval, &m_preferBlanking, &m_allowExposures); + + // install disable timer event handler + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CXWindowsScreenSaver::handleDisableTimer)); } CXWindowsScreenSaver::~CXWindowsScreenSaver() { - // clear watch list - clearWatchForXScreenSaver(); - - // stop watching root for events - CXWindowsUtil::CErrorLock lock(m_display); - Window root = DefaultRootWindow(m_display); - XSelectInput(m_display, root, m_rootEventMask); - - // destroy dummy sink window - XDestroyWindow(m_display, m_xscreensaverSink); - // done with disable job - m_screen->removeTimer(m_disableJob); - delete m_disableJob; + if (m_disableTimer != NULL) { + EVENTQUEUE->deleteTimer(m_disableTimer); + } + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + + if (m_display != NULL) { + XSetScreenSaver(m_display, m_timeout, m_interval, + m_preferBlanking, m_allowExposures); + clearWatchForXScreenSaver(); + CXWindowsUtil::CErrorLock lock(m_display); + XSelectInput(m_display, DefaultRootWindow(m_display), m_rootEventMask); + } +} + +void +CXWindowsScreenSaver::destroy() +{ + m_display = NULL; + delete this; } bool -CXWindowsScreenSaver::onPreDispatch(const XEvent* xevent) +CXWindowsScreenSaver::handleXEvent(const XEvent* xevent) { switch (xevent->type) { case CreateNotify: @@ -175,18 +166,12 @@ CXWindowsScreenSaver::onPreDispatch(const XEvent* xevent) return false; } -void -CXWindowsScreenSaver::setNotify(Window notify) -{ - m_notify = notify; -} - void CXWindowsScreenSaver::enable() { // for xscreensaver m_disabled = false; - updateDisableJob(); + updateDisableTimer(); // for built-in X screen saver XSetScreenSaver(m_display, m_timeout, m_interval, @@ -198,7 +183,7 @@ CXWindowsScreenSaver::disable() { // for xscreensaver m_disabled = true; - updateDisableJob(); + updateDisableTimer(); // use built-in X screen saver XGetScreenSaver(m_display, &m_timeout, &m_interval, @@ -213,7 +198,7 @@ CXWindowsScreenSaver::activate() { // remove disable job timer m_suppressDisable = true; - updateDisableJob(); + updateDisableTimer(); // try xscreensaver findXScreenSaver(); @@ -231,7 +216,7 @@ CXWindowsScreenSaver::deactivate() { // reinstall disable job timer m_suppressDisable = false; - updateDisableJob(); + updateDisableTimer(); // try xscreensaver findXScreenSaver(); @@ -256,27 +241,6 @@ CXWindowsScreenSaver::isActive() const return false; } -void -CXWindowsScreenSaver::sendNotify(bool activated) -{ - if (m_notify != None) { - XEvent event; - event.xclient.type = ClientMessage; - event.xclient.display = m_display; - event.xclient.window = m_notify; - event.xclient.message_type = m_atomSynergyScreenSaver; - event.xclient.format = 32; - event.xclient.data.l[0] = activated ? 1 : 0; - event.xclient.data.l[1] = 0; - event.xclient.data.l[2] = 0; - event.xclient.data.l[3] = 0; - event.xclient.data.l[4] = 0; - - CXWindowsUtil::CErrorLock lock(m_display); - XSendEvent(m_display, m_notify, False, 0, &event); - } -} - bool CXWindowsScreenSaver::findXScreenSaver() { @@ -351,9 +315,18 @@ CXWindowsScreenSaver::setXScreenSaverActive(bool activated) // from activating since that'll just pop up the password // dialog if locking is enabled. m_suppressDisable = activated; - updateDisableJob(); + updateDisableTimer(); - sendNotify(activated); + if (activated) { + EVENTQUEUE->addEvent(CEvent( + IPlatformScreen::getScreensaverActivatedEvent(), + m_eventTarget)); + } + else { + EVENTQUEUE->addEvent(CEvent( + IPlatformScreen::getScreensaverDeactivatedEvent(), + m_eventTarget)); + } } } @@ -441,23 +414,20 @@ CXWindowsScreenSaver::addWatchXScreenSaver(Window window) } void -CXWindowsScreenSaver::updateDisableJob() +CXWindowsScreenSaver::updateDisableTimer() { - assert(m_disableJob != NULL); - - if (m_disabled && !m_suppressDisable && !m_disableJobInstalled) { + if (m_disabled && !m_suppressDisable && m_disableTimer == NULL) { // 5 seconds should be plenty often to suppress the screen saver - m_disableJobInstalled = true; - m_screen->addTimer(m_disableJob, 5.0); + m_disableTimer = EVENTQUEUE->newTimer(5.0, this); } - else if ((!m_disabled || m_suppressDisable) && m_disableJobInstalled) { - m_disableJobInstalled = false; - m_screen->removeTimer(m_disableJob); + else if ((!m_disabled || m_suppressDisable) && m_disableTimer != NULL) { + EVENTQUEUE->deleteTimer(m_disableTimer); + m_disableTimer = NULL; } } void -CXWindowsScreenSaver::disableCallback(void*) +CXWindowsScreenSaver::handleDisableTimer(const CEvent&, void*) { // send fake mouse motion directly to xscreensaver if (m_xscreensaver != None) { diff --git a/lib/platform/CXWindowsScreenSaver.h b/lib/platform/CXWindowsScreenSaver.h index 3285a1b7..b956a84b 100644 --- a/lib/platform/CXWindowsScreenSaver.h +++ b/lib/platform/CXWindowsScreenSaver.h @@ -23,16 +23,13 @@ # include #endif -class IJob; -class CXWindowsScreen; +class CEvent; +class CEventQueueTimer; //! X11 screen saver implementation class CXWindowsScreenSaver : public IScreenSaver { public: - // note -- the caller must ensure that Display* passed to c'tor isn't - // being used in another call to Xlib when calling any method on this - // object (including during the c'tor and d'tor). - CXWindowsScreenSaver(CXWindowsScreen*, Display*); + CXWindowsScreenSaver(Display*, Window, void* eventTarget); virtual ~CXWindowsScreenSaver(); //! @name manipulators @@ -40,22 +37,17 @@ public: //! Event filtering /*! - Called for each event before event translation and dispatch. Return - true to skip translation and dispatch. Subclasses should call the - superclass's version first and return true if it returns true. + Should be called for each system event before event translation and + dispatch. Returns true to skip translation and dispatch. */ - bool onPreDispatch(const XEvent*); + bool handleXEvent(const XEvent*); - //! Set notify target + //! Destroy without the display /*! - Tells this object to send a ClientMessage to the given window - when the screen saver activates or deactivates. Only one window - can be notified at a time. The message type is the "SCREENSAVER" - atom, the format is 32, and the data.l[0] member is non-zero - if activated, zero if deactivated. Pass None to disable - notification. + Tells this object to delete itself without using the X11 display. + It may leak some resources as a result. */ - void setNotify(Window); + void destroy(); //@} @@ -67,9 +59,6 @@ public: virtual bool isActive() const; private: - // send a notification - void sendNotify(bool activated); - // find and set the running xscreensaver's window. returns true iff // found. bool findXScreenSaver(); @@ -98,25 +87,22 @@ private: void addWatchXScreenSaver(Window window); // install/uninstall the job used to suppress the screensaver - void updateDisableJob(); + void updateDisableTimer(); // called periodically to prevent the screen saver from starting - void disableCallback(void*); + void handleDisableTimer(const CEvent&, void*); private: typedef std::map CWatchList; - // the event loop object - CXWindowsScreen* m_screen; - // the X display Display* m_display; - // old event mask on root window - long m_rootEventMask; + // window to receive xscreensaver repsonses + Window m_xscreensaverSink; - // window to notify on screen saver activation/deactivation - Window m_notify; + // the target for the events we generate + void* m_eventTarget; // xscreensaver's window Window m_xscreensaver; @@ -124,8 +110,8 @@ private: // xscreensaver activation state bool m_xscreensaverActive; - // dummy window to receive xscreensaver repsonses - Window m_xscreensaverSink; + // old event mask on root window + long m_rootEventMask; // potential xscreensaver windows being watched CWatchList m_watchWindows; @@ -135,7 +121,6 @@ private: Atom m_atomScreenSaverVersion; Atom m_atomScreenSaverActivate; Atom m_atomScreenSaverDeactivate; - Atom m_atomSynergyScreenSaver; // built-in screen saver settings int m_timeout; @@ -151,11 +136,8 @@ private: // to activate the screen saver even if disabled. bool m_suppressDisable; - // true iff the disabled job timer is installed - bool m_disableJobInstalled; - - // the job used to invoke disableCallback - IJob* m_disableJob; + // the disable timer (NULL if not installed) + CEventQueueTimer* m_disableTimer; }; #endif diff --git a/lib/platform/Makefile.am b/lib/platform/Makefile.am index 25629cc4..ec33a35a 100644 --- a/lib/platform/Makefile.am +++ b/lib/platform/Makefile.am @@ -49,7 +49,7 @@ libplatform_a_SOURCES = \ CXWindowsClipboardTextConverter.cpp \ CXWindowsClipboardUCS2Converter.cpp \ CXWindowsClipboardUTF8Converter.cpp \ - CXWindowsEventQueue.cpp \ + CXWindowsEventQueueBuffer.cpp \ CXWindowsKeyMapper.cpp \ CXWindowsScreen.cpp \ CXWindowsScreenSaver.cpp \ @@ -58,7 +58,7 @@ libplatform_a_SOURCES = \ CXWindowsClipboardTextConverter.h \ CXWindowsClipboardUCS2Converter.h \ CXWindowsClipboardUTF8Converter.h \ - CXWindowsEventQueue.h \ + CXWindowsEventQueueBuffer.h \ CXWindowsKeyMapper.h \ CXWindowsScreen.h \ CXWindowsScreenSaver.h \ diff --git a/lib/server/CClientListener.cpp b/lib/server/CClientListener.cpp new file mode 100644 index 00000000..9a811a36 --- /dev/null +++ b/lib/server/CClientListener.cpp @@ -0,0 +1,189 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CClientListener.h" +#include "CClientProxy.h" +#include "CClientProxyUnknown.h" +#include "CPacketStreamFilter.h" +#include "IStreamFilterFactory.h" +#include "IDataSocket.h" +#include "IListenSocket.h" +#include "ISocketFactory.h" +#include "XSocket.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CClientListener +// + +CEvent::Type CClientListener::s_connectedEvent = CEvent::kUnknown; + +CClientListener::CClientListener(const CNetworkAddress& address, + ISocketFactory* socketFactory, + IStreamFilterFactory* streamFilterFactory) : + m_socketFactory(socketFactory), + m_streamFilterFactory(streamFilterFactory) +{ + assert(m_socketFactory != NULL); + + try { + // create listen socket + m_listen = m_socketFactory->createListen(); + + // bind listen address + LOG((CLOG_DEBUG1 "binding listen socket")); + m_listen->bind(address); + } + catch (XSocketAddressInUse& e) { + delete m_listen; + delete m_socketFactory; + delete m_streamFilterFactory; + throw; + } + catch (XBase& e) { + delete m_listen; + delete m_socketFactory; + delete m_streamFilterFactory; + throw; + } + LOG((CLOG_DEBUG1 "listening for clients")); + + // setup event handler + EVENTQUEUE->adoptHandler(IListenSocket::getConnectingEvent(), m_listen, + new TMethodEventJob(this, + &CClientListener::handleClientConnecting)); +} + +CClientListener::~CClientListener() +{ + LOG((CLOG_DEBUG1 "stop listening for clients")); + + // discard already connected clients + for (CNewClients::iterator index = m_newClients.begin(); + index != m_newClients.end(); ++index) { + CClientProxyUnknown* client = *index; + EVENTQUEUE->removeHandler(client); + delete client; + } + + // discard waiting clients + CClientProxy* client = getNextClient(); + while (client != NULL) { + delete client; + client = getNextClient(); + } + + EVENTQUEUE->removeHandler(IListenSocket::getConnectingEvent(), m_listen); + delete m_listen; + delete m_socketFactory; + delete m_streamFilterFactory; +} + +CClientProxy* +CClientListener::getNextClient() +{ + CClientProxy* client = NULL; + if (!m_waitingClients.empty()) { + client = m_waitingClients.front(); + m_waitingClients.pop_front(); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + } + return client; +} + +CEvent::Type +CClientListener::getConnectedEvent() +{ + return CEvent::registerTypeOnce(s_connectedEvent, + "CClientListener::connected"); +} + +void +CClientListener::handleClientConnecting(const CEvent&, void*) +{ + // accept client connection + IStream* stream = m_listen->accept(); + if (stream == NULL) { + return; + } + LOG((CLOG_NOTE "accepted client connection")); + + // filter socket messages, including a packetizing filter + if (m_streamFilterFactory != NULL) { + stream = m_streamFilterFactory->create(stream, true); + } + stream = new CPacketStreamFilter(stream, true); + + // create proxy for unknown client + CClientProxyUnknown* client = new CClientProxyUnknown(stream, 30.0); + m_newClients.insert(client); + + // watch for events from unknown client + EVENTQUEUE->adoptHandler(CClientProxyUnknown::getSuccessEvent(), client, + new TMethodEventJob(this, + &CClientListener::handleUnknownClient, client)); + EVENTQUEUE->adoptHandler(CClientProxyUnknown::getFailureEvent(), client, + new TMethodEventJob(this, + &CClientListener::handleUnknownClient, client)); +} + +void +CClientListener::handleUnknownClient(const CEvent&, void* vclient) +{ + CClientProxyUnknown* unknownClient = + reinterpret_cast(vclient); + + // we should have the client in our new client list + assert(m_newClients.count(unknownClient) == 1); + + // get the real client proxy and install it + CClientProxy* client = unknownClient->orphanClientProxy(); + if (client != NULL) { + // handshake was successful + m_waitingClients.push_back(client); + EVENTQUEUE->addEvent(CEvent(getConnectedEvent(), this)); + + // watch for client to disconnect while it's in our queue + EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), client, + new TMethodEventJob(this, + &CClientListener::handleClientDisconnected, + client)); + } + + // now finished with unknown client + EVENTQUEUE->removeHandler(CClientProxyUnknown::getSuccessEvent(), client); + EVENTQUEUE->removeHandler(CClientProxyUnknown::getFailureEvent(), client); + m_newClients.erase(unknownClient); + delete unknownClient; +} + +void +CClientListener::handleClientDisconnected(const CEvent&, void* vclient) +{ + CClientProxy* client = reinterpret_cast(vclient); + + // find client in waiting clients queue + for (CWaitingClients::iterator i = m_waitingClients.begin(), + n = m_waitingClients.end(); i != n; ++i) { + if (*i == client) { + m_waitingClients.erase(i); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), + client); + delete client; + break; + } + } +} diff --git a/lib/server/CClientListener.h b/lib/server/CClientListener.h new file mode 100644 index 00000000..e5302e69 --- /dev/null +++ b/lib/server/CClientListener.h @@ -0,0 +1,76 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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. + */ + +#ifndef CCLIENTLISTENER_H +#define CCLIENTLISTENER_H + +#include "CConfig.h" +#include "CEvent.h" +#include "stddeque.h" +#include "stdset.h" + +class CClientProxy; +class CClientProxyUnknown; +class CNetworkAddress; +class IListenSocket; +class ISocketFactory; +class IStreamFilterFactory; + +class CClientListener { +public: + // The factories are adopted. + CClientListener(const CNetworkAddress&, + ISocketFactory*, IStreamFilterFactory*); + ~CClientListener(); + + //! @name accessors + //@{ + + //! Get next connected client + /*! + Returns the next connected client and removes it from the internal + list. The client is responsible for deleting the returned client. + Returns NULL if no clients are available. + */ + CClientProxy* getNextClient(); + + //! Get connected event type + /*! + Returns the connected event type. This is sent whenever a + a client connects. + */ + static CEvent::Type getConnectedEvent(); + + //@} + +private: + // client connection event handlers + void handleClientConnecting(const CEvent&, void*); + void handleUnknownClient(const CEvent&, void*); + void handleClientDisconnected(const CEvent&, void*); + +private: + typedef std::set CNewClients; + typedef std::deque CWaitingClients; + + IListenSocket* m_listen; + ISocketFactory* m_socketFactory; + IStreamFilterFactory* m_streamFilterFactory; + CNewClients m_newClients; + CWaitingClients m_waitingClients; + + static CEvent::Type s_connectedEvent; +}; + +#endif diff --git a/lib/server/CClientProxy.cpp b/lib/server/CClientProxy.cpp index 0df174c9..e5dc02c0 100644 --- a/lib/server/CClientProxy.cpp +++ b/lib/server/CClientProxy.cpp @@ -13,15 +13,18 @@ */ #include "CClientProxy.h" +#include "CProtocolUtil.h" #include "IStream.h" +#include "CLog.h" // // CClientProxy // -CClientProxy::CClientProxy(IServer* server, - const CString& name, IStream* stream) : - m_server(server), +CEvent::Type CClientProxy::s_readyEvent = CEvent::kUnknown; +CEvent::Type CClientProxy::s_disconnectedEvent = CEvent::kUnknown; + +CClientProxy::CClientProxy(const CString& name, IStream* stream) : m_name(name), m_stream(stream) { @@ -33,10 +36,14 @@ CClientProxy::~CClientProxy() delete m_stream; } -IServer* -CClientProxy::getServer() const +void +CClientProxy::close(const char* msg) { - return m_server; + LOG((CLOG_DEBUG1 "send close \"%s\" to \"%s\"", msg, getName().c_str())); + CProtocolUtil::writef(getStream(), msg); + + // force the close to be sent before we return + getStream()->flush(); } IStream* @@ -51,8 +58,22 @@ CClientProxy::getName() const return m_name; } -const CMutex* -CClientProxy::getMutex() const +CEvent::Type +CClientProxy::getReadyEvent() { - return &m_mutex; + return CEvent::registerTypeOnce(s_readyEvent, + "CClientProxy::ready"); +} + +CEvent::Type +CClientProxy::getDisconnectedEvent() +{ + return CEvent::registerTypeOnce(s_disconnectedEvent, + "CClientProxy::disconnected"); +} + +void* +CClientProxy::getEventTarget() const +{ + return static_cast(const_cast(this)); } diff --git a/lib/server/CClientProxy.h b/lib/server/CClientProxy.h index 751442ae..aad57f6a 100644 --- a/lib/server/CClientProxy.h +++ b/lib/server/CClientProxy.h @@ -16,11 +16,10 @@ #define CCLIENTPROXY_H #include "IClient.h" -#include "CMutex.h" +#include "CEvent.h" #include "CString.h" class IStream; -class IServer; //! Generic proxy for client class CClientProxy : public IClient { @@ -28,17 +27,21 @@ public: /*! \c name is the name of the client. */ - CClientProxy(IServer* server, const CString& name, IStream* adoptedStream); + CClientProxy(const CString& name, IStream* adoptedStream); ~CClientProxy(); - //! @name accessors + //! @name manipulators //@{ - //! Get server + //! Disconnect /*! - Returns the server passed to the c'tor. + Ask the client to disconnect, using \p msg as the reason. */ - IServer* getServer() const; + void close(const char* msg); + + //@} + //! @name accessors + //@{ //! Get stream /*! @@ -46,12 +49,31 @@ public: */ IStream* getStream() const; + //! Get ready event type + /*! + Returns the ready event type. This is sent when the client has + completed the initial handshake. Until it is sent, the client is + not fully connected. + */ + static CEvent::Type getReadyEvent(); + + //! Get disconnect event type + /*! + Returns the disconnect event type. This is sent when the client + disconnects or is disconnected. The target is getEventTarget(). + */ + static CEvent::Type getDisconnectedEvent(); + //@} + // IScreen + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + // IClient overrides - virtual void open() = 0; - virtual void mainLoop() = 0; - virtual void close() = 0; virtual void enter(SInt32 xAbs, SInt32 yAbs, UInt32 seqNum, KeyModifierMask mask, bool forScreensaver) = 0; @@ -71,25 +93,13 @@ public: virtual void resetOptions() = 0; virtual void setOptions(const COptionsList& options) = 0; virtual CString getName() const; - virtual SInt32 getJumpZoneSize() const = 0; - virtual void getShape(SInt32& x, SInt32& y, - SInt32& width, SInt32& height) const = 0; - virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; - virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; - -protected: - //! Get mutex - /*! - Returns the mutex for this object. Subclasses should use this - mutex to protect their data. - */ - const CMutex* getMutex() const; private: - CMutex m_mutex; - IServer* m_server; CString m_name; IStream* m_stream; + + static CEvent::Type s_readyEvent; + static CEvent::Type s_disconnectedEvent; }; #endif diff --git a/lib/server/CClientProxy1_0.cpp b/lib/server/CClientProxy1_0.cpp index ccf5b357..505eacfa 100644 --- a/lib/server/CClientProxy1_0.cpp +++ b/lib/server/CClientProxy1_0.cpp @@ -13,12 +13,9 @@ */ #include "CClientProxy1_0.h" -#include "CServer.h" -#include "CClipboard.h" #include "CProtocolUtil.h" #include "XSynergy.h" #include "IStream.h" -#include "CLock.h" #include "CLog.h" #include "IEventQueue.h" #include "TMethodEventJob.h" @@ -28,16 +25,12 @@ // CClientProxy1_0 // -CClientProxy1_0::CClientProxy1_0(IServer* server, - const CString& name, IStream* stream) : - CClientProxy(server, name, stream), +CClientProxy1_0::CClientProxy1_0(const CString& name, IStream* stream) : + CClientProxy(name, stream), m_heartbeatAlarm(kHeartRate * kHeartBeatsUntilDeath), - m_heartbeatTimer(NULL) + m_heartbeatTimer(NULL), + m_parser(&CClientProxy1_0::parseHandshakeMessage) { - for (UInt32 i = 0; i < kClipboardEnd; ++i) { - m_clipboardDirty[i] = true; - } - // install event handlers EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), stream->getEventTarget(), @@ -59,7 +52,8 @@ CClientProxy1_0::CClientProxy1_0(IServer* server, new TMethodEventJob(this, &CClientProxy1_0::handleFlatline, NULL)); - // FIXME -- open() replacement must install initial heartbeat timer + LOG((CLOG_DEBUG1 "querying client \"%s\" info", getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgQInfo); } CClientProxy1_0::~CClientProxy1_0() @@ -70,11 +64,9 @@ CClientProxy1_0::~CClientProxy1_0() void CClientProxy1_0::disconnect() { - CLock lock(getMutex()); removeHandlers(); - // FIXME -- send disconnect event (server should be listening for this) - getStream()->flush(); getStream()->close(); + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), getEventTarget())); } void @@ -98,7 +90,6 @@ CClientProxy1_0::removeHandlers() void CClientProxy1_0::addHeartbeatTimer() { - CLock lock(getMutex()); if (m_heartbeatAlarm > 0.0) { m_heartbeatTimer = EVENTQUEUE->newOneShotTimer(m_heartbeatAlarm, this); } @@ -107,7 +98,6 @@ CClientProxy1_0::addHeartbeatTimer() void CClientProxy1_0::removeHeartbeatTimer() { - CLock lock(getMutex()); if (m_heartbeatTimer != NULL) { EVENTQUEUE->deleteTimer(m_heartbeatTimer); m_heartbeatTimer = NULL; @@ -130,7 +120,7 @@ CClientProxy1_0::handleData(const CEvent&, void*) // parse message LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); - if (!parseMessage(code)) { + if (!(this->*m_parser)(code)) { LOG((CLOG_ERR "invalid message from client \"%s\"", getName().c_str())); disconnect(); return; @@ -145,11 +135,35 @@ CClientProxy1_0::handleData(const CEvent&, void*) 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) { - return recvInfo(true); + if (recvInfo()) { + EVENTQUEUE->addEvent( + CEvent(getShapeChangedEvent(), getEventTarget())); + return true; + } + return false; } else if (memcmp(code, kMsgCNoop, 4) == 0) { // discard no-ops @@ -168,14 +182,14 @@ CClientProxy1_0::parseMessage(const UInt8* code) void CClientProxy1_0::handleDisconnect(const CEvent&, void*) { - LOG((CLOG_NOTE "client \"%s\" disconnected", getName().c_str())); + LOG((CLOG_NOTE "client \"%s\" has disconnected", getName().c_str())); disconnect(); } void CClientProxy1_0::handleWriteError(const CEvent&, void*) { - LOG((CLOG_ERR "error writing to client \"%s\"", getName().c_str())); + LOG((CLOG_WARN "error writing to client \"%s\"", getName().c_str())); disconnect(); } @@ -187,44 +201,28 @@ CClientProxy1_0::handleFlatline(const CEvent&, void*) disconnect(); } -// FIXME -- replace this -void -CClientProxy1_0::open() +bool +CClientProxy1_0::getClipboard(ClipboardID id, IClipboard* clipboard) const { - // send request - LOG((CLOG_DEBUG1 "querying client \"%s\" info", getName().c_str())); - CProtocolUtil::writef(getStream(), kMsgQInfo); - getStream()->flush(); - - // wait for and verify reply - UInt8 code[4]; - for (;;) { - UInt32 n = getStream()->read(code, 4); - if (n == 4) { - if (memcmp(code, kMsgCNoop, 4) == 0) { - // discard heartbeats - continue; - } - if (memcmp(code, kMsgDInfo, 4) == 0) { - break; - } - } - throw XBadClient(); - } - - // handle reply - recvInfo(false); + CClipboard::copy(clipboard, &m_clipboard[id].m_clipboard); + return true; } -// FIXME -- replace this void -CClientProxy1_0::close() +CClientProxy1_0::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const { - LOG((CLOG_DEBUG1 "send close to \"%s\"", getName().c_str())); - CProtocolUtil::writef(getStream(), kMsgCClose); + x = m_info.m_x; + y = m_info.m_y; + w = m_info.m_w; + h = m_info.m_h; +} - // force the close to be sent before we return - getStream()->flush(); +void +CClientProxy1_0::getCursorPos(SInt32& x, SInt32& y) const +{ + assert(0 && "shouldn't be called"); + x = m_info.m_mx; + y = m_info.m_my; } void @@ -250,10 +248,9 @@ void CClientProxy1_0::setClipboard(ClipboardID id, const CString& data) { // ignore if this clipboard is already clean - CLock lock(getMutex()); - if (m_clipboardDirty[id]) { + if (m_clipboard[id].m_dirty) { // this clipboard is now clean - m_clipboardDirty[id] = false; + m_clipboard[id].m_dirty = false; LOG((CLOG_DEBUG "send clipboard %d to \"%s\" size=%d", id, getName().c_str(), data.size())); CProtocolUtil::writef(getStream(), kMsgDClipboard, id, 0, &data); @@ -267,15 +264,13 @@ CClientProxy1_0::grabClipboard(ClipboardID id) CProtocolUtil::writef(getStream(), kMsgCClipboard, id, 0); // this clipboard is now dirty - CLock lock(getMutex()); - m_clipboardDirty[id] = true; + m_clipboard[id].m_dirty = true; } void CClientProxy1_0::setClipboardDirty(ClipboardID id, bool dirty) { - CLock lock(getMutex()); - m_clipboardDirty[id] = dirty; + m_clipboard[id].m_dirty = dirty; } void @@ -342,7 +337,6 @@ CClientProxy1_0::resetOptions() CProtocolUtil::writef(getStream(), kMsgCResetOptions); // reset heart rate and death - CLock lock(getMutex()); m_heartbeatAlarm = kHeartRate * kHeartBeatsUntilDeath; removeHeartbeatTimer(); addHeartbeatTimer(); @@ -355,7 +349,6 @@ CClientProxy1_0::setOptions(const COptionsList& options) CProtocolUtil::writef(getStream(), kMsgDSetOptions, &options); // check options - CLock lock(getMutex()); for (UInt32 i = 0, n = options.size(); i < n; i += 2) { if (options[i] == kOptionHeartbeat) { double rate = 1.0e-3 * static_cast(options[i + 1]); @@ -369,73 +362,33 @@ CClientProxy1_0::setOptions(const COptionsList& options) } } -SInt32 -CClientProxy1_0::getJumpZoneSize() const -{ - CLock lock(getMutex()); - return m_info.m_zoneSize; -} - -void -CClientProxy1_0::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const -{ - CLock lock(getMutex()); - 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::getCursorCenter(SInt32& x, SInt32& y) const -{ - CLock lock(getMutex()); - x = m_info.m_mx; - y = m_info.m_my; -} - bool -CClientProxy1_0::recvInfo(bool notify) +CClientProxy1_0::recvInfo() { - { - CLock lock(getMutex()); + // parse the message + SInt16 x, y, w, h, zoneSize, mx, my; + if (!CProtocolUtil::readf(getStream(), kMsgDInfo + 4, + &x, &y, &w, &h, &zoneSize, &mx, &my)) { + return false; + } + LOG((CLOG_DEBUG "received client \"%s\" info shape=%d,%d %dx%d, zone=%d, pos=%d,%d", getName().c_str(), x, y, w, h, zoneSize, mx, my)); - // parse the message - SInt16 x, y, w, h, zoneSize, mx, my; - if (!CProtocolUtil::readf(getStream(), kMsgDInfo + 4, - &x, &y, &w, &h, &zoneSize, &mx, &my)) { - return false; - } - LOG((CLOG_DEBUG "received client \"%s\" info shape=%d,%d %dx%d, zone=%d, pos=%d,%d", getName().c_str(), x, y, w, h, zoneSize, mx, my)); - - // validate - if (w <= 0 || h <= 0 || zoneSize < 0) { - return false; - } - if (mx < x || my < y || mx >= x + w || my >= y + h) { - return false; - } - - // save - m_info.m_x = x; - m_info.m_y = y; - m_info.m_w = w; - m_info.m_h = h; - m_info.m_zoneSize = zoneSize; - m_info.m_mx = mx; - m_info.m_my = my; + // validate + if (w <= 0 || h <= 0 || zoneSize < 0) { + return false; + } + if (mx < x || my < y || mx >= x + w || my >= y + h) { + return false; } - // tell server of change - if (notify) { - getServer()->onInfoChanged(getName(), m_info); - } + // save + m_info.m_x = x; + m_info.m_y = y; + m_info.m_w = w; + m_info.m_h = h; + m_info.m_zoneSize = zoneSize; + m_info.m_mx = mx; + m_info.m_my = my; // acknowledge receipt LOG((CLOG_DEBUG1 "send info ack to \"%s\"", getName().c_str())); @@ -461,9 +414,17 @@ CClientProxy1_0::recvClipboard() return false; } - // send update. this calls us back to reset our clipboard dirty flag - // so don't hold a lock during the call. - getServer()->onClipboardChanged(id, seqNum, data); + // 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; } @@ -483,8 +444,25 @@ CClientProxy1_0::recvGrabClipboard() return false; } - // send update. this calls us back to reset our clipboard dirty flag - // so don't hold a lock during the call. - getServer()->onGrabClipboard(getName(), id, seqNum); + // 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 +} diff --git a/lib/server/CClientProxy1_0.h b/lib/server/CClientProxy1_0.h index 175f3c37..26584e97 100644 --- a/lib/server/CClientProxy1_0.h +++ b/lib/server/CClientProxy1_0.h @@ -16,6 +16,7 @@ #define CCLIENTPROXY1_0_H #include "CClientProxy.h" +#include "CClipboard.h" #include "ProtocolTypes.h" class CEvent; @@ -24,14 +25,16 @@ class CEventQueueTimer; //! Proxy for client implementing protocol version 1.0 class CClientProxy1_0 : public CClientProxy { public: - CClientProxy1_0(IServer* server, const CString& name, - IStream* adoptedStream); + CClientProxy1_0(const CString& name, IStream* adoptedStream); ~CClientProxy1_0(); + // IScreen + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + // IClient overrides - virtual void open(); - virtual void mainLoop(); - virtual void close(); virtual void enter(SInt32 xAbs, SInt32 yAbs, UInt32 seqNum, KeyModifierMask mask, bool forScreensaver); @@ -50,13 +53,9 @@ public: virtual void screensaver(bool activate); virtual void resetOptions(); virtual void setOptions(const COptionsList& options); - virtual SInt32 getJumpZoneSize() const; - virtual void getShape(SInt32& x, SInt32& y, - SInt32& width, SInt32& height) const; - virtual void getCursorPos(SInt32& x, SInt32& y) const; - virtual void getCursorCenter(SInt32& x, SInt32& y) const; protected: + virtual bool parseHandshakeMessage(const UInt8* code); virtual bool parseMessage(const UInt8* code); private: @@ -70,15 +69,27 @@ private: void handleWriteError(const CEvent&, void*); void handleFlatline(const CEvent&, void*); - bool recvInfo(bool notify); + bool recvInfo(); bool recvClipboard(); bool recvGrabClipboard(); private: + typedef bool (CClientProxy1_0::*MessageParser)(const UInt8*); + struct CClientClipboard { + public: + CClientClipboard(); + + public: + CClipboard m_clipboard; + UInt32 m_sequenceNumber; + bool m_dirty; + }; + CClientInfo m_info; - bool m_clipboardDirty[kClipboardEnd]; + CClientClipboard m_clipboard[kClipboardEnd]; double m_heartbeatAlarm; CEventQueueTimer* m_heartbeatTimer; + MessageParser m_parser; }; #endif diff --git a/lib/server/CClientProxy1_1.cpp b/lib/server/CClientProxy1_1.cpp index b5d2cf21..ccce9305 100644 --- a/lib/server/CClientProxy1_1.cpp +++ b/lib/server/CClientProxy1_1.cpp @@ -21,9 +21,8 @@ // CClientProxy1_1 // -CClientProxy1_1::CClientProxy1_1(IServer* server, - const CString& name, IStream* stream) : - CClientProxy1_0(server, name, stream) +CClientProxy1_1::CClientProxy1_1(const CString& name, IStream* stream) : + CClientProxy1_0(name, stream) { // do nothing } diff --git a/lib/server/CClientProxy1_1.h b/lib/server/CClientProxy1_1.h index 5e21f21b..08764829 100644 --- a/lib/server/CClientProxy1_1.h +++ b/lib/server/CClientProxy1_1.h @@ -20,8 +20,7 @@ //! Proxy for client implementing protocol version 1.1 class CClientProxy1_1 : public CClientProxy1_0 { public: - CClientProxy1_1(IServer* server, const CString& name, - IStream* adoptedStream); + CClientProxy1_1(const CString& name, IStream* adoptedStream); ~CClientProxy1_1(); // IClient overrides diff --git a/lib/server/CClientProxyUnknown.cpp b/lib/server/CClientProxyUnknown.cpp new file mode 100644 index 00000000..4c45ba07 --- /dev/null +++ b/lib/server/CClientProxyUnknown.cpp @@ -0,0 +1,277 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CClientProxyUnknown.h" +#include "CClientProxy1_0.h" +#include "CClientProxy1_1.h" +#include "ProtocolTypes.h" +#include "CProtocolUtil.h" +#include "XSynergy.h" +#include "IStream.h" +#include "XIO.h" +#include "CLog.h" +#include "CString.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CClientProxyUnknown +// + +CEvent::Type CClientProxyUnknown::s_successEvent = CEvent::kUnknown; +CEvent::Type CClientProxyUnknown::s_failureEvent = CEvent::kUnknown; + +CClientProxyUnknown::CClientProxyUnknown(IStream* stream, double timeout) : + m_stream(stream), + m_proxy(NULL), + m_ready(false) +{ + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CClientProxyUnknown::handleTimeout, NULL)); + m_timer = EVENTQUEUE->newOneShotTimer(timeout, this); + addStreamHandlers(); + + LOG((CLOG_DEBUG1 "saying hello")); + CProtocolUtil::writef(m_stream, kMsgHello, + kProtocolMajorVersion, + kProtocolMinorVersion); +} + +CClientProxyUnknown::~CClientProxyUnknown() +{ + removeHandlers(); + removeTimer(); + delete m_stream; + delete m_proxy; +} + +CClientProxy* +CClientProxyUnknown::orphanClientProxy() +{ + if (m_ready) { + removeHandlers(); + CClientProxy* proxy = m_proxy; + m_proxy = NULL; + return proxy; + } + else { + return NULL; + } +} + +CEvent::Type +CClientProxyUnknown::getSuccessEvent() +{ + return CEvent::registerTypeOnce(s_successEvent, + "CClientProxy::success"); +} + +CEvent::Type +CClientProxyUnknown::getFailureEvent() +{ + return CEvent::registerTypeOnce(s_failureEvent, + "CClientProxy::failure"); +} + +void +CClientProxyUnknown::sendSuccess() +{ + m_ready = true; + removeTimer(); + EVENTQUEUE->addEvent(CEvent(getSuccessEvent(), this)); +} + +void +CClientProxyUnknown::sendFailure() +{ + delete m_proxy; + m_proxy = NULL; + m_ready = false; + removeHandlers(); + removeTimer(); + EVENTQUEUE->addEvent(CEvent(getFailureEvent(), this)); +} + +void +CClientProxyUnknown::addStreamHandlers() +{ + assert(m_stream != NULL); + + EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleData)); + EVENTQUEUE->adoptHandler(IStream::getOutputErrorEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleWriteError)); + EVENTQUEUE->adoptHandler(IStream::getInputShutdownEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleDisconnect)); + EVENTQUEUE->adoptHandler(IStream::getOutputShutdownEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleWriteError)); +} + +void +CClientProxyUnknown::addProxyHandlers() +{ + assert(m_proxy != NULL); + + EVENTQUEUE->adoptHandler(CClientProxy::getReadyEvent(), + m_proxy, + new TMethodEventJob(this, + &CClientProxyUnknown::handleReady)); + EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), + m_proxy, + new TMethodEventJob(this, + &CClientProxyUnknown::handleDisconnect)); +} + +void +CClientProxyUnknown::removeHandlers() +{ + if (m_stream != NULL) { + EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputErrorEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getInputShutdownEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputShutdownEvent(), + m_stream->getEventTarget()); + } + if (m_proxy != NULL) { + EVENTQUEUE->removeHandler(CClientProxy::getReadyEvent(), + m_proxy); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), + m_proxy); + } +} + +void +CClientProxyUnknown::removeTimer() +{ + if (m_timer != NULL) { + EVENTQUEUE->deleteTimer(m_timer); + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + m_timer = NULL; + } +} + +void +CClientProxyUnknown::handleData(const CEvent&, void*) +{ + LOG((CLOG_DEBUG1 "parsing hello reply")); + + CString name(""); + try { + // limit the maximum length of the hello + UInt32 n = m_stream->getSize(); + if (n > kMaxHelloLength) { + LOG((CLOG_DEBUG1 "hello reply too long")); + throw XBadClient(); + } + + // parse the reply to hello + SInt16 major, minor; + if (!CProtocolUtil::readf(m_stream, kMsgHelloBack, + &major, &minor, &name)) { + throw XBadClient(); + } + + // disallow invalid version numbers + if (major <= 0 || minor < 0) { + throw XIncompatibleClient(major, minor); + } + + // remove stream event handlers. the proxy we're about to create + // may install its own handlers and we don't want to accidentally + // remove those later. + removeHandlers(); + + // create client proxy for highest version supported by the client + if (major == 1) { + switch (minor) { + case 0: + m_proxy = new CClientProxy1_0(name, m_stream); + break; + + case 1: + m_proxy = new CClientProxy1_1(name, m_stream); + break; + } + } + + // hangup (with error) if version isn't supported + if (m_proxy == NULL) { + throw XIncompatibleClient(major, minor); + } + + // the proxy is created and now proxy now owns the stream + LOG((CLOG_DEBUG1 "created proxy for client \"%s\" version %d.%d", name.c_str(), major, minor)); + m_stream = NULL; + + // wait until the proxy signals that it's ready or has disconnected + addProxyHandlers(); + return; + } + catch (XIncompatibleClient& e) { + // client is incompatible + LOG((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor())); + CProtocolUtil::writef(m_stream, + kMsgEIncompatible, + kProtocolMajorVersion, kProtocolMinorVersion); + } + catch (XBadClient&) { + // client not behaving + LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str())); + CProtocolUtil::writef(m_stream, kMsgEBad); + } + catch (XBase& e) { + // misc error + LOG((CLOG_WARN "error communicating with client \"%s\": %s", name.c_str(), e.what())); + } + sendFailure(); +} + +void +CClientProxyUnknown::handleWriteError(const CEvent&, void*) +{ + LOG((CLOG_NOTE "error communicating with new client")); + sendFailure(); +} + +void +CClientProxyUnknown::handleTimeout(const CEvent&, void*) +{ + LOG((CLOG_NOTE "new client is unresponsive")); + sendFailure(); +} + +void +CClientProxyUnknown::handleDisconnect(const CEvent&, void*) +{ + LOG((CLOG_NOTE "new client disconnected")); + sendFailure(); +} + +void +CClientProxyUnknown::handleReady(const CEvent&, void*) +{ + sendSuccess(); +} diff --git a/lib/server/CClientProxyUnknown.h b/lib/server/CClientProxyUnknown.h new file mode 100644 index 00000000..140a1aee --- /dev/null +++ b/lib/server/CClientProxyUnknown.h @@ -0,0 +1,83 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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. + */ + +#ifndef CCLIENTPROXYUNKNOWN_H +#define CCLIENTPROXYUNKNOWN_H + +#include "CEvent.h" + +class CClientProxy; +class CEventQueueTimer; +class IStream; + +class CClientProxyUnknown { +public: + CClientProxyUnknown(IStream* stream, double timeout); + ~CClientProxyUnknown(); + + //! @name manipulators + //@{ + + //! Get the client proxy + /*! + Returns the client proxy created after a successful handshake + (i.e. when this object sends a success event). Returns NULL + if the handshake is unsuccessful or incomplete. + */ + CClientProxy* orphanClientProxy(); + + //@} + //! @name accessors + //@{ + + //! Get success event type + /*! + Returns the success event type. This is sent when the client has + correctly responded to the hello message. The target is this. + */ + static CEvent::Type getSuccessEvent(); + + //! Get failure event type + /*! + Returns the failure event type. This is sent when a client fails + to correctly respond to the hello message. The target is this. + */ + static CEvent::Type getFailureEvent(); + + //@} + +private: + void sendSuccess(); + void sendFailure(); + void addStreamHandlers(); + void addProxyHandlers(); + void removeHandlers(); + void removeTimer(); + void handleData(const CEvent&, void*); + void handleWriteError(const CEvent&, void*); + void handleTimeout(const CEvent&, void*); + void handleDisconnect(const CEvent&, void*); + void handleReady(const CEvent&, void*); + +private: + IStream* m_stream; + CEventQueueTimer* m_timer; + CClientProxy* m_proxy; + bool m_ready; + + static CEvent::Type s_successEvent; + static CEvent::Type s_failureEvent; +}; + +#endif diff --git a/lib/server/CConfig.cpp b/lib/server/CConfig.cpp index de20f312..0b40a666 100644 --- a/lib/server/CConfig.cpp +++ b/lib/server/CConfig.cpp @@ -446,11 +446,9 @@ CConfig::getOptions(const CString& name) const bool CConfig::operator==(const CConfig& x) const { -/* FIXME -- no compare available for CNetworkAddress if (m_synergyAddress != x.m_synergyAddress) { return false; } -*/ if (m_map.size() != x.m_map.size()) { return false; } diff --git a/lib/server/CPrimaryClient.cpp b/lib/server/CPrimaryClient.cpp index 54a207f1..5281b092 100644 --- a/lib/server/CPrimaryClient.cpp +++ b/lib/server/CPrimaryClient.cpp @@ -14,10 +14,6 @@ #include "CPrimaryClient.h" #include "CScreen.h" -#include "IScreenFactory.h" -#include "IServer.h" -#include "XScreen.h" -#include "XSynergy.h" #include "CClipboard.h" #include "CLog.h" @@ -25,66 +21,31 @@ // CPrimaryClient // -CPrimaryClient::CPrimaryClient(IScreenFactory* screenFactory, - IServer* server, - IPrimaryScreenReceiver* receiver, - const CString& name) : - m_server(server), +CPrimaryClient::CPrimaryClient(const CString& name, CScreen* screen) : m_name(name), - m_seqNum(0) + m_screen(screen) { - assert(m_server != NULL); - - // create screen - LOG((CLOG_DEBUG1 "creating primary screen")); - if (screenFactory != NULL) { - IPlatformScreen* platformScreen = - screenFactory->create(this, receiver); - if (platformScreen != NULL) { - m_screen = new CScreen(platformScreen, this); - } - } - if (m_screen == NULL) { - throw XScreenOpenFailure(); + // all clipboards are clean + for (UInt32 i = 0; i < kClipboardEnd; ++i) { + m_clipboardDirty[i] = false; } } CPrimaryClient::~CPrimaryClient() { - LOG((CLOG_DEBUG1 "destroying primary screen")); delete m_screen; } -void -CPrimaryClient::exitMainLoop() -{ - m_screen->exitMainLoop(); -} - void CPrimaryClient::reconfigure(UInt32 activeSides) { m_screen->reconfigure(activeSides); } -UInt32 -CPrimaryClient::addOneShotTimer(double timeout) -{ - return m_screen->addOneShotTimer(timeout); -} - void -CPrimaryClient::getClipboard(ClipboardID id, CString& data) const +CPrimaryClient::getCursorCenter(SInt32& x, SInt32& y) const { - CClipboard clipboard; - m_screen->getClipboard(id, &clipboard); - data = clipboard.marshall(); -} - -bool -CPrimaryClient::isLockedToScreen() const -{ - return m_screen->isLockedToScreen(); + m_screen->getCursorCenter(x, y); } KeyModifierMask @@ -93,64 +54,41 @@ CPrimaryClient::getToggleMask() const return m_screen->getActiveModifiers(); } -void -CPrimaryClient::onError() +bool +CPrimaryClient::isLockedToScreen() const { - // forward to server - m_server->onError(); + return m_screen->isLockedToScreen(); } -void -CPrimaryClient::onInfoChanged(const CClientInfo& info) +void* +CPrimaryClient::getEventTarget() const { - m_info = info; - try { - m_server->onInfoChanged(getName(), m_info); - } - catch (XBadClient&) { - // ignore - } + return m_screen->getEventTarget(); } bool -CPrimaryClient::onGrabClipboard(ClipboardID id) +CPrimaryClient::getClipboard(ClipboardID id, IClipboard* clipboard) const { - try { - return m_server->onGrabClipboard(getName(), id, m_seqNum); - } - catch (XBadClient&) { - return false; - } + return m_screen->getClipboard(id, clipboard); +} + +SInt32 +CPrimaryClient::getJumpZoneSize() const +{ + return m_screen->getJumpZoneSize(); } void -CPrimaryClient::onClipboardChanged(ClipboardID id, const CString& data) +CPrimaryClient::getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const { - m_server->onClipboardChanged(id, m_seqNum, data); + m_screen->getShape(x, y, width, height); } void -CPrimaryClient::open() +CPrimaryClient::getCursorPos(SInt32& x, SInt32& y) const { - // all clipboards are clean - for (UInt32 i = 0; i < kClipboardEnd; ++i) { - m_clipboardDirty[i] = false; - } - - // now open the screen - m_screen->open(); -} - -void -CPrimaryClient::mainLoop() -{ - m_screen->mainLoop(); -} - -void -CPrimaryClient::close() -{ - m_screen->close(); + m_screen->getCursorPos(x, y); } void @@ -169,8 +107,7 @@ void CPrimaryClient::enter(SInt32 xAbs, SInt32 yAbs, UInt32 seqNum, KeyModifierMask, bool screensaver) { - // note -- we must not call any server methods except onError(). - m_seqNum = seqNum; + m_screen->setSequenceNumber(seqNum); if (!screensaver) { m_screen->warpCursor(xAbs, yAbs); } @@ -180,15 +117,12 @@ CPrimaryClient::enter(SInt32 xAbs, SInt32 yAbs, bool CPrimaryClient::leave() { - // note -- we must not call any server methods except onError(). return m_screen->leave(); } void CPrimaryClient::setClipboard(ClipboardID id, const CString& data) { - // note -- we must not call any server methods except onError(). - // ignore if this clipboard is already clean if (m_clipboardDirty[id]) { // this clipboard is now clean @@ -284,31 +218,3 @@ CPrimaryClient::getName() const { return m_name; } - -SInt32 -CPrimaryClient::getJumpZoneSize() const -{ - return m_info.m_zoneSize; -} - -void -CPrimaryClient::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 -CPrimaryClient::getCursorPos(SInt32&, SInt32&) const -{ - assert(0 && "shouldn't be called"); -} - -void -CPrimaryClient::getCursorCenter(SInt32& x, SInt32& y) const -{ - x = m_info.m_mx; - y = m_info.m_my; -} diff --git a/lib/server/CPrimaryClient.h b/lib/server/CPrimaryClient.h index 823c7297..4d80f86a 100644 --- a/lib/server/CPrimaryClient.h +++ b/lib/server/CPrimaryClient.h @@ -16,14 +16,9 @@ #define CPRIMARYCLIENT_H #include "IClient.h" -#include "IScreenReceiver.h" #include "ProtocolTypes.h" class CScreen; -class IClipboard; -class IScreenFactory; -class IPrimaryScreenReceiver; -class IServer; //! Primary screen as pseudo-client /*! @@ -31,50 +26,34 @@ The primary screen does not have a client associated with it. This class provides a pseudo-client to allow the primary screen to be treated as if it was on a client. */ -class CPrimaryClient : public IScreenReceiver, public IClient { +class CPrimaryClient : public IClient { public: /*! - \c name is the name of the server. The caller retains ownership of - \c factory. Throws XScreenOpenFailure or whatever the factory can - throw if the screen cannot be created. + \c name is the name of the server. \p screen is adopted. */ - CPrimaryClient(IScreenFactory* factory, IServer*, - IPrimaryScreenReceiver*, const CString& name); + CPrimaryClient(const CString& name, CScreen* screen); ~CPrimaryClient(); //! @name manipulators //@{ - //! Exit event loop - /*! - Force mainLoop() to return. This call can return before - mainLoop() does (i.e. asynchronously). This may only be - called between a successful open() and close(). - */ - void exitMainLoop(); - //! Update configuration /*! Handles reconfiguration of jump zones. */ void reconfigure(UInt32 activeSides); - //! Install a one-shot timer - /*! - Installs a one-shot timer for \c timeout seconds and returns the - id of the timer (which will be passed to \c onTimerExpired()). - */ - UInt32 addOneShotTimer(double timeout); - //@} //! @name accessors //@{ - //! Get clipboard + //! Get cursor center position /*! - Save the marshalled contents of the clipboard indicated by \c id. + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. */ - void getClipboard(ClipboardID, CString&) const; + void getCursorCenter(SInt32& x, SInt32& y) const; //! Get toggle key state /*! @@ -90,19 +69,19 @@ public: //@} - // IScreenReceiver overrides - virtual void onError(); - virtual void onInfoChanged(const CClientInfo&); - virtual bool onGrabClipboard(ClipboardID); - virtual void onClipboardChanged(ClipboardID, const CString&); - -// XXX -- these go in IClient + // FIXME -- these probably belong on IScreen virtual void enable(); virtual void disable(); + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual SInt32 getJumpZoneSize() const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + // IClient overrides - virtual void open(); - virtual void mainLoop(); - virtual void close(); virtual void enter(SInt32 xAbs, SInt32 yAbs, UInt32 seqNum, KeyModifierMask mask, bool forScreensaver); @@ -122,18 +101,10 @@ public: virtual void resetOptions(); virtual void setOptions(const COptionsList& options); virtual CString getName() const; - virtual SInt32 getJumpZoneSize() const; - virtual void getShape(SInt32& x, SInt32& y, - SInt32& width, SInt32& height) const; - virtual void getCursorPos(SInt32& x, SInt32& y) const; - virtual void getCursorCenter(SInt32& x, SInt32& y) const; private: - IServer* m_server; - CScreen* m_screen; CString m_name; - UInt32 m_seqNum; - CClientInfo m_info; + CScreen* m_screen; bool m_clipboardDirty[kClipboardEnd]; }; diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp index 47361435..cc2282f5 100644 --- a/lib/server/CServer.cpp +++ b/lib/server/CServer.cpp @@ -13,212 +13,168 @@ */ #include "CServer.h" +#include "CClientProxy.h" +#include "CClientProxyUnknown.h" #include "CPrimaryClient.h" -#include "IScreenFactory.h" -#include "CInputPacketStream.h" -#include "COutputPacketStream.h" -#include "CProtocolUtil.h" -#include "CClientProxy1_0.h" -#include "CClientProxy1_1.h" +#include "CPacketStreamFilter.h" +#include "IPlatformScreen.h" #include "OptionTypes.h" #include "ProtocolTypes.h" #include "XScreen.h" #include "XSynergy.h" -#include "CTCPListenSocket.h" #include "IDataSocket.h" -#include "ISocketFactory.h" +#include "IListenSocket.h" #include "XSocket.h" -#include "IStreamFilterFactory.h" -#include "CLock.h" -#include "CThread.h" -#include "CTimerThread.h" -#include "XMT.h" -#include "XThread.h" -#include "CFunctionJob.h" +#include "IEventQueue.h" #include "CLog.h" -#include "CStopwatch.h" -#include "TMethodJob.h" +#include "TMethodEventJob.h" #include "CArch.h" // // CServer // -CServer::CServer(const CString& serverName) : - m_name(serverName), - m_error(false), - m_bindTimeout(5.0 * 60.0), - m_screenFactory(NULL), - m_socketFactory(NULL), - m_streamFilterFactory(NULL), - m_acceptClientThread(NULL), - m_active(NULL), - m_primaryClient(NULL), +CEvent::Type CServer::s_errorEvent = CEvent::kUnknown; +CEvent::Type CServer::s_disconnectedEvent = CEvent::kUnknown; + +CServer::CServer(const CConfig& config, CPrimaryClient* primaryClient) : + m_active(primaryClient), + m_primaryClient(primaryClient), m_seqNum(0), + m_config(config), m_activeSaver(NULL), m_switchDir(kNoDirection), m_switchScreen(NULL), m_switchWaitDelay(0.0), - m_switchWaitEngaged(false), + m_switchWaitTimer(NULL), m_switchTwoTapDelay(0.0), m_switchTwoTapEngaged(false), m_switchTwoTapArmed(false), - m_switchTwoTapZone(3), - m_status(kNotRunning) + m_switchTwoTapZone(3) { - // do nothing + // must have a primary client and it must have a canonical name + assert(m_primaryClient != NULL); + assert(m_config.isScreen(primaryClient->getName())); + + CString primaryName = getName(primaryClient); + + // clear clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + CClipboardInfo& clipboard = m_clipboards[id]; + clipboard.m_clipboardOwner = primaryName; + clipboard.m_clipboardSeqNum = m_seqNum; + if (clipboard.m_clipboard.open(0)) { + clipboard.m_clipboard.empty(); + clipboard.m_clipboard.close(); + } + clipboard.m_clipboardData = clipboard.m_clipboard.marshall(); + } + + // install event handlers + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CServer::handleSwitchWaitTimeout)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyDownEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleKeyDownEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyUpEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleKeyUpEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyRepeatEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleKeyRepeatEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonDownEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleButtonDownEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonUpEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleButtonUpEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnPrimaryEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleMotionPrimaryEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnSecondaryEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleMotionSecondaryEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getWheelEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleWheelEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverActivatedEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleScreensaverActivatedEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverDeactivatedEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleScreensaverDeactivatedEvent)); + + // add connection + addClient(m_primaryClient); + + // tell it about the active sides + m_primaryClient->reconfigure(getActivePrimarySides()); + + // tell primary client about its options + sendOptions(m_primaryClient); + + m_primaryClient->enable(); } CServer::~CServer() { - delete m_screenFactory; - delete m_socketFactory; - delete m_streamFilterFactory; -} + // remove event handlers and timers + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyDownEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyUpEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyRepeatEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getButtonDownEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getButtonUpEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnPrimaryEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnSecondaryEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getWheelEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverActivatedEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverDeactivatedEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + stopSwitch(); -void -CServer::open() -{ - // open the screen - try { - LOG((CLOG_INFO "opening screen")); - openPrimaryScreen(); - setStatus(kNotRunning); - m_primaryClient->enable(); - } - catch (XScreen& e) { - // can't open screen - setStatus(kError, e.what()); - LOG((CLOG_INFO "failed to open screen")); - throw; - } - catch (XUnknownClient& e) { - // can't open screen - setStatus(kServerNameUnknown); - LOG((CLOG_CRIT "unknown screen name `%s'", e.getName().c_str())); - throw; - } -} - -void -CServer::mainLoop() -{ - // check preconditions - { - CLock lock(&m_mutex); - assert(m_primaryClient != NULL); + // force immediate disconnection of secondary clients + disconnect(); + for (COldClients::iterator index = m_oldClients.begin(); + index != m_oldClients.begin(); ++index) { + IClient* client = index->first; + EVENTQUEUE->deleteTimer(index->second); + EVENTQUEUE->removeHandler(CEvent::kTimer, client); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + delete client; } - try { - setStatus(kNotRunning); - LOG((CLOG_NOTE "starting server")); - -// FIXME -- started here - createClientListener(); -// FIXME -- finished here - - // start listening for new clients - m_acceptClientThread = new CThread(startThread( - new TMethodJob(this, - &CServer::acceptClients))); - - // handle events - m_primaryClient->mainLoop(); - - // clean up - LOG((CLOG_NOTE "stopping server")); - - // use a macro to write the stuff that should go into a finally - // block so we can repeat it easily. stroustrup's view that - // "resource acquistion is initialization" is a better solution - // than a finally block is parochial. they both have their - // place. adding finally to C++ would've been a drop in a big - // bucket. -#define FINALLY do { \ - stopThreads(); \ - runStatusJobs(); \ - } while (false) - FINALLY; - } - catch (XMT& e) { - LOG((CLOG_ERR "server error: %s", e.what())); - setStatus(kError, e.what()); - - // clean up - LOG((CLOG_NOTE "stopping server")); - FINALLY; - throw; - } - catch (XBase& e) { - LOG((CLOG_ERR "server error: %s", e.what())); - setStatus(kError, e.what()); - - // clean up - LOG((CLOG_NOTE "stopping server")); - FINALLY; - } - catch (XThread&) { - setStatus(kNotRunning); - - // clean up - LOG((CLOG_NOTE "stopping server")); - FINALLY; - throw; - } - catch (...) { - LOG((CLOG_DEBUG "unknown server error")); - setStatus(kError); - - // clean up - LOG((CLOG_NOTE "stopping server")); - FINALLY; - throw; - } -#undef FINALLY - - // throw if there was an error - if (m_error) { - LOG((CLOG_DEBUG "forwarding child thread exception")); - throw XServerRethrow(); - } -} - -void -CServer::exitMainLoop() -{ - m_primaryClient->exitMainLoop(); -} - -void -CServer::exitMainLoopWithError() -{ - { - CLock lock(&m_mutex); - m_error = true; - } - exitMainLoop(); -} - -void -CServer::close() -{ - if (m_primaryClient != NULL) { - m_primaryClient->disable(); - closePrimaryScreen(); - } - LOG((CLOG_INFO "closed screen")); + // disconnect primary client + removeClient(m_primaryClient); } bool CServer::setConfig(const CConfig& config) { // refuse configuration if it doesn't include the primary screen - { - CLock lock(&m_mutex); - if (m_primaryClient != NULL && - !config.isScreen(m_primaryClient->getName())) { - return false; - } + if (m_primaryClient != NULL && + !config.isScreen(m_primaryClient->getName())) { + return false; } // close clients that are connected but being dropped from the @@ -226,32 +182,8 @@ CServer::setConfig(const CConfig& config) closeClients(config); // cut over - CLock lock(&m_mutex); m_config = config; - - // process global options - const CConfig::CScreenOptions* options = m_config.getOptions(""); - if (options != NULL && options->size() > 0) { - for (CConfig::CScreenOptions::const_iterator index = options->begin(); - index != options->end(); ++index) { - const OptionID id = index->first; - const OptionValue value = index->second; - if (id == kOptionScreenSwitchDelay) { - m_switchWaitDelay = 1.0e-3 * static_cast(value); - if (m_switchWaitDelay < 0.0) { - m_switchWaitDelay = 0.0; - } - m_switchWaitEngaged = false; - } - else if (id == kOptionScreenSwitchTwoTap) { - m_switchTwoTapDelay = 1.0e-3 * static_cast(value); - if (m_switchTwoTapDelay < 0.0) { - m_switchTwoTapDelay = 0.0; - } - m_switchTwoTapEngaged = false; - } - } - } + processOptions(); // tell primary screen about reconfiguration if (m_primaryClient != NULL) { @@ -265,65 +197,66 @@ CServer::setConfig(const CConfig& config) sendOptions(client); } - // notify of status - runStatusJobs(); - return true; } void -CServer::setScreenFactory(IScreenFactory* adopted) +CServer::adoptClient(IClient* client) { - CLock lock(&m_mutex); - delete m_screenFactory; - m_screenFactory = adopted; + assert(client != NULL); + + // name must be in our configuration + if (!m_config.isScreen(client->getName())) { + LOG((CLOG_WARN "a client with name \"%s\" is not in the map", client->getName().c_str())); + closeClient(client, kMsgEUnknown); + return; + } + + // add client to client list + if (!addClient(client)) { + // can only have one screen with a given name at any given time + LOG((CLOG_WARN "a client with name \"%s\" is already connected", getName(client).c_str())); + closeClient(client, kMsgEBusy); + return; + } + LOG((CLOG_NOTE "client \"%s\" has connected", getName(client).c_str())); + + // watch for client disconnection + EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), client, + new TMethodEventJob(this, + &CServer::handleClientDisconnected, client)); + + // send configuration options to client + sendOptions(client); + + // activate screen saver on new client if active on the primary screen + if (m_activeSaver != NULL) { + client->screensaver(true); + } } void -CServer::setSocketFactory(ISocketFactory* adopted) +CServer::disconnect() { - CLock lock(&m_mutex); - delete m_socketFactory; - m_socketFactory = adopted; -} - -void -CServer::setStreamFilterFactory(IStreamFilterFactory* adopted) -{ - CLock lock(&m_mutex); - delete m_streamFilterFactory; - m_streamFilterFactory = adopted; -} - -void -CServer::addStatusJob(IJob* job) -{ - m_statusJobs.addJob(job); -} - -void -CServer::removeStatusJob(IJob* job) -{ - m_statusJobs.removeJob(job); -} - -CString -CServer::getPrimaryScreenName() const -{ - return m_name; + // close all secondary clients + if (m_clients.size() > 1 || !m_oldClients.empty()) { + CConfig emptyConfig; + closeClients(emptyConfig); + } + else { + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this)); + } } UInt32 CServer::getNumClients() const { - CLock lock(&m_mutex); return m_clients.size(); } void CServer::getClients(std::vector& list) const { - CLock lock(&m_mutex); list.clear(); for (CClientList::const_iterator index = m_clients.begin(); index != m_clients.end(); ++index) { @@ -331,582 +264,18 @@ CServer::getClients(std::vector& list) const } } -CServer::EStatus -CServer::getStatus(CString* msg) const +CEvent::Type +CServer::getErrorEvent() { - CLock lock(&m_mutex); - if (msg != NULL) { - *msg = m_statusMessage; - } - return m_status; + return CEvent::registerTypeOnce(s_errorEvent, + "CServer::error"); } -void -CServer::getConfig(CConfig* config) const +CEvent::Type +CServer::getDisconnectedEvent() { - assert(config != NULL); - - CLock lock(&m_mutex); - *config = m_config; -} - -CString -CServer::getCanonicalName(const CString& name) const -{ - CLock lock(&m_mutex); - if (m_config.isScreen(name)) { - return m_config.getCanonicalName(name); - } - return name; -} - -void -CServer::runStatusJobs() const -{ - m_statusJobs.runJobs(); -} - -void -CServer::setStatus(EStatus status, const char* msg) -{ - { - CLock lock(&m_mutex); - m_status = status; - if (m_status == kError) { - m_statusMessage = (msg == NULL) ? "Error" : msg; - } - else { - m_statusMessage = (msg == NULL) ? "" : msg; - } - } - runStatusJobs(); -} - -UInt32 -CServer::getActivePrimarySides() const -{ - // note -- m_mutex must be locked on entry - UInt32 sides = 0; - if (!m_config.getNeighbor(getPrimaryScreenName(), kLeft).empty()) { - sides |= kLeftMask; - } - if (!m_config.getNeighbor(getPrimaryScreenName(), kRight).empty()) { - sides |= kRightMask; - } - if (!m_config.getNeighbor(getPrimaryScreenName(), kTop).empty()) { - sides |= kTopMask; - } - if (!m_config.getNeighbor(getPrimaryScreenName(), kBottom).empty()) { - sides |= kBottomMask; - } - return sides; -} - -void -CServer::onError() -{ - setStatus(kError); - - // stop all running threads but don't wait too long since some - // threads may be unable to proceed until this thread returns. - stopThreads(3.0); - - // note -- we do not attempt to close down the primary screen -} - -void -CServer::onInfoChanged(const CString& name, const CClientInfo& info) -{ - CLock lock(&m_mutex); - - // look up client - CClientList::iterator index = m_clients.find(name); - if (index == m_clients.end()) { - throw XBadClient(); - } - IClient* client = index->second; - assert(client != NULL); - - // update the remote mouse coordinates - if (client == m_active) { - m_x = info.m_mx; - m_y = info.m_my; - } - LOG((CLOG_INFO "screen \"%s\" shape=%d,%d %dx%d zone=%d pos=%d,%d", name.c_str(), info.m_x, info.m_y, info.m_w, info.m_h, info.m_zoneSize, info.m_mx, info.m_my)); - - // handle resolution change to primary screen - if (client == m_primaryClient) { - if (client == m_active) { - onMouseMovePrimaryNoLock(m_x, m_y); - } - else { - onMouseMoveSecondaryNoLock(0, 0); - } - } -} - -bool -CServer::onGrabClipboard(const CString& name, ClipboardID id, UInt32 seqNum) -{ - CLock lock(&m_mutex); - - // screen must be connected - CClientList::iterator grabber = m_clients.find(name); - if (grabber == m_clients.end()) { - throw XBadClient(); - } - - // ignore grab if sequence number is old. always allow primary - // screen to grab. - CClipboardInfo& clipboard = m_clipboards[id]; - if (name != m_primaryClient->getName() && - seqNum < clipboard.m_clipboardSeqNum) { - LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", name.c_str(), id)); - return false; - } - - // mark screen as owning clipboard - LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", name.c_str(), id, clipboard.m_clipboardOwner.c_str())); - clipboard.m_clipboardOwner = name; - clipboard.m_clipboardSeqNum = seqNum; - - // clear the clipboard data (since it's not known at this point) - if (clipboard.m_clipboard.open(0)) { - clipboard.m_clipboard.empty(); - clipboard.m_clipboard.close(); - } - clipboard.m_clipboardData = clipboard.m_clipboard.marshall(); - - // tell all other screens to take ownership of clipboard. tell the - // grabber that it's clipboard isn't dirty. - for (CClientList::iterator index = m_clients.begin(); - index != m_clients.end(); ++index) { - IClient* client = index->second; - if (index == grabber) { - client->setClipboardDirty(id, false); - } - else { - client->grabClipboard(id); - } - } - - return true; -} - -void -CServer::onClipboardChanged(ClipboardID id, UInt32 seqNum, const CString& data) -{ - CLock lock(&m_mutex); - onClipboardChangedNoLock(id, seqNum, data); -} - -void -CServer::onClipboardChangedNoLock(ClipboardID id, - UInt32 seqNum, const CString& data) -{ - 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 (missequenced)", clipboard.m_clipboardOwner.c_str(), id)); - return; - } - - // ignore if data hasn't changed - if (data == clipboard.m_clipboardData) { - LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", 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_clipboardData = data; - clipboard.m_clipboard.unmarshall(clipboard.m_clipboardData, 0); - - // tell all clients except the sender that the clipboard is dirty - CClientList::const_iterator sender = - m_clients.find(clipboard.m_clipboardOwner); - for (CClientList::const_iterator index = m_clients.begin(); - index != m_clients.end(); ++index) { - IClient* client = index->second; - client->setClipboardDirty(id, index != sender); - } - - // send the new clipboard to the active screen - m_active->setClipboard(id, m_clipboards[id].m_clipboardData); -} - -void -CServer::onScreensaver(bool activated) -{ - LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated")); - CLock lock(&m_mutex); - - if (activated) { - // save current screen and position - m_activeSaver = m_active; - m_xSaver = m_x; - m_ySaver = m_y; - - // jump to primary screen - if (m_active != m_primaryClient) { - switchScreen(m_primaryClient, 0, 0, true); - } - } - else { - // jump back to previous screen and position. we must check - // that the position is still valid since the screen may have - // changed resolutions while the screen saver was running. - if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) { - // check position - IClient* screen = m_activeSaver; - SInt32 x, y, w, h; - screen->getShape(x, y, w, h); - SInt32 zoneSize = screen->getJumpZoneSize(); - if (m_xSaver < x + zoneSize) { - m_xSaver = x + zoneSize; - } - else if (m_xSaver >= x + w - zoneSize) { - m_xSaver = x + w - zoneSize - 1; - } - if (m_ySaver < y + zoneSize) { - m_ySaver = y + zoneSize; - } - else if (m_ySaver >= y + h - zoneSize) { - m_ySaver = y + h - zoneSize - 1; - } - - // jump - switchScreen(screen, m_xSaver, m_ySaver, false); - } - - // reset state - m_activeSaver = NULL; - } - - // send message to all clients - for (CClientList::const_iterator index = m_clients.begin(); - index != m_clients.end(); ++index) { - IClient* client = index->second; - client->screensaver(activated); - } -} - -void -CServer::onOneShotTimerExpired(UInt32 id) -{ - CLock lock(&m_mutex); - - // ignore if it's an old timer or if switch wait isn't engaged anymore - if (!m_switchWaitEngaged || id != m_switchWaitTimer) { - return; - } - - // ignore if mouse is locked to screen - if (isLockedToScreenNoLock()) { - LOG((CLOG_DEBUG1 "locked to screen")); - clearSwitchState(); - return; - } - - // switch screen - switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false); -} - -void -CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button) -{ - LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button)); - CLock lock(&m_mutex); - assert(m_active != NULL); - - // handle command keys - if (onCommandKey(id, mask, true)) { - return; - } - - // relay - m_active->keyDown(id, mask, button); -} - -void -CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button) -{ - LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button)); - CLock lock(&m_mutex); - assert(m_active != NULL); - - // handle command keys - if (onCommandKey(id, mask, false)) { - return; - } - - // relay - m_active->keyUp(id, mask, button); -} - -void -CServer::onKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button) -{ - LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button)); - CLock lock(&m_mutex); - assert(m_active != NULL); - - // handle command keys - if (onCommandKey(id, mask, false)) { - onCommandKey(id, mask, true); - return; - } - - // relay - m_active->keyRepeat(id, mask, count, button); -} - -void -CServer::onMouseDown(ButtonID id) -{ - LOG((CLOG_DEBUG1 "onMouseDown id=%d", id)); - CLock lock(&m_mutex); - assert(m_active != NULL); - - // relay - m_active->mouseDown(id); -} - -void -CServer::onMouseUp(ButtonID id) -{ - LOG((CLOG_DEBUG1 "onMouseUp id=%d", id)); - CLock lock(&m_mutex); - assert(m_active != NULL); - - // relay - m_active->mouseUp(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_primaryClient != NULL); - assert(m_active == m_primaryClient); - - // get screen shape - SInt32 ax, ay, aw, ah; - m_active->getShape(ax, ay, aw, ah); - SInt32 zoneSize = m_active->getJumpZoneSize(); - - // see if we should change screens - EDirection dir; - if (x < ax + zoneSize) { - x -= zoneSize; - dir = kLeft; - } - else if (x >= ax + aw - zoneSize) { - x += zoneSize; - dir = kRight; - } - else if (y < ay + zoneSize) { - y -= zoneSize; - dir = kTop; - } - else if (y >= ay + ah - zoneSize) { - y += zoneSize; - dir = kBottom; - } - else { - // still on local screen. check if we're inside the tap region. - SInt32 tapZone = (zoneSize < m_switchTwoTapZone) ? - m_switchTwoTapZone : zoneSize; - bool inTapZone = (x < ax + tapZone || - x >= ax + aw - tapZone || - y < ay + tapZone || - y >= ay + ah - tapZone); - - // failed to switch - onNoSwitch(inTapZone); - return false; - } - - // get jump destination - IClient* newScreen = getNeighbor(m_active, dir, x, y); - - // should we switch or not? - if (isSwitchOkay(newScreen, dir, x, y)) { - // switch screen - switchScreen(newScreen, x, y, false); - return true; - } - else { - return false; - } -} - -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_primaryClient) { - // 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; - - // get screen shape - SInt32 ax, ay, aw, ah; - m_active->getShape(ax, ay, aw, ah); - - // find direction of neighbor and get the neighbor - bool jump = true; - IClient* newScreen; - do { - EDirection dir; - if (m_x < ax) { - dir = kLeft; - } - else if (m_x > ax + aw - 1) { - dir = kRight; - } - else if (m_y < ay) { - dir = kTop; - } - else if (m_y > ay + ah - 1) { - dir = kBottom; - } - else { - // we haven't left the screen - newScreen = m_active; - jump = false; - - // if waiting and mouse is not on the border we're waiting - // on then stop waiting. also if it's not on the border - // then arm the double tap. - if (m_switchScreen != NULL) { - bool clearWait; - SInt32 zoneSize = m_primaryClient->getJumpZoneSize(); - switch (m_switchDir) { - case kLeft: - clearWait = (m_x >= ax + zoneSize); - break; - - case kRight: - clearWait = (m_x <= ax + aw - 1 - zoneSize); - break; - - case kTop: - clearWait = (m_y >= ay + zoneSize); - break; - - case kBottom: - clearWait = (m_y <= ay + ah - 1 + zoneSize); - break; - - default: - clearWait = false; - break; - } - if (clearWait) { - // still on local screen. check if we're inside the - // tap region. - SInt32 tapZone = (zoneSize < m_switchTwoTapZone) ? - m_switchTwoTapZone : zoneSize; - bool inTapZone = (m_x < ax + tapZone || - m_x >= ax + aw - tapZone || - m_y < ay + tapZone || - m_y >= ay + ah - tapZone); - - // failed to switch - onNoSwitch(inTapZone); - } - } - - // skip rest of block - break; - } - - // try to switch screen. get the neighbor. - newScreen = getNeighbor(m_active, dir, m_x, m_y); - - // see if we should switch - if (!isSwitchOkay(newScreen, dir, m_x, m_y)) { - newScreen = m_active; - jump = false; - } - } while (false); - - if (jump) { - // switch screens - switchScreen(newScreen, m_x, m_y, false); - } - else { - // same screen. clamp mouse to edge. - m_x = xOld + dx; - m_y = yOld + dy; - if (m_x < ax) { - m_x = ax; - LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", m_active->getName().c_str())); - } - else if (m_x > ax + aw - 1) { - m_x = ax + aw - 1; - LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", m_active->getName().c_str())); - } - if (m_y < ay) { - m_y = ay; - LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", m_active->getName().c_str())); - } - else if (m_y > ay + ah - 1) { - m_y = ay + ah - 1; - LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", m_active->getName().c_str())); - } - - // warp cursor if it moved. - if (m_x != xOld || m_y != yOld) { - LOG((CLOG_DEBUG2 "move on %s to %d,%d", m_active->getName().c_str(), m_x, m_y)); - m_active->mouseMove(m_x, m_y); - } - } -} - -void -CServer::onMouseWheel(SInt32 delta) -{ - LOG((CLOG_DEBUG1 "onMouseWheel %+d", delta)); - CLock lock(&m_mutex); - assert(m_active != NULL); - - // relay - m_active->mouseWheel(delta); + return CEvent::registerTypeOnce(s_disconnectedEvent, + "CServer::disconnected"); } bool @@ -915,8 +284,34 @@ CServer::onCommandKey(KeyID /*id*/, KeyModifierMask /*mask*/, bool /*down*/) return false; } +CString +CServer::getName(const IClient* client) const +{ + return m_config.getCanonicalName(client->getName()); +} + +UInt32 +CServer::getActivePrimarySides() const +{ + CString primaryName = getName(m_primaryClient); + UInt32 sides = 0; + if (!m_config.getNeighbor(primaryName, kLeft).empty()) { + sides |= kLeftMask; + } + if (!m_config.getNeighbor(primaryName, kRight).empty()) { + sides |= kRightMask; + } + if (!m_config.getNeighbor(primaryName, kTop).empty()) { + sides |= kTopMask; + } + if (!m_config.getNeighbor(primaryName, kBottom).empty()) { + sides |= kBottomMask; + } + return sides; +} + bool -CServer::isLockedToScreenNoLock() const +CServer::isLockedToScreen() const { // locked if scroll-lock is toggled on if ((m_primaryClient->getToggleMask() & KeyModifierScrollLock) != 0) { @@ -933,11 +328,20 @@ CServer::isLockedToScreenNoLock() const return false; } +SInt32 +CServer::getJumpZoneSize(IClient* client) const +{ + if (client == m_primaryClient) { + return m_primaryClient->getJumpZoneSize(); + } + else { + return 0; + } +} + void CServer::switchScreen(IClient* dst, SInt32 x, SInt32 y, bool forScreensaver) { - // note -- must be locked on entry - assert(dst != NULL); #ifndef NDEBUG { @@ -948,10 +352,10 @@ CServer::switchScreen(IClient* dst, SInt32 x, SInt32 y, bool forScreensaver) #endif assert(m_active != NULL); - LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", m_active->getName().c_str(), dst->getName().c_str(), x, y)); + LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", getName(m_active).c_str(), getName(dst).c_str(), x, y)); // stop waiting to switch - clearSwitchState(); + stopSwitch(); // record new position m_x = x; @@ -973,11 +377,9 @@ CServer::switchScreen(IClient* dst, SInt32 x, SInt32 y, bool forScreensaver) if (m_active == m_primaryClient) { for (ClipboardID id = 0; id < kClipboardEnd; ++id) { CClipboardInfo& clipboard = m_clipboards[id]; - if (clipboard.m_clipboardOwner == m_primaryClient->getName()) { - CString clipboardData; - m_primaryClient->getClipboard(id, clipboardData); - onClipboardChangedNoLock(id, - clipboard.m_clipboardSeqNum, clipboardData); + if (clipboard.m_clipboardOwner == getName(m_primaryClient)) { + onClipboardChanged(m_primaryClient, + id, clipboard.m_clipboardSeqNum); } } } @@ -1011,7 +413,7 @@ CServer::getNeighbor(IClient* src, EDirection dir) const assert(src != NULL); // get source screen name - CString srcName = src->getName(); + CString srcName = getName(src); assert(!srcName.empty()); LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str())); @@ -1087,7 +489,7 @@ CServer::getNeighbor(IClient* src, if (x >= 0) { break; } - LOG((CLOG_DEBUG2 "skipping over screen %s", dst->getName().c_str())); + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); dst = getNeighbor(lastGoodScreen, srcSide); } assert(lastGoodScreen != NULL); @@ -1103,7 +505,7 @@ CServer::getNeighbor(IClient* src, if (x < dw) { break; } - LOG((CLOG_DEBUG2 "skipping over screen %s", dst->getName().c_str())); + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); dst = getNeighbor(lastGoodScreen, srcSide); } assert(lastGoodScreen != NULL); @@ -1119,7 +521,7 @@ CServer::getNeighbor(IClient* src, if (y >= 0) { break; } - LOG((CLOG_DEBUG2 "skipping over screen %s", dst->getName().c_str())); + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); dst = getNeighbor(lastGoodScreen, srcSide); } assert(lastGoodScreen != NULL); @@ -1135,7 +537,7 @@ CServer::getNeighbor(IClient* src, if (y < sh) { break; } - LOG((CLOG_DEBUG2 "skipping over screen %s", dst->getName().c_str())); + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); dst = getNeighbor(lastGoodScreen, srcSide); } assert(lastGoodScreen != NULL); @@ -1152,30 +554,30 @@ CServer::getNeighbor(IClient* src, // a neighbor (i.e. an asymmetrical side) then we don't need to // move inwards because that side can't provoke a jump. if (dst == m_primaryClient) { - const CString dstName(dst->getName()); + const CString dstName(getName(dst)); switch (srcSide) { case kLeft: if (!m_config.getNeighbor(dstName, kRight).empty() && - x > dx + dw - 1 - dst->getJumpZoneSize()) - x = dx + dw - 1 - dst->getJumpZoneSize(); + x > dx + dw - 1 - getJumpZoneSize(dst)) + x = dx + dw - 1 - getJumpZoneSize(dst); break; case kRight: if (!m_config.getNeighbor(dstName, kLeft).empty() && - x < dx + dst->getJumpZoneSize()) - x = dx + dst->getJumpZoneSize(); + x < dx + getJumpZoneSize(dst)) + x = dx + getJumpZoneSize(dst); break; case kTop: if (!m_config.getNeighbor(dstName, kBottom).empty() && - y > dy + dh - 1 - dst->getJumpZoneSize()) - y = dy + dh - 1 - dst->getJumpZoneSize(); + y > dy + dh - 1 - getJumpZoneSize(dst)) + y = dy + dh - 1 - getJumpZoneSize(dst); break; case kBottom: if (!m_config.getNeighbor(dstName, kTop).empty() && - y < dy + dst->getJumpZoneSize()) - y = dy + dst->getJumpZoneSize(); + y < dy + getJumpZoneSize(dst)) + y = dy + getJumpZoneSize(dst); break; } } @@ -1226,14 +628,14 @@ CServer::getNeighbor(IClient* src, bool CServer::isSwitchOkay(IClient* newScreen, EDirection dir, SInt32 x, SInt32 y) { - LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", m_active->getName().c_str(), CConfig::dirName(dir))); + LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", getName(m_active).c_str(), CConfig::dirName(dir))); // is there a neighbor? if (newScreen == NULL) { // there's no neighbor. we don't want to switch and we don't // want to try to switch later. LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(dir))); - clearSwitchState(); + stopSwitch(); return false; } @@ -1251,554 +653,145 @@ CServer::isSwitchOkay(IClient* newScreen, EDirection dir, SInt32 x, SInt32 y) // is this a double tap and do we care? if (!allowSwitch && m_switchTwoTapDelay > 0.0) { - if (isNewDirection || !m_switchTwoTapEngaged) { - // tapping a different or new edge. prepare for second tap. - preventSwitch = true; - m_switchTwoTapEngaged = true; - m_switchTwoTapArmed = false; - m_switchTwoTapTimer.reset(); - LOG((CLOG_DEBUG1 "waiting for second tap")); + if (isNewDirection || + !isSwitchTwoTapStarted() || !shouldSwitchTwoTap()) { + // tapping a different or new edge or second tap not + // fast enough. prepare for second tap. + preventSwitch = true; + startSwitchTwoTap(); } else { - // second tap if we were armed. if soon enough then switch. - if (m_switchTwoTapArmed && - m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay) { - allowSwitch = true; - } - else { - // not fast enough. reset the clock. - preventSwitch = true; - m_switchTwoTapEngaged = true; - m_switchTwoTapArmed = false; - m_switchTwoTapTimer.reset(); - LOG((CLOG_DEBUG1 "waiting for second tap")); - } + // got second tap + allowSwitch = true; } } // if waiting before a switch then prepare to switch later if (!allowSwitch && m_switchWaitDelay > 0.0) { - if (isNewDirection || !m_switchWaitEngaged) { - m_switchWaitEngaged = true; - m_switchWaitX = x; - m_switchWaitY = y; - m_switchWaitTimer = m_primaryClient->addOneShotTimer( - m_switchWaitDelay); - LOG((CLOG_DEBUG1 "waiting to switch")); + if (isNewDirection || !isSwitchWaitStarted()) { + startSwitchWait(x, y); } preventSwitch = true; } - // ignore if mouse is locked to screen - if (!preventSwitch && isLockedToScreenNoLock()) { + // ignore if mouse is locked to screen and don't try to switch later + if (!preventSwitch && isLockedToScreen()) { LOG((CLOG_DEBUG1 "locked to screen")); preventSwitch = true; - - // don't try to switch later. it's possible that we might - // not be locked to the screen when the wait delay expires - // and could switch then but we'll base the decision on - // when the user first attempts the switch. this also - // ensures that all switch tests are using the same - clearSwitchState(); + stopSwitch(); } return !preventSwitch; } void -CServer::onNoSwitch(bool inTapZone) +CServer::noSwitch(SInt32 x, SInt32 y) +{ + armSwitchTwoTap(x, y); + stopSwitchWait(); +} + +void +CServer::stopSwitch() +{ + if (m_switchScreen != NULL) { + m_switchScreen = NULL; + m_switchDir = kNoDirection; + stopSwitchTwoTap(); + stopSwitchWait(); + } +} + +void +CServer::startSwitchTwoTap() +{ + m_switchTwoTapEngaged = true; + m_switchTwoTapArmed = false; + m_switchTwoTapTimer.reset(); + LOG((CLOG_DEBUG1 "waiting for second tap")); +} + +void +CServer::armSwitchTwoTap(SInt32 x, SInt32 y) { if (m_switchTwoTapEngaged) { if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) { // second tap took too long. disengage. - m_switchTwoTapEngaged = false; - m_switchTwoTapArmed = false; + stopSwitchTwoTap(); } - else if (!inTapZone) { - // we've moved away from the edge and there's still - // time to get back for a double tap. - m_switchTwoTapArmed = true; - } - } - - // once the mouse moves away from the edge we no longer want to - // switch after a delay. - m_switchWaitEngaged = false; -} - -void -CServer::clearSwitchState() -{ - if (m_switchScreen != NULL) { - m_switchDir = kNoDirection; - m_switchScreen = NULL; - m_switchWaitEngaged = false; - m_switchTwoTapEngaged = false; - } -} - -void -CServer::closeClients(const CConfig& config) -{ - CThreadList threads; - { - CLock lock(&m_mutex); - - // get the set of clients that are connected but are being - // dropped from the configuration (or who's canonical name - // is changing) and tell them to disconnect. note that - // since m_clientThreads doesn't include a thread for the - // primary client we will not close it. - for (CClientThreadList::iterator - index = m_clientThreads.begin(); - index != m_clientThreads.end(); ) { - const CString& name = index->first; - if (!config.isCanonicalName(name)) { - // lookup IClient with name - CClientList::const_iterator index2 = m_clients.find(name); - assert(index2 != m_clients.end()); - - // save the thread and remove it from m_clientThreads - threads.push_back(index->second); - m_clientThreads.erase(index++); - - // close that client - assert(index2->second != m_primaryClient); - index2->second->close(); - - // don't switch to it if we planned to - if (index2->second == m_switchScreen) { - clearSwitchState(); - } + else if (!m_switchTwoTapArmed) { + // still time for a double tap. see if we left the tap + // zone and, if so, arm the two tap. + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + SInt32 tapZone = m_primaryClient->getJumpZoneSize(); + if (tapZone < m_switchTwoTapZone) { + tapZone = m_switchTwoTapZone; } - else { - ++index; + if (x >= ax + tapZone && x < ax + aw - tapZone && + y >= ay + tapZone && y < ay + ah - tapZone) { + m_switchTwoTapArmed = true; } } } - - // wait a moment to allow each client to close its connection - // before we close it (to avoid having our socket enter TIME_WAIT). - if (threads.size() > 0) { - ARCH->sleep(1.0); - } - - // cancel the old client threads - for (CThreadList::iterator index = threads.begin(); - index != threads.end(); ++index) { - index->cancel(); - } - - // wait for old client threads to terminate. we must not hold - // the lock while we do this so those threads can finish any - // calls to this object. - for (CThreadList::iterator index = threads.begin(); - index != threads.end(); ++index) { - index->wait(); - } - - // clean up thread list - reapThreads(); -} - -CThread -CServer::startThread(IJob* job) -{ - CLock lock(&m_mutex); - - // reap completed threads - doReapThreads(m_threads); - - // add new thread to list - CThread thread(job); - m_threads.push_back(thread); - LOG((CLOG_DEBUG1 "started thread 0x%08x", thread.getID())); - return thread; } void -CServer::stopThreads(double timeout) +CServer::stopSwitchTwoTap() { - LOG((CLOG_DEBUG1 "stopping threads")); + m_switchTwoTapEngaged = false; + m_switchTwoTapArmed = false; +} - // cancel the accept client thread to prevent more clients from - // connecting while we're shutting down. - CThread* acceptClientThread; - { - CLock lock(&m_mutex); - acceptClientThread = m_acceptClientThread; - m_acceptClientThread = NULL; - } - if (acceptClientThread != NULL) { - acceptClientThread->cancel(); - acceptClientThread->wait(timeout); - delete acceptClientThread; - } +bool +CServer::isSwitchTwoTapStarted() const +{ + return m_switchTwoTapEngaged; +} - // close all clients (except the primary) - { - CConfig emptyConfig; - closeClients(emptyConfig); - } - - // swap thread list so nobody can mess with it - CThreadList threads; - { - CLock lock(&m_mutex); - threads.swap(m_threads); - } - - // cancel every thread - for (CThreadList::iterator index = threads.begin(); - index != threads.end(); ++index) { - index->cancel(); - } - - // now wait for the threads - CStopwatch timer(true); - while (threads.size() > 0 && (timeout < 0.0 || timer.getTime() < timeout)) { - doReapThreads(threads); - ARCH->sleep(0.01); - } - - // delete remaining threads - for (CThreadList::iterator index = threads.begin(); - index != threads.end(); ++index) { - LOG((CLOG_DEBUG1 "reaped running thread 0x%08x", index->getID())); - } - - LOG((CLOG_DEBUG1 "stopped threads")); +bool +CServer::shouldSwitchTwoTap() const +{ + // this is the second tap if two-tap is armed and this tap + // came fast enough + return (m_switchTwoTapArmed && + m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay); } void -CServer::reapThreads() +CServer::startSwitchWait(SInt32 x, SInt32 y) { - CLock lock(&m_mutex); - doReapThreads(m_threads); + stopSwitchWait(); + m_switchWaitX = x; + m_switchWaitY = y; + m_switchWaitTimer = EVENTQUEUE->newOneShotTimer(m_switchWaitDelay, this); + LOG((CLOG_DEBUG1 "waiting to switch")); } void -CServer::doReapThreads(CThreadList& threads) +CServer::stopSwitchWait() { - for (CThreadList::iterator index = threads.begin(); - index != threads.end(); ) { - if (index->wait(0.0)) { - // thread terminated - LOG((CLOG_DEBUG1 "reaped thread 0x%08x", index->getID())); - index = threads.erase(index); - } - else { - // thread is running - ++index; - } + if (m_switchWaitTimer != NULL) { + EVENTQUEUE->deleteTimer(m_switchWaitTimer); + m_switchWaitTimer = NULL; } } -void -CServer::acceptClients(void*) +bool +CServer::isSwitchWaitStarted() const { - LOG((CLOG_DEBUG1 "starting to wait for clients")); - - IListenSocket* listen = NULL; - try { - // create socket listener - if (m_socketFactory != NULL) { - listen = m_socketFactory->createListen(); - } - assert(listen != NULL); - - // 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& e) { - setStatus(kError, e.what()); - LOG((CLOG_WARN "bind failed: %s", e.what())); - - // give up if we've waited too long - if (timer.getTime() >= m_bindTimeout) { - LOG((CLOG_ERR "waited too long to bind, giving up")); - throw; - } - - // wait a bit before retrying - ARCH->sleep(5.0); - } - } - - // accept connections and begin processing them - setStatus(kRunning); - 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 - startThread(new TMethodJob( - this, &CServer::runClient, socket)); - } - } - catch (XBase& e) { - setStatus(kError, e.what()); - LOG((CLOG_ERR "cannot listen for clients: %s", e.what())); - delete listen; - exitMainLoopWithError(); - } - catch (...) { - setStatus(kNotRunning); - delete listen; - throw; - } -} - -void -CServer::runClient(void* vsocket) -{ - // get the socket pointer from the argument - assert(vsocket != NULL); - IDataSocket* socket = reinterpret_cast(vsocket); - - // create proxy - CClientProxy* proxy = NULL; - try { - proxy = handshakeClient(socket); - if (proxy == NULL) { - delete socket; - return; - } - } - catch (...) { - delete socket; - throw; - } - - // add the connection - try { - addConnection(proxy); - - // save this client's thread - CLock lock(&m_mutex); - m_clientThreads.insert(std::make_pair(proxy->getName(), - CThread::getCurrentThread())); - - // send configuration options - sendOptions(proxy); - } - catch (XDuplicateClient& e) { - // client has duplicate name - LOG((CLOG_WARN "a client with name \"%s\" is already connected", e.getName().c_str())); - try { - CProtocolUtil::writef(proxy->getOutputStream(), kMsgEBusy); - } - catch (XIO&) { - // ignore - } - delete proxy; - delete socket; - return; - } - catch (XUnknownClient& e) { - // client has unknown name - LOG((CLOG_WARN "a client with name \"%s\" is not in the map", e.getName().c_str())); - try { - CProtocolUtil::writef(proxy->getOutputStream(), kMsgEUnknown); - } - catch (XIO&) { - // ignore - } - delete proxy; - delete socket; - return; - } - catch (...) { - delete proxy; - delete socket; - throw; - } - - // activate screen saver on new client if active on the primary screen - { - CLock lock(&m_mutex); - if (m_activeSaver != NULL) { - proxy->screensaver(true); - } - } - - // handle client messages - try { - LOG((CLOG_NOTE "client \"%s\" has connected", proxy->getName().c_str())); - proxy->mainLoop(); - } - catch (XBadClient&) { - // client not behaving - LOG((CLOG_WARN "protocol error from client \"%s\"", proxy->getName().c_str())); - try { - CProtocolUtil::writef(proxy->getOutputStream(), kMsgEBad); - } - catch (XIO&) { - // ignore. client probably aborted the connection. - } - } - catch (XBase& e) { - // misc error - LOG((CLOG_WARN "error communicating with client \"%s\": %s", proxy->getName().c_str(), e.what())); - } - catch (...) { - // mainLoop() was probably cancelled - removeConnection(proxy->getName()); - delete socket; - throw; - } - - // clean up - removeConnection(proxy->getName()); - delete socket; -} - -CClientProxy* -CServer::handshakeClient(IDataSocket* socket) -{ - LOG((CLOG_DEBUG1 "negotiating with new client")); - - CClientProxy* proxy = NULL; - CString name(""); - try { - // give the client a limited time to complete the handshake - CTimerThread timer(30.0); - - // say hello - LOG((CLOG_DEBUG1 "saying hello")); - CProtocolUtil::writef(output, kMsgHello, - kProtocolMajorVersion, - kProtocolMinorVersion); - output->flush(); - - // wait for the reply - LOG((CLOG_DEBUG1 "waiting for hello reply")); - UInt32 n = input->getSize(); - - // limit the maximum length of the hello - if (n > kMaxHelloLength) { - throw XBadClient(); - } - - // get and parse the reply to hello - SInt16 major, minor; - try { - LOG((CLOG_DEBUG1 "parsing hello reply")); - CProtocolUtil::readf(input, kMsgHelloBack, - &major, &minor, &name); - } - catch (XIO&) { - throw XBadClient(); - } - - // disallow invalid version numbers - if (major <= 0 || minor < 0) { - throw XIncompatibleClient(major, minor); - } - - // convert name to canonical form (if any) - if (m_config.isScreen(name)) { - name = m_config.getCanonicalName(name); - } - - // create client proxy for highest version supported by the client - LOG((CLOG_DEBUG1 "creating proxy for client \"%s\" version %d.%d", name.c_str(), major, minor)); - if (major == 1) { - switch (minor) { - case 0: - proxy = new CClientProxy1_0(this, name, input, output); - break; - - case 1: - proxy = new CClientProxy1_1(this, name, input, output); - break; - } - } - - // hangup (with error) if version isn't supported - if (proxy == NULL) { - throw XIncompatibleClient(major, minor); - } - - // negotiate - // FIXME - - // ask and wait for the client's info - LOG((CLOG_DEBUG1 "waiting for info for client \"%s\"", name.c_str())); - proxy->open(); - - return proxy; - } - catch (XIncompatibleClient& e) { - // client is incompatible - LOG((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor())); - try { - CProtocolUtil::writef(output, kMsgEIncompatible, - kProtocolMajorVersion, kProtocolMinorVersion); - } - catch (XIO&) { - // ignore - } - } - catch (XBadClient&) { - // client not behaving - LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str())); - try { - CProtocolUtil::writef(output, kMsgEBad); - } - catch (XIO&) { - // ignore. client probably aborted the connection. - } - } - catch (XBase& e) { - // misc error - LOG((CLOG_WARN "error communicating with client \"%s\": %s", name.c_str(), e.what())); - } - catch (...) { - // probably timed out - if (proxy != NULL) { - delete proxy; - } - else if (own) { - delete input; - delete output; - } - throw; - } - - // failed - if (proxy != NULL) { - delete proxy; - } - else if (own) { - delete input; - delete output; - } - - return NULL; + return (m_switchWaitTimer != NULL); } void CServer::sendOptions(IClient* client) const { - // note -- must be locked on entry - COptionsList optionsList; // look up options for client const CConfig::CScreenOptions* options = - m_config.getOptions(client->getName()); - if (options != NULL && options->size() > 0) { + m_config.getOptions(getName(client)); + if (options != NULL) { // convert options to a more convenient form for sending optionsList.reserve(2 * options->size()); for (CConfig::CScreenOptions::const_iterator index = options->begin(); @@ -1810,7 +803,7 @@ CServer::sendOptions(IClient* client) const // look up global options options = m_config.getOptions(""); - if (options != NULL && options->size() > 0) { + if (options != NULL) { // convert options to a more convenient form for sending optionsList.reserve(optionsList.size() + 2 * options->size()); for (CConfig::CScreenOptions::const_iterator index = options->begin(); @@ -1825,180 +818,762 @@ CServer::sendOptions(IClient* client) const } void -CServer::openPrimaryScreen() +CServer::processOptions() { - assert(m_primaryClient == NULL); - - // reset sequence number - m_seqNum = 0; - - // canonicalize the primary screen name - CString primaryName = m_config.getCanonicalName(getPrimaryScreenName()); - if (primaryName.empty()) { - throw XUnknownClient(getPrimaryScreenName()); + const CConfig::CScreenOptions* options = m_config.getOptions(""); + if (options == NULL) { + return; } - // clear clipboards - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - CClipboardInfo& clipboard = m_clipboards[id]; - clipboard.m_clipboardOwner = primaryName; - clipboard.m_clipboardSeqNum = m_seqNum; - if (clipboard.m_clipboard.open(0)) { - clipboard.m_clipboard.empty(); - clipboard.m_clipboard.close(); + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + const OptionID id = index->first; + const OptionValue value = index->second; + if (id == kOptionScreenSwitchDelay) { + m_switchWaitDelay = 1.0e-3 * static_cast(value); + if (m_switchWaitDelay < 0.0) { + m_switchWaitDelay = 0.0; + } + stopSwitchWait(); } - clipboard.m_clipboardData = clipboard.m_clipboard.marshall(); + else if (id == kOptionScreenSwitchTwoTap) { + m_switchTwoTapDelay = 1.0e-3 * static_cast(value); + if (m_switchTwoTapDelay < 0.0) { + m_switchTwoTapDelay = 0.0; + } + stopSwitchTwoTap(); + } + } +} + +void +CServer::handleShapeChanged(const CEvent&, void* vclient) +{ + // ignore events from unknown clients + IClient* client = reinterpret_cast(vclient); + if (m_clientSet.count(client) == 0) { + return; } - try { - // create the primary client - m_primaryClient = new CPrimaryClient(m_screenFactory, - this, this, primaryName); - - // add connection - addConnection(m_primaryClient); - m_active = m_primaryClient; - - // open the screen - LOG((CLOG_DEBUG1 "opening primary screen")); - m_primaryClient->open(); - - // tell it about the active sides - m_primaryClient->reconfigure(getActivePrimarySides()); - - // tell primary client about its options - sendOptions(m_primaryClient); + // update the mouse coordinates + if (client == m_active) { + client->getCursorPos(m_x, m_y); } - catch (...) { - // if m_active is NULL then we haven't added the connection - // for the primary client so we don't try to remove it. - if (m_active != NULL) { - removeConnection(primaryName); + LOG((CLOG_INFO "screen \"%s\" shape changed", getName(client).c_str())); + + // handle resolution change to primary screen + if (client == m_primaryClient) { + if (client == m_active) { + onMouseMovePrimary(m_x, m_y); } else { - delete m_primaryClient; + onMouseMoveSecondary(0, 0); } - m_active = NULL; - m_primaryClient = NULL; - throw; } } void -CServer::closePrimaryScreen() +CServer::handleClipboardGrabbed(const CEvent& event, void* vclient) { + // ignore events from unknown clients + IClient* grabber = reinterpret_cast(vclient); + if (m_clientSet.count(grabber) == 0) { + return; + } + const IScreen::CClipboardInfo* info = + reinterpret_cast(event.getData()); + + // ignore grab if sequence number is old. always allow primary + // screen to grab. + CClipboardInfo& clipboard = m_clipboards[info->m_id]; + if (grabber != m_primaryClient && + info->m_sequenceNumber < clipboard.m_clipboardSeqNum) { + LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", getName(grabber).c_str(), info->m_id)); + return; + } + + // mark screen as owning clipboard + LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", getName(grabber).c_str(), info->m_id, clipboard.m_clipboardOwner.c_str())); + clipboard.m_clipboardOwner = getName(grabber); + clipboard.m_clipboardSeqNum = info->m_sequenceNumber; + + // clear the clipboard data (since it's not known at this point) + if (clipboard.m_clipboard.open(0)) { + clipboard.m_clipboard.empty(); + clipboard.m_clipboard.close(); + } + clipboard.m_clipboardData = clipboard.m_clipboard.marshall(); + + // tell all other screens to take ownership of clipboard. tell the + // grabber that it's clipboard isn't dirty. + for (CClientList::iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + IClient* client = index->second; + if (client == grabber) { + client->setClipboardDirty(info->m_id, false); + } + else { + client->grabClipboard(info->m_id); + } + } +} + +void +CServer::handleClipboardChanged(const CEvent& event, void* vclient) +{ + // ignore events from unknown clients + IClient* sender = reinterpret_cast(vclient); + if (m_clientSet.count(sender) == 0) { + return; + } + const IScreen::CClipboardInfo* info = + reinterpret_cast(event.getData()); + onClipboardChanged(sender, info->m_id, info->m_sequenceNumber); +} + +void +CServer::handleKeyDownEvent(const CEvent& event, void*) +{ + IPlatformScreen::CKeyInfo* info = + reinterpret_cast(event.getData()); + onKeyDown(info->m_key, info->m_mask, info->m_button); +} + +void +CServer::handleKeyUpEvent(const CEvent& event, void*) +{ + IPlatformScreen::CKeyInfo* info = + reinterpret_cast(event.getData()); + onKeyUp(info->m_key, info->m_mask, info->m_button); +} + +void +CServer::handleKeyRepeatEvent(const CEvent& event, void*) +{ + IPlatformScreen::CKeyInfo* info = + reinterpret_cast(event.getData()); + onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button); +} + +void +CServer::handleButtonDownEvent(const CEvent& event, void*) +{ + IPlatformScreen::CButtonInfo* info = + reinterpret_cast(event.getData()); + onMouseDown(info->m_button); +} + +void +CServer::handleButtonUpEvent(const CEvent& event, void*) +{ + IPlatformScreen::CButtonInfo* info = + reinterpret_cast(event.getData()); + onMouseUp(info->m_button); +} + +void +CServer::handleMotionPrimaryEvent(const CEvent& event, void*) +{ + IPlatformScreen::CMotionInfo* info = + reinterpret_cast(event.getData()); + onMouseMovePrimary(info->m_x, info->m_y); +} + +void +CServer::handleMotionSecondaryEvent(const CEvent& event, void*) +{ + IPlatformScreen::CMotionInfo* info = + reinterpret_cast(event.getData()); + onMouseMoveSecondary(info->m_x, info->m_y); +} + +void +CServer::handleWheelEvent(const CEvent& event, void*) +{ + IPlatformScreen::CWheelInfo* info = + reinterpret_cast(event.getData()); + onMouseWheel(info->m_wheel); +} + +void +CServer::handleScreensaverActivatedEvent(const CEvent& event, void*) +{ + onScreensaver(true); +} + +void +CServer::handleScreensaverDeactivatedEvent(const CEvent& event, void*) +{ + onScreensaver(false); +} + +void +CServer::handleSwitchWaitTimeout(const CEvent&, void*) +{ + // ignore if mouse is locked to screen + if (isLockedToScreen()) { + LOG((CLOG_DEBUG1 "locked to screen")); + stopSwitch(); + return; + } + + // switch screen + switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false); +} + +void +CServer::handleClientDisconnected(const CEvent&, void* vclient) +{ + // client has disconnected. it might be an old client or an + // active client. we don't care so just handle it both ways. + IClient* client = reinterpret_cast(vclient); + removeActiveClient(client); + removeOldClient(client); + delete client; +} + +void +CServer::handleClientCloseTimeout(const CEvent&, void* vclient) +{ + // client took too long to disconnect. just dump it. + IClient* client = reinterpret_cast(vclient); + LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str())); + removeOldClient(client); + delete client; +} + +void +CServer::onClipboardChanged(IClient* sender, ClipboardID id, UInt32 seqNum) +{ + 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 (missequenced)", clipboard.m_clipboardOwner.c_str(), id)); + return; + } + + // should be the expected client + assert(sender == m_clients.find(clipboard.m_clipboardOwner)->second); + + // get data + sender->getClipboard(id, &clipboard.m_clipboard); + + // ignore if data hasn't changed + CString data = clipboard.m_clipboard.marshall(); + if (data == clipboard.m_clipboardData) { + LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id)); + return; + } + + // got new data + LOG((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id)); + clipboard.m_clipboardData = data; + + // tell all clients except the sender that the clipboard is dirty + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + IClient* client = index->second; + client->setClipboardDirty(id, client != sender); + } + + // send the new clipboard to the active screen + m_active->setClipboard(id, clipboard.m_clipboardData); +} + +void +CServer::onScreensaver(bool activated) +{ + LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated")); + + if (activated) { + // save current screen and position + m_activeSaver = m_active; + m_xSaver = m_x; + m_ySaver = m_y; + + // jump to primary screen + if (m_active != m_primaryClient) { + switchScreen(m_primaryClient, 0, 0, true); + } + } + else { + // jump back to previous screen and position. we must check + // that the position is still valid since the screen may have + // changed resolutions while the screen saver was running. + if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) { + // check position + IClient* screen = m_activeSaver; + SInt32 x, y, w, h; + screen->getShape(x, y, w, h); + SInt32 zoneSize = getJumpZoneSize(screen); + if (m_xSaver < x + zoneSize) { + m_xSaver = x + zoneSize; + } + else if (m_xSaver >= x + w - zoneSize) { + m_xSaver = x + w - zoneSize - 1; + } + if (m_ySaver < y + zoneSize) { + m_ySaver = y + zoneSize; + } + else if (m_ySaver >= y + h - zoneSize) { + m_ySaver = y + h - zoneSize - 1; + } + + // jump + switchScreen(screen, m_xSaver, m_ySaver, false); + } + + // reset state + m_activeSaver = NULL; + } + + // send message to all clients + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + IClient* client = index->second; + client->screensaver(activated); + } +} + +void +CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button) +{ + LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button)); + assert(m_active != NULL); + + // handle command keys + if (onCommandKey(id, mask, true)) { + return; + } + + // relay + m_active->keyDown(id, mask, button); +} + +void +CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button) +{ + LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button)); + assert(m_active != NULL); + + // handle command keys + if (onCommandKey(id, mask, false)) { + return; + } + + // relay + m_active->keyUp(id, mask, button); +} + +void +CServer::onKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button)); + assert(m_active != NULL); + + // handle command keys + if (onCommandKey(id, mask, false)) { + onCommandKey(id, mask, true); + return; + } + + // relay + m_active->keyRepeat(id, mask, count, button); +} + +void +CServer::onMouseDown(ButtonID id) +{ + LOG((CLOG_DEBUG1 "onMouseDown id=%d", id)); + assert(m_active != NULL); + + // relay + m_active->mouseDown(id); +} + +void +CServer::onMouseUp(ButtonID id) +{ + LOG((CLOG_DEBUG1 "onMouseUp id=%d", id)); + assert(m_active != NULL); + + // relay + m_active->mouseUp(id); +} + +bool +CServer::onMouseMovePrimary(SInt32 x, SInt32 y) +{ + LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y)); + + // mouse move on primary (server's) screen assert(m_primaryClient != NULL); + assert(m_active == m_primaryClient); - // close the primary screen - try { - LOG((CLOG_DEBUG1 "closing primary screen")); - m_primaryClient->close(); + // save position + m_x = x; + m_y = y; + + // get screen shape + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + SInt32 zoneSize = getJumpZoneSize(m_active); + + // see if we should change screens + EDirection dir; + if (x < ax + zoneSize) { + x -= zoneSize; + dir = kLeft; } - catch (...) { - // ignore + else if (x >= ax + aw - zoneSize) { + x += zoneSize; + dir = kRight; + } + else if (y < ay + zoneSize) { + y -= zoneSize; + dir = kTop; + } + else if (y >= ay + ah - zoneSize) { + y += zoneSize; + dir = kBottom; + } + else { + // still on local screen + noSwitch(x, y); + return false; } - // remove connection - removeConnection(m_primaryClient->getName()); - m_primaryClient = NULL; + // get jump destination + IClient* newScreen = getNeighbor(m_active, dir, x, y); + + // should we switch or not? + if (isSwitchOkay(newScreen, dir, x, y)) { + // switch screen + switchScreen(newScreen, x, y, false); + return true; + } + else { + return false; + } } void -CServer::addConnection(IClient* client) +CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy) { - assert(client != NULL); + LOG((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy)); - LOG((CLOG_DEBUG "adding connection \"%s\"", client->getName().c_str())); - - { - CLock lock(&m_mutex); - - // name must be in our configuration - if (!m_config.isScreen(client->getName())) { - throw XUnknownClient(client->getName()); - } - - // can only have one screen with a given name at any given time - if (m_clients.count(client->getName()) != 0) { - throw XDuplicateClient(client->getName()); - } - - // save screen info - m_clients.insert(std::make_pair(client->getName(), client)); - LOG((CLOG_DEBUG "added connection \"%s\"", client->getName().c_str())); + // mouse move on secondary (client's) screen + assert(m_active != NULL); + if (m_active == m_primaryClient) { + // 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; + + // get screen shape + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + + // find direction of neighbor and get the neighbor + bool jump = true; + IClient* newScreen; + do { + EDirection dir; + if (m_x < ax) { + dir = kLeft; + } + else if (m_x > ax + aw - 1) { + dir = kRight; + } + else if (m_y < ay) { + dir = kTop; + } + else if (m_y > ay + ah - 1) { + dir = kBottom; + } + else { + // we haven't left the screen + newScreen = m_active; + jump = false; + + // if waiting and mouse is not on the border we're waiting + // on then stop waiting. also if it's not on the border + // then arm the double tap. + if (m_switchScreen != NULL) { + bool clearWait; + SInt32 zoneSize = m_primaryClient->getJumpZoneSize(); + switch (m_switchDir) { + case kLeft: + clearWait = (m_x >= ax + zoneSize); + break; + + case kRight: + clearWait = (m_x <= ax + aw - 1 - zoneSize); + break; + + case kTop: + clearWait = (m_y >= ay + zoneSize); + break; + + case kBottom: + clearWait = (m_y <= ay + ah - 1 + zoneSize); + break; + + default: + clearWait = false; + break; + } + if (clearWait) { + // still on local screen + noSwitch(m_x, m_y); + } + } + + // skip rest of block + break; + } + + // try to switch screen. get the neighbor. + newScreen = getNeighbor(m_active, dir, m_x, m_y); + + // see if we should switch + if (!isSwitchOkay(newScreen, dir, m_x, m_y)) { + newScreen = m_active; + jump = false; + } + } while (false); + + if (jump) { + // switch screens + switchScreen(newScreen, m_x, m_y, false); + } + else { + // same screen. clamp mouse to edge. + m_x = xOld + dx; + m_y = yOld + dy; + if (m_x < ax) { + m_x = ax; + LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", getName(m_active).c_str())); + } + else if (m_x > ax + aw - 1) { + m_x = ax + aw - 1; + LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", getName(m_active).c_str())); + } + if (m_y < ay) { + m_y = ay; + LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", getName(m_active).c_str())); + } + else if (m_y > ay + ah - 1) { + m_y = ay + ah - 1; + LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", getName(m_active).c_str())); + } + + // warp cursor if it moved. + if (m_x != xOld || m_y != yOld) { + LOG((CLOG_DEBUG2 "move on %s to %d,%d", getName(m_active).c_str(), m_x, m_y)); + m_active->mouseMove(m_x, m_y); + } } - runStatusJobs(); } void -CServer::removeConnection(const CString& name) +CServer::onMouseWheel(SInt32 delta) { - LOG((CLOG_DEBUG "removing connection \"%s\"", name.c_str())); - bool updateStatus; - { - CLock lock(&m_mutex); + LOG((CLOG_DEBUG1 "onMouseWheel %+d", delta)); + assert(m_active != NULL); - // find client - CClientList::iterator index = m_clients.find(name); - assert(index != m_clients.end()); + // relay + m_active->mouseWheel(delta); +} - // if this is active screen then we have to jump off of it - IClient* active = (m_activeSaver != NULL) ? m_activeSaver : m_active; - if (active == index->second && active != m_primaryClient) { - // record new position (center of primary screen) - m_primaryClient->getCursorCenter(m_x, m_y); - - // stop waiting to switch if we were - if (active == m_switchScreen) { - clearSwitchState(); - } - - // don't notify active screen since it probably already - // disconnected. - LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", active->getName().c_str(), m_primaryClient->getName().c_str(), m_x, m_y)); - - // cut over - m_active = m_primaryClient; - - // enter new screen (unless we already have because of the - // screen saver) - if (m_activeSaver == NULL) { - m_primaryClient->enter(m_x, m_y, m_seqNum, - m_primaryClient->getToggleMask(), false); - } - } - - // if this screen had the cursor when the screen saver activated - // then we can't switch back to it when the screen saver - // deactivates. - if (m_activeSaver == index->second) { - m_activeSaver = NULL; - } - - // done with client - delete index->second; - m_clients.erase(index); - - // remove any thread for this client - m_clientThreads.erase(name); - - updateStatus = (m_clients.size() <= 1); +bool +CServer::addClient(IClient* client) +{ + CString name = getName(client); + if (m_clients.count(name) != 0) { + return false; } - if (updateStatus) { - runStatusJobs(); + // add event handlers + EVENTQUEUE->adoptHandler(IScreen::getShapeChangedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleShapeChanged, client)); + EVENTQUEUE->adoptHandler(IScreen::getClipboardGrabbedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleClipboardGrabbed, client)); + EVENTQUEUE->adoptHandler(IScreen::getClipboardChangedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleClipboardChanged, client)); + + // add to list + m_clientSet.insert(client); + m_clients.insert(std::make_pair(name, client)); + return true; +} + +bool +CServer::removeClient(IClient* client) +{ + // return false if not in list + CClientList::iterator i = m_clients.find(getName(client)); + if (i == m_clients.end()) { + return false; + } + + // remove event handlers + EVENTQUEUE->removeHandler(IScreen::getShapeChangedEvent(), + client->getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getClipboardGrabbedEvent(), + client->getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getClipboardChangedEvent(), + client->getEventTarget()); + + // remove from list + m_clients.erase(i); + m_clientSet.erase(client); + return true; +} + +void +CServer::closeClient(IClient* client, const char* msg) +{ + assert(client != m_primaryClient); + assert(msg != NULL); + + // send message to client. this message should cause the client + // to disconnect. we add this client to the closed client list + // and install a timer to remove the client if it doesn't respond + // quickly enough. we also remove the client from the active + // client list since we're not going to listen to it anymore. + // note that this method also works on clients that are not in + // the m_clients list. adoptClient() may call us with such a + // client. + LOG((CLOG_NOTE "disconnecting client \"%s\"", getName(client).c_str())); + + // send message + // FIXME -- avoid type cast (kinda hard, though) + ((CClientProxy*)client)->close(msg); + + // install timer. wait timeout seconds for client to close. + double timeout = 5.0; + CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(5.0, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, + new TMethodEventJob(this, + &CServer::handleClientCloseTimeout, client)); + + // move client to closing list + removeClient(client); + m_oldClients.insert(std::make_pair(client, timer)); + + // if this client is the active screen then we have to + // jump off of it + forceLeaveClient(client); +} + +void +CServer::closeClients(const CConfig& config) +{ + // collect the clients that are connected but are being dropped + // from the configuration (or who's canonical name is changing). + typedef std::set CRemovedClients; + CRemovedClients removed; + for (CClientList::iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + if (!config.isCanonicalName(index->first)) { + removed.insert(index->second); + } + } + + // don't close the primary client + removed.erase(m_primaryClient); + + // now close them. we collect the list then close in two steps + // because closeClient() modifies the collection we iterate over. + for (CRemovedClients::iterator index = removed.begin(); + index != removed.end(); ++index) { + closeClient(*index, kMsgCClose); } } - -// -// CServer::CClipboardInfo -// - -CString -CServer::XServerRethrow::getWhat() const throw() +void +CServer::removeActiveClient(IClient* client) { - return format("XServerRethrow", "child thread failed"); + if (removeClient(client)) { + forceLeaveClient(client); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + if (m_clients.size() == 1 && m_oldClients.empty()) { + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this)); + } + } +} + +void +CServer::removeOldClient(IClient* client) +{ + COldClients::iterator i = m_oldClients.find(client); + if (i != m_oldClients.end()) { + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + EVENTQUEUE->removeHandler(CEvent::kTimer, i->second); + EVENTQUEUE->deleteTimer(i->second); + m_oldClients.erase(i); + if (m_clients.size() == 1 && m_oldClients.empty()) { + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this)); + } + } +} + +void +CServer::forceLeaveClient(IClient* client) +{ + IClient* active = (m_activeSaver != NULL) ? m_activeSaver : m_active; + if (active == client) { + // record new position (center of primary screen) + m_primaryClient->getCursorCenter(m_x, m_y); + + // stop waiting to switch to this client + if (active == m_switchScreen) { + stopSwitch(); + } + + // don't notify active screen since it has probably already + // disconnected. + LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", getName(active).c_str(), getName(m_primaryClient).c_str(), m_x, m_y)); + + // cut over + m_active = m_primaryClient; + + // enter new screen (unless we already have because of the + // screen saver) + if (m_activeSaver == NULL) { + m_primaryClient->enter(m_x, m_y, m_seqNum, + m_primaryClient->getToggleMask(), false); + } + } + + // if this screen had the cursor when the screen saver activated + // then we can't switch back to it when the screen saver + // deactivates. + if (m_activeSaver == client) { + m_activeSaver = NULL; + } } @@ -2014,259 +1589,3 @@ CServer::CClipboardInfo::CClipboardInfo() : { // do nothing } - -// --- transitional --- - - -void -CServer::clientConnecting(const CEvent&, void*) -{ - // accept client connection - IDataSocket* socket = m_listen->accept(); - if (socket == NULL) { - return; - } - - LOG((CLOG_NOTE "accepted client connection")); - - // filter socket's streams then wrap the result in a new socket - IInputStream* input = socket->getInputStream(); - IOutputStream* output = socket->getOutputStream(); - bool own = false; - if (m_streamFilterFactory != NULL) { - input = m_streamFilterFactory->createInput(input, own); - output = m_streamFilterFactory->createOutput(output, own); - own = true; - } - input = new CInputPacketStream(input, own); - output = new COutputPacketStream(output, own); - socket = new CDataSocket(socket, - new CInputOutputStream(input, output, true)); - -// FIXME -- may want to move these event handlers (but not the -// handshake timer?) and the handshake into a new proxy object. -// we save this proxy as a provisional connection. it calls -// back to us (or maybe sends an event) to notify of failure or -// success. failure is socket failure or protocol error. -// success returns a new proxy of the appropriate version. we -// need to verify the validity of the client's name then remove -// the provisional connection and install a true connection. -// if we keep the timer then when it expires we just remove and -// delete the provisional connection. if we install a true -// connection then we remove the timer. - CProvisionalClient* client; - try { - client = new CProvisionalClient(this, socket); - } - catch (XBase&) { - delete socket; - // handle error - return; - } - - // start timer for handshake - CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(30.0, client); - - // add client to client map - m_provisional.insert(client, timer); -} - -void -CServer::recvClientHello(const CEvent&, void* vsocket) -{ - IDataSocket* socket = reinterpret_cast(vsocket); - - LOG((CLOG_DEBUG1 "parsing hello reply")); - - CClientProxy* proxy = NULL; - CString name(""); - try { - // limit the maximum length of the hello - UInt32 n = socket->getInputStream()->getSize(); - if (n > kMaxHelloLength) { - LOG((CLOG_DEBUG1 "hello reply too long")); - throw XBadClient(); - } - - // parse the reply to hello - SInt16 major, minor; - CProtocolUtil::readf(socket->getInputStream(), kMsgHelloBack, - &major, &minor, &name); - - // disallow invalid version numbers - if (major <= 0 || minor < 0) { - throw XIncompatibleClient(major, minor); - } - - // convert name to canonical form (if any) -// FIXME -- need lock? - if (m_config.isScreen(name)) { - name = m_config.getCanonicalName(name); - } - - // create client proxy for highest version supported by the client - LOG((CLOG_DEBUG1 "creating proxy for client \"%s\" version %d.%d", name.c_str(), major, minor)); - if (major == 1) { - switch (minor) { - case 0: -// FIXME -- should pass socket, not input and output - proxy = new CClientProxy1_0(this, name, input, output); - break; - - case 1: -// FIXME -- should pass socket, not input and output - proxy = new CClientProxy1_1(this, name, input, output); - break; - } - } - - // hangup (with error) if version isn't supported - if (proxy == NULL) { - throw XIncompatibleClient(major, minor); - } - - // the proxy now owns the socket - socket = NULL; - - // add provisional client connection. this also checks if - // the client's name is okay (i.e. in the map and not in use). - addProvisionalClient(proxy); - - // negotiate - // FIXME - - // request the client's info and install a handler for it. note - // that the handshake timer is still going. - EVENTQUEUE->adoptHandler(IInputStream::getInputReadyEvent(), - socket->getInputStream(), - new TMethodEventJob(this, - &CServer::recvClientInfo, socket)); - LOG((CLOG_DEBUG1 "request info for client \"%s\"", name.c_str())); -// FIXME -- maybe should send this request some other way. -// could have the proxy itself handle the input. that makes sense -// but will have to check if that'll work. for one thing, the proxy -// will have to inform this object of any errors. it takes this -// object as a parameter so it probably can do that. -/* - proxy->open(); -*/ - return; - } - catch (XDuplicateClient& e) { - // client has duplicate name - LOG((CLOG_WARN "a client with name \"%s\" is already connected", e.getName().c_str())); - CProtocolUtil::writefNoError(proxy->getOutputStream(), 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::writefNoError(proxy->getOutputStream(), kMsgEUnknown); - } - catch (XIncompatibleClient& e) { - // client is incompatible - LOG((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor())); - CProtocolUtil::writefNoError(socket->getOutputStream(), - kMsgEIncompatible, - kProtocolMajorVersion, kProtocolMinorVersion); - } - catch (XBadClient&) { - // client not behaving - LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str())); - CProtocolUtil::writefNoError(socket->getOutputStream(), kMsgEBad); - } - catch (XIO&) { - // client not behaving - LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str())); - CProtocolUtil::writefNoError(socket->getOutputStream(), kMsgEBad); - } - catch (XBase& e) { - // misc error - LOG((CLOG_WARN "error communicating with client \"%s\": %s", name.c_str(), e.what())); - } - -// FIXME -- really clean up, including event handlers and timer. -// should have a method for this. - delete proxy; - delete socket; -} - -void -CServer::recvClientInfo(const CEvent&, void* vsocket) -{ - // FIXME -- get client's info and proxy becomes first class citizen. - // adopt new input handler aimed at clientInput. remove timer. -} - -void -CServer::clientTimeout(const CEvent&, void* vsocket) -{ - // FIXME -- client failed to connect fast enough. if client is - // already first class then just ignore this (that should never - // happen though because timer events are synthesized in the - // getEvent() so there can't be a race, so don't fuss over - // checking). -} - -void -CServer::clientInput(const CEvent&, void* vsocket) -{ - // FIXME -- client has sent a message -} - -void -CServer::clientDisconnected(const CEvent&, void* vsocket) -{ - // FIXME -- handle disconnection of client -} - -void -CServer::createClientListener() -{ - LOG((CLOG_DEBUG1 "creating socket to listen for clients")); - - assert(m_socketFactory != NULL); - IListenSocket* listen = NULL; - try { - // create socket - listen = m_socketFactory->createListen(); - - // setup event handler - EVENTQUEUE->adoptHandler(IListenSocket::getConnectingEvent(), listen, - new TMethodEventJob(this, - &CServer::clientConnecting)); - - // bind listen address - LOG((CLOG_DEBUG1 "binding listen socket")); - listen->bind(m_config.getSynergyAddress()); - } - catch (XSocketAddressInUse& e) { - if (listen != NULL) { - EVENTQUEUE->removeHandler( - IListenSocket::getConnectingEvent(), listen); - delete listen; - } - setStatus(kError, e.what()); - LOG((CLOG_WARN "bind failed: %s", e.what())); - // FIXME -- throw retry in X seconds object. the caller should - // catch this and optionally wait X seconds and try again. - // alternatively we could install a timer and retry automatically. - throw; - } - catch (...) { - if (listen != NULL) { - EVENTQUEUE->removeHandler( - IListenSocket::getConnectingEvent(), listen); - delete listen; - } -/* FIXME -- set some status and log error - setStatus(kNotRunning); - setStatus(kError, e.what()); - LOG((CLOG_ERR "cannot listen for clients: %s", e.what())); -*/ - throw; - } - - m_listen = listen; - setStatus(kRunning); - LOG((CLOG_DEBUG1 "waiting for client connections")); -} diff --git a/lib/server/CServer.h b/lib/server/CServer.h index f7a9d735..874449e1 100644 --- a/lib/server/CServer.h +++ b/lib/server/CServer.h @@ -15,84 +15,40 @@ #ifndef CSERVER_H #define CSERVER_H -#include "IServer.h" -#include "IPrimaryScreenReceiver.h" #include "CConfig.h" #include "CClipboard.h" -#include "CCondVar.h" -#include "CMutex.h" -#include "CThread.h" -#include "CJobList.h" +#include "ClipboardTypes.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "CEvent.h" #include "CStopwatch.h" -#include "stdlist.h" #include "stdmap.h" +#include "stdset.h" #include "stdvector.h" class CClientProxy; +class CClientProxyUnknown; +class CEventQueueTimer; class CPrimaryClient; class IClient; -class IDataSocket; -class IScreenFactory; -class IServerProtocol; -class ISocketFactory; -class IStreamFilterFactory; //! Synergy server /*! This class implements the top-level server algorithms for synergy. */ -class CServer : public IServer, public IPrimaryScreenReceiver { +class CServer { public: - enum EStatus { - kNotRunning, - kRunning, - kServerNameUnknown, - kError, - kMaxStatus - }; - /*! - The server will look itself up in the configuration using \c serverName - as its name. + Start the server with the configuration \p config and the primary + client (local screen) \p primaryClient. The client retains + ownership of \p primaryClient. */ - CServer(const CString& serverName); + CServer(const CConfig& config, CPrimaryClient* primaryClient); ~CServer(); //! @name manipulators //@{ - //! Open server - /*! - Open the server. Throws XScreenUnavailable if the server's - screen cannot be opened but might be available after some time. - Otherwise throws some other exception if the server's screen or - the server cannot be opened and retrying won't help. - */ - void open(); - - //! Server main loop - /*! - Run server's event loop and return when exitMainLoop() is called. - This must be called between a successful open() and close(). - - (cancellation point) - */ - void mainLoop(); - - //! Exit event loop - /*! - Force mainLoop() to return. This call can return before - mainLoop() does (i.e. asynchronously). This may only be - called between a successful open() and close(). - */ - void exitMainLoop(); - - //! Close server - /*! - Close the server. - */ - void close(); - //! Set configuration /*! Change the server's configuration. Returns true iff the new @@ -101,67 +57,26 @@ public: */ bool setConfig(const CConfig&); - //! Set screen factory + //! Add a client /*! - Sets the factory for creating screens. This must be set before - calling open(). This object takes ownership of the factory. + Adds \p client to the server. The client is adopted and will be + destroyed when the client disconnects or is disconnected. */ - void setScreenFactory(IScreenFactory*); + void adoptClient(IClient* client); - //! Set socket factory + //! Disconnect clients /*! - Sets the factory used to create a socket to connect to the server. - This must be set before calling mainLoop(). This object takes - ownership of the factory. + Disconnect clients. This tells them to disconnect but does not wait + for them to actually do so. The server sends the disconnected event + when they're all disconnected (or immediately if none are connected). + The caller can also just destroy this object to force the disconnection. */ - void setSocketFactory(ISocketFactory*); - - //! Set stream filter factory - /*! - Sets the factory used to filter the socket streams used to - communicate with the server. This object takes ownership - of the factory. - */ - void setStreamFilterFactory(IStreamFilterFactory*); - - //! Add a job to notify of status changes - /*! - The added job is run whenever the server's status changes in - certain externally visible ways. The client keeps ownership - of the job. - */ - void addStatusJob(IJob*); - - //! Remove a job to notify of status changes - /*! - Removes a previously added status notification job. A job can - remove itself when called but must not remove any other jobs. - The client keeps ownership of the job. - */ - void removeStatusJob(IJob*); + void disconnect(); //@} //! @name accessors //@{ - //! Get configuration - /*! - Returns the current configuration. - */ - void getConfig(CConfig*) const; - - //! Get canonical screen name - /*! - Returns the canonical version of a screen name. - */ - CString getCanonicalName(const CString& name) const; - - //! Get name - /*! - Returns the server's name passed to the c'tor - */ - CString getPrimaryScreenName() const; - //! Get number of connected clients /*! Returns the number of connected clients, including the server itself. @@ -174,33 +89,22 @@ public: */ void getClients(std::vector& list) const; - //! Get the status + //! Get error event type /*! - Returns the current status and status message. + Returns the error event type. This is sent when the server fails + for some reason. */ - EStatus getStatus(CString* = NULL) const; + static CEvent::Type getErrorEvent(); + + //! Get disconnected event type + /*! + Returns the disconnected event type. This is sent when all the + clients have disconnected. + */ + static CEvent::Type getDisconnectedEvent(); //@} - // IServer overrides - virtual void onError(); - virtual void onInfoChanged(const CString&, const CClientInfo&); - virtual bool onGrabClipboard(const CString&, ClipboardID, UInt32); - virtual void onClipboardChanged(ClipboardID, UInt32, const CString&); - - // IPrimaryScreenReceiver overrides - virtual void onScreensaver(bool activated); - virtual void onOneShotTimerExpired(UInt32 id); - virtual void onKeyDown(KeyID, KeyModifierMask, KeyButton); - virtual void onKeyUp(KeyID, KeyModifierMask, KeyButton); - virtual void onKeyRepeat(KeyID, KeyModifierMask, - SInt32 count, KeyButton); - virtual void onMouseDown(ButtonID); - virtual void onMouseUp(ButtonID); - virtual bool onMouseMovePrimary(SInt32 x, SInt32 y); - virtual void onMouseMoveSecondary(SInt32 dx, SInt32 dy); - virtual void onMouseWheel(SInt32 delta); - protected: //! Handle special keys /*! @@ -208,36 +112,18 @@ protected: */ bool onCommandKey(KeyID, KeyModifierMask, bool down); - //! Exit event loop and note an error condition - /*! - Force mainLoop() to return by throwing an exception. This call - can return before mainLoop() does (i.e. asynchronously). This - may only be called between a successful open() and close(). - */ - void exitMainLoopWithError(); - private: - typedef std::list CThreadList; - - // notify status jobs of a change - void runStatusJobs() const; - - // set new status - void setStatus(EStatus, const char* msg = NULL); + // get canonical name of client + CString getName(const IClient*) const; // get the sides of the primary screen that have neighbors UInt32 getActivePrimarySides() const; - // handle mouse motion - bool onMouseMovePrimaryNoLock(SInt32 x, SInt32 y); - void onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy); - - // set the clipboard - void onClipboardChangedNoLock(ClipboardID, - UInt32 seqNum, const CString& data); - // returns true iff mouse should be locked to the current screen - bool isLockedToScreenNoLock() const; + bool isLockedToScreen() const; + + // returns the jump zone of the client + SInt32 getJumpZoneSize(IClient*) const; // change the active screen void switchScreen(IClient*, @@ -260,57 +146,98 @@ private: bool isSwitchOkay(IClient* dst, EDirection, SInt32 x, SInt32 y); - // update switch state due to a mouse move that doesn't try to - // switch screens. - void onNoSwitch(bool inTapZone); + // update switch state due to a mouse move at \p x, \p y that + // doesn't switch screens. + void noSwitch(SInt32 x, SInt32 y); - // reset switch wait state - void clearSwitchState(); + // stop switch timers + void stopSwitch(); + + // start two tap switch timer + void startSwitchTwoTap(); + + // arm the two tap switch timer if \p x, \p y is outside the tap zone + void armSwitchTwoTap(SInt32 x, SInt32 y); + + // stop the two tap switch timer + void stopSwitchTwoTap(); + + // returns true iff the two tap switch timer is started + bool isSwitchTwoTapStarted() const; + + // returns true iff should switch because of two tap + bool shouldSwitchTwoTap() const; + + // start delay switch timer + void startSwitchWait(SInt32 x, SInt32 y); + + // stop delay switch timer + void stopSwitchWait(); + + // returns true iff the delay switch timer is started + bool isSwitchWaitStarted() const; // send screen options to \c client void sendOptions(IClient* client) const; - // open/close the primary screen - void openPrimaryScreen(); - void closePrimaryScreen(); + // process options from configuration + void processOptions(); - // update the clipboard if owned by the primary screen - void updatePrimaryClipboard(ClipboardID); + // event handlers + void handleShapeChanged(const CEvent&, void*); + void handleClipboardGrabbed(const CEvent&, void*); + void handleClipboardChanged(const CEvent&, void*); + void handleKeyDownEvent(const CEvent&, void*); + void handleKeyUpEvent(const CEvent&, void*); + void handleKeyRepeatEvent(const CEvent&, void*); + void handleButtonDownEvent(const CEvent&, void*); + void handleButtonUpEvent(const CEvent&, void*); + void handleMotionPrimaryEvent(const CEvent&, void*); + void handleMotionSecondaryEvent(const CEvent&, void*); + void handleWheelEvent(const CEvent&, void*); + void handleScreensaverActivatedEvent(const CEvent&, void*); + void handleScreensaverDeactivatedEvent(const CEvent&, void*); + void handleSwitchWaitTimeout(const CEvent&, void*); + void handleClientDisconnected(const CEvent&, void*); + void handleClientCloseTimeout(const CEvent&, void*); - // close all clients that are *not* in config, not including the - // primary client. + // event processing + void onClipboardChanged(IClient* sender, + ClipboardID id, UInt32 seqNum); + void onScreensaver(bool activated); + void onKeyDown(KeyID, KeyModifierMask, KeyButton); + void onKeyUp(KeyID, KeyModifierMask, KeyButton); + void onKeyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton); + void onMouseDown(ButtonID); + void onMouseUp(ButtonID); + bool onMouseMovePrimary(SInt32 x, SInt32 y); + void onMouseMoveSecondary(SInt32 dx, SInt32 dy); + void onMouseWheel(SInt32 delta); + + // add client to list and attach event handlers for client + bool addClient(IClient*); + + // remove client from list and detach event handlers for client + bool removeClient(IClient*); + + // close a client + void closeClient(IClient*, const char* msg); + + // close clients not in \p config void closeClients(const CConfig& config); - // start a thread, adding it to the list of threads - CThread startThread(IJob* adopted); + // close all clients whether they've completed the handshake or not, + // except the primary client + void closeAllClients(); - // cancel running threads, waiting at most timeout seconds for - // them to finish. - void stopThreads(double timeout = -1.0); + // remove clients from internal state + void removeActiveClient(IClient*); + void removeOldClient(IClient*); - // reap threads, clearing finished threads from the thread list. - // doReapThreads does the work on the given thread list. - void reapThreads(); - void doReapThreads(CThreadList&); - - // thread method to accept incoming client connections - void acceptClients(void*); - - // thread method to do client interaction - void runClient(void*); - CClientProxy* handshakeClient(IDataSocket*); - - // connection list maintenance - void addConnection(IClient*); - void removeConnection(const CString& name); + // force the cursor off of \p client + void forceLeaveClient(IClient* client); private: - class XServerRethrow : public XBase { - protected: - // XBase overrides - virtual CString getWhat() const throw(); - }; - class CClipboardInfo { public: CClipboardInfo(); @@ -322,52 +249,27 @@ private: UInt32 m_clipboardSeqNum; }; - CMutex m_mutex; - - // the name of the primary screen - CString m_name; - - // true if we should exit the main loop by throwing an exception. - // this is used to propagate an exception from one of our threads - // to the mainLoop() thread. but, since we can't make a copy of - // the original exception, we return an arbitrary, unique - // exception type. the caller of mainLoop() cannot catch this - // exception except through XBase or .... - bool m_error; - - // how long to wait to bind our socket until we give up - double m_bindTimeout; - - // factories - IScreenFactory* m_screenFactory; - ISocketFactory* m_socketFactory; - IStreamFilterFactory* m_streamFilterFactory; - - // running threads - CThreadList m_threads; - CThread* m_acceptClientThread; - - // the screens - typedef std::map CClientList; - typedef std::map CClientThreadList; - - // all clients indexed by name - CClientList m_clients; - - // run thread of all secondary screen clients. does not include the - // primary screen's run thread. - CClientThreadList m_clientThreads; - // the primary screen client CPrimaryClient* m_primaryClient; + // all clients (including the primary client) indexed by name + typedef std::map CClientList; + typedef std::set CClientSet; + CClientList m_clients; + CClientSet m_clientSet; + + // all old connections that we're waiting to hangup + typedef std::map COldClients; + COldClients m_oldClients; + // the client with focus IClient* m_active; // the sequence number of enter messages UInt32 m_seqNum; - // current mouse position (in absolute secondary screen coordinates) + // current mouse position (in absolute screen coordinates) on + // whichever screen is active SInt32 m_x, m_y; // current configuration @@ -387,8 +289,7 @@ private: // state for delayed screen switching double m_switchWaitDelay; - UInt32 m_switchWaitTimer; - bool m_switchWaitEngaged; + CEventQueueTimer* m_switchWaitTimer; SInt32 m_switchWaitX, m_switchWaitY; // state for double-tap screen switching @@ -398,19 +299,8 @@ private: bool m_switchTwoTapArmed; SInt32 m_switchTwoTapZone; - // the status change jobs and status - CJobList m_statusJobs; - EStatus m_status; - CString m_statusMessage; - -//--- -/* - IListenSocket* m_listen; - - typedef std::map CProvisionalClients; - CProvisionalClients m_provisional; -*/ + static CEvent::Type s_errorEvent; + static CEvent::Type s_disconnectedEvent; }; #endif diff --git a/lib/server/Makefile.am b/lib/server/Makefile.am index cab17f22..6dfd8a6f 100644 --- a/lib/server/Makefile.am +++ b/lib/server/Makefile.am @@ -25,19 +25,21 @@ MAINTAINERCLEANFILES = \ noinst_LIBRARIES = libserver.a libserver_a_SOURCES = \ + CClientListener.cpp \ CClientProxy.cpp \ CClientProxy1_0.cpp \ CClientProxy1_1.cpp \ + CClientProxyUnknown.cpp \ CConfig.cpp \ CPrimaryClient.cpp \ - CProvisionalClient.cpp \ CServer.cpp \ + CClientListener.h \ CClientProxy.h \ CClientProxy1_0.h \ CClientProxy1_1.h \ + CClientProxyUnknown.h \ CConfig.h \ CPrimaryClient.h \ - CProvisionalClient.h \ CServer.h \ $(NULL) INCLUDES = \ diff --git a/lib/synergy/CProtocolUtil.cpp b/lib/synergy/CProtocolUtil.cpp index d85f840a..ecaf4f39 100644 --- a/lib/synergy/CProtocolUtil.cpp +++ b/lib/synergy/CProtocolUtil.cpp @@ -73,7 +73,6 @@ CProtocolUtil::vwritef(IStream* stream, } // fill buffer - // FIXME -- can we use alloca? UInt8* buffer = new UInt8[size]; writef(buffer, fmt, args); diff --git a/lib/synergy/CScreen.cpp b/lib/synergy/CScreen.cpp index 817c9dbf..9bbd4a82 100644 --- a/lib/synergy/CScreen.cpp +++ b/lib/synergy/CScreen.cpp @@ -14,41 +14,26 @@ #include "CScreen.h" #include "IPlatformScreen.h" -#include "IScreenReceiver.h" -#include "ISecondaryScreen.h" #include "ProtocolTypes.h" -#include "CLock.h" -#include "CThread.h" #include "CLog.h" +#include "IEventQueue.h" // // CScreen // -CScreen::CScreen(IPlatformScreen* platformScreen, IScreenReceiver* receiver) : +CScreen::CScreen(IPlatformScreen* platformScreen) : m_screen(platformScreen), - m_receiver(receiver), m_isPrimary(platformScreen->isPrimary()), m_enabled(false), m_entered(m_isPrimary), m_toggleKeys(0), m_screenSaverSync(true) { - // do nothing -} - -CScreen::~CScreen() -{ - delete m_screen; -} - -void -CScreen::open() -{ - CLock lock(&m_mutex); + assert(m_screen != NULL); // open screen - m_screen->open(this); + m_screen->setKeyState(this); // reset options resetOptions(); @@ -56,23 +41,20 @@ CScreen::open() LOG((CLOG_DEBUG "opened display")); } -void -CScreen::close() +CScreen::~CScreen() { - CLock lock(&m_mutex); + if (m_enabled) { + disable(); + } assert(!m_enabled); assert(m_entered == m_isPrimary); - - // close screen - m_screen->close(); - + delete m_screen; LOG((CLOG_DEBUG "closed display")); } void CScreen::enable() { - CLock lock(&m_mutex); assert(!m_enabled); m_screen->enable(); @@ -90,7 +72,6 @@ CScreen::enable() void CScreen::disable() { - CLock lock(&m_mutex); assert(m_enabled); if (!m_isPrimary && m_entered) { @@ -111,34 +92,9 @@ CScreen::disable() m_enabled = false; } -void -CScreen::mainLoop() -{ - // change our priority - CThread::getCurrentThread().setPriority(-14); - - // run event loop - try { - LOG((CLOG_DEBUG "entering event loop")); - m_screen->mainLoop(); - LOG((CLOG_DEBUG "exiting event loop")); - } - catch (...) { - LOG((CLOG_DEBUG "exiting event loop")); - throw; - } -} - -void -CScreen::exitMainLoop() -{ - m_screen->exitMainLoop(); -} - void CScreen::enter() { - CLock lock(&m_mutex); assert(m_entered == false); LOG((CLOG_INFO "entering screen")); @@ -157,7 +113,6 @@ CScreen::enter() bool CScreen::leave() { - CLock lock(&m_mutex); assert(m_entered == true); LOG((CLOG_INFO "leaving screen")); @@ -209,8 +164,6 @@ CScreen::grabClipboard(ClipboardID id) void CScreen::screensaver(bool activate) { - CLock lock(&m_mutex); - if (!m_isPrimary) { // activate/deactivation screen saver iff synchronization enabled if (m_screenSaverSync) { @@ -222,7 +175,6 @@ CScreen::screensaver(bool activate) void CScreen::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) { - CLock lock(&m_mutex); assert(!m_isPrimary); // check for ctrl+alt+del emulation @@ -256,7 +208,6 @@ void CScreen::keyRepeat(KeyID id, KeyModifierMask mask, SInt32 count, KeyButton button) { - CLock lock(&m_mutex); assert(!m_isPrimary); // if we haven't seen this button go down then ignore it @@ -316,7 +267,6 @@ CScreen::keyRepeat(KeyID id, void CScreen::keyUp(KeyID, KeyModifierMask, KeyButton button) { - CLock lock(&m_mutex); assert(!m_isPrimary); // if we haven't seen this button go down then ignore it @@ -372,8 +322,6 @@ CScreen::mouseWheel(SInt32 delta) void CScreen::resetOptions() { - CLock lock(&m_mutex); - // reset options m_numLockHalfDuplex = false; m_capsLockHalfDuplex = false; @@ -394,8 +342,6 @@ CScreen::resetOptions() void CScreen::setOptions(const COptionsList& options) { - CLock lock(&m_mutex); - // update options bool oldScreenSaverSync = m_screenSaverSync; for (UInt32 i = 0, n = options.size(); i < n; i += 2) { @@ -427,37 +373,18 @@ CScreen::setOptions(const COptionsList& options) m_screen->setOptions(options); } -UInt32 -CScreen::addOneShotTimer(double timeout) +void +CScreen::setSequenceNumber(UInt32 seqNum) { - return m_screen->addOneShotTimer(timeout); + return m_screen->setSequenceNumber(seqNum); } bool CScreen::isOnScreen() const { - CLock lock(&m_mutex); return m_entered; } -void -CScreen::getClipboard(ClipboardID id, - IClipboard* clipboard) const -{ - m_screen->getClipboard(id, clipboard); -} - -SInt32 -CScreen::getJumpZoneSize() const -{ - if (!m_isPrimary) { - return 0; - } - else { - return m_screen->getJumpZoneSize(); - } -} - bool CScreen::isLockedToScreen() const { @@ -488,6 +415,35 @@ CScreen::isLockedToScreen() const return false; } +SInt32 +CScreen::getJumpZoneSize() const +{ + if (!m_isPrimary) { + return 0; + } + else { + return m_screen->getJumpZoneSize(); + } +} + +void +CScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + m_screen->getCursorCenter(x, y); +} + +void* +CScreen::getEventTarget() const +{ + return m_screen; +} + +bool +CScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + void CScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const { @@ -503,8 +459,6 @@ CScreen::getCursorPos(SInt32& x, SInt32& y) const void CScreen::updateKeys() { - CLock lock(&m_mutex); - // clear key state memset(m_keys, 0, sizeof(m_keys)); memset(m_fakeKeys, 0, sizeof(m_fakeKeys)); @@ -522,8 +476,6 @@ CScreen::updateKeys() void CScreen::releaseKeys() { - CLock lock(&m_mutex); - // release keys that we've synthesized a press for and only those // keys. we don't want to synthesize a release on a key the user // is still physically pressing. @@ -539,16 +491,12 @@ CScreen::releaseKeys() void CScreen::setKeyDown(KeyButton key) { - CLock lock(&m_mutex); - m_keys[key & 0xffu] |= kDown; } void CScreen::setToggled(KeyModifierMask mask) { - CLock lock(&m_mutex); - if (!isToggle(mask)) { return; } @@ -565,8 +513,6 @@ CScreen::setToggled(KeyModifierMask mask) void CScreen::addModifier(KeyModifierMask mask, KeyButtons& keys) { - CLock lock(&m_mutex); - // the modifier must have associated keys if (keys.empty()) { return; @@ -608,8 +554,6 @@ CScreen::setToggleState(KeyModifierMask mask) KeyButton CScreen::isAnyKeyDown() const { - CLock lock(&m_mutex); - for (UInt32 i = 1; i < 256; ++i) { if ((m_keys[i] & kDown) != 0) { return static_cast(i); @@ -621,8 +565,6 @@ CScreen::isAnyKeyDown() const bool CScreen::isKeyDown(KeyButton key) const { - CLock lock(&m_mutex); - key &= 0xffu; return (key != 0 && ((m_keys[key] & kDown) != 0)); } @@ -638,8 +580,6 @@ CScreen::isToggle(KeyModifierMask mask) const bool CScreen::isHalfDuplex(KeyModifierMask mask) const { - CLock lock(&m_mutex); - return ((mask == KeyModifierCapsLock && m_capsLockHalfDuplex) || (mask == KeyModifierNumLock && m_numLockHalfDuplex)); } @@ -647,8 +587,6 @@ CScreen::isHalfDuplex(KeyModifierMask mask) const bool CScreen::isModifierActive(KeyModifierMask mask) const { - CLock lock(&m_mutex); - MaskToKeys::const_iterator i = m_maskToKeys.find(mask); if (i == m_maskToKeys.end()) { return false; @@ -675,7 +613,6 @@ CScreen::isModifierActive(KeyModifierMask mask) const KeyModifierMask CScreen::getActiveModifiers() const { - CLock lock(&m_mutex); if (m_isPrimary) { // we don't keep primary key state up to date so get the // current state. @@ -688,8 +625,6 @@ bool CScreen::mapModifier(Keystrokes& keys, Keystrokes& undo, KeyModifierMask mask, bool desireActive) const { - CLock lock(&m_mutex); - // look up modifier MaskToKeys::const_iterator i = m_maskToKeys.find(mask); if (i == m_maskToKeys.end()) { @@ -751,7 +686,6 @@ CScreen::mapModifier(Keystrokes& keys, Keystrokes& undo, KeyModifierMask CScreen::getMaskForKey(KeyButton key) const { - CLock lock(&m_mutex); KeyToMask::const_iterator i = m_keyToMask.find(key); if (i == m_keyToMask.end()) { return 0; @@ -767,12 +701,8 @@ CScreen::enablePrimary() // get notified of screen saver activation/deactivation m_screen->openScreensaver(true); - // collect and send screen info - CClientInfo info; - m_screen->getShape(info.m_x, info.m_y, info.m_w, info.m_h); - m_screen->getCursorPos(info.m_mx, info.m_my); - info.m_zoneSize = getJumpZoneSize(); - m_receiver->onInfoChanged(info); + // claim screen changed size + EVENTQUEUE->addEvent(CEvent(getShapeChangedEvent(), getEventTarget())); } void diff --git a/lib/synergy/CScreen.h b/lib/synergy/CScreen.h index 7a0f3d29..a20c021e 100644 --- a/lib/synergy/CScreen.h +++ b/lib/synergy/CScreen.h @@ -12,45 +12,32 @@ * GNU General Public License for more details. */ -#ifndef CSECONDARYSCREEN_H -#define CSECONDARYSCREEN_H +#ifndef CSCREEN_H +#define CSCREEN_H #include "IKeyState.h" +#include "IScreen.h" #include "ClipboardTypes.h" #include "MouseTypes.h" #include "OptionTypes.h" -#include "CMutex.h" #include "stdmap.h" class IClipboard; class IPlatformScreen; -class IScreenReceiver; //! Platform independent screen /*! This is a platform independent screen. It can work as either a primary or secondary screen. */ -class CScreen : public IKeyState { +class CScreen : public IScreen, public IKeyState { public: - CScreen(IPlatformScreen* platformScreen, IScreenReceiver*); + CScreen(IPlatformScreen* platformScreen); virtual ~CScreen(); //! @name manipulators //@{ - //! Open screen - /*! - Opens the screen. - */ - void open(); - - //! Close screen - /*! - Closes the screen. - */ - void close(); - //! Activate screen /*! Activate the screen, preparing it to report system and user events. @@ -66,21 +53,6 @@ public: */ void disable(); - //! Run event loop - /*! - Run the screen's event loop. This returns when it detects - the application should terminate or when exitMainLoop() is called. - mainLoop() may only be called between open() and close(). - */ - void mainLoop(); - - //! Exit event loop - /*! - Force mainLoop() to return. This call can return before - mainLoop() does (i.e. asynchronously). - */ - void exitMainLoop(); - //! Enter screen /*! Called when the user navigates to this screen. @@ -196,12 +168,11 @@ public: */ void setOptions(const COptionsList& options); - //! Install a one-shot timer + //! Set clipboard sequence number /*! - Installs a one-shot timer for \c timeout seconds and returns the - id of the timer. + Sets the sequence number to use in subsequent clipboard events. */ - UInt32 addOneShotTimer(double timeout); + void setSequenceNumber(UInt32); //@} //! @name accessors @@ -213,19 +184,6 @@ public: */ bool isOnScreen() const; - //! Get clipboard - /*! - Saves the contents of the system clipboard indicated by \c id. - */ - void getClipboard(ClipboardID id, IClipboard*) const; - - //! Get jump zone size - /*! - Returns the jump zone size, the size of the regions on the edges of - the screen that cause the cursor to jump to another screen. - */ - SInt32 getJumpZoneSize() const; - //! Get screen lock state /*! Returns true if there's any reason that the user should not be @@ -236,22 +194,30 @@ public: */ bool isLockedToScreen() const; - //! Get screen shape + //! Get jump zone size /*! - Returns the position of the upper-left corner of the screen in \c x - and \c y and the size of the screen in \c width and \c height. + Return the jump zone size, the size of the regions on the edges of + the screen that cause the cursor to jump to another screen. */ - void getShape(SInt32& x, SInt32& y, - SInt32& width, SInt32& height) const; + SInt32 getJumpZoneSize() const; - //! Get cursor position + //! Get cursor center position /*! - Returns the current position of the cursor in \c x,y. + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. */ - void getCursorPos(SInt32& x, SInt32& y) const; + void getCursorCenter(SInt32& x, SInt32& y) const; //@} + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + // IKeyState overrides virtual void updateKeys(); virtual void releaseKeys(); @@ -307,14 +273,9 @@ private: typedef std::map MaskToKeys; typedef std::map KeyToMask; - CMutex m_mutex; - // our platform dependent screen IPlatformScreen* m_screen; - // our screen receiver - IScreenReceiver* m_receiver; - // true if screen is being used as a primary screen, false otherwise bool m_isPrimary; diff --git a/lib/synergy/IClient.h b/lib/synergy/IClient.h index 01ab4411..3e7c8b08 100644 --- a/lib/synergy/IClient.h +++ b/lib/synergy/IClient.h @@ -15,7 +15,7 @@ #ifndef ICLIENT_H #define ICLIENT_H -#include "IInterface.h" +#include "IScreen.h" #include "ClipboardTypes.h" #include "KeyTypes.h" #include "MouseTypes.h" @@ -27,43 +27,18 @@ This interface defines the methods necessary for the server to communicate with a client. */ -class IClient : public IInterface { +class IClient : public IScreen { public: //! @name manipulators //@{ - //! Open client - /*! - Open the client. Throw if the client cannot be opened. If the - screen cannot be opened but retrying later may succeed then throw - XScreenUnavailable. - */ - virtual void open() = 0; - - //! Client main loop - /*! - Run client's event loop. This method is typically called in a - separate thread and is exited by cancelling the thread. This - must be called between a successful open() and close(). - - (cancellation point) - */ - virtual void mainLoop() = 0; - - //! Close client - /*! - Close the client. - */ - virtual void close() = 0; - //! Enter screen /*! - Enter the screen. The cursor should be warped to \c xAbs,yAbs. - The client should record seqNum for future reporting of - clipboard changes. \c mask is the expected toggle button state - and the client should update its state to match. \c forScreensaver - is true iff the screen is being entered because the screen saver is - starting. + Enter the screen. The cursor should be warped to \p xAbs,yAbs. + \p mask is the expected toggle button state and the client should + update its state to match. \p forScreensaver is true iff the + screen is being entered because the screen saver is starting. + Subsequent clipboard events should report \p seqNum. */ virtual void enter(SInt32 xAbs, SInt32 yAbs, UInt32 seqNum, KeyModifierMask mask, @@ -180,35 +155,14 @@ public: */ virtual CString getName() const = 0; - //! Get jump zone size - /*! - Called to get the jump zone size. - */ - virtual SInt32 getJumpZoneSize() const = 0; + //@} - //! Get screen shape - /*! - Return the position of the upper-left corner of the screen in \c x and - \c y and the size of the screen in \c width and \c height. - */ + // IScreen overrides + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; virtual void getShape(SInt32& x, SInt32& y, SInt32& width, SInt32& height) const = 0; - - //! Get cursor position - /*! - Return the current position of the cursor in \c x and \c y. - */ virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; - - //! Get cursor center position - /*! - Return the cursor center position which is where we park the - cursor to compute cursor motion deltas and should be far from - the edges of the screen, typically the center. - */ - virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; - - //@} }; #endif diff --git a/lib/synergy/IPlatformScreen.cpp b/lib/synergy/IPlatformScreen.cpp new file mode 100644 index 00000000..78d1e436 --- /dev/null +++ b/lib/synergy/IPlatformScreen.cpp @@ -0,0 +1,149 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "IPlatformScreen.h" + +// +// IPlatformScreen +// + +CEvent::Type IPlatformScreen::s_keyDownEvent = CEvent::kUnknown; +CEvent::Type IPlatformScreen::s_keyUpEvent = CEvent::kUnknown; +CEvent::Type IPlatformScreen::s_keyRepeatEvent = CEvent::kUnknown; +CEvent::Type IPlatformScreen::s_buttonDownEvent = CEvent::kUnknown; +CEvent::Type IPlatformScreen::s_buttonUpEvent = CEvent::kUnknown; +CEvent::Type IPlatformScreen::s_motionPrimaryEvent = CEvent::kUnknown; +CEvent::Type IPlatformScreen::s_motionSecondaryEvent = CEvent::kUnknown; +CEvent::Type IPlatformScreen::s_wheelEvent = CEvent::kUnknown; +CEvent::Type IPlatformScreen::s_ssActivatedEvent = CEvent::kUnknown; +CEvent::Type IPlatformScreen::s_ssDeactivatedEvent = CEvent::kUnknown; + +CEvent::Type +IPlatformScreen::getKeyDownEvent() +{ + return CEvent::registerTypeOnce(s_keyDownEvent, + "IPlatformScreen::keyDown"); +} + +CEvent::Type +IPlatformScreen::getKeyUpEvent() +{ + return CEvent::registerTypeOnce(s_keyUpEvent, + "IPlatformScreen::keyUp"); +} + +CEvent::Type +IPlatformScreen::getKeyRepeatEvent() +{ + return CEvent::registerTypeOnce(s_keyRepeatEvent, + "IPlatformScreen::keyRepeat"); +} + +CEvent::Type +IPlatformScreen::getButtonDownEvent() +{ + return CEvent::registerTypeOnce(s_buttonDownEvent, + "IPlatformScreen::buttonDown"); +} + +CEvent::Type +IPlatformScreen::getButtonUpEvent() +{ + return CEvent::registerTypeOnce(s_buttonUpEvent, + "IPlatformScreen::buttonUp"); +} + +CEvent::Type +IPlatformScreen::getMotionOnPrimaryEvent() +{ + return CEvent::registerTypeOnce(s_motionPrimaryEvent, + "IPlatformScreen::motionPrimary"); +} + +CEvent::Type +IPlatformScreen::getMotionOnSecondaryEvent() +{ + return CEvent::registerTypeOnce(s_motionSecondaryEvent, + "IPlatformScreen::motionSecondary"); +} + +CEvent::Type +IPlatformScreen::getWheelEvent() +{ + return CEvent::registerTypeOnce(s_wheelEvent, + "IPlatformScreen::wheel"); +} + +CEvent::Type +IPlatformScreen::getScreensaverActivatedEvent() +{ + return CEvent::registerTypeOnce(s_ssActivatedEvent, + "IPlatformScreen::screensaverActivated"); +} + +CEvent::Type +IPlatformScreen::getScreensaverDeactivatedEvent() +{ + return CEvent::registerTypeOnce(s_ssDeactivatedEvent, + "IPlatformScreen::screensaverDeactivated"); +} + + +// +// IPlatformScreen::CKeyInfo +// + +IPlatformScreen::CKeyInfo::CKeyInfo(KeyID id, + KeyModifierMask mask, KeyButton button, SInt32 count) : + m_key(id), + m_mask(mask), + m_button(button), + m_count(count) +{ + // do nothing +} + + +// +// IPlatformScreen::CButtonInfo +// + +IPlatformScreen::CButtonInfo::CButtonInfo(ButtonID id) : + m_button(id) +{ + // do nothing +} + + +// +// IPlatformScreen::CMotionInfo +// + +IPlatformScreen::CMotionInfo::CMotionInfo(SInt32 x, SInt32 y) : + m_x(x), + m_y(y) +{ + // do nothing +} + + +// +// IPlatformScreen::CWheelInfo +// + +IPlatformScreen::CWheelInfo::CWheelInfo(SInt32 wheel) : + m_wheel(wheel) +{ + // do nothing +} diff --git a/lib/synergy/IPlatformScreen.h b/lib/synergy/IPlatformScreen.h index 149c5526..456d58d9 100644 --- a/lib/synergy/IPlatformScreen.h +++ b/lib/synergy/IPlatformScreen.h @@ -15,10 +15,12 @@ #ifndef IPLATFORMSCREEN_H #define IPLATFORMSCREEN_H +#include "IScreen.h" #include "IPrimaryScreen.h" #include "ISecondaryScreen.h" #include "ClipboardTypes.h" #include "OptionTypes.h" +#include "CEvent.h" class IClipboard; class IKeyState; @@ -27,27 +29,60 @@ class IKeyState; /*! This interface defines the methods common to all platform dependent screen implementations that are used by both primary and secondary -screens. +screens. A platform screen is expected to post the events defined +in \c IScreen when appropriate. It should also post events defined +in \c IPlatformScreen if acting as the primary screen. The target +on the events should be the value returned by \c getEventTarget(). */ -class IPlatformScreen : public IPrimaryScreen, public ISecondaryScreen { +class IPlatformScreen : public IScreen, + public IPrimaryScreen, public ISecondaryScreen { public: + //! Key event data + class CKeyInfo { + public: + CKeyInfo(KeyID, KeyModifierMask, KeyButton, SInt32 count); + + public: + KeyID m_key; + KeyModifierMask m_mask; + KeyButton m_button; + SInt32 m_count; + }; + //! Button event data + class CButtonInfo { + public: + CButtonInfo(ButtonID); + + public: + ButtonID m_button; + }; + //! Motion event data + class CMotionInfo { + public: + CMotionInfo(SInt32 x, SInt32 y); + + public: + SInt32 m_x; + SInt32 m_y; + }; + //! Wheel motion event data + class CWheelInfo { + public: + CWheelInfo(SInt32); + + public: + SInt32 m_wheel; + }; + //! @name manipulators //@{ - //! Open screen + //! Set the key state /*! - Called to open and initialize the screen. Throw XScreenUnavailable - if the screen cannot be opened but retrying later may succeed. - Otherwise throw some other XScreenOpenFailure exception. + Sets the key state object. This object tracks keyboard state and + the screen is expected to keep it up to date. */ - virtual void open(IKeyState*) = 0; - - //! Close screen - /*! - Called to close the screen. close() should quietly ignore calls - that don't have a matching successful call to open(). - */ - virtual void close() = 0; + virtual void setKeyState(IKeyState*) = 0; //! Enable screen /*! @@ -64,21 +99,6 @@ public: */ virtual void disable() = 0; - //! Run event loop - /*! - Run the event loop and return when exitMainLoop() is called. - This must be called between a successful open() and close(). - */ - virtual void mainLoop() = 0; - - //! Exit event loop - /*! - Force mainLoop() to return. This call can return before - mainLoop() does (i.e. asynchronously). This may only be - called between a successful open() and close(). - */ - virtual void exitMainLoop() = 0; - //! Enter screen /*! Called when the user navigates to this screen. @@ -102,22 +122,19 @@ public: //! Check clipboard owner /*! - Check ownership of all clipboards and notify an IScreenReceiver (set - through some other interface) if any changed. This is used as a - backup in case the system doesn't reliably report clipboard ownership - changes. + Check ownership of all clipboards and post grab events for any that + have changed. This is used as a backup in case the system doesn't + reliably report clipboard ownership changes. */ virtual void checkClipboards() = 0; //! Open screen saver /*! Open the screen saver. If \c notify is true then this object must - call an IScreenEventHandler's (set through some other interface) - onScreenSaver() when the screensaver activates or deactivates until - it's closed. If \c notify is false then the screen saver is - disabled on open and restored on close. + send events when the screen saver activates or deactivates until + \c closeScreensaver() is called. If \c notify is false then the + screen saver is disabled and restored on \c closeScreensaver(). */ -// XXX -- pass an interface pointer, not a notify flag virtual void openScreensaver(bool notify) = 0; //! Close screen saver @@ -154,6 +171,12 @@ public: */ virtual void updateKeys() = 0; + //! Set clipboard sequence number + /*! + Sets the sequence number to use in subsequent clipboard events. + */ + virtual void setSequenceNumber(UInt32) = 0; + //@} //! @name accessors //@{ @@ -164,35 +187,49 @@ public: */ virtual bool isPrimary() const = 0; - //! Get clipboard + //! Get key down event type. Event data is CKeyInfo*, count == 1. + static CEvent::Type getKeyDownEvent(); + //! Get key up event type. Event data is CKeyInfo*, count == 1. + static CEvent::Type getKeyUpEvent(); + //! Get key repeat event type. Event data is CKeyInfo*. + static CEvent::Type getKeyRepeatEvent(); + //! Get button down event type. Event data is CButtonInfo*. + static CEvent::Type getButtonDownEvent(); + //! Get button up event type. Event data is CButtonInfo*. + static CEvent::Type getButtonUpEvent(); + //! Get mouse motion on the primary screen event type /*! - Save the contents of the clipboard indicated by \c id and return - true iff successful. + Event data is CMotionInfo* and the values are an absolute position. */ - virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; - - //! Get screen shape + static CEvent::Type getMotionOnPrimaryEvent(); + //! Get mouse motion on a secondary screen event type /*! - Return the position of the upper-left corner of the screen in \c x and - \c y and the size of the screen in \c w (width) and \c h (height). + Event data is CMotionInfo* and the values are motion deltas not + absolute coordinates. */ - virtual void getShape(SInt32& x, SInt32& y, - SInt32& w, SInt32& h) const = 0; - - //! Get cursor position - /*! - Return the current position of the cursor in \c x and \c y. - */ - virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + static CEvent::Type getMotionOnSecondaryEvent(); + //! Get mouse wheel event type. Event data is CWheelInfo*. + static CEvent::Type getWheelEvent(); + //! Get screensaver activated event type + static CEvent::Type getScreensaverActivatedEvent(); + //! Get screensaver deactivated event type + static CEvent::Type getScreensaverDeactivatedEvent(); //@} + // IScreen overrides + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + // IPrimaryScreen overrides virtual void reconfigure(UInt32 activeSides) = 0; virtual void warpCursor(SInt32 x, SInt32 y) = 0; - virtual UInt32 addOneShotTimer(double timeout) = 0; virtual SInt32 getJumpZoneSize() const = 0; virtual bool isAnyMouseButtonDown() const = 0; + virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; virtual const char* getKeyName(KeyButton) const = 0; // ISecondaryScreen overrides @@ -205,6 +242,18 @@ public: const IKeyState& keyState, KeyID id, KeyModifierMask desiredMask, bool isAutoRepeat) const = 0; + +private: + static CEvent::Type s_keyDownEvent; + static CEvent::Type s_keyUpEvent; + static CEvent::Type s_keyRepeatEvent; + static CEvent::Type s_buttonDownEvent; + static CEvent::Type s_buttonUpEvent; + static CEvent::Type s_motionPrimaryEvent; + static CEvent::Type s_motionSecondaryEvent; + static CEvent::Type s_wheelEvent; + static CEvent::Type s_ssActivatedEvent; + static CEvent::Type s_ssDeactivatedEvent; }; #endif diff --git a/lib/synergy/IPrimaryScreen.h b/lib/synergy/IPrimaryScreen.h index 560436f3..fb3f6ccb 100644 --- a/lib/synergy/IPrimaryScreen.h +++ b/lib/synergy/IPrimaryScreen.h @@ -25,7 +25,6 @@ primary screen implementations. */ class IPrimaryScreen : public IInterface { public: -// XXX -- may need an interface for sending events //! @name manipulators //@{ @@ -46,15 +45,6 @@ public: */ virtual void warpCursor(SInt32 x, SInt32 y) = 0; - //! Install a one-shot timer - /*! - Installs a one-shot timer for \c timeout seconds and returns the - id of the timer. - */ -// XXX -- need to specify the receiver of the event. or we should -// pass a job. need a method to remove the timer? - virtual UInt32 addOneShotTimer(double timeout) = 0; - //@} //! @name accessors //@{ @@ -72,6 +62,14 @@ public: */ virtual bool isAnyMouseButtonDown() const = 0; + //! Get cursor center position + /*! + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. + */ + virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; + //! Get name of key /*! Return a string describing the given key. diff --git a/lib/synergy/IPrimaryScreenReceiver.h b/lib/synergy/IPrimaryScreenReceiver.h deleted file mode 100644 index 44b1beee..00000000 --- a/lib/synergy/IPrimaryScreenReceiver.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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. - */ - -#ifndef IPRIMARYSCREENRECEIVER_H -#define IPRIMARYSCREENRECEIVER_H - -#include "IInterface.h" -#include "KeyTypes.h" -#include "MouseTypes.h" - -//! Primary screen event receiver interface -/*! -The interface for receiving notification of events on the primary -screen. The server implements this interface to handle user input. -Platform dependent primary screen implementation will need to take -an IPrimaryScreenReceiver* and notify it of events. -*/ -class IPrimaryScreenReceiver : public IInterface { -public: - //! Notify of screen saver change - /*! - Called when the screensaver is activated or deactivated. - */ - virtual void onScreensaver(bool activated) = 0; - - //! Notify of one-shot timer expiration - /*! - Called when a one-shot timer expires. - */ - virtual void onOneShotTimerExpired(UInt32 id) = 0; - - // call to notify of events. onMouseMovePrimary() returns - // true iff the mouse enters a jump zone and jumps. - //! Notify of key press - virtual void onKeyDown(KeyID, KeyModifierMask, KeyButton) = 0; - //! Notify of key release - virtual void onKeyUp(KeyID, KeyModifierMask, KeyButton) = 0; - //! Notify of key repeat - virtual void onKeyRepeat(KeyID, KeyModifierMask, - SInt32 count, KeyButton) = 0; - //! Notify of mouse button press - virtual void onMouseDown(ButtonID) = 0; - //! Notify of mouse button release - virtual void onMouseUp(ButtonID) = 0; - //! Notify of mouse motion - /*! - Called when the mouse has moved while on the primary screen. \c x - and \c y are the absolute screen position of the mouse. Return - true iff the mouse enters a jump zone and jumps. - */ - virtual bool onMouseMovePrimary(SInt32 x, SInt32 y) = 0; - //! Notify of mouse motion - /*! - Called when the mouse has moved while on the secondary screen. - \c dx and \c dy are the relative motion from the last position. - */ - virtual void onMouseMoveSecondary(SInt32 dx, SInt32 dy) = 0; - //! Notify of mouse wheen motion - virtual void onMouseWheel(SInt32 delta) = 0; -}; - -#endif diff --git a/lib/synergy/IScreen.cpp b/lib/synergy/IScreen.cpp new file mode 100644 index 00000000..1c55dc5c --- /dev/null +++ b/lib/synergy/IScreen.cpp @@ -0,0 +1,52 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "IScreen.h" + +// +// IScreen +// + +CEvent::Type IScreen::s_errorEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_shapeChangedEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_clipboardGrabbedEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_clipboardChangedEvent = CEvent::kUnknown; + +CEvent::Type +IScreen::getErrorEvent() +{ + return CEvent::registerTypeOnce(s_errorEvent, + "IScreen::error"); +} + +CEvent::Type +IScreen::getShapeChangedEvent() +{ + return CEvent::registerTypeOnce(s_shapeChangedEvent, + "IScreen::shapeChanged"); +} + +CEvent::Type +IScreen::getClipboardGrabbedEvent() +{ + return CEvent::registerTypeOnce(s_clipboardGrabbedEvent, + "IScreen::clipboardGrabbed"); +} + +CEvent::Type +IScreen::getClipboardChangedEvent() +{ + return CEvent::registerTypeOnce(s_clipboardChangedEvent, + "IScreen::clipboardChanged"); +} diff --git a/lib/synergy/IScreen.h b/lib/synergy/IScreen.h new file mode 100644 index 00000000..7af925cf --- /dev/null +++ b/lib/synergy/IScreen.h @@ -0,0 +1,106 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef ISCREEN_H +#define ISCREEN_H + +#include "IInterface.h" +#include "ClipboardTypes.h" +#include "CEvent.h" + +class IClipboard; + +//! Screen interface +/*! +This interface defines the methods common to all screens. +*/ +class IScreen : public IInterface { +public: + struct CClipboardInfo { + public: + ClipboardID m_id; + UInt32 m_sequenceNumber; + }; + + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the target used for events created by this object. + */ + virtual void* getEventTarget() const = 0; + + //! Get clipboard + /*! + Save the contents of the clipboard indicated by \c id and return + true iff successful. + */ + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + + //! Get screen shape + /*! + Return the position of the upper-left corner of the screen in \c x and + \c y and the size of the screen in \c width and \c height. + */ + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + + //! Get cursor position + /*! + Return the current position of the cursor in \c x and \c y. + */ + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + //! Get error event type + /*! + Returns the error event type. This is sent whenever the screen has + failed for some reason (e.g. the X Windows server died). + */ + static CEvent::Type getErrorEvent(); + + //! Get shape changed event type + /*! + Returns the shape changed event type. This is sent whenever the + screen's shape changes, the cursor center moves, or the jump zone + size changes. + */ + static CEvent::Type getShapeChangedEvent(); + + //! Get clipboard grabbed event type + /*! + Returns the clipboard grabbed event type. This is sent whenever the + clipboard is grabbed by some other application so we don't own it + anymore. The data is a pointer to a CClipboardInfo. + */ + static CEvent::Type getClipboardGrabbedEvent(); + + //! Get clipboard changed event type + /*! + Returns the clipboard changed event type. This is sent whenever the + contents of the clipboard has changed. The data is a pointer to a + CClipboardInfo. + */ + static CEvent::Type getClipboardChangedEvent(); + + //@} + +private: + static CEvent::Type s_errorEvent; + static CEvent::Type s_shapeChangedEvent; + static CEvent::Type s_clipboardGrabbedEvent; + static CEvent::Type s_clipboardChangedEvent; +}; + +#endif diff --git a/lib/synergy/IScreenFactory.h b/lib/synergy/IScreenFactory.h deleted file mode 100644 index 8a5ab410..00000000 --- a/lib/synergy/IScreenFactory.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2003 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. - */ - -#ifndef ISCREENFACTORY_H -#define ISCREENFACTORY_H - -#include "IInterface.h" - -class IPrimaryScreenReceiver; -class IPlatformScreen; -class IScreenReceiver; - -//! Primary screen factory interface -/*! -This interface provides factory methods to create primary and -secondary screens. -*/ -class IScreenFactory : public IInterface { -public: - //! Create screen - /*! - Create and return a screen. The caller must delete the returned - object. The screen is a primary screen iff the IPrimaryScreenReceiver - is not NULL. - */ - virtual IPlatformScreen* - create(IScreenReceiver*, IPrimaryScreenReceiver*) = 0; -}; - -#endif diff --git a/lib/synergy/IScreenReceiver.h b/lib/synergy/IScreenReceiver.h deleted file mode 100644 index 0dd55075..00000000 --- a/lib/synergy/IScreenReceiver.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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. - */ - -#ifndef ISCREENRECEIVER_H -#define ISCREENRECEIVER_H - -#include "IInterface.h" -#include "ClipboardTypes.h" -#include "ProtocolTypes.h" -#include "CString.h" - -//! Screen event receiver interface -/*! -This interface defines the methods common to most types that receive -events for changes to a screen. Note that the methods in this -interface are similar to the methods in IServer but have different -parameters. This interface is suitable for client-side types. -*/ -class IScreenReceiver : public IInterface { -public: - //! Notify of error - /*! - Called when the screen is unexpectedly closing. This implies that - the screen is no longer usable and that the program should close - the screen and probably terminate. - */ - virtual void onError() = 0; - - //! Notify of client screen change - /*! - Called when the client's info has changed. For example, when the - screen resolution has changed. - */ - virtual void onInfoChanged(const CClientInfo&) = 0; - - //! Notify of clipboad grab - /*! - Called when the clipboard was grabbed by another program and, - therefore, we no longer own it. Returns true if the grab was - honored, false otherwise. - */ - virtual bool onGrabClipboard(ClipboardID) = 0; - - //! Notify of new clipboard data - /*! - Called when the data on the clipboard has changed because some - other program has changed it. \c data will have marshalled - clipboard data. - */ - virtual void onClipboardChanged(ClipboardID, - const CString& data) = 0; -}; - -#endif diff --git a/lib/synergy/IScreenSaver.h b/lib/synergy/IScreenSaver.h index f2da8114..9076b309 100644 --- a/lib/synergy/IScreenSaver.h +++ b/lib/synergy/IScreenSaver.h @@ -16,6 +16,7 @@ #define ISCREENSAVER_H #include "IInterface.h" +#include "CEvent.h" //! Screen saver interface /*! diff --git a/lib/synergy/IServer.h b/lib/synergy/IServer.h deleted file mode 100644 index 42b7a675..00000000 --- a/lib/synergy/IServer.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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. - */ - -#ifndef ISERVER_H -#define ISERVER_H - -#include "IInterface.h" -#include "ClipboardTypes.h" -#include "CString.h" - -class CClientInfo; - -//! Server interface -/*! -This interface defines the methods necessary for clients to -communicate with the server. Note that the methods in this -interface are similar to the methods in IScreenReceiver but -include extra parameters. This interface is suitable for -server-side client proxies. Client-side objects should use -the IScreenReceiver interface since the extra parameters are -meaningless on the client-side. -*/ -class IServer : public IInterface { -public: - //! @name manipulators - //@{ - - //! Notify of error - /*! - Called when the screen is unexpectedly closing. This implies that - the screen is no longer usable and that the program should close - the screen and probably terminate. - */ - virtual void onError() = 0; - - //! Notify of client screen change - /*! - Called when the client's info has changed. - */ - virtual void onInfoChanged(const CString& clientName, - const CClientInfo&) = 0; - - //! Notify of clipboad grab - /*! - Called when the clipboard was grabbed by another program and, - therefore, we no longer own it. Returns true if the grab was - honored, false otherwise. - */ - virtual bool onGrabClipboard(const CString& clientName, - ClipboardID, UInt32 seqNum) = 0; - - //! Notify of new clipboard data - /*! - Called when the data on the clipboard has changed because some - other program has changed it. \c data has the marshalled clipboard - data. - */ - virtual void onClipboardChanged(ClipboardID, - UInt32 seqNum, const CString& data) = 0; - - //@} -}; - -#endif diff --git a/lib/synergy/Makefile.am b/lib/synergy/Makefile.am index 1203ae2f..ed4763fa 100644 --- a/lib/synergy/Makefile.am +++ b/lib/synergy/Makefile.am @@ -29,6 +29,8 @@ libsynergy_a_SOURCES = \ CPacketStreamFilter.cpp \ CProtocolUtil.cpp \ CScreen.cpp \ + IPlatformScreen.cpp \ + IScreen.cpp \ XScreen.cpp \ XSynergy.cpp \ CClipboard.h \ @@ -41,12 +43,9 @@ libsynergy_a_SOURCES = \ IKeyState.h \ IPlatformScreen.h \ IPrimaryScreen.h \ - IPrimaryScreenReceiver.h \ - IScreenFactory.h \ - IScreenReceiver.h \ + IScreen.h \ IScreenSaver.h \ ISecondaryScreen.h \ - IServer.h \ KeyTypes.h \ MouseTypes.h \ OptionTypes.h \