From ff81f708e2aa0411756e460ec242c52084aaddae Mon Sep 17 00:00:00 2001 From: crs Date: Sat, 6 Oct 2001 14:13:28 +0000 Subject: [PATCH] Started over. --- BasicTypes.h | 33 -- CClient.cpp | 293 --------- CClient.h | 28 - CEvent.h | 59 -- CEventQueue.cpp | 80 --- CEventQueue.h | 36 -- CMessageSocket.cpp | 153 ----- CMessageSocket.h | 33 -- CProtocol.h | 20 - CScreenProxy.cpp | 238 -------- CScreenProxy.h | 42 -- CServer.cpp | 812 ------------------------- CServer.h | 103 ---- CSocket.cpp | 58 -- CSocket.h | 41 -- CSocketFactory.cpp | 31 - CSocketFactory.h | 29 - CString.h | 40 -- CTrace.cpp | 16 - CTrace.h | 19 - CUnixEventQueue.cpp | 141 ----- CUnixEventQueue.h | 54 -- CUnixTCPSocket.cpp | 278 --------- CUnixTCPSocket.h | 50 -- CUnixXScreen.cpp | 34 -- CUnixXScreen.h | 16 - CXScreen.cpp | 626 -------------------- CXScreen.h | 84 --- IClient.h | 17 - IClipboard.h | 0 IEventQueue.h | 45 -- IScreen.h | 130 ---- IServer.h | 29 - ISocket.h | 45 -- Make-solaris | 67 +++ Makecommon | 93 +++ TMethodJob.h | 23 - XBase.cpp | 40 -- XBase.h | 31 - XSocket.h | 33 -- base/BasicTypes.h | 46 ++ base/CFunctionJob.cpp | 19 + base/CFunctionJob.h | 18 + base/CStopwatch.cpp | 180 ++++++ base/CStopwatch.h | 57 ++ base/CString.h | 41 ++ base/IInterface.h | 11 + IJob.h => base/IJob.h | 9 +- base/Makefile | 24 + base/TMethodJob.h | 39 ++ base/XBase.cpp | 72 +++ base/XBase.h | 44 ++ base/common.h | 32 + io/CBufferedInputStream.cpp | 107 ++++ io/CBufferedInputStream.h | 51 ++ io/CBufferedOutputStream.cpp | 79 +++ io/CBufferedOutputStream.h | 46 ++ io/CInputStreamFilter.cpp | 25 + io/CInputStreamFilter.h | 28 + io/COutputStreamFilter.cpp | 25 + io/COutputStreamFilter.h | 28 + io/CStreamBuffer.cpp | 107 ++++ io/CStreamBuffer.h | 38 ++ io/IInputStream.h | 29 + io/IOutputStream.h | 24 + io/Makefile | 29 + io/XIO.cpp | 46 ++ io/XIO.h | 33 ++ main.cpp | 114 ---- mt/CCondVar.cpp | 296 ++++++++++ mt/CCondVar.h | 144 +++++ mt/CLock.cpp | 22 + mt/CLock.h | 24 + mt/CMutex.cpp | 123 ++++ mt/CMutex.h | 35 ++ mt/CThread.cpp | 131 +++++ mt/CThread.h | 130 ++++ mt/CThreadRep.cpp | 518 ++++++++++++++++ mt/CThreadRep.h | 122 ++++ mt/CTimerThread.cpp | 30 + mt/CTimerThread.h | 27 + mt/Makefile | 28 + mt/XThread.h | 31 + net/CNetworkAddress.cpp | 65 ++ net/CNetworkAddress.h | 27 + net/CSocketInputStream.cpp | 93 +++ net/CSocketInputStream.h | 42 ++ net/CSocketOutputStream.cpp | 78 +++ net/CSocketOutputStream.h | 43 ++ net/CSocketStreamBuffer.cpp | 106 ++++ net/CSocketStreamBuffer.h | 38 ++ net/CTCPListenSocket.cpp | 72 +++ net/CTCPListenSocket.h | 24 + net/CTCPSocket.cpp | 226 +++++++ net/CTCPSocket.h | 49 ++ net/IListenSocket.h | 27 + net/ISocket.h | 35 ++ net/Makefile | 28 + net/XSocket.cpp | 84 +++ net/XSocket.h | 58 ++ notes | 85 +++ synergy/CClient.cpp | 265 +++++++++ synergy/CClient.h | 46 ++ synergy/CInputPacketStream.cpp | 109 ++++ synergy/CInputPacketStream.h | 33 ++ synergy/COutputPacketStream.cpp | 56 ++ synergy/COutputPacketStream.h | 21 + synergy/CProtocolUtil.cpp | 336 +++++++++++ synergy/CProtocolUtil.h | 53 ++ synergy/CScreenMap.cpp | 89 +++ synergy/CScreenMap.h | 46 ++ synergy/CServer.cpp | 849 +++++++++++++++++++++++++++ synergy/CServer.h | 158 +++++ synergy/CServerProtocol.cpp | 72 +++ synergy/CServerProtocol.h | 58 ++ synergy/CServerProtocol1_0.cpp | 157 +++++ synergy/CServerProtocol1_0.h | 37 ++ synergy/CTCPSocketFactory.cpp | 27 + synergy/CTCPSocketFactory.h | 20 + synergy/CXWindowsPrimaryScreen.cpp | 363 ++++++++++++ synergy/CXWindowsPrimaryScreen.h | 43 ++ synergy/IPrimaryScreen.h | 73 +++ synergy/ISecondaryScreen.h | 71 +++ synergy/IServerProtocol.h | 47 ++ synergy/ISocketFactory.h | 21 + KeyTypes.h => synergy/KeyTypes.h | 2 + synergy/Makefile | 44 ++ MouseTypes.h => synergy/MouseTypes.h | 2 + synergy/ProtocolTypes.h | 38 ++ synergy/XSynergy.cpp | 37 ++ synergy/XSynergy.h | 34 ++ test.cpp | 145 +++++ 132 files changed, 7634 insertions(+), 3960 deletions(-) delete mode 100644 BasicTypes.h delete mode 100644 CClient.cpp delete mode 100644 CClient.h delete mode 100644 CEvent.h delete mode 100644 CEventQueue.cpp delete mode 100644 CEventQueue.h delete mode 100644 CMessageSocket.cpp delete mode 100644 CMessageSocket.h delete mode 100644 CProtocol.h delete mode 100644 CScreenProxy.cpp delete mode 100644 CScreenProxy.h delete mode 100644 CServer.cpp delete mode 100644 CServer.h delete mode 100644 CSocket.cpp delete mode 100644 CSocket.h delete mode 100644 CSocketFactory.cpp delete mode 100644 CSocketFactory.h delete mode 100644 CString.h delete mode 100644 CTrace.cpp delete mode 100644 CTrace.h delete mode 100644 CUnixEventQueue.cpp delete mode 100644 CUnixEventQueue.h delete mode 100644 CUnixTCPSocket.cpp delete mode 100644 CUnixTCPSocket.h delete mode 100644 CUnixXScreen.cpp delete mode 100644 CUnixXScreen.h delete mode 100644 CXScreen.cpp delete mode 100644 CXScreen.h delete mode 100644 IClient.h delete mode 100644 IClipboard.h delete mode 100644 IEventQueue.h delete mode 100644 IScreen.h delete mode 100644 IServer.h delete mode 100644 ISocket.h create mode 100644 Make-solaris create mode 100644 Makecommon delete mode 100644 TMethodJob.h delete mode 100644 XBase.cpp delete mode 100644 XBase.h delete mode 100644 XSocket.h create mode 100644 base/BasicTypes.h create mode 100644 base/CFunctionJob.cpp create mode 100644 base/CFunctionJob.h create mode 100644 base/CStopwatch.cpp create mode 100644 base/CStopwatch.h create mode 100644 base/CString.h create mode 100644 base/IInterface.h rename IJob.h => base/IJob.h (55%) create mode 100644 base/Makefile create mode 100644 base/TMethodJob.h create mode 100644 base/XBase.cpp create mode 100644 base/XBase.h create mode 100644 base/common.h create mode 100644 io/CBufferedInputStream.cpp create mode 100644 io/CBufferedInputStream.h create mode 100644 io/CBufferedOutputStream.cpp create mode 100644 io/CBufferedOutputStream.h create mode 100644 io/CInputStreamFilter.cpp create mode 100644 io/CInputStreamFilter.h create mode 100644 io/COutputStreamFilter.cpp create mode 100644 io/COutputStreamFilter.h create mode 100644 io/CStreamBuffer.cpp create mode 100644 io/CStreamBuffer.h create mode 100644 io/IInputStream.h create mode 100644 io/IOutputStream.h create mode 100644 io/Makefile create mode 100644 io/XIO.cpp create mode 100644 io/XIO.h delete mode 100644 main.cpp create mode 100644 mt/CCondVar.cpp create mode 100644 mt/CCondVar.h create mode 100644 mt/CLock.cpp create mode 100644 mt/CLock.h create mode 100644 mt/CMutex.cpp create mode 100644 mt/CMutex.h create mode 100644 mt/CThread.cpp create mode 100644 mt/CThread.h create mode 100644 mt/CThreadRep.cpp create mode 100644 mt/CThreadRep.h create mode 100644 mt/CTimerThread.cpp create mode 100644 mt/CTimerThread.h create mode 100644 mt/Makefile create mode 100644 mt/XThread.h create mode 100644 net/CNetworkAddress.cpp create mode 100644 net/CNetworkAddress.h create mode 100644 net/CSocketInputStream.cpp create mode 100644 net/CSocketInputStream.h create mode 100644 net/CSocketOutputStream.cpp create mode 100644 net/CSocketOutputStream.h create mode 100644 net/CSocketStreamBuffer.cpp create mode 100644 net/CSocketStreamBuffer.h create mode 100644 net/CTCPListenSocket.cpp create mode 100644 net/CTCPListenSocket.h create mode 100644 net/CTCPSocket.cpp create mode 100644 net/CTCPSocket.h create mode 100644 net/IListenSocket.h create mode 100644 net/ISocket.h create mode 100644 net/Makefile create mode 100644 net/XSocket.cpp create mode 100644 net/XSocket.h create mode 100644 notes create mode 100644 synergy/CClient.cpp create mode 100644 synergy/CClient.h create mode 100644 synergy/CInputPacketStream.cpp create mode 100644 synergy/CInputPacketStream.h create mode 100644 synergy/COutputPacketStream.cpp create mode 100644 synergy/COutputPacketStream.h create mode 100644 synergy/CProtocolUtil.cpp create mode 100644 synergy/CProtocolUtil.h create mode 100644 synergy/CScreenMap.cpp create mode 100644 synergy/CScreenMap.h create mode 100644 synergy/CServer.cpp create mode 100644 synergy/CServer.h create mode 100644 synergy/CServerProtocol.cpp create mode 100644 synergy/CServerProtocol.h create mode 100644 synergy/CServerProtocol1_0.cpp create mode 100644 synergy/CServerProtocol1_0.h create mode 100644 synergy/CTCPSocketFactory.cpp create mode 100644 synergy/CTCPSocketFactory.h create mode 100644 synergy/CXWindowsPrimaryScreen.cpp create mode 100644 synergy/CXWindowsPrimaryScreen.h create mode 100644 synergy/IPrimaryScreen.h create mode 100644 synergy/ISecondaryScreen.h create mode 100644 synergy/IServerProtocol.h create mode 100644 synergy/ISocketFactory.h rename KeyTypes.h => synergy/KeyTypes.h (96%) create mode 100644 synergy/Makefile rename MouseTypes.h => synergy/MouseTypes.h (92%) create mode 100644 synergy/ProtocolTypes.h create mode 100644 synergy/XSynergy.cpp create mode 100644 synergy/XSynergy.h create mode 100644 test.cpp diff --git a/BasicTypes.h b/BasicTypes.h deleted file mode 100644 index 8dfce8c2..00000000 --- a/BasicTypes.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef BASICTYPES_H -#define BASICTYPES_H - -#if defined(__linux__) - -#define CONFIG_PLATFORM_LINUX -#define CONFIG_TYPES_X11 - -#include - -typedef int8_t SInt8; -typedef int16_t SInt16; -typedef int32_t SInt32; -typedef int64_t SInt64; - -typedef uint8_t UInt8; -typedef uint16_t UInt16; -typedef uint32_t UInt32; -typedef uint64_t UInt64; - -#else - -#error unsupported platform - -#endif - -#ifndef NULL -#define NULL 0 -#endif - -#endif - - diff --git a/CClient.cpp b/CClient.cpp deleted file mode 100644 index 83739e8c..00000000 --- a/CClient.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include "CClient.h" -#include "CString.h" -#include "TMethodJob.h" -#include "IScreen.h" -#include "ISocket.h" -#include "CMessageSocket.h" -#include "CSocketFactory.h" -#include "IEventQueue.h" -#include "CEvent.h" -#include "CTrace.h" -#include - -// -// CClient -// - -CClient::CClient(IScreen* screen) : m_screen(screen), - m_socket(NULL) -{ - assert(m_screen != NULL); - assert(!m_screen->getName().empty()); -} - -CClient::~CClient() -{ - assert(m_socket == NULL); -} - -void CClient::run(const CString& hostname) -{ - assert(m_socket == NULL); - - try { - // create socket and - m_socket = CSOCKETFACTORY->create(); - m_socket->setWriteJob(new TMethodJob(this, - &CClient::onConnect)); - TRACE(("connecting to %s...", hostname.c_str())); - m_socket->connect(hostname, 40001); // CProtocol::kDefaultPort - - bool m_done = false; // FIXME - - IEventQueue* queue = CEQ; - while (!m_done) { - // wait for connection, network messages, and events - queue->wait(-1.0); - - // handle events - while (!queue->isEmpty()) { - // get the next event - CEvent event; - queue->pop(&event); - - // handle it - switch (event.m_any.m_type) { - case CEventBase::kScreenSize: { - sendScreenSize(); - break; - } - - case CEventBase::kNull: - case CEventBase::kKeyDown: - case CEventBase::kKeyRepeat: - case CEventBase::kKeyUp: - case CEventBase::kMouseDown: - case CEventBase::kMouseUp: - case CEventBase::kMouseMove: - case CEventBase::kMouseWheel: - // FIXME -- other cases - break; - } - } - } - - delete m_socket; - m_socket = NULL; - } - - catch (...) { - delete m_socket; - m_socket = NULL; - throw; - } -} - -void CClient::onConnect() -{ - TRACE(("connected")); - - // say hello - const CString name(m_screen->getName()); - char buf[512]; - memcpy(buf, "SYNERGY\000\001", 9); - buf[9] = static_cast(name.length()); - memcpy(buf + 10, name.c_str(), name.length()); - m_socket->write(buf, 10 + name.length()); - - // handle messages - m_socket->setWriteJob(NULL); - m_socket = new CMessageSocket(m_socket); - m_socket->setReadJob(new TMethodJob(this, &CClient::onRead)); -} - -void CClient::onRead() -{ - char buf[512]; - SInt32 n = m_socket->read(buf, sizeof(buf)); - if (n == -1) { - // disconnect - TRACE(("hangup")); - } - else if (n > 0) { - TRACE(("msg: 0x%02x length %d", buf[0], n)); - switch (buf[0]) { - case '\002': - TRACE((" open")); - - // open the screen - m_screen->open(buf[1] != 0); - - // send initial size - sendScreenSize(); - break; - - case '\003': - TRACE((" close")); - m_screen->close(); - break; - - case '\004': { - const SInt32 x = static_cast( - (static_cast(buf[1]) << 24) + - (static_cast(buf[2]) << 16) + - (static_cast(buf[3]) << 8) + - (static_cast(buf[4]) )); - const SInt32 y = static_cast( - (static_cast(buf[5]) << 24) + - (static_cast(buf[6]) << 16) + - (static_cast(buf[7]) << 8) + - (static_cast(buf[8]) )); - TRACE((" enter: %d,%d", x, y)); - m_screen->enterScreen(x, y); - break; - } - - case '\005': - TRACE((" leave")); - m_screen->leaveScreen(); - break; - - case '\007': { - const KeyID k = static_cast( - (static_cast(buf[1]) << 24) + - (static_cast(buf[2]) << 16) + - (static_cast(buf[3]) << 8) + - (static_cast(buf[4]) )); - const KeyModifierMask m = static_cast( - (static_cast(buf[5]) << 24) + - (static_cast(buf[6]) << 16) + - (static_cast(buf[7]) << 8) + - (static_cast(buf[8]) )); - TRACE((" key down: %d 0x%08x", k, m)); - m_screen->onKeyDown(k, m); - break; - } - - case '\010': { - const KeyID k = static_cast( - (static_cast(buf[1]) << 24) + - (static_cast(buf[2]) << 16) + - (static_cast(buf[3]) << 8) + - (static_cast(buf[4]) )); - const KeyModifierMask m = static_cast( - (static_cast(buf[5]) << 24) + - (static_cast(buf[6]) << 16) + - (static_cast(buf[7]) << 8) + - (static_cast(buf[8]) )); - const SInt32 n = static_cast( - (static_cast(buf[9]) << 24) + - (static_cast(buf[10]) << 16) + - (static_cast(buf[11]) << 8) + - (static_cast(buf[12]) )); - TRACE((" key repeat: %d 0x%08x x%d", k, m, n)); - m_screen->onKeyRepeat(k, m, n); - break; - } - - case '\011': { - const KeyID k = static_cast( - (static_cast(buf[1]) << 24) + - (static_cast(buf[2]) << 16) + - (static_cast(buf[3]) << 8) + - (static_cast(buf[4]) )); - const KeyModifierMask m = static_cast( - (static_cast(buf[5]) << 24) + - (static_cast(buf[6]) << 16) + - (static_cast(buf[7]) << 8) + - (static_cast(buf[8]) )); - TRACE((" key up: %d 0x%08x", k, m)); - m_screen->onKeyUp(k, m); - break; - } - - case '\013': { - const ButtonID b = static_cast( - static_cast(buf[1])); - TRACE((" mouse down: %d", b)); - m_screen->onMouseDown(b); - break; - } - - case '\014': { - const ButtonID b = static_cast( - static_cast(buf[1])); - TRACE((" mouse up: %d", b)); - m_screen->onMouseUp(b); - break; - } - - case '\015': { - const SInt32 x = static_cast( - (static_cast(buf[1]) << 24) + - (static_cast(buf[2]) << 16) + - (static_cast(buf[3]) << 8) + - (static_cast(buf[4]) )); - const SInt32 y = static_cast( - (static_cast(buf[5]) << 24) + - (static_cast(buf[6]) << 16) + - (static_cast(buf[7]) << 8) + - (static_cast(buf[8]) )); - TRACE((" mouse move: %d,%d", x, y)); - m_screen->onMouseMove(x, y); - break; - } - - case '\016': { - const SInt32 n = static_cast( - (static_cast(buf[1]) << 24) + - (static_cast(buf[2]) << 16) + - (static_cast(buf[3]) << 8) + - (static_cast(buf[4]) )); - TRACE((" mouse wheel: %d", n)); - m_screen->onMouseWheel(n); - break; - } - - case '\017': { - TRACE((" screen saver: %s", buf[1] ? "on" : "off")); - m_screen->onScreenSaver(buf[1] != 0); - break; - } - - case '\020': { - const SInt32 x = static_cast( - (static_cast(buf[1]) << 24) + - (static_cast(buf[2]) << 16) + - (static_cast(buf[3]) << 8) + - (static_cast(buf[4]) )); - const SInt32 y = static_cast( - (static_cast(buf[5]) << 24) + - (static_cast(buf[6]) << 16) + - (static_cast(buf[7]) << 8) + - (static_cast(buf[8]) )); - TRACE((" warp: %d,%d", x, y)); - m_screen->warpCursor(x, y); - break; - } - - default: - TRACE((" unknown message")); - } - } -} - -void CClient::sendScreenSize() -{ - // get the size - SInt32 w, h; - m_screen->getSize(&w, &h); - - // send it - char buf[9]; - memcpy(buf, "\201", 1); - buf[1] = static_cast((w >> 24) & 0xff); - buf[2] = static_cast((w >> 16) & 0xff); - buf[3] = static_cast((w >> 8) & 0xff); - buf[4] = static_cast(w & 0xff); - buf[5] = static_cast((h >> 24) & 0xff); - buf[6] = static_cast((h >> 16) & 0xff); - buf[7] = static_cast((h >> 8) & 0xff); - buf[8] = static_cast(h & 0xff); - m_socket->write(buf, sizeof(buf)); -} diff --git a/CClient.h b/CClient.h deleted file mode 100644 index c99cb364..00000000 --- a/CClient.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef CCLIENT_H -#define CCLIENT_H - -#include "IClient.h" - -class IScreen; -class ISocket; - -class CClient : public IClient { - public: - CClient(IScreen* screen); - virtual ~CClient(); - - // IClient overrides - virtual void run(const CString& hostname); - - private: - void onConnect(); - void onRead(); - - void sendScreenSize(); - - private: - IScreen* m_screen; - ISocket* m_socket; -}; - -#endif diff --git a/CEvent.h b/CEvent.h deleted file mode 100644 index 348218a2..00000000 --- a/CEvent.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef CEVENT_H -#define CEVENT_H - -#include "BasicTypes.h" -#include "KeyTypes.h" -#include "MouseTypes.h" - -class ISocket; - -class CEventBase { - public: - enum EType { - kNull, - kKeyDown, - kKeyRepeat, - kKeyUp, - kMouseDown, - kMouseUp, - kMouseMove, - kMouseWheel, - kScreenSize - }; - - EType m_type; -}; - -class CEventKey : public CEventBase { - public: - KeyID m_key; - KeyModifierMask m_mask; - SInt32 m_count; -}; - -class CEventMouse : public CEventBase { - public: - ButtonID m_button; - SInt32 m_x; // or wheel delta - SInt32 m_y; -}; - -class CEventSize : public CEventBase { - public: - SInt32 m_w; - SInt32 m_h; -}; - -class CEvent { - public: - union { - public: - CEventBase m_any; - CEventKey m_key; - CEventMouse m_mouse; - CEventSize m_size; - }; -}; - -#endif - diff --git a/CEventQueue.cpp b/CEventQueue.cpp deleted file mode 100644 index 0cef9742..00000000 --- a/CEventQueue.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "CEventQueue.h" - -// -// IEventQueue -// - -IEventQueue* IEventQueue::s_instance = NULL; - -IEventQueue::IEventQueue() -{ - assert(s_instance == NULL); - s_instance = this; -} - -IEventQueue::~IEventQueue() -{ - s_instance = NULL; -} - -IEventQueue* IEventQueue::getInstance() -{ - return s_instance; -} - - -// -// CEventQueue -// - -CEventQueue::CEventQueue() -{ - // do nothing -} - -CEventQueue::~CEventQueue() -{ - // do nothing -} - -void CEventQueue::pop(CEvent* event) -{ - assert(event != NULL); - - // wait for an event - while (isEmpty()) - wait(-1.0); - - // lock the queue, extract an event, then unlock - lock(); - *event = m_queue.front(); - m_queue.pop_front(); - unlock(); -} - -void CEventQueue::push(const CEvent* event) -{ - // put the event at the end of the queue and signal that the queue - // is not empty - lock(); - m_queue.push_back(*event); - signalNotEmpty(); - unlock(); -} - -bool CEventQueue::isEmpty() -{ - lock(); - bool e = m_queue.empty(); - unlock(); - - // if queue is empty then poll to see if more stuff is ready to go - // on the queue and check again if the queue is empty. - if (e) { - wait(0.0); - lock(); - e = m_queue.empty(); - unlock(); - } - return e; -} diff --git a/CEventQueue.h b/CEventQueue.h deleted file mode 100644 index de6a853d..00000000 --- a/CEventQueue.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef CEVENTQUEUE_H -#define CEVENTQUEUE_H - -#include "IEventQueue.h" -#include "CEvent.h" -#include - -class CEventQueue : public IEventQueue { - public: - CEventQueue(); - virtual ~CEventQueue(); - - // IEventQueue overrides - virtual void wait(double timeout) = 0; - virtual void pop(CEvent*); - virtual void push(const CEvent*); - virtual bool isEmpty(); - - protected: - // signal the queue not-empty condition. this should cause wait() - // to stop waiting. - virtual void signalNotEmpty() = 0; - - // lock the queue mutex - virtual void lock() = 0; - - // unlock the queue mutex - virtual void unlock() = 0; - - private: - typedef std::list List; - - List m_queue; -}; - -#endif diff --git a/CMessageSocket.cpp b/CMessageSocket.cpp deleted file mode 100644 index 97b0d516..00000000 --- a/CMessageSocket.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "CMessageSocket.h" -#include "TMethodJob.h" -#include "CTrace.h" -#include -#include - -// -// CMessageSocket -// - -CMessageSocket::CMessageSocket(ISocket* socket) : - m_socket(socket), - m_buffer(NULL), - m_size(0), - m_capacity(0), - m_msgSize(0) -{ - m_socket->setReadJob(new TMethodJob(this, - &CMessageSocket::readJobCB)); -} - -CMessageSocket::~CMessageSocket() -{ - delete m_socket; - delete[] m_buffer; -} - -void CMessageSocket::setWriteJob(IJob* adoptedJob) -{ - CSocket::setWriteJob(adoptedJob); - if (adoptedJob != NULL) - m_socket->setWriteJob(new TMethodJob(this, - &CMessageSocket::writeJobCB)); - else - m_socket->setWriteJob(NULL); -} - -void CMessageSocket::connect(const CString&, UInt16) -{ - assert(0 && "connect() illegal on CMessageSocket"); -} - -void CMessageSocket::listen(const CString&, UInt16) -{ - assert(0 && "listen() illegal on CMessageSocket"); -} - -ISocket* CMessageSocket::accept() -{ - assert(0 && "accept() illegal on CMessageSocket"); - return NULL; -} - -SInt32 CMessageSocket::read(void* buffer, SInt32 n) -{ - // if we don't have an entire message yet then read more data - if (m_size == 0 || m_size < m_msgSize) { - doRead(); - } - - // if we don't have a whole message yet then return 0 - if (m_size < m_msgSize) - return 0; - - // how many bytes should we return? - if (m_msgSize - 2 < n) - n = m_msgSize - 2; - - // copy data - // FIXME -- should have method for retrieving size of next message - ::memcpy(buffer, m_buffer + 2, n); - - // discard returned message - ::memmove(m_buffer, m_buffer + m_msgSize, m_size - m_msgSize); - m_size -= m_msgSize; - m_msgSize = 0; - - // get next message size - if (m_size >= 2) { - m_msgSize = static_cast( - (static_cast(m_buffer[0]) << 8) + - (static_cast(m_buffer[1]) )); - TRACE((" next message size: %d", m_msgSize)); - } - - return n; -} - -void CMessageSocket::write(const void* buffer, SInt32 n) -{ - // FIXME -- no fixed size buffers - char tmp[512]; - assert(n < (SInt32)sizeof(tmp) - 2); - ::memcpy(tmp + 2, buffer, n); - n += 2; - tmp[0] = static_cast((n >> 8) & 0xff); - tmp[1] = static_cast(n & 0xff); - m_socket->write(tmp, n); -} - -SInt32 CMessageSocket::doRead() -{ - // if read buffer is full then grow it - if (m_size == m_capacity) { - // compute new capacity and allocate space - SInt32 newCapacity = (m_capacity < 256) ? 256 : 2 * m_capacity; - UInt8* newBuffer = new UInt8[newCapacity]; - - // cut over - ::memcpy(newBuffer, m_buffer, m_size); - delete[] m_buffer; - m_buffer = newBuffer; - m_capacity = newCapacity; - } - - // read as much data as possible - const SInt32 numRead = m_socket->read(m_buffer + m_size, - m_capacity - m_size); - TRACE(("socket %p read %d bytes", this, numRead)); - - // hangup is a special case. if buffer isn't empty then we'll - // discard the partial message. - if (numRead == -1) - return numRead; - - // get next message size - if (m_size < 2 && m_size + numRead >= 2) { - m_msgSize = static_cast( - (static_cast(m_buffer[0]) << 8) + - (static_cast(m_buffer[1]) )); - TRACE((" next message size: %d", m_msgSize)); - } - - m_size += numRead; - return numRead; -} - -void CMessageSocket::readJobCB() -{ - if (doRead() == -1) { - // remote side hungup. don't check for readability anymore. - m_socket->setReadJob(NULL); - } - else if (m_size > 0 && m_size >= m_msgSize) { - TRACE((" message ready")); - runReadJob(); - } -} - -void CMessageSocket::writeJobCB() -{ - runWriteJob(); -} diff --git a/CMessageSocket.h b/CMessageSocket.h deleted file mode 100644 index b199c3ec..00000000 --- a/CMessageSocket.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef CMESSAGESOCKET_H -#define CMESSAGESOCKET_H - -#include "CSocket.h" - -class CMessageSocket : public CSocket { - public: - CMessageSocket(ISocket* adoptedSocket); - virtual ~CMessageSocket(); - - // ISocket overrides - // connect(), listen(), and accept() may not be called. - virtual void setWriteJob(IJob* adoptedJob); - virtual void connect(const CString& hostname, UInt16 port); - virtual void listen(const CString& hostname, UInt16 port); - virtual ISocket* accept(); - virtual SInt32 read(void* buffer, SInt32 numBytes); - virtual void write(const void* buffer, SInt32 numBytes); - - private: - SInt32 doRead(); - virtual void readJobCB(); - virtual void writeJobCB(); - - private: - ISocket* m_socket; - UInt8* m_buffer; - SInt32 m_size; - SInt32 m_capacity; - SInt32 m_msgSize; -}; - -#endif diff --git a/CProtocol.h b/CProtocol.h deleted file mode 100644 index 83659fd4..00000000 --- a/CProtocol.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef CPROTOCOL_H -#define CPROTOCOL_H - -#include "BasicTypes.h" - -class CProtocol { - public: - CProtocol(); - virtual ~CProtocol(); - - // manipulators - - - // accessors - - void ReadMessage(ISocket*, CMessage&) const; - void WriteMessage(ISocket*, const CMessage&) const; -}; - -#endif diff --git a/CScreenProxy.cpp b/CScreenProxy.cpp deleted file mode 100644 index 62cf9258..00000000 --- a/CScreenProxy.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#include "CScreenProxy.h" -#include "ISocket.h" -#include "CMessageSocket.h" -#include "TMethodJob.h" -#include "CTrace.h" -// -// CScreenProxy -// - -CScreenProxy::CScreenProxy(const CString& name, ISocket* socket) : - m_name(name), - m_socket(socket), - m_w(0), m_h(0) -{ - assert(!m_name.empty()); - assert(m_socket != NULL); - - m_socket = new CMessageSocket(m_socket); - m_socket->setReadJob(new TMethodJob(this, - &CScreenProxy::onRead)); -} - -CScreenProxy::~CScreenProxy() -{ - delete m_socket; -} - -void CScreenProxy::open(bool isPrimary) -{ - char buf[2]; - memcpy(buf, "\002", 1); - buf[1] = static_cast(isPrimary ? 1 : 0); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::close() -{ - char buf[1]; - memcpy(buf, "\003", 1); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::enterScreen(SInt32 x, SInt32 y) -{ - char buf[9]; - memcpy(buf, "\004", 1); - buf[1] = static_cast((x >> 24) & 0xff); - buf[2] = static_cast((x >> 16) & 0xff); - buf[3] = static_cast((x >> 8) & 0xff); - buf[4] = static_cast(x & 0xff); - buf[5] = static_cast((y >> 24) & 0xff); - buf[6] = static_cast((y >> 16) & 0xff); - buf[7] = static_cast((y >> 8) & 0xff); - buf[8] = static_cast(y & 0xff); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::leaveScreen() -{ - char buf[1]; - memcpy(buf, "\005", 1); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::warpCursor(SInt32 x, SInt32 y) -{ - char buf[9]; - memcpy(buf, "\020", 1); - buf[1] = static_cast((x >> 24) & 0xff); - buf[2] = static_cast((x >> 16) & 0xff); - buf[3] = static_cast((x >> 8) & 0xff); - buf[4] = static_cast(x & 0xff); - buf[5] = static_cast((y >> 24) & 0xff); - buf[6] = static_cast((y >> 16) & 0xff); - buf[7] = static_cast((y >> 8) & 0xff); - buf[8] = static_cast(y & 0xff); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::setClipboard(const IClipboard*) -{ - // FIXME -} - -void CScreenProxy::onKeyDown(KeyID k, KeyModifierMask m) -{ - char buf[9]; - memcpy(buf, "\007", 1); - buf[1] = static_cast((k >> 24) & 0xff); - buf[2] = static_cast((k >> 16) & 0xff); - buf[3] = static_cast((k >> 8) & 0xff); - buf[4] = static_cast(k & 0xff); - buf[5] = static_cast((m >> 24) & 0xff); - buf[6] = static_cast((m >> 16) & 0xff); - buf[7] = static_cast((m >> 8) & 0xff); - buf[8] = static_cast(m & 0xff); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::onKeyRepeat( - KeyID k, KeyModifierMask m, SInt32 n) -{ - char buf[13]; - memcpy(buf, "\010", 1); - buf[1] = static_cast((k >> 24) & 0xff); - buf[2] = static_cast((k >> 16) & 0xff); - buf[3] = static_cast((k >> 8) & 0xff); - buf[4] = static_cast(k & 0xff); - buf[5] = static_cast((m >> 24) & 0xff); - buf[6] = static_cast((m >> 16) & 0xff); - buf[7] = static_cast((m >> 8) & 0xff); - buf[8] = static_cast(m & 0xff); - buf[9] = static_cast((n >> 24) & 0xff); - buf[10] = static_cast((n >> 16) & 0xff); - buf[11] = static_cast((n >> 8) & 0xff); - buf[12] = static_cast(n & 0xff); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::onKeyUp(KeyID k, KeyModifierMask m) -{ - char buf[9]; - memcpy(buf, "\011", 1); - buf[1] = static_cast((k >> 24) & 0xff); - buf[2] = static_cast((k >> 16) & 0xff); - buf[3] = static_cast((k >> 8) & 0xff); - buf[4] = static_cast(k & 0xff); - buf[5] = static_cast((m >> 24) & 0xff); - buf[6] = static_cast((m >> 16) & 0xff); - buf[7] = static_cast((m >> 8) & 0xff); - buf[8] = static_cast(m & 0xff); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::onMouseDown(ButtonID b) -{ - char buf[2]; - memcpy(buf, "\013", 1); - buf[1] = static_cast(b & 0xff); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::onMouseUp(ButtonID b) -{ - char buf[2]; - memcpy(buf, "\014", 1); - buf[1] = static_cast(b & 0xff); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::onMouseMove(SInt32 x, SInt32 y) -{ - char buf[9]; - memcpy(buf, "\015", 1); - buf[1] = static_cast((x >> 24) & 0xff); - buf[2] = static_cast((x >> 16) & 0xff); - buf[3] = static_cast((x >> 8) & 0xff); - buf[4] = static_cast(x & 0xff); - buf[5] = static_cast((y >> 24) & 0xff); - buf[6] = static_cast((y >> 16) & 0xff); - buf[7] = static_cast((y >> 8) & 0xff); - buf[8] = static_cast(y & 0xff); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::onMouseWheel(SInt32 n) -{ - char buf[5]; - memcpy(buf, "\016", 1); - buf[1] = static_cast((n >> 24) & 0xff); - buf[2] = static_cast((n >> 16) & 0xff); - buf[3] = static_cast((n >> 8) & 0xff); - buf[4] = static_cast(n & 0xff); - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::onScreenSaver(bool show) -{ - char buf[2]; - memcpy(buf, "\017", 1); - buf[1] = show ? 1 : 0; - m_socket->write(buf, sizeof(buf)); -} - -void CScreenProxy::onClipboardChanged() -{ - // FIXME -} - -CString CScreenProxy::getName() const -{ - return m_name; -} - -void CScreenProxy::getSize(SInt32* w, SInt32* h) const -{ - assert(w != NULL); - assert(h != NULL); - - *w = m_w; - *h = m_h; -} - -void CScreenProxy::getClipboard(IClipboard*) const -{ - // FIXME -} - -void CScreenProxy::onRead() -{ - char buf[512]; - SInt32 n = m_socket->read(buf, sizeof(buf)); - if (n == -1) { - // FIXME -- disconnect - TRACE(("hangup")); - } - else if (n > 0) { - switch (buf[0]) { - case '\201': - m_w = static_cast( - (static_cast(buf[1]) << 24) + - (static_cast(buf[2]) << 16) + - (static_cast(buf[3]) << 8) + - (static_cast(buf[4]) )); - m_h = static_cast( - (static_cast(buf[5]) << 24) + - (static_cast(buf[6]) << 16) + - (static_cast(buf[7]) << 8) + - (static_cast(buf[8]) )); - TRACE(("new size: %dx%d", m_w, m_h)); - break; - - default: - TRACE(("unknown message: 0x%02x, %d bytes", buf[0], n)); - break; - } - } -} diff --git a/CScreenProxy.h b/CScreenProxy.h deleted file mode 100644 index bbcb7a1d..00000000 --- a/CScreenProxy.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef CSCREENPROXY_H -#define CSCREENPROXY_H - -#include "IScreen.h" - -class ISocket; - -class CScreenProxy : public IScreen { - public: - CScreenProxy(const CString& name, ISocket*); - virtual ~CScreenProxy(); - - // IScreen overrides - virtual void open(bool); - virtual void close(); - virtual void enterScreen(SInt32 xAbsolute, SInt32 yAbsolute); - virtual void leaveScreen(); - virtual void warpCursor(SInt32 xAbsolute, SInt32 yAbsolute); - virtual void setClipboard(const IClipboard*); - virtual void onScreenSaver(bool show); - virtual void onKeyDown(KeyID, KeyModifierMask); - virtual void onKeyRepeat(KeyID, KeyModifierMask, SInt32 count); - virtual void onKeyUp(KeyID, KeyModifierMask); - virtual void onMouseDown(ButtonID); - virtual void onMouseUp(ButtonID); - virtual void onMouseMove(SInt32 xAbsolute, SInt32 yAbsolute); - virtual void onMouseWheel(SInt32 delta); - virtual void onClipboardChanged(); - virtual CString getName() const; - virtual void getSize(SInt32* width, SInt32* height) const; - virtual void getClipboard(IClipboard*) const; - - private: - void onRead(); - - private: - CString m_name; - ISocket* m_socket; - SInt32 m_w, m_h; -}; - -#endif diff --git a/CServer.cpp b/CServer.cpp deleted file mode 100644 index 80bf026c..00000000 --- a/CServer.cpp +++ /dev/null @@ -1,812 +0,0 @@ -#include "CServer.h" -#include "CEvent.h" -#include "IEventQueue.h" -#include "IScreen.h" -#include "CScreenProxy.h" -#include "ISocket.h" -#include "CSocketFactory.h" -#include "CMessageSocket.h" -#include "TMethodJob.h" -#include "CTrace.h" -#include -#include -#include - -#if !defined(NDEBUG) -static const char* s_dirName[] = { "left", "right", "top", "bottom" }; -#endif - -// -// CServerSocketJob -// - -class CServerSocketJob : public IJob { - public: - typedef void (CServer::*ServerMethod)(ISocket*); - - CServerSocketJob(CServer*, ServerMethod, ISocket*); - virtual ~CServerSocketJob(); - - // IJob overrides - virtual void run(); - - private: - CServer* m_server; - ServerMethod m_method; - ISocket* m_socket; -}; - -CServerSocketJob::CServerSocketJob(CServer* server, - ServerMethod method, ISocket* socket) : - m_server(server), - m_method(method), - m_socket(socket) -{ - // do nothing -} - -CServerSocketJob::~CServerSocketJob() -{ - // do nothing -} - -void CServerSocketJob::run() -{ - (m_server->*m_method)(m_socket); -} - - -// -// CServer -// - -class XServerScreenExists { // FIXME - public: - XServerScreenExists(const CString&) { } -}; - -// the width/height of the zone on the edge of the local screen that -// will provoke a switch to a neighboring screen. this generally -// shouldn't be changed because it effectively reduces the size of -// the local screen's screen. -// FIXME -- should get this from the local screen itself. it may -// need a slightly larger zone (to avoid virtual screens) or it may -// be able to generate off-screen coordinates to provoke the switch -// in which case the size can be zero. -const SInt32 CServer::s_zoneSize = 1; - -CServer::CServer() : m_running(false), m_done(false), - m_localScreen(NULL), - m_activeScreen(NULL), - m_listenHost(), - // FIXME -- define kDefaultPort - m_listenPort(40001/*CProtocol::kDefaultPort*/), - m_listenSocket(NULL) -{ - // FIXME -} - -CServer::~CServer() -{ - assert(m_listenSocket == NULL); - - // FIXME -} - -void CServer::setListenPort( - const CString& hostname, UInt16 port) -{ - m_listenHost = hostname; - m_listenPort = port; -} - -void CServer::addLocalScreen(IScreen* screen) -{ - assert(screen != NULL); - assert(m_running == false); - assert(m_localScreen == NULL); - - addScreen(screen->getName(), screen); - m_localScreen = screen; - m_activeScreen = screen; - - // open the screen as primary - screen->open(true); -} - -void CServer::addRemoteScreen(const CString& name) -{ - addScreen(name, NULL); -} - -void CServer::addScreen(const CString& name, IScreen* screen) -{ - assert(!name.empty()); - - // cannot add a screen multiple times - if (m_map.count(name) != 0) - throw XServerScreenExists(name); - - // add entry for screen in the map - ScreenCell& cell = m_map[name]; - - // set the cell's screen - cell.m_screen = screen; -} - -void CServer::removeScreen(const CString& name) -{ - // screen must in map - assert(!name.empty()); - assert(m_map.count(name) == 1); - - // look up cell - ScreenCell& cell = m_map[name]; - - // if this is the local screen then there must not be any other - // screens and we must not be running. - assert(cell.m_screen != m_localScreen || (m_map.size() == 1 && !m_running)); - - // if this is the active screen then warp to the local screen, or - // set no active screen if this is the local screen. - if (cell.m_screen == m_localScreen) { - m_activeScreen = NULL; - m_localScreen = NULL; - } - else if (cell.m_screen == m_activeScreen) { - setActiveScreen(m_localScreen); - } - - // close the screen - if (cell.m_screen) - cell.m_screen->close(); - - // fix up map - if (!cell.m_neighbor[kLeft].empty()) { - assert(m_map.count(cell.m_neighbor[kLeft]) == 1); - m_map[cell.m_neighbor[kLeft]].m_neighbor[kRight] = - cell.m_neighbor[kRight]; - } - if (!cell.m_neighbor[kRight].empty()) { - assert(m_map.count(cell.m_neighbor[kRight]) == 1); - m_map[cell.m_neighbor[kRight]].m_neighbor[kLeft] = - cell.m_neighbor[kLeft]; - } - if (!cell.m_neighbor[kTop].empty()) { - assert(m_map.count(cell.m_neighbor[kTop]) == 1); - m_map[cell.m_neighbor[kTop]].m_neighbor[kBottom] = - cell.m_neighbor[kBottom]; - } - if (!cell.m_neighbor[kBottom].empty()) { - assert(m_map.count(cell.m_neighbor[kBottom]) == 1); - m_map[cell.m_neighbor[kBottom]].m_neighbor[kTop] = - cell.m_neighbor[kTop]; - } -} - -void CServer::connectEdge( - const CString& src, EDirection srcSide, - const CString& dst) -{ - // check input - assert(!src.empty()); - assert(!dst.empty()); - assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); - - // both screens must exist in map - assert(m_map.count(src) == 1); - assert(m_map.count(dst) == 1); - - // look up map entry - ScreenCell& cell = m_map[src]; - - // set edge - cell.m_neighbor[srcSide] = dst; - - TRACE(("connect %s:%s to %s", src.c_str(), - s_dirName[srcSide], - cell.m_neighbor[srcSide].c_str())); -} - -void CServer::disconnectEdge( - const CString& src, EDirection srcSide) -{ - // check input - assert(!src.empty()); - assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); - assert(m_map.count(src) == 1); - - TRACE(("disconnect %s:%s from %s", src.c_str(), - s_dirName[srcSide], - m_map[src].m_neighbor[srcSide].c_str())); - - // look up map entry - ScreenCell& cell = m_map[src]; - - // set edge - cell.m_neighbor[srcSide] = CString(); -} - -void CServer::run() -{ - assert(m_running == false); - assert(m_activeScreen != NULL); - assert(m_activeScreen == m_localScreen); - - // prepare socket to listen for remote screens - // FIXME -- need m_socketFactory (creates sockets of desired type) -// m_listenSocket = m_socketFactory->createSocket(); -m_listenSocket = CSOCKETFACTORY->create(); - m_listenSocket->setReadJob(new TMethodJob(this, - &CServer::newConnectionCB)); - // FIXME -- keep retrying until this works (in case of FIN_WAIT). - // also, must clean up m_listenSocket if this method throws anywhere. - m_listenSocket->listen(m_listenHost, m_listenPort); - - // now running - m_running = true; - - // event loop - IEventQueue* queue = CEQ; - while (!m_done) { - // wait for new connections, network messages, and user events - queue->wait(-1.0); - - // handle events - while (!queue->isEmpty()) { - // get the next event - CEvent event; - queue->pop(&event); - - // handle it - switch (event.m_any.m_type) { - case CEventBase::kNull: - // do nothing - break; - - case CEventBase::kKeyDown: - case CEventBase::kKeyRepeat: - case CEventBase::kKeyUp: - if (!onCommandKey(&event.m_key)) - relayEvent(&event); - break; - - case CEventBase::kMouseDown: - case CEventBase::kMouseUp: - case CEventBase::kMouseWheel: - relayEvent(&event); - break; - - case CEventBase::kMouseMove: - if (m_localScreen == m_activeScreen) - onLocalMouseMove(event.m_mouse.m_x, event.m_mouse.m_y); - else - onRemoteMouseMove(event.m_mouse.m_x, event.m_mouse.m_y); - break; - - case CEventBase::kScreenSize: - // FIXME - break; - } - } - } - - // reset - m_running = false; - m_done = false; - - // tell screens to shutdown - // FIXME - - // close our socket - delete m_listenSocket; - m_listenSocket = NULL; -} - -void CServer::onClipboardChanged(IScreen*) -{ - // FIXME -- should take screen name not screen pointer - // FIXME -} - -void CServer::setActiveScreen(IScreen* screen) -{ - // FIXME -- should take screen name not screen pointer - assert(screen != NULL); - assert(m_map.count(screen->getName()) == 1); - - // ignore if no change - if (m_activeScreen == screen) - return; - - // get center of screen - SInt32 w, h; - screen->getSize(&w, &h); - w >>= 1; - h >>= 1; - - // switch - switchScreen(screen, w, h); -} - -IScreen* CServer::getActiveScreen() const -{ - return m_activeScreen; -} - -void CServer::relayEvent(const CEvent* event) -{ - assert(event != NULL); - assert(m_activeScreen != NULL); - - // ignore attempts to relay to the local screen - if (m_activeScreen == m_localScreen) - return; - - // relay the event - switch (event->m_any.m_type) { - case CEventBase::kNull: - // do nothing - break; - - case CEventBase::kKeyDown: - m_activeScreen->onKeyDown(event->m_key.m_key, event->m_key.m_mask); - break; - - case CEventBase::kKeyRepeat: - m_activeScreen->onKeyRepeat(event->m_key.m_key, - event->m_key.m_mask, event->m_key.m_count); - break; - - case CEventBase::kKeyUp: - m_activeScreen->onKeyUp(event->m_key.m_key, event->m_key.m_mask); - break; - - case CEventBase::kMouseDown: - m_activeScreen->onMouseDown(event->m_mouse.m_button); - break; - - case CEventBase::kMouseUp: - m_activeScreen->onMouseUp(event->m_mouse.m_button); - break; - - case CEventBase::kMouseWheel: - m_activeScreen->onMouseWheel(event->m_mouse.m_x); - break; - - case CEventBase::kMouseMove: - assert(0 && "kMouseMove relayed"); - break; - - default: - assert(0 && "invalid event relayed"); - break; - } -} - -bool CServer::onCommandKey(const CEventKey* /*keyEvent*/) -{ - // FIXME -- strip out command keys (e.g. lock to screen, warp, etc.) - return false; -} - -void CServer::onLocalMouseMove(SInt32 x, SInt32 y) -{ - assert(m_activeScreen == m_localScreen); - - // ignore if locked to screen - if (isLockedToScreen()) - return; - - // get local screen's size - SInt32 w, h; - m_activeScreen->getSize(&w, &h); - - // see if we should change screens - EDirection dir; - if (x < s_zoneSize) { - x -= s_zoneSize; - dir = kLeft; - } - else if (x >= w - s_zoneSize) { - x += s_zoneSize; - dir = kRight; - } - else if (y < s_zoneSize) { - y -= s_zoneSize; - dir = kTop; - } - else if (y >= h - s_zoneSize) { - y += s_zoneSize; - dir = kBottom; - } - else { - // still on local screen - return; - } - TRACE(("leave %s on %s", m_activeScreen->getName().c_str(), s_dirName[dir])); - - // get new screen. if no screen in that direction then ignore move. - IScreen* newScreen = getNeighbor(m_activeScreen, dir, x, y); - if (newScreen == NULL) - return; - - // remap position to account for resolution differences between screens - mapPosition(m_activeScreen, dir, newScreen, x, y); - - // switch screen - switchScreen(newScreen, x, y); -} - -void CServer::onRemoteMouseMove(SInt32 dx, SInt32 dy) -{ - assert(m_activeScreen != NULL); - assert(m_activeScreen != m_localScreen); - - // put mouse back in center of local screen's grab area -// XXX m_localScreen->warpToCenter(); - - // save old position - const SInt32 xOld = m_x; - const SInt32 yOld = m_y; - - // accumulate mouse position - m_x += dx; - m_y += dy; - - // get active screen's size - SInt32 w, h; - m_activeScreen->getSize(&w, &h); - - // switch screens if mouse is outside screen and not locked to screen - IScreen* newScreen = NULL; - if (!isLockedToScreen()) { - // find direction of neighbor - EDirection dir; - if (m_x < 0) - dir = kLeft; - else if (m_x > w - 1) - dir = kRight; - else if (m_y < 0) - dir = kTop; - else if (m_y > h - 1) - dir = kBottom; - else - newScreen = m_activeScreen; - - // get neighbor if we should switch - if (newScreen == NULL) { - TRACE(("leave %s on %s", m_activeScreen->getName().c_str(), - s_dirName[dir])); - - SInt32 x = m_x, y = m_y; - newScreen = getNeighbor(m_activeScreen, dir, x, y); - - // remap position to account for resolution differences - if (newScreen != NULL) { - m_x = x; - m_y = y; - mapPosition(m_activeScreen, dir, newScreen, m_x, m_y); - } - else { - if (m_x < 0) - m_x = 0; - else if (m_x > w - 1) - m_x = w - 1; - if (m_y < 0) - m_y = 0; - else if (m_y > h - 1) - m_y = h - 1; - } - } - } - - // clamp mouse position if locked to screen - else { - TRACE(("clamp to %s", m_activeScreen->getName().c_str())); - - if (m_x < 0) - m_x = 0; - else if (m_x > w - 1) - m_x = w - 1; - if (m_y < 0) - m_y = 0; - else if (m_y > h - 1) - m_y = h - 1; - } - - // if on same screen then warp cursor - if (newScreen == NULL || newScreen == m_activeScreen) { - // ignore if clamped mouse didn't move - if (m_x != xOld || m_y != yOld) { - TRACE(("move on %s to %d,%d", - m_activeScreen->getName().c_str(), m_x, m_y)); - m_activeScreen->onMouseMove(m_x, m_y); - } - } - - // otherwise switch the screen - else { - switchScreen(newScreen, m_x, m_y); - } -} - -bool CServer::isLockedToScreen() const -{ - // FIXME - return false; -} - -void CServer::mapPosition( - const IScreen* src, EDirection srcSide, - const IScreen* dst, SInt32& x, SInt32& y) const -{ - assert(src != NULL); - assert(dst != NULL); - assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); - - // get sizes - SInt32 wSrc, hSrc, wDst, hDst; - src->getSize(&wSrc, &hSrc); - dst->getSize(&wDst, &hDst); - - // remap - switch (srcSide) { - case kLeft: - case kRight: - assert(y >= 0 && y < hSrc); - y = static_cast(0.5 + y * - static_cast(hDst - 1) / (hSrc - 1)); - break; - - case kTop: - case kBottom: - assert(x >= 0 && x < wSrc); - x = static_cast(0.5 + x * - static_cast(wSrc - 1) / (wSrc - 1)); - break; - } -} - -IScreen* CServer::getNeighbor( - const IScreen* src, EDirection dir) const -{ - // check input - assert(src != NULL); - assert(dir >= kFirstDirection && dir <= kLastDirection); - assert(m_map.count(src->getName()) == 1); - - // look up source cell - ScreenMap::const_iterator index = m_map.find(src->getName()); - do { - // look up name of neighbor - const ScreenCell& cell = index->second; - const CString dstName(cell.m_neighbor[dir]); - - // if nothing in that direction then return NULL - if (dstName.empty()) - return NULL; - - // look up neighbor cell - assert(m_map.count(dstName) == 1); - index = m_map.find(dstName); - - // if no screen pointer then can't go to that neighbor so keep - // searching in the same direction. -#ifndef NDEBUG - if (index->second.m_screen == NULL) - TRACE(("skipping over unconnected screen %s", dstName.c_str())); -#endif - } while (index->second.m_screen == NULL); - - return index->second.m_screen; -} - -IScreen* CServer::getNeighbor( - const IScreen* src, EDirection srcSide, - SInt32& x, SInt32& y) const -{ - // given a position relative to src and which side of the screen we - // left, find the screen we should move onto and where. if the - // position is sufficiently far from src then we may cross multiple - // screens. - - // check input - assert(src != NULL); - assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); - - // get the first neighbor - IScreen* dst = getNeighbor(src, srcSide); - IScreen* lastGoodScreen = dst; - - // get the original screen's size (needed for kRight and kBottom) - SInt32 w, h; - src->getSize(&w, &h); - - // find destination screen, adjusting x or y (but not both) - switch (srcSide) { - case kLeft: - while (dst) { - lastGoodScreen = dst; - lastGoodScreen->getSize(&w, &h); - x += w; - if (x >= 0) - break; - TRACE(("skipping over screen %s", dst->getName().c_str())); - dst = getNeighbor(lastGoodScreen, srcSide); - } - break; - - case kRight: - while (dst) { - lastGoodScreen = dst; - x -= w; - lastGoodScreen->getSize(&w, &h); - if (x < w) - break; - TRACE(("skipping over screen %s", dst->getName().c_str())); - dst = getNeighbor(lastGoodScreen, srcSide); - } - break; - - case kTop: - while (dst) { - lastGoodScreen = dst; - lastGoodScreen->getSize(&w, &h); - y += h; - if (y >= 0) - break; - TRACE(("skipping over screen %s", dst->getName().c_str())); - dst = getNeighbor(lastGoodScreen, srcSide); - } - break; - - case kBottom: - while (dst) { - lastGoodScreen = dst; - y -= h; - lastGoodScreen->getSize(&w, &h); - if (y < h) - break; - TRACE(("skipping over screen %s", dst->getName().c_str())); - dst = getNeighbor(lastGoodScreen, srcSide); - } - break; - } - - // if entering local screen then be sure to move in far enough to - // avoid the switching zone. if entering a side that doesn't have - // a neighbor (i.e. an asymmetrical side) then we don't need to - // move inwards because that side can't provoke a switch. - if (lastGoodScreen == m_localScreen) { - ScreenMap::const_iterator index = m_map.find(m_localScreen->getName()); - const ScreenCell& cell = index->second; - switch (srcSide) { - case kLeft: - if (!cell.m_neighbor[kRight].empty() && x > w - 1 - s_zoneSize) - x = w - 1 - s_zoneSize; - break; - - case kRight: - if (!cell.m_neighbor[kLeft].empty() && x < s_zoneSize) - x = s_zoneSize; - break; - - case kTop: - if (!cell.m_neighbor[kBottom].empty() && y > h - 1 - s_zoneSize) - y = h - 1 - s_zoneSize; - break; - - case kBottom: - if (!cell.m_neighbor[kTop].empty() && y < s_zoneSize) - y = s_zoneSize; - break; - } - } - - return lastGoodScreen; -} - -void CServer::switchScreen( - IScreen* screen, SInt32 x, SInt32 y) -{ - assert(screen != NULL); - assert(m_running == true); - assert(m_activeScreen != NULL); -#ifndef NDEBUG - { - SInt32 w, h; - screen->getSize(&w, &h); - assert(x >= 0 && y >= 0 && x < w && y < h); - } -#endif - - TRACE(("switch %s to %s at %d,%d", m_activeScreen->getName().c_str(), - screen->getName().c_str(), x, y)); - - // wrapping means leaving the active screen and entering it again. - // since that's a waste of time we skip that and just warp the - // mouse. - if (m_activeScreen != screen) { - // leave active screen - m_activeScreen->leaveScreen(); - - // cut over - m_activeScreen = screen; - - // enter new screen - m_activeScreen->enterScreen(x, y); - } - else { - m_activeScreen->warpCursor(x, y); - } - - // record new position - m_x = x; - m_y = y; -} - -void CServer::newConnectionCB() -{ - ISocket* socket = m_listenSocket->accept(); - TRACE(("accepted socket %p", socket)); - socket->setReadJob(new CServerSocketJob(this, &CServer::loginCB, socket)); - m_logins.insert(socket); -} - -void CServer::loginCB(ISocket* socket) -{ - // FIXME -- no fixed size buffers - UInt8 buffer[512]; - SInt32 n = socket->read(buffer, sizeof(buffer)); - if (n == -1) { - TRACE(("socket %p disconnected", socket)); - goto fail; - } - TRACE(("read %d bytes from socket %p", n, socket)); - if (n <= 10) { - TRACE(("socket %p: bogus %d byte message; hanging up", socket, n)); - goto fail; - } - if (n > 10) { - if (::memcmp(buffer, "SYNERGY\000\001", 9) != 0) { - TRACE(("socket %p: bad login", socket)); - goto fail; - } - - const SInt32 nameLen = static_cast(buffer[9]); - if (nameLen < 1 || nameLen > 64) { - TRACE(("socket %p: bad login name length %d", socket, nameLen)); - goto fail; - } - - for (SInt32 i = 0; i < nameLen; ++i) - if (!isalnum(buffer[10 + i])) { - TRACE(("socket %p: bad login name", socket)); - goto fail; - } - - CString name(reinterpret_cast(buffer + 10), nameLen); - const ScreenMap::iterator index = m_map.find(name); - if (index == m_map.end()) { - TRACE(("socket %p: unknown screen %s", socket, name.c_str())); - goto fail; - } - if (index->second.m_screen != NULL) { - TRACE(("socket %p: screen %s already connected", - socket, name.c_str())); - goto fail; - } - - TRACE(("socket %p: login %s", socket, name.c_str())); - CScreenProxy* screen = new CScreenProxy(name, socket); - m_logins.erase(socket); - index->second.m_screen = screen; - index->second.m_screen->open(false); - } - return; - -fail: - m_logins.erase(socket); - delete socket; -} diff --git a/CServer.h b/CServer.h deleted file mode 100644 index b95fa7ef..00000000 --- a/CServer.h +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef CSERVER_H -#define CSERVER_H - -#include "IServer.h" -#include "BasicTypes.h" -#include "CString.h" -#include -#include - -class CEvent; -class CEventKey; -class IScreen; -class ISocket; - -class CServer : public IServer { - public: - enum EDirection { kLeft, kRight, kTop, kBottom, - kFirstDirection = kLeft, kLastDirection = kBottom }; - - CServer(); - virtual ~CServer(); - - // manipulators - - // set the server's interface and port to listen for remote screens - void setListenPort(const CString& hostname, UInt16 port); - - // add local screen - void addLocalScreen(IScreen*); - - // add a remote screen - void addRemoteScreen(const CString& name); - - // remove a local or remote screen. neighbors on opposite sides - // of this screen are made neighbors of each other. - void removeScreen(const CString& name); - - // connect/disconnect screen edges - void connectEdge(const CString& src, EDirection srcSide, - const CString& dst); - void disconnectEdge(const CString& src, EDirection srcSide); - - // accessors - - - // IServer overrides - virtual void run(); - virtual void onClipboardChanged(IScreen*); - virtual void setActiveScreen(IScreen*); - virtual IScreen* getActiveScreen() const; - - protected: - virtual void relayEvent(const CEvent* event); - virtual bool onCommandKey(const CEventKey* keyEvent); - virtual void onLocalMouseMove(SInt32 x, SInt32 y); - virtual void onRemoteMouseMove(SInt32 dx, SInt32 dy); - virtual bool isLockedToScreen() const; - virtual void mapPosition(const IScreen* src, EDirection srcSide, - const IScreen* dst, SInt32& x, SInt32& y) const; - IScreen* getNeighbor(const IScreen* src, EDirection) const; - IScreen* getNeighbor(const IScreen* src, EDirection srcSide, - SInt32& x, SInt32& y) const; - void switchScreen(IScreen* screen, SInt32 x, SInt32 y); - - private: - void addScreen(const CString&, IScreen*); - void newConnectionCB(); - void loginCB(ISocket*); - - struct ScreenCell { - public: - ScreenCell() : m_screen(NULL) { } - public: - IScreen* m_screen; - CString m_neighbor[kLastDirection - kFirstDirection + 1]; - }; - - private: - typedef std::map ScreenMap; - typedef std::set SocketSet; - - // main loop stuff - bool m_running; - bool m_done; - - // screen tracking - IScreen* m_localScreen; - IScreen* m_activeScreen; - SInt32 m_x, m_y; - ScreenMap m_map; - - // listen socket stuff - CString m_listenHost; - UInt16 m_listenPort; - ISocket* m_listenSocket; - - // login sockets - SocketSet m_logins; - - static const SInt32 s_zoneSize; -}; - -#endif diff --git a/CSocket.cpp b/CSocket.cpp deleted file mode 100644 index 311d87d0..00000000 --- a/CSocket.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "CSocket.h" -#include "IJob.h" - -// -// CSocket -// - -CSocket::CSocket() : m_readJob(NULL), m_writeJob(NULL) -{ - // do nothing -} - -CSocket::~CSocket() -{ - delete m_readJob; - delete m_writeJob; -} - -void CSocket::setReadJob(IJob* adoptedJob) -{ - delete m_readJob; - m_readJob = adoptedJob; - onJobChanged(); -} - -void CSocket::setWriteJob(IJob* adoptedJob) -{ - delete m_writeJob; - m_writeJob = adoptedJob; - onJobChanged(); -} - -void CSocket::onJobChanged() -{ - // do nothing -} - -void CSocket::runReadJob() -{ - if (m_readJob) - m_readJob->run(); -} - -void CSocket::runWriteJob() -{ - if (m_writeJob) - m_writeJob->run(); -} - -bool CSocket::hasReadJob() const -{ - return (m_readJob != NULL); -} - -bool CSocket::hasWriteJob() const -{ - return (m_writeJob != NULL); -} diff --git a/CSocket.h b/CSocket.h deleted file mode 100644 index aa4cf9f8..00000000 --- a/CSocket.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef CSOCKET_H -#define CSOCKET_H - -#include "ISocket.h" - -class IJob; - -class CSocket : public ISocket { - public: - CSocket(); - virtual ~CSocket(); - - // ISocket overrides - virtual void setReadJob(IJob* adoptedJob); - virtual void setWriteJob(IJob* adoptedJob); - virtual void connect(const CString& hostname, UInt16 port) = 0; - virtual void listen(const CString& hostname, UInt16 port) = 0; - virtual ISocket* accept() = 0; - virtual SInt32 read(void* buffer, SInt32 numBytes) = 0; - virtual void write(const void* buffer, SInt32 numBytes) = 0; - - protected: - // called when the read or write job is changed. default does nothing. - virtual void onJobChanged(); - - // subclasses should call these at the appropriate time. different - // platforms will arrange to detect these situations differently. - // does nothing if the respective job is NULL. - void runReadJob(); - void runWriteJob(); - - // return true iff the socket has a read job or a write job - bool hasReadJob() const; - bool hasWriteJob() const; - - private: - IJob* m_readJob; - IJob* m_writeJob; -}; - -#endif diff --git a/CSocketFactory.cpp b/CSocketFactory.cpp deleted file mode 100644 index 0995b897..00000000 --- a/CSocketFactory.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "CSocketFactory.h" -#include "BasicTypes.h" -#include - -// -// CSocketFactory -// - -CSocketFactory* CSocketFactory::s_instance = NULL; - -CSocketFactory::CSocketFactory() -{ - // do nothing -} - -CSocketFactory::~CSocketFactory() -{ - // do nothing -} - -void CSocketFactory::setInstance(CSocketFactory* factory) -{ - delete s_instance; - s_instance = factory; -} - -CSocketFactory* CSocketFactory::getInstance() -{ - assert(s_instance != NULL); - return s_instance; -} diff --git a/CSocketFactory.h b/CSocketFactory.h deleted file mode 100644 index 7a73d391..00000000 --- a/CSocketFactory.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef CSOCKETFACTORY_H -#define CSOCKETFACTORY_H - -#define CSOCKETFACTORY CSocketFactory::getInstance() - -class ISocket; - -class CSocketFactory { - public: - CSocketFactory(); - virtual ~CSocketFactory(); - - // manipulators - - static void setInstance(CSocketFactory*); - - // accessors - - // create a socket - virtual ISocket* create() const = 0; - - // get the global instance - static CSocketFactory* getInstance(); - - private: - static CSocketFactory* s_instance; -}; - -#endif diff --git a/CString.h b/CString.h deleted file mode 100644 index 9b5667ac..00000000 --- a/CString.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CSTRING_H -#define CSTRING_H - -#include - -#ifndef CSTRING_DEF_CTOR -#define CSTRING_ALLOC1 -#define CSTRING_ALLOC2 -#define CSTRING_DEF_CTOR CString() : _Myt() { } -#endif - -// use to get appropriate type for string constants. it depends on -// the internal representation type of CString. -#define _CS(_x) _x - -class CString : public std::string { - public: - typedef char _E; - typedef _E CharT; - typedef std::allocator<_E> _A; - typedef std::string _Myt; - typedef const_iterator _It; - - // same constructors as base class - CSTRING_DEF_CTOR - CString(const _Myt& _X) : _Myt(_X) { } - CString(const _Myt& _X, size_type _P, size_type _M CSTRING_ALLOC1) : - _Myt(_X, _P, _M CSTRING_ALLOC2) { } - CString(const _E *_S, size_type _N CSTRING_ALLOC1) : - _Myt(_S, _N CSTRING_ALLOC2) { } - CString(const _E *_S CSTRING_ALLOC1) : - _Myt(_S CSTRING_ALLOC2) { } - CString(size_type _N, _E _C CSTRING_ALLOC1) : - _Myt(_N, _C CSTRING_ALLOC2) { } - CString(_It _F, _It _L CSTRING_ALLOC1) : - _Myt(_F, _L CSTRING_ALLOC2) { } -}; - -#endif - diff --git a/CTrace.cpp b/CTrace.cpp deleted file mode 100644 index e782616a..00000000 --- a/CTrace.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "CTrace.h" -#include -#include - -// -// CTrace -// - -void CTrace::print(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - fprintf(stderr, "\n"); -} diff --git a/CTrace.h b/CTrace.h deleted file mode 100644 index 34787ac9..00000000 --- a/CTrace.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef CTRACE_H -#define CTRACE_H - -class CTrace { - public: - static void print(const char* fmt, ...); -}; - -#if defined(NDEBUG) - -#define TRACE(_X) - -#else // NDEBUG - -#define TRACE(_X) CTrace::print ## _X - -#endif // NDEBUG - -#endif diff --git a/CUnixEventQueue.cpp b/CUnixEventQueue.cpp deleted file mode 100644 index db685dca..00000000 --- a/CUnixEventQueue.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "CUnixEventQueue.h" -#include "IJob.h" -#include -#include -#include - -// -// CUnixEventQueue -// - -CUnixEventQueue::CUnixEventQueue() -{ - // do nothing -} - -CUnixEventQueue::~CUnixEventQueue() -{ - // clean up lists - clearList(m_readList); - clearList(m_writeList); -} - -void CUnixEventQueue::addFileDesc(int fd, - IJob* readJob, IJob* writeJob) -{ - assert(fd != -1); - assert(m_readList.count(fd) == 0 && m_writeList.count(fd) == 0); - assert(readJob != writeJob || readJob == NULL); - - if (readJob) - m_readList[fd] = readJob; - if (writeJob) - m_writeList[fd] = writeJob; -} - -void CUnixEventQueue::removeFileDesc(int fd) -{ - assert(fd != -1); - - // remove from lists - eraseList(m_readList, fd); - eraseList(m_writeList, fd); -} - -void CUnixEventQueue::wait(double timeout) -{ - // prepare sets - fd_set fdRead, fdWrite; - const int maxRead = prepList(m_readList, &fdRead); - const int maxWrite = prepList(m_writeList, &fdWrite); - - // compute the larger of maxRead and maxWrite - const int fdMax = (maxRead > maxWrite) ? maxRead : maxWrite; - if (fdMax == -1) - return; - - // prepare timeout - struct timeval* pTimeout = NULL; - struct timeval sTimeout; - if (timeout >= 0.0) { - sTimeout.tv_sec = static_cast(timeout); - sTimeout.tv_usec = static_cast(1000000.0 * - (timeout - sTimeout.tv_sec)); - pTimeout = &sTimeout; - } - - // wait - const int n = ::select(fdMax + 1, &fdRead, &fdWrite, NULL, pTimeout); - - // return on error or if nothing to do - if (n <= 0) - return; - - // invoke jobs - // note -- calling removeFileDesc() from a job is likely to crash the - // program because we expect all jobs with active file descriptors to - // persist for the duration of these loops. - int fd; - for (fd = 0; fd <= maxRead; ++fd) - if (FD_ISSET(fd, &fdRead)) { - assert(m_readList.count(fd) > 0); - assert(m_readList[fd] != NULL); - m_readList[fd]->run(); - } - for (fd = 0; fd <= maxWrite; ++fd) - if (FD_ISSET(fd, &fdWrite)) { - assert(m_writeList.count(fd) > 0); - assert(m_writeList[fd] != NULL); - m_writeList[fd]->run(); - } -} - -void CUnixEventQueue::lock() -{ - // do nothing -} - -void CUnixEventQueue::unlock() -{ - // do nothing -} - -void CUnixEventQueue::signalNotEmpty() -{ - // do nothing -} - -void CUnixEventQueue::eraseList(List& list, int fd) const -{ - List::iterator index = list.find(fd); - if (index != list.end()) { - delete index->second; - list.erase(index); - } -} - -void CUnixEventQueue::clearList(List& list) const -{ - for (List::const_iterator index = list.begin(); - index != list.end(); ++index) - delete index->second; - list.clear(); -} - -int CUnixEventQueue::prepList( - const List& list, void* vfdSet) const -{ - fd_set* fdSet = reinterpret_cast(vfdSet); - FD_ZERO(fdSet); - - int fdMax = -1; - for (List::const_iterator index = list.begin(); - index != list.end(); ++index) { - const int fd = index->first; - FD_SET(fd, fdSet); - if (fd > fdMax) - fdMax = fd; - } - - return fdMax; -} diff --git a/CUnixEventQueue.h b/CUnixEventQueue.h deleted file mode 100644 index 217efbf4..00000000 --- a/CUnixEventQueue.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef CUNIXEVENTQUEUE_H -#define CUNIXEVENTQUEUE_H - -#include "CEventQueue.h" -#include - -#undef CEQ -#define CEQ ((CUnixEventQueue*)CEventQueue::getInstance()) - -class IJob; - -class CUnixEventQueue : public CEventQueue { - public: - CUnixEventQueue(); - virtual ~CUnixEventQueue(); - - // manipulators - - // add a file descriptor to wait on. if adoptedReadJob is not NULL - // then it'll be called when the file descriptor is readable. if - // adoptedWriteJob is not NULL then it will be called then the file - // descriptor is writable. at least one job must not be NULL and - // the jobs may not be the same. ownership of the jobs is assumed. - // the file descriptor must not have already been added or, if it - // was, it must have been removed. - void addFileDesc(int fd, - IJob* adoptedReadJob, IJob* adoptedWriteJob); - - // remove a file descriptor from the list being waited on. the - // associated jobs are destroyed. the file descriptor must have - // been added and not since removed. - void removeFileDesc(int fd); - - // IEventQueue overrides - virtual void wait(double timeout); - - protected: - // CEventQueue overrides - virtual void lock(); - virtual void unlock(); - virtual void signalNotEmpty(); - - private: - typedef std::map List; - void eraseList(List&, int fd) const; - void clearList(List&) const; - int prepList(const List&, void* fdSet) const; - - private: - List m_readList; - List m_writeList; -}; - -#endif diff --git a/CUnixTCPSocket.cpp b/CUnixTCPSocket.cpp deleted file mode 100644 index ed5d2202..00000000 --- a/CUnixTCPSocket.cpp +++ /dev/null @@ -1,278 +0,0 @@ -#include "CUnixTCPSocket.h" -#include "CUnixEventQueue.h" -#include "CString.h" -#include "TMethodJob.h" -#include "XSocket.h" -#include -#include -#include -#include -#include // FIXME -- for disabling nagle algorithm -#include -#include -#include -#include -#include -extern int h_errno; - -CUnixTCPSocket::CUnixTCPSocket() : m_fd(-1), - m_state(kNone), - m_addedJobs(false) -{ - // create socket - m_fd = ::socket(PF_INET, SOCK_STREAM, 0); - if (m_fd == -1) - throw XSocketCreate(::strerror(errno)); - - // make it non-blocking - int mode = ::fcntl(m_fd, F_GETFL, 0); - if (mode == -1 || ::fcntl(m_fd, F_SETFL, mode | O_NONBLOCK) == -1) { - ::close(m_fd); - throw XSocketCreate(::strerror(errno)); - } - - // always send immediately - setNoDelay(); -} - -CUnixTCPSocket::CUnixTCPSocket(int fd) : m_fd(fd), - m_state(kConnected), - m_addedJobs(false) -{ - assert(m_fd != -1); - setNoDelay(); -} - -CUnixTCPSocket::~CUnixTCPSocket() -{ - assert(m_fd != -1); - - // unhook events - if (m_addedJobs) - CEQ->removeFileDesc(m_fd); - - // drain socket - if (m_state == kConnected) - ::shutdown(m_fd, 0); - - // close socket - ::close(m_fd); -} - -void CUnixTCPSocket::onJobChanged() -{ - // remove old jobs - if (m_addedJobs) { - CEQ->removeFileDesc(m_fd); - m_addedJobs = false; - } - - // which jobs should we install? - bool doRead = false; - bool doWrite = false; - switch (m_state) { - case kNone: - return; - - case kConnecting: - doWrite = true; - break; - - case kConnected: - doRead = hasReadJob(); - doWrite = hasWriteJob(); - break; - - case kListening: - doRead = true; - break; - } - - // make jobs - IJob* readJob = doRead ? new TMethodJob(this, - &CUnixTCPSocket::readCB) : NULL; - IJob* writeJob = doWrite ? new TMethodJob(this, - &CUnixTCPSocket::writeCB) : NULL; - - // install jobs - CEQ->addFileDesc(m_fd, readJob, writeJob); - m_addedJobs = true; -} - -void CUnixTCPSocket::readCB() -{ - runReadJob(); -} - -void CUnixTCPSocket::writeCB() -{ - if (m_state == kConnecting) { - // now connected. start watching for reads. - m_state = kConnected; - onJobChanged(); - } - runWriteJob(); -} - -void CUnixTCPSocket::connect( - const CString& hostname, UInt16 port) -{ - assert(m_fd != -1); - assert(m_state == kNone); - - // hostname to address - struct hostent* hent = ::gethostbyname(hostname.c_str()); - if (hent == NULL) - throw XSocketName(::hstrerror(h_errno)); - - // construct address - struct sockaddr_in addr; - assert(hent->h_addrtype == AF_INET); - assert(hent->h_length == sizeof(addr.sin_addr)); - addr.sin_family = hent->h_addrtype; - addr.sin_port = htons(port); - ::memcpy(&addr.sin_addr, hent->h_addr_list[0], hent->h_length); - - // start connecting - if (::connect(m_fd, reinterpret_cast(&addr), - sizeof(addr)) == -1) { - if (errno != EINPROGRESS) - throw XSocketConnect(::strerror(errno)); - m_state = kConnecting; - } - else { - m_state = kConnected; - runWriteJob(); - } - onJobChanged(); -} - -void CUnixTCPSocket::listen( - const CString& hostname, UInt16 port) -{ - assert(m_fd != -1); - assert(m_state == kNone); - assert(port != 0); - - // construct address - struct sockaddr_in addr; - if (!hostname.empty()) { - // hostname to address - struct hostent* hent = ::gethostbyname(hostname.c_str()); - if (hent == NULL) - throw XSocketName(::hstrerror(h_errno)); - - // fill in address - assert(hent->h_addrtype == AF_INET); - assert(hent->h_length == sizeof(addr.sin_addr)); - ::memcpy(&addr.sin_addr, hent->h_addr_list[0], hent->h_length); - } - else { - // all addresses - addr.sin_addr.s_addr = htonl(INADDR_ANY); - } - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - - // bind to address - if (::bind(m_fd, reinterpret_cast(&addr), - sizeof(addr)) == -1) - throw XSocketListen(::strerror(errno)); - - // start listening - if (::listen(m_fd, 3) == -1) - throw XSocketListen(::strerror(errno)); - m_state = kListening; - onJobChanged(); -} - -ISocket* CUnixTCPSocket::accept() -{ - assert(m_fd != -1); - assert(m_state == kListening); - - for (;;) { - // wait for connection - fd_set fdset; - FD_ZERO(&fdset); - FD_SET(m_fd, &fdset); - ::select(m_fd + 1, &fdset, NULL, NULL, NULL); - - // accept connection - struct sockaddr addr; - socklen_t addrlen = sizeof(addr); - int fd = ::accept(m_fd, &addr, &addrlen); - if (fd == -1) - if (errno == EAGAIN) - continue; - else - throw XSocketAccept(::strerror(errno)); - - // return new socket object - return new CUnixTCPSocket(fd); - } -} - -SInt32 CUnixTCPSocket::read(void* buffer, SInt32 numBytes) -{ - assert(m_fd != -1); - assert(m_state == kConnected); - - const ssize_t n = ::read(m_fd, buffer, numBytes); - if (n == -1) { - // check for no data to read - if (errno == EAGAIN || errno == EINTR) - return 0; - - // error - return -1; - } - - // check for socket closed - if (n == 0) - return -1; - - // return num bytes read - return n; -} - -void CUnixTCPSocket::write( - const void* buffer, SInt32 numBytes) -{ - const char* ptr = static_cast(buffer); - - while (numBytes > 0) { - // write more data - const ssize_t n = ::write(m_fd, ptr, numBytes); - - // check for errors - if (n == -1) { - // wait if can't write data then try again - if (errno == EAGAIN || errno == EINTR) { - fd_set fdset; - FD_ZERO(&fdset); - FD_SET(m_fd, &fdset); - ::select(m_fd + 1, NULL, &fdset, NULL, NULL); - continue; - } - - // error - throw XSocketWrite(::strerror(errno)); - } - - // account for written data - ptr += n; - numBytes -= n; - } -} - -void CUnixTCPSocket::setNoDelay() -{ - // turn off Nagle algorithm. we send lots of really short messages - // so we'll accept the (much) larger overhead to reduce latency. - struct protoent* p = getprotobyname("tcp"); - if (p) { - int on = 1; - setsockopt(m_fd, p->p_proto, TCP_NODELAY, &on, sizeof(on)); - } -} diff --git a/CUnixTCPSocket.h b/CUnixTCPSocket.h deleted file mode 100644 index 0a04c5c4..00000000 --- a/CUnixTCPSocket.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef CUNIXTCPSOCKET_H -#define CUNIXTCPSOCKET_H - -#include "CSocket.h" -#include "CSocketFactory.h" - -class CUnixTCPSocket : public CSocket { - public: - CUnixTCPSocket(); - virtual ~CUnixTCPSocket(); - - // ISocket overrides - virtual void connect(const CString& hostname, UInt16 port); - virtual void listen(const CString& hostname, UInt16 port); - virtual ISocket* accept(); - virtual SInt32 read(void* buffer, SInt32 numBytes); - virtual void write(const void* buffer, SInt32 numBytes); - - protected: - // CSocket overrides - virtual void onJobChanged(); - - private: - CUnixTCPSocket(int); - - // disable Nagle algorithm - void setNoDelay(); - - // callbacks for read/write events - void readCB(); - void writeCB(); - - private: - enum EState { kNone, kConnecting, kConnected, kListening }; - int m_fd; - EState m_state; - bool m_addedJobs; -}; - -class CUnixTCPSocketFactory : public CSocketFactory { - public: - CUnixTCPSocketFactory() { } - virtual ~CUnixTCPSocketFactory() { } - - // CSocketFactory overrides - virtual ISocket* create() const - { return new CUnixTCPSocket; } -}; - -#endif diff --git a/CUnixXScreen.cpp b/CUnixXScreen.cpp deleted file mode 100644 index 752a774d..00000000 --- a/CUnixXScreen.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "CUnixXScreen.h" -#include "CUnixEventQueue.h" -#include "TMethodJob.h" -#include - -// -// CUnixXScreen -// - -CUnixXScreen::CUnixXScreen(const CString& name) : - CXScreen(name) -{ - // do nothing -} - -CUnixXScreen::~CUnixXScreen() -{ - // do nothing -} - -void CUnixXScreen::onOpen(bool) -{ - // register our X event handler - CEQ->addFileDesc(ConnectionNumber(getDisplay()), - new TMethodJob(this, - &CUnixXScreen::onEvents), NULL); - -} - -void CUnixXScreen::onClose() -{ - // unregister the X event handler - CEQ->removeFileDesc(ConnectionNumber(getDisplay())); -} diff --git a/CUnixXScreen.h b/CUnixXScreen.h deleted file mode 100644 index 459eed8c..00000000 --- a/CUnixXScreen.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef CUNIXXSCREEN_H -#define CUNIXXSCREEN_H - -#include "CXScreen.h" - -class CUnixXScreen : public CXScreen { - public: - CUnixXScreen(const CString& name); - virtual ~CUnixXScreen(); - - protected: - virtual void onOpen(bool isPrimary); - virtual void onClose(); -}; - -#endif diff --git a/CXScreen.cpp b/CXScreen.cpp deleted file mode 100644 index 049641c6..00000000 --- a/CXScreen.cpp +++ /dev/null @@ -1,626 +0,0 @@ -#include "CXScreen.h" -#include "CEvent.h" -#include "CEventQueue.h" -#include -#include -#include -#include - -// -// CXScreen -// -class XClientOpen { }; // FIXME - -CXScreen::CXScreen(const CString& name) : - m_name(name), - m_display(NULL), - m_primary(false), - m_w(0), m_h(0), - m_window(None), - m_active(false) -{ - // do nothing -} - -CXScreen::~CXScreen() -{ - assert(m_display == NULL); -} - -void CXScreen::open(bool isPrimary) -{ - assert(m_display == NULL); - - m_primary = isPrimary; - - bool opened = false; - try { - // open the display - m_display = ::XOpenDisplay(NULL); // FIXME -- allow non-default - if (m_display == NULL) - throw XClientOpen(); - - // hook up event handling - onOpen(m_primary); - opened = true; - - // get default screen - m_screen = DefaultScreen(m_display); - Screen* screen = ScreenOfDisplay(m_display, m_screen); - - // get screen size - m_w = WidthOfScreen(screen); - m_h = HeightOfScreen(screen); - - // type specific operations - if (m_primary) - openPrimary(); - else - openSecondary(); - } - catch (...) { - if (opened) - onClose(); - - if (m_display != NULL) { - ::XCloseDisplay(m_display); - m_display = NULL; - } - - throw; - } -} - -void CXScreen::close() -{ - assert(m_display != NULL); - - // type specific operations - if (m_primary) - closePrimary(); - else - closeSecondary(); - - // unhook event handling - onClose(); - - // close the display - ::XCloseDisplay(m_display); - m_display = NULL; -} - -void CXScreen::enterScreen(SInt32 x, SInt32 y) -{ - assert(m_display != NULL); - - if (m_primary) - enterScreenPrimary(x, y); - else - enterScreenSecondary(x, y); -} - -void CXScreen::leaveScreen() -{ - assert(m_display != NULL); - - if (m_primary) - leaveScreenPrimary(); - else - leaveScreenSecondary(); -} - -void CXScreen::warpCursor(SInt32 x, SInt32 y) -{ - assert(m_display != NULL); - - // warp the mouse - Window root = RootWindow(m_display, m_screen); - ::XWarpPointer(m_display, None, root, 0, 0, 0, 0, x, y); - ::XSync(m_display, False); - - // discard mouse events since we just added one we don't want - XEvent xevent; - while (::XCheckWindowEvent(m_display, m_window, - PointerMotionMask, &xevent)) - ; // do nothing -} - -void CXScreen::setClipboard( - const IClipboard* clipboard) -{ - assert(m_display != NULL); - - if (m_primary) - setClipboardPrimary(clipboard); - else - setClipboardSecondary(clipboard); -} - -void CXScreen::onScreenSaver(bool show) -{ - assert(m_display != NULL); - - if (m_primary) - onScreenSaverPrimary(show); - else - onScreenSaverSecondary(show); -} - -void CXScreen::onKeyDown(KeyID key, KeyModifierMask) -{ - assert(m_display != NULL); - assert(m_primary == false); - - // FIXME -- use mask - ::XTestFakeKeyEvent(m_display, mapKeyToX(key), True, CurrentTime); - ::XSync(m_display, False); -} - -void CXScreen::onKeyRepeat(KeyID, KeyModifierMask, SInt32) -{ - assert(m_display != NULL); - assert(m_primary == false); - - // FIXME -} - -void CXScreen::onKeyUp(KeyID key, KeyModifierMask) -{ - assert(m_display != NULL); - assert(m_primary == false); - - // FIXME -- use mask - ::XTestFakeKeyEvent(m_display, mapKeyToX(key), False, CurrentTime); - ::XSync(m_display, False); -} - -void CXScreen::onMouseDown(ButtonID button) -{ - assert(m_display != NULL); - assert(m_primary == false); - - ::XTestFakeButtonEvent(m_display, mapButtonToX(button), True, CurrentTime); - ::XSync(m_display, False); -} - -void CXScreen::onMouseUp(ButtonID button) -{ - assert(m_display != NULL); - assert(m_primary == false); - - ::XTestFakeButtonEvent(m_display, mapButtonToX(button), False, CurrentTime); - ::XSync(m_display, False); -} - -void CXScreen::onMouseMove(SInt32 x, SInt32 y) -{ - assert(m_display != NULL); - assert(m_primary == false); - - ::XTestFakeMotionEvent(m_display, m_screen, x, y, CurrentTime); - ::XSync(m_display, False); -} - -void CXScreen::onMouseWheel(SInt32) -{ - assert(m_display != NULL); - assert(m_primary == false); - - // FIXME -} - -void CXScreen::onClipboardChanged() -{ - assert(m_display != NULL); - assert(m_primary == false); - - // FIXME -} - -CString CXScreen::getName() const -{ - return m_name; -} - -void CXScreen::getSize( - SInt32* width, SInt32* height) const -{ - assert(m_display != NULL); - assert(width != NULL && height != NULL); - - *width = m_w; - *height = m_h; -} - -void CXScreen::getClipboard( - IClipboard* /*clipboard*/) const -{ - assert(m_display != NULL); - - // FIXME -} - -void CXScreen::openPrimary() -{ - // get the root window - Window root = RootWindow(m_display, m_screen); - - // create the grab window. this window is used to capture user - // input when the user is focussed on another client. don't let - // the window manager mess with it. - XSetWindowAttributes attr; - attr.event_mask = PointerMotionMask |// PointerMotionHintMask | - ButtonPressMask | ButtonReleaseMask | - KeyPressMask | KeyReleaseMask | - KeymapStateMask; - attr.do_not_propagate_mask = 0; - attr.override_redirect = True; - attr.cursor = None; - m_window = ::XCreateWindow(m_display, root, 0, 0, m_w, m_h, 0, 0, - InputOnly, CopyFromParent, - CWDontPropagate | CWEventMask | - CWOverrideRedirect | CWCursor, - &attr); - - // start watching for events on other windows - selectEvents(root); -} - -void CXScreen::closePrimary() -{ - assert(m_window != None); - - // destroy window - ::XDestroyWindow(m_display, m_window); - m_window = None; -} - -void CXScreen::enterScreenPrimary(SInt32 x, SInt32 y) -{ - assert(m_window != None); - assert(m_active == true); - - // warp to requested location - ::XWarpPointer(m_display, None, m_window, 0, 0, 0, 0, x, y); - - // unmap the grab window. this also ungrabs the mouse and keyboard. - ::XUnmapWindow(m_display, m_window); - - // remove all input events for grab window - XEvent event; - while (::XCheckWindowEvent(m_display, m_window, - PointerMotionMask | - ButtonPressMask | ButtonReleaseMask | - KeyPressMask | KeyReleaseMask | - KeymapStateMask, - &event)) - ; // do nothing - - // not active anymore - m_active = false; -} - -void CXScreen::leaveScreenPrimary() -{ - assert(m_window != None); - assert(m_active == false); - - // raise and show the input window - ::XMapRaised(m_display, m_window); - - // grab the mouse and keyboard. keep trying until we get them. - // if we can't grab one after grabbing the other then ungrab - // and wait before retrying. - int result; - do { - // mouse first - do { - result = ::XGrabPointer(m_display, m_window, True, 0, - GrabModeAsync, GrabModeAsync, - m_window, None, CurrentTime); - assert(result != GrabNotViewable); - if (result != GrabSuccess) - ::sleep(1); - } while (result != GrabSuccess); - - // now the keyboard - result = ::XGrabKeyboard(m_display, m_window, True, - GrabModeAsync, GrabModeAsync, CurrentTime); - assert(result != GrabNotViewable); - if (result != GrabSuccess) { - ::XUngrabPointer(m_display, CurrentTime); - ::sleep(1); - } - } while (result != GrabSuccess); - - // move the mouse to the center of grab window - warpCursor(m_w >> 1, m_h >> 1); - - // local client now active - m_active = true; -} - -void CXScreen::setClipboardPrimary( - const IClipboard* /*clipboard*/) -{ - // FIXME -} - -void CXScreen::onScreenSaverPrimary(bool /*show*/) -{ - // FIXME -} - -void CXScreen::openSecondary() -{ - // verify the availability of the XTest extension - int majorOpcode, firstEvent, firstError; - if (!::XQueryExtension(m_display, XTestExtensionName, - &majorOpcode, &firstEvent, &firstError)) - throw XClientOpen(); - - // become impervious to server grabs - XTestGrabControl(m_display, True); -} - -void CXScreen::closeSecondary() -{ - // no longer impervious to server grabs - XTestGrabControl(m_display, False); -} - -void CXScreen::enterScreenSecondary( - SInt32 x, SInt32 y) -{ - // FIXME -} - -void CXScreen::leaveScreenSecondary() -{ - // FIXME -} - -void CXScreen::setClipboardSecondary( - const IClipboard* /*clipboard*/) -{ - // FIXME -} - -void CXScreen::onScreenSaverSecondary(bool /*show*/) -{ - // FIXME -} - -Display* CXScreen::getDisplay() const -{ - return m_display; -} - -void CXScreen::onEvents() -{ - if (m_primary) - onPrimaryEvents(); - else - onSecondaryEvents(); -} - -void CXScreen::selectEvents(Window w) const -{ - // we want to track the mouse everywhere on the display. to achieve - // that we select PointerMotionMask on every window. we also select - // SubstructureNotifyMask in order to get CreateNotify events so we - // select events on new windows too. - - // we don't want to adjust our grab window - if (w == m_window) - return; - - // select events of interest - ::XSelectInput(m_display, w, PointerMotionMask | SubstructureNotifyMask); - - // recurse on child windows - Window rw, pw, *cw; - unsigned int nc; - if (::XQueryTree(m_display, w, &rw, &pw, &cw, &nc)) { - for (unsigned int i = 0; i < nc; ++i) - selectEvents(cw[i]); - ::XFree(cw); - } -} - -KeyModifierMask CXScreen::mapModifierFromX(unsigned int state) const -{ - // FIXME -- should be configurable - KeyModifierMask mask = 0; - if (state & 1) - mask |= KeyModifierShift; - if (state & 2) - mask |= KeyModifierCapsLock; - if (state & 4) - mask |= KeyModifierControl; - if (state & 8) - mask |= KeyModifierAlt; - if (state & 16) - mask |= KeyModifierNumLock; - if (state & 32) - mask |= KeyModifierMeta; - if (state & 128) - mask |= KeyModifierScrollLock; - return mask; -} - -unsigned int CXScreen::mapModifierToX(KeyModifierMask mask) const -{ - // FIXME -- should be configurable - unsigned int state = 0; - if (mask & KeyModifierShift) - state |= 1; - if (mask & KeyModifierControl) - state |= 4; - if (mask & KeyModifierAlt) - state |= 8; - if (mask & KeyModifierMeta) - state |= 32; - if (mask & KeyModifierCapsLock) - state |= 2; - if (mask & KeyModifierNumLock) - state |= 16; - if (mask & KeyModifierScrollLock) - state |= 128; - return state; -} - -KeyID CXScreen::mapKeyFromX( - KeyCode keycode, KeyModifierMask mask) const -{ - int index; - if (mask & KeyModifierShift) - index = 1; - else - index = 0; - return static_cast(::XKeycodeToKeysym(m_display, keycode, index)); -} - -KeyCode CXScreen::mapKeyToX(KeyID keyID) const -{ - return ::XKeysymToKeycode(m_display, static_cast(keyID)); -} - -ButtonID CXScreen::mapButtonFromX(unsigned int button) const -{ - // FIXME -- should use button mapping? - if (button >= 1 && button <= 3) - return static_cast(button); - else - return kButtonNone; -} - -unsigned int CXScreen::mapButtonToX(ButtonID buttonID) const -{ - // FIXME -- should use button mapping? - return static_cast(buttonID); -} - -void CXScreen::onPrimaryEvents() -{ - while (XPending(m_display) > 0) { - XEvent xevent; - XNextEvent(m_display, &xevent); - - switch (xevent.type) { - case KeyPress: { - const KeyModifierMask mask = mapModifierFromX(xevent.xkey.state); - const KeyID key = mapKeyFromX(xevent.xkey.keycode, mask); - if (key != kKeyNone) { - CEvent event; - event.m_key.m_type = CEventBase::kKeyDown; - event.m_key.m_key = key; - event.m_key.m_mask = mask; - event.m_key.m_count = 0; - CEQ->push(&event); - } - break; - } - - // FIXME -- simulate key repeat. X sends press/release for - // repeat. must detect auto repeat and use kKeyRepeat. - case KeyRelease: { - const KeyModifierMask mask = mapModifierFromX(xevent.xkey.state); - const KeyID key = mapKeyFromX(xevent.xkey.keycode, mask); - if (key != kKeyNone) { - CEvent event; - event.m_key.m_type = CEventBase::kKeyUp; - event.m_key.m_key = key; - event.m_key.m_mask = mask; - event.m_key.m_count = 0; - CEQ->push(&event); - } - break; - } - - case ButtonPress: { - const ButtonID button = mapButtonFromX(xevent.xbutton.button); - if (button != kButtonNone) { - CEvent event; - event.m_mouse.m_type = CEventBase::kMouseDown; - event.m_mouse.m_button = button; - event.m_mouse.m_x = 0; - event.m_mouse.m_y = 0; - CEQ->push(&event); - } - break; - } - - case ButtonRelease: { - const ButtonID button = mapButtonFromX(xevent.xbutton.button); - if (button != kButtonNone) { - CEvent event; - event.m_mouse.m_type = CEventBase::kMouseUp; - event.m_mouse.m_button = button; - event.m_mouse.m_x = 0; - event.m_mouse.m_y = 0; - CEQ->push(&event); - } - break; - } - - case MotionNotify: { - CEvent event; - event.m_mouse.m_type = CEventBase::kMouseMove; - event.m_mouse.m_button = kButtonNone; - if (!m_active) { - event.m_mouse.m_x = xevent.xmotion.x_root; - event.m_mouse.m_y = xevent.xmotion.y_root; - } - else { - // FIXME -- slurp up all remaining motion events? - // probably not since key strokes may go to wrong place. - - // get mouse deltas - Window root, window; - int xRoot, yRoot, xWindow, yWindow; - unsigned int mask; - if (!::XQueryPointer(m_display, m_window, &root, &window, - &xRoot, &yRoot, &xWindow, &yWindow, &mask)) - break; - event.m_mouse.m_x = xRoot - (m_w >> 1); - event.m_mouse.m_y = yRoot - (m_h >> 1); - - // warp mouse back to center - warpCursor(m_w >> 1, m_h >> 1); - } - CEQ->push(&event); - break; - } - - case CreateNotify: - // select events on new window - if (m_primary) - selectEvents(xevent.xcreatewindow.window); - break; - -/* - case SelectionClear: - target->XXX(xevent.xselectionclear.); - break; - - case SelectionNotify: - target->XXX(xevent.xselection.); - break; - - case SelectionRequest: - target->XXX(xevent.xselectionrequest.); - break; -*/ - } - } -} - -void CXScreen::onSecondaryEvents() -{ - while (XPending(m_display) > 0) { - XEvent xevent; - XNextEvent(m_display, &xevent); - // FIXME - } -} diff --git a/CXScreen.h b/CXScreen.h deleted file mode 100644 index 96a4ab28..00000000 --- a/CXScreen.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef CXSCREEN_H -#define CXSCREEN_H - -#include "IScreen.h" -#include - -class CXScreen : public IScreen { - public: - CXScreen(const CString& name); - virtual ~CXScreen(); - - // IScreen overrides - virtual void open(bool isPrimary); - virtual void close(); - virtual void enterScreen(SInt32 x, SInt32 y); - virtual void leaveScreen(); - virtual void warpCursor(SInt32 x, SInt32 y); - virtual void setClipboard(const IClipboard*); - virtual void onScreenSaver(bool); - virtual void onKeyDown(KeyID, KeyModifierMask); - virtual void onKeyRepeat(KeyID, KeyModifierMask, SInt32); - virtual void onKeyUp(KeyID, KeyModifierMask); - virtual void onMouseDown(ButtonID); - virtual void onMouseUp(ButtonID); - virtual void onMouseMove(SInt32, SInt32); - virtual void onMouseWheel(SInt32); - virtual void onClipboardChanged(); - virtual CString getName() const; - virtual void getSize(SInt32* width, SInt32* height) const; - virtual void getClipboard(IClipboard*) const; - - protected: - // primary screen implementations - virtual void openPrimary(); - virtual void closePrimary(); - virtual void enterScreenPrimary(SInt32 x, SInt32 y); - virtual void leaveScreenPrimary(); - virtual void setClipboardPrimary(const IClipboard*); - virtual void onScreenSaverPrimary(bool); - - // secondary screen implementations - virtual void openSecondary(); - virtual void closeSecondary(); - virtual void enterScreenSecondary(SInt32 x, SInt32 y); - virtual void leaveScreenSecondary(); - virtual void setClipboardSecondary(const IClipboard*); - virtual void onScreenSaverSecondary(bool); - - // get the display - Display* getDisplay() const; - - // process X events from the display - void onEvents(); - - // called by open() and close(). override to hook up and unhook the - // display connection to the event queue. call onEvents() when events - // are available. - virtual void onOpen(bool isPrimary) = 0; - virtual void onClose() = 0; - - private: - void selectEvents(Window) const; - KeyModifierMask mapModifierFromX(unsigned int) const; - unsigned int mapModifierToX(KeyModifierMask) const; - KeyID mapKeyFromX(KeyCode, KeyModifierMask) const; - KeyCode mapKeyToX(KeyID) const; - ButtonID mapButtonFromX(unsigned int button) const; - unsigned int mapButtonToX(ButtonID) const; - void onPrimaryEvents(); - void onSecondaryEvents(); - - private: - CString m_name; - Display* m_display; - int m_screen; - bool m_primary; - SInt32 m_w, m_h; - - // stuff for primary screens - Window m_window; - bool m_active; -}; - -#endif diff --git a/IClient.h b/IClient.h deleted file mode 100644 index 02dd7129..00000000 --- a/IClient.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef ICLIENT_H -#define ICLIENT_H - -class CString; - -class IClient { - public: - IClient() { } - virtual ~IClient() { } - - // manipulators - - // connect to server and begin processing events - virtual void run(const CString& hostname) = 0; -}; - -#endif diff --git a/IClipboard.h b/IClipboard.h deleted file mode 100644 index e69de29b..00000000 diff --git a/IEventQueue.h b/IEventQueue.h deleted file mode 100644 index 36609b0c..00000000 --- a/IEventQueue.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef IEVENTQUEUE_H -#define IEVENTQUEUE_H - -#define CEQ (IEventQueue::getInstance()) - -class CEvent; - -class IEventQueue { - public: - IEventQueue(); - virtual ~IEventQueue(); - - // note -- all of the methods in an IEventQueue subclass for a - // platform must be thread safe if it will be used by multiple - // threads simultaneously on that platform. - - // manipulators - - // wait up to timeout seconds for the queue to become not empty. - // as a side effect this can do the insertion of events. if - // timeout < 0.0 then wait indefinitely. it's possible for - // wait() to return prematurely so always call isEmpty() to - // see if there are any events. - virtual void wait(double timeout) = 0; - - // reads and removes the next event on the queue. waits indefinitely - // for an event if the queue is empty. - virtual void pop(CEvent*) = 0; - - // push an event onto the queue - virtual void push(const CEvent*) = 0; - - // returns true if the queue is empty and wait() would block - virtual bool isEmpty() = 0; - - // accessors - - // get the singleton event queue - static IEventQueue* getInstance(); - - private: - static IEventQueue* s_instance; -}; - -#endif diff --git a/IScreen.h b/IScreen.h deleted file mode 100644 index fd2a8346..00000000 --- a/IScreen.h +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef ISCREEN_H -#define ISCREEN_H - -/* - * IScreen -- interface for display screens - * - * a screen encapsulates input and output devices, typically a mouse - * and keyboard for input and a graphical display for output. one - * screen is designated as the primary screen. only input from the - * primary screen's input devices is used. other screens are secondary - * screens and they simulate input from their input devices but ignore - * any actual input. a screen can be either a primary or a secondary - * but not both at the same time. most methods behave differently - * depending on the screen type. - */ - -#include "BasicTypes.h" -#include "KeyTypes.h" -#include "MouseTypes.h" -#include "CString.h" - -class IClipboard; - -class IScreen { - public: - IScreen() { } - virtual ~IScreen() { } - - // manipulators - - // open/close screen. these are where the client should do - // initialization and cleanup of the system's screen. if isPrimary - // is true then this screen will be used (exclusively) as the - // primary screen, otherwise it will be used (exclusively) as a - // secondary screen. - // - // primary: - // open(): open the screen and begin reporting input events to - // the event queue. input events should be reported no matter - // where on the screen they occur but the screen should not - // interfere with the normal dispatching of events. the screen - // should detect when the screen saver is activated. if it can't - // do that it should disable the screen saver and start it itself - // after the appropriate duration of no input. - // - // secondary: - // open(): open the screen, hide the cursor and disable the - // screen saver. then wait for an enterScreen() or close(), - // reporting the following events: FIXME. - virtual void open(bool isPrimary) = 0; - virtual void close() = 0; - - // enter/leave screen - // - // primary: - // enterScreen(): the user has navigated back to the primary - // screen. warp the cursor to the given coordinates, unhide the - // cursor and ungrab the input devices. the screen must also - // detect and report (enqueue) input events. for the primary - // screen, enterScreen() is only called after a leaveScreen(). - // leaveScreen(): the user has navigated off the primary screen. - // hide the cursor and grab exclusive access to the input devices. - // input events must be reported. - // - // secondary: - // enterScreen(): the user has navigated to this secondary - // screen. warp the cursor to the given coordinates and show it. - // prepare to simulate input events. - // leaveScreen(): the user has navigated off this secondary - // screen. clean up input event simulation. hide the cursor. - virtual void enterScreen(SInt32 xAbsolute, SInt32 yAbsolute) = 0; - virtual void leaveScreen() = 0; - - // warp the cursor to the given position - virtual void warpCursor(SInt32 xAbsolute, SInt32 yAbsolute) = 0; - - // - // clipboard operations - // - - // set the screen's clipboard contents. this is usually called - // soon after an enterScreen(). - virtual void setClipboard(const IClipboard*) = 0; - - // - // screen saver operations - // - - // show or hide the screen saver - virtual void onScreenSaver(bool show) = 0; - - // - // input simulation - // - // these methods must simulate the appropriate input event. - // these methods are only called on secondary screens. - // - - // keyboard input - virtual void onKeyDown(KeyID, KeyModifierMask) = 0; - virtual void onKeyRepeat(KeyID, KeyModifierMask, SInt32 count) = 0; - virtual void onKeyUp(KeyID, KeyModifierMask) = 0; - - // mouse input - virtual void onMouseDown(ButtonID) = 0; - virtual void onMouseUp(ButtonID) = 0; - virtual void onMouseMove(SInt32 xAbsolute, SInt32 yAbsolute) = 0; - virtual void onMouseWheel(SInt32 delta) = 0; - - // clipboard input - // FIXME -- do we need this? - virtual void onClipboardChanged() = 0; - - // accessors - - // get the screen's name. all screens must have a name unique on - // the server they connect to. the hostname is usually an - // appropriate name. - virtual CString getName() const = 0; - - // get the size of the screen - virtual void getSize(SInt32* width, SInt32* height) const = 0; - - // clipboard operations - - // get the screen's clipboard contents - virtual void getClipboard(IClipboard*) const = 0; -}; - -#endif diff --git a/IServer.h b/IServer.h deleted file mode 100644 index 2a041d64..00000000 --- a/IServer.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ISERVER_H -#define ISERVER_H - -class IScreen; - -class IServer { - public: - IServer() { } - virtual ~IServer() { } - - // manipulators - - // run the server until terminated - virtual void run() = 0; - - // clipboard operations - virtual void onClipboardChanged(IScreen*) = 0; - - // enter the given screen, leaving the previous screen. the cursor - // should be warped to the center of the screen. - virtual void setActiveScreen(IScreen*) = 0; - - // accessors - - // get the screen that was last entered - virtual IScreen* getActiveScreen() const = 0; -}; - -#endif diff --git a/ISocket.h b/ISocket.h deleted file mode 100644 index 1a46f5d9..00000000 --- a/ISocket.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef ISOCKET_H -#define ISOCKET_H - -#include "BasicTypes.h" - -class IJob; -class CString; - -class ISocket { - public: - // d'tor closes the socket - ISocket() { } - virtual ~ISocket() { } - - // manipulators - - // set the job to invoke when the socket is readable or writable. - // a socket that has connected after a call to connect() becomes - // writable. a socket that is ready to accept a connection after - // a call to listen() becomes readable. the socket returned by - // accept() does not have any jobs assigned to it. - virtual void setReadJob(IJob* adoptedJob) = 0; - virtual void setWriteJob(IJob* adoptedJob) = 0; - - // open/close. connect() begins connecting to the given host but - // doesn't wait for the connection to complete. listen() begins - // listening on the given interface and port; if hostname is - // empty then listen on all interfaces. accept() waits for a - // connection on the listening interface and returns a new - // socket for the connection. - virtual void connect(const CString& hostname, UInt16 port) = 0; - virtual void listen(const CString& hostname, UInt16 port) = 0; - virtual ISocket* accept() = 0; - - // read data from socket. returns without waiting if not enough - // data is available. returns the number of bytes actually read, - // which is zero if there were no bytes to read and -1 if the - // remote end of the socket has disconnected. - virtual SInt32 read(void* buffer, SInt32 numBytes) = 0; - - // write data to socket. waits until all data has been written. - virtual void write(const void* buffer, SInt32 numBytes) = 0; -}; - -#endif diff --git a/Make-solaris b/Make-solaris new file mode 100644 index 00000000..4a78355d --- /dev/null +++ b/Make-solaris @@ -0,0 +1,67 @@ +# +# build tools +# +AR = /usr/ccs/bin/ar cru +CD = cd +CXX = g++ +ECHO = echo +LD = /usr/css/bin/ld +MKDIR = /bin/mkdir +RM = /bin/rm -f +RMR = /bin/rm -rf + +# +# compiler options +# +GCXXDEFS = +GCXXINCS = -I$(DEPTH)/include -I/usr/X11R6/include +GCXXOPTS = -Wall -W -fexceptions -fno-rtti +CXXOPTIMIZER = -g +#CXXOPTIMIZER = -O2 -DNDEBUG + +# +# linker options +# +#GLDLIBS = -L$(LIBDIR) -L/usr/X11R6/lib -lX11 -lXext -lXtst +GLDLIBS = -L$(LIBDIR) -lsocket -lnsl -lposix4 +GLDOPTS = -z muldefs + +# +# library stuff +# +LIBTARGET = $(LIBDIR)/lib$(TARGET).a + +# +# dependency generation stuff +# +MKDEP = $(DEPTH)/tools/depconv +MKDEPOPT = -MD +MKDEPPRE = +MKDEPPOST = $(MKDEP) -f $(MKDEPFILE) $*.d; $(RM) $*.d +MKDEPFILE = Makedepend + +# +# stuff to clean +# +DIRT = $(_FORCE) $(LDIRT) $(GDIRT) +GDIRT = *.[eoud] a.out core ar.tmp.* $(MKDEPFILE) + +# +# Rule macros for nonterminal makefiles that iterate over subdirectories, +# making the current target. Set SUBDIRS to the relevant list of kids. +# +# Set NOSUBMESG to any value to suppress a warning that subdirectories +# are not present. +# +SUBDIR_MAKERULE= \ + if test ! -d $$d; then \ + if test "$(NOSUBMESG)" = "" ; then \ + ${ECHO} "SKIPPING $$d: No such directory."; \ + fi \ + else \ + ${ECHO} "${CD} $$d; $(MAKE) $${RULE:=$@}"; \ + (${CD} $$d; ${MAKE} $${RULE:=$@}); \ + fi + +SUBDIRS_MAKERULE= \ + @for d in $(SUBDIRS); do $(SUBDIR_MAKERULE); done diff --git a/Makecommon b/Makecommon new file mode 100644 index 00000000..ad8c1558 --- /dev/null +++ b/Makecommon @@ -0,0 +1,93 @@ +# +# +# common definitions +# +# + +# +# empty define, used to terminate lists +# +NULL = + +# +# target directories +# +LIBDIR = $(DEPTH)/lib +TARGETDIR = $(DEPTH)/bin + +# +# compiler options +# +CXXFLAGS = $(LCXXFLAGS) $(GCXXFLAGS) $(CXXOPTIMIZER) $(MKDEPOPT) +LCXXFLAGS = $(LCXXDEFS) $(LCXXINCS) $(LCXXOPTS) +GCXXFLAGS = $(GCXXDEFS) $(GCXXINCS) $(GCXXOPTS) + +# +# linker options +# +LDFLAGS = $(LDOPTS) $(LDLIBS) +LDOPTS = $(LLDOPTS) $(GLDOPTS) +LDLIBS = $(LLDLIBS) $(GLDLIBS) + +# +# ar options +# +ARF = $(AR) + +# +# Convenience file list macros: +# +SOURCES = $(CXXFILES) +OBJECTS = $(CXXFILES:.cpp=.o) + +# +# always unsatisfied target +# +_FORCE = $(COMMONPREF)_force + +# +# +# common rules +# +# + +# +# default target. makefiles must define a target named `targets'. +# +$(COMMONPREF)default: targets + +# +# always unsatisfied target +# +$(_FORCE): + +# +# cleaners +# +COMMONTARGETS = clean clobber +$(COMMONPREF)clean: $(_FORCE) + $(RM) $(DIRT) + +$(COMMONPREF)clobber: clean $(_FORCE) + $(RM) $(TARGETS) + +# +# implicit target rules +# +.SUFFIXES: .cpp .o + +.cpp.o: + $(MKDEPPRE) + $(CXX) $(CXXFLAGS) -c $< + $(MKDEPPOST) + +# +# platform stuff +# +include $(DEPTH)/Make-linux +#include $(DEPTH)/Make-solaris + +# +# load dependencies +# +sinclude $(MKDEPFILE) diff --git a/TMethodJob.h b/TMethodJob.h deleted file mode 100644 index 341e3491..00000000 --- a/TMethodJob.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef TMETHODJOB_H -#define TMETHODJOB_H - -#include "IJob.h" - -template -class TMethodJob : public IJob { - public: - typedef void (T::*Method)(); - - TMethodJob(T* object, Method method) : - m_object(object), m_method(method) { } - virtual ~TMethodJob() { } - - // IJob overrides - virtual void run() { (m_object->*m_method)(); } - - private: - T* m_object; - Method m_method; -}; - -#endif diff --git a/XBase.cpp b/XBase.cpp deleted file mode 100644 index df1bd475..00000000 --- a/XBase.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "XBase.h" - -// win32 wants a const char* argument to std::exception c'tor -#if CONFIG_PLATFORM_WIN32 -#define STDEXCEPTARG "" -#endif - -// default to no argument -#ifndef STDEXCEPTARG -#define STDEXCEPTARG -#endif - -// -// XBase -// - -XBase::XBase() : exception(STDEXCEPTARG) -{ - // do nothing -} - -XBase::~XBase() -{ - // do nothing -} - -const char* XBase::what() const -{ - return getType(); -} - -const char* XBase::getType() const -{ - return "XBase.h"; -} - -CString XBase::format(const CString& fmt) const -{ - return fmt; -} diff --git a/XBase.h b/XBase.h deleted file mode 100644 index f2ed5162..00000000 --- a/XBase.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef XBASE_H -#define XBASE_H - -#include "CString.h" -#include - -class XBase : public std::exception { - public: - XBase(); - virtual ~XBase(); - - // accessors - - // return the name of the exception type - virtual const char* getType() const; - - // format and return formatString by replacing positional - // arguments (%1, %2, etc.). default returns formatString - // unchanged. subclasses should document what positional - // arguments they replace. - virtual CString format(const CString& formatString) const; - - // std::exception overrides - virtual const char* what() const; -}; - -#define XNAME(_n) \ - public: \ - virtual const char* getType() const { return #_n; } - -#endif diff --git a/XSocket.h b/XSocket.h deleted file mode 100644 index e8f775f2..00000000 --- a/XSocket.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef XSOCKET_H -#define XSOCKET_H - -#include "XBase.h" - -class XSocket : public XBase { - public: - // accessors - - const char* getMessage() const { return m_msg; } - - protected: - XSocket(const char* msg) : m_msg(msg) { } - - private: - const char* m_msg; -}; - -#define XSOCKETDEF(_n) \ -class _n : public XSocket { \ - public: \ - _n(const char* msg) : XSocket(msg) { } \ - XNAME(_n) \ -}; - -XSOCKETDEF(XSocketCreate) -XSOCKETDEF(XSocketName) -XSOCKETDEF(XSocketConnect) -XSOCKETDEF(XSocketListen) -XSOCKETDEF(XSocketAccept) -XSOCKETDEF(XSocketWrite) - -#endif diff --git a/base/BasicTypes.h b/base/BasicTypes.h new file mode 100644 index 00000000..1a88d3a0 --- /dev/null +++ b/base/BasicTypes.h @@ -0,0 +1,46 @@ +#ifndef BASICTYPES_H +#define BASICTYPES_H + +#include "common.h" + +#if defined(CONFIG_PLATFORM_LINUX) + +#include + +typedef int8_t SInt8; +typedef int16_t SInt16; +typedef int32_t SInt32; +typedef int64_t SInt64; + +typedef uint8_t UInt8; +typedef uint16_t UInt16; +typedef uint32_t UInt32; +typedef uint64_t UInt64; + +#endif // CONFIG_PLATFORM_LINUX + +#if defined(CONFIG_PLATFORM_SOLARIS) + +#include + +typedef int8_t SInt8; +typedef int16_t SInt16; +typedef int32_t SInt32; +typedef int64_t SInt64; + +typedef uint8_t UInt8; +typedef uint16_t UInt16; +typedef uint32_t UInt32; +typedef uint64_t UInt64; + +#endif // CONFIG_PLATFORM_SOLARIS + +#if defined(CONFIG_PLATFORM_WIN32) + +// FIXME + +#endif // CONFIG_PLATFORM_WIN32 + +#endif + + diff --git a/base/CFunctionJob.cpp b/base/CFunctionJob.cpp new file mode 100644 index 00000000..50a55100 --- /dev/null +++ b/base/CFunctionJob.cpp @@ -0,0 +1,19 @@ +#include "CFunctionJob.h" + +// +// CFunctionJob +// + +CFunctionJob::CFunctionJob(void (*func)(void*), void* arg) : + m_func(func), + m_arg(arg) +{ + // do nothing +} + +void CFunctionJob::run() +{ + if (m_func != NULL) { + m_func(m_arg); + } +} diff --git a/base/CFunctionJob.h b/base/CFunctionJob.h new file mode 100644 index 00000000..346d28e5 --- /dev/null +++ b/base/CFunctionJob.h @@ -0,0 +1,18 @@ +#ifndef CFUNCTIONJOB_H +#define CFUNCTIONJOB_H + +#include "IJob.h" + +class CFunctionJob : public IJob { + public: + CFunctionJob(void (*func)(void*), void* arg = NULL); + + // IJob overrides + virtual void run(); + + private: + void (*m_func)(void*); + void* m_arg; +}; + +#endif diff --git a/base/CStopwatch.cpp b/base/CStopwatch.cpp new file mode 100644 index 00000000..f7adac6b --- /dev/null +++ b/base/CStopwatch.cpp @@ -0,0 +1,180 @@ +#include "CStopwatch.h" + +// +// CStopwatch +// + +CStopwatch::CStopwatch(bool triggered) : + m_mark(0.0), + m_triggered(triggered), + m_stopped(triggered) +{ + if (!triggered) + m_mark = getClock(); +} + +CStopwatch::~CStopwatch() +{ + // do nothing +} + +double CStopwatch::reset() +{ + if (m_stopped) { + const double dt = m_mark; + m_mark = 0.0; + return dt; + } + else { + const double t = getClock(); + const double dt = t - m_mark; + m_mark = t; + return dt; + } +} + +void CStopwatch::stop() +{ + if (m_stopped) + return; + + // save the elapsed time + m_mark = getClock() - m_mark; + m_stopped = true; +} + +void CStopwatch::start() +{ + m_triggered = false; + if (!m_stopped) + return; + + // set the mark such that it reports the time elapsed at stop() + m_mark = getClock() - m_mark; + m_stopped = false; +} + +void CStopwatch::setTrigger() +{ + stop(); + m_triggered = true; +} + +double CStopwatch::getTime() +{ + if (m_triggered) { + const double dt = m_mark; + start(); + return dt; + } + if (m_stopped) + return m_mark; + return getClock() - m_mark; +} + +CStopwatch::operator double() +{ + return getTime(); +} + +bool CStopwatch::isStopped() const +{ + return m_stopped; +} + +double CStopwatch::getTime() const +{ + if (m_stopped) + return m_mark; + return getClock() - m_mark; +} + +CStopwatch::operator double() const +{ + return getTime(); +} + +#if defined(CONFIG_PLATFORM_UNIX) + +#include + +double CStopwatch::getClock() const +{ + struct timeval t; + gettimeofday(&t, NULL); + return (double)t.tv_sec + 1.0e-6 * (double)t.tv_usec; +} + +#endif // CONFIG_PLATFORM_UNIX + +#if defined(CONFIG_PLATFORM_WIN32) + +// avoid getting a lot a crap from mmsystem.h that we don't need +#define MMNODRV // Installable driver support +#define MMNOSOUND // Sound support +#define MMNOWAVE // Waveform support +#define MMNOMIDI // MIDI support +#define MMNOAUX // Auxiliary audio support +#define MMNOMIXER // Mixer support +#define MMNOJOY // Joystick support +#define MMNOMCI // MCI support +#define MMNOMMIO // Multimedia file I/O support +#define MMNOMMSYSTEM // General MMSYSTEM functions + +#include +#include + +typedef WINMMAPI DWORD (WINAPI *PTimeGetTime)(void); + +static double s_freq = 0.0; +static HINSTANCE s_mmInstance = NULL; +static PTimeGetTime s_tgt = NULL; + +// +// initialize local variables +// + +class CStopwatchInit { + public: + CStopwatchInit(); + ~CStopwatchInit(); +}; +static CStopwatchInit s_init; + +CStopwatchInit::CStopwatchInit() +{ + LARGE_INTEGER freq; + if (QueryPerformanceFrequency(&freq) && freq.QuadPart != 0) { + s_freq = 1.0 / static_cast(freq.QuadPart); + } + else { + // load winmm.dll and get timeGetTime + s_mmInstance = LoadLibrary("winmm"); + if (s_mmInstance) + s_tgt = (PTimeGetTime)GetProcAddress(s_mmInstance, "timeGetTime"); + } +} + +CStopwatchInit::~CStopwatchInit() +{ + if (s_mmInstance) + FreeLibrary(reinterpret_cast(s_mmInstance)); +} + +double CStopwatch::getClock() const +{ + // get time. we try three ways, in order of descending precision + if (s_freq != 0.0) { + LARGE_INTEGER c; + QueryPerformanceCounter(&c); + return s_freq * static_cast(c.QuadPart); + } + else if (s_tgt) { + return 0.001 * static_cast(s_tgt()); + } + else { + return 0.001 * static_cast(GetTickCount()); + } +} + +#endif // CONFIG_PLATFORM_WIN32 diff --git a/base/CStopwatch.h b/base/CStopwatch.h new file mode 100644 index 00000000..da0c1ab5 --- /dev/null +++ b/base/CStopwatch.h @@ -0,0 +1,57 @@ +#ifndef CSTOPWATCH_H +#define CSTOPWATCH_H + +#include "common.h" + +class CStopwatch { + public: + // the default constructor does an implicit reset() or setTrigger(). + // if triggered == false then the clock starts ticking. + CStopwatch(bool triggered = false); + ~CStopwatch(); + + // manipulators + + // set the start time to the current time, returning the time since + // the last reset. this does not remove the trigger if it's set nor + // does it start a stopped clock. if the clock is stopped then + // subsequent reset()'s will return 0. + double reset(); + + // stop and start the stopwatch. while stopped, no time elapses. + // stop() does not remove the trigger but start() does, even if + // the clock was already started. + void stop(); + void start(); + + // setTrigger() stops the clock like stop() except there's an + // implicit start() the next time (non-const) getTime() is called. + // this is useful when you want the clock to start the first time + // you check it. + void setTrigger(); + + // return the time since the last reset() (or call reset() and + // return zero if the trigger is set). + double getTime(); + operator double(); + + // accessors + + // returns true if the watch is stopped + bool isStopped() const; + + // return the time since the last reset(). these cannot trigger + // the clock to start so if the trigger is set it's as if it wasn't. + double getTime() const; + operator double() const; + + private: + double getClock() const; + + private: + double m_mark; + bool m_triggered; + bool m_stopped; +}; + +#endif diff --git a/base/CString.h b/base/CString.h new file mode 100644 index 00000000..88041939 --- /dev/null +++ b/base/CString.h @@ -0,0 +1,41 @@ +#ifndef CSTRING_H +#define CSTRING_H + +#include "common.h" +#include + +#ifndef CSTRING_DEF_CTOR +#define CSTRING_ALLOC1 +#define CSTRING_ALLOC2 +#define CSTRING_DEF_CTOR CString() : _Myt() { } +#endif + +// use to get appropriate type for string constants. it depends on +// the internal representation type of CString. +#define _CS(_x) _x + +class CString : public std::string { + public: + typedef char _e; + typedef _e CharT; + typedef std::allocator<_e> _a; + typedef std::string _Myt; + typedef const_iterator _It; + + // same constructors as base class + CSTRING_DEF_CTOR + CString(const _Myt& _x) : _Myt(_x) { } + CString(const _Myt& _x, size_type _p, size_type _m CSTRING_ALLOC1) : + _Myt(_x, _p, _m CSTRING_ALLOC2) { } + CString(const _e *_s, size_type _n CSTRING_ALLOC1) : + _Myt(_s, _n CSTRING_ALLOC2) { } + CString(const _e *_s CSTRING_ALLOC1) : + _Myt(_s CSTRING_ALLOC2) { } + CString(size_type _n, _e _c CSTRING_ALLOC1) : + _Myt(_n, _c CSTRING_ALLOC2) { } + CString(_It _f, _It _l CSTRING_ALLOC1) : + _Myt(_f, _l CSTRING_ALLOC2) { } +}; + +#endif + diff --git a/base/IInterface.h b/base/IInterface.h new file mode 100644 index 00000000..bb3676bc --- /dev/null +++ b/base/IInterface.h @@ -0,0 +1,11 @@ +#ifndef IINTERFACE_H +#define IINTERFACE_H + +#include "common.h" + +class IInterface { + public: + virtual ~IInterface() { } +}; + +#endif diff --git a/IJob.h b/base/IJob.h similarity index 55% rename from IJob.h rename to base/IJob.h index 381e29ad..3efb1b51 100644 --- a/IJob.h +++ b/base/IJob.h @@ -1,13 +1,10 @@ #ifndef IJOB_H #define IJOB_H -class IJob { +#include "IInterface.h" + +class IJob : public IInterface { public: - IJob() { } - virtual ~IJob() { } - - // manipulators - virtual void run() = 0; }; diff --git a/base/Makefile b/base/Makefile new file mode 100644 index 00000000..42128cec --- /dev/null +++ b/base/Makefile @@ -0,0 +1,24 @@ +DEPTH=.. +include $(DEPTH)/Makecommon + +# +# target file +# +TARGET = base + +# +# source files +# +LCXXINCS = \ + $(NULL) +CXXFILES = \ + XBase.cpp \ + CFunctionJob.cpp \ + CStopwatch.cpp \ + $(NULL) + +targets: $(LIBTARGET) + +$(LIBTARGET): $(OBJECTS) + if test ! -d $(LIBDIR); then $(MKDIR) $(LIBDIR); fi + $(ARF) $(LIBTARGET) $(OBJECTS) diff --git a/base/TMethodJob.h b/base/TMethodJob.h new file mode 100644 index 00000000..8e889b3d --- /dev/null +++ b/base/TMethodJob.h @@ -0,0 +1,39 @@ +#ifndef CMETHODJOB_H +#define CMETHODJOB_H + +#include "IJob.h" + +template +class TMethodJob : public IJob { + public: + TMethodJob(T* object, void (T::*method)(void*), void* arg = NULL); + + // IJob overrides + virtual void run(); + + private: + T* m_object; + void (T::*m_method)(void*); + void* m_arg; +}; + +template +inline +TMethodJob::TMethodJob(T* object, void (T::*method)(void*), void* arg) : + m_object(object), + m_method(method), + m_arg(arg) +{ + // do nothing +} + +template +inline +void TMethodJob::run() +{ + if (m_object != NULL) { + (m_object->*m_method)(m_arg); + } +} + +#endif diff --git a/base/XBase.cpp b/base/XBase.cpp new file mode 100644 index 00000000..6558d5e3 --- /dev/null +++ b/base/XBase.cpp @@ -0,0 +1,72 @@ +#include "XBase.h" +#include + +// win32 wants a const char* argument to std::exception c'tor +#if CONFIG_PLATFORM_WIN32 +#define STDEXCEPTARG "" +#endif + +// default to no argument +#ifndef STDEXCEPTARG +#define STDEXCEPTARG +#endif + +// +// XBase +// + +XBase::XBase() : exception(STDEXCEPTARG), m_what() +{ + // do nothing +} + +XBase::XBase(const CString& msg) : exception(STDEXCEPTARG), m_what(msg) +{ + // do nothing +} + +XBase::~XBase() +{ + // do nothing +} + +const char* XBase::what() const +{ + if (m_what.empty()) { + m_what = getWhat(); + } + return m_what.c_str(); +} + +CString XBase::format(const char* /*id*/, + const char* fmt, ...) const throw() +{ + // FIXME -- use id to lookup formating string + // FIXME -- format string with arguments + return fmt; +} + + +// +// MXErrno +// + +MXErrno::MXErrno() : m_errno(errno) +{ + // do nothing +} + +MXErrno::MXErrno(int err) : m_errno(err) +{ + // do nothing +} + +int MXErrno::getErrno() const +{ + return m_errno; +} + +const char* MXErrno::getErrstr() const +{ + return strerror(m_errno); +} diff --git a/base/XBase.h b/base/XBase.h new file mode 100644 index 00000000..196f2122 --- /dev/null +++ b/base/XBase.h @@ -0,0 +1,44 @@ +#ifndef XBASE_H +#define XBASE_H + +#include "CString.h" +#include + +class XBase : public std::exception { + public: + XBase(); + XBase(const CString& msg); + virtual ~XBase(); + + // std::exception overrides + virtual const char* what() const; + + protected: + // returns a human readable string describing the exception + virtual CString getWhat() const throw() = 0; + + // look up a message and format it + virtual CString format(const char* id, + const char* defaultFormat, ...) const throw(); + + private: + mutable CString m_what; +}; + +class MXErrno { + public: + MXErrno(); + MXErrno(int); + + // manipulators + + // accessors + + int getErrno() const; + const char* getErrstr() const; + + private: + int m_errno; +}; + +#endif diff --git a/base/common.h b/base/common.h new file mode 100644 index 00000000..ca1224ca --- /dev/null +++ b/base/common.h @@ -0,0 +1,32 @@ +#ifndef COMMON_H +#define COMMON_H + +#if defined(__linux__) + +#define CONFIG_PLATFORM_LINUX +#define CONFIG_PLATFORM_UNIX +#define CONFIG_TYPES_X11 +#define CONFIG_PTHREADS + +#elif defined(__sun__) + +#define CONFIG_PLATFORM_SOLARIS +#define CONFIG_PLATFORM_UNIX +#define CONFIG_TYPES_X11 +#define CONFIG_PTHREADS + +#elif defined(_WINDOWS) && defined(WIN32) + +#define CONFIG_PLATFORM_WIN32 + +#else + +#error unsupported platform + +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#endif diff --git a/io/CBufferedInputStream.cpp b/io/CBufferedInputStream.cpp new file mode 100644 index 00000000..d0978dfb --- /dev/null +++ b/io/CBufferedInputStream.cpp @@ -0,0 +1,107 @@ +#include "CBufferedInputStream.h" +#include "CLock.h" +#include "CMutex.h" +#include "CThread.h" +#include "IJob.h" +#include "XIO.h" +#include +#include + +// +// CBufferedInputStream +// + +CBufferedInputStream::CBufferedInputStream(CMutex* mutex, IJob* closeCB) : + m_mutex(mutex), + m_empty(mutex, true), + m_closeCB(closeCB), + m_closed(false), + m_hungup(false) +{ + assert(m_mutex != NULL); +} + +CBufferedInputStream::~CBufferedInputStream() +{ + delete m_closeCB; +} + +void CBufferedInputStream::write( + const void* data, UInt32 n) throw() +{ + if (!m_hungup && n > 0) { + m_buffer.write(data, n); + m_empty = (m_buffer.getSize() == 0); + m_empty.broadcast(); + } +} + +void CBufferedInputStream::hangup() throw() +{ + m_hungup = true; + m_empty.broadcast(); +} + +UInt32 CBufferedInputStream::readNoLock( + void* dst, UInt32 n) throw(XIO) +{ + if (m_closed) { + throw XIOClosed(); + } + + // wait for data (or hangup) + while (!m_hungup && m_empty == true) { + m_empty.wait(); + } + + // read data + const UInt32 count = m_buffer.getSize(); + if (n > count) { + n = count; + } + if (n > 0) { + if (dst != NULL) { + ::memcpy(dst, m_buffer.peek(n), n); + } + m_buffer.pop(n); + } + + // update empty state + if (m_buffer.getSize() == 0) { + m_empty = true; + m_empty.broadcast(); + } + return n; +} + +UInt32 CBufferedInputStream::getSizeNoLock() const throw() +{ + return m_buffer.getSize(); +} + +void CBufferedInputStream::close() throw(XIO) +{ + CLock lock(m_mutex); + if (m_closed) { + throw XIOClosed(); + } + + m_closed = true; + if (m_closeCB) { + m_closeCB->run(); + } +} + +UInt32 CBufferedInputStream::read( + void* dst, UInt32 n) throw(XIO) +{ + CLock lock(m_mutex); + return readNoLock(dst, n); +} + +UInt32 CBufferedInputStream::getSize() const throw() +{ + CLock lock(m_mutex); + return getSizeNoLock(); +} + diff --git a/io/CBufferedInputStream.h b/io/CBufferedInputStream.h new file mode 100644 index 00000000..8a4dd7be --- /dev/null +++ b/io/CBufferedInputStream.h @@ -0,0 +1,51 @@ +#ifndef CBUFFEREDINPUTSTREAM_H +#define CBUFFEREDINPUTSTREAM_H + +#include "CStreamBuffer.h" +#include "CCondVar.h" +#include "IInputStream.h" + +class CMutex; +class IJob; + +class CBufferedInputStream : public IInputStream { + public: + CBufferedInputStream(CMutex*, IJob* adoptedCloseCB); + ~CBufferedInputStream(); + + // the caller is expected to lock the mutex before calling + // methods unless otherwise noted. + + // manipulators + + // write() appends n bytes to the buffer + void write(const void*, UInt32 n) throw(); + + // causes read() to always return immediately. if there is no + // more data then it returns 0. further writes are discarded. + void hangup() throw(); + + // same as read() but caller must lock the mutex + UInt32 readNoLock(void*, UInt32 count) throw(XIO); + + // accessors + + // same as getSize() but caller must lock the mutex + UInt32 getSizeNoLock() const throw(); + + // IInputStream overrides + // these all lock the mutex for their duration + virtual void close() throw(XIO); + virtual UInt32 read(void*, UInt32 count) throw(XIO); + virtual UInt32 getSize() const throw(); + + private: + CMutex* m_mutex; + CCondVar m_empty; + IJob* m_closeCB; + CStreamBuffer m_buffer; + bool m_closed; + bool m_hungup; +}; + +#endif diff --git a/io/CBufferedOutputStream.cpp b/io/CBufferedOutputStream.cpp new file mode 100644 index 00000000..bf3d9165 --- /dev/null +++ b/io/CBufferedOutputStream.cpp @@ -0,0 +1,79 @@ +#include "CBufferedOutputStream.h" +#include "CLock.h" +#include "CMutex.h" +#include "CThread.h" +#include "IJob.h" +#include "XIO.h" +#include + +// +// CBufferedOutputStream +// + +CBufferedOutputStream::CBufferedOutputStream(CMutex* mutex, IJob* closeCB) : + m_mutex(mutex), + m_closeCB(closeCB), + m_closed(false) +{ + assert(m_mutex != NULL); +} + +CBufferedOutputStream::~CBufferedOutputStream() +{ + delete m_closeCB; +} + +const void* CBufferedOutputStream::peek(UInt32 n) throw() +{ + return m_buffer.peek(n); +} + +void CBufferedOutputStream::pop(UInt32 n) throw() +{ + m_buffer.pop(n); +} + +UInt32 CBufferedOutputStream::getSize() const throw() +{ + return m_buffer.getSize(); +} + +void CBufferedOutputStream::close() throw(XIO) +{ + CLock lock(m_mutex); + if (m_closed) { + throw XIOClosed(); + } + + m_closed = true; + if (m_closeCB) { + m_closeCB->run(); + } +} + +UInt32 CBufferedOutputStream::write( + const void* data, UInt32 n) throw(XIO) +{ + CLock lock(m_mutex); + if (m_closed) { + throw XIOClosed(); + } + + m_buffer.write(data, n); + return n; +} + +void CBufferedOutputStream::flush() throw(XIO) +{ + // wait until all data is written + while (getSizeWithLock() > 0) { + CThread::sleep(0.05); + } +} + +UInt32 CBufferedOutputStream::getSizeWithLock() const throw() +{ + CLock lock(m_mutex); + return m_buffer.getSize(); +} + diff --git a/io/CBufferedOutputStream.h b/io/CBufferedOutputStream.h new file mode 100644 index 00000000..ab43aecb --- /dev/null +++ b/io/CBufferedOutputStream.h @@ -0,0 +1,46 @@ +#ifndef CBUFFEREDOUTPUTSTREAM_H +#define CBUFFEREDOUTPUTSTREAM_H + +#include "CStreamBuffer.h" +#include "IOutputStream.h" + +class CMutex; +class IJob; + +class CBufferedOutputStream : public IOutputStream { + public: + CBufferedOutputStream(CMutex*, IJob* adoptedCloseCB); + ~CBufferedOutputStream(); + + // the caller is expected to lock the mutex before calling + // methods unless otherwise noted. + + // manipulators + + // peek() returns a buffer of n bytes (which must be <= getSize()). + // pop() discards the next n bytes. + const void* peek(UInt32 n) throw(); + void pop(UInt32 n) throw(); + + // accessors + + // return the number of bytes in the buffer + UInt32 getSize() const throw(); + + // IOutputStream overrides + // these all lock the mutex for their duration + virtual void close() throw(XIO); + virtual UInt32 write(const void*, UInt32 count) throw(XIO); + virtual void flush() throw(XIO); + + private: + UInt32 getSizeWithLock() const throw(); + + private: + CMutex* m_mutex; + IJob* m_closeCB; + CStreamBuffer m_buffer; + bool m_closed; +}; + +#endif diff --git a/io/CInputStreamFilter.cpp b/io/CInputStreamFilter.cpp new file mode 100644 index 00000000..486a6298 --- /dev/null +++ b/io/CInputStreamFilter.cpp @@ -0,0 +1,25 @@ +#include "CInputStreamFilter.h" +#include + +// +// CInputStreamFilter +// + +CInputStreamFilter::CInputStreamFilter(IInputStream* stream, bool adopted) : + m_stream(stream), + m_adopted(adopted) +{ + assert(m_stream != NULL); +} + +CInputStreamFilter::~CInputStreamFilter() +{ + if (m_adopted) { + delete m_stream; + } +} + +IInputStream* CInputStreamFilter::getStream() const throw() +{ + return m_stream; +} diff --git a/io/CInputStreamFilter.h b/io/CInputStreamFilter.h new file mode 100644 index 00000000..51f40b35 --- /dev/null +++ b/io/CInputStreamFilter.h @@ -0,0 +1,28 @@ +#ifndef CINPUTSTREAMFILTER_H +#define CINPUTSTREAMFILTER_H + +#include "IInputStream.h" + +class CInputStreamFilter : public IInputStream { + public: + CInputStreamFilter(IInputStream*, bool adoptStream = true); + ~CInputStreamFilter(); + + // manipulators + + // accessors + + // IInputStream overrides + virtual void close() throw(XIO) = 0; + virtual UInt32 read(void*, UInt32 maxCount) throw(XIO) = 0; + virtual UInt32 getSize() const throw() = 0; + + protected: + IInputStream* getStream() const throw(); + + private: + IInputStream* m_stream; + bool m_adopted; +}; + +#endif diff --git a/io/COutputStreamFilter.cpp b/io/COutputStreamFilter.cpp new file mode 100644 index 00000000..3f62075c --- /dev/null +++ b/io/COutputStreamFilter.cpp @@ -0,0 +1,25 @@ +#include "COutputStreamFilter.h" +#include + +// +// 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 throw() +{ + return m_stream; +} diff --git a/io/COutputStreamFilter.h b/io/COutputStreamFilter.h new file mode 100644 index 00000000..11499d80 --- /dev/null +++ b/io/COutputStreamFilter.h @@ -0,0 +1,28 @@ +#ifndef COUTPUTSTREAMFILTER_H +#define COUTPUTSTREAMFILTER_H + +#include "IOutputStream.h" + +class COutputStreamFilter : public IOutputStream { + public: + COutputStreamFilter(IOutputStream*, bool adoptStream = true); + ~COutputStreamFilter(); + + // manipulators + + // accessors + + // IOutputStream overrides + virtual void close() throw(XIO) = 0; + virtual UInt32 write(const void*, UInt32 count) throw(XIO) = 0; + virtual void flush() throw(XIO) = 0; + + protected: + IOutputStream* getStream() const throw(); + + private: + IOutputStream* m_stream; + bool m_adopted; +}; + +#endif diff --git a/io/CStreamBuffer.cpp b/io/CStreamBuffer.cpp new file mode 100644 index 00000000..d2ce3140 --- /dev/null +++ b/io/CStreamBuffer.cpp @@ -0,0 +1,107 @@ +#include "CStreamBuffer.h" +#include + +// +// CStreamBuffer +// + +const UInt32 CStreamBuffer::kChunkSize = 4096; + +CStreamBuffer::CStreamBuffer() : m_size(0) +{ + // do nothing +} + +CStreamBuffer::~CStreamBuffer() +{ + // do nothing +} + +const void* CStreamBuffer::peek(UInt32 n) throw() +{ + assert(n <= m_size); + + // reserve space in first chunk + ChunkList::iterator head = m_chunks.begin(); + head->reserve(n); + + // consolidate chunks into the first chunk until it has n bytes + ChunkList::iterator scan = head; + ++scan; + while (head->size() < n && scan != m_chunks.end()) { + head->insert(head->end(), scan->begin(), scan->end()); + scan = m_chunks.erase(scan); + } + + return reinterpret_cast(head->begin()); +} + +void CStreamBuffer::pop(UInt32 n) throw() +{ + m_size -= n; + + // discard chunks until more than n bytes would've been discarded + ChunkList::iterator scan = m_chunks.begin(); + while (scan->size() <= n && scan != m_chunks.end()) { + n -= scan->size(); + scan = m_chunks.erase(scan); + } + + // if there's anything left over then remove it from the head chunk. + // if there's no head chunk then we're already empty. + if (scan == m_chunks.end()) { + m_size = 0; + } + else if (n > 0) { + scan->erase(scan->begin(), scan->begin() + n); + } +} + +void CStreamBuffer::write( + const void* vdata, UInt32 n) throw() +{ + assert(vdata != NULL); + + if (n == 0) { + return; + } + m_size += n; + + // cast data to bytes + const UInt8* data = reinterpret_cast(vdata); + + // point to last chunk if it has space, otherwise append an empty chunk + ChunkList::iterator scan = m_chunks.end(); + if (scan != m_chunks.begin()) { + --scan; + if (scan->size() >= kChunkSize) + ++scan; + } + if (scan == m_chunks.end()) { + scan = m_chunks.insert(scan); + } + + // append data in chunks + while (n > 0) { + // choose number of bytes for next chunk + UInt32 count = kChunkSize - scan->size(); + if (count > n) + count = n; + + // transfer data + scan->insert(scan->end(), data, data + count); + n -= count; + data += count; + + // append another empty chunk if we're not done yet + if (n > 0) { + scan = m_chunks.insert(scan); + } + } +} + +UInt32 CStreamBuffer::getSize() const throw() +{ + return m_size; +} + diff --git a/io/CStreamBuffer.h b/io/CStreamBuffer.h new file mode 100644 index 00000000..cb5bb864 --- /dev/null +++ b/io/CStreamBuffer.h @@ -0,0 +1,38 @@ +#ifndef CSTREAMBUFFER_H +#define CSTREAMBUFFER_H + +#include "BasicTypes.h" +#include +#include + +class CStreamBuffer { + public: + CStreamBuffer(); + ~CStreamBuffer(); + + // manipulators + + // peek() returns a buffer of n bytes (which must be <= getSize()). + // pop() discards the next n bytes. + const void* peek(UInt32 n) throw(); + void pop(UInt32 n) throw(); + + // write() appends n bytes to the buffer + void write(const void*, UInt32 n) throw(); + + // accessors + + // return the number of bytes in the buffer + UInt32 getSize() const throw(); + + private: + static const UInt32 kChunkSize; + + typedef std::vector Chunk; + typedef std::list ChunkList; + + ChunkList m_chunks; + UInt32 m_size; +}; + +#endif diff --git a/io/IInputStream.h b/io/IInputStream.h new file mode 100644 index 00000000..e4f93394 --- /dev/null +++ b/io/IInputStream.h @@ -0,0 +1,29 @@ +#ifndef IINPUTSTREAM_H +#define IINPUTSTREAM_H + +#include "IInterface.h" +#include "BasicTypes.h" +#include "XIO.h" + +class IInputStream : public IInterface { + public: + // manipulators + + // close the stream + virtual void close() throw(XIO) = 0; + + // read up to maxCount bytes into buffer, return number read. + // blocks if no data is currently available. if buffer is NULL + // then the data is discarded. + virtual UInt32 read(void* buffer, UInt32 maxCount) throw(XIO) = 0; + + // accessors + + // get 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 throw() = 0; +}; + +#endif diff --git a/io/IOutputStream.h b/io/IOutputStream.h new file mode 100644 index 00000000..f0b46244 --- /dev/null +++ b/io/IOutputStream.h @@ -0,0 +1,24 @@ +#ifndef IOUTPUTSTREAM_H +#define IOUTPUTSTREAM_H + +#include "IInterface.h" +#include "BasicTypes.h" +#include "XIO.h" + +class IOutputStream : public IInterface { + public: + // manipulators + + // close the stream + virtual void close() throw(XIO) = 0; + + // write count bytes to stream + virtual UInt32 write(const void*, UInt32 count) throw(XIO) = 0; + + // flush the stream + virtual void flush() throw(XIO) = 0; + + // accessors +}; + +#endif diff --git a/io/Makefile b/io/Makefile new file mode 100644 index 00000000..49496cd9 --- /dev/null +++ b/io/Makefile @@ -0,0 +1,29 @@ +DEPTH=.. +include $(DEPTH)/Makecommon + +# +# target file +# +TARGET = io + +# +# source files +# +LCXXINCS = \ + -I$(DEPTH)/base \ + -I$(DEPTH)/mt \ + $(NULL) +CXXFILES = \ + XIO.cpp \ + CInputStreamFilter.cpp \ + COutputStreamFilter.cpp \ + CStreamBuffer.cpp \ + CBufferedInputStream.cpp \ + CBufferedOutputStream.cpp \ + $(NULL) + +targets: $(LIBTARGET) + +$(LIBTARGET): $(OBJECTS) $(DEPLIBS) + if test ! -d $(LIBDIR); then $(MKDIR) $(LIBDIR); fi + $(ARF) $(LIBTARGET) $(OBJECTS) diff --git a/io/XIO.cpp b/io/XIO.cpp new file mode 100644 index 00000000..c51641d4 --- /dev/null +++ b/io/XIO.cpp @@ -0,0 +1,46 @@ +#include "XIO.h" + +// +// XIOErrno +// + +XIOErrno::XIOErrno() : MXErrno() +{ + // do nothing +} + +XIOErrno::XIOErrno(int err) : MXErrno(err) +{ + // do nothing +} + + +// +// XIOClose +// + +CString XIOClose::getWhat() const throw() +{ + return format("XIOClose", "close: %1", XIOErrno::getErrstr()); +} + + +// +// XIOClosed +// + +CString XIOClosed::getWhat() const throw() +{ + return format("XIOClosed", "already closed"); +} + + +// +// XIOEndOfStream +// + +CString XIOEndOfStream::getWhat() const throw() +{ + return format("XIOEndOfStream", "reached end of stream"); +} + diff --git a/io/XIO.h b/io/XIO.h new file mode 100644 index 00000000..b468e764 --- /dev/null +++ b/io/XIO.h @@ -0,0 +1,33 @@ +#ifndef XIO_H +#define XIO_H + +#include "XBase.h" +#include "BasicTypes.h" + +class XIO : public XBase { }; + +class XIOErrno : public XIO, public MXErrno { + public: + XIOErrno(); + XIOErrno(int); +}; + +class XIOClose: public XIOErrno { + protected: + // XBase overrides + virtual CString getWhat() const throw(); +}; + +class XIOClosed : public XIO { + protected: + // XBase overrides + virtual CString getWhat() const throw(); +}; + +class XIOEndOfStream : public XIO { + protected: + // XBase overrides + virtual CString getWhat() const throw(); +}; + +#endif diff --git a/main.cpp b/main.cpp deleted file mode 100644 index b879e1a1..00000000 --- a/main.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include -#include - -#include "CServer.h" -#include "CClient.h" -#include "CUnixTCPSocket.h" -#include "CUnixEventQueue.h" -#include "CUnixXScreen.h" - -/* -static void selectMotion(Display* dpy, Window w) -{ - // select events - XSelectInput(dpy, w, PointerMotionMask | SubstructureNotifyMask); - - // recurse on child windows - Window rw, pw, *cw; - unsigned int nc; - if (XQueryTree(dpy, w, &rw, &pw, &cw, &nc)) { - for (unsigned int i = 0; i < nc; ++i) - selectMotion(dpy, cw[i]); - XFree(cw); - } -} - -static void trackMouse(Display* dpy) -{ - // note -- this doesn't track the mouse when it's grabbed. that's - // okay for synergy because we don't want to cross screens then. - selectMotion(dpy, DefaultRootWindow(dpy)); - while (true) { - XEvent event; - XNextEvent(dpy, &event); - switch (event.type) { - case MotionNotify: - fprintf(stderr, "mouse: %d,%d\n", event.xmotion.x_root, event.xmotion.y_root); - break; - - case CreateNotify: - selectMotion(dpy, event.xcreatewindow.window); - break; - } - } -} - -static void checkLEDs(Display* dpy) -{ - XKeyboardState values; - XGetKeyboardControl(dpy, &values); - - fprintf(stderr, "led (%08x): ", (unsigned int)values.led_mask); - for (int i = 0; i < 32; ++i) - fprintf(stderr, "%c", (values.led_mask & (1 << i)) ? 'O' : '.'); - fprintf(stderr, "\n"); - - XKeyboardControl ctrl; - for (int i = 0; i < 32; i += 2) { - ctrl.led = i + 1; - ctrl.led_mode = LedModeOff; - XChangeKeyboardControl(dpy, KBLed | KBLedMode, &ctrl); - XSync(dpy, False); - } -} -*/ - -int main(int argc, char** argv) -{ -/* - printf("Hello world\n"); - - Display* dpy = XOpenDisplay(NULL); - - checkLEDs(dpy); - trackMouse(dpy); - - XCloseDisplay(dpy); -*/ - - // install socket factory - CSocketFactory::setInstance(new CUnixTCPSocketFactory); - - // create event queue - CUnixEventQueue eventQueue; - - if (argc <= 1) { - // create server - CServer server; - - // create clients - CUnixXScreen localScreen("audrey2"); - - // register clients - server.addLocalScreen(&localScreen); - server.addRemoteScreen("remote1"); - - // hook up edges - server.connectEdge("audrey2", CServer::kLeft, "remote1"); - server.connectEdge("audrey2", CServer::kTop, "audrey2"); - server.connectEdge("audrey2", CServer::kBottom, "audrey2"); - server.connectEdge("remote1", CServer::kLeft, "audrey2"); - - // do it - server.run(); - } - else { - // create client - CUnixXScreen screen("remote1"); - CClient client(&screen); - client.run(argv[1]); - } - - return 0; -} diff --git a/mt/CCondVar.cpp b/mt/CCondVar.cpp new file mode 100644 index 00000000..e2d441ae --- /dev/null +++ b/mt/CCondVar.cpp @@ -0,0 +1,296 @@ +#include "CCondVar.h" +#include "CStopwatch.h" +#include + +// +// CCondVarBase +// + +CCondVarBase::CCondVarBase(CMutex* mutex) : + m_mutex(mutex) +#if defined(CONFIG_PLATFORM_WIN32) + , m_waitCountMutex() +#endif +{ + assert(m_mutex != NULL); + init(); +} + +CCondVarBase::~CCondVarBase() +{ + fini(); +} + +void CCondVarBase::lock() const throw() +{ + m_mutex->lock(); +} + +void CCondVarBase::unlock() const throw() +{ + m_mutex->unlock(); +} + +bool CCondVarBase::wait(double timeout) const +{ + CStopwatch timer(true); + return wait(timer, timeout); +} + +CMutex* CCondVarBase::getMutex() const throw() +{ + return m_mutex; +} + +#if defined(CONFIG_PTHREADS) + +#include "CThread.h" +#include +#include +#include + +void CCondVarBase::init() +{ + pthread_cond_t* cond = new pthread_cond_t; + int status = pthread_cond_init(cond, NULL); + assert(status == 0); + m_cond = reinterpret_cast(cond); +} + +void CCondVarBase::fini() +{ + pthread_cond_t* cond = reinterpret_cast(m_cond); + int status = pthread_cond_destroy(cond); + assert(status == 0); + delete cond; +} + +void CCondVarBase::signal() throw() +{ + pthread_cond_t* cond = reinterpret_cast(m_cond); + int status = pthread_cond_signal(cond); + assert(status == 0); +} + +void CCondVarBase::broadcast() throw() +{ + pthread_cond_t* cond = reinterpret_cast(m_cond); + int status = pthread_cond_broadcast(cond); + assert(status == 0); +} + +bool CCondVarBase::wait( + CStopwatch& timer, double timeout) const +{ + // check timeout against timer + if (timeout >= 0.0) { + timeout -= timer.getTime(); + if (timeout < 0.0) + return false; + } + + // get condition variable and mutex + pthread_cond_t* cond = reinterpret_cast(m_cond); + pthread_mutex_t* mutex = reinterpret_cast(m_mutex->m_mutex); + + // get final time + struct timeval now; + gettimeofday(&now, NULL); + struct timespec finalTime; + finalTime.tv_sec = now.tv_sec; + finalTime.tv_nsec = now.tv_usec * 1000; + if (timeout >= 0.0) { + const long timeout_sec = (long)timeout; + const long timeout_nsec = (long)(1000000000.0 * (timeout - timeout_sec)); + finalTime.tv_sec += timeout_sec; + finalTime.tv_nsec += timeout_nsec; + if (finalTime.tv_nsec >= 1000000000) { + finalTime.tv_nsec -= 1000000000; + finalTime.tv_sec += 1; + } + } + + // repeat until we reach the final time + int status; + for (;;) { + // compute the next timeout + gettimeofday(&now, NULL); + struct timespec endTime; + endTime.tv_sec = now.tv_sec; + endTime.tv_nsec = now.tv_usec * 1000 + 50000000; + if (endTime.tv_nsec >= 1000000000) { + endTime.tv_nsec -= 1000000000; + endTime.tv_sec += 1; + } + + // see if we should cancel this thread + CThread::testCancel(); + + // done if past final timeout + if (timeout >= 0.0) { + if (endTime.tv_sec > finalTime.tv_sec || + (endTime.tv_sec == finalTime.tv_sec && + endTime.tv_nsec >= finalTime.tv_nsec)) { + status = ETIMEDOUT; + break; + } + } + + // wait + status = pthread_cond_timedwait(cond, mutex, &endTime); + + // check for cancel again + CThread::testCancel(); + + // check wait status + if (status != ETIMEDOUT) + break; + } + + switch (status) { + case 0: + // success + return true; + + case ETIMEDOUT: + return false; + + default: + assert(0 && "condition variable wait error"); + return false; + } +} + +#endif // CONFIG_PTHREADS + +#if defined(CONFIG_PLATFORM_WIN32) + +#include "CLock.h" +#include "CThreadRep.h" +#include + +// +// note -- implementation taken from +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// titled "Strategies for Implementing POSIX Condition Variables +// on Win32." it also provides an implementation that doesn't +// suffer from the incorrectness problem described in our +// corresponding header but it is slower, still unfair, and +// can cause busy waiting. +// + +void CCondVarBase::init() +{ + // prepare events + HANDLE* events = new HANDLE[2]; + events[kSignal] = CreateEvent(NULL, FALSE, FALSE, NULL); + events[kBroadcast] = CreateEvent(NULL, TRUE, FALSE, NULL); + + // prepare members + m_cond = reinterpret_cast(events); + m_waitCount = 0; +} + +void CCondVarBase::fini() +{ + HANDLE* events = reinterpret_cast(m_cond); + CloseHandle(events[kSignal]); + CloseHandle(events[kBroadcast]); + delete[] events; +} + +void CCondVarBase::signal() throw() +{ + // is anybody waiting? + bool hasWaiter; + { + CLock lock(&m_waitCountMutex); + hasWaiter = (m_waitCount > 0); + } + + // wake one thread if anybody is waiting + if (hasWaiter) + SetEvent(reinterpret_cast(m_cond)[kSignal]); +} + +void CCondVarBase::broadcast() throw() +{ + // is anybody waiting? + bool hasWaiter; + { + CLock lock(&m_waitCountMutex); + hasWaiter = (m_waitCount > 0); + } + + // wake all threads if anybody is waiting + if (hasWaiter) + SetEvent(reinterpret_cast(m_cond)[kBroadcast]); +} + +bool CCondVarBase::wait( + CStopwatch& timer, double timeout) const +{ + // check timeout against timer + if (timeout >= 0.0) { + timeout -= timer.getTime(); + if (timeout < 0.0) + return false; + } + + // prepare to wait + CRefCountedPtr currentRep(CThreadRep::getCurrentThreadRep()); + const DWORD winTimeout = (timeout < 0.0) ? INFINITE : + static_cast(1000.0 * timeout); + HANDLE* events = reinterpret_cast(m_cond); + HANDLE handles[3]; + handles[0] = events[kSignal]; + handles[1] = events[kBroadcast]; + handles[2] = currentRep->getCancelEvent(); + const DWORD n = currentRep->isCancellable() ? 3 : 2; + + // update waiter count + { + CLock lock(&m_waitCountMutex); + ++m_waitCount; + } + + // release mutex. this should be atomic with the wait so that it's + // impossible for another thread to signal us between the unlock and + // the wait, which would lead to a lost signal on broadcasts. + // however, we're using a manual reset event for broadcasts which + // stays set until we reset it, so we don't lose the broadcast. + m_mutex->unlock(); + + // wait for a signal or broadcast + DWORD result = ::WaitForMultipleObjects(n, handles, FALSE, winTimeout); + + // cancel takes priority + if (n == 3 && result != WAIT_OBJECT_0 + 2 && + ::WaitForSingleObject(handles[2], 0) == WAIT_OBJECT_0) + result = WAIT_OBJECT_0 + 2; + + // update the waiter count and check if we're the last waiter + bool last; + { + CLock lock(&m_waitCountMutex); + --m_waitCount; + last = (result == WAIT_OBJECT_0 + 1 && m_waitCount == 0); + } + + // reset the broadcast event if we're the last waiter + if (last) + ResetEvent(events[kBroadcast]); + + // reacquire the mutex + m_mutex->lock(); + + // cancel thread if necessary + if (result == WAIT_OBJECT_0 + 2) + currentRep->testCancel(); + + // return success or failure + return (result == WAIT_OBJECT_0 + 0 || + result == WAIT_OBJECT_0 + 1); +} + +#endif // CONFIG_PLATFORM_WIN32 diff --git a/mt/CCondVar.h b/mt/CCondVar.h new file mode 100644 index 00000000..a42e25ab --- /dev/null +++ b/mt/CCondVar.h @@ -0,0 +1,144 @@ +#ifndef CCONDVAR_H +#define CCONDVAR_H + +#include "CMutex.h" +#include "BasicTypes.h" + +class CStopwatch; + +class CCondVarBase { + public: + // mutex must be supplied. all condition variables have an + // associated mutex. the copy c'tor uses the same mutex as the + // argument and is otherwise like the default c'tor. + CCondVarBase(CMutex* mutex); + ~CCondVarBase(); + + // manipulators + + // lock/unlock the mutex. see CMutex. + void lock() const throw(); + void unlock() const throw(); + + // signal the condition. Signal() wakes one waiting thread. + // Broadcast() wakes all waiting threads. + void signal() throw(); + void broadcast() throw(); + + // accessors + + // wait on the condition. if timeout < 0 then wait until signalled, + // otherwise up to timeout seconds or until signalled, whichever + // comes first. since clients normally wait on condition variables + // in a loop, clients can provide a CStopwatch that acts as the + // timeout clock. using it, clients don't have to recalculate the + // timeout on each iteration. passing a stopwatch with a negative + // timeout is pointless but permitted. + // + // returns true if the object was signalled during the wait, false + // otherwise. + // + // (cancellation point) + bool wait(double timeout = -1.0) const; + bool wait(CStopwatch&, double timeout) const; + + // get the mutex passed to the c'tor + CMutex* getMutex() const throw(); + + private: + void init(); + void fini(); + + // not implemented + CCondVarBase(const CCondVarBase&); + CCondVarBase& operator=(const CCondVarBase&); + + private: + CMutex* m_mutex; + void* m_cond; + +#if defined(CONFIG_PLATFORM_WIN32) + enum { kSignal, kBroadcast }; + mutable UInt32 m_waitCount; + CMutex m_waitCountMutex; +#endif +}; + +template +class CCondVar : public CCondVarBase { + public: + CCondVar(CMutex* mutex, const T&); + CCondVar(const CCondVar&); + ~CCondVar(); + + // manipulators + + // assigns the value of the variable + CCondVar& operator=(const CCondVar&); + + // assign the value + CCondVar& operator=(const T&); + + // accessors + + // get the const value. this object should be locked before + // calling this method. + operator const T&() const throw(); + + private: + T m_data; +}; + +template +inline +CCondVar::CCondVar(CMutex* mutex, const T& data) : + CCondVarBase(mutex), m_data(data) +{ + // do nothing +} + +template +inline +CCondVar::CCondVar(const CCondVar& cv) : + CCondVarBase(cv.getMutex()), + m_data(cv.m_data) +{ + // do nothing +} + +template +inline +CCondVar::~CCondVar() +{ + // do nothing +} + +template +inline +CCondVar& CCondVar::operator=(const CCondVar& cv) +{ + m_data = cv.m_data; + return *this; +} + +template +inline +CCondVar& CCondVar::operator=(const T& data) +{ + m_data = data; + return *this; +} + +template +inline +CCondVar::operator const T&() const throw() +{ + return m_data; +} + + +// force instantiation of these common types +template class CCondVar; +template class CCondVar; + +#endif diff --git a/mt/CLock.cpp b/mt/CLock.cpp new file mode 100644 index 00000000..86aa721e --- /dev/null +++ b/mt/CLock.cpp @@ -0,0 +1,22 @@ +#include "CLock.h" +#include "CMutex.h" +#include "CCondVar.h" + +// +// CLock +// + +CLock::CLock(const CMutex* mutex) throw() : m_mutex(mutex) +{ + m_mutex->lock(); +} + +CLock::CLock(const CCondVarBase* cv) throw() : m_mutex(cv->getMutex()) +{ + m_mutex->lock(); +} + +CLock::~CLock() throw() +{ + m_mutex->unlock(); +} diff --git a/mt/CLock.h b/mt/CLock.h new file mode 100644 index 00000000..9bd81942 --- /dev/null +++ b/mt/CLock.h @@ -0,0 +1,24 @@ +#ifndef CLOCK_H +#define CLOCK_H + +#include "common.h" + +class CMutex; +class CCondVarBase; + +class CLock { + public: + CLock(const CMutex* mutex) throw(); + CLock(const CCondVarBase* cv) throw(); + ~CLock() throw(); + + private: + // not implemented + CLock(const CLock&); + CLock& operator=(const CLock&); + + private: + const CMutex* m_mutex; +}; + +#endif diff --git a/mt/CMutex.cpp b/mt/CMutex.cpp new file mode 100644 index 00000000..854d52e5 --- /dev/null +++ b/mt/CMutex.cpp @@ -0,0 +1,123 @@ +#include "CMutex.h" +#include + +// +// CMutex +// + +CMutex::CMutex() +{ + init(); +} + +CMutex::CMutex(const CMutex&) +{ + init(); +} + +CMutex::~CMutex() +{ + fini(); +} + +CMutex& CMutex::operator=(const CMutex&) +{ + return *this; +} + +#if defined(CONFIG_PTHREADS) + +#include +#include + +void CMutex::init() +{ + pthread_mutex_t* mutex = new pthread_mutex_t; + int status = pthread_mutex_init(mutex, NULL); + assert(status == 0); +// status = pthread_mutexattr_settype(mutex, PTHREAD_MUTEX_RECURSIVE); +// assert(status == 0); + m_mutex = reinterpret_cast(mutex); +} + +void CMutex::fini() +{ + pthread_mutex_t* mutex = reinterpret_cast(m_mutex); + int status = pthread_mutex_destroy(mutex); + assert(status == 0); + delete mutex; +} + +void CMutex::lock() const throw() +{ + pthread_mutex_t* mutex = reinterpret_cast(m_mutex); + int status = pthread_mutex_lock(mutex); + + switch (status) { + case 0: + // success + return; + + case EDEADLK: + assert(0 && "lock already owned"); + break; + + case EAGAIN: + assert(0 && "too many recursive locks"); + break; + + default: + assert(0 && "unexpected error"); + } +} + +void CMutex::unlock() const throw() +{ + pthread_mutex_t* mutex = reinterpret_cast(m_mutex); + int status = pthread_mutex_unlock(mutex); + + switch (status) { + case 0: + // success + return; + + case EPERM: + assert(0 && "thread doesn't own a lock"); + break; + + default: + assert(0 && "unexpected error"); + } +} + +#endif // CONFIG_PTHREADS + +#if defined(CONFIG_PLATFORM_WIN32) + +#include + +void CMutex::init() +{ + CRITICAL_SECTION* mutex = new CRITICAL_SECTION; + ::InitializeCriticalSection(mutex); + m_mutex = reinterpret_cast(mutex); +} + +void CMutex::fini() +{ + CRITICAL_SECTION* mutex = reinterpret_cast(m_mutex); + ::DeleteCriticalSection(mutex); + delete mutex; +} + +void CMutex::lock() const throw() +{ + ::EnterCriticalSection(reinterpret_cast(m_mutex)); +} + +void CMutex::unlock() const throw() +{ + ::LeaveCriticalSection(reinterpret_cast(m_mutex)); +} + +#endif // CONFIG_PLATFORM_WIN32 diff --git a/mt/CMutex.h b/mt/CMutex.h new file mode 100644 index 00000000..f9423827 --- /dev/null +++ b/mt/CMutex.h @@ -0,0 +1,35 @@ +#ifndef CMUTEX_H +#define CMUTEX_H + +#include "common.h" + +// recursive mutex class +class CMutex { + public: + // copy c'tor is equivalent to default c'tor. it's here to + // allow copying of objects that have mutexes. + CMutex(); + CMutex(const CMutex&); + ~CMutex(); + + // manipulators + + // this has no effect. it's only here to allow assignment of + // objects that have mutexes. + CMutex& operator=(const CMutex&); + + // accessors + + void lock() const throw(); + void unlock() const throw(); + + private: + void init(); + void fini(); + + private: + friend class CCondVarBase; + void* m_mutex; +}; + +#endif diff --git a/mt/CThread.cpp b/mt/CThread.cpp new file mode 100644 index 00000000..17fd85c1 --- /dev/null +++ b/mt/CThread.cpp @@ -0,0 +1,131 @@ +#include "CThread.h" +#include "CThreadRep.h" +#include "XThread.h" +#include "CLock.h" +#include "CStopwatch.h" + +// +// CThreadPtr +// + +class CThreadPtr { + public: + CThreadPtr(CThreadRep* rep) : m_rep(rep) { } + ~CThreadPtr() { m_rep->unref(); } + + CThreadRep* operator->() const { return m_rep; } + + private: + // not implemented + CThreadPtr(const CThreadPtr&); + CThreadPtr& operator=(const CThreadPtr&); + + private: + CThreadRep* m_rep; +}; + +// +// CThread +// + +CThread::CThread(IJob* job, void* userData) +{ + m_rep = new CThreadRep(job, userData); +} + +CThread::CThread(const CThread& thread) : m_rep(thread.m_rep) +{ + m_rep->ref(); +} + +CThread::CThread(CThreadRep* rep) : m_rep(rep) +{ + // do nothing. rep should have already been Ref()'d. +} + +CThread::~CThread() +{ + m_rep->unref(); +} + +CThread& CThread::operator=(const CThread& thread) +{ + if (thread.m_rep != m_rep) { + m_rep->unref(); + m_rep = thread.m_rep; + m_rep->ref(); + } + return *this; +} + +void CThread::sleep(double timeout) +{ + CThreadPtr currentRep(CThreadRep::getCurrentThreadRep()); + if (timeout >= 0.0) { + currentRep->testCancel(); + currentRep->sleep(timeout); + } + currentRep->testCancel(); +} + +void CThread::exit(void* result) +{ + throw XThreadExit(result); +} + +bool CThread::enableCancel(bool enable) +{ + CThreadPtr currentRep(CThreadRep::getCurrentThreadRep()); + return currentRep->enableCancel(enable); +} + +void CThread::cancel() +{ + m_rep->cancel(); +} + +void CThread::setPriority(int n) +{ + m_rep->setPriority(n); +} + +CThread CThread::getCurrentThread() +{ + return CThread(CThreadRep::getCurrentThreadRep()); +} + +bool CThread::wait(double timeout) const +{ + CThreadPtr currentRep(CThreadRep::getCurrentThreadRep()); + return currentRep->wait(m_rep, timeout); +} + +void CThread::testCancel() +{ + CThreadPtr currentRep(CThreadRep::getCurrentThreadRep()); + currentRep->testCancel(); +} + +void* CThread::getResult() const +{ + if (wait()) + return m_rep->getResult(); + else + return NULL; +} + +void* CThread::getUserData() +{ + CThreadPtr currentRep(CThreadRep::getCurrentThreadRep()); + return currentRep->getUserData(); +} + +bool CThread::operator==(const CThread& thread) const +{ + return (m_rep == thread.m_rep); +} + +bool CThread::operator!=(const CThread& thread) const +{ + return (m_rep != thread.m_rep); +} diff --git a/mt/CThread.h b/mt/CThread.h new file mode 100644 index 00000000..b5cf6d49 --- /dev/null +++ b/mt/CThread.h @@ -0,0 +1,130 @@ +#ifndef CTHREAD_H +#define CTHREAD_H + +#include "common.h" + +class IJob; +class CThreadRep; + +// note -- do not derive from this class +class CThread { + public: + // create and start a new thread executing the job. + // the user data can be retrieved with getUserData(). + CThread(IJob* adopted, void* userData = 0); + + // make a new thread object that refers to an existing thread. + // this does *not* start a new thread. + CThread(const CThread&); + + // release thread. this does not terminate the thread. a thread + // will keep running until the job completes or calls exit(). + ~CThread(); + + // manipulators + + // assign thread. this has no effect on the threads. it simply + // makes this thread object refer to another thread. it does *not* + // start a new thread. + CThread& operator=(const CThread&); + + // the calling thread sleeps for the given number of seconds. if + // timeout <= 0.0 then the call returns immediately. if timeout + // == 0.0 then the calling thread yields the CPU. + // (cancellation point) + static void sleep(double timeout); + + // terminate the calling thread. this function does not return but + // the stack is unwound and automatic objects are destroyed, as if + // exit() threw an exception (which is, in fact, what it does). the + // argument is saved as the result returned by getResult(). if you + // have a catch(...) block then you should add the following before + // it to avoid catching the exit: catch(CThreadExit&) { throw; } + static void exit(void*); + + // enable/disable cancellation. default is enabled. this is not + // a cancellation point so if you enabled cancellation and want to + // allow immediate cancellation you need to call testCancel(). + // return value is the previous state. + static bool enableCancel(bool); + + // cancel the thread. cancel() never waits for the thread to + // terminate; it just posts the cancel and returns. a thread will + // terminate when it enters a cancellation point with cancellation + // enabled. if cancellation is disabled then the cancel is + // remembered but not acted on until the first call to a + // cancellation point after cancellation is enabled. + // + // a cancellation point is a function that can act on cancellation. + // a cancellation point does not return if there's a cancel pending. + // instead, it unwinds the stack and destroys automatic objects, as + // if cancel() threw an exception (which is, in fact, what it does). + // threads must take care to clean up and release any resources they + // may have, especially mutexes. they can catch (XThreadCancel) to + // do that then rethrow the exception or they can let it happen + // automatically by doing clean up in the d'tors of automatic + // objects. clients are strongly encouraged to do the latter. + // during cancellation, further cancel() calls are ignored (i.e. + // a thread cannot be interrupted by a cancel during cancellation). + // + // clients that catch (XThreadCancel) must always rethrow the + // exception. clients that catch(...) must either rethrow the + // exception or include a catch (XThreadCancel) handler that + // rethrows. + void cancel(); + + // change the priority of the thread. normal priority is 0, 1 is + // the next lower, etc. -1 is the next higher, etc. but boosting + // the priority may not be available. + void setPriority(int n); + + // accessors + + // return a thread object representing the calling thread + static CThread getCurrentThread(); + + // get the user data passed to the constructor for the current + // thread. + static void* getUserData(); + + // testCancel() does nothing but is a cancellation point. call + // this to make a function itself a cancellation point. + // (cancellation point) + static void testCancel(); + + // waits for the thread to terminate (by exit() or cancel() or + // by returning from the thread job). returns immediately if + // the thread has already terminated. returns immediately with + // false if called by a thread on itself. returns false on + // timeout (or error) and true on success. + // (cancellation point) + bool wait(double timeout = -1.0) const; + + // get the exit result. does an implicit wait(). returns NULL + // immediately if called by a thread on itself. returns NULL for + // threads that were cancelled. + // (cancellation point) + void* getResult() const; + + // compare threads for (in)equality + bool operator==(const CThread&) const; + bool operator!=(const CThread&) const; + + private: + CThread(CThreadRep*); + + private: + CThreadRep* m_rep; +}; + +// disables cancellation in the c'tor and enables it in the d'tor. +class CThreadMaskCancel { + public: + CThreadMaskCancel() : m_old(CThread::enableCancel(false)) { } + ~CThreadMaskCancel() { CThread::enableCancel(m_old); } + + private: + bool m_old; +}; + +#endif diff --git a/mt/CThreadRep.cpp b/mt/CThreadRep.cpp new file mode 100644 index 00000000..296b50da --- /dev/null +++ b/mt/CThreadRep.cpp @@ -0,0 +1,518 @@ +#include "CThreadRep.h" +#include "CThread.h" +#include "XThread.h" +#include "CLock.h" +#include "IJob.h" +#include + +#if defined(CONFIG_PTHREADS) +#include +#endif + +// FIXME -- temporary exception type +class XThreadUnavailable { }; + +// +// CThreadRep +// + +CMutex CThreadRep::s_mutex; +CThreadRep* CThreadRep::s_head = NULL; + +CThreadRep::CThreadRep() : m_prev(NULL), + m_next(NULL), + m_refCount(1), + m_job(NULL), + m_userData(NULL) +{ + // note -- s_mutex must be locked on entry + + // initialize stuff + init(); +#if defined(CONFIG_PTHREADS) + // get main thread id + m_thread = pthread_self(); + + // install SIGALRM handler + struct sigaction act; + act.sa_handler = &threadCancel; +# if defined(SA_INTERRUPT) + act.sa_flags = SA_INTERRUPT; +# else + act.sa_flags = 0; +# endif + sigemptyset(&act.sa_mask); + sigaction(SIGALRM, &act, NULL); +#elif defined(CONFIG_PLATFORM_WIN32) + // get main thread id + m_thread = NULL; + m_id = GetCurrentThreadId(); +#endif + + // insert ourself into linked list + if (s_head != NULL) { + s_head->m_prev = this; + m_next = s_head; + } + s_head = this; +} + +CThreadRep::CThreadRep(IJob* job, void* userData) : + m_prev(NULL), + m_next(NULL), + m_refCount(2), // 1 for us, 1 for thread + m_job(job), + m_userData(userData) +{ + assert(m_job != NULL); + + // create a thread rep for the main thread if the current thread + // is unknown. note that this might cause multiple "main" threads + // if threads are created external to this library. + getCurrentThreadRep()->unref(); + + // initialize + init(); + + // hold mutex while we create the thread + CLock lock(&s_mutex); + + // start the thread. throw if it doesn't start. +#if defined(CONFIG_PTHREADS) + int status = pthread_create(&m_thread, NULL, threadFunc, (void*)this); + if (status != 0) + throw XThreadUnavailable(); + sigset_t sigset; + sigemptyset(&sigset); + sigaddset(&sigset, SIGALRM); + pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); +#elif defined(CONFIG_PLATFORM_WIN32) + unsigned int id; + m_thread = reinterpret_cast(_beginthreadex(NULL, 0, + threadFunc, (void*)this, 0, &id)); + m_id = static_cast(id); + if (m_thread == 0) + throw XThreadUnavailable(); +#endif + + // insert ourself into linked list + if (s_head != NULL) { + s_head->m_prev = this; + m_next = s_head; + } + s_head = this; + + // returning releases the locks, allowing the child thread to run +} + +CThreadRep::~CThreadRep() +{ + // note -- s_mutex must be locked on entry + + // remove ourself from linked list + if (m_prev != NULL) { + m_prev->m_next = m_next; + } + if (m_next != NULL) { + m_next->m_prev = m_prev; + } + if (s_head == this) { + s_head = m_next; + } + + // clean up + fini(); +} + +void CThreadRep::ref() +{ + CLock lock(&s_mutex); + ++m_refCount; +} + +void CThreadRep::unref() +{ + CLock lock(&s_mutex); + if (--m_refCount == 0) { + delete this; + } +} + +bool CThreadRep::enableCancel(bool enable) +{ + CLock lock(&s_mutex); + const bool old = m_cancellable; + m_cancellable = enable; + return old; +} + +bool CThreadRep::isCancellable() const +{ + CLock lock(&s_mutex); + return (m_cancellable && !m_cancelling); +} + +void* CThreadRep::getResult() const +{ + // no lock necessary since thread isn't running + return m_result; +} + +void* CThreadRep::getUserData() const +{ + // no lock necessary because the value never changes + return m_userData; +} + +CThreadRep* CThreadRep::getCurrentThreadRep() +{ +#if defined(CONFIG_PTHREADS) + const pthread_t thread = pthread_self(); +#elif defined(CONFIG_PLATFORM_WIN32) + const DWORD id = GetCurrentThreadId(); +#endif + + // lock list while we search + CLock lock(&s_mutex); + + // search + CThreadRep* scan = s_head; + while (scan != NULL) { +#if defined(CONFIG_PTHREADS) + if (scan->m_thread == thread) { + break; + } +#elif defined(CONFIG_PLATFORM_WIN32) + if (scan->m_id == id) { + break; + } +#endif + scan = scan->m_next; + } + + // create and use main thread rep if thread not found + if (scan == NULL) { + scan = new CThreadRep(); + } + + // ref for caller + ++scan->m_refCount; + + return scan; +} + +void CThreadRep::doThreadFunc() +{ + // default priority is slightly below normal + setPriority(1); + + // wait for parent to initialize this object + { CLock lock(&s_mutex); } + + void* result = NULL; + try { + // go + m_job->run(); + } + + catch (XThreadCancel&) { + // client called cancel() + } + + catch (XThreadExit& e) { + // client called exit() + result = e.m_result; + } + + // note -- don't catch (...) to avoid masking bugs + + // done with job + delete m_job; + + // store exit result (no lock necessary because the result will + // not be accessed until m_exit is set) + m_result = result; +} + +#if defined(CONFIG_PTHREADS) + +#include "CStopwatch.h" +#include + +void CThreadRep::init() +{ + m_result = NULL; + m_cancellable = true; + m_cancelling = false; + m_cancel = false; + m_exit = false; +} + +void CThreadRep::fini() +{ + // main thread has NULL job + if (m_job != NULL) { + pthread_detach(m_thread); + } +} + +void CThreadRep::sleep(double timeout) +{ + if (timeout < 0.0) + return; + struct timespec t; + t.tv_sec = (long)timeout; + t.tv_nsec = (long)(1000000000.0 * (timeout - (double)t.tv_sec)); + nanosleep(&t, NULL); +} + +void CThreadRep::cancel() +{ + CLock lock(&s_mutex); + if (m_cancellable && !m_cancelling) { + m_cancel = true; + + // break out of system calls + pthread_kill(m_thread, SIGALRM); + } +} + +void CThreadRep::testCancel() +{ + { + // prevent further cancellation + CLock lock(&s_mutex); + if (!m_cancel || !m_cancellable || m_cancelling) + return; + + // update state for cancel + m_cancel = false; + m_cancelling = true; + } + + // start cancel + throw XThreadCancel(); +} + +bool CThreadRep::wait(CThreadRep* target, double timeout) +{ + if (target == this) + return false; + + testCancel(); + if (target->isExited()) + return true; + + if (timeout > 0.0) { + CStopwatch timer; + do { + sleep(0.05); + testCancel(); + if (target->isExited()) + return true; + } while (timer.getTime() <= timeout); + } + + return false; +} + +void CThreadRep::setPriority(int) +{ + // FIXME +} + +bool CThreadRep::isExited() const +{ + CLock lock(&s_mutex); + return m_exit; +} + +void* CThreadRep::threadFunc(void* arg) +{ + CThreadRep* rep = (CThreadRep*)arg; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); + + // run thread + rep->doThreadFunc(); + + // unref thread + rep->unref(); + + // mark as terminated + CLock lock(&s_mutex); + rep->m_exit = true; + + // terminate the thread + return NULL; +} + +void CThreadRep::threadCancel(int) +{ + // do nothing +} + +#elif defined(CONFIG_PLATFORM_WIN32) + +#include + +void CThreadRep::init() +{ + m_result = NULL; + m_cancellable = true; + m_cancelling = false; + m_exit = CreateEvent(NULL, TRUE, FALSE, NULL); + m_cancel = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +void CThreadRep::fini() +{ + // destroy the events + CloseHandle(m_cancel); + CloseHandle(m_exit); + + // close the handle (main thread has a NULL handle) + if (m_thread != NULL) { + CloseHandle(m_thread); + } +} + +void CThreadRep::sleep(double timeout) +{ + if (isCancellable()) + WaitForSingleObject(m_cancel, (DWORD)(1000.0 * timeout)); + else + ::Sleep((DWORD)(1000.0 * timeout)); +} + +void CThreadRep::cancel() +{ + SetEvent(m_cancel); +} + +void CThreadRep::testCancel() +{ + // poll cancel event. return if not set. + const DWORD result = ::WaitForSingleObject(getCancelEvent(), 0); + if (result != WAIT_OBJECT_0) + return; + + { + // ignore if disabled or already cancelling + CLock lock(&s_mutex); + if (!m_cancellable || m_cancelling) + return; + + // update state for cancel + m_cancelling = true; + ResetEvent(m_cancel); + } + + // start cancel + throw XThreadCancel(); +} + +bool CThreadRep::wait(CThreadRep* target, double timeout) +{ + // get the current thread. if it's the same as the target thread + // then the thread is waiting on itself. + CRefCountedPtr currentRep(CThreadRep::getCurrentThreadRep()); + if (target == this) + return false; + + // is cancellation enabled? + const DWORD n = (isCancellable() ? 2 : 1); + + // convert timeout + DWORD t; + if (timeout < 0.0) + t = INFINITE; + else + t = (DWORD)(1000.0 * timeout); + + // wait for this thread to be cancelled or for the target thread to + // terminate. + HANDLE handles[2]; + handles[0] = target->getExitEvent(); + handles[1] = m_cancel; + DWORD result = ::WaitForMultipleObjects(n, handles, FALSE, t); + + // cancel takes priority + if (n == 2 && result != WAIT_OBJECT_0 + 1 && + ::WaitForSingleObject(handles[1], 0) == WAIT_OBJECT_0) + result = WAIT_OBJECT_0 + 1; + + // handle result + switch (result) { + case WAIT_OBJECT_0 + 0: + // target thread terminated + return true; + + case WAIT_OBJECT_0 + 1: + // this thread was cancelled. does not return. + testCancel(); + + default: + // error + return false; + } +} + +void CThreadRep::setPriority(int n) +{ + if (n < 0) { + switch (-n) { + case 1: n = THREAD_PRIORITY_ABOVE_NORMAL; break; + default: n = THREAD_PRIORITY_HIGHEST; break; + } + } + else { + switch (n) { + case 0: n = THREAD_PRIORITY_NORMAL; break; + case 1: n = THREAD_PRIORITY_BELOW_NORMAL; break; + case 2: n = THREAD_PRIORITY_LOWEST; break; + default: n = THREAD_PRIORITY_IDLE; break; + } + } + SetThreadPriority(m_thread, n); +} + +HANDLE CThreadRep::getExitEvent() const +{ + // no lock necessary because the value never changes + return m_exit; +} + +HANDLE CThreadRep::getCancelEvent() const +{ + // no lock necessary because the value never changes + return m_cancel; +} + +unsigned int __stdcall CThreadRep::threadFunc(void* arg) +{ + CThreadRep* rep = (CThreadRep*)arg; + + // initialize OLE + const HRESULT hr = ::OleInitialize(NULL); + + // run thread + rep->doThreadFunc(); + + // close OLE + if (!FAILED(hr)) { + OleUninitialize(); + } + + // signal termination + SetEvent(rep->m_exit); + + // unref thread + rep->unref(); + + // terminate the thread + return 0; +} + +#endif diff --git a/mt/CThreadRep.h b/mt/CThreadRep.h new file mode 100644 index 00000000..89bb6131 --- /dev/null +++ b/mt/CThreadRep.h @@ -0,0 +1,122 @@ +#ifndef CTHREADREP_H +#define CTHREADREP_H + +#include "CMutex.h" +#include "BasicTypes.h" + +#if defined(CONFIG_PTHREADS) +#include +#elif defined(CONFIG_PLATFORM_WIN32) +#include +#endif + +class IJob; + +class CThreadRep { + public: + CThreadRep(IJob*, void* userData); + + // manipulators + + // change ref count + void ref(); + void unref(); + + // the calling thread sleeps for t seconds. if t == 0.0 then + // the thread yields the CPU. + void sleep(double timeout); + + // cancel the thread + void cancel(); + + // set cancellation state + bool enableCancel(bool enable); + + // permanently disable further cancellation and start cancel cleanup + // if cancel has been called and cancellation hasn't been started yet. + void testCancel(); + + // wait for thread to exit or for current thread to cancel + bool wait(CThreadRep*, double timeout); + + // set the priority + void setPriority(int n); + + // accessors + + // get the exit result for this thread. thread must be terminated. + void* getResult() const; + + // get the user data passed to the constructor + void* getUserData() const; + + // get the current cancellable state + bool isCancellable() const; + +#if defined(CONFIG_PTHREADS) + bool isExited() const; +#elif defined(CONFIG_PLATFORM_WIN32) + HANDLE getExitEvent() const; + HANDLE getCancelEvent() const; +#endif + + // return the thread rep for the calling thread. the returned + // rep has been ref()'d. + static CThreadRep* getCurrentThreadRep(); + + protected: + virtual ~CThreadRep(); + + private: + // internal constructor + CThreadRep(); + + // initialization/cleanup + void init(); + void fini(); + + // thread rep lookup + static CThreadRep* find(); + + // thread functions +#if defined(CONFIG_PTHREADS) + static void* threadFunc(void* arg); + static void threadCancel(int); +#elif defined(CONFIG_PLATFORM_WIN32) + static unsigned int __stdcall threadFunc(void* arg); +#endif + void doThreadFunc(); + + // not implemented + CThreadRep(const CThreadRep&); + CThreadRep& operator=(const CThreadRep&); + + private: + static CMutex s_mutex; + static CThreadRep* s_head; + + CThreadRep* m_prev; + CThreadRep* m_next; + + SInt32 m_refCount; + IJob* m_job; + void* m_userData; + void* m_result; + bool m_cancellable; + bool m_cancelling; + +#if defined(CONFIG_PTHREADS) + pthread_t m_thread; + bool m_exit; + bool m_cancel; +#endif + +#if defined(CONFIG_PLATFORM_WIN32) + HANDLE m_thread; + DWORD m_id; + HANDLE m_exit; + HANDLE m_cancel; +#endif +}; + +#endif diff --git a/mt/CTimerThread.cpp b/mt/CTimerThread.cpp new file mode 100644 index 00000000..afe2925c --- /dev/null +++ b/mt/CTimerThread.cpp @@ -0,0 +1,30 @@ +#include "CTimerThread.h" +#include "CThread.h" +#include "TMethodJob.h" +#include + +// +// CTimerThread +// + +CTimerThread::CTimerThread(double timeout) : m_timeout(timeout) +{ + assert(m_timeout > 0.0); + m_callingThread = new CThread(CThread::getCurrentThread()); + m_timingThread = new CThread(new TMethodJob( + this, &CTimerThread::timer)); +} + +CTimerThread::~CTimerThread() +{ + m_timingThread->cancel(); + delete m_timingThread; + delete m_callingThread; +} + +void CTimerThread::timer(void*) +{ + CThread::sleep(m_timeout); + m_callingThread->cancel(); +} + diff --git a/mt/CTimerThread.h b/mt/CTimerThread.h new file mode 100644 index 00000000..48ca64cb --- /dev/null +++ b/mt/CTimerThread.h @@ -0,0 +1,27 @@ +#ifndef CTIMERTHREAD_H +#define CTIMERTHREAD_H + +#include "common.h" + +class CThread; + +class CTimerThread { + public: + CTimerThread(double timeout); + ~CTimerThread(); + + private: + void timer(void*); + + // not implemented + CTimerThread(const CTimerThread&); + CTimerThread& operator=(const CTimerThread&); + + private: + double m_timeout; + CThread* m_callingThread; + CThread* m_timingThread; +}; + +#endif + diff --git a/mt/Makefile b/mt/Makefile new file mode 100644 index 00000000..1a7270c8 --- /dev/null +++ b/mt/Makefile @@ -0,0 +1,28 @@ +DEPTH=.. +include $(DEPTH)/Makecommon + +# +# target file +# +TARGET = mt + +# +# source files +# +LCXXINCS = \ + -I$(DEPTH)/base \ + $(NULL) +CXXFILES = \ + CLock.cpp \ + CMutex.cpp \ + CCondVar.cpp \ + CThread.cpp \ + CThreadRep.cpp \ + CTimerThread.cpp \ + $(NULL) + +targets: $(LIBTARGET) + +$(LIBTARGET): $(OBJECTS) + if test ! -d $(LIBDIR); then $(MKDIR) $(LIBDIR); fi + $(ARF) $(LIBTARGET) $(OBJECTS) diff --git a/mt/XThread.h b/mt/XThread.h new file mode 100644 index 00000000..3fd7441f --- /dev/null +++ b/mt/XThread.h @@ -0,0 +1,31 @@ +#ifndef XTHREAD_H +#define XTHREAD_H + +#include "common.h" + +// generic thread exception +class XThread { }; + +// thrown by CThread::Exit() to exit a thread. clients of CThread +// must not throw this type but must rethrow it if caught (by +// XThreadExit, XThread, or ...). +class XThreadExit : public XThread { + public: + XThreadExit(void* result) : m_result(result) { } + ~XThreadExit() { } + + public: + void* m_result; +}; + +// thrown to cancel a thread. clients must not throw this type, but +// must rethrow it if caught (by XThreadCancel, XThread, or ...). +class XThreadCancel : public XThread { }; + +// convenience macro to rethrow an XThread exception but ignore other +// exceptions. put this in your catch (...) handler after necessary +// cleanup but before leaving or returning from the handler. +#define RETHROW_XTHREAD \ + try { throw; } catch (XThread&) { throw; } catch (...) { } + +#endif diff --git a/net/CNetworkAddress.cpp b/net/CNetworkAddress.cpp new file mode 100644 index 00000000..024965ce --- /dev/null +++ b/net/CNetworkAddress.cpp @@ -0,0 +1,65 @@ +#include "CNetworkAddress.h" +#include +#include +#include +#include + +// +// CNetworkAddress +// + +CNetworkAddress::CNetworkAddress(UInt16 port) throw(XSocketAddress) +{ + if (port == 0) + throw XSocketAddress(XSocketAddress::kBadPort, CString(), port); + + struct sockaddr_in* inetAddress = reinterpret_cast(&m_address); + inetAddress->sin_family = AF_INET; + inetAddress->sin_port = htons(port); + inetAddress->sin_addr.s_addr = INADDR_ANY; + ::memset(inetAddress->sin_zero, 0, sizeof(inetAddress->sin_zero)); +} + +CNetworkAddress::CNetworkAddress(const CString& hostname, UInt16 port) + throw(XSocketAddress) +{ + if (port == 0) + throw XSocketAddress(XSocketAddress::kBadPort, hostname, port); + + struct hostent* hent = gethostbyname(hostname.c_str()); + if (hent == NULL) { + switch (h_errno) { + case HOST_NOT_FOUND: + throw XSocketAddress(XSocketAddress::kNotFound, hostname, port); + + case NO_DATA: + throw XSocketAddress(XSocketAddress::kNoAddress, hostname, port); + + case NO_RECOVERY: + case TRY_AGAIN: + default: + throw XSocketAddress(XSocketAddress::kUnknown, hostname, port); + } + } + + struct sockaddr_in* inetAddress = reinterpret_cast(&m_address); + inetAddress->sin_family = hent->h_addrtype; + inetAddress->sin_port = htons(port); + ::memcpy(&inetAddress->sin_addr, hent->h_addr_list[0], hent->h_length); + ::memset(inetAddress->sin_zero, 0, sizeof(inetAddress->sin_zero)); +} + +CNetworkAddress::~CNetworkAddress() +{ + // do nothing +} + +const struct sockaddr* CNetworkAddress::getAddress() const throw() +{ + return &m_address; +} + +int CNetworkAddress::getAddressLength() const throw() +{ + return sizeof(m_address); +} diff --git a/net/CNetworkAddress.h b/net/CNetworkAddress.h new file mode 100644 index 00000000..f9394b32 --- /dev/null +++ b/net/CNetworkAddress.h @@ -0,0 +1,27 @@ +#ifndef CNETWORKADDRESS_H +#define CNETWORKADDRESS_H + +#include "BasicTypes.h" +#include "XSocket.h" +#include + +class CString; + +class CNetworkAddress { + public: + CNetworkAddress(UInt16 port) throw(XSocketAddress); + CNetworkAddress(const CString& hostname, UInt16 port) throw(XSocketAddress); + ~CNetworkAddress(); + + // manipulators + + // accessors + + const struct sockaddr* getAddress() const throw(); + int getAddressLength() const throw(); + + private: + struct sockaddr m_address; +}; + +#endif diff --git a/net/CSocketInputStream.cpp b/net/CSocketInputStream.cpp new file mode 100644 index 00000000..d6f9d7ae --- /dev/null +++ b/net/CSocketInputStream.cpp @@ -0,0 +1,93 @@ +#include "CSocketInputStream.h" +#include "CLock.h" +#include "CMutex.h" +#include "CThread.h" +#include "IJob.h" +#include "XIO.h" +#include +#include + +// +// CSocketInputStream +// + +CSocketInputStream::CSocketInputStream(CMutex* mutex, IJob* closeCB) : + m_mutex(mutex), + m_empty(mutex, true), + m_closeCB(closeCB), + m_closed(false), + m_hungup(false) +{ + assert(m_mutex != NULL); +} + +CSocketInputStream::~CSocketInputStream() +{ + delete m_closeCB; +} + +void CSocketInputStream::write( + const void* data, UInt32 n) throw() +{ + if (!m_hungup && n > 0) { + m_buffer.write(data, n); + m_empty = (m_buffer.getSize() == 0); + m_empty.broadcast(); + } +} + +void CSocketInputStream::hangup() throw() +{ + m_hungup = true; + m_empty.broadcast(); +} + +void CSocketInputStream::close() throw(XIO) +{ + CLock lock(m_mutex); + if (m_closed) { + throw XIOClosed(); + } + + m_closed = true; + if (m_closeCB) { + m_closeCB->run(); + } +} + +UInt32 CSocketInputStream::read( + void* dst, UInt32 n) throw(XIO) +{ + CLock lock(m_mutex); + if (m_closed) { + throw XIOClosed(); + } + + // wait for data (or hangup) + while (!m_hungup && m_empty == true) { + m_empty.wait(); + } + + // read data + const UInt32 count = m_buffer.getSize(); + if (n > count) { + n = count; + } + if (n > 0) { + ::memcpy(dst, m_buffer.peek(n), n); + m_buffer.pop(n); + } + + // update empty state + if (m_buffer.getSize() == 0) { + m_empty = true; + m_empty.broadcast(); + } + return n; +} + +UInt32 CSocketInputStream::getSize() const throw() +{ + CLock lock(m_mutex); + return m_buffer.getSize(); +} diff --git a/net/CSocketInputStream.h b/net/CSocketInputStream.h new file mode 100644 index 00000000..46cb1c5b --- /dev/null +++ b/net/CSocketInputStream.h @@ -0,0 +1,42 @@ +#ifndef CSOCKETINPUTSTREAM_H +#define CSOCKETINPUTSTREAM_H + +#include "CSocketStreamBuffer.h" +#include "CCondVar.h" +#include "IInputStream.h" + +class CMutex; +class IJob; + +class CSocketInputStream : public IInputStream { + public: + CSocketInputStream(CMutex*, IJob* adoptedCloseCB); + ~CSocketInputStream(); + + // manipulators + + // write() appends n bytes to the buffer + void write(const void*, UInt32 n) throw(); + + // causes read() to always return immediately. if there is no + // more data then it returns 0. further writes are discarded. + void hangup() throw(); + + // accessors + + // IInputStream overrides + // these all lock the mutex for their duration + virtual void close() throw(XIO); + virtual UInt32 read(void*, UInt32 count) throw(XIO); + virtual UInt32 getSize() const throw(); + + private: + CMutex* m_mutex; + CCondVar m_empty; + IJob* m_closeCB; + CSocketStreamBuffer m_buffer; + bool m_closed; + bool m_hungup; +}; + +#endif diff --git a/net/CSocketOutputStream.cpp b/net/CSocketOutputStream.cpp new file mode 100644 index 00000000..a1d36c18 --- /dev/null +++ b/net/CSocketOutputStream.cpp @@ -0,0 +1,78 @@ +#include "CSocketOutputStream.h" +#include "CLock.h" +#include "CMutex.h" +#include "CThread.h" +#include "IJob.h" +#include "XIO.h" +#include + +// +// CSocketOutputStream +// + +CSocketOutputStream::CSocketOutputStream(CMutex* mutex, IJob* closeCB) : + m_mutex(mutex), + m_closeCB(closeCB), + m_closed(false) +{ + assert(m_mutex != NULL); +} + +CSocketOutputStream::~CSocketOutputStream() +{ + delete m_closeCB; +} + +const void* CSocketOutputStream::peek(UInt32 n) throw() +{ + return m_buffer.peek(n); +} + +void CSocketOutputStream::pop(UInt32 n) throw() +{ + m_buffer.pop(n); +} + +UInt32 CSocketOutputStream::getSize() const throw() +{ + return m_buffer.getSize(); +} + +void CSocketOutputStream::close() throw(XIO) +{ + CLock lock(m_mutex); + if (m_closed) { + throw XIOClosed(); + } + + m_closed = true; + if (m_closeCB) { + m_closeCB->run(); + } +} + +UInt32 CSocketOutputStream::write( + const void* data, UInt32 n) throw(XIO) +{ + CLock lock(m_mutex); + if (m_closed) { + throw XIOClosed(); + } + + m_buffer.write(data, n); + return n; +} + +void CSocketOutputStream::flush() throw(XIO) +{ + // wait until all data is written + while (getSizeWithLock() > 0) { + CThread::sleep(0.05); + } +} + +UInt32 CSocketOutputStream::getSizeWithLock() const throw() +{ + CLock lock(m_mutex); + return m_buffer.getSize(); +} diff --git a/net/CSocketOutputStream.h b/net/CSocketOutputStream.h new file mode 100644 index 00000000..d55443c7 --- /dev/null +++ b/net/CSocketOutputStream.h @@ -0,0 +1,43 @@ +#ifndef CSOCKETOUTPUTSTREAM_H +#define CSOCKETOUTPUTSTREAM_H + +#include "CSocketStreamBuffer.h" +#include "IOutputStream.h" + +class CMutex; +class IJob; + +class CSocketOutputStream : public IOutputStream { + public: + CSocketOutputStream(CMutex*, IJob* adoptedCloseCB); + ~CSocketOutputStream(); + + // manipulators + + // peek() returns a buffer of n bytes (which must be <= getSize()). + // pop() discards the next n bytes. + const void* peek(UInt32 n) throw(); + void pop(UInt32 n) throw(); + + // accessors + + // return the number of bytes in the buffer + UInt32 getSize() const throw(); + + // IOutputStream overrides + // these all lock the mutex for their duration + virtual void close() throw(XIO); + virtual UInt32 write(const void*, UInt32 count) throw(XIO); + virtual void flush() throw(XIO); + + private: + UInt32 getSizeWithLock() const throw(); + + private: + CMutex* m_mutex; + IJob* m_closeCB; + CSocketStreamBuffer m_buffer; + bool m_closed; +}; + +#endif diff --git a/net/CSocketStreamBuffer.cpp b/net/CSocketStreamBuffer.cpp new file mode 100644 index 00000000..0a2848ce --- /dev/null +++ b/net/CSocketStreamBuffer.cpp @@ -0,0 +1,106 @@ +#include "CSocketStreamBuffer.h" +#include + +// +// CSocketStreamBuffer +// + +const UInt32 CSocketStreamBuffer::kChunkSize = 4096; + +CSocketStreamBuffer::CSocketStreamBuffer() : m_size(0) +{ + // do nothing +} + +CSocketStreamBuffer::~CSocketStreamBuffer() +{ + // do nothing +} + +const void* CSocketStreamBuffer::peek(UInt32 n) throw() +{ + assert(n <= m_size); + + // reserve space in first chunk + ChunkList::iterator head = m_chunks.begin(); + head->reserve(n); + + // consolidate chunks into the first chunk until it has n bytes + ChunkList::iterator scan = head; + ++scan; + while (head->size() < n && scan != m_chunks.end()) { + head->insert(head->end(), scan->begin(), scan->end()); + scan = m_chunks.erase(scan); + } + + return reinterpret_cast(head->begin()); +} + +void CSocketStreamBuffer::pop(UInt32 n) throw() +{ + m_size -= n; + + // discard chunks until more than n bytes would've been discarded + ChunkList::iterator scan = m_chunks.begin(); + while (scan->size() <= n && scan != m_chunks.end()) { + n -= scan->size(); + scan = m_chunks.erase(scan); + } + + // if there's anything left over then remove it from the head chunk. + // if there's no head chunk then we're already empty. + if (scan == m_chunks.end()) { + m_size = 0; + } + else if (n > 0) { + scan->erase(scan->begin(), scan->begin() + n); + } +} + +void CSocketStreamBuffer::write( + const void* vdata, UInt32 n) throw() +{ + assert(vdata != NULL); + + if (n == 0) { + return; + } + m_size += n; + + // cast data to bytes + const UInt8* data = reinterpret_cast(vdata); + + // point to last chunk if it has space, otherwise append an empty chunk + ChunkList::iterator scan = m_chunks.end(); + if (scan != m_chunks.begin()) { + --scan; + if (scan->size() >= kChunkSize) + ++scan; + } + if (scan == m_chunks.end()) { + scan = m_chunks.insert(scan); + } + + // append data in chunks + while (n > 0) { + // choose number of bytes for next chunk + UInt32 count = kChunkSize - scan->size(); + if (count > n) + count = n; + + // transfer data + scan->insert(scan->end(), data, data + count); + n -= count; + data += count; + + // append another empty chunk if we're not done yet + if (n > 0) { + scan = m_chunks.insert(scan); + } + } +} + +UInt32 CSocketStreamBuffer::getSize() const throw() +{ + return m_size; +} diff --git a/net/CSocketStreamBuffer.h b/net/CSocketStreamBuffer.h new file mode 100644 index 00000000..ea6234b4 --- /dev/null +++ b/net/CSocketStreamBuffer.h @@ -0,0 +1,38 @@ +#ifndef CSOCKETSTREAMBUFFER_H +#define CSOCKETSTREAMBUFFER_H + +#include "BasicTypes.h" +#include +#include + +class CSocketStreamBuffer { + public: + CSocketStreamBuffer(); + ~CSocketStreamBuffer(); + + // manipulators + + // peek() returns a buffer of n bytes (which must be <= getSize()). + // pop() discards the next n bytes. + const void* peek(UInt32 n) throw(); + void pop(UInt32 n) throw(); + + // write() appends n bytes to the buffer + void write(const void*, UInt32 n) throw(); + + // accessors + + // return the number of bytes in the buffer + UInt32 getSize() const throw(); + + private: + static const UInt32 kChunkSize; + + typedef std::vector Chunk; + typedef std::list ChunkList; + + ChunkList m_chunks; + UInt32 m_size; +}; + +#endif diff --git a/net/CTCPListenSocket.cpp b/net/CTCPListenSocket.cpp new file mode 100644 index 00000000..959e8c52 --- /dev/null +++ b/net/CTCPListenSocket.cpp @@ -0,0 +1,72 @@ +#include "CTCPListenSocket.h" +#include "CTCPSocket.h" +#include "CNetworkAddress.h" +#include "CThread.h" +#include +#include +#include +#include +#include + +// +// CTCPListenSocket +// + +CTCPListenSocket::CTCPListenSocket() +{ + m_fd = socket(PF_INET, SOCK_STREAM, 0); + if (m_fd == -1) { + throw XSocketCreate(); + } +} + +CTCPListenSocket::~CTCPListenSocket() +{ + try { + close(); + } + catch (...) { + // ignore + } +} + +void CTCPListenSocket::bind( + const CNetworkAddress& addr) throw(XSocket) +{ + if (::bind(m_fd, addr.getAddress(), addr.getAddressLength()) == -1) { + if (errno == EADDRINUSE) { + throw XSocketAddressInUse(); + } + throw XSocketBind(); + } + if (listen(m_fd, 3) == -1) { + throw XSocketBind(); + } +} + +ISocket* CTCPListenSocket::accept() throw(XSocket) +{ + for (;;) { + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + CThread::testCancel(); + int fd = ::accept(m_fd, &addr, &addrlen); + if (fd == -1) { + CThread::testCancel(); + } + else { + return new CTCPSocket(fd); + } + } +} + +void CTCPListenSocket::close() throw(XIO) +{ + if (m_fd == -1) { + throw XIOClosed(); + } + if (::close(m_fd) == -1) { + throw XIOClose(); + } + m_fd = -1; +} diff --git a/net/CTCPListenSocket.h b/net/CTCPListenSocket.h new file mode 100644 index 00000000..8807fba8 --- /dev/null +++ b/net/CTCPListenSocket.h @@ -0,0 +1,24 @@ +#ifndef CTCPLISTENSOCKET_H +#define CTCPLISTENSOCKET_H + +#include "IListenSocket.h" + +class CTCPListenSocket : public IListenSocket { + public: + CTCPListenSocket(); + ~CTCPListenSocket(); + + // manipulators + + // accessors + + // IListenSocket overrides + virtual void bind(const CNetworkAddress&) throw(XSocket); + virtual ISocket* accept() throw(XSocket); + virtual void close() throw(XIO); + + private: + int m_fd; +}; + +#endif diff --git a/net/CTCPSocket.cpp b/net/CTCPSocket.cpp new file mode 100644 index 00000000..86a1b0bd --- /dev/null +++ b/net/CTCPSocket.cpp @@ -0,0 +1,226 @@ +#include "CTCPSocket.h" +#include "CBufferedInputStream.h" +#include "CBufferedOutputStream.h" +#include "CNetworkAddress.h" +#include "CLock.h" +#include "CMutex.h" +#include "CCondVar.h" +#include "CThread.h" +#include "TMethodJob.h" +#include "CStopwatch.h" +#include +#include +#include +#include +#include +#include +#include + +// +// CTCPSocket +// + +CTCPSocket::CTCPSocket() throw(XSocket) +{ + m_fd = socket(PF_INET, SOCK_STREAM, 0); + if (m_fd == -1) { + throw XSocketCreate(); + } + init(); +} + +CTCPSocket::CTCPSocket(int fd) throw() : + m_fd(fd) +{ + assert(m_fd != -1); + + init(); + + // socket starts in connected state + m_connected = kReadWrite; + + // start handling socket + m_thread = new CThread(new TMethodJob( + this, &CTCPSocket::service)); +} + +CTCPSocket::~CTCPSocket() +{ + try { + close(); + } + catch (...) { + // ignore failures + } + + // clean up + delete m_mutex; + delete m_input; + delete m_output; +} + +void CTCPSocket::bind(const CNetworkAddress& addr) + throw(XSocket) +{ + if (::bind(m_fd, addr.getAddress(), addr.getAddressLength()) == -1) { + if (errno == EADDRINUSE) { + throw XSocketAddressInUse(); + } + throw XSocketBind(); + } +} + +void CTCPSocket::connect(const CNetworkAddress& addr) + throw(XSocket) +{ + CThread::testCancel(); + if (::connect(m_fd, addr.getAddress(), addr.getAddressLength()) == -1) { + CThread::testCancel(); + throw XSocketConnect(); + } + + // start servicing the socket + m_connected = kReadWrite; + m_thread = new CThread(new TMethodJob( + this, &CTCPSocket::service)); +} + +void CTCPSocket::close() throw(XIO) +{ + // shutdown I/O thread before close + if (m_thread != NULL) { + // flush if output buffer not empty and output buffer not closed + bool doFlush; + { + CLock lock(m_mutex); + doFlush = ((m_connected & kWrite) != 0); + } + if (doFlush) { + m_output->flush(); + } + + m_thread->cancel(); + m_thread->wait(); + delete m_thread; + m_thread = NULL; + } + + CLock lock(m_mutex); + if (m_fd != -1) { + if (::close(m_fd) == -1) { + throw XIOClose(); + } + m_fd = -1; + } +} + +IInputStream* CTCPSocket::getInputStream() throw() +{ + return m_input; +} + +IOutputStream* CTCPSocket::getOutputStream() throw() +{ + return m_output; +} + +void CTCPSocket::init() throw(XIO) +{ + m_mutex = new CMutex; + m_thread = NULL; + m_connected = kClosed; + m_input = new CBufferedInputStream(m_mutex, + new TMethodJob( + this, &CTCPSocket::closeInput)); + m_output = new CBufferedOutputStream(m_mutex, + new TMethodJob( + this, &CTCPSocket::closeOutput)); +} + +void CTCPSocket::service(void*) throw(XThread) +{ + assert(m_fd != -1); + + // now service the connection + struct pollfd pfds[1]; + pfds[0].fd = m_fd; + for (;;) { + { + // choose events to poll for + CLock lock(m_mutex); + pfds[0].events = 0; + if ((m_connected & kRead) != 0) { + // still open for reading + pfds[0].events |= POLLIN; + } + if ((m_connected & kWrite) != 0 && m_output->getSize() > 0) { + // data queued for writing + pfds[0].events |= POLLOUT; + } + } + + // check for status + CThread::testCancel(); + const int status = poll(pfds, 1, 50); + CThread::testCancel(); + + // transfer data and handle errors + if (status == 1) { + if ((pfds[0].revents & (POLLERR | POLLNVAL)) != 0) { + // stream is no good anymore so bail + m_input->hangup(); + return; + } + + // read some data + if (pfds[0].revents & POLLIN) { + UInt8 buffer[4096]; + ssize_t n = read(m_fd, buffer, sizeof(buffer)); + if (n > 0) { + CLock lock(m_mutex); + m_input->write(buffer, n); + } + else if (n == 0) { + // stream hungup + m_input->hangup(); + return; + } + } + + // write some data + if (pfds[0].revents & POLLOUT) { + CLock lock(m_mutex); + + // get amount of data to write + UInt32 n = m_output->getSize(); + if (n > 4096) { + // limit write size + n = 4096; + } + + // write data + const void* buffer = m_output->peek(n); + n = write(m_fd, buffer, n); + + // discard written data + if (n > 0) { + m_output->pop(n); + } + } + } + } +} + +void CTCPSocket::closeInput(void*) throw() +{ + // note -- m_mutex should already be locked + shutdown(m_fd, 0); + m_connected &= ~kRead; +} + +void CTCPSocket::closeOutput(void*) throw() +{ + // note -- m_mutex should already be locked + shutdown(m_fd, 1); + m_connected &= ~kWrite; +} diff --git a/net/CTCPSocket.h b/net/CTCPSocket.h new file mode 100644 index 00000000..8218bbad --- /dev/null +++ b/net/CTCPSocket.h @@ -0,0 +1,49 @@ +#ifndef CTCPSOCKET_H +#define CTCPSOCKET_H + +#include "ISocket.h" +#include "XThread.h" + +class CMutex; +template +class CCondVar; +class CThread; +class CBufferedInputStream; +class CBufferedOutputStream; + +class CTCPSocket : public ISocket { + public: + CTCPSocket() throw(XSocket); + CTCPSocket(int fd) throw(); + ~CTCPSocket(); + + // manipulators + + // accessors + + // ISocket overrides + virtual void bind(const CNetworkAddress&) throw(XSocket); + virtual void connect(const CNetworkAddress&) throw(XSocket); + virtual void close() throw(XIO); + virtual IInputStream* getInputStream() throw(); + virtual IOutputStream* getOutputStream() throw(); + + private: + void init() throw(XIO); + void service(void*) throw(XThread); + void closeInput(void*) throw(); + void closeOutput(void*) throw(); + + private: + enum { kClosed = 0, kRead = 1, kWrite = 2, kReadWrite = 3 }; + + int m_fd; + CBufferedInputStream* m_input; + CBufferedOutputStream* m_output; + + CMutex* m_mutex; + CThread* m_thread; + UInt32 m_connected; +}; + +#endif diff --git a/net/IListenSocket.h b/net/IListenSocket.h new file mode 100644 index 00000000..eb8d7903 --- /dev/null +++ b/net/IListenSocket.h @@ -0,0 +1,27 @@ +#ifndef ILISTENSOCKET_H +#define ILISTENSOCKET_H + +#include "IInterface.h" +#include "XIO.h" +#include "XSocket.h" + +class CNetworkAddress; +class ISocket; + +class IListenSocket : public IInterface { + public: + // manipulators + + // bind the socket to a particular address + virtual void bind(const CNetworkAddress&) throw(XSocket) = 0; + + // wait for a connection + virtual ISocket* accept() throw(XSocket) = 0; + + // close the socket + virtual void close() throw(XIO) = 0; + + // accessors +}; + +#endif diff --git a/net/ISocket.h b/net/ISocket.h new file mode 100644 index 00000000..98d1fd45 --- /dev/null +++ b/net/ISocket.h @@ -0,0 +1,35 @@ +#ifndef ISOCKET_H +#define ISOCKET_H + +#include "IInterface.h" +#include "BasicTypes.h" +#include "XSocket.h" +#include "XIO.h" + +class CNetworkAddress; +class IInputStream; +class IOutputStream; + +class ISocket : public IInterface { + public: + // manipulators + + // bind the socket to a particular address + virtual void bind(const CNetworkAddress&) throw(XSocket) = 0; + + // connect the socket + virtual void connect(const CNetworkAddress&) throw(XSocket) = 0; + + // close the socket. this will flush the output stream if it + // hasn't been closed yet. + virtual void close() throw(XIO) = 0; + + // get the input and output streams for the socket. closing + // these streams closes the appropriate half of the socket. + virtual IInputStream* getInputStream() throw() = 0; + virtual IOutputStream* getOutputStream() throw() = 0; + + // accessors +}; + +#endif diff --git a/net/Makefile b/net/Makefile new file mode 100644 index 00000000..5b9534f1 --- /dev/null +++ b/net/Makefile @@ -0,0 +1,28 @@ +DEPTH=.. +include $(DEPTH)/Makecommon + +# +# target file +# +TARGET = net + +# +# source files +# +LCXXINCS = \ + -I$(DEPTH)/base \ + -I$(DEPTH)/mt \ + -I$(DEPTH)/io \ + $(NULL) +CXXFILES = \ + XSocket.cpp \ + CNetworkAddress.cpp \ + CTCPSocket.cpp \ + CTCPListenSocket.cpp \ + $(NULL) + +targets: $(LIBTARGET) + +$(LIBTARGET): $(OBJECTS) + if test ! -d $(LIBDIR); then $(MKDIR) $(LIBDIR); fi + $(ARF) $(LIBTARGET) $(OBJECTS) diff --git a/net/XSocket.cpp b/net/XSocket.cpp new file mode 100644 index 00000000..d5478908 --- /dev/null +++ b/net/XSocket.cpp @@ -0,0 +1,84 @@ +#include "XSocket.h" + +// +// XSocketAddress +// + +XSocketAddress::XSocketAddress(Error error, + const CString& hostname, SInt16 port) throw() : + m_error(error), + m_hostname(hostname), + m_port(port) +{ + // do nothing +} + +XSocketAddress::Error XSocketAddress::getError() const throw() +{ + return m_error; +} + +CString XSocketAddress::getHostname() const throw() +{ + return m_hostname; +} + +SInt16 XSocketAddress::getPort() const throw() +{ + return m_port; +} + +CString XSocketAddress::getWhat() const throw() +{ + return "no address"; +/* + return format("XSocketAddress", "no address: %1:%2", + m_hostname.t_str(), + CString::sprintf("%d", m_port).t_str()); +*/ +} + + +// +// XSocketErrno +// + +XSocketErrno::XSocketErrno() : MXErrno() +{ + // do nothing +} + +XSocketErrno::XSocketErrno(int err) : MXErrno(err) +{ + // do nothing +} + + +// +// XSocketBind +// + +CString XSocketBind::getWhat() const throw() +{ + return format("XSocketBind", "cannot bind address"); +} + + +// +// XSocketConnect +// + +CString XSocketConnect::getWhat() const throw() +{ + return format("XSocketConnect", "cannot connect socket"); +} + + +// +// XSocketCreate +// + +CString XSocketCreate::getWhat() const throw() +{ + return format("XSocketCreate", "cannot create socket"); +} diff --git a/net/XSocket.h b/net/XSocket.h new file mode 100644 index 00000000..578009fa --- /dev/null +++ b/net/XSocket.h @@ -0,0 +1,58 @@ +#ifndef XSOCKET_H +#define XSOCKET_H + +#include "CString.h" +#include "XBase.h" +#include "BasicTypes.h" + +class XSocket : public XBase { }; + +class XSocketAddress : public XSocket { + public: + enum Error { kUnknown, kNotFound, kNoAddress, kBadPort }; + + XSocketAddress(Error, const CString& hostname, SInt16 port) throw(); + + // accessors + + virtual Error getError() const throw(); + virtual CString getHostname() const throw(); + virtual SInt16 getPort() const throw(); + + protected: + // XBase overrides + virtual CString getWhat() const throw(); + + private: + Error m_error; + CString m_hostname; + SInt16 m_port; +}; + +class XSocketErrno : public XSocket, public MXErrno { + public: + XSocketErrno(); + XSocketErrno(int); +}; + +class XSocketBind : public XSocketErrno { + protected: + // XBase overrides + virtual CString getWhat() const throw(); +}; + +class XSocketAddressInUse : public XSocketBind { }; + +class XSocketConnect : public XSocketErrno { + protected: + // XBase overrides + virtual CString getWhat() const throw(); +}; + +class XSocketCreate : public XSocketErrno { + protected: + // XBase overrides + virtual CString getWhat() const throw(); +}; + +#endif diff --git a/notes b/notes new file mode 100644 index 00000000..0effd884 --- /dev/null +++ b/notes @@ -0,0 +1,85 @@ +throw specs: + must add XThread if method can be cancelled + must add out-of-memory exception if method allocs and returns an object + should catch and handle out-of-mem if object wasn't going to be returned + +CServer + * must have connection thread in screen info map + allows disconnect if screen map changes and screen is removed + * put mutex locks wherever necessary (like accessing m_active) + +CClient + * need thread to handle screen + * need methods for screen event handler to call as appropriate + +server client +------ ------ +[accept] <-- connect +challenge --> [encrypt] +[verify] <-- response (encrypted challenge, client name) +hangup if invalid +query info --> + <-- info (size) + +... +enter (x,y) --> +clipboard data --> optional +mouse/key events --> optional +query clipboard --> optional + <-- clipboard data (cont.) +leave --> + +... +grab clipboard --> + +... (on clipboard ownership stolen) + <-- clipboard lost + +... (on screen resize) + <-- info (size) + +... (on screen saver, primary screen) +saver (on/off) --> + +... +quit --> + <-- close + +--- +primary screen + open + close + enter + leave + warp + clipboard (get/set) + screen saver (show/hide) + queue events with server (including screen saver activation) + +secondary screen + open + close + enter + leave + warp + synth mouse + synth key + clipboard (get/set) + screen saver (show/hide) + queue events with client (clipboard lost/changed, size change) + +--- +client: + open + close + wait: server messages, clipboard taken, screen resize, quit + +server: + accept + asynchronously accept new clients + config + asynchronously accept and handle config message (via HTTP) + primary + asynchronously handle primary screen events + comm + send/recv messages to/from clients diff --git a/synergy/CClient.cpp b/synergy/CClient.cpp new file mode 100644 index 00000000..bd4e011b --- /dev/null +++ b/synergy/CClient.cpp @@ -0,0 +1,265 @@ +#include "CClient.h" +#include "CInputPacketStream.h" +#include "COutputPacketStream.h" +#include "CProtocolUtil.h" +#include "CTimerThread.h" +#include + +// +// CClient +// + +CClient::CClient(const CString& clientName) : + m_name(clientName) +{ +} + +CClient::~CClient() +{ +} + +#include "CTCPSocket.h" +void CClient::run(const CNetworkAddress& serverAddress) +{ + std::auto_ptr socket; + std::auto_ptr input; + std::auto_ptr output; + try { + // allow connect this much time to succeed + CTimerThread timer(30.0); // FIXME -- timeout in member + + // create socket and attempt to connect to server + socket.reset(new CTCPSocket()); // FIXME -- use factory + socket->connect(serverAddress); + + // get the input and output streams + IInputStream* srcInput = socket->getInputStream(); + IOutputStream* srcOutput = socket->getOutputStream(); + + // attach the encryption layer +/* FIXME -- implement ISecurityFactory + if (m_securityFactory != NULL) { + input.reset(m_securityFactory->createInputFilter(srcInput, false)); + output.reset(m_securityFactory->createOutputFilter(srcOutput, false)); + srcInput = input.get(); + srcOutput = output.get(); + } +*/ + + // attach the packetizing filters + input.reset(new CInputPacketStream(srcInput, true)); + output.reset(new COutputPacketStream(srcOutput, true)); + + // wait for hello from server + SInt32 major, minor; + CProtocolUtil::readf(input.get(), "Synergy%2i%2i", &major, &minor); + + // check versions + if (major < kMajorVersion || + (major == kMajorVersion && minor < kMinorVersion)) { + throw XIncompatibleClient(major, minor); + } + + // say hello back + CProtocolUtil::writef(output.get(), "Synergy%2i%2i%s", + kMajorVersion, kMinorVersion, + m_name.size(), m_name.data()); + + // record streams in a more useful place + m_input = input.get(); + m_output = output.get(); + } + catch (XIncompatibleClient& e) { + fprintf(stderr, "incompatible server version (%d.%d)\n", + e.getMajor(), e.getMinor()); + return; + } + catch (XThread&) { + fprintf(stderr, "connection timed out\n"); + throw; + } + catch (XBase& e) { + fprintf(stderr, "connection failed: %s\n", e.what()); + return; + } + + // connect to screen + // FIXME -- make object that closes and destroys screen in + // it's d'tor. screen must not be handling event queue by + // the time the streams are destroyed. + + // handle messages from server + try { + for (;;) { + // wait for reply + UInt8 code[4]; + UInt32 n = input->read(code, 4); + + // verify we got an entire code + if (n == 0) { + // server hungup + break; + } + if (n != 4) { + // client sent an incomplete message + fprintf(stderr, "incomplete message from server\n"); + break; + } + + // parse message + if (memcmp(code, kMsgDMouseMove, 4) == 0) { + onMouseMove(input.get()); + } + else if (memcmp(code, kMsgDMouseWheel, 4) == 0) { + onMouseWheel(input.get()); + } + else if (memcmp(code, kMsgDKeyDown, 4) == 0) { + onKeyDown(input.get()); + } + else if (memcmp(code, kMsgDKeyUp, 4) == 0) { + onKeyUp(input.get()); + } + else if (memcmp(code, kMsgDMouseDown, 4) == 0) { + onMouseDown(input.get()); + } + else if (memcmp(code, kMsgDMouseUp, 4) == 0) { + onMouseUp(input.get()); + } + else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) { + onKeyRepeat(input.get()); + } + else if (memcmp(code, kMsgCEnter, 4) == 0) { + onEnter(input.get()); + } + else if (memcmp(code, kMsgCLeave, 4) == 0) { + onLeave(input.get()); + } + else if (memcmp(code, kMsgCClipboard, 4) == 0) { + onGrabClipboard(input.get()); + } + else if (memcmp(code, kMsgCScreenSaver, 4) == 0) { + onScreenSaver(input.get()); + } + else if (memcmp(code, kMsgQInfo, 4) == 0) { + onQueryInfo(input.get()); + } + else if (memcmp(code, kMsgQClipboard, 4) == 0) { + onQueryClipboard(input.get()); + } + else if (memcmp(code, kMsgDClipboard, 4) == 0) { + onSetClipboard(input.get()); + } + else if (memcmp(code, kMsgCClose, 4) == 0) { + // server wants us to hangup + break; + } + else { + // unknown message + fprintf(stderr, "unknown message from server\n"); + break; + } + } + + // done with socket + m_socket->close(); + } + catch (XBase& e) { + fprintf(stderr, "error: %s\n", e.what()); + return; + } +} + +void CClient::onEnter() +{ + SInt32 x, y; + CProtocolUtil::readf(m_input, kMsgCEnter + 4, &x, &y); + m_screen->enter(x, y); +} + +void CClient::onLeave() +{ + m_screen->leave(); +} + +void CClient::onGrabClipboard() +{ + // FIXME +} + +void CClient::onScreenSaver() +{ + SInt32 on; + CProtocolUtil::readf(m_input, kMsgCScreenSaver + 4, &on); + // FIXME +} + +void CClient::onQueryInfo() +{ + SInt32 w, h; + m_screen->getSize(w, h); + SInt32 zoneSize = m_screen->getJumpZoneSize(); + CProtocolUtil::writef(m_output, kMsgDInfo, w, h, zoneSize); +} + +void CClient::onQueryClipboard() +{ + // FIXME +} + +void CClient::onSetClipboard() +{ + // FIXME +} + +void CClient::onKeyDown() +{ + SInt32 id, mask; + CProtocolUtil::readf(m_input, kMsgDKeyDown + 4, &id, &mask); + m_screen->onKeyDown(reinterpret_cast(id), + reinterpret_cast(mask)); +} + +void CClient::onKeyRepeat() +{ + SInt32 id, mask, count; + CProtocolUtil::readf(m_input, kMsgDKeyRepeat + 4, &id, &mask, &count); + m_screen->onKeyRepeat(reinterpret_cast(id), + reinterpret_cast(mask), + count); +} + +void CClient::onKeyUp() +{ + SInt32 id, mask; + CProtocolUtil::readf(m_input, kMsgDKeyUp + 4, &id, &mask); + m_screen->onKeyUp(reinterpret_cast(id), + reinterpret_cast(mask)); +} + +void CClient::onMouseDown() +{ + SInt32 id; + CProtocolUtil::readf(m_input, kMsgDMouseDown + 4, &id); + m_screen->onMouseDown(reinterpret_cast(id)); +} + +void CClient::onMouseUp() +{ + SInt32 id; + CProtocolUtil::readf(m_input, kMsgDMouseUp + 4, &id); + m_screen->onMouseUp(reinterpret_cast(id)); +} + +void CClient::onMouseMove() +{ + SInt32 x, y; + CProtocolUtil::readf(m_input, kMsgDMouseMove + 4, &x, &y); + m_screen->onMouseMove(x, y); +} + +void CClient::onMouseWheel() +{ + SInt32 delta; + CProtocolUtil::readf(m_input, kMsgDMouseWheel + 4, &delta); + m_screen->onMouseWheel(delta); +} diff --git a/synergy/CClient.h b/synergy/CClient.h new file mode 100644 index 00000000..7ad0b7f7 --- /dev/null +++ b/synergy/CClient.h @@ -0,0 +1,46 @@ +#ifndef CCLIENT_H +#define CCLIENT_H + +#include "CString.h" +#include "BasicTypes.h" + +class CNetworkAddress; +class IInputStream; +class IOutputStream; + +class CClient { + public: + CClient(const CString& clientName); + ~CClient(); + + // manipulators + + void run(const CNetworkAddress& serverAddress); + + // accessors + + + private: + // message handlers + void onEnter(); + void onLeave(); + void onGrabClipboard(); + void onScreenSaver(); + void onQueryInfo(); + void onQueryClipboard(); + void onSetClipboard(); + void onKeyDown(); + void onKeyRepeat(); + void onKeyUp(); + void onMouseDown(); + void onMouseUp(); + void onMouseMove(); + void onMouseWheel(); + + private: + CString m_name; + IInputStream* m_output; + IOutputStream* m_output; +}; + +#endif diff --git a/synergy/CInputPacketStream.cpp b/synergy/CInputPacketStream.cpp new file mode 100644 index 00000000..c7642e45 --- /dev/null +++ b/synergy/CInputPacketStream.cpp @@ -0,0 +1,109 @@ +#include "CInputPacketStream.h" +#include "CLock.h" +#include + +// +// 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() throw(XIO) +{ + getStream()->close(); +} + +UInt32 CInputPacketStream::read( + void* buffer, UInt32 n) throw(XIO) +{ + CLock lock(&m_mutex); + + // wait for entire message to be read. return immediately if + // stream hungup. + if (getSizeNoLock() == 0) { + return 0; + } + + // 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); + assert(n <= m_size); + m_size -= n; + + return n; +} + +UInt32 CInputPacketStream::getSize() const throw() +{ + CLock lock(&m_mutex); + return getSizeNoLock(); +} + +UInt32 CInputPacketStream::getSizeNoLock() const throw() +{ + while (!hasFullMessage()) { + // read more data + char buffer[4096]; + UInt32 n = getStream()->read(buffer, sizeof(buffer)); + + // return if stream hungup + if (n == 0) { + m_buffer.hangup(); + return 0; + } + + // append to our buffer + m_buffer.write(buffer, n); + } + + return m_size; +} + +bool CInputPacketStream::hasFullMessage() const throw() +{ + // 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)); + 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/synergy/CInputPacketStream.h b/synergy/CInputPacketStream.h new file mode 100644 index 00000000..5bd31772 --- /dev/null +++ b/synergy/CInputPacketStream.h @@ -0,0 +1,33 @@ +#ifndef CINPUTPACKETSTREAM_H +#define CINPUTPACKETSTREAM_H + +#include "CInputStreamFilter.h" +#include "CBufferedInputStream.h" +#include "CMutex.h" + +class CInputPacketStream : public CInputStreamFilter { + public: + CInputPacketStream(IInputStream*, bool adoptStream = true); + ~CInputPacketStream(); + + // manipulators + + // accessors + + // IInputStream overrides + virtual void close() throw(XIO); + virtual UInt32 read(void*, UInt32 maxCount) throw(XIO); + virtual UInt32 getSize() const throw(); + + private: + UInt32 getSizeNoLock() const throw(); + bool hasFullMessage() const throw(); + + private: + CMutex m_mutex; + mutable UInt32 m_size; + mutable CBufferedInputStream m_buffer; +}; + +#endif + diff --git a/synergy/COutputPacketStream.cpp b/synergy/COutputPacketStream.cpp new file mode 100644 index 00000000..cffd050a --- /dev/null +++ b/synergy/COutputPacketStream.cpp @@ -0,0 +1,56 @@ +#include "COutputPacketStream.h" + +// +// COuputPacketStream +// + +COutputPacketStream::COutputPacketStream(IOutputStream* stream, bool adopt) : + COutputStreamFilter(stream, adopt) +{ + // do nothing +} + +COutputPacketStream::~COutputPacketStream() +{ + // do nothing +} + +void COutputPacketStream::close() throw(XIO) +{ + getStream()->close(); +} + +UInt32 COutputPacketStream::write( + const void* buffer, UInt32 count) throw(XIO) +{ + // 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() throw(XIO) +{ + getStream()->flush(); +} + diff --git a/synergy/COutputPacketStream.h b/synergy/COutputPacketStream.h new file mode 100644 index 00000000..20293386 --- /dev/null +++ b/synergy/COutputPacketStream.h @@ -0,0 +1,21 @@ +#ifndef COUTPUTPACKETSTREAM_H +#define COUTPUTPACKETSTREAM_H + +#include "COutputStreamFilter.h" + +class COutputPacketStream : public COutputStreamFilter { + public: + COutputPacketStream(IOutputStream*, bool adoptStream = true); + ~COutputPacketStream(); + + // manipulators + + // accessors + + // IOutputStream overrides + virtual void close() throw(XIO); + virtual UInt32 write(const void*, UInt32 count) throw(XIO); + virtual void flush() throw(XIO); +}; + +#endif diff --git a/synergy/CProtocolUtil.cpp b/synergy/CProtocolUtil.cpp new file mode 100644 index 00000000..7ebed7b2 --- /dev/null +++ b/synergy/CProtocolUtil.cpp @@ -0,0 +1,336 @@ +#include "CProtocolUtil.h" +#include "IInputStream.h" +#include "IOutputStream.h" +#include +#include +#include + +// +// CProtocolUtil +// + +void CProtocolUtil::writef(IOutputStream* stream, + const char* fmt, ...) throw(XIO) +{ + assert(stream != NULL); + assert(fmt != NULL); + + va_list args; + + // determine total size to write + va_start(args, fmt); + UInt32 count = 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); + va_end(args); + + // write buffer + UInt8* scan = buffer; + while (count > 0) { + const UInt32 n = stream->write(scan, n); + count -= n; + scan += n; + } + + delete[] buffer; +} + +void CProtocolUtil::readf(IInputStream* stream, + const char* fmt, ...) throw(XIO) +{ + assert(stream != NULL); + assert(fmt != NULL); + + va_list args; + + // begin scanning + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': { + // check for valid length + assert(len == 1 || len == 2 || len == 4); + + // read the data + UInt8 buffer[4]; + read(stream, buffer, len); + + // convert it + UInt32* v = va_arg(args, UInt32*); + switch (len) { + case 1: + // 1 byte integer + *v = static_cast(buffer[0]); + break; + + case 2: + // 2 byte integer + *v = (static_cast(buffer[0]) << 8) | + static_cast(buffer[1]); + break; + + case 4: + // 4 byte integer + *v = (static_cast(buffer[0]) << 24) | + (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3]); + break; + } + break; + } + + case 's': { + assert(len == 0); + + // read the string length + UInt8 buffer[128]; + read(stream, buffer, 4); + UInt32 len = (static_cast(buffer[0]) << 24) | + (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3]); + + // use a fixed size buffer if its big enough + const bool useFixed = (len <= sizeof(buffer)); + + // allocate a buffer to read the data + UInt8* sBuffer; + if (useFixed) { + sBuffer = buffer; + } + else { + sBuffer = new UInt8[len]; + } + + // read the data + try { + read(stream, sBuffer, len); + } + catch (...) { + if (!useFixed) { + delete[] sBuffer; + } + throw; + } + + // save the data + CString* dst = va_arg(args, CString*); + dst->assign((const char*)sBuffer, len); + + // release the buffer + if (!useFixed) { + delete[] sBuffer; + } + break; + } + + case '%': + assert(len == 0); + break; + + default: + assert(0 && "invalid format specifier"); + } + + // next format character + ++fmt; + } + else { + // read next character + char buffer[1]; + read(stream, buffer, 1); + + // verify match + if (buffer[0] != *fmt) { + throw XIOReadMismatch(); + } + + // next format character + ++fmt; + } + } +} + +UInt32 CProtocolUtil::getLength( + const char* fmt, va_list args) throw() +{ + UInt32 n = 0; + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': + assert(len == 1 || len == 2 || len == 4); + (void)va_arg(args, UInt32); + break; + + case 's': + assert(len == 0); + len = va_arg(args, UInt32) + 4; + (void)va_arg(args, UInt8*); + break; + + case '%': + assert(len == 0); + len = 1; + break; + + default: + assert(0 && "invalid format specifier"); + } + + // accumulate size + n += len; + ++fmt; + } + else { + // regular character + ++n; + ++fmt; + } + } + return n; +} + +void CProtocolUtil::writef(void* buffer, + const char* fmt, va_list args) throw(XIO) +{ + UInt8* dst = reinterpret_cast(buffer); + + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': { + const UInt32 v = va_arg(args, UInt32); + switch (len) { + case 1: + // 1 byte integer + *dst++ = static_cast(v & 0xff); + break; + + case 2: + // 2 byte integer + *dst++ = static_cast((v >> 8) & 0xff); + *dst++ = static_cast( v & 0xff); + break; + + case 4: + // 4 byte integer + *dst++ = static_cast((v >> 24) & 0xff); + *dst++ = static_cast((v >> 16) & 0xff); + *dst++ = static_cast((v >> 8) & 0xff); + *dst++ = static_cast( v & 0xff); + break; + + default: + assert(0 && "invalid integer format length"); + return; + } + break; + } + + case 's': { + assert(len == 0); + const UInt32 len = va_arg(args, UInt32); + const UInt8* src = va_arg(args, UInt8*); + *dst++ = static_cast((len >> 24) & 0xff); + *dst++ = static_cast((len >> 16) & 0xff); + *dst++ = static_cast((len >> 8) & 0xff); + *dst++ = static_cast( len & 0xff); + memcpy(dst, src, len); + dst += len; + break; + } + + case '%': + assert(len == 0); + *dst++ = '%'; + break; + + default: + assert(0 && "invalid format specifier"); + } + + // next format character + ++fmt; + } + else { + // copy regular character + *dst++ = *fmt++; + } + } +} + +UInt32 CProtocolUtil::eatLength(const char** pfmt) throw() +{ + const char* fmt = *pfmt; + UInt32 n = 0; + for (;;) { + UInt32 d; + switch (*fmt) { + case '0': d = 0; break; + case '1': d = 1; break; + case '2': d = 2; break; + case '3': d = 3; break; + case '4': d = 4; break; + case '5': d = 5; break; + case '6': d = 6; break; + case '7': d = 7; break; + case '8': d = 8; break; + case '9': d = 9; break; + default: *pfmt = fmt; return n; + } + n = 10 * n + d; + ++fmt; + } +} + +void CProtocolUtil::read(IInputStream* stream, + void* vbuffer, UInt32 count) throw(XIO) +{ + assert(stream != NULL); + assert(vbuffer != NULL); + + UInt8* buffer = reinterpret_cast(vbuffer); + while (count > 0) { + // read more + UInt32 n = stream->read(buffer, count); + + // bail if stream has hungup + if (n == 0) { + throw XIOEndOfStream(); + } + + // prepare for next read + buffer += n; + count -= n; + } +} + + +// +// XIOReadMismatch +// + +CString XIOReadMismatch::getWhat() const throw() +{ + return "CProtocolUtil::readf() mismatch"; +} diff --git a/synergy/CProtocolUtil.h b/synergy/CProtocolUtil.h new file mode 100644 index 00000000..346accda --- /dev/null +++ b/synergy/CProtocolUtil.h @@ -0,0 +1,53 @@ +#ifndef CPROTOCOLUTIL_H +#define CPROTOCOLUTIL_H + +#include "BasicTypes.h" +#include "XIO.h" +#include + +class IInputStream; +class IOutputStream; + +class CProtocolUtil { + public: + // write formatted binary data to a stream. fmt consists of + // regular characters and format specifiers. format specifiers + // begin with %. all characters not part of a format specifier + // are regular and are transmitted unchanged. + // + // format specifiers are: + // %% -- writes % + // %1i -- converts integer argument to 1 byte integer + // %2i -- converts integer argument to 2 byte integer in NBO + // %4i -- converts integer argument to 4 byte integer in NBO + // %s -- converts integer N and const UInt8* to stream of N bytes + static void writef(IOutputStream*, + const char* fmt, ...) throw(XIO); + + // read formatted binary data from a buffer. this performs the + // reverse operation of writef(). + // + // format specifiers are: + // %% -- read (and discard) a % + // %1i -- reads a 1 byte integer; argument is a SInt32* or UInt32* + // %2i -- reads an NBO 2 byte integer; arg is SInt32* or UInt32* + // %4i -- reads an NBO 4 byte integer; arg is SInt32* or UInt32* + // %s -- reads bytes; argument must be a CString*, *not* a char* + static void readf(IInputStream*, + const char* fmt, ...) throw(XIO); + + private: + static UInt32 getLength(const char* fmt, va_list) throw(); + static void writef(void*, const char* fmt, va_list) throw(XIO); + static UInt32 eatLength(const char** fmt) throw(); + static void read(IInputStream*, void*, UInt32) throw(XIO); +}; + +class XIOReadMismatch : public XIO { + public: + // XBase overrides + virtual CString getWhat() const throw(); +}; + +#endif + diff --git a/synergy/CScreenMap.cpp b/synergy/CScreenMap.cpp new file mode 100644 index 00000000..7e59ea1d --- /dev/null +++ b/synergy/CScreenMap.cpp @@ -0,0 +1,89 @@ +#include "CScreenMap.h" +#include + +// +// CScreenMap +// + +CScreenMap::CScreenMap() +{ + // do nothing +} + +CScreenMap::~CScreenMap() +{ + // do nothing +} + +void CScreenMap::addScreen(const CString& name) +{ + if (m_map.count(name) != 0) { + assert(0 && "name already in map"); // FIXME -- throw instead + } + m_map.insert(std::make_pair(name, CCell())); +} + +void CScreenMap::removeScreen(const CString& name) +{ + CCellMap::iterator index = m_map.find(name); + if (index == m_map.end()) { + assert(0 && "name not in map"); // FIXME -- throw instead + } + + // remove from map + m_map.erase(index); + + // disconnect + for (index = m_map.begin(); index != m_map.end(); ++index) { + CCell& cell = index->second; + for (SInt32 i = 0; kLastDirection - kFirstDirection; ++i) + if (cell.m_neighbor[i] == name) { + cell.m_neighbor[i].erase(); + } + } +} + +void CScreenMap::removeAllScreens() +{ + m_map.clear(); +} + +void CScreenMap::connect(const CString& srcName, + EDirection srcSide, + const CString& dstName) +{ + // find source cell + CCellMap::iterator index = m_map.find(srcName); + if (index == m_map.end()) { + assert(0 && "name not in map"); // FIXME -- throw instead + } + + // connect side (overriding any previous connection) + index->second.m_neighbor[srcSide - kFirstDirection] = dstName; +} + +void CScreenMap::disconnect(const CString& srcName, + EDirection srcSide) +{ + // find source cell + CCellMap::iterator index = m_map.find(srcName); + if (index == m_map.end()) { + assert(0 && "name not in map"); // FIXME -- throw instead + } + + // disconnect side + index->second.m_neighbor[srcSide - kFirstDirection].erase(); +} + +CString CScreenMap::getNeighbor(const CString& srcName, + EDirection srcSide) const throw() +{ + // find source cell + CCellMap::const_iterator index = m_map.find(srcName); + if (index == m_map.end()) { + assert(0 && "name not in map"); // FIXME -- throw instead + } + + // return connection + return index->second.m_neighbor[srcSide - kFirstDirection]; +} diff --git a/synergy/CScreenMap.h b/synergy/CScreenMap.h new file mode 100644 index 00000000..234ae05b --- /dev/null +++ b/synergy/CScreenMap.h @@ -0,0 +1,46 @@ +#ifndef CSCREENMAP_H +#define CSCREENMAP_H + +#include "BasicTypes.h" +#include "CString.h" +#include + +class CScreenMap { + public: + enum EDirection { kLeft, kRight, kTop, kBottom, + kFirstDirection = kLeft, kLastDirection = kBottom }; + + CScreenMap(); + virtual ~CScreenMap(); + + // manipulators + + // add/remove screens + void addScreen(const CString& name); + void removeScreen(const CString& name); + void removeAllScreens(); + + // connect edges + void connect(const CString& srcName, + EDirection srcSide, + const CString& dstName); + void disconnect(const CString& srcName, + EDirection srcSide); + + // accessors + + // get the neighbor in the given direction. returns the empty string + // if there is no neighbor in that direction. + CString getNeighbor(const CString&, EDirection) const throw(); + + private: + class CCell { + public: + CString m_neighbor[kLastDirection - kFirstDirection + 1]; + }; + typedef std::map CCellMap; + + CCellMap m_map; +}; + +#endif diff --git a/synergy/CServer.cpp b/synergy/CServer.cpp new file mode 100644 index 00000000..28699806 --- /dev/null +++ b/synergy/CServer.cpp @@ -0,0 +1,849 @@ +#include "CServer.h" +#include "CInputPacketStream.h" +#include "COutputPacketStream.h" +#include "CServerProtocol.h" +#include "CProtocolUtil.h" +#include "IPrimaryScreen.h" +#include "ISocketFactory.h" +#include "ProtocolTypes.h" +#include "XSynergy.h" +#include "CNetworkAddress.h" +#include "ISocket.h" +#include "IListenSocket.h" +#include "XSocket.h" +#include "CLock.h" +#include "CThread.h" +#include "CTimerThread.h" +#include "CStopwatch.h" +#include "TMethodJob.h" +#include +#include +#include + +// +// CServer +// + +CServer::CServer() : m_done(&m_mutex, false) +{ + m_socketFactory = NULL; + m_securityFactory = NULL; + m_bindTimeout = 5.0 * 60.0; +} + +CServer::~CServer() +{ +} + +void CServer::run() +{ + try { + // connect to primary screen + openPrimaryScreen(); + + // start listening for new clients + CThread(new TMethodJob(this, &CServer::acceptClients)); + + // start listening for configuration connections + // FIXME + + // wait until done + CLock lock(&m_mutex); + while (m_done == false) { + m_done.wait(); + } + + // clean up + closePrimaryScreen(); + cleanupThreads(); + } + catch (XBase& e) { + fprintf(stderr, "server error: %s\n", e.what()); + + // clean up + closePrimaryScreen(); + cleanupThreads(); + } + catch (...) { + // clean up + closePrimaryScreen(); + cleanupThreads(); + throw; + } +} + +void CServer::setScreenMap(const CScreenMap& screenMap) +{ + CLock lock(&m_mutex); + // FIXME -- must disconnect screens no longer listed + // (that may include warping back to server's screen) + // FIXME -- server screen must be in new map or map is rejected + m_screenMap = screenMap; +} + +void CServer::getScreenMap(CScreenMap* screenMap) const +{ + assert(screenMap != NULL); + + CLock lock(&m_mutex); + *screenMap = m_screenMap; +} + +void CServer::setInfo(const CString& client, + SInt32 w, SInt32 h, SInt32 zoneSize) throw() +{ + CLock lock(&m_mutex); + + // client must be connected + CScreenList::iterator index = m_screens.find(client); + if (index == m_screens.end()) { + throw XBadClient(); + } + + // update client info + CScreenInfo* info = index->second; + if (info == m_active) { + // FIXME -- ensure mouse is still on screen. warp it if necessary. + } + info->m_width = w; + info->m_height = h; + info->m_zoneSize = zoneSize; +} + +bool CServer::onCommandKey(KeyID /*id*/, + KeyModifierMask /*mask*/, bool /*down*/) +{ + return false; +} + +void CServer::onKeyDown(KeyID id, KeyModifierMask mask) +{ + assert(m_active != NULL); + + // handle command keys + if (onCommandKey(id, mask, true)) { + return; + } + + // relay + if (m_active->m_protocol != NULL) { + m_active->m_protocol->sendKeyDown(id, mask); + } +} + +void CServer::onKeyUp(KeyID id, KeyModifierMask mask) +{ + assert(m_active != NULL); + + // handle command keys + if (onCommandKey(id, mask, false)) { + return; + } + + // relay + if (m_active->m_protocol != NULL) { + m_active->m_protocol->sendKeyUp(id, mask); + } +} + +void CServer::onKeyRepeat(KeyID id, KeyModifierMask mask) +{ + assert(m_active != NULL); + + // handle command keys + if (onCommandKey(id, mask, false)) { + onCommandKey(id, mask, true); + return; + } + + // relay + if (m_active->m_protocol != NULL) { + m_active->m_protocol->sendKeyRepeat(id, mask); + } +} + +void CServer::onMouseDown(ButtonID id) +{ + assert(m_active != NULL); + + // relay + if (m_active->m_protocol != NULL) { + m_active->m_protocol->sendMouseDown(id); + } +} + +void CServer::onMouseUp(ButtonID id) +{ + assert(m_active != NULL); + + // relay + if (m_active->m_protocol != NULL) { + m_active->m_protocol->sendMouseUp(id); + } +} + +void CServer::onMouseMovePrimary(SInt32 x, SInt32 y) +{ + // mouse move on primary (server's) screen + assert(m_active != NULL); + assert(m_active->m_protocol == NULL); + + // ignore if mouse is locked to screen + if (isLockedToScreen()) { + return; + } + + // see if we should change screens + CScreenMap::EDirection dir; + if (x < m_active->m_zoneSize) { + x -= m_active->m_zoneSize; + dir = CScreenMap::kLeft; + } + else if (x >= m_active->m_width - m_active->m_zoneSize) { + x += m_active->m_zoneSize; + dir = CScreenMap::kRight; + } + else if (y < m_active->m_zoneSize) { + y -= m_active->m_zoneSize; + dir = CScreenMap::kTop; + } + else if (y >= m_active->m_height - m_active->m_zoneSize) { + y += m_active->m_zoneSize; + dir = CScreenMap::kBottom; + } + else { + // still on local screen + return; + } + + // get jump destination + CScreenInfo* newScreen = getNeighbor(m_active, dir, x, y); + + // if no screen in jump direction then ignore the move + if (newScreen == NULL) { + return; + } + + // remap position to account for resolution differences + mapPosition(m_active, dir, newScreen, x, y); + + // switch screen + switchScreen(newScreen, x, y); +} + +void CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy) +{ + // mouse move on secondary (client's) screen + assert(m_active != NULL); + assert(m_active->m_protocol != NULL); + + // save old position + const SInt32 xOld = m_x; + const SInt32 yOld = m_y; + + // accumulate motion + m_x += dx; + m_y += dy; + + // switch screens if the mouse is outside the screen and not + // locked to the screen + CScreenInfo* newScreen = NULL; + if (!isLockedToScreen()) { + // find direction of neighbor + CScreenMap::EDirection dir; + if (m_x < 0) + dir = CScreenMap::kLeft; + else if (m_x > m_active->m_width - 1) + dir = CScreenMap::kRight; + else if (m_y < 0) + dir = CScreenMap::kTop; + else if (m_y > m_active->m_height - 1) + dir = CScreenMap::kBottom; + else + newScreen = m_active; + + // get neighbor if we should switch + if (newScreen == NULL) { +// TRACE(("leave %s on %s", m_activeScreen->getName().c_str(), +// s_dirName[dir])); + + SInt32 x = m_x, y = m_y; + newScreen = getNeighbor(m_active, dir, x, y); + + // remap position to account for resolution differences + if (newScreen != NULL) { + mapPosition(m_active, dir, newScreen, x, y); + m_x = x; + m_y = y; + } + else { + if (m_x < 0) + m_x = 0; + else if (m_x > m_active->m_width - 1) + m_x = m_active->m_width - 1; + if (m_y < 0) + m_y = 0; + else if (m_y > m_active->m_height - 1) + m_y = m_active->m_height - 1; + } + } + } + else { + // clamp to edge when locked +// TRACE(("clamp to %s", m_activeScreen->getName().c_str())); + if (m_x < 0) + m_x = 0; + else if (m_x > m_active->m_width - 1) + m_x = m_active->m_width - 1; + if (m_y < 0) + m_y = 0; + else if (m_y > m_active->m_height - 1) + m_y = m_active->m_height - 1; + } + + // warp cursor if on same screen + if (newScreen == NULL || newScreen == m_active) { + // do nothing if mouse didn't move + if (m_x != xOld || m_y != yOld) { +// TRACE(("move on %s to %d,%d", +// m_activeScreen->getName().c_str(), m_x, m_y)); + m_active->m_protocol->sendMouseMove(m_x, m_y); + } + } + + // otherwise screen screens + else { + switchScreen(newScreen, m_x, m_y); + } +} + +void CServer::onMouseWheel(SInt32 delta) +{ + assert(m_active != NULL); + + // relay + if (m_active->m_protocol != NULL) { + m_active->m_protocol->sendMouseWheel(delta); + } +} + +bool CServer::isLockedToScreen() const +{ + // FIXME + return false; +} + +void CServer::switchScreen(CScreenInfo* dst, + SInt32 x, SInt32 y) +{ + assert(dst != NULL); + assert(x >= 0 && y >= 0 && x < dst->m_width && y < dst->m_height); + assert(m_active != NULL); + +// TRACE(("switch %s to %s at %d,%d", m_active->m_name.c_str(), +// dst->m_name.c_str(), x, y)); + + // wrapping means leaving the active screen and entering it again. + // since that's a waste of time we skip that and just warp the + // mouse. + if (m_active != dst) { + // leave active screen + if (m_active->m_protocol == NULL) { + m_primary->leave(); + } + else { + m_active->m_protocol->sendLeave(); + } + + // cut over + m_active = dst; + + // enter new screen + if (m_active->m_protocol == NULL) { + m_primary->enter(x, y); + } + else { + m_active->m_protocol->sendEnter(x, y); + } + } + else { + if (m_active->m_protocol == NULL) { + m_primary->warpCursor(x, y); + } + else { + m_active->m_protocol->sendMouseMove(x, y); + } + } + + // record new position + m_x = x; + m_y = y; +} + +CServer::CScreenInfo* CServer::getNeighbor(CScreenInfo* src, + CScreenMap::EDirection dir) const +{ + assert(src != NULL); + + CString srcName = src->m_name; + assert(!srcName.empty()); + for (;;) { + // look up name of neighbor + const CString dstName(m_screenMap.getNeighbor(srcName, dir)); + + // if nothing in that direction then return NULL + if (dstName.empty()) + return NULL; + + // look up neighbor cell. if the screen is connected then + // we can stop. otherwise we skip over an unconnected + // screen. + CScreenList::const_iterator index = m_screens.find(dstName); + if (index != m_screens.end()) { + return index->second; + } + + srcName = dstName; + } +} + +CServer::CScreenInfo* CServer::getNeighbor(CScreenInfo* src, + CScreenMap::EDirection srcSide, + SInt32& x, SInt32& y) const +{ + assert(src != NULL); + + // get the first neighbor + CScreenInfo* dst = getNeighbor(src, srcSide); + CScreenInfo* lastGoodScreen = dst; + + // get the source screen's size (needed for kRight and kBottom) + SInt32 w = src->m_width, h = src->m_height; + + // find destination screen, adjusting x or y (but not both) + switch (srcSide) { + case CScreenMap::kLeft: + while (dst != NULL) { + lastGoodScreen = dst; + w = lastGoodScreen->m_width; + h = lastGoodScreen->m_height; + x += w; + if (x >= 0) { + break; + } +// TRACE(("skipping over screen %s", dst->m_name.c_str())); + dst = getNeighbor(lastGoodScreen, srcSide); + } + break; + + case CScreenMap::kRight: + while (dst != NULL) { + lastGoodScreen = dst; + x -= w; + w = lastGoodScreen->m_width; + h = lastGoodScreen->m_height; + if (x < w) { + break; + } +// TRACE(("skipping over screen %s", dst->m_name.c_str())); + dst = getNeighbor(lastGoodScreen, srcSide); + } + break; + + case CScreenMap::kTop: + while (dst != NULL) { + lastGoodScreen = dst; + w = lastGoodScreen->m_width; + h = lastGoodScreen->m_height; + y += h; + if (y >= 0) { + break; + } +// TRACE(("skipping over screen %s", dst->m_name.c_str())); + dst = getNeighbor(lastGoodScreen, srcSide); + } + break; + + case CScreenMap::kBottom: + while (dst != NULL) { + lastGoodScreen = dst; + y -= h; + w = lastGoodScreen->m_width; + h = lastGoodScreen->m_height; + if (y < h) { + break; + } +// TRACE(("skipping over screen %s", dst->m_name.c_str())); + dst = getNeighbor(lastGoodScreen, srcSide); + } + break; + } + + // if entering primary screen then be sure to move in far enough + // to avoid the jump zone. if entering a side that doesn't have + // a neighbor (i.e. an asymmetrical side) then we don't need to + // move inwards because that side can't provoke a jump. + assert(lastGoodScreen != NULL); + if (lastGoodScreen->m_protocol == NULL) { + const CString dstName(lastGoodScreen->m_name); + switch (srcSide) { + case CScreenMap::kLeft: + if (!m_screenMap.getNeighbor(dstName, CScreenMap::kRight).empty() && + x > w - 1 - lastGoodScreen->m_zoneSize) + x = w - 1 - lastGoodScreen->m_zoneSize; + break; + + case CScreenMap::kRight: + if (!m_screenMap.getNeighbor(dstName, CScreenMap::kLeft).empty() && + x < lastGoodScreen->m_zoneSize) + x = lastGoodScreen->m_zoneSize; + break; + + case CScreenMap::kTop: + if (!m_screenMap.getNeighbor(dstName, CScreenMap::kBottom).empty() && + y > h - 1 - lastGoodScreen->m_zoneSize) + y = h - 1 - lastGoodScreen->m_zoneSize; + break; + + case CScreenMap::kBottom: + if (!m_screenMap.getNeighbor(dstName, CScreenMap::kTop).empty() && + y < lastGoodScreen->m_zoneSize) + y = lastGoodScreen->m_zoneSize; + break; + } + } + + return lastGoodScreen; +} + +void CServer::mapPosition(CScreenInfo* src, + CScreenMap::EDirection srcSide, + CScreenInfo* dst, + SInt32& x, SInt32& y) const +{ + assert(src != NULL); + assert(dst != NULL); + assert(srcSide >= CScreenMap::kFirstDirection && + srcSide <= CScreenMap::kLastDirection); + + switch (srcSide) { + case CScreenMap::kLeft: + case CScreenMap::kRight: + assert(y >= 0 && y < src->m_height); + y = static_cast(0.5 + y * + static_cast(dst->m_height - 1) / + (src->m_height - 1)); + break; + + case CScreenMap::kTop: + case CScreenMap::kBottom: + assert(x >= 0 && x < src->m_width); + x = static_cast(0.5 + x * + static_cast(dst->m_width - 1) / + (src->m_width - 1)); + break; + } +} + +void CServer::acceptClients(void*) +{ + // add this thread to the list of threads to cancel. remove from + // list in d'tor. + CCleanupNote cleanupNote(this); + + std::auto_ptr listen; + try { + // create socket listener + listen.reset(m_socketFactory->createListen()); + + // bind to the desired port. keep retrying if we can't bind + // the address immediately. + CStopwatch timer; + CNetworkAddress addr(50001 /* FIXME -- m_port */); + for (;;) { + try { + listen->bind(addr); + break; + } + catch (XSocketAddressInUse&) { + // give up if we've waited too long + if (timer.getTime() >= m_bindTimeout) { + throw; + } + + // wait a bit before retrying + CThread::sleep(5.0); + } + } + + // accept connections and begin processing them + for (;;) { + // accept connection + CThread::testCancel(); + ISocket* socket = listen->accept(); + CThread::testCancel(); + + // start handshake thread + CThread(new TMethodJob( + this, &CServer::handshakeClient, socket)); + } + } + catch (XBase& e) { + fprintf(stderr, "cannot listen for clients: %s\n", e.what()); + quit(); + } +} + +void CServer::handshakeClient(void* vsocket) +{ + // get the socket pointer from the argument + assert(vsocket != NULL); + std::auto_ptr socket(reinterpret_cast(vsocket)); + + // add this thread to the list of threads to cancel. remove from + // list in d'tor. + CCleanupNote cleanupNote(this); + + CString name(""); + try { + // get the input and output streams + IInputStream* srcInput = socket->getInputStream(); + IOutputStream* srcOutput = socket->getOutputStream(); + std::auto_ptr input; + std::auto_ptr output; + + // attach the encryption layer + if (m_securityFactory != NULL) { +/* FIXME -- implement ISecurityFactory + input.reset(m_securityFactory->createInputFilter(srcInput, false)); + output.reset(m_securityFactory->createOutputFilter(srcOutput, false)); + srcInput = input.get(); + srcOutput = output.get(); +*/ + } + + // attach the packetizing filters + input.reset(new CInputPacketStream(srcInput, true)); + output.reset(new COutputPacketStream(srcOutput, true)); + + std::auto_ptr protocol; + std::auto_ptr connectedNote; + { + // give the client a limited time to complete the handshake + CTimerThread timer(30.0); + + // limit the maximum length of the hello + static const UInt32 maxHelloLen = 1024; + + // say hello + CProtocolUtil::writef(output.get(), "Synergy%2i%2i", + kMajorVersion, kMinorVersion); + output->flush(); + + // wait for the reply + UInt32 n = input->getSize(); + if (n > maxHelloLen) { + throw XBadClient(); + } + + // get and parse the reply to hello + SInt32 major, minor; + try { + CProtocolUtil::readf(input.get(), "Synergy%2i%2i%s", + &major, &minor, &name); + } + catch (XIO&) { + throw XBadClient(); + } + if (major < 0 || minor < 0) { + throw XBadClient(); + } + + // create a protocol interpreter for the version + protocol.reset(CServerProtocol::create(major, minor, + this, name, input.get(), output.get())); + + // client is now pending + connectedNote.reset(new CConnectionNote(this, + name, protocol.get())); + + // ask and wait for the client's info + protocol->queryInfo(); + } + + // handle messages from client. returns when the client + // disconnects. + protocol->run(); + } + catch (XIncompatibleClient& e) { + // client is incompatible + fprintf(stderr, "client is incompatible (%s, %d.%d)\n", + name.c_str(), e.getMajor(), e.getMinor()); + // FIXME -- could print network address if socket had suitable method + } + catch (XBadClient&) { + // client not behaving + fprintf(stderr, "protocol error from client %s\n", name.c_str()); + // FIXME -- could print network address if socket had suitable method + } + catch (XBase& e) { + // misc error + fprintf(stderr, "error communicating with client %s: %s\n", + name.c_str(), e.what()); + // FIXME -- could print network address if socket had suitable method + } +} + +void CServer::quit() throw() +{ + CLock lock(&m_mutex); + m_done = true; + m_done.broadcast(); +} + +// FIXME -- use factory to create screen +#include "CXWindowsPrimaryScreen.h" +void CServer::openPrimaryScreen() +{ + assert(m_primary == NULL); + + // open screen + m_primary = new CXWindowsPrimaryScreen; + m_primary->open(this); + + // add connection + m_active = addConnection(CString("primary"/* FIXME */), NULL); +} + +void CServer::closePrimaryScreen() throw() +{ + assert(m_primary != NULL); + + // remove connection + removeConnection(CString("primary"/* FIXME */)); + + // close the primary screen + try { + m_primary->close(); + } + catch (...) { + // ignore + } + + // clean up + delete m_primary; + m_primary = NULL; +} + +void CServer::addCleanupThread(const CThread& thread) +{ + CLock lock(&m_mutex); + m_cleanupList.insert(m_cleanupList.begin(), new CThread(thread)); +} + +void CServer::removeCleanupThread(const CThread& thread) +{ + CLock lock(&m_mutex); + for (CThreadList::iterator index = m_cleanupList.begin(); + index != m_cleanupList.end(); ++index) { + if (**index == thread) { + m_cleanupList.erase(index); + delete *index; + return; + } + } +} + +void CServer::cleanupThreads() throw() +{ + m_mutex.lock(); + while (m_cleanupList.begin() != m_cleanupList.end()) { + // get the next thread and cancel it + CThread* thread = m_cleanupList.front(); + thread->cancel(); + + // wait for thread to finish with cleanup list unlocked. the + // thread will remove itself from the cleanup list. + m_mutex.unlock(); + thread->wait(); + m_mutex.lock(); + } + + // FIXME -- delete remaining threads from list + m_mutex.unlock(); +} + +CServer::CScreenInfo* CServer::addConnection( + const CString& name, IServerProtocol* protocol) +{ + CLock lock(&m_mutex); + assert(m_screens.count(name) == 0); + CScreenInfo* newScreen = new CScreenInfo(name, protocol); + m_screens.insert(std::make_pair(name, newScreen)); + return newScreen; +} + +void CServer::removeConnection(const CString& name) +{ + CLock lock(&m_mutex); + CScreenList::iterator index = m_screens.find(name); + assert(index == m_screens.end()); + delete index->second; + m_screens.erase(index); +} + + +// +// CServer::CCleanupNote +// + +CServer::CCleanupNote::CCleanupNote(CServer* server) : m_server(server) +{ + assert(m_server != NULL); + m_server->addCleanupThread(CThread::getCurrentThread()); +} + +CServer::CCleanupNote::~CCleanupNote() +{ + m_server->removeCleanupThread(CThread::getCurrentThread()); +} + + +// +// CServer::CConnectionNote +// + +CServer::CConnectionNote::CConnectionNote(CServer* server, + const CString& name, + IServerProtocol* protocol) : + m_server(server), + m_name(name) +{ + assert(m_server != NULL); + m_server->addConnection(m_name, protocol); +} + +CServer::CConnectionNote::~CConnectionNote() +{ + m_server->removeConnection(m_name); +} + + +// +// CServer::CScreenInfo +// + +CServer::CScreenInfo::CScreenInfo(const CString& name, + IServerProtocol* protocol) : + m_name(name), + m_protocol(protocol), + m_width(0), m_height(0), + m_zoneSize(0) +{ + // do nothing +} + +CServer::CScreenInfo::~CScreenInfo() +{ + // do nothing +} diff --git a/synergy/CServer.h b/synergy/CServer.h new file mode 100644 index 00000000..e7fc814e --- /dev/null +++ b/synergy/CServer.h @@ -0,0 +1,158 @@ +#ifndef CSERVER_H +#define CSERVER_H + +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "CScreenMap.h" +#include "CCondVar.h" +#include "CMutex.h" +#include "CString.h" +#include "XBase.h" +#include +#include + +class CThread; +class IServerProtocol; +class ISocketFactory; +class ISecurityFactory; +class IPrimaryScreen; + +class CServer { + public: + CServer(); + ~CServer(); + + // manipulators + + void run(); + + // update screen map + void setScreenMap(const CScreenMap&); + + // handle events on server's screen + void onKeyDown(KeyID, KeyModifierMask); + void onKeyUp(KeyID, KeyModifierMask); + void onKeyRepeat(KeyID, KeyModifierMask); + void onMouseDown(ButtonID); + void onMouseUp(ButtonID); + void onMouseMovePrimary(SInt32 x, SInt32 y); + void onMouseMoveSecondary(SInt32 dx, SInt32 dy); + void onMouseWheel(SInt32 delta); + + // handle messages from clients + void setInfo(const CString& clientName, + SInt32 w, SInt32 h, SInt32 zoneSize) throw(); + + // accessors + + bool isLockedToScreen() const; + + // get the current screen map + void getScreenMap(CScreenMap*) const; + + protected: + bool onCommandKey(KeyID, KeyModifierMask, bool down); + void quit() throw(); + + private: + class CCleanupNote { + public: + CCleanupNote(CServer*); + ~CCleanupNote(); + + private: + CServer* m_server; + }; + + class CConnectionNote { + public: + CConnectionNote(CServer*, const CString&, IServerProtocol*); + ~CConnectionNote(); + + private: + bool m_pending; + CServer* m_server; + CString m_name; + }; + + class CScreenInfo { + public: + CScreenInfo(const CString& name, IServerProtocol*); + ~CScreenInfo(); + + public: + CString m_name; + IServerProtocol* m_protocol; + SInt32 m_width, m_height; + SInt32 m_zoneSize; + }; + + // change the active screen + void switchScreen(CScreenInfo*, SInt32 x, SInt32 y); + + // lookup neighboring screen + CScreenInfo* getNeighbor(CScreenInfo*, CScreenMap::EDirection) const; + + // lookup neighboring screen. given a position relative to the + // source screen, find the screen we should move onto and where. + // if the position is sufficiently far from the source then we + // cross multiple screens. + CScreenInfo* getNeighbor(CScreenInfo*, + CScreenMap::EDirection, + SInt32& x, SInt32& y) const; + + // adjust coordinates to account for resolution differences. the + // position is converted to a resolution independent form then + // converted back to screen coordinates on the destination screen. + void mapPosition(CScreenInfo* src, + CScreenMap::EDirection srcSide, + CScreenInfo* dst, + SInt32& x, SInt32& y) const; + + // open/close the primary screen + void openPrimaryScreen(); + void closePrimaryScreen() throw(); + + // cancel running threads + void cleanupThreads() throw(); + + // thread method to accept incoming client connections + void acceptClients(void*); + + // thread method to do startup handshake with client + void handshakeClient(void*); + + // thread cleanup list maintenance + friend class CCleanupNote; + void addCleanupThread(const CThread& thread); + void removeCleanupThread(const CThread& thread); + + // connection list maintenance + friend class CConnectionNote; + CScreenInfo* addConnection(const CString& name, IServerProtocol*); + void removeConnection(const CString& name); + + private: + typedef std::list CThreadList; + typedef std::map CScreenList; + + CMutex m_mutex; + CCondVar m_done; + + double m_bindTimeout; + + ISocketFactory* m_socketFactory; + ISecurityFactory* m_securityFactory; + + CThreadList m_cleanupList; + + IPrimaryScreen* m_primary; + CScreenList m_screens; + CScreenInfo* m_active; + + SInt32 m_x, m_y; + + CScreenMap m_screenMap; +}; + +#endif diff --git a/synergy/CServerProtocol.cpp b/synergy/CServerProtocol.cpp new file mode 100644 index 00000000..eb52afd8 --- /dev/null +++ b/synergy/CServerProtocol.cpp @@ -0,0 +1,72 @@ +#include "CServerProtocol.h" +#include "CServerProtocol1_0.h" +#include "ProtocolTypes.h" +#include "IOutputStream.h" +#include +#include + +// +// CServerProtocol +// + +CServerProtocol::CServerProtocol(CServer* server, const CString& client, + IInputStream* input, IOutputStream* output) : + m_server(server), + m_client(client), + m_input(input), + m_output(output) +{ + assert(m_server != NULL); + assert(m_input != NULL); + assert(m_output != NULL); +} + +CServerProtocol::~CServerProtocol() +{ + // do nothing +} + +CServer* CServerProtocol::getServer() const throw() +{ + return m_server; +} + +CString CServerProtocol::getClient() const throw() +{ + return m_client; +} + +IInputStream* CServerProtocol::getInputStream() const throw() +{ + return m_input; +} + +IOutputStream* CServerProtocol::getOutputStream() const throw() +{ + return m_output; +} + +IServerProtocol* CServerProtocol::create(SInt32 major, SInt32 minor, + CServer* server, const CString& client, + IInputStream* input, IOutputStream* output) +{ + // disallow connection from test versions to release versions + if (major == 0 && kMajorVersion != 0) { + output->write(kMsgEIncompatible, sizeof(kMsgEIncompatible) - 1); + output->flush(); + throw XIncompatibleClient(major, minor); + } + + // hangup (with error) if version isn't supported + if (major > kMajorVersion || + (major == kMajorVersion && minor > kMinorVersion)) { + output->write(kMsgEIncompatible, sizeof(kMsgEIncompatible) - 1); + output->flush(); + throw XIncompatibleClient(major, minor); + } + + // create highest version protocol object not higher than the + // given version. + return new CServerProtocol1_0(server, client, input, output); +} + diff --git a/synergy/CServerProtocol.h b/synergy/CServerProtocol.h new file mode 100644 index 00000000..485ee2c3 --- /dev/null +++ b/synergy/CServerProtocol.h @@ -0,0 +1,58 @@ +#ifndef CSERVERPROTOCOL_H +#define CSERVERPROTOCOL_H + +#include "CString.h" +#include "IServerProtocol.h" + +class CServer; +class IInputStream; +class IOutputStream; + +class CServerProtocol : public IServerProtocol { + public: + CServerProtocol(CServer*, const CString& clientName, + IInputStream*, IOutputStream*); + ~CServerProtocol(); + + // manipulators + + // accessors + + virtual CServer* getServer() const throw(); + virtual CString getClient() const throw(); + virtual IInputStream* getInputStream() const throw(); + virtual IOutputStream* getOutputStream() const throw(); + + static IServerProtocol* create(SInt32 major, SInt32 minor, + CServer*, const CString& clientName, + IInputStream*, IOutputStream*); + + // IServerProtocol overrides + virtual void run() throw(XIO,XBadClient) = 0; + virtual void queryInfo() throw(XIO,XBadClient) = 0; + virtual void sendClose() throw(XIO) = 0; + virtual void sendEnter(SInt32 xAbs, SInt32 yAbs) throw(XIO) = 0; + virtual void sendLeave() throw(XIO) = 0; + virtual void sendGrabClipboard() throw(XIO) = 0; + virtual void sendQueryClipboard() throw(XIO) = 0; + virtual void sendScreenSaver(bool on) throw(XIO) = 0; + virtual void sendKeyDown(KeyID, KeyModifierMask) throw(XIO) = 0; + virtual void sendKeyRepeat(KeyID, KeyModifierMask) throw(XIO) = 0; + virtual void sendKeyUp(KeyID, KeyModifierMask) throw(XIO) = 0; + virtual void sendMouseDown(ButtonID) throw(XIO) = 0; + virtual void sendMouseUp(ButtonID) throw(XIO) = 0; + virtual void sendMouseMove(SInt32 xAbs, SInt32 yAbs) throw(XIO) = 0; + virtual void sendMouseWheel(SInt32 delta) throw(XIO) = 0; + + protected: + //IServerProtocol overrides + virtual void recvInfo() throw(XIO,XBadClient) = 0; + + private: + CServer* m_server; + CString m_client; + IInputStream* m_input; + IOutputStream* m_output; +}; + +#endif diff --git a/synergy/CServerProtocol1_0.cpp b/synergy/CServerProtocol1_0.cpp new file mode 100644 index 00000000..eb6ab9cc --- /dev/null +++ b/synergy/CServerProtocol1_0.cpp @@ -0,0 +1,157 @@ +#include "CServerProtocol1_0.h" +#include "CServer.h" +#include "CProtocolUtil.h" +#include "ProtocolTypes.h" +#include "IInputStream.h" +#include + +// +// CServerProtocol1_0 +// + +CServerProtocol1_0::CServerProtocol1_0(CServer* server, const CString& client, + IInputStream* input, IOutputStream* output) : + CServerProtocol(server, client, input, output) +{ + // do nothing +} + +CServerProtocol1_0::~CServerProtocol1_0() +{ + // do nothing +} + +void CServerProtocol1_0::run() throw(XIO,XBadClient) +{ + // handle messages until the client hangs up + for (;;) { + // wait for a message + UInt8 code[4]; + UInt32 n = getInputStream()->read(code, 4); + + // verify we got an entire code + if (n == 0) { + // client hungup + return; + } + if (n != 4) { + // client sent an incomplete message + throw XBadClient(); + } + + // parse message + if (memcmp(code, kMsgDInfo, 4) == 0) { + recvInfo(); + } + // FIXME -- more message here + else { + // unknown message + throw XBadClient(); + } + } +} + +void CServerProtocol1_0::queryInfo() throw(XIO,XBadClient) +{ + // send request + CProtocolUtil::writef(getOutputStream(), kMsgQInfo); + + // wait for and verify reply + UInt8 code[4]; + UInt32 n = getInputStream()->read(code, 4); + if (n != 4 && memcmp(code, kMsgDInfo, 4) != 0) { + throw XBadClient(); + } + + // handle reply + recvInfo(); +} + +void CServerProtocol1_0::sendClose() throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgCClose); +} + +void CServerProtocol1_0::sendEnter( + SInt32 xAbs, SInt32 yAbs) throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgCEnter, xAbs, yAbs); +} + +void CServerProtocol1_0::sendLeave() throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgCLeave); +} + +void CServerProtocol1_0::sendGrabClipboard() throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgCClipboard); +} + +void CServerProtocol1_0::sendQueryClipboard() throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgQClipboard); +} + +void CServerProtocol1_0::sendScreenSaver(bool on) throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgCScreenSaver, on ? 1 : 0); +} + +void CServerProtocol1_0::sendKeyDown( + KeyID key, KeyModifierMask mask) throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgDKeyDown, key, mask); +} + +void CServerProtocol1_0::sendKeyRepeat( + KeyID key, KeyModifierMask mask) throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgDKeyRepeat, key, mask); +} + +void CServerProtocol1_0::sendKeyUp( + KeyID key, KeyModifierMask mask) throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgDKeyUp, key, mask); +} + +void CServerProtocol1_0::sendMouseDown( + ButtonID button) throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgDMouseDown, button); +} + +void CServerProtocol1_0::sendMouseUp( + ButtonID button) throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgDMouseUp, button); +} + +void CServerProtocol1_0::sendMouseMove( + SInt32 xAbs, SInt32 yAbs) throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgDMouseMove, xAbs, yAbs); +} + +void CServerProtocol1_0::sendMouseWheel( + SInt32 delta) throw(XIO) +{ + CProtocolUtil::writef(getOutputStream(), kMsgDMouseWheel, delta); +} + +void CServerProtocol1_0::recvInfo() throw(XIO,XBadClient) +{ + // parse the message + SInt32 w, h, zoneInfo; + CProtocolUtil::readf(getInputStream(), kMsgDInfo + 4, &w, &h, &zoneInfo); + + // validate + if (w == 0 || h == 0) { + throw XBadClient(); + } + + // tell server of change + getServer()->setInfo(getClient(), w, h, zoneInfo); +} + diff --git a/synergy/CServerProtocol1_0.h b/synergy/CServerProtocol1_0.h new file mode 100644 index 00000000..eec2d156 --- /dev/null +++ b/synergy/CServerProtocol1_0.h @@ -0,0 +1,37 @@ +#ifndef CSERVERPROTOCOL1_0_H +#define CSERVERPROTOCOL1_0_H + +#include "CServerProtocol.h" + +class CServerProtocol1_0 : public CServerProtocol { + public: + CServerProtocol1_0(CServer*, const CString&, IInputStream*, IOutputStream*); + ~CServerProtocol1_0(); + + // manipulators + + // accessors + + // IServerProtocol overrides + virtual void run() throw(XIO,XBadClient); + virtual void queryInfo() throw(XIO,XBadClient); + virtual void sendClose() throw(XIO); + virtual void sendEnter(SInt32 xAbs, SInt32 yAbs) throw(XIO); + virtual void sendLeave() throw(XIO); + virtual void sendGrabClipboard() throw(XIO); + virtual void sendQueryClipboard() throw(XIO); + virtual void sendScreenSaver(bool on) throw(XIO); + virtual void sendKeyDown(KeyID, KeyModifierMask) throw(XIO); + virtual void sendKeyRepeat(KeyID, KeyModifierMask) throw(XIO); + virtual void sendKeyUp(KeyID, KeyModifierMask) throw(XIO); + virtual void sendMouseDown(ButtonID) throw(XIO); + virtual void sendMouseUp(ButtonID) throw(XIO); + virtual void sendMouseMove(SInt32 xAbs, SInt32 yAbs) throw(XIO); + virtual void sendMouseWheel(SInt32 delta) throw(XIO); + + protected: + // IServerProtocol overrides + virtual void recvInfo() throw(XIO,XBadClient); +}; + +#endif diff --git a/synergy/CTCPSocketFactory.cpp b/synergy/CTCPSocketFactory.cpp new file mode 100644 index 00000000..cecbb348 --- /dev/null +++ b/synergy/CTCPSocketFactory.cpp @@ -0,0 +1,27 @@ +#include "CTCPSocketFactory.h" +#include "CTCPSocket.h" +#include "CTCPListenSocket.h" + +// +// CTCPSocketFactory +// + +CTCPSocketFactory::CTCPSocketFactory() +{ + // do nothing +} + +CTCPSocketFactory::~CTCPSocketFactory() +{ + // do nothing +} + +ISocket* CTCPSocketFactory::create() const throw(XSocket) +{ + return new CTCPSocket; +} + +IListenSocket* CTCPSocketFactory::createListen() const throw(XSocket) +{ + return new CTCPListenSocket; +} diff --git a/synergy/CTCPSocketFactory.h b/synergy/CTCPSocketFactory.h new file mode 100644 index 00000000..db38ee6e --- /dev/null +++ b/synergy/CTCPSocketFactory.h @@ -0,0 +1,20 @@ +#ifndef CTCPSOCKETFACTORY_H +#define CTCPSOCKETFACTORY_H + +#include "ISocketFactory.h" + +class CTCPSocketFactory : public ISocketFactory { + public: + CTCPSocketFactory(); + virtual ~CTCPSocketFactory(); + + // manipulators + + // accessors + + // ISocketFactory overrides + virtual ISocket* create() const throw(XSocket); + virtual IListenSocket* createListen() const throw(XSocket); +}; + +#endif diff --git a/synergy/CXWindowsPrimaryScreen.cpp b/synergy/CXWindowsPrimaryScreen.cpp new file mode 100644 index 00000000..1c24b332 --- /dev/null +++ b/synergy/CXWindowsPrimaryScreen.cpp @@ -0,0 +1,363 @@ +#include "CXWindowsPrimaryScreen.h" +#include "CServer.h" +#include "CThread.h" +#include "TMethodJob.h" +#include +#include +#include + +// +// CXWindowsPrimaryScreen +// + +CXWindowsPrimaryScreen::CXWindowsPrimaryScreen() : + m_server(NULL), + m_display(NULL), + m_w(0), m_h(0), + m_window(None), + m_active(false) +{ + // do nothing +} + +CXWindowsPrimaryScreen::~CXWindowsPrimaryScreen() +{ + assert(m_display == NULL); +} + +void CXWindowsPrimaryScreen::open(CServer* server) +{ + assert(m_server == NULL); + assert(server != NULL); + + // set the server + m_server = server; + + // open the display + m_display = ::XOpenDisplay(NULL); // FIXME -- allow non-default + if (m_display == NULL) + throw int(5); // FIXME -- make exception for this + + // get default screen + m_screen = DefaultScreen(m_display); + Screen* screen = ScreenOfDisplay(m_display, m_screen); + + // get screen size + m_w = WidthOfScreen(screen); + m_h = HeightOfScreen(screen); + + // get the root window + Window root = RootWindow(m_display, m_screen); + + // create the grab window. this window is used to capture user + // input when the user is focussed on another client. don't let + // the window manager mess with it. + XSetWindowAttributes attr; + attr.event_mask = PointerMotionMask |// PointerMotionHintMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask; + attr.do_not_propagate_mask = 0; + attr.override_redirect = True; + attr.cursor = None; + m_window = ::XCreateWindow(m_display, root, 0, 0, m_w, m_h, 0, 0, + InputOnly, CopyFromParent, + CWDontPropagate | CWEventMask | + CWOverrideRedirect | CWCursor, + &attr); + + // start watching for events on other windows + selectEvents(root); + + // start processing events + m_eventThread = new CThread(new TMethodJob( + this, &CXWindowsPrimaryScreen::eventThread)); +} + +void CXWindowsPrimaryScreen::close() +{ + assert(m_server != NULL); + assert(m_window != None); + assert(m_eventThread != NULL); + + // stop event thread + m_eventThread->cancel(); + m_eventThread->wait(); + delete m_eventThread; + m_eventThread = NULL; + + // destroy window + ::XDestroyWindow(m_display, m_window); + m_window = None; + + // close the display + ::XCloseDisplay(m_display); + m_display = NULL; +} + +void CXWindowsPrimaryScreen::enter(SInt32 x, SInt32 y) +{ + assert(m_display != NULL); + assert(m_window != None); + assert(m_active == true); + + // warp to requested location + ::XWarpPointer(m_display, None, m_window, 0, 0, 0, 0, x, y); + + // unmap the grab window. this also ungrabs the mouse and keyboard. + ::XUnmapWindow(m_display, m_window); + + // remove all input events for grab window + XEvent event; + while (::XCheckWindowEvent(m_display, m_window, + PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask, + &event)) { + // do nothing + } + + // not active anymore + m_active = false; +} + +void CXWindowsPrimaryScreen::leave() +{ + assert(m_display != NULL); + assert(m_window != None); + assert(m_active == false); + + // raise and show the input window + ::XMapRaised(m_display, m_window); + + // grab the mouse and keyboard. keep trying until we get them. + // if we can't grab one after grabbing the other then ungrab + // and wait before retrying. + int result; + do { + // mouse first + do { + result = ::XGrabPointer(m_display, m_window, True, 0, + GrabModeAsync, GrabModeAsync, + m_window, None, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) + CThread::sleep(0.25); + } while (result != GrabSuccess); + + // now the keyboard + result = ::XGrabKeyboard(m_display, m_window, True, + GrabModeAsync, GrabModeAsync, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + ::XUngrabPointer(m_display, CurrentTime); + CThread::sleep(0.25); + } + } while (result != GrabSuccess); + + // move the mouse to the center of grab window + warpCursor(m_w >> 1, m_h >> 1); + + // local client now active + m_active = true; +} + +void CXWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y) +{ + + // warp the mouse + Window root = RootWindow(m_display, m_screen); + ::XWarpPointer(m_display, None, root, 0, 0, 0, 0, x, y); + ::XSync(m_display, False); + + // discard mouse events since we just added one we don't want + XEvent xevent; + while (::XCheckWindowEvent(m_display, m_window, + PointerMotionMask, &xevent)) { + // do nothing + } +} + +void CXWindowsPrimaryScreen::getSize( + SInt32* width, SInt32* height) const +{ + assert(m_display != NULL); + assert(width != NULL && height != NULL); + + *width = m_w; + *height = m_h; +} + +SInt32 CXWindowsPrimaryScreen::getJumpZoneSize() const +{ + assert(m_display != NULL); + + return 1; +} + +void CXWindowsPrimaryScreen::selectEvents(Window w) const +{ + // we want to track the mouse everywhere on the display. to achieve + // that we select PointerMotionMask on every window. we also select + // SubstructureNotifyMask in order to get CreateNotify events so we + // select events on new windows too. + + // we don't want to adjust our grab window + if (w == m_window) + return; + + // select events of interest + ::XSelectInput(m_display, w, PointerMotionMask | SubstructureNotifyMask); + + // recurse on child windows + Window rw, pw, *cw; + unsigned int nc; + if (::XQueryTree(m_display, w, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) + selectEvents(cw[i]); + ::XFree(cw); + } +} + +void CXWindowsPrimaryScreen::eventThread(void*) +{ + for (;;) { + // wait for and then get the next event + while (XPending(m_display) == 0) { + CThread::sleep(0.05); + } + XEvent xevent; + XNextEvent(m_display, &xevent); + + // handle event + switch (xevent.type) { + case CreateNotify: + // select events on new window + selectEvents(xevent.xcreatewindow.window); + break; + + case KeyPress: { + const KeyModifierMask mask = mapModifier(xevent.xkey.state); + const KeyID key = mapKey(xevent.xkey.keycode, mask); + if (key != kKeyNone) { + m_server->onKeyDown(key, mask); + } + break; + } + + // FIXME -- simulate key repeat. X sends press/release for + // repeat. must detect auto repeat and use kKeyRepeat. + case KeyRelease: { + const KeyModifierMask mask = mapModifier(xevent.xkey.state); + const KeyID key = mapKey(xevent.xkey.keycode, mask); + if (key != kKeyNone) { + m_server->onKeyUp(key, mask); + } + break; + } + + case ButtonPress: { + const ButtonID button = mapButton(xevent.xbutton.button); + if (button != kButtonNone) { + m_server->onMouseDown(button); + } + break; + } + + case ButtonRelease: { + const ButtonID button = mapButton(xevent.xbutton.button); + if (button != kButtonNone) { + m_server->onMouseUp(button); + } + break; + } + + case MotionNotify: { + SInt32 x, y; + if (!m_active) { + x = xevent.xmotion.x_root; + y = xevent.xmotion.y_root; + m_server->onMouseMovePrimary(x, y); + } + else { + // FIXME -- slurp up all remaining motion events? + // probably not since key strokes may go to wrong place. + + // get mouse deltas + Window root, window; + int xRoot, yRoot, xWindow, yWindow; + unsigned int mask; + if (!::XQueryPointer(m_display, m_window, &root, &window, + &xRoot, &yRoot, &xWindow, &yWindow, &mask)) + break; + x = xRoot - (m_w >> 1); + y = yRoot - (m_h >> 1); + + // warp mouse back to center + warpCursor(m_w >> 1, m_h >> 1); + + m_server->onMouseMoveSecondary(x, y); + } + break; + } + +/* + case SelectionClear: + target->XXX(xevent.xselectionclear.); + break; + + case SelectionNotify: + target->XXX(xevent.xselection.); + break; + + case SelectionRequest: + target->XXX(xevent.xselectionrequest.); + break; +*/ + } + } +} + +KeyModifierMask CXWindowsPrimaryScreen::mapModifier( + unsigned int state) const +{ + // FIXME -- should be configurable + KeyModifierMask mask = 0; + if (state & 1) + mask |= KeyModifierShift; + if (state & 2) + mask |= KeyModifierCapsLock; + if (state & 4) + mask |= KeyModifierControl; + if (state & 8) + mask |= KeyModifierAlt; + if (state & 16) + mask |= KeyModifierNumLock; + if (state & 32) + mask |= KeyModifierMeta; + if (state & 128) + mask |= KeyModifierScrollLock; + return mask; +} + +KeyID CXWindowsPrimaryScreen::mapKey( + KeyCode keycode, KeyModifierMask mask) const +{ + int index; + if (mask & KeyModifierShift) + index = 1; + else + index = 0; + return static_cast(::XKeycodeToKeysym(m_display, keycode, index)); +} + +ButtonID CXWindowsPrimaryScreen::mapButton( + unsigned int button) const +{ + // FIXME -- should use button mapping? + if (button >= 1 && button <= 3) + return static_cast(button); + else + return kButtonNone; +} diff --git a/synergy/CXWindowsPrimaryScreen.h b/synergy/CXWindowsPrimaryScreen.h new file mode 100644 index 00000000..14134cf4 --- /dev/null +++ b/synergy/CXWindowsPrimaryScreen.h @@ -0,0 +1,43 @@ +#ifndef CXWINDOWSPRIMARYSCREEN_H +#define CXWINDOWSPRIMARYSCREEN_H + +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "IPrimaryScreen.h" +#include + +class CThread; + +class CXWindowsPrimaryScreen : public IPrimaryScreen { + public: + CXWindowsPrimaryScreen(); + virtual ~CXWindowsPrimaryScreen(); + + // IPrimaryScreen overrides + virtual void open(CServer*); + virtual void close(); + virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute); + virtual void leave(); + virtual void warpCursor(SInt32 xAbsolute, SInt32 yAbsolute); + virtual void getSize(SInt32* width, SInt32* height) const; + virtual SInt32 getJumpZoneSize() const; + + private: + void selectEvents(Window) const; + + void eventThread(void*); + KeyModifierMask mapModifier(unsigned int state) const; + KeyID mapKey(KeyCode, KeyModifierMask) const; + ButtonID mapButton(unsigned int button) const; + + private: + CServer* m_server; + CThread* m_eventThread; + Display* m_display; + int m_screen; + SInt32 m_w, m_h; + Window m_window; + bool m_active; +}; + +#endif diff --git a/synergy/IPrimaryScreen.h b/synergy/IPrimaryScreen.h new file mode 100644 index 00000000..86f98498 --- /dev/null +++ b/synergy/IPrimaryScreen.h @@ -0,0 +1,73 @@ +#ifndef IPRIMARYSCREEN_H +#define IPRIMARYSCREEN_H + +#include "IInterface.h" +#include "BasicTypes.h" + +class CServer; +//class IClipboard; + +class IPrimaryScreen : public IInterface { + public: + // manipulators + + // initialize the screen and start reporting events to the server. + // events should be reported no matter where on the screen they + // occur but do not interfere with normal event dispatch. the + // screen saver engaging should be reported as an event. if that + // can't be detected then this object should disable the system's + // screen saver timer and should start the screen saver after + // idling for an appropriate time. + virtual void open(CServer*) = 0; + + // close the screen. should restore the screen saver timer if it + // was disabled. + virtual void close() = 0; + + // called when the user navigates back to the primary screen. + // warp the cursor to the given coordinates, unhide it, and + // ungrab the input devices. every call to method has a matching + // call to leave() which preceeds it. + virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute) = 0; + + // called when the user navigates off the primary screen. hide + // the cursor and grab exclusive access to the input devices. + virtual void leave() = 0; + + // warp the cursor to the given position + virtual void warpCursor(SInt32 xAbsolute, SInt32 yAbsolute) = 0; + +/* + // set the screen's clipboard contents. this is usually called + // soon after an enter(). + virtual void setClipboard(const IClipboard*) = 0; + + // show or hide the screen saver + virtual void onScreenSaver(bool show) = 0; + + // clipboard input + virtual void onClipboardChanged() = 0; +*/ + + // accessors + +/* + // get the screen's name. all screens must have a name unique on + // the server they connect to. the hostname is usually an + // appropriate name. + virtual CString getName() const = 0; +*/ + + // get the size of the screen + virtual void getSize(SInt32* width, SInt32* height) const = 0; + + // get the size of jump zone + virtual SInt32 getJumpZoneSize() const = 0; + +/* + // get the screen's clipboard contents + virtual void getClipboard(IClipboard*) const = 0; +*/ +}; + +#endif diff --git a/synergy/ISecondaryScreen.h b/synergy/ISecondaryScreen.h new file mode 100644 index 00000000..1deb45f9 --- /dev/null +++ b/synergy/ISecondaryScreen.h @@ -0,0 +1,71 @@ +#ifndef ISECONDARYSCREEN_H +#define ISECONDARYSCREEN_H + +#include "IInterface.h" +#include "BasicTypes.h" + +class CClient; +//class IClipboard; + +class ISecondaryScreen : public IInterface { + public: + // manipulators + + // initialize the screen, hide the cursor, and disable the screen + // saver. start reporting certain events to the client (clipboard + // stolen and screen size changed). + virtual void open(CClient*) = 0; + + // close the screen. should restore the screen saver. + virtual void close() = 0; + + // called when the user navigates to the secondary screen. warp + // the cursor to the given coordinates and unhide it. prepare to + // simulate input events. + virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute) = 0; + + // called when the user navigates off the secondary screen. clean + // up input event simulation and hide the cursor. + virtual void leave() = 0; + + // warp the cursor to the given position + virtual void warpCursor(SInt32 xAbsolute, SInt32 yAbsolute) = 0; + + // keyboard input simulation + virtual void onKeyDown(KeyID, KeyModifierMask) = 0; + virtual void onKeyRepeat(KeyID, KeyModifierMask, SInt32 count) = 0; + virtual void onKeyUp(KeyID, KeyModifierMask) = 0; + + // mouse input simulation + virtual void onMouseDown(ButtonID) = 0; + virtual void onMouseUp(ButtonID) = 0; + virtual void onMouseMove(SInt32 xAbsolute, SInt32 yAbsolute) = 0; + virtual void onMouseWheel(SInt32 delta) = 0; + +/* + // set the screen's clipboard contents. this is usually called + // soon after an enter(). + virtual void setClipboard(const IClipboard*) = 0; + + // show or hide the screen saver + virtual void onScreenSaver(bool show) = 0; + + // clipboard input + virtual void onClipboardChanged() = 0; +*/ + + // accessors + + // get the size of the screen + virtual void getSize(SInt32* width, SInt32* height) const = 0; + + // get the size of jump zone + virtual SInt32 getJumpZoneSize() const = 0; + +/* + // get the screen's clipboard contents + virtual void getClipboard(IClipboard*) const = 0; +*/ +}; + +#endif diff --git a/synergy/IServerProtocol.h b/synergy/IServerProtocol.h new file mode 100644 index 00000000..cf5ece9f --- /dev/null +++ b/synergy/IServerProtocol.h @@ -0,0 +1,47 @@ +#ifndef ISERVERPROTOCOL_H +#define ISERVERPROTOCOL_H + +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "IInterface.h" +#include "XSynergy.h" +#include "XIO.h" + +class IServerProtocol : public IInterface { + public: + // manipulators + + // process messages from the client and insert the appropriate + // events into the server's event queue. return when the client + // disconnects. + virtual void run() throw(XIO,XBadClient) = 0; + + // send client info query and process reply + virtual void queryInfo() throw(XIO,XBadClient) = 0; + + // send various messages to client + virtual void sendClose() throw(XIO) = 0; + virtual void sendEnter(SInt32 xAbs, SInt32 yAbs) throw(XIO) = 0; + virtual void sendLeave() throw(XIO) = 0; + virtual void sendGrabClipboard() throw(XIO) = 0; + virtual void sendQueryClipboard() throw(XIO) = 0; + virtual void sendScreenSaver(bool on) throw(XIO) = 0; + virtual void sendKeyDown(KeyID, KeyModifierMask) throw(XIO) = 0; + virtual void sendKeyRepeat(KeyID, KeyModifierMask) throw(XIO) = 0; + virtual void sendKeyUp(KeyID, KeyModifierMask) throw(XIO) = 0; + virtual void sendMouseDown(ButtonID) throw(XIO) = 0; + virtual void sendMouseUp(ButtonID) throw(XIO) = 0; + virtual void sendMouseMove(SInt32 xAbs, SInt32 yAbs) throw(XIO) = 0; + virtual void sendMouseWheel(SInt32 delta) throw(XIO) = 0; + + // accessors + + protected: + // manipulators + + virtual void recvInfo() throw(XIO,XBadClient) = 0; + + // accessors +}; + +#endif diff --git a/synergy/ISocketFactory.h b/synergy/ISocketFactory.h new file mode 100644 index 00000000..e919432d --- /dev/null +++ b/synergy/ISocketFactory.h @@ -0,0 +1,21 @@ +#ifndef ISOCKETFACTORY_H +#define ISOCKETFACTORY_H + +#include "IInterface.h" +#include "XSocket.h" + +class ISocket; +class IListenSocket; + +class ISocketFactory : public IInterface { + public: + // manipulators + + // accessors + + // create sockets + virtual ISocket* create() const throw(XSocket) = 0; + virtual IListenSocket* createListen() const throw(XSocket) = 0; +}; + +#endif diff --git a/KeyTypes.h b/synergy/KeyTypes.h similarity index 96% rename from KeyTypes.h rename to synergy/KeyTypes.h index c3789fa1..ec070e3f 100644 --- a/KeyTypes.h +++ b/synergy/KeyTypes.h @@ -1,6 +1,8 @@ #ifndef KEYTYPES_H #define KEYTYPES_H +#include "BasicTypes.h" + // type to hold a key identifier typedef UInt32 KeyID; diff --git a/synergy/Makefile b/synergy/Makefile new file mode 100644 index 00000000..c16c90e7 --- /dev/null +++ b/synergy/Makefile @@ -0,0 +1,44 @@ +DEPTH=.. +include $(DEPTH)/Makecommon + +# +# source files +# +LCXXINCS = \ + -I$(DEPTH)/base \ + -I$(DEPTH)/mt \ + -I$(DEPTH)/io \ + -I$(DEPTH)/net \ + $(NULL) +CXXFILES = \ + CInputPacketStream.cpp \ + COutputPacketStream.cpp \ + CTCPSocketFactory.cpp \ + CProtocolUtil.cpp \ + CServerProtocol.cpp \ + CServerProtocol1_0.cpp \ + CScreenMap.cpp \ + CServer.cpp \ + CXWindowsPrimaryScreen.cpp \ + XSynergy.cpp \ + $(NULL) + +# +# libraries we depend on +# +DEPLIBS = \ + $(LIBDIR)/libnet.a \ + $(LIBDIR)/libio.a \ + $(LIBDIR)/libmt.a \ + $(LIBDIR)/libbase.a \ + $(NULL) +LLDLIBS = \ + $(DEPLIBS) \ + -lpthread \ + $(NULL) + +targets: server + +server: $(OBJECTS) $(DEPLIBS) + $(CXX) $(CXXFLAGS) -o $@ $(OBJECTS) $(LDFLAGS) + diff --git a/MouseTypes.h b/synergy/MouseTypes.h similarity index 92% rename from MouseTypes.h rename to synergy/MouseTypes.h index 3dea13d9..ad5f0c7a 100644 --- a/MouseTypes.h +++ b/synergy/MouseTypes.h @@ -1,6 +1,8 @@ #ifndef MOUSETYPES_H #define MOUSETYPES_H +#include "BasicTypes.h" + // type to hold mouse button identifier typedef UInt8 ButtonID; diff --git a/synergy/ProtocolTypes.h b/synergy/ProtocolTypes.h new file mode 100644 index 00000000..c41935c0 --- /dev/null +++ b/synergy/ProtocolTypes.h @@ -0,0 +1,38 @@ +#ifndef PROTOCOLTYPES_H +#define PROTOCOLTYPES_H + +#include "BasicTypes.h" + +// version number +static const SInt32 kMajorVersion = 0; +static const SInt32 kMinorVersion = 1; + +// message codes (trailing NUL is not part of code). codes are +// grouped into: +// commands -- request an action, no reply expected +// queries -- request info +// data -- send info +// errors -- notify of error +static const char kMsgCClose[] = "CBYE"; // server +static const char kMsgCEnter[] = "CINN%2i%2i"; // server +static const char kMsgCLeave[] = "COUT"; // server +static const char kMsgCClipboard[] = "CCLP"; // server +static const char kMsgCScreenSaver[] = "CSEC%1i"; // server + +static const char kMsgDKeyDown[] = "DKDN%2i%2i"; // server +static const char kMsgDKeyRepeat[] = "DKRP%2i%2i%2i"; // server +static const char kMsgDKeyUp[] = "DKUP%2i%2i"; // server +static const char kMsgDMouseDown[] = "DMDN%1i"; // server +static const char kMsgDMouseUp[] = "DMUP%1i"; // server +static const char kMsgDMouseMove[] = "DMMV%2i%2i"; // server +static const char kMsgDMouseWheel[] = "DMWM%2i"; // server +static const char kMsgDClipboard[] = "DCLP%s"; // server +static const char kMsgDInfo[] = "DINF%2i%2i%2i"; // client + +static const char kMsgQClipboard[] = "QCLP"; // server +static const char kMsgQInfo[] = "QINF"; // server + +static const char kMsgEIncompatible[] = "EICV"; + +#endif + diff --git a/synergy/XSynergy.cpp b/synergy/XSynergy.cpp new file mode 100644 index 00000000..bda330a3 --- /dev/null +++ b/synergy/XSynergy.cpp @@ -0,0 +1,37 @@ +#include "XSynergy.h" + +// +// XBadClient +// + +CString XBadClient::getWhat() const throw() +{ + return "XBadClient"; +} + +// +// +// + +XIncompatibleClient::XIncompatibleClient(int major, int minor) : + m_major(major), + m_minor(minor) +{ + // do nothing +} + +int XIncompatibleClient::getMajor() const throw() +{ + return m_major; +} + +int XIncompatibleClient::getMinor() const throw() +{ + return m_minor; +} + +CString XIncompatibleClient::getWhat() const throw() +{ + return "XIncompatibleClient"; +} + diff --git a/synergy/XSynergy.h b/synergy/XSynergy.h new file mode 100644 index 00000000..da54af49 --- /dev/null +++ b/synergy/XSynergy.h @@ -0,0 +1,34 @@ +#ifndef XSYNERGY_H +#define XSYNERGY_H + +#include "XBase.h" + +class XSynergy : public XBase { }; + +// client is misbehaving +class XBadClient : public XSynergy { + protected: + virtual CString getWhat() const throw(); +}; + +// client has incompatible version +class XIncompatibleClient : public XSynergy { + public: + XIncompatibleClient(int major, int minor); + + // manipulators + + // accessors + + int getMajor() const throw(); + int getMinor() const throw(); + + protected: + virtual CString getWhat() const throw(); + + private: + int m_major; + int m_minor; +}; + +#endif diff --git a/test.cpp b/test.cpp new file mode 100644 index 00000000..cd62450f --- /dev/null +++ b/test.cpp @@ -0,0 +1,145 @@ +#include "CTCPSocket.h" +#include "CTCPListenSocket.h" +#include "CNetworkAddress.h" +#include "IInputStream.h" +#include "IOutputStream.h" +#include "CThread.h" +#include "CFunctionJob.h" +#include + +// +// test +// + +SInt16 port = 50000; + +static void thread1(void*) +{ + try { + fprintf(stdout, "client started\n"); + CThread::sleep(1.0); + CNetworkAddress addr("127.0.0.1", port); + + fprintf(stdout, "client connecting\n"); + CTCPSocket* socket = new CTCPSocket; + socket->connect(addr); + + fprintf(stdout, "client connected (%p). waiting.\n", socket); + CThread::sleep(2.0); + + fprintf(stdout, "client sending message\n"); + static const char msg[] = "message from client\n"; + socket->getOutputStream()->write(msg, sizeof(msg) - 1); + socket->getOutputStream()->flush(); + + fprintf(stdout, "client waiting for reply\n"); + UInt8 buffer[4096]; + UInt32 n; + do { + n = socket->getInputStream()->read(buffer, sizeof(buffer) - 1); + buffer[n] = 0; + fprintf(stdout, "%s", buffer); + } while (n == 0 && memchr(buffer, '\n', n) == NULL); + + fprintf(stdout, "client closing\n"); + socket->close(); + delete socket; + fprintf(stdout, "client terminating\n"); + } + catch (XBase& e) { + fprintf(stderr, "exception: %s\n", e.what()); + } +} + +static void thread2(void*) +{ + try { + fprintf(stdout, "server started\n"); + CNetworkAddress addr("127.0.0.1", port); + CTCPListenSocket listenSocket; + listenSocket.bind(addr); + + fprintf(stdout, "server accepting\n"); + ISocket* socket = listenSocket.accept(); + fprintf(stdout, "server accepted %p\n", socket); + + UInt8 buffer[4096]; + UInt32 n; + do { + n = socket->getInputStream()->read(buffer, sizeof(buffer) - 1); + buffer[n] = 0; + fprintf(stdout, "%s", buffer); + } while (n == 0 && memchr(buffer, '\n', n) == NULL); + + fprintf(stdout, "server replying\n"); + static const char reply[] = "data received\n"; + socket->getOutputStream()->write(reply, sizeof(reply) - 1); + + fprintf(stdout, "server closing\n"); + socket->close(); + delete socket; + fprintf(stdout, "server terminating\n"); + } + catch (XBase& e) { + fprintf(stderr, "exception: %s\n", e.what()); + } +} + +static void thread3(void*) +{ + try { + fprintf(stdout, "### looking up address\n"); + CNetworkAddress addr("www.google.com", 80); + + fprintf(stdout, "### connecting\n"); + CTCPSocket* socket = new CTCPSocket; + socket->connect(addr); + + fprintf(stdout, "### sending message\n"); + static const char msg[] = "GET / HTTP/1.0\nAccept: */*\n\n"; + socket->getOutputStream()->write(msg, sizeof(msg) - 1); + socket->getOutputStream()->flush(); + socket->getOutputStream()->close(); + + fprintf(stdout, "### waiting for reply\n"); + UInt8 buffer[4096]; + UInt32 n; + do { + n = socket->getInputStream()->read(buffer, sizeof(buffer) - 1); + buffer[n] = 0; + fprintf(stdout, "%s", buffer); + } while (n != 0); + + fprintf(stdout, "### closing\n"); + socket->close(); + delete socket; + fprintf(stdout, "### terminating\n"); + } + catch (XBase& e) { + fprintf(stderr, "### exception: %s\n", e.what()); + } +} + +int main(int argc, char** argv) +{ +/* + if (argc > 1) { + port = (SInt16)atoi(argv[1]); + } + + fprintf(stdout, "starting threads\n"); + CThread t1(new CFunctionJob(thread1)); + CThread t2(new CFunctionJob(thread2)); + + fprintf(stdout, "waiting for threads\n"); + t1.wait(); + t2.wait(); + fprintf(stdout, "threads finished\n"); +*/ + int tick = 0; + CThread t3(new CFunctionJob(thread3)); + while (!t3.wait(0.1)) { + fprintf(stdout, "$$$ %d\n", ++tick); + } + return 0; +}