commit 900b075e3aa03890f1538d97558920fc2bb9ae59 Author: crs Date: Sun May 13 11:40:29 2001 +0000 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. 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