From 848aee7a3ac37e5ed7260470e8e7cdf48ae62752 Mon Sep 17 00:00:00 2001 From: crs Date: Sun, 1 Feb 2004 21:09:22 +0000 Subject: [PATCH] Checkpoint. Code does not run. Still converting over to new event loop model. Streams, stream filters, and sockets are converted. Client proxies are almost converted. CServer is in progress. Removed all HTTP code. Haven't converted the necessary win32 arch stuff. --- cmd/synergys/synergys.cpp | 20 - lib/arch/CArch.cpp | 12 + lib/arch/CArch.h | 2 + lib/arch/CArchMultithreadPosix.cpp | 37 +- lib/arch/CArchMultithreadPosix.h | 4 + lib/arch/CArchNetworkBSD.cpp | 11 + lib/arch/IArchMultithread.h | 12 + lib/arch/XArch.h | 5 +- lib/base/CEventQueue.cpp | 123 ++- lib/base/CEventQueue.h | 47 +- .../IEventQueue.cpp} | 32 +- lib/base/IEventQueue.h | 72 +- lib/base/Makefile.am | 1 + lib/io/CBufferedInputStream.cpp | 140 --- lib/io/CBufferedInputStream.h | 101 --- lib/io/CBufferedOutputStream.cpp | 104 --- lib/io/CBufferedOutputStream.h | 92 -- lib/io/CInputStreamFilter.h | 52 -- lib/io/COutputStreamFilter.cpp | 39 - lib/io/COutputStreamFilter.h | 52 -- lib/io/CStreamFilter.cpp | 105 +++ lib/io/CStreamFilter.h | 62 ++ lib/io/IInputStream.h | 67 -- lib/io/IOutputStream.h | 58 -- lib/io/IStream.cpp | 55 ++ lib/io/IStream.h | 175 ++++ lib/io/IStreamFilterFactory.h | 22 +- lib/io/Makefile.am | 14 +- lib/io/XIO.cpp | 11 + lib/io/XIO.h | 6 + lib/net/CSocketMultiplexer.cpp | 304 +++++++ lib/net/CSocketMultiplexer.h | 95 +++ lib/net/CTCPListenSocket.cpp | 42 +- lib/net/CTCPListenSocket.h | 3 +- lib/net/CTCPSocket.cpp | 622 +++++++------- lib/net/CTCPSocket.h | 73 +- lib/net/IDataSocket.cpp | 25 +- lib/net/IDataSocket.h | 70 +- lib/net/IListenSocket.h | 2 +- lib/net/ISocket.h | 15 +- lib/net/ISocketMultiplexerJob.h | 75 ++ lib/net/TSocketMultiplexerMethodJob.h | 108 +++ lib/server/CClientProxy.cpp | 25 +- lib/server/CClientProxy.h | 22 +- lib/server/CClientProxy1_0.cpp | 316 ++++--- lib/server/CClientProxy1_0.h | 29 +- lib/server/CClientProxy1_1.cpp | 12 +- lib/server/CClientProxy1_1.h | 3 +- lib/server/CConfig.cpp | 27 - lib/server/CConfig.h | 10 - lib/server/CHTTPServer.cpp | 799 ------------------ lib/server/CHTTPServer.h | 142 ---- lib/server/CServer.cpp | 411 +++++---- lib/server/CServer.h | 27 +- lib/server/Makefile.am | 5 +- lib/synergy/CInputPacketStream.cpp | 181 ---- lib/synergy/CInputPacketStream.h | 51 -- lib/synergy/COutputPacketStream.cpp | 72 -- lib/synergy/COutputPacketStream.h | 36 - lib/synergy/CPacketStreamFilter.cpp | 221 +++++ lib/synergy/CPacketStreamFilter.h | 57 ++ lib/synergy/CProtocolUtil.cpp | 87 +- lib/synergy/CProtocolUtil.h | 17 +- lib/synergy/Makefile.am | 6 +- 64 files changed, 2581 insertions(+), 2942 deletions(-) rename lib/{io/CInputStreamFilter.cpp => base/IEventQueue.cpp} (52%) delete mode 100644 lib/io/CBufferedInputStream.cpp delete mode 100644 lib/io/CBufferedInputStream.h delete mode 100644 lib/io/CBufferedOutputStream.cpp delete mode 100644 lib/io/CBufferedOutputStream.h delete mode 100644 lib/io/CInputStreamFilter.h delete mode 100644 lib/io/COutputStreamFilter.cpp delete mode 100644 lib/io/COutputStreamFilter.h create mode 100644 lib/io/CStreamFilter.cpp create mode 100644 lib/io/CStreamFilter.h delete mode 100644 lib/io/IInputStream.h delete mode 100644 lib/io/IOutputStream.h create mode 100644 lib/io/IStream.cpp create mode 100644 lib/io/IStream.h create mode 100644 lib/net/CSocketMultiplexer.cpp create mode 100644 lib/net/CSocketMultiplexer.h create mode 100644 lib/net/ISocketMultiplexerJob.h create mode 100644 lib/net/TSocketMultiplexerMethodJob.h delete mode 100644 lib/server/CHTTPServer.cpp delete mode 100644 lib/server/CHTTPServer.h delete mode 100644 lib/synergy/CInputPacketStream.cpp delete mode 100644 lib/synergy/CInputPacketStream.h delete mode 100644 lib/synergy/COutputPacketStream.cpp delete mode 100644 lib/synergy/COutputPacketStream.h create mode 100644 lib/synergy/CPacketStreamFilter.cpp create mode 100644 lib/synergy/CPacketStreamFilter.h diff --git a/cmd/synergys/synergys.cpp b/cmd/synergys/synergys.cpp index 59b76fce..cfa66e8a 100644 --- a/cmd/synergys/synergys.cpp +++ b/cmd/synergys/synergys.cpp @@ -88,7 +88,6 @@ public: const char* m_logFilter; CString m_name; CNetworkAddress m_synergyAddress; - CNetworkAddress m_httpAddress; CConfig m_config; }; @@ -191,11 +190,6 @@ realMain(void) ARG->m_config.setSynergyAddress(CNetworkAddress(kDefaultPort)); } - // set HTTP address if provided - if (ARG->m_httpAddress.isValid()) { - ARG->m_config.setHTTPAddress(ARG->m_httpAddress); - } - // create server s_server = new CServer(ARG->m_name); s_server->setConfig(ARG->m_config); @@ -454,20 +448,6 @@ parse(int argc, const char* const* argv) ++i; } - else if (isArg(i, argc, argv, NULL, "--http", 1)) { - // save listen address - try { - ARG->m_httpAddress = CNetworkAddress(argv[i + 1], - kDefaultPort + 1); - } - catch (XSocketAddress& e) { - LOG((CLOG_PRINT "%s: %s" BYE, - ARG->m_pname, e.what(), ARG->m_pname)); - bye(kExitArgs); - } - ++i; - } - else if (isArg(i, argc, argv, "-n", "--name", 1)) { // save screen name ARG->m_name = argv[++i]; diff --git a/lib/arch/CArch.cpp b/lib/arch/CArch.cpp index d276c4ee..4f95bc90 100644 --- a/lib/arch/CArch.cpp +++ b/lib/arch/CArch.cpp @@ -387,6 +387,18 @@ CArch::getIDOfThread(CArchThread thread) return m_mt->getIDOfThread(thread); } +void +CArch::setInterruptHandler(InterruptFunc func, void* userData) +{ + return m_mt->setInterruptHandler(func, userData); +} + +void +CArch::interrupt() +{ + return m_mt->interrupt(); +} + CArchSocket CArch::newSocket(EAddressFamily family, ESocketType type) { diff --git a/lib/arch/CArch.h b/lib/arch/CArch.h index aad9fefb..9e80d174 100644 --- a/lib/arch/CArch.h +++ b/lib/arch/CArch.h @@ -122,6 +122,8 @@ public: virtual bool isExitedThread(CArchThread); virtual void* getResultOfThread(CArchThread); virtual ThreadID getIDOfThread(CArchThread); + virtual void setInterruptHandler(InterruptFunc, void*); + virtual void interrupt(); // IArchNetwork overrides virtual CArchSocket newSocket(EAddressFamily, ESocketType); diff --git a/lib/arch/CArchMultithreadPosix.cpp b/lib/arch/CArchMultithreadPosix.cpp index 707ef0fb..707f4dfd 100644 --- a/lib/arch/CArchMultithreadPosix.cpp +++ b/lib/arch/CArchMultithreadPosix.cpp @@ -83,7 +83,9 @@ CArchMultithreadPosix* CArchMultithreadPosix::s_instance = NULL; CArchMultithreadPosix::CArchMultithreadPosix() : m_newThreadCalled(false), - m_nextID(0) + m_nextID(0), + m_signalFunc(NULL), + m_signalUserData(NULL) { assert(s_instance == NULL); @@ -558,6 +560,28 @@ CArchMultithreadPosix::getIDOfThread(CArchThread thread) return thread->m_id; } +void +CArchMultithreadPosix::setInterruptHandler(InterruptFunc func, void* userData) +{ + lockMutex(m_threadMutex); + m_signalFunc = func; + m_signalUserData = userData; + unlockMutex(m_threadMutex); +} + +void +CArchMultithreadPosix::interrupt() +{ + lockMutex(m_threadMutex); + if (m_signalFunc != NULL) { + m_signalFunc(m_signalUserData); + } + else { + ARCH->cancelThread(m_mainThread); + } + unlockMutex(m_threadMutex); +} + void CArchMultithreadPosix::startSignalHandler() { @@ -578,7 +602,7 @@ CArchMultithreadPosix::startSignalHandler() if (status == 0) { status = pthread_create(&m_signalThread, &attr, &CArchMultithreadPosix::threadSignalHandler, - m_mainThread); + NULL); pthread_attr_destroy(&attr); } if (status != 0) { @@ -735,10 +759,8 @@ CArchMultithreadPosix::threadCancel(int) } void* -CArchMultithreadPosix::threadSignalHandler(void* vrep) +CArchMultithreadPosix::threadSignalHandler(void*) { - CArchThreadImpl* mainThread = reinterpret_cast(vrep); - // detach pthread_detach(pthread_self()); @@ -768,8 +790,7 @@ CArchMultithreadPosix::threadSignalHandler(void* vrep) sigwait(&sigset); #endif - // if we get here then the signal was raised. cancel the main - // thread so it can shut down cleanly. - ARCH->cancelThread(mainThread); + // if we get here then the signal was raised + ARCH->interrupt(); } } diff --git a/lib/arch/CArchMultithreadPosix.h b/lib/arch/CArchMultithreadPosix.h index b0a1ca36..4b87f0a0 100644 --- a/lib/arch/CArchMultithreadPosix.h +++ b/lib/arch/CArchMultithreadPosix.h @@ -61,6 +61,8 @@ public: virtual bool isExitedThread(CArchThread); virtual void* getResultOfThread(CArchThread); virtual ThreadID getIDOfThread(CArchThread); + virtual void setInterruptHandler(InterruptFunc, void*); + virtual void interrupt(); private: void startSignalHandler(); @@ -91,6 +93,8 @@ private: ThreadID m_nextID; pthread_t m_signalThread; + InterruptFunc m_signalFunc; + void* m_signalUserData; }; #endif diff --git a/lib/arch/CArchNetworkBSD.cpp b/lib/arch/CArchNetworkBSD.cpp index 7de3db05..bb8dae02 100644 --- a/lib/arch/CArchNetworkBSD.cpp +++ b/lib/arch/CArchNetworkBSD.cpp @@ -437,6 +437,10 @@ CArchNetworkBSD::readSocket(CArchSocket s, void* buf, size_t len) ARCH->testCancelThread(); continue; } + else if (errno == EAGAIN) { + n = 0; + break; + } throwError(errno); } } while (false); @@ -458,6 +462,11 @@ CArchNetworkBSD::writeSocket(CArchSocket s, const void* buf, size_t len) ARCH->testCancelThread(); continue; } + else if (errno == EAGAIN) { + // no buffer space + n = 0; + break; + } throwError(errno); } } while (false); @@ -794,6 +803,8 @@ CArchNetworkBSD::throwError(int err) throw XArchNetworkNotConnected(new XArchEvalUnix(err)); case EPIPE: + throw XArchNetworkShutdown(new XArchEvalUnix(err)); + case ECONNABORTED: case ECONNRESET: throw XArchNetworkDisconnected(new XArchEvalUnix(err)); diff --git a/lib/arch/IArchMultithread.h b/lib/arch/IArchMultithread.h index d8319213..c06d4cfa 100644 --- a/lib/arch/IArchMultithread.h +++ b/lib/arch/IArchMultithread.h @@ -268,6 +268,18 @@ public: */ virtual ThreadID getIDOfThread(CArchThread thread) = 0; + //! Set the interrupt handler + /*! + Sets the function to call on receipt of an external interrupt. + By default and when \p func is NULL, the main thread is cancelled. + */ + typedef void (*InterruptFunc)(void*); + virtual void setInterruptHandler(InterruptFunc func, + void* userData) = 0; + + //! Invoke the interrupt handler + virtual void interrupt() = 0; + //@} }; diff --git a/lib/arch/XArch.h b/lib/arch/XArch.h index 44c9394d..027868ad 100644 --- a/lib/arch/XArch.h +++ b/lib/arch/XArch.h @@ -89,7 +89,7 @@ library to indicate various errors. */ XARCH_SUBCLASS(XArchNetwork, XArch); -//! Network insufficient permission +//! Operation would block XARCH_SUBCLASS(XArchNetworkWouldBlock, XArchNetwork); //! Network insufficient permission @@ -116,6 +116,9 @@ XARCH_SUBCLASS(XArchNetworkNoRoute, XArchNetwork); //! Socket not connected XARCH_SUBCLASS(XArchNetworkNotConnected, XArchNetwork); +//! Remote read end of socket has closed +XARCH_SUBCLASS(XArchNetworkShutdown, XArchNetwork); + //! Remote end of socket has disconnected XARCH_SUBCLASS(XArchNetworkDisconnected, XArchNetwork); diff --git a/lib/base/CEventQueue.cpp b/lib/base/CEventQueue.cpp index ce8c0058..9c6d6ca4 100644 --- a/lib/base/CEventQueue.cpp +++ b/lib/base/CEventQueue.cpp @@ -16,37 +16,31 @@ #include "IEventJob.h" #include "CArch.h" +// interrupt handler. this just adds a quit event to the queue. +static +void +interrupt(void*) +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + + // // CEventQueue // -static int g_systemTarget = 0; -CEventQueue* CEventQueue::s_instance = NULL; - CEventQueue::CEventQueue() { - assert(s_instance == NULL); - s_instance = this; - m_mutex = ARCH->newMutex(); + setInstance(this); + m_mutex = ARCH->newMutex(); + ARCH->setInterruptHandler(&interrupt, NULL); } CEventQueue::~CEventQueue() { + ARCH->setInterruptHandler(NULL, NULL); ARCH->closeMutex(m_mutex); - s_instance = NULL; -} - -void* -CEventQueue::getSystemTarget() -{ - // any unique arbitrary pointer will do - return &g_systemTarget; -} - -CEventQueue* -CEventQueue::getInstance() -{ - return s_instance; + setInstance(NULL); } bool @@ -83,7 +77,7 @@ bool CEventQueue::dispatchEvent(const CEvent& event) { void* target = event.getTarget(); - IEventJob* job = getHandler(target); + IEventJob* job = getHandler(event.getType(), target); if (job != NULL) { job->run(event); return true; @@ -164,16 +158,56 @@ void CEventQueue::adoptHandler(void* target, IEventJob* handler) { CArchMutexLock lock(m_mutex); - IEventJob*& job = m_handlers[target]; - delete job; - job = handler; + doAdoptHandler(CEvent::kUnknown, target, handler); +} + +void +CEventQueue::adoptHandler(CEvent::Type type, void* target, IEventJob* handler) +{ + assert(type != CEvent::kUnknown); + CArchMutexLock lock(m_mutex); + doAdoptHandler(type, target, handler); } IEventJob* CEventQueue::orphanHandler(void* target) { CArchMutexLock lock(m_mutex); - CHandlerTable::iterator index = m_handlers.find(target); + return doOrphanHandler(CEvent::kUnknown, target); +} + +IEventJob* +CEventQueue::orphanHandler(CEvent::Type type, void* target) +{ + assert(type != CEvent::kUnknown); + CArchMutexLock lock(m_mutex); + return doOrphanHandler(type, target); +} + +void +CEventQueue::removeHandler(void* target) +{ + delete orphanHandler(target); +} + +void +CEventQueue::removeHandler(CEvent::Type type, void* target) +{ + delete orphanHandler(type, target); +} + +void +CEventQueue::doAdoptHandler(CEvent::Type type, void* target, IEventJob* handler) +{ + IEventJob*& job = m_handlers[CTypeTarget(type, target)]; + delete job; + job = handler; +} + +IEventJob* +CEventQueue::doOrphanHandler(CEvent::Type type, void* target) +{ + CHandlerTable::iterator index = m_handlers.find(CTypeTarget(type, target)); if (index != m_handlers.end()) { IEventJob* handler = index->second; m_handlers.erase(index); @@ -191,16 +225,19 @@ CEventQueue::isEmpty() const } IEventJob* -CEventQueue::getHandler(void* target) const +CEventQueue::getHandler(CEvent::Type type, void* target) const { CArchMutexLock lock(m_mutex); - CHandlerTable::const_iterator index = m_handlers.find(target); + CHandlerTable::const_iterator index = + m_handlers.find(CTypeTarget(type, target)); if (index != m_handlers.end()) { return index->second; } - else { - return NULL; + index = m_handlers.find(CTypeTarget(CEvent::kUnknown, target)); + if (index != m_handlers.end()) { + return index->second; } + return NULL; } UInt32 @@ -217,7 +254,7 @@ CEventQueue::saveEvent(const CEvent& event) } else { // make a new id - id = static_cast(m_oldEventIDs.size()); + id = static_cast(m_events.size()); } // save data @@ -309,7 +346,31 @@ CEventQueue::getNextTimerTimeout() const // -// CXWindowsScreen::CTimer +// CEventQueue::CTypeTarget +// + +CEventQueue::CTypeTarget::CTypeTarget(CEvent::Type type, void* target) : + m_type(type), + m_target(target) +{ + // do nothing +} + +CEventQueue::CTypeTarget::~CTypeTarget() +{ + // do nothing +} + +bool +CEventQueue::CTypeTarget::operator<(const CTypeTarget& tt) const +{ + return (m_type < tt.m_type || + (m_type == tt.m_type && m_target < tt.m_target)); +} + + +// +// CEventQueue::CTimer // CEventQueue::CTimer::CTimer(CEventQueueTimer* timer, diff --git a/lib/base/CEventQueue.h b/lib/base/CEventQueue.h index e6ac9ee2..5443c394 100644 --- a/lib/base/CEventQueue.h +++ b/lib/base/CEventQueue.h @@ -33,27 +33,6 @@ public: CEventQueue(); virtual ~CEventQueue(); - //! @name manipulators - //@{ - - //@} - //! @name accessors - //@{ - - //! Get the system event type target - /*! - Returns the target to use for dispatching \c CEvent::kSystem events. - */ - static void* getSystemTarget(); - - //! Get the singleton instance - /*! - Returns the singleton instance of the event queue - */ - static CEventQueue* getInstance(); - - //@} - // IEventQueue overrides virtual bool getEvent(CEvent& event, double timeout = -1.0); virtual bool dispatchEvent(const CEvent& event); @@ -64,9 +43,14 @@ public: newOneShotTimer(double duration, void* target = NULL); virtual void deleteTimer(CEventQueueTimer*); virtual void adoptHandler(void* target, IEventJob* dispatcher); + virtual void adoptHandler(CEvent::Type type, + void* target, IEventJob* handler); virtual IEventJob* orphanHandler(void* target); + virtual IEventJob* orphanHandler(CEvent::Type type, void* target); + virtual void removeHandler(void* target); + virtual void removeHandler(CEvent::Type type, void* target); virtual bool isEmpty() const; - virtual IEventJob* getHandler(void* target) const; + virtual IEventJob* getHandler(CEvent::Type type, void* target) const; protected: //! @name manipulators @@ -137,11 +121,26 @@ protected: //@} private: + void doAdoptHandler(CEvent::Type type, + void* target, IEventJob* handler); + IEventJob* doOrphanHandler(CEvent::Type type, void* target); + UInt32 saveEvent(const CEvent& event); bool hasTimerExpired(CEvent& event); double getNextTimerTimeout() const; private: + class CTypeTarget { + public: + CTypeTarget(CEvent::Type type, void* target); + ~CTypeTarget(); + + bool operator<(const CTypeTarget&) const; + + private: + CEvent::Type m_type; + void* m_target; + }; class CTimer { public: CTimer(CEventQueueTimer*, double timeout, void* target, bool oneShot); @@ -172,9 +171,7 @@ private: typedef CPriorityQueue CTimerQueue; typedef std::map CEventTable; typedef std::vector CEventIDList; - typedef std::map CHandlerTable; - - static CEventQueue* s_instance; + typedef std::map CHandlerTable; CArchMutex m_mutex; diff --git a/lib/io/CInputStreamFilter.cpp b/lib/base/IEventQueue.cpp similarity index 52% rename from lib/io/CInputStreamFilter.cpp rename to lib/base/IEventQueue.cpp index 6d058b4a..a10621f2 100644 --- a/lib/io/CInputStreamFilter.cpp +++ b/lib/base/IEventQueue.cpp @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2002 Chris Schoeneman + * 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 @@ -12,28 +12,32 @@ * GNU General Public License for more details. */ -#include "CInputStreamFilter.h" +#include "IEventQueue.h" // -// CInputStreamFilter +// IEventQueue // -CInputStreamFilter::CInputStreamFilter(IInputStream* stream, bool adopted) : - m_stream(stream), - m_adopted(adopted) +static int g_systemTarget = 0; +IEventQueue* IEventQueue::s_instance = NULL; + +void* +IEventQueue::getSystemTarget() { - assert(m_stream != NULL); + // any unique arbitrary pointer will do + return &g_systemTarget; } -CInputStreamFilter::~CInputStreamFilter() +IEventQueue* +IEventQueue::getInstance() { - if (m_adopted) { - delete m_stream; - } + assert(s_instance != NULL); + return s_instance; } -IInputStream* -CInputStreamFilter::getStream() const +void +IEventQueue::setInstance(IEventQueue* instance) { - return m_stream; + assert(s_instance == NULL || instance == NULL); + s_instance = instance; } diff --git a/lib/base/IEventQueue.h b/lib/base/IEventQueue.h index aaaf9f33..0d144ebf 100644 --- a/lib/base/IEventQueue.h +++ b/lib/base/IEventQueue.h @@ -16,9 +16,10 @@ #define IEVENTQUEUE_H #include "IInterface.h" -#include "BasicTypes.h" +#include "CEvent.h" + +#define EVENTQUEUE IEventQueue::getInstance() -class CEvent; class IEventJob; // Opaque type for timer info. This is defined by subclasses of @@ -107,9 +108,21 @@ public: /*! Registers an event handler for \p target. The \p handler is adopted. Any existing handler for the target is deleted. + \c dispatchEvent() will invoke \p handler for any event for + \p target that doesn't have a type specific handler. */ virtual void adoptHandler(void* target, IEventJob* handler) = 0; + //! Register an event handler for an event type + /*! + Registers an event handler for \p type and \p target. The \p handler + is adopted. Any existing handler for the type,target pair is deleted. + \c dispatchEvent() will invoke \p handler for any event for \p target + of type \p type. + */ + virtual void adoptHandler(CEvent::Type type, + void* target, IEventJob* handler) = 0; + //! Unregister an event handler /*! Unregisters an event handler for \p target and returns it. @@ -118,6 +131,27 @@ public: */ virtual IEventJob* orphanHandler(void* target) = 0; + //! Unregister an event handler for an event type + /*! + Unregisters an event handler for the \p type, \p target pair and + returns it. Returns NULL if there was no such handler. The + client becomes responsible for deleting the returned handler. + */ + virtual IEventJob* orphanHandler(CEvent::Type type, void* target) = 0; + + //! Unregister an event handler + /*! + Unregisters an event handler for \p target and deletes it. + */ + virtual void removeHandler(void* target) = 0; + + //! Unregister an event handler for an event type + /*! + Unregisters an event handler for the \p type, \p target pair and + deletes it. + */ + virtual void removeHandler(CEvent::Type type, void* target) = 0; + //@} //! @name accessors //@{ @@ -131,12 +165,40 @@ public: //! Get an event handler /*! - Finds and returns the event handler for \p target, or NULL if - there is no such handler. + Finds and returns the event handler for the \p type, \p target pair. + If there is no such handler, returns the handler for \p target. If + that doesn't exist, returns NULL. */ - virtual IEventJob* getHandler(void* target) const = 0; + virtual IEventJob* getHandler(CEvent::Type type, void* target) const = 0; + + //! Get the system event type target + /*! + Returns the target to use for dispatching \c CEvent::kSystem events. + */ + static void* getSystemTarget(); + + //! Get the singleton instance + /*! + Returns the singleton instance of the event queue + */ + static IEventQueue* getInstance(); //@} + +protected: + //! @name manipulators + //@{ + + //! Set the singleton instance + /*! + Sets the singleton instance of the event queue + */ + static void setInstance(IEventQueue*); + + //@} + +private: + static IEventQueue* s_instance; }; #endif diff --git a/lib/base/Makefile.am b/lib/base/Makefile.am index db17a892..f146cb11 100644 --- a/lib/base/Makefile.am +++ b/lib/base/Makefile.am @@ -35,6 +35,7 @@ libbase_a_SOURCES = \ CStopwatch.cpp \ CStringUtil.cpp \ CUnicode.cpp \ + IEventQueue.cpp \ LogOutputters.cpp \ XBase.cpp \ CEvent.h \ diff --git a/lib/io/CBufferedInputStream.cpp b/lib/io/CBufferedInputStream.cpp deleted file mode 100644 index 87cf78a3..00000000 --- a/lib/io/CBufferedInputStream.cpp +++ /dev/null @@ -1,140 +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. - */ - -#include "CBufferedInputStream.h" -#include "CLock.h" -#include "CMutex.h" -#include "CThread.h" -#include "CStopwatch.h" -#include "IJob.h" -#include "XIO.h" -#include - -// -// CBufferedInputStream -// - -CBufferedInputStream::CBufferedInputStream( - CMutex* mutex, IJob* adoptedEmptyCB, IJob* adoptedCloseCB) : - m_mutex(mutex), - m_empty(mutex, true), - m_emptyCB(adoptedEmptyCB), - m_closeCB(adoptedCloseCB), - m_closed(false), - m_hungup(false) -{ - assert(m_mutex != NULL); -} - -CBufferedInputStream::~CBufferedInputStream() -{ - delete m_closeCB; - delete m_emptyCB; -} - -void -CBufferedInputStream::write(const void* buffer, UInt32 n) -{ - if (!m_hungup && n > 0) { - m_buffer.write(buffer, n); - m_empty = (m_buffer.getSize() == 0); - m_empty.broadcast(); - } -} - -void -CBufferedInputStream::hangup() -{ - m_hungup = true; - m_empty.broadcast(); -} - -UInt32 -CBufferedInputStream::readNoLock(void* buffer, UInt32 n, double timeout) -{ - if (m_closed) { - throw XIOClosed(); - } - if (n == 0) { - return n; - } - - // wait for data, hangup, or timeout - CStopwatch timer(true); - while (!m_hungup && m_empty == true) { - if (!m_empty.wait(timer, timeout)) { - // timed out - return (UInt32)-1; - } - } - - // read data - const UInt32 count = m_buffer.getSize(); - if (n > count) { - n = count; - } - if (n > 0) { - if (buffer != NULL) { - memcpy(buffer, m_buffer.peek(n), n); - } - m_buffer.pop(n); - } - - // update empty state - if (m_buffer.getSize() == 0) { - m_empty = true; - m_empty.broadcast(); - if (m_emptyCB != NULL) { - m_emptyCB->run(); - } - } - return n; -} - -UInt32 -CBufferedInputStream::getSizeNoLock() const -{ - return m_buffer.getSize(); -} - -void -CBufferedInputStream::close() -{ - CLock lock(m_mutex); - if (m_closed) { - throw XIOClosed(); - } - - m_closed = true; - m_hungup = true; - m_buffer.pop(m_buffer.getSize()); - m_empty.broadcast(); - if (m_closeCB != NULL) { - m_closeCB->run(); - } -} - -UInt32 -CBufferedInputStream::read(void* buffer, UInt32 n, double timeout) -{ - CLock lock(m_mutex); - return readNoLock(buffer, n, timeout); -} - -UInt32 -CBufferedInputStream::getSize() const -{ - CLock lock(m_mutex); - return getSizeNoLock(); -} diff --git a/lib/io/CBufferedInputStream.h b/lib/io/CBufferedInputStream.h deleted file mode 100644 index 01137563..00000000 --- a/lib/io/CBufferedInputStream.h +++ /dev/null @@ -1,101 +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 CBUFFEREDINPUTSTREAM_H -#define CBUFFEREDINPUTSTREAM_H - -#include "IInputStream.h" -#include "CStreamBuffer.h" -#include "CCondVar.h" - -class CMutex; -class IJob; - -//! Memory buffer input stream -/*! -This class provides an input stream that reads from a memory buffer. -It also provides a means for the owner to ensure thread safe access. -Typically, an owner object will make this object visible to clients -that need access to an IInputStream while using the CBufferedInputStream -methods to write data to the stream. -*/ -class CBufferedInputStream : public IInputStream { -public: - /*! - The \c mutex must not be NULL and will be used to ensure thread - safe access. If \c adoptedCloseCB is not NULL it will be called - when close() is called, allowing the creator to detect the close. - If adoptedEmptyCB is not NULL, it will be called whenever the - buffer becomes empty (except it won't be called by the c'tor nor - when the buffer is closed). - */ - CBufferedInputStream(CMutex* mutex, - IJob* adoptedEmptyCB, IJob* adoptedCloseCB); - ~CBufferedInputStream(); - - //! @name manipulators - //@{ - - //! Write data to stream - /*! - Write \c n bytes from \c buffer to the stream. The mutex must - be locked before calling this. - */ - void write(const void* buffer, UInt32 n); - - //! Hangup stream - /*! - Causes read() to always return immediately. If there is no - more data to read then it returns 0. Further writes are discarded. - The mutex must be locked before calling this. - */ - void hangup(); - - //! Read from stream - /*! - This is the same as read() but the mutex must be locked before - calling this. - */ - UInt32 readNoLock(void*, UInt32 n, double timeout); - - //@} - //! @name accessors - //@{ - - //! Get remaining size of stream - /*! - This is the same as getSize() but the mutex must be locked before - calling this. - */ - UInt32 getSizeNoLock() const; - - //@} - - // IInputStream overrides - // these all lock the mutex for their duration - virtual void close(); - virtual UInt32 read(void*, UInt32 n, double timeout); - virtual UInt32 getSize() const; - -private: - CMutex* m_mutex; - CCondVar m_empty; - IJob* m_emptyCB; - IJob* m_closeCB; - CStreamBuffer m_buffer; - bool m_closed; - bool m_hungup; -}; - -#endif diff --git a/lib/io/CBufferedOutputStream.cpp b/lib/io/CBufferedOutputStream.cpp deleted file mode 100644 index 2b666d1f..00000000 --- a/lib/io/CBufferedOutputStream.cpp +++ /dev/null @@ -1,104 +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. - */ - -#include "CBufferedOutputStream.h" -#include "XIO.h" -#include "CLock.h" -#include "CMutex.h" -#include "CThread.h" -#include "IJob.h" - -// -// CBufferedOutputStream -// - -CBufferedOutputStream::CBufferedOutputStream( - CMutex* mutex, IJob* adoptedFillCB, IJob* adoptedCloseCB) : - m_mutex(mutex), - m_fillCB(adoptedFillCB), - m_closeCB(adoptedCloseCB), - m_empty(mutex, true), - m_closed(false) -{ - assert(m_mutex != NULL); -} - -CBufferedOutputStream::~CBufferedOutputStream() -{ - delete m_closeCB; - delete m_fillCB; -} - -const void* -CBufferedOutputStream::peek(UInt32 n) -{ - return m_buffer.peek(n); -} - -void -CBufferedOutputStream::pop(UInt32 n) -{ - m_buffer.pop(n); - if (m_buffer.getSize() == 0) { - m_empty.broadcast(); - } -} - -UInt32 -CBufferedOutputStream::getSize() const -{ - return m_buffer.getSize(); -} - -void -CBufferedOutputStream::close() -{ - CLock lock(m_mutex); - if (m_closed) { - throw XIOClosed(); - } - - m_closed = true; - if (m_closeCB != NULL) { - m_closeCB->run(); - } -} - -UInt32 -CBufferedOutputStream::write(const void* buffer, UInt32 n) -{ - CLock lock(m_mutex); - if (m_closed) { - throw XIOClosed(); - } - - bool wasEmpty = (m_buffer.getSize() == 0); - m_buffer.write(buffer, n); - if (wasEmpty && n > 0) { - if (m_fillCB != NULL) { - m_fillCB->run(); - } - } - return n; -} - -void -CBufferedOutputStream::flush() -{ - // wait until all data is written - CLock lock(m_mutex); - while (m_buffer.getSize() > 0) { - m_empty.wait(); - } -} diff --git a/lib/io/CBufferedOutputStream.h b/lib/io/CBufferedOutputStream.h deleted file mode 100644 index 1853a161..00000000 --- a/lib/io/CBufferedOutputStream.h +++ /dev/null @@ -1,92 +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 CBUFFEREDOUTPUTSTREAM_H -#define CBUFFEREDOUTPUTSTREAM_H - -#include "IOutputStream.h" -#include "CStreamBuffer.h" -#include "CCondVar.h" - -class CMutex; -class IJob; - -//! Memory buffer output stream -/*! -This class provides an output stream that writes to a memory buffer. -It also provides a means for the owner to ensure thread safe access. -Typically, an owner object will make this object visible to clients -that need access to an IOutputStream while using the CBufferedOutputStream -methods to read the data written to the stream. -*/ -class CBufferedOutputStream : public IOutputStream { -public: - /*! - The \c mutex must not be NULL and will be used to ensure thread - safe access. If \c adoptedCloseCB is not NULL it will be called - when close() is called, allowing the creator to detect the close. - If \c adoptedFillCB is not NULL, it will be called whenever the - buffer becomes non-empty. - */ - CBufferedOutputStream(CMutex* mutex, - IJob* adoptedFillCB, IJob* adoptedCloseCB); - ~CBufferedOutputStream(); - - //! @name manipulators - //@{ - - //! Read data without removing from buffer - /*! - Returns a buffer of \c n bytes (which must be <= getSize()). The - caller must not modify the buffer nor delete it. The mutex must - be locked before calling this. - */ - const void* peek(UInt32 n); - - //! Discard data - /*! - Discards the next \c n bytes. If \c n >= getSize() then the buffer - is cleared. The mutex must be locked before calling this. - */ - void pop(UInt32 n); - - //@} - //! @name accessors - //@{ - - //! Get size of buffer - /*! - Returns the number of bytes in the buffer. The mutex must be locked - before calling this. - */ - UInt32 getSize() const; - - //@} - - // IOutputStream overrides - // these all lock the mutex for their duration - virtual void close(); - virtual UInt32 write(const void*, UInt32 n); - virtual void flush(); - -private: - CMutex* m_mutex; - IJob* m_fillCB; - IJob* m_closeCB; - CCondVar m_empty; - CStreamBuffer m_buffer; - bool m_closed; -}; - -#endif diff --git a/lib/io/CInputStreamFilter.h b/lib/io/CInputStreamFilter.h deleted file mode 100644 index 9274a810..00000000 --- a/lib/io/CInputStreamFilter.h +++ /dev/null @@ -1,52 +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 CINPUTSTREAMFILTER_H -#define CINPUTSTREAMFILTER_H - -#include "IInputStream.h" - -//! A filtering input stream -/*! -This class wraps an input stream. Subclasses provide indirect access -to the stream, typically performing some filtering. -*/ -class CInputStreamFilter : public IInputStream { -public: - /*! - Create a wrapper around \c stream. Iff \c adoptStream is true then - this object takes ownership of the stream and will delete it in the - d'tor. - */ - CInputStreamFilter(IInputStream* stream, bool adoptStream = true); - ~CInputStreamFilter(); - - // IInputStream overrides - virtual void close() = 0; - virtual UInt32 read(void*, UInt32 n, double timeout) = 0; - virtual UInt32 getSize() const = 0; - -protected: - //! Get the stream - /*! - Returns the stream passed to the c'tor. - */ - IInputStream* getStream() const; - -private: - IInputStream* m_stream; - bool m_adopted; -}; - -#endif diff --git a/lib/io/COutputStreamFilter.cpp b/lib/io/COutputStreamFilter.cpp deleted file mode 100644 index d90465a7..00000000 --- a/lib/io/COutputStreamFilter.cpp +++ /dev/null @@ -1,39 +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. - */ - -#include "COutputStreamFilter.h" - -// -// COutputStreamFilter -// - -COutputStreamFilter::COutputStreamFilter(IOutputStream* stream, bool adopted) : - m_stream(stream), - m_adopted(adopted) -{ - assert(m_stream != NULL); -} - -COutputStreamFilter::~COutputStreamFilter() -{ - if (m_adopted) { - delete m_stream; - } -} - -IOutputStream* -COutputStreamFilter::getStream() const -{ - return m_stream; -} diff --git a/lib/io/COutputStreamFilter.h b/lib/io/COutputStreamFilter.h deleted file mode 100644 index 027e32dc..00000000 --- a/lib/io/COutputStreamFilter.h +++ /dev/null @@ -1,52 +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 COUTPUTSTREAMFILTER_H -#define COUTPUTSTREAMFILTER_H - -#include "IOutputStream.h" - -//! A filtering output stream -/*! -This class wraps an output stream. Subclasses provide indirect access -to the stream, typically performing some filtering. -*/ -class COutputStreamFilter : public IOutputStream { -public: - /*! - Create a wrapper around \c stream. Iff \c adoptStream is true then - this object takes ownership of the stream and will delete it in the - d'tor. - */ - COutputStreamFilter(IOutputStream* stream, bool adoptStream = true); - ~COutputStreamFilter(); - - // IOutputStream overrides - virtual void close() = 0; - virtual UInt32 write(const void*, UInt32 count) = 0; - virtual void flush() = 0; - -protected: - //! Get the stream - /*! - Returns the stream passed to the c'tor. - */ - IOutputStream* getStream() const; - -private: - IOutputStream* m_stream; - bool m_adopted; -}; - -#endif diff --git a/lib/io/CStreamFilter.cpp b/lib/io/CStreamFilter.cpp new file mode 100644 index 00000000..00c064a0 --- /dev/null +++ b/lib/io/CStreamFilter.cpp @@ -0,0 +1,105 @@ +/* + * 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 "CStreamFilter.h" + +// +// CStreamFilter +// + +CStreamFilter::CStreamFilter(IStream* stream, bool adoptStream) : + m_stream(stream), + m_adopted(adoptStream) +{ + // do nothing +} + +CStreamFilter::~CStreamFilter() +{ + if (m_adopted) { + delete m_stream; + } +} + +void +CStreamFilter::close() +{ + getStream()->close(); +} + +UInt32 +CStreamFilter::read(void* buffer, UInt32 n) +{ + return getStream()->read(buffer, n); +} + +void +CStreamFilter::write(const void* buffer, UInt32 n) +{ + getStream()->write(buffer, n); +} + +void +CStreamFilter::flush() +{ + getStream()->flush(); +} + +void +CStreamFilter::shutdownInput() +{ + getStream()->shutdownInput(); +} + +void +CStreamFilter::shutdownOutput() +{ + getStream()->shutdownOutput(); +} + +void +CStreamFilter::setEventFilter(IEventJob* filter) +{ + getStream()->setEventFilter(filter); +} + +void* +CStreamFilter::getEventTarget() const +{ + return const_cast(reinterpret_cast(this)); +} + +bool +CStreamFilter::isReady() const +{ + return getStream()->isReady(); +} + +UInt32 +CStreamFilter::getSize() const +{ + return getStream()->getSize(); +} + +IEventJob* +CStreamFilter::getEventFilter() const +{ + return getStream()->getEventFilter(); +} + +IStream* +CStreamFilter::getStream() const +{ + return m_stream; +} diff --git a/lib/io/CStreamFilter.h b/lib/io/CStreamFilter.h new file mode 100644 index 00000000..5706cf4a --- /dev/null +++ b/lib/io/CStreamFilter.h @@ -0,0 +1,62 @@ +/* + * 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 CSTREAMFILTER_H +#define CSTREAMFILTER_H + +#include "IStream.h" + +//! A stream filter +/*! +This class wraps a stream. Subclasses provide indirect access +to the wrapped stream, typically performing some filtering. +*/ +class CStreamFilter : public IStream { +public: + /*! + Create a wrapper around \c stream. Iff \c adoptStream is true then + this object takes ownership of the stream and will delete it in the + d'tor. + */ + CStreamFilter(IStream* stream, bool adoptStream = true); + ~CStreamFilter(); + + // IStream overrides + // These all just forward to the underlying stream. Override as + // necessary. + virtual void close(); + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void flush(); + virtual void shutdownInput(); + virtual void shutdownOutput(); + virtual void setEventFilter(IEventJob* filter); + virtual void* getEventTarget() const; + virtual bool isReady() const; + virtual UInt32 getSize() const; + virtual IEventJob* getEventFilter() const; + +protected: + //! Get the stream + /*! + Returns the stream passed to the c'tor. + */ + IStream* getStream() const; + +private: + IStream* m_stream; + bool m_adopted; +}; + +#endif diff --git a/lib/io/IInputStream.h b/lib/io/IInputStream.h deleted file mode 100644 index a1b250de..00000000 --- a/lib/io/IInputStream.h +++ /dev/null @@ -1,67 +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 IINPUTSTREAM_H -#define IINPUTSTREAM_H - -#include "IInterface.h" -#include "BasicTypes.h" - -//! Input stream interface -/*! -Defines the interface for all input streams. -*/ -class IInputStream : public IInterface { -public: - //! @name manipulators - //@{ - - //! Close the stream - /*! - Closes the stream. Attempting to read() after close() throws - XIOClosed and getSize() always returns zero. - */ - virtual void close() = 0; - - //! Read from stream - /*! - Read up to \c n bytes into buffer, returning the number read. - Blocks for up to \c timeout seconds if no data is available but does - not wait if any data is available, even if less than \c n bytes. - If \c timeout < 0 then it blocks indefinitely until data is available. - If \c buffer is NULL then the data is discarded. Returns (UInt32)-1 if - it times out and 0 if no data is available and the other end of the - stream has hungup. - - (cancellation point) - */ - virtual UInt32 read(void* buffer, UInt32 n, double timeout) = 0; - - //@} - //! @name accessors - //@{ - - //! Get remaining size of stream - /*! - Returns a conservative estimate of the available bytes to read - (i.e. a number not greater than the actual number of bytes). - Some streams may not be able to determine this and will always - return zero. - */ - virtual UInt32 getSize() const = 0; - - //@} -}; - -#endif diff --git a/lib/io/IOutputStream.h b/lib/io/IOutputStream.h deleted file mode 100644 index 5540ce0a..00000000 --- a/lib/io/IOutputStream.h +++ /dev/null @@ -1,58 +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 IOUTPUTSTREAM_H -#define IOUTPUTSTREAM_H - -#include "IInterface.h" -#include "BasicTypes.h" - -//! Output stream interface -/*! -Defines the interface for all output streams. -*/ -class IOutputStream : public IInterface { -public: - //! @name manipulators - //@{ - - //! Close the stream - /*! - Closes the stream. Attempting to write() after close() throws - XIOClosed. - */ - virtual void close() = 0; - - //! Write to stream - /*! - Write \c n bytes from \c buffer to the stream. If this can't - complete immeditely it will block. If cancelled, an indeterminate - amount of data may have been written. - - (cancellation point) - */ - virtual UInt32 write(const void* buffer, UInt32 n) = 0; - - //! Flush the stream - /*! - Waits until all buffered data has been written to the stream. - - (cancellation point) - */ - virtual void flush() = 0; - - //@} -}; - -#endif diff --git a/lib/io/IStream.cpp b/lib/io/IStream.cpp new file mode 100644 index 00000000..42947190 --- /dev/null +++ b/lib/io/IStream.cpp @@ -0,0 +1,55 @@ +/* + * 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 "IStream.h" + +// +// IStream +// + +CEvent::Type IStream::s_inputReadyEvent = CEvent::kUnknown; +CEvent::Type IStream::s_outputFlushedEvent = CEvent::kUnknown; +CEvent::Type IStream::s_outputErrorEvent = CEvent::kUnknown; +CEvent::Type IStream::s_inputShutdownEvent = CEvent::kUnknown; +CEvent::Type IStream::s_outputShutdownEvent = CEvent::kUnknown; + +CEvent::Type +IStream::getInputReadyEvent() +{ + return CEvent::registerTypeOnce(s_inputReadyEvent); +} + +CEvent::Type +IStream::getOutputFlushedEvent() +{ + return CEvent::registerTypeOnce(s_outputFlushedEvent); +} + +CEvent::Type +IStream::getOutputErrorEvent() +{ + return CEvent::registerTypeOnce(s_outputErrorEvent); +} + +CEvent::Type +IStream::getInputShutdownEvent() +{ + return CEvent::registerTypeOnce(s_inputShutdownEvent); +} + +CEvent::Type +IStream::getOutputShutdownEvent() +{ + return CEvent::registerTypeOnce(s_outputShutdownEvent); +} diff --git a/lib/io/IStream.h b/lib/io/IStream.h new file mode 100644 index 00000000..1dc07570 --- /dev/null +++ b/lib/io/IStream.h @@ -0,0 +1,175 @@ +/* + * 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 ISTREAM_H +#define ISTREAM_H + +#include "IInterface.h" +#include "CEvent.h" + +class IEventJob; + +//! Bidirectional stream interface +/*! +Defines the interface for all streams. +*/ +class IStream : public IInterface { +public: + //! @name manipulators + //@{ + + //! Close the stream + /*! + Closes the stream. Pending input data and buffered output data + are discarded. Use \c flush() before \c close() to send buffered + output data. Attempts to \c read() after a close return 0, + attempts to \c write() generate output error events, and attempts + to \c flush() return immediately. + */ + virtual void close() = 0; + + //! Read from stream + /*! + Read up to \p n bytes into \p buffer, returning the number read + (zero if no data is available or input is shutdown). \p buffer + may be NULL in which case the data is discarded. + */ + virtual UInt32 read(void* buffer, UInt32 n) = 0; + + //! Write to stream + /*! + Write \c n bytes from \c buffer to the stream. If this can't + complete immediately it will block. Data may be buffered in + order to return more quickly. A output error event is generated + when writing fails. + */ + virtual void write(const void* buffer, UInt32 n) = 0; + + //! Flush the stream + /*! + Waits until all buffered data has been written to the stream. + */ + virtual void flush() = 0; + + //! Shutdown input + /*! + Shutdown the input side of the stream. Any pending input data is + discarded and further reads immediately return 0. + */ + virtual void shutdownInput() = 0; + + //! Shutdown output + /*! + Shutdown the output side of the stream. Any buffered output data + is discarded and further writes generate output error events. Use + \c flush() before \c shutdownOutput() to send buffered output data. + */ + virtual void shutdownOutput() = 0; + + //! Set the event filter + /*! + If not NULL, the \p filter is passed any event that would've been + added to the queue. The filter can discard the event, modify it + and add it to the queue, and add other events. The default filter + is NULL. The caller retains ownership of the filter. + */ + virtual void setEventFilter(IEventJob* filter) = 0; + + //@} + + //@} + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the event target for events generated by this stream. + */ + virtual void* getEventTarget() const = 0; + + //! Test if \c read() will succeed + /*! + Returns true iff an immediate \c read() will return data. This + may or may not be the same as \c getSize() > 0, depending on the + stream type. + */ + virtual bool isReady() const = 0; + + //! Get bytes available to read + /*! + Returns a conservative estimate of the available bytes to read + (i.e. a number not greater than the actual number of bytes). + Some streams may not be able to determine this and will always + return zero. + */ + virtual UInt32 getSize() const = 0; + + //! Get the event filter + /*! + Returns the current event filter. + */ + virtual IEventJob* getEventFilter() const = 0; + + //! Get input ready event type + /*! + Returns the input ready event type. A stream sends this event + when \c read() will return with data. + */ + static CEvent::Type getInputReadyEvent(); + + //! Get output flushed event type + /*! + Returns the output flushed event type. A stream sends this event + when the output buffer has been flushed. If there have been no + writes since the event was posted, calling \c shutdownOutput() or + \c close() will not discard any data and \c flush() will return + immediately. + */ + static CEvent::Type getOutputFlushedEvent(); + + //! Get output error event type + /*! + Returns the output error event type. A stream sends this event + when a write has failed. + */ + static CEvent::Type getOutputErrorEvent(); + + //! Get input shutdown event type + /*! + Returns the input shutdown event type. This is sent when the + input side of the stream has shutdown. When the input has + shutdown, no more data will ever be available to read. + */ + static CEvent::Type getInputShutdownEvent(); + + //! Get output shutdown event type + /*! + Returns the output shutdown event type. This is sent when the + output side of the stream has shutdown. When the output has + shutdown, no more data can ever be written to the stream. Any + attempt to do so will generate a output error event. + */ + static CEvent::Type getOutputShutdownEvent(); + + //@} + +private: + static CEvent::Type s_inputReadyEvent; + static CEvent::Type s_outputFlushedEvent; + static CEvent::Type s_outputErrorEvent; + static CEvent::Type s_inputShutdownEvent; + static CEvent::Type s_outputShutdownEvent; +}; + +#endif diff --git a/lib/io/IStreamFilterFactory.h b/lib/io/IStreamFilterFactory.h index 2f459ef5..6e6c86ef 100644 --- a/lib/io/IStreamFilterFactory.h +++ b/lib/io/IStreamFilterFactory.h @@ -17,10 +17,7 @@ #include "IInterface.h" -class CInputStreamFilter; -class COutputStreamFilter; -class IInputStream; -class IOutputStream; +class IStream; //! Stream filter factory interface /*! @@ -28,21 +25,12 @@ This interface provides factory methods to create stream filters. */ class IStreamFilterFactory : public IInterface { public: - //! Create input filter + //! Create filter /*! - Create and return an input stream filter. The caller must delete the - returned object. + Create and return a stream filter on \p stream. The caller must + delete the returned object. */ - virtual CInputStreamFilter* - createInput(IInputStream*, bool adoptStream) = 0; - - //! Create output filter - /*! - Create and return an output stream filter. The caller must delete the - returned object. - */ - virtual COutputStreamFilter* - createOutput(IOutputStream*, bool adoptStream) = 0; + virtual IStream* create(IStream* stream, bool adoptStream) = 0; }; #endif diff --git a/lib/io/Makefile.am b/lib/io/Makefile.am index 8bac4c8e..ad4f67fa 100644 --- a/lib/io/Makefile.am +++ b/lib/io/Makefile.am @@ -25,19 +25,13 @@ MAINTAINERCLEANFILES = \ noinst_LIBRARIES = libio.a libio_a_SOURCES = \ - CBufferedInputStream.cpp \ - CBufferedOutputStream.cpp \ - CInputStreamFilter.cpp \ - COutputStreamFilter.cpp \ CStreamBuffer.cpp \ + CStreamFilter.cpp \ + IStream.cpp \ XIO.cpp \ - CBufferedInputStream.h \ - CBufferedOutputStream.h \ - CInputStreamFilter.h \ - COutputStreamFilter.h \ CStreamBuffer.h \ - IInputStream.h \ - IOutputStream.h \ + CStreamFilter.h \ + IStream.h \ IStreamFilterFactory.h \ XIO.h \ $(NULL) diff --git a/lib/io/XIO.cpp b/lib/io/XIO.cpp index 710c4190..b7101a4d 100644 --- a/lib/io/XIO.cpp +++ b/lib/io/XIO.cpp @@ -34,3 +34,14 @@ XIOEndOfStream::getWhat() const throw() { return format("XIOEndOfStream", "reached end of stream"); } + + +// +// XIOWouldBlock +// + +CString +XIOWouldBlock::getWhat() const throw() +{ + return format("XIOWouldBlock", "stream operation would block"); +} diff --git a/lib/io/XIO.h b/lib/io/XIO.h index 31871bf5..cc41ef40 100644 --- a/lib/io/XIO.h +++ b/lib/io/XIO.h @@ -39,4 +39,10 @@ Thrown when attempting to read beyond the end of a stream. */ XBASE_SUBCLASS_WHAT(XIOEndOfStream, XIO); +//! I/O would block exception +/*! +Thrown if an operation on a stream would block. +*/ +XBASE_SUBCLASS_WHAT(XIOWouldBlock, XIO); + #endif diff --git a/lib/net/CSocketMultiplexer.cpp b/lib/net/CSocketMultiplexer.cpp new file mode 100644 index 00000000..46191b3c --- /dev/null +++ b/lib/net/CSocketMultiplexer.cpp @@ -0,0 +1,304 @@ +/* + * 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 "CSocketMultiplexer.h" +#include "ISocketMultiplexerJob.h" +#include "CCondVar.h" +#include "CLock.h" +#include "CMutex.h" +#include "CThread.h" +#include "TMethodJob.h" +#include "CArch.h" +#include "XArch.h" +#include "stdvector.h" + +// +// CSocketMultiplexer +// + +CSocketMultiplexer* CSocketMultiplexer::s_instance = NULL; + +CSocketMultiplexer::CSocketMultiplexer() : + m_mutex(new CMutex), + m_pollable(new CCondVar(m_mutex, false)), + m_polling(new CCondVar(m_mutex, false)), + m_thread(NULL), + m_update(false) +{ + assert(s_instance == NULL); + + // this pointer just has to be unique and not NULL. it will + // never be dereferenced. it's used to identify cursor nodes + // in the jobs list. + m_cursorMark = reinterpret_cast(this); + + // start thread + m_thread = new CThread(new TMethodJob( + this, &CSocketMultiplexer::serviceThread)); + + s_instance = this; +} + +CSocketMultiplexer::~CSocketMultiplexer() +{ + m_thread->cancel(); + m_thread->wait(); + delete m_thread; + delete m_polling; + delete m_pollable; + delete m_mutex; + + // clean up jobs + for (CSocketJobMap::iterator i = m_socketJobMap.begin(); + i != m_socketJobMap.end(); ++i) { + delete *(i->second); + } + + s_instance = NULL; +} + +CSocketMultiplexer* +CSocketMultiplexer::getInstance() +{ + return s_instance; +} + +void +CSocketMultiplexer::addSocket(ISocket* socket, ISocketMultiplexerJob* job) +{ + assert(socket != NULL); + assert(job != NULL); + + CLock lock(m_mutex); + + // prevent service thread from restarting poll + *m_pollable = false; + + // break thread out of poll + m_thread->unblock(); + + // wait for poll to finish + while (*m_polling) { + m_polling->wait(); + } + + // insert/replace job + CSocketJobMap::iterator i = m_socketJobMap.find(socket); + if (i == m_socketJobMap.end()) { + // we *must* put the job at the end so the order of jobs in + // the list continue to match the order of jobs in pfds in + // serviceThread(). + CJobCursor j = m_socketJobs.insert(m_socketJobs.end(), job); + m_update = true; + m_socketJobMap.insert(std::make_pair(socket, j)); + } + else { + CJobCursor j = i->second; + if (*j != job) { + delete *j; + *j = job; + } + m_update = true; + } + + // there must be at least one socket so we can poll now + *m_pollable = true; + m_pollable->broadcast(); +} + +void +CSocketMultiplexer::removeSocket(ISocket* socket) +{ + assert(socket != NULL); + + CLock lock(m_mutex); + + // prevent service thread from restarting poll + *m_pollable = false; + + // break thread out of poll + m_thread->unblock(); + + // wait until thread finishes poll + while (*m_polling) { + m_polling->wait(); + } + + // remove job. rather than removing it from the map we put NULL + // in the list instead so the order of jobs in the list continues + // to match the order of jobs in pfds in serviceThread(). + CSocketJobMap::iterator i = m_socketJobMap.find(socket); + if (i != m_socketJobMap.end()) { + if (*(i->second) != NULL) { + delete *(i->second); + *(i->second) = NULL; + m_update = true; + } + } + + *m_pollable = true; + m_pollable->broadcast(); +} + +void +CSocketMultiplexer::serviceThread(void*) +{ + std::vector pfds; + IArchNetwork::CPollEntry pfd; + + // service the connections + for (;;) { + { + CLock lock(m_mutex); + + // wait until pollable + while (!*m_pollable) { + m_pollable->wait(); + } + + // now we're polling + *m_polling = true; + m_polling->broadcast(); + } + + // we're now the only thread that can access m_sockets and + // m_update because m_polling and m_pollable are both true. + // therefore, we don't need to hold the mutex. + + // collect poll entries + if (m_update) { + m_update = false; + pfds.clear(); + pfds.reserve(m_socketJobMap.size()); + + CJobCursor cursor = newCursor(); + CJobCursor jobCursor = nextCursor(cursor); + while (jobCursor != m_socketJobs.end()) { + ISocketMultiplexerJob* job = *jobCursor; + if (job != NULL) { + pfd.m_socket = job->getSocket(); + pfd.m_events = 0; + if (job->isReadable()) { + pfd.m_events |= IArchNetwork::kPOLLIN; + } + if (job->isWritable()) { + pfd.m_events |= IArchNetwork::kPOLLOUT; + } + pfds.push_back(pfd); + } + jobCursor = nextCursor(cursor); + } + deleteCursor(cursor); + } + + int status; + try { + // check for status + status = ARCH->pollSocket(&pfds[0], pfds.size(), -1); + } + catch (XArchNetwork&) { + // FIXME -- uh oh + status = 0; + } + + if (status != 0) { + // iterate over socket jobs, invoking each and saving the + // new job. + UInt32 i = 0; + CJobCursor cursor = newCursor(); + CJobCursor jobCursor = nextCursor(cursor); + while (i < pfds.size() && jobCursor != m_socketJobs.end()) { + if (*jobCursor != NULL) { + // get poll state + unsigned short revents = pfds[i].m_revents; + bool read = ((revents & IArchNetwork::kPOLLIN) != 0); + bool write = ((revents & IArchNetwork::kPOLLOUT) != 0); + bool error = ((revents & (IArchNetwork::kPOLLERR | + IArchNetwork::kPOLLNVAL)) != 0); + + // run job + ISocketMultiplexerJob* job = *jobCursor; + ISocketMultiplexerJob* newJob = job->run(read, write, error); + + // save job, if different + if (newJob != job) { + CLock lock(m_mutex); + delete job; + *jobCursor = newJob; + m_update = true; + } + ++i; + } + + // next job + jobCursor = nextCursor(cursor); + } + deleteCursor(cursor); + } + + // delete any removed socket jobs + CLock lock(m_mutex); + for (CSocketJobMap::iterator i = m_socketJobMap.begin(); + i != m_socketJobMap.end();) { + if (*(i->second) == NULL) { + m_socketJobMap.erase(i++); + m_update = true; + } + else { + ++i; + } + } + if (m_socketJobMap.empty()) { + *m_pollable = false; + } + + // done polling + *m_polling = false; + m_polling->broadcast(); + } +} + +CSocketMultiplexer::CJobCursor +CSocketMultiplexer::newCursor() +{ + CLock lock(m_mutex); + return m_socketJobs.insert(m_socketJobs.begin(), m_cursorMark); +} + +CSocketMultiplexer::CJobCursor +CSocketMultiplexer::nextCursor(CJobCursor cursor) +{ + CLock lock(m_mutex); + ISocketMultiplexerJob* job = NULL; + CJobCursor j = m_socketJobs.end(); + CJobCursor i = cursor; + while (++i != m_socketJobs.end()) { + if (*i != m_cursorMark) { + // found a real job (as opposed to a cursor) + j = i; + + // move our cursor just past the job + m_socketJobs.splice(++i, m_socketJobs, cursor); + break; + } + } + return j; +} + +void +CSocketMultiplexer::deleteCursor(CJobCursor cursor) +{ + CLock lock(m_mutex); + m_socketJobs.erase(cursor); +} diff --git a/lib/net/CSocketMultiplexer.h b/lib/net/CSocketMultiplexer.h new file mode 100644 index 00000000..8abd57ba --- /dev/null +++ b/lib/net/CSocketMultiplexer.h @@ -0,0 +1,95 @@ +/* + * 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 CSOCKETMULTIPLEXER_H +#define CSOCKETMULTIPLEXER_H + +#include "IArchNetwork.h" +#include "stdlist.h" +#include "stdmap.h" + +template +class CCondVar; +class CMutex; +class CThread; +class ISocket; +class ISocketMultiplexerJob; + +//! Socket multiplexer +/*! +A socket multiplexer services multiple sockets simultaneously. +*/ +class CSocketMultiplexer { +public: + CSocketMultiplexer(); + ~CSocketMultiplexer(); + + //! @name manipulators + //@{ + + void addSocket(ISocket*, ISocketMultiplexerJob*); + + void removeSocket(ISocket*); + + //@} + //! @name accessors + //@{ + + // maybe belongs on ISocketMultiplexer + static CSocketMultiplexer* + getInstance(); + + //@} + +private: + // list of jobs. we use a list so we can safely iterate over it + // while other threads modify it. + typedef std::list CSocketJobs; + typedef CSocketJobs::iterator CJobCursor; + typedef std::map CSocketJobMap; + + // service sockets. the service thread will only access m_sockets + // and m_update while m_pollable and m_polling are true. all other + // threads must only modify these when m_pollable and m_polling are + // false. only the service thread sets m_polling. + void serviceThread(void*); + + // create, iterate, and destroy a cursor. a cursor is used to + // safely iterate through the job list while other threads modify + // the list. it works by inserting a dummy item in the list and + // moving that item through the list. the dummy item will never + // be removed by other edits so an iterator pointing at the item + // remains valid until we remove the dummy item in deleteCursor(). + // nextCursor() finds the next non-dummy item, moves our dummy + // item just past it, and returns an iterator for the non-dummy + // item. all cursor calls lock the mutex for their duration. + CJobCursor newCursor(); + CJobCursor nextCursor(CJobCursor); + void deleteCursor(CJobCursor); + +private: + CMutex* m_mutex; + CCondVar* m_pollable; + CCondVar* m_polling; + CThread* m_thread; + bool m_update; + + CSocketJobs m_socketJobs; + CSocketJobMap m_socketJobMap; + ISocketMultiplexerJob* m_cursorMark; + + static CSocketMultiplexer* s_instance; +}; + +#endif diff --git a/lib/net/CTCPListenSocket.cpp b/lib/net/CTCPListenSocket.cpp index fb35f9d1..49695446 100644 --- a/lib/net/CTCPListenSocket.cpp +++ b/lib/net/CTCPListenSocket.cpp @@ -30,8 +30,7 @@ // CTCPListenSocket // -CTCPListenSocket::CTCPListenSocket() : - m_target(NULL) +CTCPListenSocket::CTCPListenSocket() { m_mutex = new CMutex; try { @@ -76,21 +75,6 @@ CTCPListenSocket::bind(const CNetworkAddress& addr) } } -IDataSocket* -CTCPListenSocket::accept() -{ - try { - CSocketMultiplexer::getInstance()->addSocket(this, - new TSocketMultiplexerMethodJob( - this, &CTCPListenSocket::serviceListening, - m_socket, true, false)); - return new CTCPSocket(ARCH->acceptSocket(m_socket, NULL)); - } - catch (XArchNetwork&) { - return NULL; - } -} - void CTCPListenSocket::close() { @@ -108,11 +92,25 @@ CTCPListenSocket::close() } } -void -CTCPListenSocket::setEventTarget(void* target) +void* +CTCPListenSocket::getEventTarget() const { - CLock lock(m_mutex); - m_target = target; + return const_cast(reinterpret_cast(this)); +} + +IDataSocket* +CTCPListenSocket::accept() +{ + try { + CSocketMultiplexer::getInstance()->addSocket(this, + new TSocketMultiplexerMethodJob( + this, &CTCPListenSocket::serviceListening, + m_socket, true, false)); + return new CTCPSocket(ARCH->acceptSocket(m_socket, NULL)); + } + catch (XArchNetwork&) { + return NULL; + } } ISocketMultiplexerJob* @@ -125,7 +123,7 @@ CTCPListenSocket::serviceListening(ISocketMultiplexerJob* job, } if (read) { CEventQueue::getInstance()->addEvent( - CEvent(getConnectingEvent(), m_target, NULL)); + CEvent(getConnectingEvent(), this, NULL)); // stop polling on this socket until the client accepts return NULL; } diff --git a/lib/net/CTCPListenSocket.h b/lib/net/CTCPListenSocket.h index 8c582399..5321b8db 100644 --- a/lib/net/CTCPListenSocket.h +++ b/lib/net/CTCPListenSocket.h @@ -33,7 +33,7 @@ public: // ISocket overrides virtual void bind(const CNetworkAddress&); virtual void close(); - virtual void setEventTarget(void*); + virtual void* getEventTarget() const; // IListenSocket overrides virtual IDataSocket* accept(); @@ -46,7 +46,6 @@ private: private: CArchSocket m_socket; CMutex* m_mutex; - void* m_target; }; #endif diff --git a/lib/net/CTCPSocket.cpp b/lib/net/CTCPSocket.cpp index 7ab154b1..cdfed02e 100644 --- a/lib/net/CTCPSocket.cpp +++ b/lib/net/CTCPSocket.cpp @@ -16,14 +16,10 @@ #include "CNetworkAddress.h" #include "CSocketMultiplexer.h" #include "TSocketMultiplexerMethodJob.h" -#include "CBufferedInputStream.h" -#include "CBufferedOutputStream.h" #include "XSocket.h" -#include "XIO.h" #include "CLock.h" -#include "CMutex.h" #include "CEventQueue.h" -#include "TMethodJob.h" +#include "IEventJob.h" #include "CArch.h" #include "XArch.h" @@ -31,7 +27,10 @@ // CTCPSocket // -CTCPSocket::CTCPSocket() +CTCPSocket::CTCPSocket() : + m_mutex(), + m_flushed(&m_mutex, true), + m_eventFilter(NULL) { try { m_socket = ARCH->newSocket(IArchNetwork::kINET, IArchNetwork::kSTREAM); @@ -39,34 +38,32 @@ CTCPSocket::CTCPSocket() catch (XArchNetwork& e) { throw XSocketCreate(e.what()); } + init(); } CTCPSocket::CTCPSocket(CArchSocket socket) : - m_socket(socket) + m_mutex(), + m_socket(socket), + m_flushed(&m_mutex, true), + m_eventFilter(NULL) { assert(m_socket != NULL); // socket starts in connected state init(); - setState(kReadWrite, true); + onConnected(); + setJob(newJob()); } CTCPSocket::~CTCPSocket() { try { - if (m_socket != NULL) { - close(); - } + close(); } catch (...) { // ignore } - - // clean up - delete m_input; - delete m_output; - delete m_mutex; } void @@ -86,92 +83,202 @@ CTCPSocket::bind(const CNetworkAddress& addr) void CTCPSocket::close() { - // flush buffers - m_output->flush(); + CLock lock(&m_mutex); - // now closed - setState(kClosed, true); + // clear buffers and enter disconnected state + if (m_connected) { + sendSocketEvent(getDisconnectedEvent()); + } + onDisconnected(); - // close buffers - try { - m_input->close(); - } - catch (...) { - // ignore - } - try { - m_output->close(); - } - catch (...) { - // ignore - } + // remove ourself from the multiplexer + setJob(NULL); - // close socket - CLock lock(m_mutex); + // close the socket if (m_socket != NULL) { + CArchSocket socket = m_socket; + m_socket = NULL; try { - ARCH->closeSocket(m_socket); - m_socket = NULL; + ARCH->closeSocket(socket); } catch (XArchNetwork& e) { - throw XSocketIOClose(e.what()); + // FIXME -- just discard this for now + //throw XSocketIOClose(e.what()); } } } -void -CTCPSocket::setEventTarget(void* target) +void* +CTCPSocket::getEventTarget() const { - CLock lock(m_mutex); - m_target = target; + return const_cast(reinterpret_cast(this)); +} + +UInt32 +CTCPSocket::read(void* buffer, UInt32 n) +{ + // copy data directly from our input buffer + CLock lock(&m_mutex); + UInt32 size = m_inputBuffer.getSize(); + if (n > size) { + n = size; + } + if (buffer != NULL) { + memcpy(buffer, m_inputBuffer.peek(n), n); + } + m_inputBuffer.pop(n); + + // if no more data and we cannot read or write then send disconnected + if (n > 0 && !m_readable && !m_writable) { + sendSocketEvent(getDisconnectedEvent()); + } + + return n; +} + +void +CTCPSocket::write(const void* buffer, UInt32 n) +{ + CLock lock(&m_mutex); + + // must not have shutdown output + if (!m_writable) { + sendStreamEvent(getOutputErrorEvent()); + return; + } + + // ignore empty writes + if (n == 0) { + return; + } + + // copy data to the output buffer + bool wasEmpty = (m_outputBuffer.getSize() == 0); + m_outputBuffer.write(buffer, n); + + // there's data to write + m_flushed = false; + + // make sure we're waiting to write + if (wasEmpty) { + setJob(newJob()); + } +} + +void +CTCPSocket::flush() +{ + CLock lock(&m_mutex); + while (m_flushed == false) { + m_flushed.wait(); + } +} + +void +CTCPSocket::shutdownInput() +{ + CLock lock(&m_mutex); + + // shutdown socket for reading + try { + ARCH->closeSocketForRead(m_socket); + } + catch (XArchNetwork&) { + // ignore + } + + // shutdown buffer for reading + if (m_readable) { + sendStreamEvent(getInputShutdownEvent()); + onInputShutdown(); + setJob(newJob()); + } +} + +void +CTCPSocket::shutdownOutput() +{ + CLock lock(&m_mutex); + + // shutdown socket for writing + try { + ARCH->closeSocketForWrite(m_socket); + } + catch (XArchNetwork&) { + // ignore + } + + // shutdown buffer for writing + if (m_writable) { + sendStreamEvent(getOutputShutdownEvent()); + onOutputShutdown(); + setJob(newJob()); + } +} + +void +CTCPSocket::setEventFilter(IEventJob* filter) +{ + CLock lock(&m_mutex); + m_eventFilter = filter; +} + +bool +CTCPSocket::isReady() const +{ + CLock lock(&m_mutex); + return (m_inputBuffer.getSize() > 0); +} + +UInt32 +CTCPSocket::getSize() const +{ + CLock lock(&m_mutex); + return m_inputBuffer.getSize(); +} + +IEventJob* +CTCPSocket::getEventFilter() const +{ + CLock lock(&m_mutex); + return m_eventFilter; } void CTCPSocket::connect(const CNetworkAddress& addr) { + CLock lock(&m_mutex); + + // fail on attempts to reconnect + if (m_socket == NULL || m_connected) { + sendSocketEvent(getConnectionFailedEvent()); + return; + } + try { // FIXME -- don't throw if in progress, just return that info ARCH->connectSocket(m_socket, addr.getAddress()); - setState(kReadWrite, true); + sendSocketEvent(getConnectedEvent()); + onConnected(); + setJob(newJob()); } catch (XArchNetworkConnecting&) { // connection is in progress - setState(kConnecting, true); + m_writable = true; + setJob(newJob()); } catch (XArchNetwork& e) { throw XSocketConnect(e.what()); } } -IInputStream* -CTCPSocket::getInputStream() -{ - return m_input; -} - -IOutputStream* -CTCPSocket::getOutputStream() -{ - return m_output; -} - void CTCPSocket::init() { - m_mutex = new CMutex; - m_input = new CBufferedInputStream(m_mutex, - new TMethodJob( - this, &CTCPSocket::emptyInput), - new TMethodJob( - this, &CTCPSocket::closeInput)); - m_output = new CBufferedOutputStream(m_mutex, - new TMethodJob( - this, &CTCPSocket::fillOutput), - new TMethodJob( - this, &CTCPSocket::closeOutput)); - m_state = kUnconnected; - m_target = NULL; - m_job = NULL; + // default state + m_connected = false; + m_readable = false; + m_writable = false; // make socket non-blocking // FIXME -- check for error @@ -196,172 +303,97 @@ CTCPSocket::init() } } -ISocketMultiplexerJob* -CTCPSocket::newMultiplexerJob(JobFunc func, bool readable, bool writable) -{ - return new TSocketMultiplexerMethodJob( - this, func, m_socket, readable, writable); -} - -ISocketMultiplexerJob* -CTCPSocket::setState(State state, bool setJob) -{ - if (m_state == state || m_state == kClosed) { - return m_job; - } - - State oldState = m_state; - m_state = state; - - bool read = (m_input->getSize() > 0); - bool write = (m_output->getSize() > 0); - CEvent::Type eventType = 0; - m_job = NULL; - switch (m_state) { - case kUnconnected: - assert(0 && "cannot re-enter unconnected state"); - break; - - case kConnecting: - m_job = newMultiplexerJob(&CTCPSocket::serviceConnecting, false, true); - break; - - case kReadWrite: - if (oldState == kConnecting) { - eventType = IDataSocket::getConnectedEvent(); - } - m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, true, write); - break; - - case kReadOnly: - if (!write) { - eventType = IDataSocket::getShutdownOutputEvent(); - } - if (oldState == kWriteOnly) { - goto shutdown; - } - m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, true, write); - break; - - case kWriteOnly: - if (!read) { - m_input->hangup(); - eventType = IDataSocket::getShutdownInputEvent(); - } - if (oldState == kReadOnly) { - goto shutdown; - } - m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, false, write); - break; - - case kShutdown: -shutdown: - if (!read && !write) { - eventType = ISocket::getDisconnectedEvent(); - m_state = kClosed; - } - else { - m_state = kShutdown; - } - break; - - case kClosed: - m_input->hangup(); - if (oldState == kConnecting) { - eventType = IDataSocket::getConnectionFailedEvent(); - } - else { - eventType = ISocket::getDisconnectedEvent(); - } - break; - } - - // notify - if (eventType != 0) { - sendEvent(eventType); - } - - // cut over to new job. multiplexer will delete the old job. - if (setJob) { - if (m_job == NULL) { - CSocketMultiplexer::getInstance()->removeSocket(this); - } - else { - CSocketMultiplexer::getInstance()->addSocket(this, m_job); - } - } - return m_job; -} - void -CTCPSocket::closeInput(void*) +CTCPSocket::setJob(ISocketMultiplexerJob* job) { - // note -- m_mutex should already be locked - try { - ARCH->closeSocketForRead(m_socket); - setState(kWriteOnly, true); - } - catch (XArchNetwork&) { - // ignore - } -} - -void -CTCPSocket::closeOutput(void*) -{ - // note -- m_mutex should already be locked - try { -// ARCH->closeSocketForWrite(m_socket); - setState(kReadOnly, true); - } - catch (XArchNetwork&) { - // ignore - } -} - -void -CTCPSocket::emptyInput(void*) -{ - // note -- m_mutex should already be locked - bool write = (m_output->getSize() > 0); - if (m_state == kWriteOnly && !write) { - m_state = kShutdown; - } - if (m_state == kWriteOnly) { - m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, false, write); - CSocketMultiplexer::getInstance()->addSocket(this, m_job); - m_input->hangup(); - sendEvent(IDataSocket::getShutdownInputEvent()); - } - else if (m_state == kShutdown) { - m_job = NULL; + // multiplexer will delete the old job + if (job == NULL) { CSocketMultiplexer::getInstance()->removeSocket(this); - if (!write) { - sendEvent(ISocket::getDisconnectedEvent()); - m_state = kClosed; - } + } + else { + CSocketMultiplexer::getInstance()->addSocket(this, job); + } +} + +ISocketMultiplexerJob* +CTCPSocket::newJob() +{ + // note -- must have m_mutex locked on entry + + if (m_socket == NULL || !(m_readable || m_writable)) { + return NULL; + } + else if (!m_connected) { + assert(!m_readable); + return new TSocketMultiplexerMethodJob( + this, &CTCPSocket::serviceConnecting, + m_socket, m_readable, m_writable); + } + else { + return new TSocketMultiplexerMethodJob( + this, &CTCPSocket::serviceConnected, + m_socket, m_readable, + m_writable && (m_outputBuffer.getSize() > 0)); } } void -CTCPSocket::fillOutput(void*) +CTCPSocket::sendSocketEvent(CEvent::Type type) { - // note -- m_mutex should already be locked - if (m_state == kReadWrite) { - m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, true, true); - CSocketMultiplexer::getInstance()->addSocket(this, m_job); + EVENTQUEUE->addEvent(CEvent(type, this, NULL)); +} + +void +CTCPSocket::sendStreamEvent(CEvent::Type type) +{ + if (m_eventFilter != NULL) { + m_eventFilter->run(CEvent(type, this, NULL)); } - else if (m_state == kWriteOnly) { - m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, false, true); - CSocketMultiplexer::getInstance()->addSocket(this, m_job); + else { + EVENTQUEUE->addEvent(CEvent(type, this, NULL)); } } +void +CTCPSocket::onConnected() +{ + m_connected = true; + m_readable = true; + m_writable = true; +} + +void +CTCPSocket::onInputShutdown() +{ + m_inputBuffer.pop(m_inputBuffer.getSize()); + m_readable = false; +} + +void +CTCPSocket::onOutputShutdown() +{ + m_outputBuffer.pop(m_outputBuffer.getSize()); + m_writable = false; + + // we're now flushed + m_flushed = true; + m_flushed.broadcast(); +} + +void +CTCPSocket::onDisconnected() +{ + // disconnected + onInputShutdown(); + onOutputShutdown(); + m_connected = false; +} + ISocketMultiplexerJob* CTCPSocket::serviceConnecting(ISocketMultiplexerJob* job, bool, bool write, bool error) { - CLock lock(m_mutex); + CLock lock(&m_mutex); if (write && !error) { try { @@ -374,11 +406,15 @@ CTCPSocket::serviceConnecting(ISocketMultiplexerJob* job, } if (error) { - return setState(kClosed, false); + sendSocketEvent(getConnectionFailedEvent()); + onDisconnected(); + return newJob(); } if (write) { - return setState(kReadWrite, false); + sendSocketEvent(getConnectedEvent()); + onConnected(); + return newJob(); } return job; @@ -388,79 +424,99 @@ ISocketMultiplexerJob* CTCPSocket::serviceConnected(ISocketMultiplexerJob* job, bool read, bool write, bool error) { - CLock lock(m_mutex); + CLock lock(&m_mutex); + if (error) { - return setState(kClosed, false); + sendSocketEvent(getDisconnectedEvent()); + onDisconnected(); + return newJob(); } - if (write) { - // get amount of data to write - UInt32 n = m_output->getSize(); + bool needNewJob = false; - // write data + if (write) { try { - const void* buffer = m_output->peek(n); - size_t n2 = ARCH->writeSocket(m_socket, buffer, n); + // write data + UInt32 n = m_outputBuffer.getSize(); + const void* buffer = m_outputBuffer.peek(n); + n = (UInt32)ARCH->writeSocket(m_socket, buffer, n); // discard written data - if (n2 > 0) { - m_output->pop(n2); + if (n > 0) { + m_outputBuffer.pop(n); + if (m_outputBuffer.getSize() == 0) { + sendStreamEvent(getOutputFlushedEvent()); + m_flushed = true; + m_flushed.broadcast(); + needNewJob = true; + } + } + } + catch (XArchNetworkShutdown&) { + // remote read end of stream hungup. our output side + // has therefore shutdown. + onOutputShutdown(); + sendStreamEvent(getOutputShutdownEvent()); + if (!m_readable && m_inputBuffer.getSize() == 0) { + sendSocketEvent(getDisconnectedEvent()); + } + needNewJob = true; + } + catch (XArchNetworkDisconnected&) { + // stream hungup + onDisconnected(); + sendSocketEvent(getDisconnectedEvent()); + needNewJob = true; + } + catch (XArchNetwork&) { + // other write error + onDisconnected(); + sendStreamEvent(getOutputErrorEvent()); + sendSocketEvent(getDisconnectedEvent()); + needNewJob = true; + } + } + + if (read && m_readable) { + try { + UInt8 buffer[4096]; + size_t n = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); + if (n > 0) { + bool wasEmpty = (m_inputBuffer.getSize() == 0); + + // slurp up as much as possible + do { + m_inputBuffer.write(buffer, n); + n = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); + } while (n > 0); + + // send input ready if input buffer was empty + if (wasEmpty) { + sendStreamEvent(getInputReadyEvent()); + } + } + else { + // remote write end of stream hungup. our input side + // has therefore shutdown but don't flush our buffer + // since there's still data to be read. + sendStreamEvent(getInputShutdownEvent()); + if (!m_writable && m_inputBuffer.getSize() == 0) { + sendSocketEvent(getDisconnectedEvent()); + } + m_readable = false; + needNewJob = true; } } catch (XArchNetworkDisconnected&) { // stream hungup - return setState(kReadOnly, false); + sendSocketEvent(getDisconnectedEvent()); + onDisconnected(); + needNewJob = true; + } + catch (XArchNetwork&) { + // ignore other read error } } - if (read) { - UInt8 buffer[4096]; - size_t n = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); - if (n > 0) { - // slurp up as much as possible - do { - m_input->write(buffer, n); - try { - n = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); - } - catch (XArchNetworkWouldBlock&) { - break; - } - } while (n > 0); - - // notify - sendEvent(IDataSocket::getInputEvent()); - } - else { - // stream hungup - return setState(kWriteOnly, false); - } - } - - if (write && m_output->getSize() == 0) { - if (m_state == kReadOnly) { - ARCH->closeSocketForWrite(m_socket); - sendEvent(IDataSocket::getShutdownOutputEvent()); - m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, - true, false); - job = m_job; - } - else if (m_state == kReadWrite || m_state == kReadOnly) { - m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, - true, false); - job = m_job; - } - else if (m_state == kWriteOnly) { - m_job = NULL; - job = m_job; - } - } - - return job; -} - -void -CTCPSocket::sendEvent(CEvent::Type type) -{ - CEventQueue::getInstance()->addEvent(CEvent(type, m_target, NULL)); + return needNewJob ? newJob() : job; } diff --git a/lib/net/CTCPSocket.h b/lib/net/CTCPSocket.h index 142ac4a6..0335dce1 100644 --- a/lib/net/CTCPSocket.h +++ b/lib/net/CTCPSocket.h @@ -16,14 +16,13 @@ #define CTCPSOCKET_H #include "IDataSocket.h" -#include "CEvent.h" -#include "BasicTypes.h" +#include "CStreamBuffer.h" +#include "CCondVar.h" +#include "CMutex.h" #include "IArchNetwork.h" class CMutex; class CThread; -class CBufferedInputStream; -class CBufferedOutputStream; class ISocketMultiplexerJob; //! TCP data socket @@ -39,33 +38,34 @@ public: // ISocket overrides virtual void bind(const CNetworkAddress&); virtual void close(); - virtual void setEventTarget(void*); + virtual void* getEventTarget() const; + + // IStream overrides + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void flush(); + virtual void shutdownInput(); + virtual void shutdownOutput(); + virtual void setEventFilter(IEventJob* filter); + virtual bool isReady() const; + virtual UInt32 getSize() const; + virtual IEventJob* getEventFilter() const; // IDataSocket overrides virtual void connect(const CNetworkAddress&); - virtual IInputStream* getInputStream(); - virtual IOutputStream* getOutputStream(); private: - enum State { - kUnconnected, - kConnecting, - kReadWrite, - kReadOnly, - kWriteOnly, - kShutdown, - kClosed - }; - void init(); - ISocketMultiplexerJob* - setState(State, bool setJob); + void setJob(ISocketMultiplexerJob*); + ISocketMultiplexerJob* newJob(); + void sendSocketEvent(CEvent::Type); + void sendStreamEvent(CEvent::Type); - void closeInput(void*); - void closeOutput(void*); - void emptyInput(void*); - void fillOutput(void*); + void onConnected(); + void onInputShutdown(); + void onOutputShutdown(); + void onDisconnected(); ISocketMultiplexerJob* serviceConnecting(ISocketMultiplexerJob*, @@ -74,25 +74,16 @@ private: serviceConnected(ISocketMultiplexerJob*, bool, bool, bool); - typedef ISocketMultiplexerJob* (CTCPSocket::*JobFunc)( - ISocketMultiplexerJob*, - bool, bool, bool); - ISocketMultiplexerJob* - newMultiplexerJob(JobFunc, - bool readable, bool writable); - - void sendEvent(CEvent::Type); - private: - CArchSocket m_socket; - CBufferedInputStream* m_input; - CBufferedOutputStream* m_output; - - CMutex* m_mutex; - State m_state; - void* m_target; - - ISocketMultiplexerJob* m_job; + CMutex m_mutex; + CArchSocket m_socket; + CStreamBuffer m_inputBuffer; + CStreamBuffer m_outputBuffer; + CCondVar m_flushed; + bool m_connected; + bool m_readable; + bool m_writable; + IEventJob* m_eventFilter; }; #endif diff --git a/lib/net/IDataSocket.cpp b/lib/net/IDataSocket.cpp index f6d577ab..b3f6a5e1 100644 --- a/lib/net/IDataSocket.cpp +++ b/lib/net/IDataSocket.cpp @@ -18,11 +18,8 @@ // IDataSocket // -CEvent::Type IDataSocket::s_connectedEvent = CEvent::kUnknown; -CEvent::Type IDataSocket::s_failedEvent = CEvent::kUnknown; -CEvent::Type IDataSocket::s_inputEvent = CEvent::kUnknown; -CEvent::Type IDataSocket::s_shutdownInputEvent = CEvent::kUnknown; -CEvent::Type IDataSocket::s_shutdownOutputEvent = CEvent::kUnknown; +CEvent::Type IDataSocket::s_connectedEvent = CEvent::kUnknown; +CEvent::Type IDataSocket::s_failedEvent = CEvent::kUnknown; CEvent::Type IDataSocket::getConnectedEvent() @@ -35,21 +32,3 @@ IDataSocket::getConnectionFailedEvent() { return CEvent::registerTypeOnce(s_failedEvent); } - -CEvent::Type -IDataSocket::getInputEvent() -{ - return CEvent::registerTypeOnce(s_inputEvent); -} - -CEvent::Type -IDataSocket::getShutdownInputEvent() -{ - return CEvent::registerTypeOnce(s_shutdownInputEvent); -} - -CEvent::Type -IDataSocket::getShutdownOutputEvent() -{ - return CEvent::registerTypeOnce(s_shutdownOutputEvent); -} diff --git a/lib/net/IDataSocket.h b/lib/net/IDataSocket.h index 8fa2d6da..fd071669 100644 --- a/lib/net/IDataSocket.h +++ b/lib/net/IDataSocket.h @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2002 Chris Schoeneman + * 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 @@ -16,44 +16,27 @@ #define IDATASOCKET_H #include "ISocket.h" - -class IInputStream; -class IOutputStream; +#include "IStream.h" //! Data stream socket interface /*! This interface defines the methods common to all network sockets that represent a full-duplex data stream. */ -class IDataSocket : public ISocket { +class IDataSocket : public ISocket, public IStream { public: //! @name manipulators //@{ //! Connect socket /*! - Attempt to connect to a remote endpoint. This waits until the - connection is established or fails. If it fails it throws an - XSocketConnect exception. - - (cancellation point) + Attempt to connect to a remote endpoint. This returns immediately + and sends a connected event when successful or a connection failed + event when it fails. The stream acts as if shutdown for input and + output until the stream connects. */ virtual void connect(const CNetworkAddress&) = 0; - //! Get input stream - /*! - Returns the input stream for reading from the socket. Closing this - stream will shutdown the socket for reading. - */ - virtual IInputStream* getInputStream() = 0; - - //! Get output stream - /*! - Returns the output stream for writing to the socket. Closing this - stream will shutdown the socket for writing. - */ - virtual IOutputStream* getOutputStream() = 0; - //@} //! @name accessors //@{ @@ -72,42 +55,27 @@ public: */ static CEvent::Type getConnectionFailedEvent(); - //! Get input event type - /*! - Returns the socket input event type. A socket sends this - event when data is available to read from the input stream. - */ - static CEvent::Type getInputEvent(); - - //! Get shutdown input event type - /*! - Returns the socket shutdown input event type. A socket sends this - event when the remote side of the connection has shutdown for - writing and there is no more data to read from the socket. - */ - static CEvent::Type getShutdownInputEvent(); - - //! Get shutdown input event type - /*! - Returns the socket shutdown input event type. A socket sends this - event when the remote side of the connection has shutdown for - writing and there is no more data to read from the socket. - */ - static CEvent::Type getShutdownOutputEvent(); - //@} // ISocket overrides virtual void bind(const CNetworkAddress&) = 0; virtual void close() = 0; - virtual void setEventTarget(void*) = 0; + virtual void* getEventTarget() const = 0; + + // IStream overrides + virtual UInt32 read(void* buffer, UInt32 n) = 0; + virtual void write(const void* buffer, UInt32 n) = 0; + virtual void flush() = 0; + virtual void shutdownInput() = 0; + virtual void shutdownOutput() = 0; + virtual void setEventFilter(IEventJob* filter) = 0; + virtual bool isReady() const = 0; + virtual UInt32 getSize() const = 0; + virtual IEventJob* getEventFilter() const = 0; private: static CEvent::Type s_connectedEvent; static CEvent::Type s_failedEvent; - static CEvent::Type s_inputEvent; - static CEvent::Type s_shutdownInputEvent; - static CEvent::Type s_shutdownOutputEvent; }; #endif diff --git a/lib/net/IListenSocket.h b/lib/net/IListenSocket.h index 36524750..894234f4 100644 --- a/lib/net/IListenSocket.h +++ b/lib/net/IListenSocket.h @@ -53,7 +53,7 @@ public: // ISocket overrides virtual void bind(const CNetworkAddress&) = 0; virtual void close() = 0; - virtual void setEventTarget(void*) = 0; + virtual void* getEventTarget() const = 0; private: static CEvent::Type s_connectingEvent; diff --git a/lib/net/ISocket.h b/lib/net/ISocket.h index 79e55966..4452e023 100644 --- a/lib/net/ISocket.h +++ b/lib/net/ISocket.h @@ -23,6 +23,7 @@ class CNetworkAddress; //! Generic socket interface /*! This interface defines the methods common to all network sockets. +Generated events use \c this as the target. */ class ISocket : public IInterface { public: @@ -41,21 +42,21 @@ public: */ virtual void close() = 0; - //! Set the socket's event target - /*! - Sets the target of any events sent by the socket. The default is NULL. - */ - virtual void setEventTarget(void*) = 0; - //@} //! @name accessors //@{ + //! Get event target + /*! + Returns the event target for events generated by this socket. + */ + virtual void* getEventTarget() const = 0; + //! Get disconnected event type /*! Returns the socket disconnected event type. A socket sends this event when the remote side of the socket has disconnected or - shutdown. + shutdown both input and output. */ static CEvent::Type getDisconnectedEvent(); diff --git a/lib/net/ISocketMultiplexerJob.h b/lib/net/ISocketMultiplexerJob.h new file mode 100644 index 00000000..ac722326 --- /dev/null +++ b/lib/net/ISocketMultiplexerJob.h @@ -0,0 +1,75 @@ +/* + * 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 ISOCKETMULTIPLEXERJOB_H +#define ISOCKETMULTIPLEXERJOB_H + +#include "IArchNetwork.h" +#include "IInterface.h" + +//! Socket multiplexer job +/*! +A socket multiplexer job handles events on a socket. +*/ +class ISocketMultiplexerJob : public IInterface { +public: + //! @name manipulators + //@{ + + //! Handle socket event + /*! + Called by a socket multiplexer when the socket becomes readable, + writable, or has an error. It should return itself if the same + job can continue to service events, a new job if the socket must + be serviced differently, or NULL if the socket should no longer + be serviced. The socket is readable if \p readable is true, + writable if \p writable is true, and in error if \p error is + true. + + This call must not attempt to directly change the job for this + socket by calling \c addSocket() or \c removeSocket() on the + multiplexer. It must instead return the new job. It can, + however, add or remove jobs for other sockets. + */ + virtual ISocketMultiplexerJob* + run(bool readable, bool writable, bool error) = 0; + + //@} + //! @name accessors + //@{ + + //! Get the socket + /*! + Return the socket to multiplex + */ + virtual CArchSocket getSocket() const = 0; + + //! Check for interest in readability + /*! + Return true if the job is interested in being run if the socket + becomes readable. + */ + virtual bool isReadable() const = 0; + + //! Check for interest in writability + /*! + Return true if the job is interested in being run if the socket + becomes writable. + */ + virtual bool isWritable() const = 0; + + //@} +}; + +#endif diff --git a/lib/net/TSocketMultiplexerMethodJob.h b/lib/net/TSocketMultiplexerMethodJob.h new file mode 100644 index 00000000..992885c4 --- /dev/null +++ b/lib/net/TSocketMultiplexerMethodJob.h @@ -0,0 +1,108 @@ +/* + * 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 TSOCKERMULTIPLEXERMETHODJOB_H +#define TSOCKERMULTIPLEXERMETHODJOB_H + +#include "ISocketMultiplexerJob.h" +#include "CArch.h" + +//! Use a method as a socket multiplexer job +/*! +A socket multiplexer job class that invokes a member function. +*/ +template +class TSocketMultiplexerMethodJob : public ISocketMultiplexerJob { +public: + typedef ISocketMultiplexerJob* + (T::*Method)(ISocketMultiplexerJob*, bool, bool, bool); + + //! run() invokes \c object->method(arg) + TSocketMultiplexerMethodJob(T* object, Method method, + CArchSocket socket, bool readable, bool writeable); + virtual ~TSocketMultiplexerMethodJob(); + + // IJob overrides + virtual ISocketMultiplexerJob* + run(bool readable, bool writable, bool error); + virtual CArchSocket getSocket() const; + virtual bool isReadable() const; + virtual bool isWritable() const; + +private: + T* m_object; + Method m_method; + CArchSocket m_socket; + bool m_readable; + bool m_writable; + void* m_arg; +}; + +template +inline +TSocketMultiplexerMethodJob::TSocketMultiplexerMethodJob(T* object, + Method method, CArchSocket socket, + bool readable, bool writable) : + m_object(object), + m_method(method), + m_socket(ARCH->copySocket(socket)), + m_readable(readable), + m_writable(writable) +{ + // do nothing +} + +template +inline +TSocketMultiplexerMethodJob::~TSocketMultiplexerMethodJob() +{ + ARCH->closeSocket(m_socket); +} + +template +inline +ISocketMultiplexerJob* +TSocketMultiplexerMethodJob::run(bool read, bool write, bool error) +{ + if (m_object != NULL) { + return (m_object->*m_method)(this, read, write, error); + } + return NULL; +} + +template +inline +CArchSocket +TSocketMultiplexerMethodJob::getSocket() const +{ + return m_socket; +} + +template +inline +bool +TSocketMultiplexerMethodJob::isReadable() const +{ + return m_readable; +} + +template +inline +bool +TSocketMultiplexerMethodJob::isWritable() const +{ + return m_writable; +} + +#endif diff --git a/lib/server/CClientProxy.cpp b/lib/server/CClientProxy.cpp index 55e940f7..0df174c9 100644 --- a/lib/server/CClientProxy.cpp +++ b/lib/server/CClientProxy.cpp @@ -13,27 +13,24 @@ */ #include "CClientProxy.h" -#include "IInputStream.h" -#include "IOutputStream.h" +#include "IStream.h" // // CClientProxy // -CClientProxy::CClientProxy(IServer* server, const CString& name, - IInputStream* input, IOutputStream* output) : +CClientProxy::CClientProxy(IServer* server, + const CString& name, IStream* stream) : m_server(server), m_name(name), - m_input(input), - m_output(output) + m_stream(stream) { // do nothing } CClientProxy::~CClientProxy() { - delete m_output; - delete m_input; + delete m_stream; } IServer* @@ -42,16 +39,10 @@ CClientProxy::getServer() const return m_server; } -IInputStream* -CClientProxy::getInputStream() const +IStream* +CClientProxy::getStream() const { - return m_input; -} - -IOutputStream* -CClientProxy::getOutputStream() const -{ - return m_output; + return m_stream; } CString diff --git a/lib/server/CClientProxy.h b/lib/server/CClientProxy.h index 2ab41c35..751442ae 100644 --- a/lib/server/CClientProxy.h +++ b/lib/server/CClientProxy.h @@ -19,8 +19,7 @@ #include "CMutex.h" #include "CString.h" -class IInputStream; -class IOutputStream; +class IStream; class IServer; //! Generic proxy for client @@ -29,9 +28,7 @@ public: /*! \c name is the name of the client. */ - CClientProxy(IServer* server, const CString& name, - IInputStream* adoptedInput, - IOutputStream* adoptedOutput); + CClientProxy(IServer* server, const CString& name, IStream* adoptedStream); ~CClientProxy(); //! @name accessors @@ -43,17 +40,11 @@ public: */ IServer* getServer() const; - //! Get input stream + //! Get stream /*! - Returns the input stream passed to the c'tor. + Returns the stream passed to the c'tor. */ - IInputStream* getInputStream() const; - - //! Get output stream - /*! - Returns the output stream passed to the c'tor. - */ - IOutputStream* getOutputStream() const; + IStream* getStream() const; //@} @@ -98,8 +89,7 @@ private: CMutex m_mutex; IServer* m_server; CString m_name; - IInputStream* m_input; - IOutputStream* m_output; + IStream* m_stream; }; #endif diff --git a/lib/server/CClientProxy1_0.cpp b/lib/server/CClientProxy1_0.cpp index 7a9e734c..ccf5b357 100644 --- a/lib/server/CClientProxy1_0.cpp +++ b/lib/server/CClientProxy1_0.cpp @@ -17,46 +17,189 @@ #include "CClipboard.h" #include "CProtocolUtil.h" #include "XSynergy.h" -#include "IInputStream.h" -#include "IOutputStream.h" +#include "IStream.h" #include "CLock.h" -#include "CThread.h" #include "CLog.h" -#include "CStopwatch.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" #include // // CClientProxy1_0 // -CClientProxy1_0::CClientProxy1_0(IServer* server, const CString& name, - IInputStream* input, IOutputStream* output) : - CClientProxy(server, name, input, output), - m_heartRate(kHeartRate), - m_heartDeath(kHeartRate * kHeartBeatsUntilDeath) +CClientProxy1_0::CClientProxy1_0(IServer* server, + const CString& name, IStream* stream) : + CClientProxy(server, name, stream), + m_heartbeatAlarm(kHeartRate * kHeartBeatsUntilDeath), + m_heartbeatTimer(NULL) { for (UInt32 i = 0; i < kClipboardEnd; ++i) { m_clipboardDirty[i] = true; } + + // install event handlers + EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleData, NULL)); + EVENTQUEUE->adoptHandler(IStream::getOutputErrorEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleWriteError, NULL)); + EVENTQUEUE->adoptHandler(IStream::getInputShutdownEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleDisconnect, NULL)); + EVENTQUEUE->adoptHandler(IStream::getOutputShutdownEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleWriteError, NULL)); + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CClientProxy1_0::handleFlatline, NULL)); + + // FIXME -- open() replacement must install initial heartbeat timer } CClientProxy1_0::~CClientProxy1_0() { - // do nothing + removeHandlers(); } +void +CClientProxy1_0::disconnect() +{ + CLock lock(getMutex()); + removeHandlers(); + // FIXME -- send disconnect event (server should be listening for this) + getStream()->flush(); + getStream()->close(); +} + +void +CClientProxy1_0::removeHandlers() +{ + // uninstall event handlers + EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputErrorEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getInputShutdownEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputShutdownEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + + // remove timer + removeHeartbeatTimer(); +} + +void +CClientProxy1_0::addHeartbeatTimer() +{ + CLock lock(getMutex()); + if (m_heartbeatAlarm > 0.0) { + m_heartbeatTimer = EVENTQUEUE->newOneShotTimer(m_heartbeatAlarm, this); + } +} + +void +CClientProxy1_0::removeHeartbeatTimer() +{ + CLock lock(getMutex()); + if (m_heartbeatTimer != NULL) { + EVENTQUEUE->deleteTimer(m_heartbeatTimer); + m_heartbeatTimer = NULL; + } +} + +void +CClientProxy1_0::handleData(const CEvent&, void*) +{ + // handle messages until there are no more. first read message code. + UInt8 code[4]; + UInt32 n = getStream()->read(code, 4); + while (n != 0) { + // verify we got an entire code + if (n != 4) { + LOG((CLOG_ERR "incomplete message from \"%s\": %d bytes", getName().c_str(), n)); + disconnect(); + return; + } + + // parse message + LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); + if (!parseMessage(code)) { + LOG((CLOG_ERR "invalid message from client \"%s\"", getName().c_str())); + disconnect(); + return; + } + + // next message + n = getStream()->read(code, 4); + } + + // restart heartbeat timer + removeHeartbeatTimer(); + addHeartbeatTimer(); +} + +bool +CClientProxy1_0::parseMessage(const UInt8* code) +{ + if (memcmp(code, kMsgDInfo, 4) == 0) { + return recvInfo(true); + } + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // discard no-ops + LOG((CLOG_DEBUG2 "no-op from", getName().c_str())); + return true; + } + else if (memcmp(code, kMsgCClipboard, 4) == 0) { + return recvGrabClipboard(); + } + else if (memcmp(code, kMsgDClipboard, 4) == 0) { + return recvClipboard(); + } + return false; +} + +void +CClientProxy1_0::handleDisconnect(const CEvent&, void*) +{ + LOG((CLOG_NOTE "client \"%s\" disconnected", getName().c_str())); + disconnect(); +} + +void +CClientProxy1_0::handleWriteError(const CEvent&, void*) +{ + LOG((CLOG_ERR "error writing to client \"%s\"", getName().c_str())); + disconnect(); +} + +void +CClientProxy1_0::handleFlatline(const CEvent&, void*) +{ + // didn't get a heartbeat fast enough. assume client is dead. + LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str())); + disconnect(); +} + +// FIXME -- replace this void CClientProxy1_0::open() { // send request LOG((CLOG_DEBUG1 "querying client \"%s\" info", getName().c_str())); - CProtocolUtil::writef(getOutputStream(), kMsgQInfo); - getOutputStream()->flush(); + CProtocolUtil::writef(getStream(), kMsgQInfo); + getStream()->flush(); // wait for and verify reply UInt8 code[4]; for (;;) { - UInt32 n = getInputStream()->read(code, 4, -1.0); + UInt32 n = getStream()->read(code, 4); if (n == 4) { if (memcmp(code, kMsgCNoop, 4) == 0) { // discard heartbeats @@ -73,79 +216,15 @@ CClientProxy1_0::open() recvInfo(false); } -void -CClientProxy1_0::mainLoop() -{ - // handle messages until the client hangs up or stops sending heartbeats - CStopwatch heartTimer; - for (;;) { - CThread::testCancel(); - - // wait for a message - UInt8 code[4]; - UInt32 n = getInputStream()->read(code, 4, m_heartRate); - CThread::testCancel(); - - // check if client hungup - if (n == 0) { - LOG((CLOG_NOTE "client \"%s\" disconnected", getName().c_str())); - return; - } - - // check if client has stopped sending heartbeats - if (n == (UInt32)-1) { - if (m_heartDeath >= 0.0 && heartTimer.getTime() > m_heartDeath) { - LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str())); - return; - } - continue; - } - - // got a message so reset heartbeat monitor - heartTimer.reset(); - - // verify we got an entire code - if (n != 4) { - LOG((CLOG_ERR "incomplete message from \"%s\": %d bytes", getName().c_str(), n)); - - // client sent an incomplete message - throw XBadClient(); - } - - // parse message - LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); - if (memcmp(code, kMsgDInfo, 4) == 0) { - recvInfo(true); - } - else if (memcmp(code, kMsgCNoop, 4) == 0) { - // discard no-ops - LOG((CLOG_DEBUG2 "no-op from", getName().c_str())); - continue; - } - else if (memcmp(code, kMsgCClipboard, 4) == 0) { - recvGrabClipboard(); - } - else if (memcmp(code, kMsgDClipboard, 4) == 0) { - recvClipboard(); - } - // note -- more message handlers go here - else { - LOG((CLOG_ERR "invalid message from client \"%s\"", getName().c_str())); - - // unknown message - throw XBadClient(); - } - } -} - +// FIXME -- replace this void CClientProxy1_0::close() { LOG((CLOG_DEBUG1 "send close to \"%s\"", getName().c_str())); - CProtocolUtil::writef(getOutputStream(), kMsgCClose); + CProtocolUtil::writef(getStream(), kMsgCClose); // force the close to be sent before we return - getOutputStream()->flush(); + getStream()->flush(); } void @@ -153,7 +232,7 @@ CClientProxy1_0::enter(SInt32 xAbs, SInt32 yAbs, UInt32 seqNum, KeyModifierMask mask, bool) { LOG((CLOG_DEBUG1 "send enter to \"%s\", %d,%d %d %04x", getName().c_str(), xAbs, yAbs, seqNum, mask)); - CProtocolUtil::writef(getOutputStream(), kMsgCEnter, + CProtocolUtil::writef(getStream(), kMsgCEnter, xAbs, yAbs, seqNum, mask); } @@ -161,7 +240,7 @@ bool CClientProxy1_0::leave() { LOG((CLOG_DEBUG1 "send leave to \"%s\"", getName().c_str())); - CProtocolUtil::writef(getOutputStream(), kMsgCLeave); + CProtocolUtil::writef(getStream(), kMsgCLeave); // we can never prevent the user from leaving return true; @@ -177,7 +256,7 @@ CClientProxy1_0::setClipboard(ClipboardID id, const CString& data) m_clipboardDirty[id] = false; LOG((CLOG_DEBUG "send clipboard %d to \"%s\" size=%d", id, getName().c_str(), data.size())); - CProtocolUtil::writef(getOutputStream(), kMsgDClipboard, id, 0, &data); + CProtocolUtil::writef(getStream(), kMsgDClipboard, id, 0, &data); } } @@ -185,7 +264,7 @@ void CClientProxy1_0::grabClipboard(ClipboardID id) { LOG((CLOG_DEBUG "send grab clipboard %d to \"%s\"", id, getName().c_str())); - CProtocolUtil::writef(getOutputStream(), kMsgCClipboard, id, 0); + CProtocolUtil::writef(getStream(), kMsgCClipboard, id, 0); // this clipboard is now dirty CLock lock(getMutex()); @@ -203,7 +282,7 @@ void CClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton) { LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); - CProtocolUtil::writef(getOutputStream(), kMsgDKeyDown1_0, key, mask); + CProtocolUtil::writef(getStream(), kMsgDKeyDown1_0, key, mask); } void @@ -211,78 +290,81 @@ CClientProxy1_0::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton) { LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d", getName().c_str(), key, mask, count)); - CProtocolUtil::writef(getOutputStream(), kMsgDKeyRepeat1_0, key, mask, count); + CProtocolUtil::writef(getStream(), kMsgDKeyRepeat1_0, key, mask, count); } void CClientProxy1_0::keyUp(KeyID key, KeyModifierMask mask, KeyButton) { LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); - CProtocolUtil::writef(getOutputStream(), kMsgDKeyUp1_0, key, mask); + CProtocolUtil::writef(getStream(), kMsgDKeyUp1_0, key, mask); } void CClientProxy1_0::mouseDown(ButtonID button) { LOG((CLOG_DEBUG1 "send mouse down to \"%s\" id=%d", getName().c_str(), button)); - CProtocolUtil::writef(getOutputStream(), kMsgDMouseDown, button); + CProtocolUtil::writef(getStream(), kMsgDMouseDown, button); } void CClientProxy1_0::mouseUp(ButtonID button) { LOG((CLOG_DEBUG1 "send mouse up to \"%s\" id=%d", getName().c_str(), button)); - CProtocolUtil::writef(getOutputStream(), kMsgDMouseUp, button); + CProtocolUtil::writef(getStream(), kMsgDMouseUp, button); } void CClientProxy1_0::mouseMove(SInt32 xAbs, SInt32 yAbs) { LOG((CLOG_DEBUG2 "send mouse move to \"%s\" %d,%d", getName().c_str(), xAbs, yAbs)); - CProtocolUtil::writef(getOutputStream(), kMsgDMouseMove, xAbs, yAbs); + CProtocolUtil::writef(getStream(), kMsgDMouseMove, xAbs, yAbs); } void CClientProxy1_0::mouseWheel(SInt32 delta) { LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d", getName().c_str(), delta)); - CProtocolUtil::writef(getOutputStream(), kMsgDMouseWheel, delta); + CProtocolUtil::writef(getStream(), kMsgDMouseWheel, delta); } void CClientProxy1_0::screensaver(bool on) { LOG((CLOG_DEBUG1 "send screen saver to \"%s\" on=%d", getName().c_str(), on ? 1 : 0)); - CProtocolUtil::writef(getOutputStream(), kMsgCScreenSaver, on ? 1 : 0); + CProtocolUtil::writef(getStream(), kMsgCScreenSaver, on ? 1 : 0); } void CClientProxy1_0::resetOptions() { LOG((CLOG_DEBUG1 "send reset options to \"%s\"", getName().c_str())); - CProtocolUtil::writef(getOutputStream(), kMsgCResetOptions); + CProtocolUtil::writef(getStream(), kMsgCResetOptions); // reset heart rate and death CLock lock(getMutex()); - m_heartRate = kHeartRate; - m_heartDeath = kHeartRate * kHeartBeatsUntilDeath; + m_heartbeatAlarm = kHeartRate * kHeartBeatsUntilDeath; + removeHeartbeatTimer(); + addHeartbeatTimer(); } void CClientProxy1_0::setOptions(const COptionsList& options) { LOG((CLOG_DEBUG1 "send set options to \"%s\" size=%d", getName().c_str(), options.size())); - CProtocolUtil::writef(getOutputStream(), kMsgDSetOptions, &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) { - m_heartRate = 1.0e-3 * static_cast(options[i + 1]); - if (m_heartRate <= 0.0) { - m_heartRate = -1.0; + double rate = 1.0e-3 * static_cast(options[i + 1]); + if (rate <= 0.0) { + rate = -1.0; } - m_heartDeath = m_heartRate * kHeartBeatsUntilDeath; + m_heartbeatAlarm = rate * kHeartBeatsUntilDeath; + removeHeartbeatTimer(); + addHeartbeatTimer(); } } } @@ -318,7 +400,7 @@ CClientProxy1_0::getCursorCenter(SInt32& x, SInt32& y) const y = m_info.m_my; } -void +bool CClientProxy1_0::recvInfo(bool notify) { { @@ -326,16 +408,18 @@ CClientProxy1_0::recvInfo(bool notify) // parse the message SInt16 x, y, w, h, zoneSize, mx, my; - CProtocolUtil::readf(getInputStream(), kMsgDInfo + 4, - &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) { - throw XBadClient(); + return false; } if (mx < x || my < y || mx >= x + w || my >= y + h) { - throw XBadClient(); + return false; } // save @@ -355,44 +439,52 @@ CClientProxy1_0::recvInfo(bool notify) // acknowledge receipt LOG((CLOG_DEBUG1 "send info ack to \"%s\"", getName().c_str())); - CProtocolUtil::writef(getOutputStream(), kMsgCInfoAck); + CProtocolUtil::writef(getStream(), kMsgCInfoAck); + return true; } -void +bool CClientProxy1_0::recvClipboard() { // parse message ClipboardID id; UInt32 seqNum; CString data; - CProtocolUtil::readf(getInputStream(), kMsgDClipboard + 4, &id, &seqNum, &data); + if (!CProtocolUtil::readf(getStream(), + kMsgDClipboard + 4, &id, &seqNum, &data)) { + return false; + } LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d", getName().c_str(), id, seqNum, data.size())); // validate if (id >= kClipboardEnd) { - throw XBadClient(); + 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); + return true; } -void +bool CClientProxy1_0::recvGrabClipboard() { // parse message ClipboardID id; UInt32 seqNum; - CProtocolUtil::readf(getInputStream(), kMsgCClipboard + 4, &id, &seqNum); + if (!CProtocolUtil::readf(getStream(), kMsgCClipboard + 4, &id, &seqNum)) { + return false; + } LOG((CLOG_DEBUG "received client \"%s\" grabbed clipboard %d seqnum=%d", getName().c_str(), id, seqNum)); // validate if (id >= kClipboardEnd) { - throw XBadClient(); + 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); + return true; } diff --git a/lib/server/CClientProxy1_0.h b/lib/server/CClientProxy1_0.h index 0a86e1a9..175f3c37 100644 --- a/lib/server/CClientProxy1_0.h +++ b/lib/server/CClientProxy1_0.h @@ -18,12 +18,14 @@ #include "CClientProxy.h" #include "ProtocolTypes.h" +class CEvent; +class CEventQueueTimer; + //! Proxy for client implementing protocol version 1.0 class CClientProxy1_0 : public CClientProxy { public: CClientProxy1_0(IServer* server, const CString& name, - IInputStream* adoptedInput, - IOutputStream* adoptedOutput); + IStream* adoptedStream); ~CClientProxy1_0(); // IClient overrides @@ -54,16 +56,29 @@ public: virtual void getCursorPos(SInt32& x, SInt32& y) const; virtual void getCursorCenter(SInt32& x, SInt32& y) const; +protected: + virtual bool parseMessage(const UInt8* code); + private: - void recvInfo(bool notify); - void recvClipboard(); - void recvGrabClipboard(); + void disconnect(); + void removeHandlers(); + void addHeartbeatTimer(); + void removeHeartbeatTimer(); + + void handleData(const CEvent&, void*); + void handleDisconnect(const CEvent&, void*); + void handleWriteError(const CEvent&, void*); + void handleFlatline(const CEvent&, void*); + + bool recvInfo(bool notify); + bool recvClipboard(); + bool recvGrabClipboard(); private: CClientInfo m_info; bool m_clipboardDirty[kClipboardEnd]; - double m_heartRate; - double m_heartDeath; + double m_heartbeatAlarm; + CEventQueueTimer* m_heartbeatTimer; }; #endif diff --git a/lib/server/CClientProxy1_1.cpp b/lib/server/CClientProxy1_1.cpp index a09b27bd..b5d2cf21 100644 --- a/lib/server/CClientProxy1_1.cpp +++ b/lib/server/CClientProxy1_1.cpp @@ -21,9 +21,9 @@ // CClientProxy1_1 // -CClientProxy1_1::CClientProxy1_1(IServer* server, const CString& name, - IInputStream* input, IOutputStream* output) : - CClientProxy1_0(server, name, input, output) +CClientProxy1_1::CClientProxy1_1(IServer* server, + const CString& name, IStream* stream) : + CClientProxy1_0(server, name, stream) { // do nothing } @@ -37,7 +37,7 @@ void CClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) { LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); - CProtocolUtil::writef(getOutputStream(), kMsgDKeyDown, key, mask, button); + CProtocolUtil::writef(getStream(), kMsgDKeyDown, key, mask, button); } void @@ -45,12 +45,12 @@ CClientProxy1_1::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button) { LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d, button=0x%04x", getName().c_str(), key, mask, count, button)); - CProtocolUtil::writef(getOutputStream(), kMsgDKeyRepeat, key, mask, count, button); + CProtocolUtil::writef(getStream(), kMsgDKeyRepeat, key, mask, count, button); } void CClientProxy1_1::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) { LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); - CProtocolUtil::writef(getOutputStream(), kMsgDKeyUp, key, mask, button); + CProtocolUtil::writef(getStream(), kMsgDKeyUp, key, mask, button); } diff --git a/lib/server/CClientProxy1_1.h b/lib/server/CClientProxy1_1.h index dbae783e..5e21f21b 100644 --- a/lib/server/CClientProxy1_1.h +++ b/lib/server/CClientProxy1_1.h @@ -21,8 +21,7 @@ class CClientProxy1_1 : public CClientProxy1_0 { public: CClientProxy1_1(IServer* server, const CString& name, - IInputStream* adoptedInput, - IOutputStream* adoptedOutput); + IStream* adoptedStream); ~CClientProxy1_1(); // IClient overrides diff --git a/lib/server/CConfig.cpp b/lib/server/CConfig.cpp index 89417b28..de20f312 100644 --- a/lib/server/CConfig.cpp +++ b/lib/server/CConfig.cpp @@ -239,12 +239,6 @@ CConfig::setSynergyAddress(const CNetworkAddress& addr) m_synergyAddress = addr; } -void -CConfig::setHTTPAddress(const CNetworkAddress& addr) -{ - m_httpAddress = addr; -} - bool CConfig::addOption(const CString& name, OptionID option, OptionValue value) { @@ -430,12 +424,6 @@ CConfig::getSynergyAddress() const return m_synergyAddress; } -const CNetworkAddress& -CConfig::getHTTPAddress() const -{ - return m_httpAddress; -} - const CConfig::CScreenOptions* CConfig::getOptions(const CString& name) const { @@ -462,9 +450,6 @@ CConfig::operator==(const CConfig& x) const if (m_synergyAddress != x.m_synergyAddress) { return false; } - if (m_httpAddress != x.m_httpAddress) { - return false; - } */ if (m_map.size() != x.m_map.size()) { return false; @@ -782,14 +767,6 @@ CConfig::readSectionOptions(std::istream& s) throw XConfigRead("invalid address argument"); } } - else if (name == "http") { - try { - m_httpAddress = CNetworkAddress(value, kDefaultPort + 1); - } - catch (XSocketAddress&) { - throw XConfigRead("invalid http argument"); - } - } else if (name == "heartbeat") { addOption("", kOptionHeartbeat, parseInt(value)); } @@ -1063,10 +1040,6 @@ operator<<(std::ostream& s, const CConfig& config) s << "\taddress = " << config.m_synergyAddress.getHostname().c_str() << std::endl; } - if (config.m_httpAddress.isValid()) { - s << "\thttp = " << - config.m_httpAddress.getHostname().c_str() << std::endl; - } s << "end" << std::endl; // screens section diff --git a/lib/server/CConfig.h b/lib/server/CConfig.h index 28e2c5b4..a2a9eed5 100644 --- a/lib/server/CConfig.h +++ b/lib/server/CConfig.h @@ -175,13 +175,6 @@ public: */ void setSynergyAddress(const CNetworkAddress&); - //! Set HTTP server address - /*! - Set the HTTP listen addresses. There is no default address so - this must be called to run an HTTP server using this configuration. - */ - void setHTTPAddress(const CNetworkAddress&); - //! Add a screen option /*! Adds an option and its value to the named screen. Replaces the @@ -255,8 +248,6 @@ public: //! Get the server address const CNetworkAddress& getSynergyAddress() const; - //! Get the HTTP server address - const CNetworkAddress& getHTTPAddress() const; //! Get the screen options /*! @@ -308,7 +299,6 @@ private: CCellMap m_map; CNameMap m_nameToCanonicalName; CNetworkAddress m_synergyAddress; - CNetworkAddress m_httpAddress; CScreenOptions m_globalOptions; }; diff --git a/lib/server/CHTTPServer.cpp b/lib/server/CHTTPServer.cpp deleted file mode 100644 index d3582ac4..00000000 --- a/lib/server/CHTTPServer.cpp +++ /dev/null @@ -1,799 +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. - */ - -#include "CHTTPServer.h" -#include "CConfig.h" -#include "CHTTPProtocol.h" -#include "CServer.h" -#include "XHTTP.h" -#include "IDataSocket.h" -#include "XThread.h" -#include "CLog.h" -#include "stdset.h" -#include "stdsstream.h" - -// -// CHTTPServer -// - -// maximum size of an HTTP request. this should be large enough to -// handle any reasonable request but small enough to prevent a -// malicious client from causing us to use too much memory. -const UInt32 CHTTPServer::s_maxRequestSize = 32768; - -CHTTPServer::CHTTPServer( - CServer* server) : - m_server(server) -{ - // do nothing -} - -CHTTPServer::~CHTTPServer() -{ - // do nothing -} - -void -CHTTPServer::processRequest(IDataSocket* socket) -{ - assert(socket != NULL); - - CHTTPRequest* request = NULL; - try { - // parse request - request = CHTTPProtocol::readRequest( - socket->getInputStream(), s_maxRequestSize); - if (request == NULL) { - throw XHTTP(400); - } - - // if absolute uri then strip off scheme and host - if (request->m_uri[0] != '/') { - CString::size_type n = request->m_uri.find('/'); - if (n == CString::npos) { - throw XHTTP(404); - } - request->m_uri = request->m_uri.substr(n); - } - - // prepare reply - CHTTPReply reply; - reply.m_majorVersion = request->m_majorVersion; - reply.m_minorVersion = request->m_minorVersion; - reply.m_status = 200; - reply.m_reason = "OK"; - reply.m_method = request->m_method; - - // process - doProcessRequest(*request, reply); - - // send reply - CHTTPProtocol::reply(socket->getOutputStream(), reply); - LOG((CLOG_INFO "HTTP reply %d for %s %s", reply.m_status, request->m_method.c_str(), request->m_uri.c_str())); - - // clean up - delete request; - } - catch (XHTTP& e) { - LOG((CLOG_WARN "returning HTTP error %d %s for %s", e.getStatus(), e.getReason().c_str(), (request != NULL) ? request->m_uri.c_str() : "")); - - // clean up - delete request; - - // return error - CHTTPReply reply; - reply.m_majorVersion = 1; - reply.m_minorVersion = 0; - reply.m_status = e.getStatus(); - reply.m_reason = e.getReason(); - reply.m_method = "GET"; -// FIXME -- use a nicer error page - reply.m_headers.push_back(std::make_pair(CString("Content-Type"), - CString("text/plain"))); - reply.m_body = e.getReason(); - e.addHeaders(reply); - CHTTPProtocol::reply(socket->getOutputStream(), reply); - } - catch (...) { - // ignore other exceptions - RETHROW_XTHREAD - } -} - -void -CHTTPServer::doProcessRequest(CHTTPRequest& request, CHTTPReply& reply) -{ - // switch based on uri - if (request.m_uri == "/editmap") { - if (request.m_method == "GET" || request.m_method == "HEAD") { - doProcessGetEditMap(request, reply); - reply.m_headers.push_back(std::make_pair( - CString("Content-Type"), - CString("text/html"))); - } - else if (request.m_method == "POST") { - doProcessPostEditMap(request, reply); - reply.m_headers.push_back(std::make_pair( - CString("Content-Type"), - CString("text/html"))); - } - else { - throw XHTTPAllow("GET, HEAD, POST"); - } - } - else { - // unknown page - throw XHTTP(404); - } -} - -void -CHTTPServer::doProcessGetEditMap(CHTTPRequest& /*request*/, CHTTPReply& reply) -{ - static const char* s_editMapProlog1 = - "\r\n" - "\r\n" - " Synergy -- Edit Screens\r\n" - "\r\n" - "\r\n" - "
\r\n" - " \r\n"; - static const char* s_editMapEpilog = - " \r\n" - " \r\n" - "
\r\n" - "
\r\n" - "\r\n" - "\r\n"; - static const char* s_editMapTableProlog = - "
" - " \r\n"; - static const char* s_editMapTableEpilog = - "
" - "
\r\n"; - static const char* s_editMapRowProlog = - "\r\n"; - static const char* s_editMapRowEpilog = - "\r\n"; - static const char* s_editMapScreenDummy = - ""; - static const char* s_editMapScreenPrimary = - ""; - static const char* s_editMapScreenEnd = - "\r\n"; - - std::ostringstream s; - - // convert screen map into a temporary screen map - CScreenArray screens; - { - CConfig config; - m_server->getConfig(&config); - screens.convertFrom(config); - // FIXME -- note to user if config couldn't be exactly represented - } - - // insert blank columns and rows around array (to allow the user - // to insert new screens) - screens.insertColumn(0); - screens.insertColumn(screens.getWidth()); - screens.insertRow(0); - screens.insertRow(screens.getHeight()); - - // get array size - const SInt32 w = screens.getWidth(); - const SInt32 h = screens.getHeight(); - - // construct reply - reply.m_body += s_editMapProlog1; - s << w << "x" << h; - reply.m_body += s.str(); - reply.m_body += s_editMapProlog2; - - // add screen map for editing - const CString primaryName = m_server->getPrimaryScreenName(); - reply.m_body += s_editMapTableProlog; - for (SInt32 y = 0; y < h; ++y) { - reply.m_body += s_editMapRowProlog; - for (SInt32 x = 0; x < w; ++x) { - s.str(""); - if (!screens.isAllowed(x, y) && screens.get(x, y) != primaryName) { - s << s_editMapScreenDummy; - } - else { - if (!screens.isSet(x, y)) { - s << s_editMapScreenDead; - } - else if (screens.get(x, y) == primaryName) { - s << s_editMapScreenPrimary; - } - else { - s << s_editMapScreenLive; - } - s << screens.get(x, y) << - s_editMapScreenLiveDead1 << - "n" << x << "x" << y << - s_editMapScreenLiveDead2; - } - s << s_editMapScreenEnd; - reply.m_body += s.str(); - } - reply.m_body += s_editMapRowEpilog; - } - reply.m_body += s_editMapTableEpilog; - - reply.m_body += s_editMapEpilog; -} - -void -CHTTPServer::doProcessPostEditMap(CHTTPRequest& request, CHTTPReply& reply) -{ - typedef std::vector ScreenArray; - typedef std::set ScreenSet; - - // parse the result - CHTTPProtocol::CFormParts parts; - if (!CHTTPProtocol::parseFormData(request, parts)) { - LOG((CLOG_WARN "editmap: cannot parse form data")); - throw XHTTP(400); - } - - try { - std::ostringstream s; - - // convert post data into a temporary screen map. also check - // that no screen name is invalid or used more than once. - SInt32 w, h; - CHTTPProtocol::CFormParts::iterator index = parts.find("size"); - if (index == parts.end() || - !parseXY(index->second, w, h) || - w <= 0 || h <= 0) { - LOG((CLOG_WARN "editmap: cannot parse size or size is invalid")); - throw XHTTP(400); - } - ScreenSet screenNames; - CScreenArray screens; - screens.resize(w, h); - for (SInt32 y = 0; y < h; ++y) { - for (SInt32 x = 0; x < w; ++x) { - // find part - s.str(""); - s << "n" << x << "x" << y; - index = parts.find(s.str()); - if (index == parts.end()) { - // FIXME -- screen is missing. error? - continue; - } - - // skip blank names - const CString& name = index->second; - if (name.empty()) { - continue; - } - - // check name. name must be legal and must not have - // already been seen. - if (screenNames.count(name)) { - // FIXME -- better error message - LOG((CLOG_WARN "editmap: duplicate name %s", name.c_str())); - throw XHTTP(400); - } - // FIXME -- check that name is legal - - // save name. if we've already seen the name then - // report an error. - screens.set(x, y, name); - screenNames.insert(name); - } - } - - // if new map is invalid then return error. map is invalid if: - // there are no screens, or - // the screens are not 4-connected. - if (screenNames.empty()) { - // no screens - // FIXME -- need better no screens - LOG((CLOG_WARN "editmap: no screens")); - throw XHTTP(400); - } - if (!screens.isValid()) { - // FIXME -- need better unconnected screens error - LOG((CLOG_WARN "editmap: unconnected screens")); - throw XHTTP(400); - } - - // convert temporary screen map into a regular map - CConfig config; - m_server->getConfig(&config); - screens.convertTo(config); - - // set new screen map on server - m_server->setConfig(config); - - // now reply with current map - doProcessGetEditMap(request, reply); - } - catch (XHTTP&) { - // FIXME -- construct a more meaningful error? - throw; - } -} - -bool -CHTTPServer::parseXY(const CString& xy, SInt32& x, SInt32& y) -{ - std::istringstream s(xy); - char delimiter; - s >> x; - s.get(delimiter); - s >> y; - return (!!s && delimiter == 'x'); -} - - -// -// CHTTPServer::CScreenArray -// - -CHTTPServer::CScreenArray::CScreenArray() : - m_w(0), - m_h(0) -{ - // do nothing -} - -CHTTPServer::CScreenArray::~CScreenArray() -{ - // do nothing -} - -void -CHTTPServer::CScreenArray::resize(SInt32 w, SInt32 h) -{ - m_screens.clear(); - m_screens.resize(w * h); - m_w = w; - m_h = h; -} - -void -CHTTPServer::CScreenArray::insertRow(SInt32 i) -{ - assert(i >= 0 && i <= m_h); - - CNames newScreens; - newScreens.resize(m_w * (m_h + 1)); - - for (SInt32 y = 0; y < i; ++y) { - for (SInt32 x = 0; x < m_w; ++x) { - newScreens[x + y * m_w] = m_screens[x + y * m_w]; - } - } - for (SInt32 y = i; y < m_h; ++y) { - for (SInt32 x = 0; x < m_w; ++x) { - newScreens[x + (y + 1) * m_w] = m_screens[x + y * m_w]; - } - } - - m_screens.swap(newScreens); - ++m_h; -} - -void -CHTTPServer::CScreenArray::insertColumn(SInt32 i) -{ - assert(i >= 0 && i <= m_w); - - CNames newScreens; - newScreens.resize((m_w + 1) * m_h); - - for (SInt32 y = 0; y < m_h; ++y) { - for (SInt32 x = 0; x < i; ++x) { - newScreens[x + y * (m_w + 1)] = m_screens[x + y * m_w]; - } - for (SInt32 x = i; x < m_w; ++x) { - newScreens[(x + 1) + y * (m_w + 1)] = m_screens[x + y * m_w]; - } - } - - m_screens.swap(newScreens); - ++m_w; -} - -void -CHTTPServer::CScreenArray::eraseRow(SInt32 i) -{ - assert(i >= 0 && i < m_h); - - CNames newScreens; - newScreens.resize(m_w * (m_h - 1)); - - for (SInt32 y = 0; y < i; ++y) { - for (SInt32 x = 0; x < m_w; ++x) { - newScreens[x + y * m_w] = m_screens[x + y * m_w]; - } - } - for (SInt32 y = i + 1; y < m_h; ++y) { - for (SInt32 x = 0; x < m_w; ++x) { - newScreens[x + (y - 1) * m_w] = m_screens[x + y * m_w]; - } - } - - m_screens.swap(newScreens); - --m_h; -} - -void -CHTTPServer::CScreenArray::eraseColumn(SInt32 i) -{ - assert(i >= 0 && i < m_w); - - CNames newScreens; - newScreens.resize((m_w - 1) * m_h); - - for (SInt32 y = 0; y < m_h; ++y) { - for (SInt32 x = 0; x < m_w; ++x) { - newScreens[x + y * (m_w - 1)] = m_screens[x + y * m_w]; - } - for (SInt32 x = i + 1; x < m_w; ++x) { - newScreens[(x - 1) + y * (m_w - 1)] = m_screens[x + y * m_w]; - } - } - - m_screens.swap(newScreens); - --m_w; -} - -void -CHTTPServer::CScreenArray::rotateRows(SInt32 i) -{ - // nothing to do if no rows - if (m_h == 0) { - return; - } - - // convert to canonical form - if (i < 0) { - i = m_h - ((-i) % m_h); - } - else { - i %= m_h; - } - if (i == 0 || i == m_h) { - return; - } - - while (i > 0) { - // rotate one row - for (SInt32 x = 0; x < m_w; ++x) { - CString tmp = m_screens[x]; - for (SInt32 y = 1; y < m_h; ++y) { - m_screens[x + (y - 1) * m_w] = m_screens[x + y * m_w]; - } - m_screens[x + (m_h - 1) * m_w] = tmp; - } - } -} - -void -CHTTPServer::CScreenArray::rotateColumns(SInt32 i) -{ - // nothing to do if no columns - if (m_h == 0) { - return; - } - - // convert to canonical form - if (i < 0) { - i = m_w - ((-i) % m_w); - } - else { - i %= m_w; - } - if (i == 0 || i == m_w) { - return; - } - - while (i > 0) { - // rotate one column - for (SInt32 y = 0; y < m_h; ++y) { - CString tmp = m_screens[0 + y * m_w]; - for (SInt32 x = 1; x < m_w; ++x) { - m_screens[x - 1 + y * m_w] = m_screens[x + y * m_w]; - } - m_screens[m_w - 1 + y * m_w] = tmp; - } - } -} - -void -CHTTPServer::CScreenArray::remove(SInt32 x, SInt32 y) -{ - set(x, y, CString()); -} - -void -CHTTPServer::CScreenArray::set(SInt32 x, SInt32 y, const CString& name) -{ - assert(x >= 0 && x < m_w); - assert(y >= 0 && y < m_h); - - m_screens[x + y * m_w] = name; -} - -bool -CHTTPServer::CScreenArray::isAllowed(SInt32 x, SInt32 y) const -{ - assert(x >= 0 && x < m_w); - assert(y >= 0 && y < m_h); - - if (x > 0 && !m_screens[(x - 1) + y * m_w].empty()) { - return true; - } - if (x < m_w - 1 && !m_screens[(x + 1) + y * m_w].empty()) { - return true; - } - if (y > 0 && !m_screens[x + (y - 1) * m_w].empty()) { - return true; - } - if (y < m_h - 1 && !m_screens[x + (y + 1) * m_w].empty()) { - return true; - } - return false; -} - -bool -CHTTPServer::CScreenArray::isSet(SInt32 x, SInt32 y) const -{ - assert(x >= 0 && x < m_w); - assert(y >= 0 && y < m_h); - - return !m_screens[x + y * m_w].empty(); -} - -CString -CHTTPServer::CScreenArray::get(SInt32 x, SInt32 y) const -{ - assert(x >= 0 && x < m_w); - assert(y >= 0 && y < m_h); - - return m_screens[x + y * m_w]; -} - -bool -CHTTPServer::CScreenArray::find(const CString& name, - SInt32& xOut, SInt32& yOut) const -{ - for (SInt32 y = 0; y < m_h; ++y) { - for (SInt32 x = 0; x < m_w; ++x) { - if (m_screens[x + y * m_w] == name) { - xOut = x; - yOut = y; - return true; - } - } - } - return false; -} - -bool -CHTTPServer::CScreenArray::isValid() const -{ - SInt32 count = 0, isolated = 0; - for (SInt32 y = 0; y < m_h; ++y) { - for (SInt32 x = 0; x < m_w; ++x) { - if (isSet(x, y)) { - ++count; - if (!isAllowed(x, y)) { - ++isolated; - } - } - } - } - return (count <= 1 || isolated == 0); -} - -bool -CHTTPServer::CScreenArray::convertFrom(const CConfig& config) -{ - typedef std::set ScreenSet; - - // insert the first screen - CConfig::const_iterator index = config.begin(); - if (index == config.end()) { - // no screens - resize(0, 0); - return true; - } - CString name = *index; - resize(1, 1); - set(0, 0, name); - - // flood fill state - CNames screenStack; - ScreenSet doneSet; - - // put all but the first screen on the stack - // note -- if all screens are 4-connected then we can skip this - while (++index != config.end()) { - screenStack.push_back(*index); - } - - // put the first screen on the stack last so we process it first - screenStack.push_back(name); - - // perform a flood fill using the stack as the seeds - while (!screenStack.empty()) { - // get next screen from stack - CString name = screenStack.back(); - screenStack.pop_back(); - - // skip screen if we've seen it before - if (doneSet.count(name) > 0) { - continue; - } - - // add this screen to doneSet so we don't process it again - doneSet.insert(name); - - // find the screen. if it's not found then not all of the - // screens are 4-connected. discard disconnected screens. - SInt32 x, y; - if (!find(name, x, y)) { - continue; - } - - // insert the screen's neighbors - // FIXME -- handle edge wrapping - CString neighbor; - neighbor = config.getNeighbor(name, kLeft); - if (!neighbor.empty() && doneSet.count(neighbor) == 0) { - // insert left neighbor, adding a column if necessary - if (x == 0 || get(x - 1, y) != neighbor) { - ++x; - insertColumn(x - 1); - set(x - 1, y, neighbor); - } - screenStack.push_back(neighbor); - } - neighbor = config.getNeighbor(name, kRight); - if (!neighbor.empty() && doneSet.count(neighbor) == 0) { - // insert right neighbor, adding a column if necessary - if (x == m_w - 1 || get(x + 1, y) != neighbor) { - insertColumn(x + 1); - set(x + 1, y, neighbor); - } - screenStack.push_back(neighbor); - } - neighbor = config.getNeighbor(name, kTop); - if (!neighbor.empty() && doneSet.count(neighbor) == 0) { - // insert top neighbor, adding a row if necessary - if (y == 0 || get(x, y - 1) != neighbor) { - ++y; - insertRow(y - 1); - set(x, y - 1, neighbor); - } - screenStack.push_back(neighbor); - } - neighbor = config.getNeighbor(name, kBottom); - if (!neighbor.empty() && doneSet.count(neighbor) == 0) { - // insert bottom neighbor, adding a row if necessary - if (y == m_h - 1 || get(x, y + 1) != neighbor) { - insertRow(y + 1); - set(x, y + 1, neighbor); - } - screenStack.push_back(neighbor); - } - } - - // check symmetry - // FIXME -- handle edge wrapping - for (index = config.begin(); index != config.end(); ++index) { - const CString& name = *index; - SInt32 x, y; - if (!find(name, x, y)) { - return false; - } - - CString neighbor; - neighbor = config.getNeighbor(name, kLeft); - if ((x == 0 && !neighbor.empty()) || - (x > 0 && get(x - 1, y) != neighbor)) { - return false; - } - - neighbor = config.getNeighbor(name, kRight); - if ((x == m_w - 1 && !neighbor.empty()) || - (x < m_w - 1 && get(x + 1, y) != neighbor)) { - return false; - } - - neighbor = config.getNeighbor(name, kTop); - if ((y == 0 && !neighbor.empty()) || - (y > 0 && get(x, y - 1) != neighbor)) { - return false; - } - - neighbor = config.getNeighbor(name, kBottom); - if ((y == m_h - 1 && !neighbor.empty()) || - (y < m_h - 1 && get(x, y + 1) != neighbor)) { - return false; - } - } - - return true; -} - -void -CHTTPServer::CScreenArray::convertTo(CConfig& config) const -{ - config.removeAllScreens(); - - // add screens and find smallest box containing all screens - SInt32 x0 = m_w, x1 = 0, y0 = m_h, y1 = 0; - for (SInt32 y = 0; y < m_h; ++y) { - for (SInt32 x = 0; x < m_w; ++x) { - if (isSet(x, y)) { - config.addScreen(get(x, y)); - if (x < x0) { - x0 = x; - } - if (x > x1) { - x1 = x; - } - if (y < y0) { - y0 = y; - } - if (y > y1) { - y1 = y; - } - } - - } - } - - // make connections between screens - // FIXME -- add support for wrapping - // FIXME -- mark topmost and leftmost screens - for (SInt32 y = 0; y < m_h; ++y) { - for (SInt32 x = 0; x < m_w; ++x) { - if (!isSet(x, y)) { - continue; - } - if (x > x0 && isSet(x - 1, y)) { - config.connect(get(x, y), kLeft, get(x - 1, y)); - } - if (x < x1 && isSet(x + 1, y)) { - config.connect(get(x, y), kRight, get(x + 1, y)); - } - if (y > y0 && isSet(x, y - 1)) { - config.connect(get(x, y), kTop, get(x, y - 1)); - } - if (y < y1 && isSet(x, y + 1)) { - config.connect(get(x, y), kBottom, get(x, y + 1)); - } - } - } -} diff --git a/lib/server/CHTTPServer.h b/lib/server/CHTTPServer.h deleted file mode 100644 index 18f80440..00000000 --- a/lib/server/CHTTPServer.h +++ /dev/null @@ -1,142 +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 CHTTPSERVER_H -#define CHTTPSERVER_H - -#include "CString.h" -#include "BasicTypes.h" -#include "stdvector.h" - -class CServer; -class CConfig; -class CHTTPRequest; -class CHTTPReply; -class IDataSocket; - -//! Simple HTTP server -/*! -This class implements a simple HTTP server for interacting with the -synergy server. -*/ -class CHTTPServer { -public: - CHTTPServer(CServer*); - virtual ~CHTTPServer(); - - //! @name manipulators - //@{ - - //! Process HTTP request - /*! - Synchronously processes an HTTP request on the given socket. - */ - void processRequest(IDataSocket*); - - //@} - -protected: - //! Process HTTP request - /*! - Processes a successfully read HTTP request. The reply is partially - filled in (version, method, status (200) and reason (OK)). This - method checks the URI and handles the request, filling in the rest - of the reply. If the request cannot be satisfied it throws an - appropriate XHTTP exception. - */ - virtual void doProcessRequest(CHTTPRequest&, CHTTPReply&); - - //! Process request for map - virtual void doProcessGetEditMap(CHTTPRequest&, CHTTPReply&); - - //! Process request for changing map - virtual void doProcessPostEditMap(CHTTPRequest&, CHTTPReply&); - - //! Parse coordinate string - static bool parseXY(const CString&, SInt32& x, SInt32& y); - - //! Screen map helper - /*! - This class represents the screen map as a resizable array. It's - used to handle map requests. - */ - class CScreenArray { - public: - CScreenArray(); - ~CScreenArray(); - - // resize the array. this also clears all the elements. - void resize(SInt32 w, SInt32 h); - - // insert/remove a row/column. all elements in a new row/column - // are unset. - void insertRow(SInt32 insertedBeforeRow); - void insertColumn(SInt32 insertedBeforeColumn); - void eraseRow(SInt32 row); - void eraseColumn(SInt32 column); - - // rotate rows or columns - void rotateRows(SInt32 rowsDown); - void rotateColumns(SInt32 columnsDown); - - // remove/set a screen name. setting an empty name is the - // same as removing a name. names are not checked for - // validity. - void remove(SInt32 x, SInt32 y); - void set(SInt32 x, SInt32 y, const CString&); - - // convert a CConfig to a CScreenArray. returns true iff - // all connections are symmetric and therefore exactly - // representable by a CScreenArray. - bool convertFrom(const CConfig&); - - // accessors - - // get the array size - SInt32 getWidth() const { return m_w; } - SInt32 getHeight() const { return m_h; } - - // returns true iff the cell has a 4-connected neighbor - bool isAllowed(SInt32 x, SInt32 y) const; - - // returns true iff the cell has a (non-empty) name - bool isSet(SInt32 x, SInt32 y) const; - - // get a screen name - CString get(SInt32 x, SInt32 y) const; - - // find a screen by name. returns true iff found. - bool find(const CString&, SInt32& x, SInt32& y) const; - - // return true iff the overall array is valid. that means - // just zero or one screen or all screens are 4-connected - // to other screens. - bool isValid() const; - - // convert this to a CConfig - void convertTo(CConfig&) const; - - private: - typedef std::vector CNames; - - SInt32 m_w, m_h; - CNames m_screens; - }; - -private: - CServer* m_server; - static const UInt32 s_maxRequestSize; -}; - -#endif diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp index 0710875d..47361435 100644 --- a/lib/server/CServer.cpp +++ b/lib/server/CServer.cpp @@ -13,7 +13,6 @@ */ #include "CServer.h" -#include "CHTTPServer.h" #include "CPrimaryClient.h" #include "IScreenFactory.h" #include "CInputPacketStream.h" @@ -45,8 +44,6 @@ // CServer // -const SInt32 CServer::s_httpMaxSimultaneousRequests = 3; - CServer::CServer(const CString& serverName) : m_name(serverName), m_error(false), @@ -59,8 +56,6 @@ CServer::CServer(const CString& serverName) : m_primaryClient(NULL), m_seqNum(0), m_activeSaver(NULL), - m_httpServer(NULL), - m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests), m_switchDir(kNoDirection), m_switchScreen(NULL), m_switchWaitDelay(0.0), @@ -118,18 +113,15 @@ CServer::mainLoop() 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))); - // start listening for HTTP requests - if (m_config.getHTTPAddress().isValid()) { - m_httpServer = new CHTTPServer(this); - startThread(new TMethodJob(this, - &CServer::acceptHTTPClients)); - } - // handle events m_primaryClient->mainLoop(); @@ -144,8 +136,6 @@ CServer::mainLoop() // bucket. #define FINALLY do { \ stopThreads(); \ - delete m_httpServer; \ - m_httpServer = NULL; \ runStatusJobs(); \ } while (false) FINALLY; @@ -360,6 +350,16 @@ CServer::getConfig(CConfig* config) const *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 { @@ -411,11 +411,6 @@ CServer::onError() // threads may be unable to proceed until this thread returns. stopThreads(3.0); - // done with the HTTP server - CLock lock(&m_mutex); - delete m_httpServer; - m_httpServer = NULL; - // note -- we do not attempt to close down the primary screen } @@ -1673,23 +1668,6 @@ CServer::handshakeClient(IDataSocket* socket) { LOG((CLOG_DEBUG1 "negotiating with new client")); - // get the input and output streams - IInputStream* input = socket->getInputStream(); - IOutputStream* output = socket->getOutputStream(); - bool own = false; - - // attach filters - if (m_streamFilterFactory != NULL) { - input = m_streamFilterFactory->createInput(input, own); - output = m_streamFilterFactory->createOutput(output, own); - own = true; - } - - // attach the packetizing filters - input = new CInputPacketStream(input, own); - output = new COutputPacketStream(output, own); - own = true; - CClientProxy* proxy = NULL; CString name(""); try { @@ -1810,111 +1788,6 @@ CServer::handshakeClient(IDataSocket* socket) return NULL; } -void -CServer::acceptHTTPClients(void*) -{ - LOG((CLOG_DEBUG1 "starting to wait for HTTP clients")); - - IListenSocket* listen = NULL; - try { - // create socket listener - listen = new CTCPListenSocket; - - // bind to the desired port. keep retrying if we can't bind - // the address immediately. - CStopwatch timer; - for (;;) { - try { - LOG((CLOG_DEBUG1 "binding HTTP listen socket")); - listen->bind(m_config.getHTTPAddress()); - break; - } - catch (XSocketBind& e) { - LOG((CLOG_DEBUG1 "bind HTTP failed: %s", e.what())); - - // give up if we've waited too long - if (timer.getTime() >= m_bindTimeout) { - LOG((CLOG_DEBUG1 "waited too long to bind HTTP, giving up")); - throw; - } - - // wait a bit before retrying - ARCH->sleep(5.0); - } - } - - // accept connections and begin processing them - LOG((CLOG_DEBUG1 "waiting for HTTP connections")); - for (;;) { - // limit the number of HTTP requests being handled at once - { - CLock lock(&m_httpAvailable); - while (m_httpAvailable == 0) { - m_httpAvailable.wait(); - } - assert(m_httpAvailable > 0); - m_httpAvailable = m_httpAvailable - 1; - } - - // accept connection - CThread::testCancel(); - IDataSocket* socket = listen->accept(); - LOG((CLOG_NOTE "accepted HTTP connection")); - CThread::testCancel(); - - // handle HTTP request - startThread(new TMethodJob( - this, &CServer::processHTTPRequest, socket)); - } - - // clean up - delete listen; - } - catch (XBase& e) { - LOG((CLOG_ERR "cannot listen for HTTP clients: %s", e.what())); - delete listen; - exitMainLoopWithError(); - } - catch (...) { - delete listen; - throw; - } -} - -void -CServer::processHTTPRequest(void* vsocket) -{ - IDataSocket* socket = reinterpret_cast(vsocket); - try { - // process the request and force delivery - m_httpServer->processRequest(socket); - socket->getOutputStream()->flush(); - - // wait a moment to give the client a chance to hangup first - ARCH->sleep(3.0); - - // clean up - socket->close(); - delete socket; - - // increment available HTTP handlers - { - CLock lock(&m_httpAvailable); - m_httpAvailable = m_httpAvailable + 1; - m_httpAvailable.signal(); - } - } - catch (...) { - delete socket; - { - CLock lock(&m_httpAvailable); - m_httpAvailable = m_httpAvailable + 1; - m_httpAvailable.signal(); - } - throw; - } -} - void CServer::sendOptions(IClient* client) const { @@ -2141,3 +2014,259 @@ 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 92476365..f7a9d735 100644 --- a/lib/server/CServer.h +++ b/lib/server/CServer.h @@ -29,7 +29,6 @@ #include "stdvector.h" class CClientProxy; -class CHTTPServer; class CPrimaryClient; class IClient; class IDataSocket; @@ -151,6 +150,12 @@ public: */ 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 @@ -295,12 +300,6 @@ private: void runClient(void*); CClientProxy* handshakeClient(IDataSocket*); - // thread method to accept incoming HTTP connections - void acceptHTTPClients(void*); - - // thread method to process HTTP requests - void processHTTPRequest(void*); - // connection list maintenance void addConnection(IClient*); void removeConnection(const CString& name); @@ -381,11 +380,6 @@ private: IClient* m_activeSaver; SInt32 m_xSaver, m_ySaver; - // HTTP request processing stuff - CHTTPServer* m_httpServer; - CCondVar m_httpAvailable; - static const SInt32 s_httpMaxSimultaneousRequests; - // common state for screen switch tests. all tests are always // trying to reach the same screen in the same direction. EDirection m_switchDir; @@ -408,6 +402,15 @@ private: CJobList m_statusJobs; EStatus m_status; CString m_statusMessage; + +//--- +/* + IListenSocket* m_listen; + + typedef std::map CProvisionalClients; + CProvisionalClients m_provisional; +*/ }; #endif diff --git a/lib/server/Makefile.am b/lib/server/Makefile.am index a414bc90..cab17f22 100644 --- a/lib/server/Makefile.am +++ b/lib/server/Makefile.am @@ -29,15 +29,15 @@ libserver_a_SOURCES = \ CClientProxy1_0.cpp \ CClientProxy1_1.cpp \ CConfig.cpp \ - CHTTPServer.cpp \ CPrimaryClient.cpp \ + CProvisionalClient.cpp \ CServer.cpp \ CClientProxy.h \ CClientProxy1_0.h \ CClientProxy1_1.h \ CConfig.h \ - CHTTPServer.h \ CPrimaryClient.h \ + CProvisionalClient.h \ CServer.h \ $(NULL) INCLUDES = \ @@ -46,7 +46,6 @@ INCLUDES = \ -I$(VDEPTH)/lib/base \ -I$(VDEPTH)/lib/mt \ -I$(VDEPTH)/lib/io \ - -I$(VDEPTH)/lib/http \ -I$(VDEPTH)/lib/net \ -I$(VDEPTH)/lib/synergy \ -I$(VDEPTH)/lib/platform \ diff --git a/lib/synergy/CInputPacketStream.cpp b/lib/synergy/CInputPacketStream.cpp deleted file mode 100644 index 8b0096fc..00000000 --- a/lib/synergy/CInputPacketStream.cpp +++ /dev/null @@ -1,181 +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. - */ - -#include "CInputPacketStream.h" -#include "CLock.h" -#include "CStopwatch.h" - -// -// CInputPacketStream -// - -CInputPacketStream::CInputPacketStream(IInputStream* stream, bool adopt) : - CInputStreamFilter(stream, adopt), - m_mutex(), - m_size(0), - m_buffer(&m_mutex, NULL) -{ - // do nothing -} - -CInputPacketStream::~CInputPacketStream() -{ - // do nothing -} - -void -CInputPacketStream::close() -{ - getStream()->close(); -} - -UInt32 -CInputPacketStream::read(void* buffer, UInt32 n, double timeout) -{ - CLock lock(&m_mutex); - - // wait for entire message to be read. return if stream - // hungup or timeout. - switch (waitForFullMessage(timeout)) { - case kData: - break; - - case kHungup: - return 0; - - case kTimedout: - return (UInt32)-1; - } - - // limit number of bytes to read to the number of bytes left in the - // current message. - if (n > m_size) { - n = m_size; - } - - // now read from our buffer - n = m_buffer.readNoLock(buffer, n, -1.0); - assert(n <= m_size); - m_size -= n; - - return n; -} - -UInt32 -CInputPacketStream::getSize() const -{ - CLock lock(&m_mutex); - return getSizeNoLock(); -} - -UInt32 -CInputPacketStream::getSizeNoLock() const -{ - CStopwatch timer(true); - while (!hasFullMessage() && getStream()->getSize() > 0) { - // read more data - if (getMoreMessage(-1.0) != kData) { - // stream hungup - return 0; - } - } - - return m_size; -} - -CInputPacketStream::EResult -CInputPacketStream::waitForFullMessage(double timeout) const -{ - CStopwatch timer(true); - while (!hasFullMessage()) { - // compute remaining timeout - double t = timeout - timer.getTime(); - if (timeout >= 0.0 && t <= 0.0) { - // timeout - return kTimedout; - } - - // read more data - switch (getMoreMessage(t)) { - case kData: - break; - - case kHungup: - // stream hungup - return kHungup; - - case kTimedout: - // stream timed out - return kTimedout; - } - } - - return kData; -} - -CInputPacketStream::EResult -CInputPacketStream::getMoreMessage(double timeout) const -{ - // read more data - char buffer[4096]; - UInt32 n = getStream()->read(buffer, sizeof(buffer), timeout); - - // return if stream timed out - if (n == (UInt32)-1) { - return kTimedout; - } - - // return if stream hungup - if (n == 0) { - m_buffer.hangup(); - return kHungup; - } - - // append to our buffer - m_buffer.write(buffer, n); - - return kData; -} - -bool -CInputPacketStream::hasFullMessage() const -{ - // get payload length if we don't have it yet - if (m_size == 0) { - // check if length field has been read yet - if (m_buffer.getSizeNoLock() < 4) { - // not enough data for payload length - return false; - } - - // save payload length - UInt8 buffer[4]; - UInt32 n = m_buffer.readNoLock(buffer, sizeof(buffer), -1.0); - assert(n == 4); - m_size = ((UInt32)buffer[0] << 24) | - ((UInt32)buffer[1] << 16) | - ((UInt32)buffer[2] << 8) | - (UInt32)buffer[3]; - - // if payload length is zero then discard null message - if (m_size == 0) { - return false; - } - } - assert(m_size > 0); - - // we have the full message when we have at least m_size bytes in - // the buffer - return (m_buffer.getSizeNoLock() >= m_size); -} diff --git a/lib/synergy/CInputPacketStream.h b/lib/synergy/CInputPacketStream.h deleted file mode 100644 index a4122f33..00000000 --- a/lib/synergy/CInputPacketStream.h +++ /dev/null @@ -1,51 +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 CINPUTPACKETSTREAM_H -#define CINPUTPACKETSTREAM_H - -#include "CInputStreamFilter.h" -#include "CBufferedInputStream.h" -#include "CMutex.h" - -//! Packetizing input stream filter -/*! -Filters an input stream to extract packet by packet. -*/ -class CInputPacketStream : public CInputStreamFilter { -public: - CInputPacketStream(IInputStream*, bool adoptStream = true); - ~CInputPacketStream(); - - // IInputStream overrides - virtual void close(); - virtual UInt32 read(void*, UInt32 maxCount, double timeout); - virtual UInt32 getSize() const; - -private: - enum EResult { kData, kHungup, kTimedout }; - - UInt32 getSizeNoLock() const; - EResult waitForFullMessage(double timeout) const; - EResult getMoreMessage(double timeout) const; - bool hasFullMessage() const; - -private: - CMutex m_mutex; - mutable UInt32 m_size; - mutable CBufferedInputStream m_buffer; -}; - -#endif - diff --git a/lib/synergy/COutputPacketStream.cpp b/lib/synergy/COutputPacketStream.cpp deleted file mode 100644 index 7fbf1ad2..00000000 --- a/lib/synergy/COutputPacketStream.cpp +++ /dev/null @@ -1,72 +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. - */ - -#include "COutputPacketStream.h" - -// -// COuputPacketStream -// - -COutputPacketStream::COutputPacketStream(IOutputStream* stream, bool adopt) : - COutputStreamFilter(stream, adopt) -{ - // do nothing -} - -COutputPacketStream::~COutputPacketStream() -{ - // do nothing -} - -void -COutputPacketStream::close() -{ - getStream()->close(); -} - -UInt32 -COutputPacketStream::write(const void* buffer, UInt32 count) -{ - // write the length of the payload - UInt8 length[4]; - length[0] = (UInt8)((count >> 24) & 0xff); - length[1] = (UInt8)((count >> 16) & 0xff); - length[2] = (UInt8)((count >> 8) & 0xff); - length[3] = (UInt8)( count & 0xff); - UInt32 count2 = sizeof(length); - const UInt8* cbuffer = length; - while (count2 > 0) { - UInt32 n = getStream()->write(cbuffer, count2); - cbuffer += n; - count2 -= n; - } - - // write the payload - count2 = count; - cbuffer = reinterpret_cast(buffer); - while (count2 > 0) { - UInt32 n = getStream()->write(cbuffer, count2); - cbuffer += n; - count2 -= n; - } - - return count; -} - -void -COutputPacketStream::flush() -{ - getStream()->flush(); -} - diff --git a/lib/synergy/COutputPacketStream.h b/lib/synergy/COutputPacketStream.h deleted file mode 100644 index 7e25d47d..00000000 --- a/lib/synergy/COutputPacketStream.h +++ /dev/null @@ -1,36 +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 COUTPUTPACKETSTREAM_H -#define COUTPUTPACKETSTREAM_H - -#include "COutputStreamFilter.h" - -//! Packetizing output stream filter -/*! -Filters an output stream to create packets that include message -boundaries. Each write() is considered a single packet. -*/ -class COutputPacketStream : public COutputStreamFilter { -public: - COutputPacketStream(IOutputStream*, bool adoptStream = true); - ~COutputPacketStream(); - - // IOutputStream overrides - virtual void close(); - virtual UInt32 write(const void*, UInt32 count); - virtual void flush(); -}; - -#endif diff --git a/lib/synergy/CPacketStreamFilter.cpp b/lib/synergy/CPacketStreamFilter.cpp new file mode 100644 index 00000000..0ce5e7c3 --- /dev/null +++ b/lib/synergy/CPacketStreamFilter.cpp @@ -0,0 +1,221 @@ +/* + * 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 "CPacketStreamFilter.h" +#include "IEventQueue.h" +#include "CLock.h" +#include "TMethodEventJob.h" + +// +// CPacketStreamFilter +// + +CPacketStreamFilter::CPacketStreamFilter(IStream* stream, bool adoptStream) : + CStreamFilter(stream, adoptStream), + m_size(0), + m_eventFilter(NULL), + m_inputShutdown(false) +{ + // install event filter + getStream()->setEventFilter(new TMethodEventJob( + this, &CPacketStreamFilter::filterEvent, NULL)); +} + +CPacketStreamFilter::~CPacketStreamFilter() +{ + delete getStream()->getEventFilter(); +} + +void +CPacketStreamFilter::close() +{ + CLock lock(&m_mutex); + m_size = 0; + m_buffer.pop(m_buffer.getSize()); + CStreamFilter::close(); +} + +UInt32 +CPacketStreamFilter::read(void* buffer, UInt32 n) +{ + if (n == 0) { + return 0; + } + + CLock lock(&m_mutex); + + // if not enough data yet then give up + if (!isReadyNoLock()) { + return 0; + } + + // read no more than what's left in the buffered packet + if (n > m_size) { + n = m_size; + } + + // read it + if (buffer != NULL) { + memcpy(buffer, m_buffer.peek(n), n); + } + m_buffer.pop(n); + m_size -= n; + + // get next packet's size if we've finished with this packet and + // there's enough data to do so. + readPacketSize(); + + if (m_inputShutdown && m_size == 0) { + sendEvent(CEvent(getInputShutdownEvent(), getEventTarget(), NULL)); + } + + return n; +} + +void +CPacketStreamFilter::write(const void* buffer, UInt32 count) +{ + // write the length of the payload + UInt8 length[4]; + length[0] = (UInt8)((count >> 24) & 0xff); + length[1] = (UInt8)((count >> 16) & 0xff); + length[2] = (UInt8)((count >> 8) & 0xff); + length[3] = (UInt8)( count & 0xff); + getStream()->write(length, sizeof(length)); + + // write the payload + getStream()->write(buffer, count); +} + +void +CPacketStreamFilter::shutdownInput() +{ + CLock lock(&m_mutex); + m_size = 0; + m_buffer.pop(m_buffer.getSize()); + CStreamFilter::shutdownInput(); +} + +void +CPacketStreamFilter::setEventFilter(IEventJob* filter) +{ + CLock lock(&m_mutex); + m_eventFilter = filter; +} + +bool +CPacketStreamFilter::isReady() const +{ + CLock lock(&m_mutex); + return isReadyNoLock(); +} + +UInt32 +CPacketStreamFilter::getSize() const +{ + CLock lock(&m_mutex); + return isReadyNoLock() ? m_size : 0; +} + +IEventJob* +CPacketStreamFilter::getEventFilter() const +{ + CLock lock(&m_mutex); + return m_eventFilter; +} + +bool +CPacketStreamFilter::isReadyNoLock() const +{ + return (m_size != 0 && m_buffer.getSize() >= m_size); +} + +void +CPacketStreamFilter::readPacketSize() +{ + // note -- m_mutex must be locked on entry + + if (m_size == 0 && m_buffer.getSize() >= 4) { + UInt8 buffer[4]; + memcpy(buffer, m_buffer.peek(sizeof(buffer)), sizeof(buffer)); + m_buffer.pop(sizeof(buffer)); + m_size = ((UInt32)buffer[0] << 24) | + ((UInt32)buffer[1] << 16) | + ((UInt32)buffer[2] << 8) | + (UInt32)buffer[3]; + } +} + +void +CPacketStreamFilter::readMore() +{ + // note -- m_mutex must be locked on entry + + // note if we have whole packet + bool wasReady = isReadyNoLock(); + + // read more data + char buffer[4096]; + UInt32 n = getStream()->read(buffer, sizeof(buffer)); + while (n > 0) { + m_buffer.write(buffer, n); + n = getStream()->read(buffer, sizeof(buffer)); + } + + // if we don't yet have the next packet size then get it, + // if possible. + readPacketSize(); + + // note if we now have a whole packet + bool isReady = isReadyNoLock(); + + // if we weren't ready before but now we are then send a + // input ready event apparently from the filtered stream. + if (wasReady != isReady) { + sendEvent(CEvent(getInputReadyEvent(), getEventTarget(), NULL)); + } +} + +void +CPacketStreamFilter::sendEvent(const CEvent& event) +{ + if (m_eventFilter != NULL) { + m_eventFilter->run(event); + } + else { + EVENTQUEUE->addEvent(event); + } +} + +void +CPacketStreamFilter::filterEvent(const CEvent& event, void*) +{ + CLock lock(&m_mutex); + + if (event.getType() == getInputReadyEvent()) { + readMore(); + return; + } + else if (event.getType() == getInputShutdownEvent()) { + // discard this if we have buffered data + m_inputShutdown = true; + if (m_size == 0) { + sendEvent(CEvent(getInputShutdownEvent(), getEventTarget(), NULL)); + } + return; + } + + // pass event + sendEvent(event); +} diff --git a/lib/synergy/CPacketStreamFilter.h b/lib/synergy/CPacketStreamFilter.h new file mode 100644 index 00000000..3156f40d --- /dev/null +++ b/lib/synergy/CPacketStreamFilter.h @@ -0,0 +1,57 @@ +/* + * 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 CPACKETSTREAMFILTER_H +#define CPACKETSTREAMFILTER_H + +#include "CStreamFilter.h" +#include "CStreamBuffer.h" +#include "CMutex.h" + +//! Packetizing stream filter +/*! +Filters a stream to read and write packets. +*/ +class CPacketStreamFilter : public CStreamFilter { +public: + CPacketStreamFilter(IStream* stream, bool adoptStream = true); + ~CPacketStreamFilter(); + + // IStream overrides + virtual void close(); + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void shutdownInput(); + virtual void setEventFilter(IEventJob* filter); + virtual bool isReady() const; + virtual UInt32 getSize() const; + virtual IEventJob* getEventFilter() const; + +private: + bool isReadyNoLock() const; + void readPacketSize(); + + void readMore(); + void sendEvent(const CEvent&); + void filterEvent(const CEvent&, void*); + +private: + CMutex m_mutex; + UInt32 m_size; + CStreamBuffer m_buffer; + IEventJob* m_eventFilter; + bool m_inputShutdown; +}; + +#endif diff --git a/lib/synergy/CProtocolUtil.cpp b/lib/synergy/CProtocolUtil.cpp index 7ef9de6a..d85f840a 100644 --- a/lib/synergy/CProtocolUtil.cpp +++ b/lib/synergy/CProtocolUtil.cpp @@ -13,8 +13,7 @@ */ #include "CProtocolUtil.h" -#include "IInputStream.h" -#include "IOutputStream.h" +#include "IStream.h" #include "CLog.h" #include "stdvector.h" #include @@ -25,51 +24,77 @@ // void -CProtocolUtil::writef(IOutputStream* stream, const char* fmt, ...) +CProtocolUtil::writef(IStream* stream, const char* fmt, ...) { assert(stream != NULL); assert(fmt != NULL); LOG((CLOG_DEBUG2 "writef(%s)", fmt)); va_list args; - - // determine total size to write va_start(args, fmt); - UInt32 count = getLength(fmt, args); + UInt32 size = getLength(fmt, args); va_end(args); - - // done if nothing to write - if (count == 0) { - return; - } - - // fill buffer - UInt8* buffer = new UInt8[count]; va_start(args, fmt); - writef(buffer, fmt, args); + vwritef(stream, fmt, size, args); va_end(args); - - // write buffer - UInt8* scan = buffer; - while (count > 0) { - const UInt32 n = stream->write(scan, count); - LOG((CLOG_DEBUG2 "wrote %d of %d bytes", n, count)); - count -= n; - scan += n; - } - - delete[] buffer; } -void -CProtocolUtil::readf(IInputStream* stream, const char* fmt, ...) +bool +CProtocolUtil::readf(IStream* stream, const char* fmt, ...) { assert(stream != NULL); assert(fmt != NULL); LOG((CLOG_DEBUG2 "readf(%s)", fmt)); + bool result; va_list args; va_start(args, fmt); + try { + vreadf(stream, fmt, args); + result = true; + } + catch (XIO&) { + result = false; + } + va_end(args); + return result; +} + +void +CProtocolUtil::vwritef(IStream* stream, + const char* fmt, UInt32 size, va_list args) +{ + assert(stream != NULL); + assert(fmt != NULL); + + // done if nothing to write + if (size == 0) { + return; + } + + // fill buffer + // FIXME -- can we use alloca? + UInt8* buffer = new UInt8[size]; + writef(buffer, fmt, args); + + try { + // write buffer + stream->write(buffer, size); + LOG((CLOG_DEBUG2 "wrote %d bytes", size)); + + delete[] buffer; + } + catch (XBase&) { + delete[] buffer; + throw; + } +} + +bool +CProtocolUtil::vreadf(IStream* stream, const char* fmt, va_list args) +{ + assert(stream != NULL); + assert(fmt != NULL); // begin scanning while (*fmt) { @@ -242,8 +267,6 @@ CProtocolUtil::readf(IInputStream* stream, const char* fmt, ...) ++fmt; } } - - va_end(args); } UInt32 @@ -485,7 +508,7 @@ CProtocolUtil::eatLength(const char** pfmt) } void -CProtocolUtil::read(IInputStream* stream, void* vbuffer, UInt32 count) +CProtocolUtil::read(IStream* stream, void* vbuffer, UInt32 count) { assert(stream != NULL); assert(vbuffer != NULL); @@ -493,7 +516,7 @@ CProtocolUtil::read(IInputStream* stream, void* vbuffer, UInt32 count) UInt8* buffer = reinterpret_cast(vbuffer); while (count > 0) { // read more - UInt32 n = stream->read(buffer, count, -1.0); + UInt32 n = stream->read(buffer, count); // bail if stream has hungup if (n == 0) { diff --git a/lib/synergy/CProtocolUtil.h b/lib/synergy/CProtocolUtil.h index 1e0db39f..2ea1609f 100644 --- a/lib/synergy/CProtocolUtil.h +++ b/lib/synergy/CProtocolUtil.h @@ -19,8 +19,7 @@ #include "XIO.h" #include -class IInputStream; -class IOutputStream; +class IStream; //! Synergy protocol utilities /*! @@ -47,13 +46,14 @@ public: - \%s -- converts CString* to stream of bytes - \%S -- converts integer N and const UInt8* to stream of N bytes */ - static void writef(IOutputStream*, + static void writef(IStream*, const char* fmt, ...); //! Read formatted data /*! Read formatted binary data from a buffer. This performs the - reverse operation of writef(). + reverse operation of writef(). Returns true if the entire + format was successfully parsed, false otherwise. Format specifiers are: - \%\% -- read (and discard) a literal `\%' @@ -65,14 +65,19 @@ public: - \%4I -- reads NBO 4 byte integers; arg is std::vector* - \%s -- reads bytes; argument must be a CString*, \b not a char* */ - static void readf(IInputStream*, + static bool readf(IStream*, const char* fmt, ...); private: + static void vwritef(IStream*, + const char* fmt, UInt32 size, va_list); + static bool vreadf(IStream*, + const char* fmt, va_list); + static UInt32 getLength(const char* fmt, va_list); static void writef(void*, const char* fmt, va_list); static UInt32 eatLength(const char** fmt); - static void read(IInputStream*, void*, UInt32); + static void read(IStream*, void*, UInt32); }; //! Mismatched read exception diff --git a/lib/synergy/Makefile.am b/lib/synergy/Makefile.am index 9923fa6e..1203ae2f 100644 --- a/lib/synergy/Makefile.am +++ b/lib/synergy/Makefile.am @@ -26,15 +26,13 @@ MAINTAINERCLEANFILES = \ noinst_LIBRARIES = libsynergy.a libsynergy_a_SOURCES = \ CClipboard.cpp \ - CInputPacketStream.cpp \ - COutputPacketStream.cpp \ + CPacketStreamFilter.cpp \ CProtocolUtil.cpp \ CScreen.cpp \ XScreen.cpp \ XSynergy.cpp \ CClipboard.h \ - CInputPacketStream.h \ - COutputPacketStream.h \ + CPacketStreamFilter.h \ CProtocolUtil.h \ CScreen.h \ ClipboardTypes.h \