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; +}