From 854d2c7fbf5a4be23cd4dc8bad95cd2ea442b448 Mon Sep 17 00:00:00 2001 From: crs Date: Mon, 27 May 2002 16:22:59 +0000 Subject: [PATCH] checkpoint. changed clipboard model. the clipboard can only be accessed now between open()/close(). ownership of the clipboard is asserted via the empty() method. this parallels the win32 model (but the win32 code hasn't been updated yet). refactored X11 clipboard code. moved the bulk of it into CXWindowsClipboard and moved some comment event handling into CXWindowsScreen. changed how requests are processed into a hopefully easier to understand model. added support for getting clipboard from and sending clipboard to motif (or at least lesstif) clients. sending to lesstif required a hack to work around an apparent bug in lesstif. --- client/CXWindowsSecondaryScreen.cpp | 87 +- client/CXWindowsSecondaryScreen.h | 4 + server/CXWindowsPrimaryScreen.cpp | 87 +- server/CXWindowsPrimaryScreen.h | 3 + synergy/CClipboard.cpp | 72 +- synergy/CClipboard.h | 15 +- synergy/CXWindowsClipboard.cpp | 1275 ++++++++++++++++++++++++++- synergy/CXWindowsClipboard.h | 250 +++++- synergy/CXWindowsScreen.cpp | 1006 +++------------------ synergy/CXWindowsScreen.h | 141 +-- synergy/IClipboard.h | 41 +- synergy/Makefile | 1 + 12 files changed, 1800 insertions(+), 1182 deletions(-) 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)