diff --git a/client/CXWindowsSecondaryScreen.cpp b/client/CXWindowsSecondaryScreen.cpp index 1a58a7bd..ec4f5d21 100644 --- a/client/CXWindowsSecondaryScreen.cpp +++ b/client/CXWindowsSecondaryScreen.cpp @@ -1,4 +1,6 @@ #include "CXWindowsSecondaryScreen.h" +#include "CXWindowsClipboard.h" +#include "CXWindowsUtil.h" #include "CClient.h" #include "CThread.h" #include "CLog.h" @@ -57,71 +59,6 @@ void CXWindowsSecondaryScreen::run() XUnmapWindow(display, m_window); break; } - - case SelectionClear: - // we just lost the selection. that means someone else - // grabbed the selection so this screen is now the - // selection owner. report that to the server. - if (lostClipboard(xevent.xselectionclear.selection, - xevent.xselectionclear.time)) { - m_client->onClipboardChanged(getClipboardID( - xevent.xselectionclear.selection)); - } - break; - - case SelectionNotify: - // notification of selection transferred. we shouldn't - // get this here because we handle them in the selection - // retrieval methods. we'll just delete the property - // with the data (satisfying the usual ICCCM protocol). - if (xevent.xselection.property != None) { - CDisplayLock display(this); - XDeleteProperty(display, m_window, xevent.xselection.property); - } - break; - - case SelectionRequest: - // somebody is asking for clipboard data - if (xevent.xselectionrequest.owner == m_window) { - addClipboardRequest(m_window, - xevent.xselectionrequest.requestor, - xevent.xselectionrequest.selection, - xevent.xselectionrequest.target, - xevent.xselectionrequest.property, - xevent.xselectionrequest.time); - } - else { - // unknown window. return failure. - CDisplayLock display(this); - XEvent event; - event.xselection.type = SelectionNotify; - event.xselection.display = display; - event.xselection.requestor = xevent.xselectionrequest.requestor; - event.xselection.selection = xevent.xselectionrequest.selection; - event.xselection.target = xevent.xselectionrequest.target; - event.xselection.property = None; - event.xselection.time = xevent.xselectionrequest.time; - XSendEvent(display, xevent.xselectionrequest.requestor, - False, 0, &event); - } - break; - - case PropertyNotify: - // clipboard transfers involve property changes so forward - // the event to the superclass. we only care about the - // deletion of properties. - if (xevent.xproperty.state == PropertyDelete) { - processClipboardRequest(xevent.xproperty.window, - xevent.xproperty.atom, - xevent.xproperty.time); - } - break; - - case DestroyNotify: - // looks like one of the windows that requested a clipboard - // transfer has gone bye-bye. - destroyClipboardRequest(xevent.xdestroywindow.window); - break; } } } @@ -315,12 +252,12 @@ void CXWindowsSecondaryScreen::mouseWheel(SInt32 delta) void CXWindowsSecondaryScreen::setClipboard( ClipboardID id, const IClipboard* clipboard) { - setDisplayClipboard(id, clipboard, m_window, getCurrentTime(m_window)); + setDisplayClipboard(id, clipboard); } void CXWindowsSecondaryScreen::grabClipboard(ClipboardID id) { - setDisplayClipboard(id, NULL, m_window, getCurrentTime(m_window)); + setDisplayClipboard(id, NULL); } void CXWindowsSecondaryScreen::getMousePos( @@ -350,7 +287,7 @@ SInt32 CXWindowsSecondaryScreen::getJumpZoneSize() const void CXWindowsSecondaryScreen::getClipboard( ClipboardID id, IClipboard* clipboard) const { - getDisplayClipboard(id, clipboard, m_window, getCurrentTime(m_window)); + getDisplayClipboard(id, clipboard); } void CXWindowsSecondaryScreen::onOpenDisplay() @@ -381,6 +318,13 @@ void CXWindowsSecondaryScreen::onOpenDisplay() leaveNoLock(display); } +CXWindowsClipboard* CXWindowsSecondaryScreen::createClipboard( + ClipboardID id) +{ + CDisplayLock display(this); + return new CXWindowsClipboard(display, m_window, id); +} + void CXWindowsSecondaryScreen::onCloseDisplay() { assert(m_window != None); @@ -394,6 +338,13 @@ void CXWindowsSecondaryScreen::onCloseDisplay() m_window = None; } +void CXWindowsSecondaryScreen::onLostClipboard( + ClipboardID id) +{ + // tell client that the clipboard was grabbed locally + m_client->onClipboardChanged(id); +} + long CXWindowsSecondaryScreen::getEventMask(Window w) const { if (w == m_window) diff --git a/client/CXWindowsSecondaryScreen.h b/client/CXWindowsSecondaryScreen.h index 44f63182..52bbde1b 100644 --- a/client/CXWindowsSecondaryScreen.h +++ b/client/CXWindowsSecondaryScreen.h @@ -3,6 +3,7 @@ #include "CXWindowsScreen.h" #include "ISecondaryScreen.h" +#include #include class CXWindowsSecondaryScreen : public CXWindowsScreen, public ISecondaryScreen { @@ -35,7 +36,10 @@ public: protected: // CXWindowsScreen overrides virtual void onOpenDisplay(); + virtual CXWindowsClipboard* + createClipboard(ClipboardID); virtual void onCloseDisplay(); + virtual void onLostClipboard(ClipboardID); virtual long getEventMask(Window) const; private: diff --git a/server/CXWindowsPrimaryScreen.cpp b/server/CXWindowsPrimaryScreen.cpp index db112338..149b1835 100644 --- a/server/CXWindowsPrimaryScreen.cpp +++ b/server/CXWindowsPrimaryScreen.cpp @@ -1,4 +1,6 @@ #include "CXWindowsPrimaryScreen.h" +#include "CXWindowsClipboard.h" +#include "CXWindowsUtil.h" #include "CServer.h" #include "CThread.h" #include "CLog.h" @@ -171,71 +173,6 @@ void CXWindowsPrimaryScreen::run() } break; } - - case SelectionClear: - // we just lost the selection. that means someone else - // grabbed the selection so this screen is now the - // selection owner. report that to the server. - if (lostClipboard(xevent.xselectionclear.selection, - xevent.xselectionclear.time)) { - m_server->grabClipboard(getClipboardID( - xevent.xselectionclear.selection)); - } - break; - - case SelectionNotify: - // notification of selection transferred. we shouldn't - // get this here because we handle them in the selection - // retrieval methods. we'll just delete the property - // with the data (satisfying the usual ICCCM protocol). - if (xevent.xselection.property != None) { - CDisplayLock display(this); - XDeleteProperty(display, m_window, xevent.xselection.property); - } - break; - - case SelectionRequest: - // somebody is asking for clipboard data - if (xevent.xselectionrequest.owner == m_window) { - addClipboardRequest(m_window, - xevent.xselectionrequest.requestor, - xevent.xselectionrequest.selection, - xevent.xselectionrequest.target, - xevent.xselectionrequest.property, - xevent.xselectionrequest.time); - } - else { - // unknown window. return failure. - CDisplayLock display(this); - XEvent event; - event.xselection.type = SelectionNotify; - event.xselection.display = display; - event.xselection.requestor = xevent.xselectionrequest.requestor; - event.xselection.selection = xevent.xselectionrequest.selection; - event.xselection.target = xevent.xselectionrequest.target; - event.xselection.property = None; - event.xselection.time = xevent.xselectionrequest.time; - XSendEvent(display, xevent.xselectionrequest.requestor, - False, 0, &event); - } - break; - - case PropertyNotify: - // clipboard transfers involve property changes so forward - // the event to the superclass. we only care about the - // deletion of properties. - if (xevent.xproperty.state == PropertyDelete) { - processClipboardRequest(xevent.xproperty.window, - xevent.xproperty.atom, - xevent.xproperty.time); - } - break; - - case DestroyNotify: - // looks like one of the windows that requested a clipboard - // transfer has gone bye-bye. - destroyClipboardRequest(xevent.xdestroywindow.window); - break; } } } @@ -394,12 +331,12 @@ void CXWindowsPrimaryScreen::warpCursorNoLock( void CXWindowsPrimaryScreen::setClipboard( ClipboardID id, const IClipboard* clipboard) { - setDisplayClipboard(id, clipboard, m_window, getCurrentTime(m_window)); + setDisplayClipboard(id, clipboard); } void CXWindowsPrimaryScreen::grabClipboard(ClipboardID id) { - setDisplayClipboard(id, NULL, m_window, getCurrentTime(m_window)); + setDisplayClipboard(id, NULL); } void CXWindowsPrimaryScreen::getSize( @@ -416,7 +353,7 @@ SInt32 CXWindowsPrimaryScreen::getJumpZoneSize() const void CXWindowsPrimaryScreen::getClipboard( ClipboardID id, IClipboard* clipboard) const { - getDisplayClipboard(id, clipboard, m_window, getCurrentTime(m_window)); + getDisplayClipboard(id, clipboard); } KeyModifierMask CXWindowsPrimaryScreen::getToggleMask() const @@ -508,6 +445,13 @@ void CXWindowsPrimaryScreen::onOpenDisplay() selectEvents(display, getRoot()); } +CXWindowsClipboard* CXWindowsPrimaryScreen::createClipboard( + ClipboardID id) +{ + CDisplayLock display(this); + return new CXWindowsClipboard(display, m_window, id); +} + void CXWindowsPrimaryScreen::onCloseDisplay() { assert(m_window != None); @@ -518,6 +462,13 @@ void CXWindowsPrimaryScreen::onCloseDisplay() m_window = None; } +void CXWindowsPrimaryScreen::onLostClipboard( + ClipboardID id) +{ + // tell server that the clipboard was grabbed locally + m_server->grabClipboard(id); +} + long CXWindowsPrimaryScreen::getEventMask(Window w) const { if (w == m_window) diff --git a/server/CXWindowsPrimaryScreen.h b/server/CXWindowsPrimaryScreen.h index 03e5bc98..b3fc98b6 100644 --- a/server/CXWindowsPrimaryScreen.h +++ b/server/CXWindowsPrimaryScreen.h @@ -30,7 +30,10 @@ public: protected: // CXWindowsScreen overrides virtual void onOpenDisplay(); + virtual CXWindowsClipboard* + createClipboard(ClipboardID); virtual void onCloseDisplay(); + virtual void onLostClipboard(ClipboardID); virtual long getEventMask(Window) const; private: diff --git a/synergy/CClipboard.cpp b/synergy/CClipboard.cpp index bf5d94ed..f60c5a76 100644 --- a/synergy/CClipboard.cpp +++ b/synergy/CClipboard.cpp @@ -5,9 +5,10 @@ // CClipboard // -CClipboard::CClipboard() +CClipboard::CClipboard() : m_open(false), m_owner(false) { open(0); + empty(); close(); } @@ -16,8 +17,10 @@ CClipboard::~CClipboard() // do nothing } -bool CClipboard::open(Time time) +bool CClipboard::empty() { + assert(m_open); + // clear all data for (SInt32 index = 0; index < kNumFormats; ++index) { m_data[index] = ""; @@ -25,60 +28,90 @@ bool CClipboard::open(Time time) } // save time + m_timeOwned = m_time; + + // we're the owner now + m_owner = true; + + return true; +} + +void CClipboard::add(EFormat format, const CString& data) +{ + assert(m_open); + assert(m_owner); + + m_data[format] = data; + m_added[format] = true; +} + +bool CClipboard::open(Time time) const +{ + assert(!m_open); + + m_open = true; m_time = time; return true; } -void CClipboard::close() +void CClipboard::close() const { - // do nothing -} + assert(m_open); -void CClipboard::add(EFormat format, const CString& data) -{ - m_data[format] = data; - m_added[format] = true; + m_open = false; } CClipboard::Time CClipboard::getTime() const { - return m_time; + return m_timeOwned; } bool CClipboard::has(EFormat format) const { + assert(m_open); return m_added[format]; } CString CClipboard::get(EFormat format) const { + assert(m_open); return m_data[format]; } -void CClipboard::copy(IClipboard* dst, const IClipboard* src) +bool CClipboard::copy(IClipboard* dst, const IClipboard* src) { assert(dst != NULL); assert(src != NULL); - copy(dst, src, src->getTime()); + return copy(dst, src, src->getTime()); } -void CClipboard::copy(IClipboard* dst, +bool CClipboard::copy(IClipboard* dst, const IClipboard* src, Time time) { assert(dst != NULL); assert(src != NULL); - if (dst->open(time)) { - for (SInt32 format = 0; format != IClipboard::kNumFormats; ++format) { - IClipboard::EFormat eFormat = (IClipboard::EFormat)format; - if (src->has(eFormat)) { - dst->add(eFormat, src->get(eFormat)); + bool success = false; + if (src->open(time)) { + if (dst->open(time)) { + if (dst->empty()) { + for (SInt32 format = 0; + format != IClipboard::kNumFormats; ++format) { + IClipboard::EFormat eFormat = (IClipboard::EFormat)format; + if (src->has(eFormat)) { + dst->add(eFormat, src->get(eFormat)); + } + } + success = true; } + dst->close(); } - dst->close(); + src->close(); } + + return success; } void CClipboard::unmarshall(const CString& data, Time time) @@ -87,6 +120,7 @@ void CClipboard::unmarshall(const CString& data, Time time) // clear existing data open(time); + empty(); // read the number of formats const UInt32 numFormats = readUInt32(index); diff --git a/synergy/CClipboard.h b/synergy/CClipboard.h index 734631f6..80fdec04 100644 --- a/synergy/CClipboard.h +++ b/synergy/CClipboard.h @@ -24,9 +24,10 @@ public: CString marshall() const; // IClipboard overrides - virtual bool open(Time); - virtual void close(); + virtual bool empty(); virtual void add(EFormat, const CString& data); + virtual bool open(Time) const; + virtual void close() const; virtual Time getTime() const; virtual bool has(EFormat) const; virtual CString get(EFormat) const; @@ -37,15 +38,19 @@ public: // clipboards can be of any concrete clipboard type (and // they don't have to be the same type). this also sets // the timestamp to time, if provided, or the time in src. - static void copy(IClipboard* dst, const IClipboard* src); - static void copy(IClipboard* dst, const IClipboard* src, Time); + // returns true iff the copy succeeded. + static bool copy(IClipboard* dst, const IClipboard* src); + static bool copy(IClipboard* dst, const IClipboard* src, Time); private: UInt32 readUInt32(const char*) const; void writeUInt32(CString*, UInt32) const; private: - Time m_time; + mutable bool m_open; + mutable Time m_time; + bool m_owner; + Time m_timeOwned; bool m_added[kNumFormats]; CString m_data[kNumFormats]; }; diff --git a/synergy/CXWindowsClipboard.cpp b/synergy/CXWindowsClipboard.cpp index a8b3823a..30ade009 100644 --- a/synergy/CXWindowsClipboard.cpp +++ b/synergy/CXWindowsClipboard.cpp @@ -1,42 +1,1297 @@ #include "CXWindowsClipboard.h" -#include "CString.h" +#include "CXWindowsUtil.h" #include "CLog.h" +#include "CStopwatch.h" +#include "CThread.h" +#include +#include // // CXWindowsClipboard // -CXWindowsClipboard::CXWindowsClipboard() +CXWindowsClipboard::CXWindowsClipboard(Display* display, + Window window, ClipboardID id) : + m_display(display), + m_window(window), + m_id(id), + m_open(false), + m_time(0), + m_owner(false), + m_timeOwned(0), + m_timeLost(0) { + // get some atoms + m_atomTargets = XInternAtom(m_display, "TARGETS", False); + m_atomMultiple = XInternAtom(m_display, "MULTIPLE", False); + m_atomTimestamp = XInternAtom(m_display, "TIMESTAMP", False); + m_atomAtom = XInternAtom(m_display, "ATOM", False); + m_atomAtomPair = XInternAtom(m_display, "ATOM_PAIR", False); + m_atomInteger = XInternAtom(m_display, "INTEGER", False); + m_atomData = XInternAtom(m_display, "CLIP_TEMPORARY", False); + m_atomINCR = XInternAtom(m_display, "INCR", False); + m_atomString = XInternAtom(m_display, "STRING", False); + m_atomText = XInternAtom(m_display, "TEXT", False); + m_atomCompoundText = XInternAtom(m_display, "COMPOUND_TEXT", False); + m_atomMotifClipLock = XInternAtom(m_display, "_MOTIF_CLIP_LOCK", False); + m_atomMotifClipHeader = XInternAtom(m_display, "_MOTIF_CLIP_HEADER", False); + m_atomMotifClipAccess = XInternAtom(m_display, + "_MOTIF_CLIP_LOCK_ACCESS_VALID", False); + m_atomGDKSelection = XInternAtom(m_display, "GDK_SELECTION", False); + + // set selection atom based on clipboard id + switch (id) { + case kClipboardClipboard: + m_selection = XInternAtom(m_display, "CLIPBOARD", False); + break; + + case kClipboardSelection: + default: + m_selection = XA_PRIMARY; + break; + } + + // we have no data + clearCache(); } CXWindowsClipboard::~CXWindowsClipboard() { + clearReplies(); } -bool CXWindowsClipboard::open() +void CXWindowsClipboard::lost(Time time) { - log((CLOG_INFO "open clipboard")); + log((CLOG_DEBUG "lost clipboard %d ownership at %d", m_id, time)); + if (m_owner) { + m_owner = false; + m_timeLost = time; + clearCache(); + } +} + +void CXWindowsClipboard::addRequest( + Window owner, + Window requestor, Atom target, + ::Time time, Atom property) +{ + // must be for our window and we must have owned the selection + // at the given time. + bool success = false; + if (owner == m_window) { + log((CLOG_DEBUG "request for clipboard %d, target %d by 0x%08x (property=%d)", m_selection, target, requestor, property)); + if (wasOwnedAtTime(time)) { + if (target == m_atomMultiple) { + // add a multiple request. property may not be None + // according to ICCCM. + if (property != None) { + success = insertMultipleReply(requestor, time, property); + } + } + else { + addSimpleRequest(requestor, target, time, property); + + // addSimpleRequest() will have already handled failure + success = true; + } + } + else { + log((CLOG_DEBUG "failed, not owned at time %d", time)); + } + } + + if (!success) { + // send failure + log((CLOG_DEBUG "failed")); + insertReply(new CReply(requestor, target, time)); + } + + // send notifications that are pending + pushReplies(); +} + +bool CXWindowsClipboard::addSimpleRequest( + Window requestor, Atom target, + ::Time time, Atom property) +{ + // obsolete requestors may supply a None property. in + // that case we use the target as the property to store + // the conversion. + if (property == None) { + property = target; + } + + // handle targets + CString data; + Atom type = None; + int format = 0; + if (target == m_atomTargets) { + type = getTargetsData(data, &format); + } + else if (target == m_atomTimestamp) { + type = getTimestampData(data, &format); + } + else if (target == m_atomString || + target == m_atomText) { + type = getStringData(data, &format); + } + + if (type != None) { + // success + log((CLOG_DEBUG "success")); + insertReply(new CReply(requestor, target, time, + property, data, type, format)); + return true; + } + else { + // failure + log((CLOG_DEBUG "failed")); + insertReply(new CReply(requestor, target, time)); + return false; + } +} + +bool CXWindowsClipboard::processRequest( + Window requestor, + ::Time /*time*/, Atom property) +{ + CReplyMap::iterator index = m_replies.find(requestor); + if (index == m_replies.end()) { + // unknown requestor window + return false; + } + log((CLOG_DEBUG1 "received property %d delete from 0x08%x", property, requestor)); + + // find the property in the known requests. it should be the + // first property but we'll check 'em all if we have to. + CReplyList& replies = index->second; + for (CReplyList::iterator index2 = replies.begin(); + index2 != replies.end(); ++index2) { + CReply* reply = *index2; + if (reply->m_replied && reply->m_property == property) { + // if reply is complete then remove it and start the + // next one. + pushReplies(index, replies, index2); + return true; + } + } + + return false; +} + +bool CXWindowsClipboard::destroyRequest( + Window requestor) +{ + CReplyMap::iterator index = m_replies.find(requestor); + if (index == m_replies.end()) { + // unknown requestor window + return false; + } + + // destroy all replies for this window + clearReplies(index->second); + + // note -- we don't stop watching the window for events because + // we're called in response to the window being destroyed. + return true; } -void CXWindowsClipboard::close() +Window CXWindowsClipboard::getWindow() const { - log((CLOG_INFO "close clipboard")); + return m_window; +} + +Atom CXWindowsClipboard::getSelection() const +{ + return m_selection; +} + +bool CXWindowsClipboard::empty() +{ + assert(m_open); + + log((CLOG_DEBUG "empty clipboard %d", m_id)); + + // assert ownership of clipboard + XSetSelectionOwner(m_display, m_selection, m_window, m_time); + if (XGetSelectionOwner(m_display, m_selection) != m_window) { + log((CLOG_DEBUG "failed to grab clipboard %d", m_id)); + return false; + } + + // clear all data. since we own the data now, the cache is up + // to date. + clearCache(); + m_cached = true; + + // FIXME -- actually delete motif clipboard items? + // FIXME -- do anything to motif clipboard properties? + + // save time + m_timeOwned = m_time; + m_timeLost = 0; + + // we're the owner now + m_owner = true; + log((CLOG_DEBUG "grabbed clipboard %d", m_id)); + + return true; } void CXWindowsClipboard::add( EFormat format, const CString& data) { - log((CLOG_INFO "add clipboard format: %d\n%s", format, data.c_str())); + assert(m_open); + assert(m_owner); + + log((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format)); + + m_data[format] = data; + m_added[format] = true; + + // FIXME -- set motif clipboard item? } -bool CXWindowsClipboard::has(EFormat /*format*/) const +bool CXWindowsClipboard::open(Time time) const { + assert(!m_open); + + log((CLOG_DEBUG "open clipboard %d", m_id)); + + // assume not motif + m_motif = false; + + // lock clipboard + if (m_id == kClipboardClipboard) { + if (!motifLockClipboard()) { + return false; + } + + // check if motif owns the selection. unlock motif clipboard + // if it does not. + m_motif = motifOwnsClipboard(); + log((CLOG_DEBUG1 "motif does %sown clipboard", m_motif ? "" : "not ")); + if (!m_motif) { + motifUnlockClipboard(); + } + } + + // now open + m_open = true; + m_time = time; + + // get the time the clipboard ownership was taken by the current + // owner. + if (m_motif) { + m_timeOwned = motifGetTime(); + } + else { + m_timeOwned = icccmGetTime(); + } + + // if we can't get the time then use the time passed to us + if (m_timeOwned == 0) { + m_timeOwned = m_time; + } + + // if the cache is dirty then flush it + if (m_timeOwned != m_cacheTime) { + clearCache(); + } + + return true; +} + +void CXWindowsClipboard::close() const +{ + assert(m_open); + + log((CLOG_DEBUG "close clipboard %d", m_id)); + + // unlock clipboard + if (m_motif) { + motifUnlockClipboard(); + } + + m_motif = false; + m_open = false; +} + +IClipboard::Time CXWindowsClipboard::getTime() const +{ + return m_timeOwned; +} + +bool CXWindowsClipboard::has(EFormat format) const +{ + assert(m_open); + + fillCache(); + return m_added[format]; +} + +CString CXWindowsClipboard::get(EFormat format) const +{ + assert(m_open); + + fillCache(); + return m_data[format]; +} + +IClipboard::EFormat CXWindowsClipboard::getFormat(Atom src) const +{ + // FIXME -- handle more formats (especially mime-type-like formats + // and various character encodings like unicode). + if (src == m_atomString || + src == m_atomText /*|| + src == m_atomCompoundText*/) + return IClipboard::kText; + return IClipboard::kNumFormats; +} + +void CXWindowsClipboard::clearCache() const +{ + const_cast(this)->doClearCache(); +} + +void CXWindowsClipboard::doClearCache() +{ + m_cached = false; + for (SInt32 index = 0; index < kNumFormats; ++index) { + m_data[index] = ""; + m_added[index] = false; + } +} + +void CXWindowsClipboard::fillCache() const +{ + // get the selection data if not already cached + if (!m_cached) { + const_cast(this)->doFillCache(); + } +} + +void CXWindowsClipboard::doFillCache() +{ + if (m_motif) { + motifFillCache(); + } + else { + icccmFillCache(); + } + m_cached = true; + m_cacheTime = m_timeOwned; +} + +void CXWindowsClipboard::icccmFillCache() +{ + log((CLOG_DEBUG "ICCCM fill clipboard %d", m_id)); + + // see if we can get the list of available formats from the selection. + // if not then use a default list of formats. + const Atom atomTargets = m_atomTargets; + Atom target; + CString data; + if (!icccmGetSelection(atomTargets, &target, &data)) { + log((CLOG_DEBUG1 "selection doesn't support TARGETS")); + data = ""; + + target = XA_STRING; + data.append(reinterpret_cast(&target), sizeof(target)); + } + + // try getting each format + const Atom* targets = reinterpret_cast(data.data()); + const UInt32 numTargets = data.size() / sizeof(Atom); + for (UInt32 i = 0; i < numTargets; ++i) { + // determine the expected clipboard format + Atom target = targets[i]; + IClipboard::EFormat expectedFormat = getFormat(target); + if (expectedFormat == IClipboard::kNumFormats) { + log((CLOG_DEBUG1 " no format for target %d", target)); + continue; + } + log((CLOG_DEBUG1 " source target %d -> %d", target, expectedFormat)); + + // skip already handled targets + if (m_added[expectedFormat]) { + log((CLOG_DEBUG1 " skipping handled format %d", expectedFormat)); + continue; + } + + Atom actualTarget; + CString targetData; + if (!icccmGetSelection(target, &actualTarget, &targetData)) { + log((CLOG_DEBUG1 " no data for target", target)); + continue; + } + logc(actualTarget != target, (CLOG_DEBUG1 " actual target is %d", actualTarget)); + + // use the actual format, not the expected + IClipboard::EFormat actualFormat = getFormat(actualTarget); + if (actualFormat == IClipboard::kNumFormats) { + log((CLOG_DEBUG1 " no format for target %d", actualTarget)); + continue; + } + if (m_added[actualFormat]) { + log((CLOG_DEBUG1 " skipping handled format %d", actualFormat)); + continue; + } + + // add to clipboard and note we've done it + m_data[actualFormat] = targetData; + m_added[actualFormat] = true; + log((CLOG_DEBUG " added format %d for target %d", actualFormat, target)); + } +} + +bool CXWindowsClipboard::icccmGetSelection( + Atom target, + Atom* actualTarget, + CString* data) const +{ + assert(actualTarget != NULL); + assert(data != NULL); + + // request data conversion + CICCCMGetClipboard getter(m_window, m_time, m_atomData); + if (!getter.readClipboard(m_display, m_selection, + target, actualTarget, data)) { + log((CLOG_DEBUG1 "can't get data for selection target %d", target)); + logc(getter.m_error, (CLOG_WARN "ICCCM violation by clipboard owner")); + return false; + } + else if (*actualTarget == None) { + log((CLOG_DEBUG1 "selection conversion failed for target %d", target)); + return false; + } + return true; +} + +IClipboard::Time CXWindowsClipboard::icccmGetTime() const +{ + Atom actualTarget; + CString data; + if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) && + actualTarget == m_atomTimestamp) { + Time time = *reinterpret_cast(data.data()); + log((CLOG_DEBUG1 "got ICCCM time %d", time)); + return time; + } + else { + // no timestamp + log((CLOG_DEBUG1 "can't get ICCCM time")); + return 0; + } +} + +bool CXWindowsClipboard::motifLockClipboard() const +{ + // fail if anybody owns the lock (even us, so this is non-recursive) + Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != None) { + return false; + } + + // try to grab the lock + // FIXME -- is this right? there's a race condition here -- + // A grabs successfully, B grabs successfully, A thinks it + // still has the grab until it gets a SelectionClear. + Time time = CXWindowsUtil::getCurrentTime(m_display, m_window); + XSetSelectionOwner(m_display, m_atomMotifClipLock, m_window, time); + lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != m_window) { + return false; + } + + log((CLOG_DEBUG1 "locked motif clipboard")); + return true; +} + +void CXWindowsClipboard::motifUnlockClipboard() const +{ + log((CLOG_DEBUG1 "unlocked motif clipboard")); + + // fail if we don't own the lock + Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != m_window) { + return; + } + + // release lock + Time time = CXWindowsUtil::getCurrentTime(m_display, m_window); + XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time); +} + +bool CXWindowsClipboard::motifOwnsClipboard() const +{ + // get the current selection owner + // FIXME -- this can't be right. even if the window is destroyed + // Motif will still have a valid clipboard. how can we tell if + // some other client owns CLIPBOARD? + Window owner = XGetSelectionOwner(m_display, m_selection); + if (owner == None) { + return false; + } + + // get the Motif clipboard header property from the root window + Atom target; + SInt32 format; + CString data; + Window root = RootWindow(m_display, DefaultScreen(m_display)); + if (!CXWindowsUtil::getWindowProperty(m_display, root, + m_atomMotifClipHeader, + &data, &target, &format, False)) { + return false; + } + if (target != m_atomMotifClipHeader) { + return false; + } + + // check the owner window against the current clipboard owner + const CMotifClipHeader* header = + reinterpret_cast(data.data()); + if (data.size() >= sizeof(CMotifClipHeader) && + header->m_id == kMotifClipHeader) { + if (header->m_selectionOwner == owner) { + return true; + } + } + return false; } -CString CXWindowsClipboard::get(EFormat /*format*/) const +void CXWindowsClipboard::motifFillCache() { - return CString(); + log((CLOG_DEBUG "Motif fill clipboard %d", m_id)); + + // get the Motif clipboard header property from the root window + Atom target; + SInt32 format; + CString data; + Window root = RootWindow(m_display, DefaultScreen(m_display)); + if (!CXWindowsUtil::getWindowProperty(m_display, root, + m_atomMotifClipHeader, + &data, &target, &format, False)) { + return; + } + if (target != m_atomMotifClipHeader) { + return; + } + + // check that the header is okay + const CMotifClipHeader* header = + reinterpret_cast(data.data()); + if (data.size() < sizeof(CMotifClipHeader) || + header->m_id != kMotifClipHeader || + header->m_numItems < 1) { + return; + } + + // get the Motif item property from the root window + char name[18 + 20]; + sprintf(name, "_MOTIF_CLIP_ITEM_%d", header->m_item); + Atom atomItem = XInternAtom(m_display, name, False); + data = ""; + if (!CXWindowsUtil::getWindowProperty(m_display, root, + atomItem, &data, + &target, &format, False)) { + return; + } + if (target != atomItem) { + return; + } + + // check that the item is okay + const CMotifClipItem* item = + reinterpret_cast(data.data()); + if (data.size() < sizeof(CMotifClipItem) || + item->m_id != kMotifClipItem || + item->m_numFormats < 1) { + return; + } + + // convert each available format + for (SInt32 i = 0; i < item->m_numFormats; ++i) { + // get Motif format property from the root window + sprintf(name, "_MOTIF_CLIP_ITEM_%d", item->m_formats[i]); + Atom atomFormat = XInternAtom(m_display, name, False); + CString data; + if (!CXWindowsUtil::getWindowProperty(m_display, root, + atomFormat, &data, + &target, &format, False)) { + continue; + } + if (target != atomFormat) { + continue; + } + + // check that the format is okay + const CMotifClipFormat* motifFormat = + reinterpret_cast(data.data()); + if (data.size() < sizeof(CMotifClipFormat) || + motifFormat->m_id != kMotifClipFormat || + motifFormat->m_length < 0 || + motifFormat->m_type == None) { + continue; + } + + // determine the expected clipboard format + Atom target = motifFormat->m_type; + IClipboard::EFormat expectedFormat = getFormat(target); + if (expectedFormat == IClipboard::kNumFormats) { + log((CLOG_DEBUG1 " no format for target %d", target)); + continue; + } + log((CLOG_DEBUG1 " source target %d -> %d", target, expectedFormat)); + + // skip already handled targets + if (m_added[expectedFormat]) { + log((CLOG_DEBUG1 " skipping handled format %d", expectedFormat)); + continue; + } + + // get the data (finally) + SInt32 length = motifFormat->m_length; + sprintf(name, "_MOTIF_CLIP_ITEM_%d", motifFormat->m_data); + Atom atomData = XInternAtom(m_display, name, False); + data = ""; + if (!CXWindowsUtil::getWindowProperty(m_display, root, + atomData, &data, + &target, &format, False)) { + continue; + } + if (target != atomData) { + continue; + } + + // truncate data to length specified in the format + data.erase(length); + + // add to clipboard and note we've done it + m_data[expectedFormat] = data; + m_added[expectedFormat] = true; + log((CLOG_DEBUG " added format %d for target %d", expectedFormat, motifFormat->m_type)); + } +} + +IClipboard::Time CXWindowsClipboard::motifGetTime() const +{ + // FIXME -- does Motif report this? + return 0; +} + +bool CXWindowsClipboard::insertMultipleReply( + Window requestor, ::Time time, Atom property) +{ + // get the requested targets + Atom target; + SInt32 format; + CString data; + if (!CXWindowsUtil::getWindowProperty(m_display, requestor, + property, &data, &target, &format, False)) { + // can't get the requested targets + return false; + } + + // fail if the requested targets isn't of the correct form + if (format != 32 || + target != m_atomAtomPair) { + return false; + } + + // data is a list of atom pairs: target, property + const Atom* targets = reinterpret_cast(data.data()); + const UInt32 numTargets = data.size() / sizeof(Atom); + + // add replies for each target + bool changed = false; + for (UInt32 i = 0; i < numTargets; i += 2) { + const Atom target = targets[i + 0]; + const Atom property = targets[i + 1]; + if (!addSimpleRequest(requestor, target, time, property)) { + // note that we can't perform the requested conversion + static const Atom none = None; + data.replace(i * sizeof(Atom), sizeof(Atom), + reinterpret_cast(&none), + sizeof(Atom)); + changed = true; + } + } + + // update the targets property if we changed it + if (changed) { + CXWindowsUtil::setWindowProperty(m_display, requestor, + property, data.data(), data.size(), + target, format); + } + + // add reply for MULTIPLE request + insertReply(new CReply(requestor, m_atomMultiple, + time, property, CString(), None, 32)); + + return true; +} + +void CXWindowsClipboard::insertReply(CReply* reply) +{ + assert(reply != NULL); + + // note -- we must respond to requests in order if requestor,target,time + // are the same, otherwise we can use whatever order we like with one + // exception: each reply in a MULTIPLE reply must be handled in order + // as well. those replies will almost certainly not share targets so + // we can't simply use requestor,target,time as map index. + // + // instead we'll use just the requestor. that's more restrictive than + // necessary but we're guaranteed to do things in the right order. + // note that we could also include the time in the map index and still + // ensure the right order. but since that'll just make it harder to + // find the right reply when handling property notify events we stick + // to just the requestor. + + const bool newWindow = (m_replies.count(reply->m_requestor) == 0); + m_replies[reply->m_requestor].push_back(reply); + + // adjust requestor's event mask if we haven't done so already. we + // want events in case the window is destroyed or any of its + // properties change. + if (newWindow) { + XWindowAttributes attr; + XGetWindowAttributes(m_display, reply->m_requestor, &attr); + XSelectInput(m_display, reply->m_requestor, attr.your_event_mask | + StructureNotifyMask | PropertyChangeMask); + m_eventMasks[reply->m_requestor] = attr.your_event_mask; + } +} + +void CXWindowsClipboard::pushReplies() +{ + // send the first reply for each window if that reply hasn't + // been sent yet. + for (CReplyMap::iterator index = m_replies.begin(); + index != m_replies.end(); ++index) { + assert(!index->second.empty()); + if (!index->second.front()->m_replied) { + pushReplies(index, index->second, index->second.begin()); + } + } +} + +void CXWindowsClipboard::pushReplies( + CReplyMap::iterator mapIndex, + CReplyList& replies, + CReplyList::iterator index) +{ + CReply* reply = *index; + while (sendReply(reply)) { + // reply is complete. discard it and send the next reply, + // if any. + index = replies.erase(index); + delete reply; + if (index == replies.end()) { + break; + } + reply = *index; + } + + // if there are no more replies in the list then remove the list + // and stop watching the requestor for events. + if (replies.empty()) { + Window requestor = mapIndex->first; + XSelectInput(m_display, requestor, m_eventMasks[requestor]); + m_replies.erase(mapIndex); + m_eventMasks.erase(requestor); + } +} + +bool CXWindowsClipboard::sendReply(CReply* reply) +{ + assert(reply != NULL); + + // bail out immediately if reply is done + if (reply->m_done) { + log((CLOG_DEBUG1 "clipboard: finished reply to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + return true; + } + + // start in failed state if property is None + bool failed = (reply->m_property == None); + if (!failed) { + log((CLOG_DEBUG1 "clipboard: setting property on 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + + // send using INCR if already sending incrementally or if reply + // is too large, otherwise just send it. + const UInt32 maxRequestSize = 4 * XMaxRequestSize(m_display); + const bool useINCR = (reply->m_data.size() > maxRequestSize); + + // send INCR reply if incremental and we haven't replied yet + if (useINCR && !reply->m_replied) { + UInt32 size = reply->m_data.size(); + if (!CXWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + &size, 4, m_atomINCR, 32)) { + failed = true; + } + } + + // send more INCR reply or entire non-incremental reply + else { + // how much more data should we send? + UInt32 size = reply->m_data.size() - reply->m_ptr; + if (size > maxRequestSize) + size = maxRequestSize; + + // send it + if (!CXWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + reply->m_data.data() + reply->m_ptr, + size, + reply->m_type, reply->m_format)) { + failed = true; + } + else { + reply->m_ptr += size; + + // we've finished the reply if we just sent the zero + // size incremental chunk or if we're not incremental. + reply->m_done = (size == 0 || !useINCR); + } + } + } + + // if we've failed then delete the property and say we're done. + // if we haven't replied yet then we can send a failure notify, + // otherwise we've failed in the middle of an incremental + // transfer; i don't know how to cancel that so i'll just send + // the final zero-length property. + // FIXME -- how do you gracefully cancel an incremental transfer? + if (failed) { + log((CLOG_DEBUG1 "clipboard: sending failure to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + reply->m_done = true; + if (reply->m_property != None) { + XDeleteProperty(m_display, reply->m_requestor, reply->m_property); + } + + if (!reply->m_replied) { + sendNotify(reply->m_requestor, m_selection, + reply->m_target, None, + reply->m_time); + + // don't wait for any reply (because we're not expecting one) + return true; + } + else { + static const char dummy = 0; + CXWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + &dummy, + 0, + reply->m_type, reply->m_format); + + // wait for delete notify + return false; + } + } + + // send notification if we haven't yet + if (!reply->m_replied) { + log((CLOG_DEBUG1 "clipboard: sending notify to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + reply->m_replied = true; + + // HACK -- work around apparent bug in lesstif, which doesn't + // wait around for the SelectionNotify then gets confused when + // it sees it the next time it requests the selection. if it + // looks like a lesstif requestor window then don't send the + // SelectionNotify. it looks like a lesstif requestor if: + // it has a _MOTIF_CLIP_LOCK_ACCESS_VALID property + // it does not have a GDK_SELECTION property + CString dummy; + if (!CXWindowsUtil::getWindowProperty(m_display, + reply->m_requestor, + m_atomMotifClipAccess, + &dummy, NULL, NULL, False) || + CXWindowsUtil::getWindowProperty(m_display, + reply->m_requestor, + m_atomGDKSelection, + &dummy, NULL, NULL, False)) { + sendNotify(reply->m_requestor, m_selection, + reply->m_target, reply->m_property, + reply->m_time); + } + } + + // wait for delete notify + return false; +} + +void CXWindowsClipboard::clearReplies() +{ + for (CReplyMap::iterator index = m_replies.begin(); + index != m_replies.end(); ++index) { + clearReplies(index->second); + } + m_replies.clear(); + m_eventMasks.clear(); +} + +void CXWindowsClipboard::clearReplies(CReplyList& replies) +{ + for (CReplyList::iterator index = replies.begin(); + index != replies.end(); ++index) { + delete *index; + } + replies.clear(); +} + +void CXWindowsClipboard::sendNotify( + Window requestor, Atom selection, + Atom target, Atom property, Time time) +{ + XEvent event; + event.xselection.type = SelectionNotify; + event.xselection.display = m_display; + event.xselection.requestor = requestor; + event.xselection.selection = selection; + event.xselection.target = target; + event.xselection.property = property; + event.xselection.time = time; + XSendEvent(m_display, requestor, False, 0, &event); +} + +bool CXWindowsClipboard::wasOwnedAtTime( + ::Time time) const +{ + // not owned if we've never owned the selection + if (m_timeOwned == 0) + return false; + + // if time is CurrentTime then return true if we still own the + // selection and false if we do not. else if we still own the + // selection then get the current time, otherwise use + // m_timeLost as the end time. + Time lost = m_timeLost; + if (m_timeLost == 0) + if (time == CurrentTime) + return true; + else + lost = CXWindowsUtil::getCurrentTime(m_display, m_window); + else + if (time == CurrentTime) + return false; + + // compare time to range + Time duration = lost - m_timeOwned; + Time when = time - m_timeOwned; + return (/*when >= 0 &&*/ when < duration); +} + +Atom CXWindowsClipboard::getTargetsData( + CString& data, int* format) const +{ + assert(format != NULL); + + // construct response + Atom atom; + atom = m_atomTargets; + data.append(reinterpret_cast(&atom), sizeof(Atom)); + atom = m_atomMultiple; + data.append(reinterpret_cast(&atom), sizeof(Atom)); + atom = m_atomTimestamp; + data.append(reinterpret_cast(&atom), sizeof(Atom)); + if (m_added[kText]) { + atom = m_atomString; + data.append(reinterpret_cast(&atom), sizeof(Atom)); + atom = m_atomText; + data.append(reinterpret_cast(&atom), sizeof(Atom)); + } + + *format = 32; + return m_atomTargets; +} + +Atom CXWindowsClipboard::getTimestampData( + CString& data, int* format) const +{ + assert(format != NULL); + + assert(sizeof(m_timeOwned) == 4); + data.append(reinterpret_cast(&m_timeOwned), 4); + *format = 32; + return m_atomTimestamp; +} + +Atom CXWindowsClipboard::getStringData( + CString& data, int* format) const +{ + assert(format != NULL); + + if (m_added[kText]) { + data = m_data[kText]; + *format = 8; + return m_atomString; + } + else { + return None; + } +} + + +// +// CXWindowsClipboard::CICCCMGetClipboard +// + +CXWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard( + Window requestor, Time time, Atom property) : + m_requestor(requestor), + m_time(time), + m_property(property), + m_incr(false), + m_failed(false), + m_done(false), + m_reading(false), + m_data(NULL), + m_actualTarget(NULL), + m_error(false) +{ + // do nothing +} + +CXWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard() +{ + // do nothing +} + +bool CXWindowsClipboard::CICCCMGetClipboard::readClipboard( + Display* display, + Atom selection, Atom target, + Atom* actualTarget, CString* data) +{ + assert(actualTarget != NULL); + assert(data != NULL); + + log((CLOG_DEBUG1 "request selection=%d, target=%d, window=%x", selection, target, m_requestor)); + + // save output pointers + m_actualTarget = actualTarget; + m_data = data; + + // assume failure + *m_actualTarget = None; + *m_data = ""; + + // delete target property + XDeleteProperty(display, m_requestor, m_property); + + // select window for property changes + XWindowAttributes attr; + XGetWindowAttributes(display, m_requestor, &attr); + XSelectInput(display, m_requestor, + attr.your_event_mask | PropertyChangeMask); + + // request data conversion + XConvertSelection(display, selection, target, + m_property, m_requestor, m_time); + + // process selection events. use a timeout so we don't get + // screwed by a bad selection owner. + CStopwatch timer(true); + XEvent xevent; + while (!m_done && !m_failed) { + // return false if we've timed-out + if (timer.getTime() >= 0.2) { + log((CLOG_DEBUG1 "request timed out")); + XSelectInput(display, m_requestor, attr.your_event_mask); + return false; + } + + // process events + if (!XCheckIfEvent(display, &xevent, + &CXWindowsClipboard::CICCCMGetClipboard::eventPredicate, + reinterpret_cast(this))) { + // wait a bit to avoid spinning + CThread::sleep(0.05); + } + } + + // restore mask + XSelectInput(display, m_requestor, attr.your_event_mask); + + // return success or failure + log((CLOG_DEBUG1 "request %s", m_failed ? "failed" : "succeeded")); + return !m_failed; +} + +bool CXWindowsClipboard::CICCCMGetClipboard::doEventPredicate( + Display* display, + XEvent* xevent) +{ + // process event + switch (xevent->type) { + case DestroyNotify: + if (xevent->xdestroywindow.window == m_requestor) { + m_failed = true; + return true; + } + + // not interested + return false; + + case SelectionNotify: + if (xevent->xselection.requestor == m_requestor) { + // done if we can't convert + if (xevent->xselection.property == None) { + m_done = true; + return true; + } + + // proceed if conversion successful + else if (xevent->xselection.property == m_property) { + m_reading = true; + break; + } + } + + // otherwise not interested + return false; + + case PropertyNotify: + // proceed if conversion successful and we're receiving more data + if (xevent->xproperty.window == m_requestor && + xevent->xproperty.atom == m_property && + xevent->xproperty.state == PropertyNewValue) { + if (!m_reading) { + return false; + } + break; + } + + // otherwise not interested + return false; + + default: + // not interested + return false; + } + + // get the data from the property + Atom target; + const CString::size_type oldSize = m_data->size(); + if (!CXWindowsUtil::getWindowProperty(display, m_requestor, + m_property, m_data, &target, NULL, True)) { + // unable to read property + m_failed = true; + return True; + } + + // note if incremental. if we're already incremental then the + // selection owner is busted. if the INCR property has no size + // then the selection owner is busted. + if (target == XInternAtom(display, "INCR", False)) { +log((CLOG_INFO " INCR")); // FIXME + if (m_incr) { +log((CLOG_INFO " INCR repeat")); // FIXME + m_failed = true; + m_error = true; + } + else if (m_data->size() == oldSize) { +log((CLOG_INFO " INCR zero size")); // FIXME + m_failed = true; + m_error = true; + } + else { +log((CLOG_INFO " INCR start")); // FIXME + m_incr = true; + + // discard INCR data + *m_data = ""; + } + } + + // handle incremental chunks + else if (m_incr) { + // if first incremental chunk then save target + if (oldSize == 0) { + log((CLOG_DEBUG1 " INCR first chunk, target %d", target)); + *m_actualTarget = target; + } + + // secondary chunks must have the same target + else { +log((CLOG_INFO " INCR secondary chunk")); // FIXME + if (target != *m_actualTarget) { + log((CLOG_WARN " INCR target mismatch")); + m_failed = true; + m_error = true; + } + } + + // note if this is the final chunk + if (m_data->size() == oldSize) { + log((CLOG_DEBUG1 " INCR final chunk: %d bytes total", m_data->size())); + m_done = true; + } + } + + // not incremental; save the target. + else { + log((CLOG_DEBUG1 " target %d", target)); + *m_actualTarget = target; + m_done = true; + } + + // say we're not interested in this event if the conversion is + // incremental. that'll cause this method to be called again + // when there's more data. we finally finish the incremental + // copy when we read a 0 byte property. + logc(!m_incr, (CLOG_DEBUG1 " got data, %d bytes", m_data->size())); + return !m_incr; +} + +Bool CXWindowsClipboard::CICCCMGetClipboard::eventPredicate( + Display* display, + XEvent* xevent, + XPointer arg) +{ + CICCCMGetClipboard* self = reinterpret_cast(arg); + return self->doEventPredicate(display, xevent) ? True : False; +} + + +// +// CXWindowsClipboard::CReply +// + +CXWindowsClipboard::CReply::CReply(Window requestor, + Atom target, ::Time time) : + m_requestor(requestor), + m_target(target), + m_time(time), + m_property(None), + m_replied(false), + m_done(false), + m_data(), + m_type(None), + m_format(32), + m_ptr(0) +{ + // do nothing +} + +CXWindowsClipboard::CReply::CReply(Window requestor, + Atom target, ::Time time, Atom property, + const CString& data, Atom type, int format) : + m_requestor(requestor), + m_target(target), + m_time(time), + m_property(property), + m_replied(false), + m_done(false), + m_data(data), + m_type(type), + m_format(format), + m_ptr(0) +{ + // do nothing } diff --git a/synergy/CXWindowsClipboard.h b/synergy/CXWindowsClipboard.h index 7bdfb08c..bb0fde8e 100644 --- a/synergy/CXWindowsClipboard.h +++ b/synergy/CXWindowsClipboard.h @@ -2,18 +2,262 @@ #define CXWINDOWSCLIPBOARD_H #include "IClipboard.h" +#include "ClipboardTypes.h" +#include "CString.h" +#include +#include +#include class CXWindowsClipboard : public IClipboard { public: - CXWindowsClipboard(); + CXWindowsClipboard(Display*, Window, ClipboardID); virtual ~CXWindowsClipboard(); + // tell clipboard it lost ownership + void lost(Time); + + // add a selection request to the request list. if the given + // owner window isn't this clipboard's window then this simply + // sends a failure event to the requestor. + void addRequest(Window owner, + Window requestor, Atom target, + ::Time time, Atom property); + + // continue processing a selection request. returns true if the + // request was handled, false if the request was unknown. + bool processRequest(Window requestor, + ::Time time, Atom property); + + // terminate a selection request. returns true iff the request + // was known and handled. + bool destroyRequest(Window requestor); + + // get the clipboard's window + Window getWindow() const; + + // get the clipboard selection atom + Atom getSelection() const; + // IClipboard overrides - virtual bool open(); - virtual void close(); + virtual bool empty(); virtual void add(EFormat, const CString& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; virtual bool has(EFormat) const; virtual CString get(EFormat) const; + +private: + // convert target atom to clipboard format + EFormat getFormat(Atom target) const; + + // add a non-MULTIPLE request. does not verify that the selection + // was owned at the given time. returns true if the conversion + // could be performed, false otherwise. in either case, the + // reply is inserted. + bool addSimpleRequest( + Window requestor, Atom target, + ::Time time, Atom property); + + // clear the cache, resetting the cached flag and the added flag for + // each format. + void clearCache() const; + void doClearCache(); + + // cache all formats of the selection + void fillCache() const; + void doFillCache(); + + // ICCCM interoperability methods + void icccmFillCache(); + bool icccmGetSelection(Atom target, + Atom* actualTarget, + CString* data) const; + Time icccmGetTime() const; + + // motif interoperability methods + bool motifLockClipboard() const; + void motifUnlockClipboard() const; + bool motifOwnsClipboard() const; + Time motifGetTime() const; + void motifFillCache(); + // FIXME + + // + // helper classes + // + + // read an ICCCM conforming selection + class CICCCMGetClipboard { + public: + CICCCMGetClipboard(Window requestor, Time time, Atom property); + ~CICCCMGetClipboard(); + + // convert the given selection to the given type. returns + // true iff the conversion was successful or the conversion + // cannot be performed (in which case *actualTarget == None). + bool readClipboard(Display* display, + Atom selection, Atom target, + Atom* actualTarget, CString* data); + + private: + bool doEventPredicate(Display* display, + XEvent* event); + static Bool eventPredicate(Display* display, + XEvent* event, + XPointer arg); + + private: + Window m_requestor; + Time m_time; + Atom m_property; + bool m_incr; + bool m_failed; + bool m_done; + + // true iff we've received the selection notify + bool m_reading; + + // the converted selection data + CString* m_data; + + // the actual type of the data. if this is None then the + // selection owner cannot convert to the requested type. + Atom* m_actualTarget; + + public: + // true iff the selection owner didn't follow ICCCM conventions + bool m_error; + }; + + // Motif structure IDs + enum { kMotifClipFormat = 1, kMotifClipItem, kMotifClipHeader }; + + // _MOTIF_CLIP_HEADER structure + class CMotifClipHeader { + public: + SInt32 m_id; // kMotifClipHeader + SInt32 m_pad1[3]; + SInt32 m_item; + SInt32 m_pad2[4]; + SInt32 m_numItems; + SInt32 m_pad3[3]; + Window m_selectionOwner; + SInt32 m_pad4[2]; + SInt32 m_items[1]; // m_numItems items + }; + + // Motif clip item structure + class CMotifClipItem { + public: + SInt32 m_id; // kMotifClipItem + SInt32 m_pad1[6]; + SInt32 m_numFormats; + SInt32 m_pad2[7]; + SInt32 m_formats[1]; // m_numFormats formats + }; + + // Motif clip format structure + class CMotifClipFormat { + public: + SInt32 m_id; // kMotifClipFormat + SInt32 m_pad1[6]; + SInt32 m_length; + SInt32 m_data; + Atom m_type; + SInt32 m_pad2[6]; + }; + + // stores data needed to respond to a selection request + class CReply { + public: + CReply(Window, Atom target, ::Time); + CReply(Window, Atom target, ::Time, Atom property, + const CString& data, Atom type, int format); + + public: + // information about the request + Window m_requestor; + Atom m_target; + ::Time m_time; + Atom m_property; + + // true iff we've sent the notification for this reply + bool m_replied; + + // true iff the reply has sent its last message + bool m_done; + + // the data to send and its type and format + CString m_data; + Atom m_type; + int m_format; + + // index of next byte in m_data to send + UInt32 m_ptr; + }; + typedef std::list CReplyList; + typedef std::map CReplyMap; + typedef std::map CReplyEventMask; + + // reply methods + bool insertMultipleReply(Window, ::Time, Atom); + void insertReply(CReply*); + void pushReplies(); + void pushReplies(CReplyMap::iterator, + CReplyList&, CReplyList::iterator); + bool sendReply(CReply*); + void clearReplies(); + void clearReplies(CReplyList&); + void sendNotify(Window requestor, Atom selection, + Atom target, Atom property, Time time); + bool wasOwnedAtTime(::Time) const; + + // data conversion methods + Atom getTargetsData(CString&, int* format) const; + Atom getTimestampData(CString&, int* format) const; + Atom getStringData(CString&, int* format) const; + +private: + Display* m_display; + Window m_window; + ClipboardID m_id; + Atom m_selection; + mutable bool m_open; + mutable Time m_time; + bool m_owner; + mutable Time m_timeOwned; + Time m_timeLost; + + // true iff open and clipboard owned by a motif app + mutable bool m_motif; + + // the added/cached clipboard data + bool m_cached; + Time m_cacheTime; + bool m_added[kNumFormats]; + CString m_data[kNumFormats]; + + // conversion request replies + CReplyMap m_replies; + CReplyEventMask m_eventMasks; + + // atoms we'll need + Atom m_atomTargets; + Atom m_atomMultiple; + Atom m_atomTimestamp; + Atom m_atomAtom; + Atom m_atomAtomPair; + Atom m_atomInteger; + Atom m_atomData; + Atom m_atomINCR; + Atom m_atomString; + Atom m_atomText; + Atom m_atomCompoundText; + Atom m_atomMotifClipLock; + Atom m_atomMotifClipHeader; + Atom m_atomMotifClipAccess; + Atom m_atomGDKSelection; }; #endif diff --git a/synergy/CXWindowsScreen.cpp b/synergy/CXWindowsScreen.cpp index 377a3da7..33d716b1 100644 --- a/synergy/CXWindowsScreen.cpp +++ b/synergy/CXWindowsScreen.cpp @@ -1,22 +1,18 @@ #include "CXWindowsScreen.h" -#include "CThread.h" +#include "CXWindowsClipboard.h" +#include "CXWindowsUtil.h" +#include "CClipboard.h" #include "CLock.h" -#include "TMethodJob.h" #include "CLog.h" #include "CString.h" -#include "CStopwatch.h" +#include "CThread.h" #include #include -#include -#include -#include // // CXWindowsScreen // -static const UInt32 kMaxRequestSize = 4096; - CXWindowsScreen::CXWindowsScreen() : m_display(NULL), m_root(None), @@ -53,27 +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_atomMultiple = XInternAtom(m_display, "MULTIPLE", False); - m_atomTimestamp = XInternAtom(m_display, "TIMESTAMP", False); - m_atomAtom = XInternAtom(m_display, "ATOM", False); - m_atomAtomPair = XInternAtom(m_display, "ATOM_PAIR", False); - m_atomInteger = XInternAtom(m_display, "INTEGER", False); - m_atomData = XInternAtom(m_display, "DESTINATION", False); - m_atomINCR = XInternAtom(m_display, "INCR", False); - m_atomString = XInternAtom(m_display, "STRING", False); - m_atomText = XInternAtom(m_display, "TEXT", False); - m_atomCompoundText = XInternAtom(m_display, "COMPOUND_TEXT", False); - m_atomSynergyTime = XInternAtom(m_display, "SYNERGY_TIME", False); - - // clipboard atoms - m_atomClipboard[kClipboardClipboard] = - XInternAtom(m_display, "CLIPBOARD", False); - m_atomClipboard[kClipboardSelection] = XA_PRIMARY; - // let subclass prep display onOpenDisplay(); + + // initialize clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_clipboard[id] = createClipboard(id); + } } void CXWindowsScreen::closeDisplay() @@ -83,19 +65,9 @@ void CXWindowsScreen::closeDisplay() // let subclass close down display onCloseDisplay(); - // clear out the clipboard request lists + // destroy clipboards for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - CClipboardInfo& clipboard = m_clipboards[id]; - for (CRequestMap::iterator index = clipboard.m_requests.begin(); - index != clipboard.m_requests.end(); ++index) { - CRequestList* list = index->second; - for (CRequestList::iterator index2 = list->begin(); - index2 != list->end(); ++index2) { - delete *index2; - } - delete list; - } - clipboard.m_requests.clear(); + delete m_clipboard[id]; } // close the display @@ -166,19 +138,27 @@ bool CXWindowsScreen::getEvent(XEvent* xevent) const // wait for an event in a cancellable way and don't lock the // display while we're waiting. m_mutex.lock(); - while (!m_stop && XPending(m_display) == 0) { - m_mutex.unlock(); - CThread::sleep(0.05); - m_mutex.lock(); - } - if (m_stop) { - m_mutex.unlock(); - return false; - } - else { - XNextEvent(m_display, xevent); - m_mutex.unlock(); - return true; + for (;;) { + while (!m_stop && XPending(m_display) == 0) { + m_mutex.unlock(); + CThread::sleep(0.05); + m_mutex.lock(); + } + if (m_stop) { + m_mutex.unlock(); + return false; + } + else { + // get the event + XNextEvent(m_display, xevent); + + // process the event. return the event if unhandled. + m_mutex.unlock(); + if (!const_cast(this)->processEvent(xevent)) { + return true; + } + m_mutex.lock(); + } } } @@ -188,544 +168,147 @@ void CXWindowsScreen::doStop() m_stop = true; } -ClipboardID CXWindowsScreen::getClipboardID(Atom selection) +ClipboardID CXWindowsScreen::getClipboardID(Atom selection) const { - for (ClipboardID id = 0; id < kClipboardEnd; ++id) - if (selection == m_atomClipboard[id]) + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->getSelection() == selection) { return id; + } + } return kClipboardEnd; } -bool CXWindowsScreen::lostClipboard( - Atom selection, Time timestamp) +bool CXWindowsScreen::processEvent(XEvent* xevent) { - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - if (selection == m_atomClipboard[id]) { - // note the time - CLock lock(&m_mutex); - m_clipboards[id].m_lostClipboard = timestamp; - log((CLOG_INFO "lost clipboard %d ownership at %d", id, timestamp)); + switch (xevent->type) { + case SelectionClear: { + // we just lost the selection. that means someone else + // grabbed the selection so this screen is now the + // selection owner. report that to the subclass. + ClipboardID id = getClipboardID(xevent->xselectionclear.selection); + if (id != kClipboardEnd) { + log((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); + m_clipboard[id]->lost(xevent->xselectionclear.time); + onLostClipboard(id); return true; } + break; } - return false; -} -bool CXWindowsScreen::setDisplayClipboard( - ClipboardID id, - const IClipboard* clipboard, - Window requestor, Time timestamp) -{ - CLock lock(&m_mutex); - - XSetSelectionOwner(m_display, m_atomClipboard[id], requestor, timestamp); - if (XGetSelectionOwner(m_display, m_atomClipboard[id]) == requestor) { - // we got the selection - log((CLOG_INFO "grabbed clipboard at %d", timestamp)); - m_clipboards[id].m_lostClipboard = CurrentTime; - if (clipboard != NULL) { - // save clipboard to serve requests - CClipboard::copy(&m_clipboards[id].m_clipboard, - clipboard, timestamp); - } - else { - // clear clipboard - if (m_clipboards[id].m_clipboard.open(timestamp)) { - m_clipboards[id].m_clipboard.close(); - } + case SelectionNotify: + // notification of selection transferred. we shouldn't + // get this here because we handle them in the selection + // retrieval methods. we'll just delete the property + // with the data (satisfying the usual ICCCM protocol). + if (xevent->xselection.property != None) { + CLock lock(&m_mutex); + XDeleteProperty(m_display, + xevent->xselection.requestor, + xevent->xselection.property); } + return true; + case SelectionRequest: { + // somebody is asking for clipboard data + ClipboardID id = getClipboardID(xevent->xselectionrequest.selection); + if (id != kClipboardEnd) { + CLock lock(&m_mutex); + m_clipboard[id]->addRequest( + xevent->xselectionrequest.owner, + xevent->xselectionrequest.requestor, + xevent->xselectionrequest.target, + xevent->xselectionrequest.time, + xevent->xselectionrequest.property); + return true; + } + break; + } + + case PropertyNotify: + // property delete may be part of a selection conversion + if (xevent->xproperty.state == PropertyDelete) { + processClipboardRequest(xevent->xproperty.window, + xevent->xproperty.time, + xevent->xproperty.atom); + return true; + } + break; + + case DestroyNotify: + // looks like one of the windows that requested a clipboard + // transfer has gone bye-bye. + destroyClipboardRequest(xevent->xdestroywindow.window); return true; } return false; } -void CXWindowsScreen::getDisplayClipboard( +bool CXWindowsScreen::setDisplayClipboard( ClipboardID id, - IClipboard* clipboard, - Window requestor, Time timestamp) const + const IClipboard* clipboard) { - assert(clipboard != NULL); - assert(requestor != None); - - // 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); - Atom selection = m_atomClipboard[id]; - // if we're trying to request the clipboard from the same - // unresponsive owner then immediately give up. this is lame - // because we can't be sure the owner won't become responsive; - // we can only ask the X server for the current owner, not the - // owner at time timestamp, allowing a race condition; and we - // don't detect if the owner window is destroyed in order to - // reset the unresponsive flag. - Window owner = XGetSelectionOwner(m_display, selection); - if (m_clipboards[id].m_unresponsive) { - if (owner != None && owner == m_clipboards[id].m_owner) { - log((CLOG_DEBUG1 "skip unresponsive clipboard owner")); - // clear the clipboard and return - if (!clipboard->open(timestamp)) { - clipboard->close(); - } - return; - } - } - CClipboardInfo& clipboardInfo = - const_cast(m_clipboards[id]); - - // don't update clipboard object if clipboard hasn't changed. ask - // the selection for the tiemstamp when it acquired the selection. - Atom format; -/* XXX -- timestamp not always updated when clipboard is changed - CString data; - if (getDisplayClipboard(selection, m_atomTimestamp, - requestor, timestamp, &format, &data) && - format == m_atomInteger) { - // get the owner's time - Time time = *reinterpret_cast(data.data()); - log((CLOG_DEBUG "got clipboard timestamp %08x", time)); - - // if unchanged then clipboard hasn't changed - if (time == clipboard->getTime()) - return; - - // use clipboard owner's time as timestamp - timestamp = time; - } -*/ - - // clear the clipboard object - if (!clipboard->open(timestamp)) { - return; + // fail if we don't have the requested clipboard + if (m_clipboard[id] == NULL) { + return false; } - // ask the selection for all the formats it has. some owners return - // the TARGETS atom and some the ATOM atom when TARGETS is requested. - CString targets; - if (getDisplayClipboard(selection, m_atomTargets, - requestor, timestamp, &format, &targets) && - (format == m_atomTargets || format == XA_ATOM)) { - // save owner info - clipboardInfo.m_owner = owner; - clipboardInfo.m_unresponsive = false; + // get the actual time. ICCCM does not allow CurrentTime. + Time timestamp = CXWindowsUtil::getCurrentTime( + m_display, m_clipboard[id]->getWindow()); - // 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_INFO "getting selection %d with %d targets", id, numTargets)); - for (SInt32 i = 0; i < numTargets; ++i) { - Atom format = targetAtoms[i]; - log((CLOG_DEBUG1 " source target %d", format)); - - // skip already handled targets - if (targets.count(format) > 0) { - log((CLOG_DEBUG1 " 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::kNumFormats) { - log((CLOG_DEBUG1 " no format for target", format)); - continue; - } - if (clipboardFormats.count(expectedFormat) > 0) { - log((CLOG_DEBUG1 " skipping handled format %d", expectedFormat)); - continue; - } - - CString data; - if (!getDisplayClipboard(selection, format, - requestor, timestamp, &format, &data)) { - log((CLOG_DEBUG1 " no data for target", format)); - continue; - } - - // use the actual format, not the expected - IClipboard::EFormat actualFormat = getFormat(format); - if (actualFormat == IClipboard::kNumFormats) { - log((CLOG_DEBUG1 " no format for target", format)); - continue; - } - if (clipboardFormats.count(actualFormat) > 0) { - log((CLOG_DEBUG1 " skipping handled format %d", actualFormat)); - continue; - } - - // add to clipboard and note we've done it - clipboard->add(actualFormat, data); - clipboardFormats.insert(actualFormat); - log((CLOG_INFO " added format %d for target %d", actualFormat, format)); - } + if (clipboard != NULL) { + // save clipboard data + return CClipboard::copy(m_clipboard[id], clipboard, timestamp); } else { - // non-ICCCM conforming selection owner. try TEXT format. - // FIXME - - // save owner info - clipboardInfo.m_owner = owner; - clipboardInfo.m_unresponsive = true; - - log((CLOG_DEBUG1 "selection doesn't support TARGETS, format is %d", format)); + // assert clipboard ownership + if (!m_clipboard[id]->open(timestamp)) { + return false; + } + m_clipboard[id]->empty(); + m_clipboard[id]->close(); + return true; } - - // done with clipboard - clipboard->close(); } bool CXWindowsScreen::getDisplayClipboard( - Atom selection, Atom type, - Window requestor, Time timestamp, - Atom* outputType, CString* outputData) const + ClipboardID id, + IClipboard* clipboard) const { - assert(outputType != NULL); - assert(outputData != NULL); + assert(clipboard != NULL); - // FIXME -- this doesn't work to retrieve Motif selections. - - // 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. we also set - // a time limit for a response so we're not screwed by a bad - // clipboard owner. - CStopwatch timer(true); - XEvent xevent; - while (XCheckIfEvent(m_display, &xevent, - &CXWindowsScreen::findSelectionNotify, - (XPointer)&requestor) != True) { - // return false if we've timed-out - if (timer.getTime() >= 0.2) - return false; - - // 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_DEBUG1 "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 - CPropertyNotifyInfo 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::kNumFormats; -} - -Bool CXWindowsScreen::findSelectionNotify( - Display*, XEvent* xevent, XPointer arg) -{ - Window requestor = *reinterpret_cast(arg); - return (xevent->type == SelectionNotify && - xevent->xselection.requestor == requestor) ? True : False; -} - -Bool CXWindowsScreen::findPropertyNotify( - Display*, XEvent* xevent, XPointer arg) -{ - CPropertyNotifyInfo* filter = reinterpret_cast(arg); - return (xevent->type == PropertyNotify && - xevent->xproperty.window == filter->m_window && - xevent->xproperty.atom == filter->m_property && - xevent->xproperty.state == PropertyNewValue) ? True : False; -} - -void CXWindowsScreen::addClipboardRequest( - Window owner, Window requestor, - Atom selection, Atom target, - Atom property, Time time) -{ - bool success = false; - - // see if it's a selection we know about - ClipboardID id; - for (id = 0; id < kClipboardEnd; ++id) - if (selection == m_atomClipboard[id]) - break; - - // mutex the display + // block others from using the display while we get the clipboard CLock lock(&m_mutex); - // a request for multiple targets is special - if (id != kClipboardEnd) { - // check time we own the selection against the requested time - if (!wasOwnedAtTime(id, owner, time)) { - // fail for time we didn't own selection - } - else if (target == m_atomMultiple) { - // add a multiple request - if (property != None) { - success = sendClipboardMultiple(id, requestor, property, time); - } - } - else { - // handle remaining request formats - success = sendClipboardData(id, requestor, target, property, time); - } + // fail if we don't have the requested clipboard + if (m_clipboard[id] == NULL) { + return false; } - // send success or failure - sendNotify(requestor, selection, target, success ? property : None, time); + // get the actual time. ICCCM does not allow CurrentTime. + Time timestamp = CXWindowsUtil::getCurrentTime( + m_display, m_clipboard[id]->getWindow()); + + // copy the clipboard + return CClipboard::copy(clipboard, m_clipboard[id], timestamp); } void CXWindowsScreen::processClipboardRequest( Window requestor, - Atom property, Time /*time*/) + Time time, Atom property) { CLock lock(&m_mutex); - // check every clipboard + // check every clipboard until one returns success for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - CClipboardInfo& clipboard = m_clipboards[id]; - - // find the request list - CRequestMap::iterator index = clipboard.m_requests.find(requestor); - if (index == clipboard.m_requests.end()) { - // this clipboard isn't servicing this requestor window - continue; + if (m_clipboard[id] != NULL && + m_clipboard[id]->processRequest(requestor, time, property)) { + break; } - CRequestList* list = index->second; - assert(list != NULL); - - // find the property in the list - CRequestList::iterator index2; - for (index2 = list->begin(); index2 != list->end(); ++index2) { - if ((*index2)->m_property == property) { - break; - } - } - if (index2 == list->end()) { - // this clipboard isn't waiting on this property - continue; - } - CClipboardRequest* request = *index2; - assert(request != NULL); - - // compute amount of data to send - assert(request->m_sent <= request->m_data.size()); - UInt32 count = request->m_data.size() - request->m_sent; - if (count > kMaxRequestSize) { - // limit maximum chunk size - count = kMaxRequestSize; - - // make it a multiple of the size - count &= ~((request->m_size >> 3) - 1); - } - - // send more data - // FIXME -- handle Alloc errors (by returning false) - XChangeProperty(m_display, request->m_requestor, request->m_property, - request->m_type, request->m_size, - PropModeReplace, - reinterpret_cast( - request->m_data.data() + request->m_sent), - count / (request->m_size >> 3)); - - // account for sent data - request->m_sent += count; - - // if we sent zero bytes then we're done sending this data. remove - // it from the list and, if the list is empty, the list from the - // map. also stop watching the requestor for events. - if (count == 0) { - list->erase(index2); - delete request; - if (list->empty()) { - clipboard.m_requests.erase(index); - delete list; - } - XSelectInput(m_display, requestor, getEventMask(requestor)); - } - - // request has been serviced - break; } } @@ -734,340 +317,13 @@ void CXWindowsScreen::destroyClipboardRequest( { CLock lock(&m_mutex); - // check every clipboard + // check every clipboard until one returns success for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - CClipboardInfo& clipboard = m_clipboards[id]; - - // find the request list - CRequestMap::iterator index = clipboard.m_requests.find(requestor); - if (index == clipboard.m_requests.end()) { - continue; - } - CRequestList* list = index->second; - assert(list != NULL); - - // destroy every request in the list - for (CRequestList::iterator index2 = list->begin(); - index2 != list->end(); ++index2) { - delete *index2; - } - - // remove and destroy the list - clipboard.m_requests.erase(index); - delete list; - } - - // note -- we don't stop watching the window for events because - // we're called in response to the window being destroyed. -} - -bool CXWindowsScreen::sendClipboardData( - ClipboardID id, - Window requestor, Atom target, - Atom property, Time time) -{ - if (target == m_atomTargets) { - return sendClipboardTargets(id, requestor, property, time); - } - else if (target == m_atomTimestamp) { - return sendClipboardTimestamp(id, requestor, property, time); - } - else { - // compute the type and size for the requested target and - // convert the data from the clipboard. - Atom type = None; - int size = 0; - CString data; - if (target == m_atomText || target == m_atomString) { - if (m_clipboards[id].m_clipboard.has(IClipboard::kText)) { - type = m_atomString; - size = 8; - data = m_clipboards[id].m_clipboard.get(IClipboard::kText); - } - } - - // fail if we don't recognize or can't handle the target - if (type == None || size == 0) { - return false; - } - - if (data.size() > kMaxRequestSize) { - log((CLOG_DEBUG1 "handling clipboard request for %d as INCR", target)); - - // get the appropriate list, creating it if necessary - CRequestList* list = m_clipboards[id].m_requests[requestor]; - if (list == NULL) { - list = new CRequestList; - m_clipboards[id].m_requests[requestor] = list; - } - - // create request object - CClipboardRequest* request = new CClipboardRequest; - request->m_data = data; - request->m_sent = 0; - request->m_requestor = requestor; - request->m_property = property; - request->m_type = type; - request->m_size = size; - - // add request to request list - list->push_back(request); - - // start watching requestor for property changes and - // destruction, in addition to other events required by - // the subclass. - XSelectInput(m_display, requestor, - getEventMask(requestor) | - StructureNotifyMask | - PropertyChangeMask); - - // FIXME -- handle Alloc errors (by returning false) - // set property to INCR - const UInt32 zero = 0; - XChangeProperty(m_display, requestor, property, - m_atomINCR, - 8 * sizeof(zero), - PropModeReplace, - reinterpret_cast(&zero), - 1); - } - else { - log((CLOG_DEBUG1 "handling clipboard request for %d", target)); - - // FIXME -- handle Alloc errors (by returning false) - XChangeProperty(m_display, requestor, property, - type, size, - PropModeReplace, - reinterpret_cast( - data.data()), - data.size() / (size >> 3)); - } - return true; - } - return false; -} - -bool CXWindowsScreen::sendClipboardMultiple( - ClipboardID id, - Window requestor, - Atom property, Time time) -{ - log((CLOG_DEBUG1 "handling clipboard request for MULTIPLE")); - - // get the list of requested formats - Atom type; - SInt32 size; - CString data; - if (!getData(requestor, property, &type, &size, &data)) { - type = 0; - } - - // we only handle atom pair type - bool success = false; - if (type == m_atomAtomPair) { - // check each format, replacing ones we can't do with None. set - // the property for each to the requested data (for small requests) - // or INCR (for large requests). - bool updated = false; - UInt32 numRequests = data.size() / (2 * sizeof(Atom)); - for (UInt32 index = 0; index < numRequests; ++index) { - // get request info - const Atom* request = reinterpret_cast(data.data()); - const Atom target = request[2 * index + 0]; - const Atom property = request[2 * index + 1]; - - // handle target - if (property != None) { - if (!sendClipboardData(id, requestor, target, property, time)) { - // couldn't handle target. change property to None. - const Atom none = None; - data.replace((2 * index + 1) * sizeof(Atom), sizeof(Atom), - reinterpret_cast(&none), - sizeof(none)); - updated = true; - } - else { - success = true; - } - } - } - - // update property if we changed it - if (updated) { - // FIXME -- handle Alloc errors (by returning false) - XChangeProperty(m_display, requestor, property, - m_atomAtomPair, - 8 * sizeof(Atom), - PropModeReplace, - reinterpret_cast( - data.data()), - data.length()); + if (m_clipboard[id] != NULL && + m_clipboard[id]->destroyRequest(requestor)) { + break; } } - - // send notify - sendNotify(requestor, m_atomClipboard[id], m_atomMultiple, - success ? property : None, time); - return success; -} - -bool CXWindowsScreen::sendClipboardTargets( - ClipboardID id, - Window requestor, - Atom property, Time /*time*/) -{ - log((CLOG_DEBUG1 "handling request for TARGETS")); - - // count the number of targets, plus TARGETS and MULTIPLE - SInt32 numTargets = 2; - if (m_clipboards[id].m_clipboard.has(IClipboard::kText)) { - numTargets += 2; - } - - // construct response - Atom* response = new Atom[numTargets]; - SInt32 count = 0; - response[count++] = m_atomTargets; - response[count++] = m_atomMultiple; - if (m_clipboards[id].m_clipboard.has(IClipboard::kText)) { - response[count++] = m_atomText; - response[count++] = m_atomString; - } - - // send response (we assume we can transfer the entire list at once) - // FIXME -- handle Alloc errors (by returning false) - XChangeProperty(m_display, requestor, property, - m_atomAtom, - 8 * sizeof(Atom), - PropModeReplace, - reinterpret_cast(response), - count); - - // done with our response - delete[] response; - - return true; -} - -bool CXWindowsScreen::sendClipboardTimestamp( - ClipboardID id, - Window requestor, - Atom property, Time /*time*/) -{ - log((CLOG_DEBUG1 "handling clipboard request for TIMESTAMP")); - - // FIXME -- handle Alloc errors (by returning false) - Time time = m_clipboards[id].m_clipboard.getTime(); - XChangeProperty(m_display, requestor, property, - m_atomInteger, - 32, - PropModeReplace, - reinterpret_cast(time), - 1); - return true; -} - -void CXWindowsScreen::sendNotify( - Window requestor, Atom selection, - Atom target, Atom property, Time time) -{ - XEvent event; - event.xselection.type = SelectionNotify; - event.xselection.display = m_display; - event.xselection.requestor = requestor; - event.xselection.selection = selection; - event.xselection.target = target; - event.xselection.property = property; - event.xselection.time = time; - XSendEvent(m_display, requestor, False, 0, &event); -} - -bool CXWindowsScreen::wasOwnedAtTime( - ClipboardID id, Window window, Time time) const -{ - const CClipboardInfo& clipboard = m_clipboards[id]; - - // not owned if we've never owned the selection - if (clipboard.m_clipboard.getTime() == CurrentTime) - return false; - - // if time is CurrentTime then return true if we still own the - // selection and false if we do not. else if we still own the - // selection then get the current time, otherwise use - // m_lostClipboard as the end time. - Time lost = clipboard.m_lostClipboard; - if (lost == CurrentTime) - if (time == CurrentTime) - return true; - else - lost = getCurrentTimeNoLock(window); - else - if (time == CurrentTime) - return false; - - // compare time to range - Time duration = clipboard.m_lostClipboard - clipboard.m_clipboard.getTime(); - Time when = time - clipboard.m_clipboard.getTime(); - return (/*when >= 0 &&*/ when < duration); -} - -Time CXWindowsScreen::getCurrentTime(Window window) const -{ - CLock lock(&m_mutex); - return getCurrentTimeNoLock(window); -} - -Time CXWindowsScreen::getCurrentTimeNoLock( - Window window) const -{ - // select property events on window - // note -- this will break clipboard transfer if used on a requestor - // window, so don't do that. - XSelectInput(m_display, window, getEventMask(window) | PropertyChangeMask); - - // do a zero-length append to get the current time - unsigned char dummy; - XChangeProperty(m_display, window, m_atomSynergyTime, - m_atomInteger, 8, - PropModeAppend, - &dummy, 0); - - // look for property notify events with the following - CPropertyNotifyInfo filter; - filter.m_window = window; - filter.m_property = m_atomSynergyTime; - - // wait for reply - XEvent xevent; - 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 == window); - assert(xevent.xproperty.atom == m_atomSynergyTime); - - // restore event mask - XSelectInput(m_display, window, getEventMask(window)); - - return xevent.xproperty.time; -} - - -// -// CXWindowsScreen::CClipboardInfo -// - -CXWindowsScreen::CClipboardInfo::CClipboardInfo() : - m_clipboard(), - m_lostClipboard(CurrentTime), - m_requests(), - m_owner(None), - m_unresponsive(false) -{ - // do nothing } diff --git a/synergy/CXWindowsScreen.h b/synergy/CXWindowsScreen.h index b02fd264..c97be519 100644 --- a/synergy/CXWindowsScreen.h +++ b/synergy/CXWindowsScreen.h @@ -3,13 +3,11 @@ #include "BasicTypes.h" #include "ClipboardTypes.h" -#include "CClipboard.h" #include "CMutex.h" #include -#include -#include -class CString; +class IClipboard; +class CXWindowsClipboard; class CXWindowsScreen { public: @@ -57,141 +55,54 @@ protected: // cause getEvent() to return false immediately and forever after void doStop(); - // determine the clipboard from the X selection. returns - // kClipboardEnd if no such clipboard. - ClipboardID getClipboardID(Atom selection); - - // call when we lose the clipboard ownership (i.e. when we receive - // a SelectionClear event). returns true iff we've actually lost - // a selection we care about. - bool lostClipboard(Atom selection, Time timestamp); - // set the contents of the clipboard (i.e. primary selection) bool setDisplayClipboard(ClipboardID, - const IClipboard* clipboard, - Window requestor, Time timestamp); + const IClipboard* clipboard); - // 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. - // if force is false then only update clipboard - void getDisplayClipboard(ClipboardID, - IClipboard* clipboard, - Window requestor, Time timestamp) const; - - // add a selection request to the request list - void addClipboardRequest( - Window owner, Window requestor, - Atom selection, Atom target, - Atom property, Time time); - - // continue processing a selection request - void processClipboardRequest(Window window, - Atom property, Time time); - - // terminate a selection request - void destroyClipboardRequest(Window window); - - // get the current server time - Time getCurrentTime(Window) const; + // copy the clipboard contents to clipboard + bool getDisplayClipboard(ClipboardID, + IClipboard* clipboard) const; // called by openDisplay() to allow subclasses to prepare the display virtual void onOpenDisplay() = 0; + // called by openDisplay() after onOpenDisplay() to create each clipboard + virtual CXWindowsClipboard* + createClipboard(ClipboardID) = 0; + // called by closeDisplay() to virtual void onCloseDisplay() = 0; + // called when a clipboard is lost + virtual void onLostClipboard(ClipboardID) = 0; + // get the X event mask required by the subclass for the given window virtual long getEventMask(Window) const = 0; private: - class CPropertyNotifyInfo { - public: - Window m_window; - Atom m_property; - }; - class CClipboardRequest { - public: - CString m_data; - UInt32 m_sent; - Window m_requestor; - Atom m_property; - Atom m_type; - int m_size; - }; - typedef std::list CRequestList; - typedef std::map CRequestMap; + // internal event processing + bool processEvent(XEvent*); - 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); + // determine the clipboard from the X selection. returns + // kClipboardEnd if no such clipboard. + ClipboardID getClipboardID(Atom selection) const; - bool sendClipboardData(ClipboardID, Window requestor, - Atom target, Atom property, Time time); - bool sendClipboardMultiple(ClipboardID, Window requestor, - Atom property, Time time); - bool sendClipboardTargets(ClipboardID, Window requestor, - Atom property, Time time); - bool sendClipboardTimestamp(ClipboardID, Window requestor, - Atom property, Time time); - void sendNotify(Window requestor, Atom selection, - Atom target, Atom property, Time time); - bool wasOwnedAtTime(ClipboardID, Window, Time) const; - Time getCurrentTimeNoLock(Window) const; + // continue processing a selection request + void processClipboardRequest(Window window, + Time time, Atom property); + + // terminate a selection request + void destroyClipboardRequest(Window window); private: - class CClipboardInfo { - public: - CClipboardInfo(); - - public: - // the contents of the clipboard and the time we got it - CClipboard m_clipboard; - - // when we lost the clipboard - Time m_lostClipboard; - - // the request queues - CRequestMap m_requests; - - // owner of clipboard when we last asked for it - Window m_owner; - - // true iff the previous request to m_owner got no reply - bool m_unresponsive; - }; - Display* m_display; int m_screen; Window m_root; SInt32 m_w, m_h; bool m_stop; - // atoms we'll need - Atom m_atomTargets; - Atom m_atomMultiple; - Atom m_atomTimestamp; - Atom m_atomAtom; - Atom m_atomAtomPair; - Atom m_atomInteger; - Atom m_atomData; - Atom m_atomINCR; - Atom m_atomString; - Atom m_atomText; - Atom m_atomCompoundText; - Atom m_atomClipboard[kClipboardEnd]; - Atom m_atomSynergyTime; - - // clipboard info - CClipboardInfo m_clipboards[kClipboardEnd]; + // clipboards + CXWindowsClipboard* m_clipboard[kClipboardEnd]; // X is not thread safe CMutex m_mutex; diff --git a/synergy/IClipboard.h b/synergy/IClipboard.h index 32856c1b..43632f02 100644 --- a/synergy/IClipboard.h +++ b/synergy/IClipboard.h @@ -19,38 +19,41 @@ public: // manipulators - // grab ownership of and clear the clipboard of all data. - // only add() may be called between an open() and its - // corresponding close(). if open() returns false then - // the clipboard could not be opened or grabbed; do not - // call close() in that case. iff open() returns true it - // should have saved the timestamp. the timestamp should - // be zero before the first successful open. - virtual bool open(Time) = 0; + // take ownership of the clipboard and clear all data from it. + // must be called between an open() and close(). if returns + // false then the clipboard ownership could not be taken; the + // clipboard should not be emptied in this case. + virtual bool empty() = 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 after a successful empty(). + virtual void add(EFormat, const CString& data) = 0; + + // accessors + + // open the clipboard. return true iff the clipboard could + // be opened. if open() returns true then it must be followed + // by a close() at some later time; if it returns false then + // close() must not be called. + virtual bool open(Time) const = 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 + virtual void close() const = 0; // returns the timestamp passed to the last successful open(). virtual Time getTime() const = 0; // returns true iff the clipboard contains data in the given - // format. + // format. must be called between an open() and close(). virtual bool has(EFormat) const = 0; // returns data in the given format. rturns the empty string - // if there is no data in that format. + // if there is no data in that format. must be called between + // an open() and close(). virtual CString get(EFormat) const = 0; }; diff --git a/synergy/Makefile b/synergy/Makefile index d1cef559..3d499dcd 100644 --- a/synergy/Makefile +++ b/synergy/Makefile @@ -23,6 +23,7 @@ CXXFILES = \ CTCPSocketFactory.cpp \ CXWindowsClipboard.cpp \ CXWindowsScreen.cpp \ + CXWindowsUtil.cpp \ XSynergy.cpp \ $(NULL)