From 6a223b7da5e82e56bbd88301fdfccf85c23e0644 Mon Sep 17 00:00:00 2001 From: crs Date: Tue, 13 Nov 2001 23:34:12 +0000 Subject: [PATCH] added preliminary support for getting the X selection. --- synergy/CServer.cpp | 5 + synergy/CXWindowsClipboard.cpp | 41 ++++ synergy/CXWindowsClipboard.h | 20 ++ synergy/CXWindowsPrimaryScreen.cpp | 26 ++- synergy/CXWindowsPrimaryScreen.h | 2 + synergy/CXWindowsScreen.cpp | 325 +++++++++++++++++++++++++++++ synergy/CXWindowsScreen.h | 34 +++ synergy/IClipboard.h | 44 ++++ synergy/IPrimaryScreen.h | 6 +- synergy/Makefile | 1 + 10 files changed, 499 insertions(+), 5 deletions(-) create mode 100644 synergy/CXWindowsClipboard.cpp create mode 100644 synergy/CXWindowsClipboard.h create mode 100644 synergy/IClipboard.h diff --git a/synergy/CServer.cpp b/synergy/CServer.cpp index 98f8dbe8..c60d515c 100644 --- a/synergy/CServer.cpp +++ b/synergy/CServer.cpp @@ -365,6 +365,7 @@ bool CServer::isLockedToScreen() const return false; } +#include "CXWindowsClipboard.h" // FIXME void CServer::switchScreen(CScreenInfo* dst, SInt32 x, SInt32 y) { @@ -381,6 +382,10 @@ void CServer::switchScreen(CScreenInfo* dst, // leave active screen if (m_active->m_protocol == NULL) { m_primary->leave(); + + // FIXME -- testing + CXWindowsClipboard clipboard; + m_primary->getClipboard(&clipboard); } else { m_active->m_protocol->sendLeave(); diff --git a/synergy/CXWindowsClipboard.cpp b/synergy/CXWindowsClipboard.cpp new file mode 100644 index 00000000..6d72dc8d --- /dev/null +++ b/synergy/CXWindowsClipboard.cpp @@ -0,0 +1,41 @@ +#include "CXWindowsClipboard.h" +#include "CString.h" +#include "CLog.h" + +// +// CXWindowsClipboard +// + +CXWindowsClipboard::CXWindowsClipboard() +{ +} + +CXWindowsClipboard::~CXWindowsClipboard() +{ +} + +void CXWindowsClipboard::open() +{ + log((CLOG_INFO "open clipboard")); +} + +void CXWindowsClipboard::close() +{ + log((CLOG_INFO "close clipboard")); +} + +void CXWindowsClipboard::add( + EFormat format, const CString& data) +{ + log((CLOG_INFO "add clipboard format: %d\n%s", format, data.c_str())); +} + +bool CXWindowsClipboard::has(EFormat /*format*/) const +{ + return false; +} + +CString CXWindowsClipboard::get(EFormat /*format*/) const +{ + return CString(); +} diff --git a/synergy/CXWindowsClipboard.h b/synergy/CXWindowsClipboard.h new file mode 100644 index 00000000..a67aeed8 --- /dev/null +++ b/synergy/CXWindowsClipboard.h @@ -0,0 +1,20 @@ +#ifndef CXWINDOWSCLIPBOARD_H +#define CXWINDOWSCLIPBOARD_H + +#include "IClipboard.h" + +class CXWindowsClipboard : public IClipboard { + public: + CXWindowsClipboard(); + virtual ~CXWindowsClipboard(); + + // IClipboard overrides + virtual void open(); + virtual void close(); + virtual void add(EFormat, const CString& data); + virtual bool has(EFormat) const; + virtual CString get(EFormat) const; +}; + +#endif + diff --git a/synergy/CXWindowsPrimaryScreen.cpp b/synergy/CXWindowsPrimaryScreen.cpp index 39595354..6e208869 100644 --- a/synergy/CXWindowsPrimaryScreen.cpp +++ b/synergy/CXWindowsPrimaryScreen.cpp @@ -150,6 +150,21 @@ void CXWindowsPrimaryScreen::warpCursorNoLock( } } +#include // FIXME +void CXWindowsPrimaryScreen::setClipboard( + const IClipboard* /*clipboard*/) +{ + // FIXME -- put this in superclass? + // FIXME -- don't use CurrentTime + CDisplayLock display(this); + XSetSelectionOwner(display, XA_PRIMARY, m_window, CurrentTime); + if (XGetSelectionOwner(display, XA_PRIMARY) == m_window) { + // we got the selection + log((CLOG_DEBUG "grabbed clipboard")); + } + // FIXME -- need to copy or adopt the clipboard to serve future requests +} + void CXWindowsPrimaryScreen::getSize( SInt32* width, SInt32* height) const { @@ -161,6 +176,14 @@ SInt32 CXWindowsPrimaryScreen::getJumpZoneSize() const return 1; } +void CXWindowsPrimaryScreen::getClipboard( + IClipboard* clipboard) const +{ + // FIXME -- put this in superclass? + // FIXME -- don't use CurrentTime + getDisplayClipboard(clipboard, m_window, CurrentTime); +} + void CXWindowsPrimaryScreen::onOpenDisplay() { assert(m_window == None); @@ -215,7 +238,8 @@ void CXWindowsPrimaryScreen::selectEvents( return; // select events of interest - XSelectInput(display, w, PointerMotionMask | SubstructureNotifyMask); + XSelectInput(display, w, PointerMotionMask | SubstructureNotifyMask | + PropertyChangeMask); // recurse on child windows Window rw, pw, *cw; diff --git a/synergy/CXWindowsPrimaryScreen.h b/synergy/CXWindowsPrimaryScreen.h index fd8883d8..3e056154 100644 --- a/synergy/CXWindowsPrimaryScreen.h +++ b/synergy/CXWindowsPrimaryScreen.h @@ -17,8 +17,10 @@ class CXWindowsPrimaryScreen : public CXWindowsScreen, public IPrimaryScreen { virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute); virtual void leave(); virtual void warpCursor(SInt32 xAbsolute, SInt32 yAbsolute); + virtual void setClipboard(const IClipboard*); virtual void getSize(SInt32* width, SInt32* height) const; virtual SInt32 getJumpZoneSize() const; + virtual void getClipboard(IClipboard*) const; protected: // CXWindowsScreen overrides diff --git a/synergy/CXWindowsScreen.cpp b/synergy/CXWindowsScreen.cpp index 1ec8413f..3559de52 100644 --- a/synergy/CXWindowsScreen.cpp +++ b/synergy/CXWindowsScreen.cpp @@ -3,9 +3,12 @@ #include "CLock.h" #include "TMethodJob.h" #include "CLog.h" +#include "CString.h" #include #include #include +#include +#include // // CXWindowsScreen @@ -46,6 +49,13 @@ void CXWindowsScreen::openDisplay() // get the root window m_root = RootWindow(m_display, m_screen); + // get some atoms + m_atomTargets = XInternAtom(m_display, "TARGETS", False); + m_atomData = XInternAtom(m_display, "DESTINATION", False); + m_atomINCR = XInternAtom(m_display, "INCR", False); + m_atomText = XInternAtom(m_display, "TEXT", False); + m_atomCompoundText = XInternAtom(m_display, "COMPOUND_TEXT", False); + // let subclass prep display onOpenDisplay(); @@ -147,6 +157,321 @@ void CXWindowsScreen::getEvent(XEvent* xevent) const m_mutex.unlock(); } +void CXWindowsScreen::getDisplayClipboard( + IClipboard* clipboard, + Window requestor, Time timestamp) const +{ + assert(clipboard != NULL); + assert(requestor != None); + + // clear the clipboard object + clipboard->open(); + + // block others from using the display while we get the clipboard. + // in particular, this prevents the event thread from stealing the + // selection notify event we're expecting. + CLock lock(&m_mutex); + + // use PRIMARY selection as the "clipboard" + Atom selection = XA_PRIMARY; + + // ask the selection for all the formats it has. some owners return + // the TARGETS atom and some the ATOM atom when TARGETS is requested. + Atom format; + CString targets; + if (getDisplayClipboard(selection, m_atomTargets, + requestor, timestamp, &format, &targets) && + (format == m_atomTargets || format == XA_ATOM)) { + // get each target (that we can interpret). some owners return + // some targets multiple times in the list so don't try to get + // those multiple times. + const Atom* targetAtoms = reinterpret_cast(targets.data()); + const SInt32 numTargets = targets.size() / sizeof(Atom); + std::set clipboardFormats; + std::set targets; + log((CLOG_DEBUG "selection has %d targets", numTargets)); + for (SInt32 i = 0; i < numTargets; ++i) { + Atom format = targetAtoms[i]; + log((CLOG_DEBUG " source target %d", format)); + + // skip already handled targets + if (targets.count(format) > 0) { + log((CLOG_DEBUG " skipping handled target %d", format)); + continue; + } + + // mark this target as done + targets.insert(format); + + // determine the expected clipboard format + IClipboard::EFormat expectedFormat = getFormat(format); + + // if we can use the format and we haven't already retrieved + // it then get it + if (expectedFormat == IClipboard::kNum) { + log((CLOG_DEBUG " no format for target", format)); + continue; + } + if (clipboardFormats.count(expectedFormat) > 0) { + log((CLOG_DEBUG " skipping handled format %d", expectedFormat)); + continue; + } + + CString data; + if (!getDisplayClipboard(selection, format, + requestor, timestamp, &format, &data)) { + log((CLOG_DEBUG " no data for target", format)); + continue; + } + + // use the actual format, not the expected + IClipboard::EFormat actualFormat = getFormat(format); + if (actualFormat == IClipboard::kNum) { + log((CLOG_DEBUG " no format for target", format)); + continue; + } + if (clipboardFormats.count(actualFormat) > 0) { + log((CLOG_DEBUG " skipping handled format %d", actualFormat)); + continue; + } + + // add to clipboard and note we've done it + clipboard->add(actualFormat, data); + clipboardFormats.insert(actualFormat); + } + } + else { + // non-ICCCM conforming selection owner. try TEXT format. + // FIXME + log((CLOG_DEBUG "selection doesn't support TARGETS, format is %d", format)); + } + + // done with clipboard + clipboard->close(); +} + +bool CXWindowsScreen::getDisplayClipboard( + Atom selection, Atom type, + Window requestor, Time timestamp, + Atom* outputType, CString* outputData) const +{ + assert(outputType != NULL); + assert(outputData != NULL); + + // delete data property + XDeleteProperty(m_display, requestor, m_atomData); + + // request data conversion + XConvertSelection(m_display, selection, type, + m_atomData, requestor, timestamp); + + // wait for the selection notify event. can't just mask out other + // events because X stupidly doesn't provide a mask for selection + // events, so we use a predicate to find our event. + XEvent xevent; + while (XCheckIfEvent(m_display, &xevent, + &CXWindowsScreen::findSelectionNotify, + (XPointer)&requestor) != True) { + // wait a bit + CThread::sleep(0.05); + } + assert(xevent.type == SelectionNotify); + assert(xevent.xselection.requestor == requestor); + + // make sure the transfer worked + Atom property = xevent.xselection.property; + if (property == None) { + // cannot convert + *outputType = type; + log((CLOG_DEBUG "selection conversion failed for %d", type)); + return false; + } + + // get the data and discard the property + SInt32 datumSize; + CString data; + bool okay = getData(requestor, property, outputType, &datumSize, &data); + XDeleteProperty(m_display, requestor, property); + + // fail if we couldn't get the data + if (!okay) { + log((CLOG_DEBUG "can't get data for selection format %d", type)); + return false; + } + + // handle INCR type specially. it means we'll be receiving the data + // piecemeal so we just loop until we've collected all the data. + if (*outputType == m_atomINCR) { + log((CLOG_DEBUG "selection data for format %d is incremental", type)); + // the data is a lower bound on the amount of data to be + // transferred. use it as a hint to size our buffer. + UInt32 size; + switch (datumSize) { + case 8: + size = *(reinterpret_cast(data.data())); + break; + + case 16: + size = *(reinterpret_cast(data.data())); + break; + + case 32: + size = *(reinterpret_cast(data.data())); + break; + + default: + assert(0 && "invalid datum size"); + } + + // empty the buffer and reserve the lower bound + data.erase(); + data.reserve(size); + + // look for property notify events with the following + PropertyNotifyInfo filter; + filter.m_window = requestor; + filter.m_property = property; + + // now enter the INCR loop + bool error = false; + *outputType = (Atom)0; + for (;;) { + // wait for more data + while (XCheckIfEvent(m_display, &xevent, + &CXWindowsScreen::findPropertyNotify, + (XPointer)&filter) != True) { + // wait a bit + CThread::sleep(0.05); + } + assert(xevent.type == PropertyNotify); + assert(xevent.xproperty.window == requestor); + assert(xevent.xproperty.atom == property); + + // get the additional data then delete the property to + // ask the clipboard owner for the next chunk. + Atom newType; + CString newData; + okay = getData(requestor, property, &newType, NULL, &newData); + XDeleteProperty(m_display, requestor, property); + + // transfer has failed if we can't get the data + if (!okay) + error = true; + + // a zero length property means we got the last chunk + if (newData.size() == 0) + break; + + // if this is the first chunk then save the type. otherwise + // note that the new type is the same as the first chunk's + // type. if they're not the the clipboard owner is busted + // but we have to continue the transfer because there's no + // way to cancel it. + if (*outputType == (Atom)0) + *outputType = newType; + else if (*outputType != newType) + error = true; + + // append the data + data += newData; + } + + // if there was an error we could say the transferred failed + // but we'll be liberal in what we accept. + if (error) { + log((CLOG_WARN "ICCCM violation by clipboard owner")); +// return false; + } + } + + *outputData = data; + return true; +} + +bool CXWindowsScreen::getData( + Window window, Atom property, + Atom* type, SInt32* datumSize, + CString* data) const +{ + assert(type != NULL); + assert(data != NULL); + + // clear out any existing data + data->erase(); + + // read the property + long offset = 0; + long length = 8192 / 4; + for (;;) { + // get more data + int actualDatumSize; + unsigned long numItems, bytesLeft; + unsigned char* rawData; + const int result = XGetWindowProperty(m_display, window, property, + offset, length, False, AnyPropertyType, + type, &actualDatumSize, + &numItems, &bytesLeft, + &rawData); + if (result != Success) { + // failed + return false; + } + + // save datum size + if (datumSize != NULL) + *datumSize = (SInt32)actualDatumSize; + const SInt32 bytesPerDatum = (SInt32)actualDatumSize / 8; + + // advance read pointer. since we can only read at offsets that + // are multiples of 4 byte we take care to write multiples of 4 + // bytes to data, except when we've retrieved the last chunk. + SInt32 quadCount = (numItems * bytesPerDatum) / 4; + offset += quadCount; + + // append data + if (bytesLeft == 0) + data->append((char*)rawData, bytesPerDatum * numItems); + else + data->append((char*)rawData, 4 * quadCount); + + // done with returned data + XFree(rawData); + + // done if no data is left + if (bytesLeft == 0) + return true; + } +} + +IClipboard::EFormat CXWindowsScreen::getFormat(Atom src) const +{ + // FIXME -- handle more formats (especially mime-type-like formats + // and various character encodings like unicode). + if (src == XA_STRING || + src == m_atomText || + src == m_atomCompoundText) + return IClipboard::kText; + return IClipboard::kNum; +} + +Bool CXWindowsScreen::findSelectionNotify( + Display*, XEvent* xevent, XPointer arg) +{ + Window requestor = *((Window*)arg); + return (xevent->type == SelectionNotify && + xevent->xselection.requestor == requestor) ? True : False; +} + +Bool CXWindowsScreen::findPropertyNotify( + Display*, XEvent* xevent, XPointer arg) +{ + PropertyNotifyInfo* filter = (PropertyNotifyInfo*)arg; + return (xevent->type == PropertyNotify && + xevent->xproperty.window == filter->m_window && + xevent->xproperty.atom == filter->m_property && + xevent->xproperty.state == PropertyNewValue) ? True : False; +} + // // CXWindowsScreen::CDisplayLock diff --git a/synergy/CXWindowsScreen.h b/synergy/CXWindowsScreen.h index 6ad39859..66cac76e 100644 --- a/synergy/CXWindowsScreen.h +++ b/synergy/CXWindowsScreen.h @@ -2,9 +2,11 @@ #define CXWINDOWSSCREEN_H #include "CMutex.h" +#include "IClipboard.h" #include "BasicTypes.h" #include +class CString; class CThread; class CXWindowsScreen { @@ -50,6 +52,12 @@ class CXWindowsScreen { // wait for and get the next X event. cancellable. void getEvent(XEvent*) const; + // copy the clipboard contents to clipboard. requestor must be a + // valid window; it will be used to receive the transfer. timestamp + // should be the timestamp of the provoking event and not CurrentTime. + void getDisplayClipboard(IClipboard* clipboard, + Window requestor, Time timestamp) const; + // called by openDisplay() to allow subclasses to prepare the display virtual void onOpenDisplay() = 0; @@ -59,6 +67,25 @@ class CXWindowsScreen { // override to process X events virtual void eventThread(void*) = 0; + private: + struct PropertyNotifyInfo { + public: + Window m_window; + Atom m_property; + }; + + bool getDisplayClipboard(Atom selection, Atom type, + Window requestor, Time timestamp, + Atom* outputType, CString* data) const; + bool getData(Window, Atom property, + Atom* type, SInt32* datumSize, + CString* data) const; + IClipboard::EFormat getFormat(Atom) const; + static Bool findSelectionNotify(Display*, + XEvent* xevent, XPointer arg); + static Bool findPropertyNotify(Display*, + XEvent* xevent, XPointer arg); + private: CThread* m_eventThread; Display* m_display; @@ -66,6 +93,13 @@ class CXWindowsScreen { Window m_root; SInt32 m_w, m_h; + // atoms we'll need + Atom m_atomTargets; + Atom m_atomData; + Atom m_atomINCR; + Atom m_atomText; + Atom m_atomCompoundText; + // X is not thread safe CMutex m_mutex; }; diff --git a/synergy/IClipboard.h b/synergy/IClipboard.h new file mode 100644 index 00000000..e42b2706 --- /dev/null +++ b/synergy/IClipboard.h @@ -0,0 +1,44 @@ +#ifndef ICLIPBOARD_H +#define ICLIPBOARD_H + +#include "IInterface.h" +#include "BasicTypes.h" + +class CString; + +class IClipboard : public IInterface { + public: + enum EFormat { kText, kNum }; + + // manipulators + + // grab ownership of and clear the clipboard of all data. + // only add() may be called between an open() and its + // corresponding close(). + virtual void open() = 0; + + // close the clipboard. close() must match a preceding open(). + // this signals that the clipboard has been filled with all the + // necessary data. it does not mean the clipboard ownership + // should be released. + virtual void close() = 0; + + // add data in the given format to the clipboard. data is + // passed as a string but the contents are generally not + // interpreted. may only be called between an open() and + // a close(). + virtual void add(EFormat, const CString& data) = 0; + + // accessors + + // returns true iff the clipboard contains data in the given + // format. + virtual bool has(EFormat) const = 0; + + // returns data in the given format. rturns the empty string + // if there is no data in that format. + virtual CString get(EFormat) const = 0; +}; + +#endif + diff --git a/synergy/IPrimaryScreen.h b/synergy/IPrimaryScreen.h index 86f98498..284728b4 100644 --- a/synergy/IPrimaryScreen.h +++ b/synergy/IPrimaryScreen.h @@ -5,7 +5,7 @@ #include "BasicTypes.h" class CServer; -//class IClipboard; +class IClipboard; class IPrimaryScreen : public IInterface { public: @@ -37,11 +37,11 @@ class IPrimaryScreen : public IInterface { // warp the cursor to the given position virtual void warpCursor(SInt32 xAbsolute, SInt32 yAbsolute) = 0; -/* // set the screen's clipboard contents. this is usually called // soon after an enter(). virtual void setClipboard(const IClipboard*) = 0; +/* // show or hide the screen saver virtual void onScreenSaver(bool show) = 0; @@ -64,10 +64,8 @@ class IPrimaryScreen : public IInterface { // get the size of jump zone virtual SInt32 getJumpZoneSize() const = 0; -/* // get the screen's clipboard contents virtual void getClipboard(IClipboard*) const = 0; -*/ }; #endif diff --git a/synergy/Makefile b/synergy/Makefile index 689c29c0..15d806f4 100644 --- a/synergy/Makefile +++ b/synergy/Makefile @@ -23,6 +23,7 @@ CXXFILES = \ CXWindowsScreen.cpp \ CXWindowsPrimaryScreen.cpp \ CXWindowsSecondaryScreen.cpp \ + CXWindowsClipboard.cpp \ XSynergy.cpp \ $(NULL)