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 \