diff --git a/lib/arch/CArch.cpp b/lib/arch/CArch.cpp index 30bfb38c..d276c4ee 100644 --- a/lib/arch/CArch.cpp +++ b/lib/arch/CArch.cpp @@ -357,6 +357,12 @@ CArch::waitForEvent(CArchThread thread, double timeout) return m_mt->waitForEvent(thread, timeout); } +void +CArch::unblockThread(CArchThread thread) +{ + m_mt->unblockThread(thread); +} + bool CArch::isSameThread(CArchThread thread1, CArchThread thread2) { diff --git a/lib/arch/CArch.h b/lib/arch/CArch.h index 61315b63..aad9fefb 100644 --- a/lib/arch/CArch.h +++ b/lib/arch/CArch.h @@ -117,6 +117,7 @@ public: virtual void testCancelThread(); virtual bool wait(CArchThread, double timeout); virtual EWaitResult waitForEvent(CArchThread, double timeout); + virtual void unblockThread(CArchThread thread); virtual bool isSameThread(CArchThread, CArchThread); virtual bool isExitedThread(CArchThread); virtual void* getResultOfThread(CArchThread); @@ -189,4 +190,20 @@ private: IArchTime* m_time; }; +//! Convenience object to lock/unlock an arch mutex +class CArchMutexLock { +public: + CArchMutexLock(CArchMutex mutex) : m_mutex(mutex) + { + ARCH->lockMutex(m_mutex); + } + ~CArchMutexLock() + { + ARCH->unlockMutex(m_mutex); + } + +private: + CArchMutex m_mutex; +}; + #endif diff --git a/lib/arch/CArchMultithreadPosix.cpp b/lib/arch/CArchMultithreadPosix.cpp index 67e5fcca..707ef0fb 100644 --- a/lib/arch/CArchMultithreadPosix.cpp +++ b/lib/arch/CArchMultithreadPosix.cpp @@ -522,6 +522,12 @@ CArchMultithreadPosix::waitForEvent(CArchThread, double /*timeout*/) return kTimeout; } +void +CArchMultithreadPosix::unblockThread(CArchThread thread) +{ + pthread_kill(thread->m_thread, SIGWAKEUP); +} + bool CArchMultithreadPosix::isSameThread(CArchThread thread1, CArchThread thread2) { diff --git a/lib/arch/CArchMultithreadPosix.h b/lib/arch/CArchMultithreadPosix.h index d62e2d4d..b0a1ca36 100644 --- a/lib/arch/CArchMultithreadPosix.h +++ b/lib/arch/CArchMultithreadPosix.h @@ -56,6 +56,7 @@ public: virtual void testCancelThread(); virtual bool wait(CArchThread, double timeout); virtual EWaitResult waitForEvent(CArchThread, double timeout); + virtual void unblockThread(CArchThread thread); virtual bool isSameThread(CArchThread, CArchThread); virtual bool isExitedThread(CArchThread); virtual void* getResultOfThread(CArchThread); diff --git a/lib/arch/CArchNetworkBSD.cpp b/lib/arch/CArchNetworkBSD.cpp index 9bd3c138..7de3db05 100644 --- a/lib/arch/CArchNetworkBSD.cpp +++ b/lib/arch/CArchNetworkBSD.cpp @@ -200,11 +200,6 @@ CArchNetworkBSD::acceptSocket(CArchSocket s, CArchNetAddress* addr) ARCH->testCancelThread(); continue; } - if (err == ECONNABORTED) { - // connection was aborted; try again - ARCH->testCancelThread(); - continue; - } delete newSocket; delete *addr; *addr = NULL; @@ -244,11 +239,6 @@ CArchNetworkBSD::connectSocket(CArchSocket s, CArchNetAddress addr) break; } - if (errno == EAGAIN) { - // connecting - throw XArchNetworkConnecting(new XArchEvalUnix(errno)); - } - throwError(errno); } } while (false); @@ -290,14 +280,15 @@ CArchNetworkBSD::pollSocket(CPollEntry pe[], int num, double timeout) } // do the poll + int t = (timeout < 0.0) ? -1 : static_cast(1000.0 * timeout); int n; do { - n = poll(pfd, num, static_cast(1000.0 * timeout)); + n = poll(pfd, num, t); if (n == -1) { if (errno == EINTR) { // interrupted system call ARCH->testCancelThread(); - continue; + return 0; } throwError(errno); } @@ -382,7 +373,7 @@ CArchNetworkBSD::pollSocket(CPollEntry pe[], int num, double timeout) // prepare timeout for select struct timeval timeout2; struct timeval* timeout2P; - if (timeout < 0) { + if (timeout < 0.0) { timeout2P = NULL; } else { @@ -404,7 +395,7 @@ CArchNetworkBSD::pollSocket(CPollEntry pe[], int num, double timeout) if (errno == EINTR) { // interrupted system call ARCH->testCancelThread(); - continue; + return 0; } throwError(errno); } diff --git a/lib/arch/IArchMultithread.h b/lib/arch/IArchMultithread.h index 69d88694..d8319213 100644 --- a/lib/arch/IArchMultithread.h +++ b/lib/arch/IArchMultithread.h @@ -228,6 +228,15 @@ public: */ virtual EWaitResult waitForEvent(CArchThread thread, double timeout) = 0; + //! Unblock thread in system call + /*! + Cause a thread that's in a blocking system call to return. This + call may return before the thread is unblocked. If the thread is + not in a blocking system call, this call has no effect. This does + not cause a lockMutex() or waitCondVar() to return prematurely. + */ + virtual void unblockThread(CArchThread thread) = 0; + //! Compare threads /*! Returns true iff two thread objects refer to the same thread. diff --git a/lib/base/CEvent.cpp b/lib/base/CEvent.cpp new file mode 100644 index 00000000..36f4f674 --- /dev/null +++ b/lib/base/CEvent.cpp @@ -0,0 +1,89 @@ +/* + * 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 "CEvent.h" + +// +// CEvent +// + +CEvent::Type CEvent::s_nextType = kLast; + +CEvent::CEvent() : + m_type(kUnknown), + m_target(NULL), + m_data(NULL) +{ + // do nothing +} + +CEvent::CEvent(Type type, void* target, void* data) : + m_type(type), + m_target(target), + m_data(data) +{ + // do nothing +} + +CEvent::Type +CEvent::getType() const +{ + return m_type; +} + +void* +CEvent::getTarget() const +{ + return m_target; +} + +void* +CEvent::getData() const +{ + return m_data; +} + +CEvent::Type +CEvent::registerType() +{ + // FIXME -- lock mutex (need a mutex) + return s_nextType++; +} + +CEvent::Type +CEvent::registerTypeOnce(Type& type) +{ + // FIXME -- lock mutex (need a mutex) + if (type == CEvent::kUnknown) { + type = s_nextType++; + } + return type; +} + +void +CEvent::deleteData(const CEvent& event) +{ + switch (event.getType()) { + case kUnknown: + case kQuit: + case kSystem: + case kTimer: + break; + + default: + // yes, really delete void* + delete event.getData(); + break; + } +} diff --git a/lib/base/CEvent.h b/lib/base/CEvent.h new file mode 100644 index 00000000..8257e603 --- /dev/null +++ b/lib/base/CEvent.h @@ -0,0 +1,100 @@ +/* + * 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 CEVENT_H +#define CEVENT_H + +#include "BasicTypes.h" + +//! Event +/*! +A \c CEvent holds an event type and a pointer to event data. +*/ +class CEvent { +public: + typedef UInt32 Type; + enum { + kUnknown, //!< The event type is unknown + kQuit, //!< The quit event + kSystem, //!< The data points to a system event type + kTimer, //!< The data points to timer info + kLast //!< Must be last + }; + + CEvent(); + + //! Create \c CEvent with data + /*! + The \p type must have been registered using \c registerType(). + The \p data must be POD (plain old data) which means it cannot + have a destructor or be composed of any types that do. \p target + is the intended recipient of the event. + */ + CEvent(Type type, void* target = NULL, void* data = NULL); + + //! @name manipulators + //@{ + + //@} + //! @name accessors + //@{ + + //! Get event type + /*! + Returns the event type. + */ + Type getType() const; + + //! Get the event target + /*! + Returns the event target. + */ + void* getTarget() const; + + //! Get the event data + /*! + Returns the event data. + */ + void* getData() const; + + //! Creates a new event type + /*! + Returns a unique event type id. + */ + static Type registerType(); + + //! Creates a new event type + /*! + If \p type contains \c kUnknown then it is set to a unique event + type id otherwise it is left alone. The final value of \p type + is returned. + */ + static Type registerTypeOnce(Type& type); + + //! Release event data + /*! + Deletes event data for the given event. + */ + static void deleteData(const CEvent&); + + //@} + +private: + Type m_type; + void* m_target; + void* m_data; + static Type s_nextType; +}; + +#endif diff --git a/lib/base/CEventQueue.cpp b/lib/base/CEventQueue.cpp new file mode 100644 index 00000000..ce8c0058 --- /dev/null +++ b/lib/base/CEventQueue.cpp @@ -0,0 +1,381 @@ +/* + * 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 "CEventQueue.h" +#include "IEventJob.h" +#include "CArch.h" + +// +// CEventQueue +// + +static int g_systemTarget = 0; +CEventQueue* CEventQueue::s_instance = NULL; + +CEventQueue::CEventQueue() +{ + assert(s_instance == NULL); + s_instance = this; + m_mutex = ARCH->newMutex(); +} + +CEventQueue::~CEventQueue() +{ + 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; +} + +bool +CEventQueue::getEvent(CEvent& event, double timeout) +{ + // if no events are waiting then handle timers and then wait + if (doIsEmpty()) { + // handle timers first + if (hasTimerExpired(event)) { + return true; + } + + // get time until next timer expires. if there is a timer + // and it'll expire before the client's timeout then use + // that duration for our timeout instead. + double timerTimeout = getNextTimerTimeout(); + if (timerTimeout >= 0.0 && timerTimeout < timeout) { + timeout = timerTimeout; + } + + // wait for an event + waitForEvent(timeout); + } + + // if no events are pending then do the timers + if (doIsEmpty()) { + return hasTimerExpired(event); + } + + return doGetEvent(event); +} + +bool +CEventQueue::dispatchEvent(const CEvent& event) +{ + void* target = event.getTarget(); + IEventJob* job = getHandler(target); + if (job != NULL) { + job->run(event); + return true; + } + return false; +} + +void +CEventQueue::addEvent(const CEvent& event) +{ + // discard bogus event types + switch (event.getType()) { + case CEvent::kUnknown: + case CEvent::kSystem: + case CEvent::kTimer: + return; + + default: + break; + } + + // store the event's data locally + UInt32 eventID = saveEvent(event); + + // add it + if (!doAddEvent(eventID)) { + // failed to send event + removeEvent(eventID); + CEvent::deleteData(event); + } +} + +CEventQueueTimer* +CEventQueue::newTimer(double duration, void* target) +{ + assert(duration > 0.0); + + CEventQueueTimer* timer = doNewTimer(duration, false); + CArchMutexLock lock(m_mutex); + m_timers.insert(timer); + m_timerQueue.push(CTimer(timer, duration, target, false)); + return timer; +} + +CEventQueueTimer* +CEventQueue::newOneShotTimer(double duration, void* target) +{ + assert(duration > 0.0); + + CEventQueueTimer* timer = doNewTimer(duration, true); + CArchMutexLock lock(m_mutex); + m_timers.insert(timer); + m_timerQueue.push(CTimer(timer, duration, target, true)); + return timer; +} + +void +CEventQueue::deleteTimer(CEventQueueTimer* timer) +{ + { + CArchMutexLock lock(m_mutex); + for (CTimerQueue::iterator index = m_timerQueue.begin(); + index != m_timerQueue.end(); ++index) { + if (index->getTimer() == timer) { + m_timerQueue.erase(index); + break; + } + } + CTimers::iterator index = m_timers.find(timer); + if (index != m_timers.end()) { + m_timers.erase(index); + } + } + doDeleteTimer(timer); +} + +void +CEventQueue::adoptHandler(void* target, IEventJob* handler) +{ + CArchMutexLock lock(m_mutex); + IEventJob*& job = m_handlers[target]; + delete job; + job = handler; +} + +IEventJob* +CEventQueue::orphanHandler(void* target) +{ + CArchMutexLock lock(m_mutex); + CHandlerTable::iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + IEventJob* handler = index->second; + m_handlers.erase(index); + return handler; + } + else { + return NULL; + } +} + +bool +CEventQueue::isEmpty() const +{ + return (doIsEmpty() && getNextTimerTimeout() != 0.0); +} + +IEventJob* +CEventQueue::getHandler(void* target) const +{ + CArchMutexLock lock(m_mutex); + CHandlerTable::const_iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + return index->second; + } + else { + return NULL; + } +} + +UInt32 +CEventQueue::saveEvent(const CEvent& event) +{ + CArchMutexLock lock(m_mutex); + + // choose id + UInt32 id; + if (!m_oldEventIDs.empty()) { + // reuse an id + id = m_oldEventIDs.back(); + m_oldEventIDs.pop_back(); + } + else { + // make a new id + id = static_cast(m_oldEventIDs.size()); + } + + // save data + m_events[id] = event; + return id; +} + +CEvent +CEventQueue::removeEvent(UInt32 eventID) +{ + CArchMutexLock lock(m_mutex); + + // look up id + CEventTable::iterator index = m_events.find(eventID); + if (index == m_events.end()) { + return CEvent(); + } + + // get data + CEvent event = index->second; + m_events.erase(index); + + // save old id for reuse + m_oldEventIDs.push_back(eventID); + + return event; +} + +bool +CEventQueue::hasTimerExpired(CEvent& event) +{ + CArchMutexLock lock(m_mutex); + + // return true if there's a timer in the timer priority queue that + // has expired. if returning true then fill in event appropriately + // and reset and reinsert the timer. + if (m_timerQueue.empty()) { + return false; + } + + // get time elapsed since last check + const double time = m_time.getTime(); + m_time.reset(); + + // countdown elapsed time + for (CTimerQueue::iterator index = m_timerQueue.begin(); + index != m_timerQueue.end(); ++index) { + (*index) -= time; + } + + // done if no timers are expired + if (m_timerQueue.top() > 0.0) { + return false; + } + + // remove timer from queue + CTimer timer = m_timerQueue.top(); + m_timerQueue.pop(); + + // prepare event and reset the timer's clock + timer.fillEvent(m_timerEvent); + event = CEvent(CEvent::kTimer, timer.getTarget(), &m_timerEvent); + timer.reset(); + + // reinsert timer into queue if it's not a one-shot + if (!timer.isOneShot()) { + m_timerQueue.push(timer); + } + + return true; +} + +double +CEventQueue::getNextTimerTimeout() const +{ + CArchMutexLock lock(m_mutex); + + // return -1 if no timers, 0 if the top timer has expired, otherwise + // the time until the top timer in the timer priority queue will + // expire. + if (m_timerQueue.empty()) { + return -1.0; + } + if (m_timerQueue.top() <= 0.0) { + return 0.0; + } + return m_timerQueue.top(); +} + + +// +// CXWindowsScreen::CTimer +// + +CEventQueue::CTimer::CTimer(CEventQueueTimer* timer, + double timeout, void* target, bool oneShot) : + m_timer(timer), + m_timeout(timeout), + m_target(target), + m_oneShot(oneShot), + m_time(timeout) +{ + assert(m_timeout > 0.0); +} + +CEventQueue::CTimer::~CTimer() +{ + // do nothing +} + +void +CEventQueue::CTimer::reset() +{ + m_time = m_timeout; +} + +CEventQueue::CTimer::CTimer& +CEventQueue::CTimer::operator-=(double dt) +{ + m_time -= dt; + return *this; +} + +CEventQueue::CTimer::operator double() const +{ + return m_time; +} + +bool +CEventQueue::CTimer::isOneShot() const +{ + return m_oneShot; +} + +CEventQueueTimer* +CEventQueue::CTimer::getTimer() const +{ + return m_timer; +} + +void* +CEventQueue::CTimer::getTarget() const +{ + return m_target; +} + +void +CEventQueue::CTimer::fillEvent(CTimerEvent& event) const +{ + event.m_timer = m_timer; + event.m_count = 0; + if (m_time <= 0.0) { + event.m_count = static_cast((m_timeout - m_time) / m_timeout); + } +} + +bool +CEventQueue::CTimer::operator<(const CTimer& t) const +{ + return m_time < t.m_time; +} diff --git a/lib/base/CEventQueue.h b/lib/base/CEventQueue.h new file mode 100644 index 00000000..e6ac9ee2 --- /dev/null +++ b/lib/base/CEventQueue.h @@ -0,0 +1,192 @@ +/* + * 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 CEVENTQUEUE_H +#define CEVENTQUEUE_H + +#include "IEventQueue.h" +#include "CEvent.h" +#include "CPriorityQueue.h" +#include "CStopwatch.h" +#include "IArchMultithread.h" +#include "stdmap.h" +#include "stdset.h" + +//! Event queue +/*! +An event queue that implements the platform independent parts and +delegates the platform dependent parts to a subclass. +*/ +class CEventQueue : public IEventQueue { +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); + virtual void addEvent(const CEvent& event); + virtual CEventQueueTimer* + newTimer(double duration, void* target = NULL); + virtual CEventQueueTimer* + newOneShotTimer(double duration, void* target = NULL); + virtual void deleteTimer(CEventQueueTimer*); + virtual void adoptHandler(void* target, IEventJob* dispatcher); + virtual IEventJob* orphanHandler(void* target); + virtual bool isEmpty() const; + virtual IEventJob* getHandler(void* target) const; + +protected: + //! @name manipulators + //@{ + + //! Get the data for a given id + /*! + Takes a saved event id, \p eventID, and returns a \c CEvent. The + event id becomes invalid after this call. The \p eventID must have + been passed to a successful call to \c doAddEvent() and not removed + since. + */ + CEvent removeEvent(UInt32 eventID); + + //! Block waiting for an event + /*! + Wait for an event in the system event queue for up to \p timeout + seconds. + */ + virtual void waitForEvent(double timeout) = 0; + + //! Get the next event + /*! + Remove the next system event (one should be pending) and convert it + to a \c CEvent. The event type should be either \c CEvent::kSystem + if the event was not added by \c doAddEvent() or a type returned by + \c CEvent::registerType() if it was (and not \c CEvent::kTimer). A + non-system event will normally be retrieved by \c removeEvent(), but + the implementation must be able to tell the difference between a + system event and one added by \c doAddEvent(). + */ + virtual bool doGetEvent(CEvent& event) = 0; + + //! Post an event + /*! + Add the given event to the end of the system queue. This is a user + event and \c doGetEvent() must be able to identify it as such. + This method must cause \c waitForEvent() to return at some future + time if it's blocked waiting on an event. + */ + virtual bool doAddEvent(UInt32 dataID) = 0; + + //@} + //! @name accessors + //@{ + + //! Check if system queue is empty + /*! + Return true iff the system queue is empty. + */ + virtual bool doIsEmpty() const = 0; + + //! Create a timer object + /*! + Create and return a timer object. The object is opaque and is + used only by the subclass but it must be a valid object (i.e. + not NULL). + */ + virtual CEventQueueTimer* + doNewTimer(double duration, bool oneShot) const = 0; + + //! Destroy a timer object + /*! + Destroy a timer object previously returned by \c doNewTimer(). + */ + virtual void doDeleteTimer(CEventQueueTimer*) const = 0; + + //@} + +private: + UInt32 saveEvent(const CEvent& event); + bool hasTimerExpired(CEvent& event); + double getNextTimerTimeout() const; + +private: + class CTimer { + public: + CTimer(CEventQueueTimer*, double timeout, void* target, bool oneShot); + ~CTimer(); + + void reset(); + + CTimer& operator-=(double); + + operator double() const; + + bool isOneShot() const; + CEventQueueTimer* + getTimer() const; + void* getTarget() const; + void fillEvent(CTimerEvent&) const; + + bool operator<(const CTimer&) const; + + private: + CEventQueueTimer* m_timer; + double m_timeout; + void* m_target; + bool m_oneShot; + double m_time; + }; + typedef std::set CTimers; + typedef CPriorityQueue CTimerQueue; + typedef std::map CEventTable; + typedef std::vector CEventIDList; + typedef std::map CHandlerTable; + + static CEventQueue* s_instance; + + CArchMutex m_mutex; + + CEventTable m_events; + CEventIDList m_oldEventIDs; + + CStopwatch m_time; + CTimers m_timers; + CTimerQueue m_timerQueue; + CTimerEvent m_timerEvent; + + CHandlerTable m_handlers; +}; + +#endif diff --git a/lib/base/CFunctionEventJob.cpp b/lib/base/CFunctionEventJob.cpp new file mode 100644 index 00000000..1a55aa00 --- /dev/null +++ b/lib/base/CFunctionEventJob.cpp @@ -0,0 +1,40 @@ +/* + * 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 "CFunctionEventJob.h" + +// +// CFunctionJob +// + +CFunctionEventJob::CFunctionEventJob( + void (*func)(const CEvent&, void*), void* arg) : + m_func(func), + m_arg(arg) +{ + // do nothing +} + +CFunctionEventJob::~CFunctionEventJob() +{ + // do nothing +} + +void +CFunctionEventJob::run(const CEvent& event) +{ + if (m_func != NULL) { + m_func(event, m_arg); + } +} diff --git a/lib/base/CFunctionEventJob.h b/lib/base/CFunctionEventJob.h new file mode 100644 index 00000000..517b9c45 --- /dev/null +++ b/lib/base/CFunctionEventJob.h @@ -0,0 +1,38 @@ +/* + * 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 CFUNCTIONEVENTJOB_H +#define CFUNCTIONEVENTJOB_H + +#include "IEventJob.h" + +//! Use a function as an event job +/*! +An event job class that invokes a function. +*/ +class CFunctionEventJob : public IEventJob { +public: + //! run() invokes \c func(arg) + CFunctionEventJob(void (*func)(const CEvent&, void*), void* arg = NULL); + virtual ~CFunctionEventJob(); + + // IEventJob overrides + virtual void run(const CEvent&); + +private: + void (*m_func)(const CEvent&, void*); + void* m_arg; +}; + +#endif diff --git a/lib/base/CLog.cpp b/lib/base/CLog.cpp index 4df3058b..f99d90dd 100644 --- a/lib/base/CLog.cpp +++ b/lib/base/CLog.cpp @@ -54,16 +54,6 @@ static const int g_prioritySuffixLength = 2; static const int g_priorityPad = g_maxPriorityLength + g_prioritySuffixLength; -//! Convenience object to lock/unlock a mutex -class CLogLock { -public: - CLogLock(CArchMutex mutex) : m_mutex(mutex) { ARCH->lockMutex(m_mutex); } - ~CLogLock() { ARCH->unlockMutex(m_mutex); } - -private: - CArchMutex m_mutex; -}; - // // CLog @@ -198,7 +188,7 @@ CLog::insert(ILogOutputter* outputter, bool alwaysAtHead) assert(outputter != NULL); assert(outputter->getNewline() != NULL); - CLogLock lock(m_mutex); + CArchMutexLock lock(m_mutex); if (alwaysAtHead) { m_alwaysOutputters.push_front(outputter); } @@ -214,7 +204,7 @@ CLog::insert(ILogOutputter* outputter, bool alwaysAtHead) void CLog::remove(ILogOutputter* outputter) { - CLogLock lock(m_mutex); + CArchMutexLock lock(m_mutex); m_outputters.remove(outputter); m_alwaysOutputters.remove(outputter); } @@ -222,7 +212,7 @@ CLog::remove(ILogOutputter* outputter) void CLog::pop_front(bool alwaysAtHead) { - CLogLock lock(m_mutex); + CArchMutexLock lock(m_mutex); COutputterList* list = alwaysAtHead ? &m_alwaysOutputters : &m_outputters; if (!list->empty()) { delete list->front(); @@ -248,14 +238,14 @@ CLog::setFilter(const char* maxPriority) void CLog::setFilter(int maxPriority) { - CLogLock lock(m_mutex); + CArchMutexLock lock(m_mutex); m_maxPriority = maxPriority; } int CLog::getFilter() const { - CLogLock lock(m_mutex); + CArchMutexLock lock(m_mutex); return m_maxPriority; } @@ -276,7 +266,7 @@ CLog::output(int priority, char* msg) const } // write to each outputter - CLogLock lock(m_mutex); + CArchMutexLock lock(m_mutex); for (COutputterList::const_iterator index = m_alwaysOutputters.begin(); index != m_alwaysOutputters.end(); ++index) { diff --git a/lib/base/CPriorityQueue.h b/lib/base/CPriorityQueue.h index a03024b1..c79c7e34 100644 --- a/lib/base/CPriorityQueue.h +++ b/lib/base/CPriorityQueue.h @@ -56,6 +56,13 @@ public: c.pop_back(); } + //! Erase element + void erase(iterator i) + { + c.erase(i); + std::make_heap(c.begin(), c.end(), comp); + } + //! Get start iterator iterator begin() { diff --git a/lib/base/CSimpleEventQueue.cpp b/lib/base/CSimpleEventQueue.cpp new file mode 100644 index 00000000..9fc7e616 --- /dev/null +++ b/lib/base/CSimpleEventQueue.cpp @@ -0,0 +1,88 @@ +/* + * 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 "CSimpleEventQueue.h" +#include "CArch.h" + +class CEventQueueTimer { }; + +// +// CSimpleEventQueue +// + +CSimpleEventQueue::CSimpleEventQueue() +{ + m_queueMutex = ARCH->newMutex(); + m_queueReadyCond = ARCH->newCondVar(); + m_queueReady = false; +} + +CSimpleEventQueue::~CSimpleEventQueue() +{ + ARCH->closeCondVar(m_queueReadyCond); + ARCH->closeMutex(m_queueMutex); +} + +void +CSimpleEventQueue::waitForEvent(double timeout) +{ + CArchMutexLock lock(m_queueMutex); + while (!m_queueReady) { + ARCH->waitCondVar(m_queueReadyCond, m_queueMutex, -1.0); + } +} + +bool +CSimpleEventQueue::doGetEvent(CEvent& event) +{ + CArchMutexLock lock(m_queueMutex); + if (!m_queueReady) { + return false; + } + event = removeEvent(m_queue.back()); + m_queue.pop_back(); + m_queueReady = !m_queue.empty(); + return true; +} + +bool +CSimpleEventQueue::doAddEvent(UInt32 dataID) +{ + CArchMutexLock lock(m_queueMutex); + m_queue.push_front(dataID); + if (!m_queueReady) { + m_queueReady = true; + ARCH->broadcastCondVar(m_queueReadyCond); + } + return true; +} + +bool +CSimpleEventQueue::doIsEmpty() const +{ + CArchMutexLock lock(m_queueMutex); + return !m_queueReady; +} + +CEventQueueTimer* +CSimpleEventQueue::doNewTimer(double, bool) const +{ + return new CEventQueueTimer; +} + +void +CSimpleEventQueue::doDeleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} diff --git a/lib/base/CSimpleEventQueue.h b/lib/base/CSimpleEventQueue.h new file mode 100644 index 00000000..35ad4ecc --- /dev/null +++ b/lib/base/CSimpleEventQueue.h @@ -0,0 +1,60 @@ +/* + * 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 CSIMPLEEVENTQUEUE_H +#define CSIMPLEEVENTQUEUE_H + +#include "CEventQueue.h" +#include "IArchMultithread.h" +#include "stddeque.h" + +//! Event queue for added events only +/*! +An event queue that provides no system events, just events added by +addEvent(). +*/ +class CSimpleEventQueue : public CEventQueue { +public: + CSimpleEventQueue(); + virtual ~CSimpleEventQueue(); + + //! @name manipulators + //@{ + + //@} + //! @name accessors + //@{ + + //@} + +protected: + // CEventQueue overrides + virtual void waitForEvent(double timeout); + virtual bool doGetEvent(CEvent& event); + virtual bool doAddEvent(UInt32 dataID); + virtual bool doIsEmpty() const; + virtual CEventQueueTimer* + doNewTimer(double duration, bool oneShot) const; + virtual void doDeleteTimer(CEventQueueTimer*) const; + +private: + typedef std::deque CEventDeque; + + CArchMutex m_queueMutex; + CArchCond m_queueReadyCond; + bool m_queueReady; + CEventDeque m_queue; +}; + +#endif diff --git a/lib/base/IEventJob.h b/lib/base/IEventJob.h new file mode 100644 index 00000000..01ef9a96 --- /dev/null +++ b/lib/base/IEventJob.h @@ -0,0 +1,32 @@ +/* + * 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 IEVENTJOB_H +#define IEVENTJOB_H + +#include "IInterface.h" + +class CEvent; + +//! Event handler interface +/*! +An event job is an interface for executing a event handler. +*/ +class IEventJob : public IInterface { +public: + //! Run the job + virtual void run(const CEvent&) = 0; +}; + +#endif diff --git a/lib/base/IEventQueue.h b/lib/base/IEventQueue.h new file mode 100644 index 00000000..aaaf9f33 --- /dev/null +++ b/lib/base/IEventQueue.h @@ -0,0 +1,142 @@ +/* + * 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 IEVENTQUEUE_H +#define IEVENTQUEUE_H + +#include "IInterface.h" +#include "BasicTypes.h" + +class CEvent; +class IEventJob; + +// Opaque type for timer info. This is defined by subclasses of +// IEventQueue. +class CEventQueueTimer; + +//! Event queue interface +/*! +An event queue provides a queue of CEvents. Clients can block waiting +on any event becoming available at the head of the queue and can place +new events at the end of the queue. Clients can also add and remove +timers which generate events periodically. +*/ +class IEventQueue : public IInterface { +public: + class CTimerEvent { + public: + CEventQueueTimer* m_timer; //!< The timer + UInt32 m_count; //!< Number of repeats + }; + + //! @name manipulators + //@{ + + //! Remove event from queue + /*! + Returns the next event on the queue into \p event. If no event is + available then blocks for up to \p timeout seconds, or forever if + \p timeout is negative. Returns true iff an event was available. + */ + virtual bool getEvent(CEvent& event, double timeout = -1.0) = 0; + + //! Dispatch an event + /*! + Looks up the dispatcher for the event's target and invokes it. + Returns true iff a dispatcher exists for the target. + */ + virtual bool dispatchEvent(const CEvent& event) = 0; + + //! Add event to queue + /*! + Adds \p event to the end of the queue. + */ + virtual void addEvent(const CEvent& event) = 0; + + //! Create a recurring timer + /*! + Creates and returns a timer. An event is returned after \p duration + seconds and the timer is reset to countdown again. When a timer event + is returned the data points to a \c CTimerEvent. The client must pass + the returned timer to \c deleteTimer() (whether or not the timer has + expired) to release the timer. The returned timer event uses the + given \p target. + + Events for a single timer don't accumulate in the queue, even if the + client reading events can't keep up. Instead, the \c m_count member + of the \c CTimerEvent indicates how many events for the timer would + have been put on the queue since the last event for the timer was + removed (or since the timer was added). + */ + virtual CEventQueueTimer* + newTimer(double duration, void* target = NULL) = 0; + + //! Create a one-shot timer + /*! + Creates and returns a one-shot timer. An event is returned when + the timer expires and the timer is removed from further handling. + When a timer event is returned the data points to a \c CTimerEvent. + The \m c_count member of the \c CTimerEvent is always 1. The client + must pass the returned timer to \c deleteTimer() (whether or not the + timer has expired) to release the timer. The returned timer event + uses the given \p target. + */ + virtual CEventQueueTimer* + newOneShotTimer(double duration, + void* target = NULL) = 0; + + //! Destroy a timer + /*! + Destroys a previously created timer. The timer is removed from the + queue and will not generate event, even if the timer has expired. + */ + virtual void deleteTimer(CEventQueueTimer*) = 0; + + //! Register an event handler + /*! + Registers an event handler for \p target. The \p handler is + adopted. Any existing handler for the target is deleted. + */ + virtual void adoptHandler(void* target, IEventJob* handler) = 0; + + //! Unregister an event handler + /*! + Unregisters an event handler for \p target and returns it. + Returns NULL if there was no such handler. The client becomes + responsible for deleting the returned handler. + */ + virtual IEventJob* orphanHandler(void* target) = 0; + + //@} + //! @name accessors + //@{ + + //! Test if queue is empty + /*! + Returns true iff the queue has no events in it, including timer + events. + */ + virtual bool isEmpty() const = 0; + + //! Get an event handler + /*! + Finds and returns the event handler for \p target, or NULL if + there is no such handler. + */ + virtual IEventJob* getHandler(void* target) const = 0; + + //@} +}; + +#endif diff --git a/lib/base/Makefile.am b/lib/base/Makefile.am index f9006ab2..db17a892 100644 --- a/lib/base/Makefile.am +++ b/lib/base/Makefile.am @@ -25,25 +25,34 @@ MAINTAINERCLEANFILES = \ noinst_LIBRARIES = libbase.a libbase_a_SOURCES = \ + CEvent.cpp \ + CEventQueue.cpp \ + CFunctionEventJob.cpp \ CFunctionJob.cpp \ CJobList.cpp \ CLog.cpp \ + CSimpleEventQueue.cpp \ CStopwatch.cpp \ CStringUtil.cpp \ CUnicode.cpp \ LogOutputters.cpp \ XBase.cpp \ + CEvent.h \ + CEventQueue.h \ CFunctionJob.h \ CJobList.h \ CLog.h \ CPriorityQueue.h \ + CSimpleEventQueue.h \ CStopwatch.h \ CString.h \ CStringUtil.h \ CUnicode.h \ + IEventQueue.h \ IJob.h \ ILogOutputter.h \ LogOutputters.h \ + TMethodEventJob.h \ TMethodJob.h \ XBase.h \ $(NULL) diff --git a/lib/base/TMethodEventJob.h b/lib/base/TMethodEventJob.h new file mode 100644 index 00000000..15826be0 --- /dev/null +++ b/lib/base/TMethodEventJob.h @@ -0,0 +1,70 @@ +/* + * 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 CMETHODEVENTJOB_H +#define CMETHODEVENTJOB_H + +#include "IEventJob.h" + +//! Use a member function as an event job +/*! +An event job class that invokes a member function. +*/ +template +class TMethodEventJob : public IEventJob { +public: + //! run(event) invokes \c object->method(event, arg) + TMethodEventJob(T* object, + void (T::*method)(const CEvent&, void*), + void* arg = NULL); + virtual ~TMethodEventJob(); + + // IJob overrides + virtual void run(const CEvent&); + +private: + T* m_object; + void (T::*m_method)(const CEvent&, void*); + void* m_arg; +}; + +template +inline +TMethodEventJob::TMethodEventJob(T* object, + void (T::*method)(const CEvent&, void*), void* arg) : + m_object(object), + m_method(method), + m_arg(arg) +{ + // do nothing +} + +template +inline +TMethodEventJob::~TMethodEventJob() +{ + // do nothing +} + +template +inline +void +TMethodEventJob::run(const CEvent& event) +{ + if (m_object != NULL) { + (m_object->*m_method)(event, m_arg); + } +} + +#endif diff --git a/lib/io/CBufferedInputStream.cpp b/lib/io/CBufferedInputStream.cpp index 27b7cb6f..87cf78a3 100644 --- a/lib/io/CBufferedInputStream.cpp +++ b/lib/io/CBufferedInputStream.cpp @@ -26,9 +26,10 @@ // CBufferedInputStream::CBufferedInputStream( - CMutex* mutex, IJob* adoptedCloseCB) : + 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) @@ -39,6 +40,7 @@ CBufferedInputStream::CBufferedInputStream( CBufferedInputStream::~CBufferedInputStream() { delete m_closeCB; + delete m_emptyCB; } void @@ -64,6 +66,9 @@ 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); @@ -90,6 +95,9 @@ CBufferedInputStream::readNoLock(void* buffer, UInt32 n, double timeout) if (m_buffer.getSize() == 0) { m_empty = true; m_empty.broadcast(); + if (m_emptyCB != NULL) { + m_emptyCB->run(); + } } return n; } diff --git a/lib/io/CBufferedInputStream.h b/lib/io/CBufferedInputStream.h index 132d172b..01137563 100644 --- a/lib/io/CBufferedInputStream.h +++ b/lib/io/CBufferedInputStream.h @@ -36,8 +36,12 @@ 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* adoptedCloseCB); + CBufferedInputStream(CMutex* mutex, + IJob* adoptedEmptyCB, IJob* adoptedCloseCB); ~CBufferedInputStream(); //! @name manipulators @@ -87,6 +91,7 @@ public: private: CMutex* m_mutex; CCondVar m_empty; + IJob* m_emptyCB; IJob* m_closeCB; CStreamBuffer m_buffer; bool m_closed; diff --git a/lib/io/CBufferedOutputStream.cpp b/lib/io/CBufferedOutputStream.cpp index 3406232c..2b666d1f 100644 --- a/lib/io/CBufferedOutputStream.cpp +++ b/lib/io/CBufferedOutputStream.cpp @@ -24,8 +24,9 @@ // CBufferedOutputStream::CBufferedOutputStream( - CMutex* mutex, IJob* adoptedCloseCB) : + CMutex* mutex, IJob* adoptedFillCB, IJob* adoptedCloseCB) : m_mutex(mutex), + m_fillCB(adoptedFillCB), m_closeCB(adoptedCloseCB), m_empty(mutex, true), m_closed(false) @@ -36,6 +37,7 @@ CBufferedOutputStream::CBufferedOutputStream( CBufferedOutputStream::~CBufferedOutputStream() { delete m_closeCB; + delete m_fillCB; } const void* @@ -68,7 +70,6 @@ CBufferedOutputStream::close() } m_closed = true; - m_buffer.pop(m_buffer.getSize()); if (m_closeCB != NULL) { m_closeCB->run(); } @@ -82,7 +83,13 @@ CBufferedOutputStream::write(const void* buffer, UInt32 n) 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; } diff --git a/lib/io/CBufferedOutputStream.h b/lib/io/CBufferedOutputStream.h index 675aaac0..1853a161 100644 --- a/lib/io/CBufferedOutputStream.h +++ b/lib/io/CBufferedOutputStream.h @@ -36,8 +36,11 @@ 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* adoptedCloseCB); + CBufferedOutputStream(CMutex* mutex, + IJob* adoptedFillCB, IJob* adoptedCloseCB); ~CBufferedOutputStream(); //! @name manipulators @@ -79,6 +82,7 @@ public: private: CMutex* m_mutex; + IJob* m_fillCB; IJob* m_closeCB; CCondVar m_empty; CStreamBuffer m_buffer; diff --git a/lib/mt/CThread.cpp b/lib/mt/CThread.cpp index b2463e8b..cd88113d 100644 --- a/lib/mt/CThread.cpp +++ b/lib/mt/CThread.cpp @@ -109,6 +109,12 @@ CThread::waitForEvent(double timeout) const return s_map[ARCH->waitForEvent(m_thread, timeout)]; } +void +CThread::unblock() const +{ + ARCH->unblockThread(m_thread); +} + void* CThread::getResult() const { diff --git a/lib/mt/CThread.h b/lib/mt/CThread.h index 73982612..011f8611 100644 --- a/lib/mt/CThread.h +++ b/lib/mt/CThread.h @@ -180,6 +180,15 @@ public: */ EWaitResult waitForEvent(double timeout = -1.0) const; + //! Unblock thread in system call + /*! + Cause a thread that's in a blocking system call to return. This + call may return before the thread is unblocked. If the thread is + not in a blocking system call, this call has no effect. This does + not cause CMutex::lock() or CCondVar::wait() to return prematurely. + */ + void unblock() const; + //! Get the exit result /*! Returns the exit result. This does an implicit wait(). It returns diff --git a/lib/net/CTCPListenSocket.cpp b/lib/net/CTCPListenSocket.cpp index 1177516e..fb35f9d1 100644 --- a/lib/net/CTCPListenSocket.cpp +++ b/lib/net/CTCPListenSocket.cpp @@ -13,11 +13,16 @@ */ #include "CTCPListenSocket.h" -#include "CTCPSocket.h" #include "CNetworkAddress.h" -#include "XIO.h" +#include "CSocketMultiplexer.h" +#include "CTCPSocket.h" +#include "TSocketMultiplexerMethodJob.h" #include "XSocket.h" -#include "CThread.h" +#include "XIO.h" +#include "CEvent.h" +#include "CEventQueue.h" +#include "CLock.h" +#include "CMutex.h" #include "CArch.h" #include "XArch.h" @@ -25,8 +30,10 @@ // CTCPListenSocket // -CTCPListenSocket::CTCPListenSocket() +CTCPListenSocket::CTCPListenSocket() : + m_target(NULL) { + m_mutex = new CMutex; try { m_socket = ARCH->newSocket(IArchNetwork::kINET, IArchNetwork::kSTREAM); } @@ -38,19 +45,28 @@ CTCPListenSocket::CTCPListenSocket() CTCPListenSocket::~CTCPListenSocket() { try { - ARCH->closeSocket(m_socket); + if (m_socket != NULL) { + CSocketMultiplexer::getInstance()->removeSocket(this); + ARCH->closeSocket(m_socket); + } } catch (...) { // ignore } + delete m_mutex; } void CTCPListenSocket::bind(const CNetworkAddress& addr) { try { + CLock lock(m_mutex); ARCH->bindSocket(m_socket, addr.getAddress()); ARCH->listenOnSocket(m_socket); + CSocketMultiplexer::getInstance()->addSocket(this, + new TSocketMultiplexerMethodJob( + this, &CTCPListenSocket::serviceListening, + m_socket, true, false)); } catch (XArchNetworkAddressInUse& e) { throw XSocketAddressInUse(e.what()); @@ -63,32 +79,27 @@ CTCPListenSocket::bind(const CNetworkAddress& addr) IDataSocket* CTCPListenSocket::accept() { - // accept asynchronously so we can check for cancellation - IArchNetwork::CPollEntry pfds[1]; - pfds[0].m_socket = m_socket; - pfds[0].m_events = IArchNetwork::kPOLLIN; - for (;;) { - ARCH->testCancelThread(); - try { - const int status = ARCH->pollSocket(pfds, 1, 0.01); - if (status > 0 && - (pfds[0].m_revents & IArchNetwork::kPOLLIN) != 0) { - return new CTCPSocket(ARCH->acceptSocket(m_socket, NULL)); - } - } - catch (XArchNetwork&) { - // ignore and retry - } + 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() { + CLock lock(m_mutex); if (m_socket == NULL) { throw XIOClosed(); } try { + CSocketMultiplexer::getInstance()->removeSocket(this); ARCH->closeSocket(m_socket); m_socket = NULL; } @@ -96,3 +107,27 @@ CTCPListenSocket::close() throw XSocketIOClose(e.what()); } } + +void +CTCPListenSocket::setEventTarget(void* target) +{ + CLock lock(m_mutex); + m_target = target; +} + +ISocketMultiplexerJob* +CTCPListenSocket::serviceListening(ISocketMultiplexerJob* job, + bool read, bool, bool error) +{ + if (error) { + close(); + return NULL; + } + if (read) { + CEventQueue::getInstance()->addEvent( + CEvent(getConnectingEvent(), m_target, NULL)); + // stop polling on this socket until the client accepts + return NULL; + } + return job; +} diff --git a/lib/net/CTCPListenSocket.h b/lib/net/CTCPListenSocket.h index d5116379..8c582399 100644 --- a/lib/net/CTCPListenSocket.h +++ b/lib/net/CTCPListenSocket.h @@ -18,6 +18,9 @@ #include "IListenSocket.h" #include "IArchNetwork.h" +class CMutex; +class ISocketMultiplexerJob; + //! TCP listen socket /*! A listen socket using TCP. @@ -30,12 +33,20 @@ public: // ISocket overrides virtual void bind(const CNetworkAddress&); virtual void close(); + virtual void setEventTarget(void*); // IListenSocket overrides virtual IDataSocket* accept(); +private: + ISocketMultiplexerJob* + serviceListening(ISocketMultiplexerJob*, + bool, bool, bool); + private: CArchSocket m_socket; + CMutex* m_mutex; + void* m_target; }; #endif diff --git a/lib/net/CTCPSocket.cpp b/lib/net/CTCPSocket.cpp index e74fc4f7..7ab154b1 100644 --- a/lib/net/CTCPSocket.cpp +++ b/lib/net/CTCPSocket.cpp @@ -13,14 +13,16 @@ */ #include "CTCPSocket.h" +#include "CNetworkAddress.h" +#include "CSocketMultiplexer.h" +#include "TSocketMultiplexerMethodJob.h" #include "CBufferedInputStream.h" #include "CBufferedOutputStream.h" -#include "CNetworkAddress.h" -#include "XIO.h" #include "XSocket.h" +#include "XIO.h" #include "CLock.h" #include "CMutex.h" -#include "CThread.h" +#include "CEventQueue.h" #include "TMethodJob.h" #include "CArch.h" #include "XArch.h" @@ -45,20 +47,17 @@ CTCPSocket::CTCPSocket(CArchSocket socket) : { assert(m_socket != NULL); - init(); - // socket starts in connected state - m_connected = kReadWrite; - - // start handling socket - m_thread = new CThread(new TMethodJob( - this, &CTCPSocket::ioThread)); + init(); + setState(kReadWrite, true); } CTCPSocket::~CTCPSocket() { try { - close(); + if (m_socket != NULL) { + close(); + } } catch (...) { // ignore @@ -87,44 +86,28 @@ CTCPSocket::bind(const CNetworkAddress& addr) void CTCPSocket::close() { - // see if buffers should be flushed - bool doFlush = false; - { - CLock lock(m_mutex); - doFlush = (m_thread != NULL && (m_connected & kWrite) != 0); - } - // flush buffers - if (doFlush) { - m_output->flush(); - } + m_output->flush(); - // cause ioThread to exit - if (m_socket != NULL) { - CLock lock(m_mutex); - try { - ARCH->closeSocketForRead(m_socket); - } - catch (XArchNetwork&) { - // ignore - } - try { - ARCH->closeSocketForWrite(m_socket); - } - catch (XArchNetwork&) { - // ignore - } - m_connected = kClosed; - } + // now closed + setState(kClosed, true); - // wait for thread - if (m_thread != NULL) { - m_thread->wait(); - delete m_thread; - m_thread = NULL; + // close buffers + try { + m_input->close(); + } + catch (...) { + // ignore + } + try { + m_output->close(); + } + catch (...) { + // ignore } // close socket + CLock lock(m_mutex); if (m_socket != NULL) { try { ARCH->closeSocket(m_socket); @@ -136,65 +119,28 @@ CTCPSocket::close() } } +void +CTCPSocket::setEventTarget(void* target) +{ + CLock lock(m_mutex); + m_target = target; +} + void CTCPSocket::connect(const CNetworkAddress& addr) { - do { - // connect asynchronously so we can check for cancellation. - // we can't wrap setting and resetting the blocking flag in - // the c'tor/d'tor of a class (to make resetting automatic) - // because setBlockingOnSocket() can throw and it might be - // called while unwinding the stack due to a throw. - try { - ARCH->setBlockingOnSocket(m_socket, false); - ARCH->connectSocket(m_socket, addr.getAddress()); - ARCH->setBlockingOnSocket(m_socket, true); - - // connected - break; - } - catch (XArchNetworkConnecting&) { - // connection is in progress - ARCH->setBlockingOnSocket(m_socket, true); - } - catch (XArchNetwork& e) { - ARCH->setBlockingOnSocket(m_socket, true); - throw XSocketConnect(e.what()); - } - - // wait for connection or failure - IArchNetwork::CPollEntry pfds[1]; - pfds[0].m_socket = m_socket; - pfds[0].m_events = IArchNetwork::kPOLLOUT; - for (;;) { - ARCH->testCancelThread(); - try { - const int status = ARCH->pollSocket(pfds, 1, 0.01); - if (status > 0) { - if ((pfds[0].m_revents & (IArchNetwork::kPOLLERR | - IArchNetwork::kPOLLNVAL)) != 0) { - // connection failed - ARCH->throwErrorOnSocket(m_socket); - } - if ((pfds[0].m_revents & IArchNetwork::kPOLLOUT) != 0) { - // connection may have failed or succeeded - ARCH->throwErrorOnSocket(m_socket); - - // connected! - break; - } - } - } - catch (XArchNetwork& e) { - throw XSocketConnect(e.what()); - } - } - } while (false); - - // start servicing the socket - m_connected = kReadWrite; - m_thread = new CThread(new TMethodJob( - this, &CTCPSocket::ioThread)); + try { +// FIXME -- don't throw if in progress, just return that info + ARCH->connectSocket(m_socket, addr.getAddress()); + setState(kReadWrite, true); + } + catch (XArchNetworkConnecting&) { + // connection is in progress + setState(kConnecting, true); + } + catch (XArchNetwork& e) { + throw XSocketConnect(e.what()); + } } IInputStream* @@ -212,15 +158,24 @@ CTCPSocket::getOutputStream() void CTCPSocket::init() { - m_mutex = new CMutex; - m_thread = NULL; - m_connected = kClosed; - m_input = new CBufferedInputStream(m_mutex, + 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, + 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; + + // make socket non-blocking +// FIXME -- check for error + ARCH->setBlockingOnSocket(m_socket, false); // turn off Nagle algorithm. we send lots of very short messages // that should be sent without (much) delay. for example, the @@ -241,115 +196,101 @@ CTCPSocket::init() } } -void -CTCPSocket::ioThread(void*) +ISocketMultiplexerJob* +CTCPSocket::newMultiplexerJob(JobFunc func, bool readable, bool writable) { - try { - ioService(); - ioCleanup(); - } - catch (...) { - ioCleanup(); - throw; - } + return new TSocketMultiplexerMethodJob( + this, func, m_socket, readable, writable); } -void -CTCPSocket::ioCleanup() +ISocketMultiplexerJob* +CTCPSocket::setState(State state, bool setJob) { - try { - m_input->close(); + if (m_state == state || m_state == kClosed) { + return m_job; } - catch (...) { - // ignore - } - try { - m_output->close(); - } - catch (...) { - // ignore - } -} -void -CTCPSocket::ioService() -{ - assert(m_socket != NULL); + State oldState = m_state; + m_state = state; - // now service the connection - IArchNetwork::CPollEntry pfds[1]; - pfds[0].m_socket = m_socket; - for (;;) { - { - // choose events to poll for - CLock lock(m_mutex); - pfds[0].m_events = 0; - if (m_connected == 0) { - return; - } - if ((m_connected & kRead) != 0) { - // still open for reading - pfds[0].m_events |= IArchNetwork::kPOLLIN; - } - if ((m_connected & kWrite) != 0 && m_output->getSize() > 0) { - // data queued for writing - pfds[0].m_events |= IArchNetwork::kPOLLOUT; - } + 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; - try { - // check for status - const int status = ARCH->pollSocket(pfds, 1, 0.01); - - // transfer data and handle errors - if (status == 1) { - if ((pfds[0].m_revents & (IArchNetwork::kPOLLERR | - IArchNetwork::kPOLLNVAL)) != 0) { - // stream is no good anymore so bail - CLock lock(m_mutex); - m_input->hangup(); - return; - } - - // read some data - if (pfds[0].m_revents & IArchNetwork::kPOLLIN) { - UInt8 buffer[4096]; - size_t n = ARCH->readSocket(m_socket, - buffer, sizeof(buffer)); - CLock lock(m_mutex); - if (n > 0) { - m_input->write(buffer, n); - } - else { - // stream hungup - m_input->hangup(); - m_connected &= ~kRead; - } - } - - // write some data - if (pfds[0].m_revents & IArchNetwork::kPOLLOUT) { - CLock lock(m_mutex); - - // get amount of data to write - UInt32 n = m_output->getSize(); - - // write data - const void* buffer = m_output->peek(n); - size_t n2 = ARCH->writeSocket(m_socket, buffer, n); - - // discard written data - if (n2 > 0) { - m_output->pop(n2); - } - } - } + case kReadOnly: + if (!write) { + eventType = IDataSocket::getShutdownOutputEvent(); } - catch (XArchNetwork&) { - // socket has failed - return; + 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 @@ -358,7 +299,7 @@ CTCPSocket::closeInput(void*) // note -- m_mutex should already be locked try { ARCH->closeSocketForRead(m_socket); - m_connected &= ~kRead; + setState(kWriteOnly, true); } catch (XArchNetwork&) { // ignore @@ -370,10 +311,156 @@ CTCPSocket::closeOutput(void*) { // note -- m_mutex should already be locked try { - ARCH->closeSocketForWrite(m_socket); - m_connected &= ~kWrite; +// 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; + CSocketMultiplexer::getInstance()->removeSocket(this); + if (!write) { + sendEvent(ISocket::getDisconnectedEvent()); + m_state = kClosed; + } + } +} + +void +CTCPSocket::fillOutput(void*) +{ + // note -- m_mutex should already be locked + if (m_state == kReadWrite) { + m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, true, true); + CSocketMultiplexer::getInstance()->addSocket(this, m_job); + } + else if (m_state == kWriteOnly) { + m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, false, true); + CSocketMultiplexer::getInstance()->addSocket(this, m_job); + } +} + +ISocketMultiplexerJob* +CTCPSocket::serviceConnecting(ISocketMultiplexerJob* job, + bool, bool write, bool error) +{ + CLock lock(m_mutex); + + if (write && !error) { + try { + // connection may have failed or succeeded + ARCH->throwErrorOnSocket(m_socket); + } + catch (XArchNetwork&) { + error = true; + } + } + + if (error) { + return setState(kClosed, false); + } + + if (write) { + return setState(kReadWrite, false); + } + + return job; +} + +ISocketMultiplexerJob* +CTCPSocket::serviceConnected(ISocketMultiplexerJob* job, + bool read, bool write, bool error) +{ + CLock lock(m_mutex); + if (error) { + return setState(kClosed, false); + } + + if (write) { + // get amount of data to write + UInt32 n = m_output->getSize(); + + // write data + try { + const void* buffer = m_output->peek(n); + size_t n2 = ARCH->writeSocket(m_socket, buffer, n); + + // discard written data + if (n2 > 0) { + m_output->pop(n2); + } + } + catch (XArchNetworkDisconnected&) { + // stream hungup + return setState(kReadOnly, false); + } + } + + 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)); +} diff --git a/lib/net/CTCPSocket.h b/lib/net/CTCPSocket.h index bfbade89..142ac4a6 100644 --- a/lib/net/CTCPSocket.h +++ b/lib/net/CTCPSocket.h @@ -16,6 +16,7 @@ #define CTCPSOCKET_H #include "IDataSocket.h" +#include "CEvent.h" #include "BasicTypes.h" #include "IArchNetwork.h" @@ -23,6 +24,7 @@ class CMutex; class CThread; class CBufferedInputStream; class CBufferedOutputStream; +class ISocketMultiplexerJob; //! TCP data socket /*! @@ -37,6 +39,7 @@ public: // ISocket overrides virtual void bind(const CNetworkAddress&); virtual void close(); + virtual void setEventTarget(void*); // IDataSocket overrides virtual void connect(const CNetworkAddress&); @@ -44,23 +47,52 @@ public: virtual IOutputStream* getOutputStream(); private: + enum State { + kUnconnected, + kConnecting, + kReadWrite, + kReadOnly, + kWriteOnly, + kShutdown, + kClosed + }; + void init(); - void ioThread(void*); - void ioCleanup(); - void ioService(); + + ISocketMultiplexerJob* + setState(State, bool setJob); + void closeInput(void*); void closeOutput(void*); + void emptyInput(void*); + void fillOutput(void*); + + ISocketMultiplexerJob* + serviceConnecting(ISocketMultiplexerJob*, + bool, bool, bool); + ISocketMultiplexerJob* + 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: - enum { kClosed = 0, kRead = 1, kWrite = 2, kReadWrite = 3 }; - CArchSocket m_socket; CBufferedInputStream* m_input; CBufferedOutputStream* m_output; CMutex* m_mutex; - CThread* m_thread; - UInt32 m_connected; + State m_state; + void* m_target; + + ISocketMultiplexerJob* m_job; }; #endif diff --git a/lib/net/IDataSocket.cpp b/lib/net/IDataSocket.cpp new file mode 100644 index 00000000..f6d577ab --- /dev/null +++ b/lib/net/IDataSocket.cpp @@ -0,0 +1,55 @@ +/* + * 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 "IDataSocket.h" + +// +// 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::getConnectedEvent() +{ + return CEvent::registerTypeOnce(s_connectedEvent); +} + +CEvent::Type +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 0193e1d1..8fa2d6da 100644 --- a/lib/net/IDataSocket.h +++ b/lib/net/IDataSocket.h @@ -46,6 +46,7 @@ public: 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 @@ -53,11 +54,60 @@ public: */ virtual IOutputStream* getOutputStream() = 0; + //@} + //! @name accessors + //@{ + + //! Get connected event type + /*! + Returns the socket connected event type. A socket sends this + event when a remote connection has been established. + */ + static CEvent::Type getConnectedEvent(); + + //! Get connection failed event type + /*! + Returns the socket connection failed event type. A socket sends + this event when an attempt to connect to a remote port has failed. + */ + 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; + +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.cpp b/lib/net/IListenSocket.cpp new file mode 100644 index 00000000..9c9e704c --- /dev/null +++ b/lib/net/IListenSocket.cpp @@ -0,0 +1,27 @@ +/* + * 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 "IListenSocket.h" + +// +// IListenSocket +// + +CEvent::Type IListenSocket::s_connectingEvent = CEvent::kUnknown; + +CEvent::Type +IListenSocket::getConnectingEvent() +{ + return CEvent::registerTypeOnce(s_connectingEvent); +} diff --git a/lib/net/IListenSocket.h b/lib/net/IListenSocket.h index 7a7ddaae..36524750 100644 --- a/lib/net/IListenSocket.h +++ b/lib/net/IListenSocket.h @@ -31,18 +31,32 @@ public: //! Accept connection /*! - Wait for and accept a connection, returning a socket representing - the full-duplex data stream. - - (cancellation point) + Accept a connection, returning a socket representing the full-duplex + data stream. Returns NULL if no socket is waiting to be accepted. + This is only valid after a call to \c bind(). */ virtual IDataSocket* accept() = 0; + //@} + //! @name accessors + //@{ + + //! Get connecting event type + /*! + Returns the socket connecting event type. A socket sends this + event when a remote connection is waiting to be accepted. + */ + static CEvent::Type getConnectingEvent(); + //@} // ISocket overrides virtual void bind(const CNetworkAddress&) = 0; virtual void close() = 0; + virtual void setEventTarget(void*) = 0; + +private: + static CEvent::Type s_connectingEvent; }; #endif diff --git a/lib/net/ISocket.cpp b/lib/net/ISocket.cpp new file mode 100644 index 00000000..b1adbcc3 --- /dev/null +++ b/lib/net/ISocket.cpp @@ -0,0 +1,27 @@ +/* + * 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 "ISocket.h" + +// +// ISocket +// + +CEvent::Type ISocket::s_disconnectedEvent = CEvent::kUnknown; + +CEvent::Type +ISocket::getDisconnectedEvent() +{ + return CEvent::registerTypeOnce(s_disconnectedEvent); +} diff --git a/lib/net/ISocket.h b/lib/net/ISocket.h index 623a25a7..79e55966 100644 --- a/lib/net/ISocket.h +++ b/lib/net/ISocket.h @@ -16,6 +16,7 @@ #define ISOCKET_H #include "IInterface.h" +#include "CEvent.h" class CNetworkAddress; @@ -40,7 +41,28 @@ 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 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. + */ + static CEvent::Type getDisconnectedEvent(); + + //@} + +private: + static CEvent::Type s_disconnectedEvent; }; #endif diff --git a/lib/net/Makefile.am b/lib/net/Makefile.am index efeb5198..fc8dfe4a 100644 --- a/lib/net/Makefile.am +++ b/lib/net/Makefile.am @@ -15,35 +15,42 @@ NULL = DEPTH = ../.. VDEPTH = ./$(VPATH)/$(DEPTH) -EXTRA_DIST = \ - net.dsp \ +EXTRA_DIST = \ + net.dsp \ $(NULL) -MAINTAINERCLEANFILES = \ - Makefile.in \ +MAINTAINERCLEANFILES = \ + Makefile.in \ $(NULL) noinst_LIBRARIES = libnet.a -libnet_a_SOURCES = \ - CNetworkAddress.cpp \ - CTCPListenSocket.cpp \ - CTCPSocket.cpp \ - CTCPSocketFactory.cpp \ - XSocket.cpp \ - CNetworkAddress.h \ - CTCPListenSocket.h \ - CTCPSocket.h \ - CTCPSocketFactory.h \ - IDataSocket.h \ - IListenSocket.h \ - ISocket.h \ - ISocketFactory.h \ - XSocket.h \ +libnet_a_SOURCES = \ + CNetworkAddress.cpp \ + CSocketMultiplexer.cpp \ + CTCPListenSocket.cpp \ + CTCPSocket.cpp \ + CTCPSocketFactory.cpp \ + IDataSocket.cpp \ + IListenSocket.cpp \ + ISocket.cpp \ + XSocket.cpp \ + CNetworkAddress.h \ + CSocketMultiplexer.h \ + CTCPListenSocket.h \ + CTCPSocket.h \ + CTCPSocketFactory.h \ + IDataSocket.h \ + IListenSocket.h \ + ISocket.h \ + ISocketFactory.h \ + ISocketMultiplexerJob.h \ + TSocketMultiplexerMethodJob.h \ + XSocket.h \ $(NULL) -INCLUDES = \ - -I$(VDEPTH)/lib/common \ - -I$(VDEPTH)/lib/arch \ - -I$(VDEPTH)/lib/base \ - -I$(VDEPTH)/lib/mt \ - -I$(VDEPTH)/lib/io \ +INCLUDES = \ + -I$(VDEPTH)/lib/common \ + -I$(VDEPTH)/lib/arch \ + -I$(VDEPTH)/lib/base \ + -I$(VDEPTH)/lib/mt \ + -I$(VDEPTH)/lib/io \ $(NULL) diff --git a/lib/platform/CXWindowsEventQueue.cpp b/lib/platform/CXWindowsEventQueue.cpp new file mode 100644 index 00000000..9c51284d --- /dev/null +++ b/lib/platform/CXWindowsEventQueue.cpp @@ -0,0 +1,182 @@ +/* + * 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 "CXWindowsEventQueue.h" +#include "CEvent.h" +#include "CThread.h" +#if UNIX_LIKE +# if HAVE_POLL +# include +# else +# if HAVE_SYS_SELECT_H +# include +# endif +# if HAVE_SYS_TIME_H +# include +# endif +# if HAVE_SYS_TYPES_H +# include +# endif +# if HAVE_UNISTD_H +# include +# endif +# endif +#endif + +// +// CEventQueueTimer +// + +class CEventQueueTimer { }; + + +// +// CXWindowsEventQueue +// + +CXWindowsEventQueue::CXWindowsEventQueue(Display* display) : + m_display(display) +{ + m_userEvent = XInternAtom(m_display, "SYNERGY_USER_EVENT", False); + + XSetWindowAttributes attr; + m_window = XCreateWindow(m_display, DefaultRootWindow(m_display), + 0, 0, 1, 1, 0, 0, InputOnly, CopyFromParent, + 0, &attr); +} + +CXWindowsEventQueue::~CXWindowsEventQueue() +{ + XDestroyWindow(m_display, m_window); +} + +void +CXWindowsEventQueue::processSystemEvent(CEvent& event) +{ + event = CEvent(CEvent::kSystem, getSystemTarget(), &m_event); +} + +void +CXWindowsEventQueue::processClientMessage(CEvent& event) +{ + assert(m_event.xany.type == ClientMessage); + + // handle user events specially + if (m_event.xclient.message_type == m_userEvent) { + // get event data + CEventData data = removeEventData(m_event.xclient.data.l[1]); + + // create event + event = CEvent(static_cast(m_event.xclient.data.l[0]), + data.first, data.second); + } + else { + processSystemEvent(event); + } +} + +void +CXWindowsEventQueue::waitForEvent(double dtimeout) +{ + // use poll() to wait for a message from the X server or for timeout. + // this is a good deal more efficient than polling and sleeping. +#if HAVE_POLL + struct pollfd pfds[1]; + pfds[0].fd = ConnectionNumber(m_display); + pfds[0].events = POLLIN; + int timeout = (dtimeout < 0.0) ? -1 : + static_cast(1000.0 * dtimeout); +#else + struct timeval timeout; + struct timeval* timeoutPtr; + if (dtimeout < 0.0) { + timeoutPtr = NULL; + } + else { + timeout.tv_sec = static_cast(dtimeout); + timeout.tv_usec = static_cast(1.0e+6 * + (dtimeout - timeout.tv_sec)); + timeoutPtr = &timeout; + } + + // initialize file descriptor sets + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(ConnectionNumber(m_display), &rfds); +#endif + + // wait for message from X server or for timeout. also check + // if the thread has been cancelled. poll() should return -1 + // with EINTR when the thread is cancelled. + CThread::testCancel(); +#if HAVE_POLL + poll(pfds, 1, timeout); +#else + select(ConnectionNumber(m_display) + 1, + SELECT_TYPE_ARG234 &rfds, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG5 timeoutPtr); +#endif + CThread::testCancel(); +} + +bool +CXWindowsEventQueue::doGetEvent(CEvent& event) +{ + // get next event + XNextEvent(m_display, &m_event); + + // process event + if (m_event.xany.type == ClientMessage) { + processClientMessage(event); + } + else { + processSystemEvent(event); + } + + return true; +} + +bool +CXWindowsEventQueue::doAddEvent(CEvent::Type type, UInt32 dataID) +{ + // send ourself a message + XEvent xevent; + xevent.xclient.type = ClientMessage; + xevent.xclient.window = m_window; + xevent.xclient.message_type = m_userEvent; + xevent.xclient.format = 32; + xevent.xclient.data.l[0] = static_cast(type); + xevent.xclient.data.l[1] = static_cast(dataID); + return (XSendEvent(m_display, m_window, False, 0, &xevent) != 0); +} + +bool +CXWindowsEventQueue::doIsEmpty() const +{ + return (XPending(m_display) == 0); +} + +CEventQueueTimer* +CXWindowsEventQueue::doNewTimer(double, bool) const +{ + return new CEventQueueTimer(); +} + +void +CXWindowsEventQueue::doDeleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} diff --git a/lib/platform/CXWindowsEventQueue.h b/lib/platform/CXWindowsEventQueue.h new file mode 100644 index 00000000..39d9faef --- /dev/null +++ b/lib/platform/CXWindowsEventQueue.h @@ -0,0 +1,61 @@ +/* + * 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 CXWINDOWSEVENTQUEUE_H +#define CXWINDOWSEVENTQUEUE_H + +#include "CEventQueue.h" +#if defined(X_DISPLAY_MISSING) +# error X11 is required to build synergy +#else +# include +#endif + +//! Event queue for X11 +class CXWindowsEventQueue : public CEventQueue { +public: + CXWindowsEventQueue(Display*); + virtual ~CXWindowsEventQueue(); + + //! @name manipulators + //@{ + + //@} + //! @name accessors + //@{ + + //@} + +protected: + // CEventQueue overrides + virtual void waitForEvent(double timeout); + virtual bool doGetEvent(CEvent& event); + virtual bool doAddEvent(CEvent::Type type, UInt32 dataID); + virtual bool doIsEmpty() const; + virtual CEventQueueTimer* + doNewTimer(double duration, bool oneShot) const; + virtual void doDeleteTimer(CEventQueueTimer*) const; + +private: + void processSystemEvent(CEvent& event); + void processClientMessage(CEvent& event); + +private: + Display* m_display; + Window m_window; + Atom m_userEvent; + XEvent m_event; +}; + +#endif diff --git a/lib/platform/Makefile.am b/lib/platform/Makefile.am index 891271d3..25629cc4 100644 --- a/lib/platform/Makefile.am +++ b/lib/platform/Makefile.am @@ -49,6 +49,7 @@ libplatform_a_SOURCES = \ CXWindowsClipboardTextConverter.cpp \ CXWindowsClipboardUCS2Converter.cpp \ CXWindowsClipboardUTF8Converter.cpp \ + CXWindowsEventQueue.cpp \ CXWindowsKeyMapper.cpp \ CXWindowsScreen.cpp \ CXWindowsScreenSaver.cpp \ @@ -57,6 +58,7 @@ libplatform_a_SOURCES = \ CXWindowsClipboardTextConverter.h \ CXWindowsClipboardUCS2Converter.h \ CXWindowsClipboardUTF8Converter.h \ + CXWindowsEventQueue.h \ CXWindowsKeyMapper.h \ CXWindowsScreen.h \ CXWindowsScreenSaver.h \