From 900b075e3aa03890f1538d97558920fc2bb9ae59 Mon Sep 17 00:00:00 2001 From: crs Date: Sun, 13 May 2001 11:40:29 +0000 Subject: [PATCH] initial revision of synergy. currently semi-supports X windows on unix, but client screens don't simulate events other than mouse move. also not supporting clipboard at all yet and the main app is just a temporary framework to test with. must clean up protocol and communication. --- BasicTypes.h | 33 ++ CClient.cpp | 287 ++++++++++++++++ CClient.h | 28 ++ CEvent.h | 58 ++++ CEventQueue.cpp | 80 +++++ CEventQueue.h | 36 ++ CMessageSocket.cpp | 153 +++++++++ CMessageSocket.h | 33 ++ CProtocol.h | 20 ++ CScreenProxy.cpp | 234 +++++++++++++ CScreenProxy.h | 43 +++ CServer.cpp | 811 ++++++++++++++++++++++++++++++++++++++++++++ CServer.h | 103 ++++++ CSocket.cpp | 58 ++++ CSocket.h | 41 +++ CSocketFactory.cpp | 31 ++ CSocketFactory.h | 29 ++ CString.h | 40 +++ CTrace.cpp | 16 + CTrace.h | 19 ++ CUnixEventQueue.cpp | 141 ++++++++ CUnixEventQueue.h | 54 +++ CUnixTCPSocket.cpp | 262 ++++++++++++++ CUnixTCPSocket.h | 47 +++ CUnixXScreen.cpp | 34 ++ CUnixXScreen.h | 16 + CXScreen.cpp | 571 +++++++++++++++++++++++++++++++ CXScreen.h | 81 +++++ IClient.h | 17 + IClipboard.h | 0 IEventQueue.h | 45 +++ IJob.h | 14 + IScreen.h | 132 +++++++ IServer.h | 29 ++ ISocket.h | 45 +++ KeyTypes.h | 18 + Make-linux | 103 ++++++ Makefile | 38 +++ MouseTypes.h | 13 + TMethodJob.h | 23 ++ XBase.cpp | 40 +++ XBase.h | 31 ++ XSocket.h | 33 ++ main.cpp | 114 +++++++ tools/depconv | 46 +++ 45 files changed, 4100 insertions(+) create mode 100644 BasicTypes.h create mode 100644 CClient.cpp create mode 100644 CClient.h create mode 100644 CEvent.h create mode 100644 CEventQueue.cpp create mode 100644 CEventQueue.h create mode 100644 CMessageSocket.cpp create mode 100644 CMessageSocket.h create mode 100644 CProtocol.h create mode 100644 CScreenProxy.cpp create mode 100644 CScreenProxy.h create mode 100644 CServer.cpp create mode 100644 CServer.h create mode 100644 CSocket.cpp create mode 100644 CSocket.h create mode 100644 CSocketFactory.cpp create mode 100644 CSocketFactory.h create mode 100644 CString.h create mode 100644 CTrace.cpp create mode 100644 CTrace.h create mode 100644 CUnixEventQueue.cpp create mode 100644 CUnixEventQueue.h create mode 100644 CUnixTCPSocket.cpp create mode 100644 CUnixTCPSocket.h create mode 100644 CUnixXScreen.cpp create mode 100644 CUnixXScreen.h create mode 100644 CXScreen.cpp create mode 100644 CXScreen.h create mode 100644 IClient.h create mode 100644 IClipboard.h create mode 100644 IEventQueue.h create mode 100644 IJob.h create mode 100644 IScreen.h create mode 100644 IServer.h create mode 100644 ISocket.h create mode 100644 KeyTypes.h create mode 100644 Make-linux create mode 100644 Makefile create mode 100644 MouseTypes.h create mode 100644 TMethodJob.h create mode 100644 XBase.cpp create mode 100644 XBase.h create mode 100644 XSocket.h create mode 100644 main.cpp create mode 100755 tools/depconv diff --git a/BasicTypes.h b/BasicTypes.h new file mode 100644 index 00000000..8dfce8c2 --- /dev/null +++ b/BasicTypes.h @@ -0,0 +1,33 @@ +#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 new file mode 100644 index 00000000..65030c99 --- /dev/null +++ b/CClient.cpp @@ -0,0 +1,287 @@ +#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]) )); + TRACE((" key down: %d", k)); + m_screen->onKeyDown(k); + 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 SInt32 n = static_cast( + (static_cast(buf[5]) << 24) + + (static_cast(buf[6]) << 16) + + (static_cast(buf[7]) << 8) + + (static_cast(buf[8]) )); + TRACE((" key repeat: %d x%d", k, n)); + m_screen->onKeyRepeat(k, 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]) )); + TRACE((" key up: %d", k)); + m_screen->onKeyUp(k); + break; + } + + case '\012': { + const KeyToggleMask m = static_cast( + (static_cast(buf[1]) << 8) + + (static_cast(buf[2]) )); + TRACE((" key toggle: 0x%04x", m)); + m_screen->onKeyToggle(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 new file mode 100644 index 00000000..c99cb364 --- /dev/null +++ b/CClient.h @@ -0,0 +1,28 @@ +#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 new file mode 100644 index 00000000..452aa4e6 --- /dev/null +++ b/CEvent.h @@ -0,0 +1,58 @@ +#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; + 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 new file mode 100644 index 00000000..0cef9742 --- /dev/null +++ b/CEventQueue.cpp @@ -0,0 +1,80 @@ +#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 new file mode 100644 index 00000000..de6a853d --- /dev/null +++ b/CEventQueue.h @@ -0,0 +1,36 @@ +#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 new file mode 100644 index 00000000..c89c0db1 --- /dev/null +++ b/CMessageSocket.cpp @@ -0,0 +1,153 @@ +#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[1]) << 8) + + (static_cast(m_buffer[2]) )); + 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 new file mode 100644 index 00000000..b199c3ec --- /dev/null +++ b/CMessageSocket.h @@ -0,0 +1,33 @@ +#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 new file mode 100644 index 00000000..83659fd4 --- /dev/null +++ b/CProtocol.h @@ -0,0 +1,20 @@ +#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 new file mode 100644 index 00000000..d287107d --- /dev/null +++ b/CScreenProxy.cpp @@ -0,0 +1,234 @@ +#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) +{ + char buf[5]; + 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); + m_socket->write(buf, sizeof(buf)); +} + +void CScreenProxy::onKeyRepeat(KeyID k, SInt32 n) +{ + char buf[9]; + 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((n >> 24) & 0xff); + buf[6] = static_cast((n >> 16) & 0xff); + buf[7] = static_cast((n >> 8) & 0xff); + buf[8] = static_cast(n & 0xff); + m_socket->write(buf, sizeof(buf)); +} + +void CScreenProxy::onKeyUp(KeyID k) +{ + char buf[5]; + 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); + m_socket->write(buf, sizeof(buf)); +} + +void CScreenProxy::onKeyToggle(KeyToggleMask m) +{ + char buf[3]; + memcpy(buf, "\012", 1); + buf[1] = static_cast((m >> 8) & 0xff); + buf[2] = 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 new file mode 100644 index 00000000..39786d82 --- /dev/null +++ b/CScreenProxy.h @@ -0,0 +1,43 @@ +#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); + virtual void onKeyRepeat(KeyID, SInt32 count); + virtual void onKeyUp(KeyID); + virtual void onKeyToggle(KeyToggleMask); + 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 new file mode 100644 index 00000000..fb28810f --- /dev/null +++ b/CServer.cpp @@ -0,0 +1,811 @@ +#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); + break; + + case CEventBase::kKeyRepeat: + m_activeScreen->onKeyRepeat(event->m_key.m_key, event->m_key.m_count); + break; + + case CEventBase::kKeyUp: + m_activeScreen->onKeyUp(event->m_key.m_key); + 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 new file mode 100644 index 00000000..b95fa7ef --- /dev/null +++ b/CServer.h @@ -0,0 +1,103 @@ +#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 new file mode 100644 index 00000000..311d87d0 --- /dev/null +++ b/CSocket.cpp @@ -0,0 +1,58 @@ +#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 new file mode 100644 index 00000000..aa4cf9f8 --- /dev/null +++ b/CSocket.h @@ -0,0 +1,41 @@ +#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 new file mode 100644 index 00000000..0995b897 --- /dev/null +++ b/CSocketFactory.cpp @@ -0,0 +1,31 @@ +#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 new file mode 100644 index 00000000..7a73d391 --- /dev/null +++ b/CSocketFactory.h @@ -0,0 +1,29 @@ +#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 new file mode 100644 index 00000000..9b5667ac --- /dev/null +++ b/CString.h @@ -0,0 +1,40 @@ +#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 new file mode 100644 index 00000000..e782616a --- /dev/null +++ b/CTrace.cpp @@ -0,0 +1,16 @@ +#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 new file mode 100644 index 00000000..34787ac9 --- /dev/null +++ b/CTrace.h @@ -0,0 +1,19 @@ +#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 new file mode 100644 index 00000000..db685dca --- /dev/null +++ b/CUnixEventQueue.cpp @@ -0,0 +1,141 @@ +#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 new file mode 100644 index 00000000..217efbf4 --- /dev/null +++ b/CUnixEventQueue.h @@ -0,0 +1,54 @@ +#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 new file mode 100644 index 00000000..ebec593b --- /dev/null +++ b/CUnixTCPSocket.cpp @@ -0,0 +1,262 @@ +#include "CUnixTCPSocket.h" +#include "CUnixEventQueue.h" +#include "CString.h" +#include "TMethodJob.h" +#include "XSocket.h" +#include +#include +#include +#include +#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)); + } +} + +CUnixTCPSocket::CUnixTCPSocket(int fd) : m_fd(fd), + m_state(kConnected), + m_addedJobs(false) +{ + assert(m_fd != -1); +} + +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; + } +} diff --git a/CUnixTCPSocket.h b/CUnixTCPSocket.h new file mode 100644 index 00000000..ac8d0a34 --- /dev/null +++ b/CUnixTCPSocket.h @@ -0,0 +1,47 @@ +#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); + + // 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 new file mode 100644 index 00000000..752a774d --- /dev/null +++ b/CUnixXScreen.cpp @@ -0,0 +1,34 @@ +#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 new file mode 100644 index 00000000..459eed8c --- /dev/null +++ b/CUnixXScreen.h @@ -0,0 +1,16 @@ +#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 new file mode 100644 index 00000000..bd683eb4 --- /dev/null +++ b/CXScreen.cpp @@ -0,0 +1,571 @@ +#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) +{ + assert(m_display != NULL); + assert(m_primary == false); + + // FIXME +} + +void CXScreen::onKeyRepeat(KeyID, SInt32) +{ + assert(m_display != NULL); + assert(m_primary == false); + + // FIXME +} + +void CXScreen::onKeyUp(KeyID) +{ + assert(m_display != NULL); + assert(m_primary == false); + + // FIXME +} + +void CXScreen::onKeyToggle(KeyToggleMask) +{ + assert(m_display != NULL); + assert(m_primary == false); + + // FIXME +} + +void CXScreen::onMouseDown(ButtonID) +{ + assert(m_display != NULL); + assert(m_primary == false); + + // FIXME +} + +void CXScreen::onMouseUp(ButtonID) +{ + assert(m_display != NULL); + assert(m_primary == false); + + // FIXME +} + +void CXScreen::onMouseMove(SInt32 x, SInt32 y) +{ + assert(m_display != NULL); + assert(m_primary == false); + + XTestFakeMotionEvent(m_display, m_screen, x, y, CurrentTime); +} + +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); + } +} + +KeyID CXScreen::mapKey(unsigned int keycode) const +{ + return keycode; +} + +ButtonID CXScreen::mapButton(unsigned int button) const +{ + switch (button) { + case 1: + return kButtonLeft; + + case 2: + return kButtonMiddle; + + case 3: + return kButtonRight; + } + + return kButtonNone; +} + +void CXScreen::onPrimaryEvents() +{ + while (XPending(m_display) > 0) { + XEvent xevent; + XNextEvent(m_display, &xevent); + + switch (xevent.type) { + case KeyPress: { + const KeyID key = mapKey(xevent.xkey.keycode); + if (key != kKeyNone) { + CEvent event; + event.m_key.m_type = CEventBase::kKeyDown; + event.m_key.m_key = key; + 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 KeyID key = mapKey(xevent.xkey.keycode); + if (key != kKeyNone) { + CEvent event; + event.m_key.m_type = CEventBase::kKeyUp; + event.m_key.m_key = key; + event.m_key.m_count = 0; + CEQ->push(&event); + } + break; + } + + case ButtonPress: { + const ButtonID button = mapButton(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 = mapButton(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 new file mode 100644 index 00000000..c2fb9b83 --- /dev/null +++ b/CXScreen.h @@ -0,0 +1,81 @@ +#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); + virtual void onKeyRepeat(KeyID, SInt32); + virtual void onKeyUp(KeyID); + virtual void onKeyToggle(KeyToggleMask); + 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; + KeyID mapKey(unsigned int keycode) const; + ButtonID mapButton(unsigned int button) 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 new file mode 100644 index 00000000..02dd7129 --- /dev/null +++ b/IClient.h @@ -0,0 +1,17 @@ +#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 new file mode 100644 index 00000000..e69de29b diff --git a/IEventQueue.h b/IEventQueue.h new file mode 100644 index 00000000..36609b0c --- /dev/null +++ b/IEventQueue.h @@ -0,0 +1,45 @@ +#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/IJob.h b/IJob.h new file mode 100644 index 00000000..381e29ad --- /dev/null +++ b/IJob.h @@ -0,0 +1,14 @@ +#ifndef IJOB_H +#define IJOB_H + +class IJob { + public: + IJob() { } + virtual ~IJob() { } + + // manipulators + + virtual void run() = 0; +}; + +#endif diff --git a/IScreen.h b/IScreen.h new file mode 100644 index 00000000..a8427584 --- /dev/null +++ b/IScreen.h @@ -0,0 +1,132 @@ +#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 may only called on secondary screens. + // + + // keyboard input + // onKeyToggle() sets the keyboard toggle key states (e.g. num lock). + virtual void onKeyDown(KeyID) = 0; + virtual void onKeyRepeat(KeyID, SInt32 count) = 0; + virtual void onKeyUp(KeyID) = 0; + virtual void onKeyToggle(KeyToggleMask) = 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 new file mode 100644 index 00000000..2a041d64 --- /dev/null +++ b/IServer.h @@ -0,0 +1,29 @@ +#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 new file mode 100644 index 00000000..1a46f5d9 --- /dev/null +++ b/ISocket.h @@ -0,0 +1,45 @@ +#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/KeyTypes.h b/KeyTypes.h new file mode 100644 index 00000000..c220a9fc --- /dev/null +++ b/KeyTypes.h @@ -0,0 +1,18 @@ +#ifndef KEYTYPES_H +#define KEYTYPES_H + +// type to hold a key identifier +typedef UInt32 KeyID; + +// type to hold bitmask of keys that have toggle states +typedef UInt16 KeyToggleMask; + +// toggle key bitmasks +static const UInt32 KeyToggleShiftLock = 0x0001; +static const UInt32 KeyToggleNumLock = 0x0002; +static const UInt32 KeyToggleScrollLock = 0x0004; + +// key codes +static const KeyID kKeyNone = 0; + +#endif diff --git a/Make-linux b/Make-linux new file mode 100644 index 00000000..e78e2da4 --- /dev/null +++ b/Make-linux @@ -0,0 +1,103 @@ +# +# empty define, used to terminate lists +# +NULL = + +# +# build tools +# +CXX = /usr/bin/g++ +LD = /usr/bin/ld +MKDIR = /bin/mkdir +RM = /bin/rm -f +RMR = /bin/rm -rf + +# +# compiler options +# +CXXFLAGS = $(LCXXFLAGS) $(GCXXFLAGS) $(CXXOPTIMIZER) $(MKDEPOPT) +LCXXFLAGS = $(LCXXDEFS) $(LCXXINCS) $(LCXXOPTS) +GCXXFLAGS = $(GCXXDEFS) $(GCXXINCS) $(GCXXOPTS) + +GCXXDEFS = -D_POSIX_SOURCE +GCXXINCS = -I$(DEPTH)/include -I/usr/include -I/usr/X11R6/include +GCXXOPTS = -Wall -W -fexceptions -fno-rtti +CXXOPTIMIZER = -g +#CXXOPTIMIZER = -O2 -DNDEBUG + +# +# linker options +# +LDFLAGS = $(LDOPTS) $(LDLIBS) +LDOPTS = $(LLDOPTS) $(GLDOPTS) +LDLIBS = $(LLDLIBS) $(GLDLIBS) + +GLDLIBS = -L/usr/X11R6/lib -lX11 -lXext -lXtst +GLDOPTS = + +# +# dependency generation stuff +# +MKDEP = $(DEPTH)/tools/depconv +MKDEPOPT = -MD +MKDEPPRE = +MKDEPPOST = $(MKDEP) -f $(MKDEPFILE) $*.d; $(RM) $*.d +MKDEPFILE = Makedepend + +# +# Convenience file list macros: +# +SOURCES = $(CXXFILES) +OBJECTS = $(CXXFILES:.cpp=.o) + +# +# stuff to clean +# +DIRT = $(_FORCE) $(LDIRT) $(GDIRT) +GDIRT = *.[eoud] a.out core ar.tmp.* $(MKDEPFILE) + +# +# always unsatisfied target +# +_FORCE = $(COMMONPREF)_force + +# +# +# common rules +# +# + +# +# default target. makefiles must define a target named `targets'. +# +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) + +# +# load dependencies +# +sinclude $(MKDEPFILE) diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..f8384949 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +DEPTH=. +include Make-linux + +# +# target files +# +TARGETS = main + +# +# source files +# +CXXFILES = \ + XBase.cpp \ + CTrace.cpp \ + CEventQueue.cpp \ + CSocket.cpp \ + CMessageSocket.cpp \ + CSocketFactory.cpp \ + CServer.cpp \ + CClient.cpp \ + CScreenProxy.cpp \ + CXScreen.cpp \ + CUnixXScreen.cpp \ + CUnixTCPSocket.cpp \ + CUnixEventQueue.cpp \ + main.cpp \ + $(NULL) + +# +# libraries we depend on +# +DEPLIBS = \ + $(NULL) + +targets: $(TARGETS) + +main: $(OBJECTS) $(DEPLIBS) + $(CXX) $(CXXFLAGS) -o $@ $(OBJECTS) $(LDFLAGS) diff --git a/MouseTypes.h b/MouseTypes.h new file mode 100644 index 00000000..ac908486 --- /dev/null +++ b/MouseTypes.h @@ -0,0 +1,13 @@ +#ifndef MOUSETYPES_H +#define MOUSETYPES_H + +// type to hold mouse button identifier +typedef UInt8 ButtonID; + +// mouse button identifiers +static const ButtonID kButtonNone = 0; +static const ButtonID kButtonLeft = 1; +static const ButtonID kButtonRight = 2; +static const ButtonID kButtonMiddle = 3; + +#endif diff --git a/TMethodJob.h b/TMethodJob.h new file mode 100644 index 00000000..341e3491 --- /dev/null +++ b/TMethodJob.h @@ -0,0 +1,23 @@ +#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 new file mode 100644 index 00000000..df1bd475 --- /dev/null +++ b/XBase.cpp @@ -0,0 +1,40 @@ +#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 new file mode 100644 index 00000000..f2ed5162 --- /dev/null +++ b/XBase.h @@ -0,0 +1,31 @@ +#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 new file mode 100644 index 00000000..e8f775f2 --- /dev/null +++ b/XSocket.h @@ -0,0 +1,33 @@ +#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/main.cpp b/main.cpp new file mode 100644 index 00000000..b879e1a1 --- /dev/null +++ b/main.cpp @@ -0,0 +1,114 @@ +#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/tools/depconv b/tools/depconv new file mode 100755 index 00000000..7bccd466 --- /dev/null +++ b/tools/depconv @@ -0,0 +1,46 @@ +#!/bin/sh +# +# add dependency info from files on command line to $depfile + +depfile="Makedepends" + +dependencies="" +targets="^$" + +# tmp directory +if test -z "$TMP"; then TMP=/tmp; fi + +while [ -n "$*" ]; do + case "$1" in + -f) + depfile=$2 + shift + shift + ;; + -*) + echo "usage: $0 [-f ] ..." + exit 1 + ;; + *) + break + esac +done + +# collect all dependencies +while [ -n "$*" ]; do + line=`cat $1 | sed -e 's/\\\\//g' | sed -e 's/ \/[^ ]*//g'` + target=`echo $line | sed -e 's/^\([^:]*\):.*/\1/'` + targets="$targets|^$target:" + dependencies="$dependencies$line\n" + shift +done + +# add new dependencies to $depfile +if [ -n "$targets" ]; then + if [ -r $depfile ]; then + (egrep -v $targets $depfile; echo -e -n $dependencies) > $TMP/dep$$ + if [ $? -eq 0 ]; then mv $TMP/dep$$ $depfile; fi + else + echo -e -n $dependencies > $depfile + fi +fi