• Main Page
  • Classes
  • Files
  • File List

CXWindowsClipboard.cpp

00001 /*
00002  * synergy -- mouse and keyboard sharing utility
00003  * Copyright (C) 2012 Bolton Software Ltd.
00004  * Copyright (C) 2002 Chris Schoeneman
00005  * 
00006  * This package is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * found in the file COPYING that should have accompanied this file.
00009  * 
00010  * This package is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00017  */
00018 
00019 #include "CXWindowsClipboard.h"
00020 #include "CXWindowsClipboardTextConverter.h"
00021 #include "CXWindowsClipboardUCS2Converter.h"
00022 #include "CXWindowsClipboardUTF8Converter.h"
00023 #include "CXWindowsClipboardHTMLConverter.h"
00024 #include "CXWindowsClipboardBMPConverter.h"
00025 #include "CXWindowsUtil.h"
00026 #include "CThread.h"
00027 #include "CLog.h"
00028 #include "CStopwatch.h"
00029 #include "CArch.h"
00030 #include "stdvector.h"
00031 #include <cstdio>
00032 #include <X11/Xatom.h>
00033 
00034 //
00035 // CXWindowsClipboard
00036 //
00037 
00038 CXWindowsClipboard::CXWindowsClipboard(Display* display,
00039                 Window window, ClipboardID id) :
00040     m_display(display),
00041     m_window(window),
00042     m_id(id),
00043     m_open(false),
00044     m_time(0),
00045     m_owner(false),
00046     m_timeOwned(0),
00047     m_timeLost(0)
00048 {
00049     // get some atoms
00050     m_atomTargets         = XInternAtom(m_display, "TARGETS", False);
00051     m_atomMultiple        = XInternAtom(m_display, "MULTIPLE", False);
00052     m_atomTimestamp       = XInternAtom(m_display, "TIMESTAMP", False);
00053     m_atomInteger         = XInternAtom(m_display, "INTEGER", False);
00054     m_atomAtom            = XInternAtom(m_display, "ATOM", False);
00055     m_atomAtomPair        = XInternAtom(m_display, "ATOM_PAIR", False);
00056     m_atomData            = XInternAtom(m_display, "CLIP_TEMPORARY", False);
00057     m_atomINCR            = XInternAtom(m_display, "INCR", False);
00058     m_atomMotifClipLock   = XInternAtom(m_display, "_MOTIF_CLIP_LOCK", False);
00059     m_atomMotifClipHeader = XInternAtom(m_display, "_MOTIF_CLIP_HEADER", False);
00060     m_atomMotifClipAccess = XInternAtom(m_display,
00061                                 "_MOTIF_CLIP_LOCK_ACCESS_VALID", False);
00062     m_atomGDKSelection    = XInternAtom(m_display, "GDK_SELECTION", False);
00063 
00064     // set selection atom based on clipboard id
00065     switch (id) {
00066     case kClipboardClipboard:
00067         m_selection = XInternAtom(m_display, "CLIPBOARD", False);
00068         break;
00069 
00070     case kClipboardSelection:
00071     default:
00072         m_selection = XA_PRIMARY;
00073         break;
00074     }
00075 
00076     // add converters, most desired first
00077     m_converters.push_back(new CXWindowsClipboardHTMLConverter(m_display,
00078                                 "text/html"));
00079     m_converters.push_back(new CXWindowsClipboardBMPConverter(m_display));
00080     m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display,
00081                                 "text/plain;charset=UTF-8"));
00082     m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display,
00083                                 "UTF8_STRING"));
00084     m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display,
00085                                 "text/plain;charset=ISO-10646-UCS-2"));
00086     m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display,
00087                                 "text/unicode"));
00088     m_converters.push_back(new CXWindowsClipboardTextConverter(m_display,
00089                                 "text/plain"));
00090     m_converters.push_back(new CXWindowsClipboardTextConverter(m_display,
00091                                 "STRING"));
00092 
00093     // we have no data
00094     clearCache();
00095 }
00096 
00097 CXWindowsClipboard::~CXWindowsClipboard()
00098 {
00099     clearReplies();
00100     clearConverters();
00101 }
00102 
00103 void
00104 CXWindowsClipboard::lost(Time time)
00105 {
00106     LOG((CLOG_DEBUG "lost clipboard %d ownership at %d", m_id, time));
00107     if (m_owner) {
00108         m_owner    = false;
00109         m_timeLost = time;
00110         clearCache();
00111     }
00112 }
00113 
00114 void
00115 CXWindowsClipboard::addRequest(Window owner, Window requestor,
00116                 Atom target, ::Time time, Atom property)
00117 {
00118     // must be for our window and we must have owned the selection
00119     // at the given time.
00120     bool success = false;
00121     if (owner == m_window) {
00122         LOG((CLOG_DEBUG1 "request for clipboard %d, target %s by 0x%08x (property=%s)", m_selection, CXWindowsUtil::atomToString(m_display, target).c_str(), requestor, CXWindowsUtil::atomToString(m_display, property).c_str()));
00123         if (wasOwnedAtTime(time)) {
00124             if (target == m_atomMultiple) {
00125                 // add a multiple request.  property may not be None
00126                 // according to ICCCM.
00127                 if (property != None) {
00128                     success = insertMultipleReply(requestor, time, property);
00129                 }
00130             }
00131             else {
00132                 addSimpleRequest(requestor, target, time, property);
00133 
00134                 // addSimpleRequest() will have already handled failure
00135                 success = true;
00136             }
00137         }
00138         else {
00139             LOG((CLOG_DEBUG1 "failed, not owned at time %d", time));
00140         }
00141     }
00142 
00143     if (!success) {
00144         // send failure
00145         LOG((CLOG_DEBUG1 "failed"));
00146         insertReply(new CReply(requestor, target, time));
00147     }
00148 
00149     // send notifications that are pending
00150     pushReplies();
00151 }
00152 
00153 bool
00154 CXWindowsClipboard::addSimpleRequest(Window requestor,
00155                 Atom target, ::Time time, Atom property)
00156 {
00157     // obsolete requestors may supply a None property.  in
00158     // that case we use the target as the property to store
00159     // the conversion.
00160     if (property == None) {
00161         property = target;
00162     }
00163 
00164     // handle targets
00165     CString data;
00166     Atom type  = None;
00167     int format = 0;
00168     if (target == m_atomTargets) {
00169         type = getTargetsData(data, &format);
00170     }
00171     else if (target == m_atomTimestamp) {
00172         type = getTimestampData(data, &format);
00173     }
00174     else {
00175         IXWindowsClipboardConverter* converter = getConverter(target);
00176         if (converter != NULL) {
00177             IClipboard::EFormat clipboardFormat = converter->getFormat();
00178             if (m_added[clipboardFormat]) {
00179                 try {
00180                     data   = converter->fromIClipboard(m_data[clipboardFormat]);
00181                     format = converter->getDataSize();
00182                     type   = converter->getAtom();
00183                 }
00184                 catch (...) {
00185                     // ignore -- cannot convert
00186                 }
00187             }
00188         }
00189     }
00190 
00191     if (type != None) {
00192         // success
00193         LOG((CLOG_DEBUG1 "success"));
00194         insertReply(new CReply(requestor, target, time,
00195                                 property, data, type, format));
00196         return true;
00197     }
00198     else {
00199         // failure
00200         LOG((CLOG_DEBUG1 "failed"));
00201         insertReply(new CReply(requestor, target, time));
00202         return false;
00203     }
00204 }
00205 
00206 bool
00207 CXWindowsClipboard::processRequest(Window requestor,
00208                 ::Time /*time*/, Atom property)
00209 {
00210     CReplyMap::iterator index = m_replies.find(requestor);
00211     if (index == m_replies.end()) {
00212         // unknown requestor window
00213         return false;
00214     }
00215     LOG((CLOG_DEBUG1 "received property %s delete from 0x08%x", CXWindowsUtil::atomToString(m_display, property).c_str(), requestor));
00216 
00217     // find the property in the known requests.  it should be the
00218     // first property but we'll check 'em all if we have to.
00219     CReplyList& replies = index->second;
00220     for (CReplyList::iterator index2 = replies.begin();
00221                                 index2 != replies.end(); ++index2) {
00222         CReply* reply = *index2;
00223         if (reply->m_replied && reply->m_property == property) {
00224             // if reply is complete then remove it and start the
00225             // next one.
00226             pushReplies(index, replies, index2);
00227             return true;
00228         }
00229     }
00230 
00231     return false;
00232 }
00233 
00234 bool
00235 CXWindowsClipboard::destroyRequest(Window requestor)
00236 {
00237     CReplyMap::iterator index = m_replies.find(requestor);
00238     if (index == m_replies.end()) {
00239         // unknown requestor window
00240         return false;
00241     }
00242 
00243     // destroy all replies for this window
00244     clearReplies(index->second);
00245     m_replies.erase(index);
00246 
00247     // note -- we don't stop watching the window for events because
00248     // we're called in response to the window being destroyed.
00249 
00250     return true;
00251 }
00252 
00253 Window
00254 CXWindowsClipboard::getWindow() const
00255 {
00256     return m_window;
00257 }
00258 
00259 Atom
00260 CXWindowsClipboard::getSelection() const
00261 {
00262     return m_selection;
00263 }
00264 
00265 bool
00266 CXWindowsClipboard::empty()
00267 {
00268     assert(m_open);
00269 
00270     LOG((CLOG_DEBUG "empty clipboard %d", m_id));
00271 
00272     // assert ownership of clipboard
00273     XSetSelectionOwner(m_display, m_selection, m_window, m_time);
00274     if (XGetSelectionOwner(m_display, m_selection) != m_window) {
00275         LOG((CLOG_DEBUG "failed to grab clipboard %d", m_id));
00276         return false;
00277     }
00278 
00279     // clear all data.  since we own the data now, the cache is up
00280     // to date.
00281     clearCache();
00282     m_cached = true;
00283 
00284     // FIXME -- actually delete motif clipboard items?
00285     // FIXME -- do anything to motif clipboard properties?
00286 
00287     // save time
00288     m_timeOwned = m_time;
00289     m_timeLost  = 0;
00290 
00291     // we're the owner now
00292     m_owner = true;
00293     LOG((CLOG_DEBUG "grabbed clipboard %d", m_id));
00294 
00295     return true;
00296 }
00297 
00298 void
00299 CXWindowsClipboard::add(EFormat format, const CString& data)
00300 {
00301     assert(m_open);
00302     assert(m_owner);
00303 
00304     LOG((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format));
00305 
00306     m_data[format]  = data;
00307     m_added[format] = true;
00308 
00309     // FIXME -- set motif clipboard item?
00310 }
00311 
00312 bool
00313 CXWindowsClipboard::open(Time time) const
00314 {
00315     assert(!m_open);
00316 
00317     LOG((CLOG_DEBUG "open clipboard %d", m_id));
00318 
00319     // assume not motif
00320     m_motif = false;
00321 
00322     // lock clipboard
00323     if (m_id == kClipboardClipboard) {
00324         if (!motifLockClipboard()) {
00325             return false;
00326         }
00327 
00328         // check if motif owns the selection.  unlock motif clipboard
00329         // if it does not.
00330         m_motif = motifOwnsClipboard();
00331         LOG((CLOG_DEBUG1 "motif does %sown clipboard", m_motif ? "" : "not "));
00332         if (!m_motif) {
00333             motifUnlockClipboard();
00334         }
00335     }
00336 
00337     // now open
00338     m_open = true;
00339     m_time = time;
00340 
00341     // be sure to flush the cache later if it's dirty
00342     m_checkCache = true;
00343 
00344     return true;
00345 }
00346 
00347 void
00348 CXWindowsClipboard::close() const
00349 {
00350     assert(m_open);
00351 
00352     LOG((CLOG_DEBUG "close clipboard %d", m_id));
00353 
00354     // unlock clipboard
00355     if (m_motif) {
00356         motifUnlockClipboard();
00357     }
00358 
00359     m_motif = false;
00360     m_open  = false;
00361 }
00362 
00363 IClipboard::Time
00364 CXWindowsClipboard::getTime() const
00365 {
00366     checkCache();
00367     return m_timeOwned;
00368 }
00369 
00370 bool
00371 CXWindowsClipboard::has(EFormat format) const
00372 {
00373     assert(m_open);
00374 
00375     fillCache();
00376     return m_added[format];
00377 }
00378 
00379 CString
00380 CXWindowsClipboard::get(EFormat format) const
00381 {
00382     assert(m_open);
00383 
00384     fillCache();
00385     return m_data[format];
00386 }
00387 
00388 void
00389 CXWindowsClipboard::clearConverters()
00390 {
00391     for (ConverterList::iterator index = m_converters.begin();
00392                                 index != m_converters.end(); ++index) {
00393         delete *index;
00394     }
00395     m_converters.clear();
00396 }
00397 
00398 IXWindowsClipboardConverter*
00399 CXWindowsClipboard::getConverter(Atom target, bool onlyIfNotAdded) const
00400 {
00401     IXWindowsClipboardConverter* converter = NULL;
00402     for (ConverterList::const_iterator index = m_converters.begin();
00403                                 index != m_converters.end(); ++index) {
00404         converter = *index;
00405         if (converter->getAtom() == target) {
00406             break;
00407         }
00408     }
00409     if (converter == NULL) {
00410         LOG((CLOG_DEBUG1 "  no converter for target %s", CXWindowsUtil::atomToString(m_display, target).c_str()));
00411         return NULL;
00412     }
00413 
00414     // optionally skip already handled targets
00415     if (onlyIfNotAdded) {
00416         if (m_added[converter->getFormat()]) {
00417             LOG((CLOG_DEBUG1 "  skipping handled format %d", converter->getFormat()));
00418             return NULL;
00419         }
00420     }
00421 
00422     return converter;
00423 }
00424 
00425 void
00426 CXWindowsClipboard::checkCache() const
00427 {
00428     if (!m_checkCache) {
00429         return;
00430     }
00431     m_checkCache = false;
00432 
00433     // get the time the clipboard ownership was taken by the current
00434     // owner.
00435     if (m_motif) {
00436         m_timeOwned = motifGetTime();
00437     }
00438     else {
00439         m_timeOwned = icccmGetTime();
00440     }
00441 
00442     // if we can't get the time then use the time passed to us
00443     if (m_timeOwned == 0) {
00444         m_timeOwned = m_time;
00445     }
00446 
00447     // if the cache is dirty then flush it
00448     if (m_timeOwned != m_cacheTime) {
00449         clearCache();
00450     }
00451 }
00452 
00453 void
00454 CXWindowsClipboard::clearCache() const
00455 {
00456     const_cast<CXWindowsClipboard*>(this)->doClearCache();
00457 }
00458 
00459 void
00460 CXWindowsClipboard::doClearCache()
00461 {
00462     m_checkCache = false;
00463     m_cached     = false;
00464     for (SInt32 index = 0; index < kNumFormats; ++index) {
00465         m_data[index]  = "";
00466         m_added[index] = false;
00467     }
00468 }
00469 
00470 void
00471 CXWindowsClipboard::fillCache() const
00472 {
00473     // get the selection data if not already cached
00474     checkCache();
00475     if (!m_cached) {
00476         const_cast<CXWindowsClipboard*>(this)->doFillCache();
00477     }
00478 }
00479 
00480 void
00481 CXWindowsClipboard::doFillCache()
00482 {
00483     if (m_motif) {
00484         motifFillCache();
00485     }
00486     else {
00487         icccmFillCache();
00488     }
00489     m_checkCache = false;
00490     m_cached     = true;
00491     m_cacheTime  = m_timeOwned;
00492 }
00493 
00494 void
00495 CXWindowsClipboard::icccmFillCache()
00496 {
00497     LOG((CLOG_DEBUG "ICCCM fill clipboard %d", m_id));
00498 
00499     // see if we can get the list of available formats from the selection.
00500     // if not then use a default list of formats.  note that some clipboard
00501     // owners are broken and report TARGETS as the type of the TARGETS data
00502     // instead of the correct type ATOM;  allow either.
00503     const Atom atomTargets = m_atomTargets;
00504     Atom target;
00505     CString data;
00506     if (!icccmGetSelection(atomTargets, &target, &data) ||
00507         (target != m_atomAtom && target != m_atomTargets)) {
00508         LOG((CLOG_DEBUG1 "selection doesn't support TARGETS"));
00509         data = "";
00510         CXWindowsUtil::appendAtomData(data, XA_STRING);
00511     }
00512 
00513     CXWindowsUtil::convertAtomProperty(data);
00514     const Atom* targets = reinterpret_cast<const Atom*>(data.data());
00515     const UInt32 numTargets = data.size() / sizeof(Atom);
00516     LOG((CLOG_DEBUG "  available targets: %s", CXWindowsUtil::atomsToString(m_display, targets, numTargets).c_str()));
00517 
00518     // try each converter in order (because they're in order of
00519     // preference).
00520     for (ConverterList::const_iterator index = m_converters.begin();
00521                                 index != m_converters.end(); ++index) {
00522         IXWindowsClipboardConverter* converter = *index;
00523 
00524         // skip already handled targets
00525         if (m_added[converter->getFormat()]) {
00526             continue;
00527         }
00528 
00529         // see if atom is in target list
00530         Atom target = None;
00531         // XXX -- just ask for the converter's target to see if it's
00532         // available rather than checking TARGETS.  i've seen clipboard
00533         // owners that don't report all the targets they support.
00534         target = converter->getAtom();
00535         /*
00536         for (UInt32 i = 0; i < numTargets; ++i) {
00537             if (converter->getAtom() == targets[i]) {
00538                 target = targets[i];
00539                 break;
00540             }
00541         }
00542         */
00543         if (target == None) {
00544             continue;
00545         }
00546 
00547         // get the data
00548         Atom actualTarget;
00549         CString targetData;
00550         if (!icccmGetSelection(target, &actualTarget, &targetData)) {
00551             LOG((CLOG_DEBUG1 "  no data for target %s", CXWindowsUtil::atomToString(m_display, target).c_str()));
00552             continue;
00553         }
00554 
00555         // add to clipboard and note we've done it
00556         IClipboard::EFormat format = converter->getFormat();
00557         m_data[format]  = converter->toIClipboard(targetData);
00558         m_added[format] = true;
00559         LOG((CLOG_DEBUG "  added format %d for target %s (%u %s)", format, CXWindowsUtil::atomToString(m_display, target).c_str(), targetData.size(), targetData.size() == 1 ? "byte" : "bytes"));
00560     }
00561 }
00562 
00563 bool
00564 CXWindowsClipboard::icccmGetSelection(Atom target,
00565                 Atom* actualTarget, CString* data) const
00566 {
00567     assert(actualTarget != NULL);
00568     assert(data         != NULL);
00569 
00570     // request data conversion
00571     CICCCMGetClipboard getter(m_window, m_time, m_atomData);
00572     if (!getter.readClipboard(m_display, m_selection,
00573                                 target, actualTarget, data)) {
00574         LOG((CLOG_DEBUG1 "can't get data for selection target %s", CXWindowsUtil::atomToString(m_display, target).c_str()));
00575         LOGC(getter.m_error, (CLOG_WARN "ICCCM violation by clipboard owner"));
00576         return false;
00577     }
00578     else if (*actualTarget == None) {
00579         LOG((CLOG_DEBUG1 "selection conversion failed for target %s", CXWindowsUtil::atomToString(m_display, target).c_str()));
00580         return false;
00581     }
00582     return true;
00583 }
00584 
00585 IClipboard::Time
00586 CXWindowsClipboard::icccmGetTime() const
00587 {
00588     Atom actualTarget;
00589     CString data;
00590     if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) &&
00591         actualTarget == m_atomInteger) {
00592         Time time = *reinterpret_cast<const Time*>(data.data());
00593         LOG((CLOG_DEBUG1 "got ICCCM time %d", time));
00594         return time;
00595     }
00596     else {
00597         // no timestamp
00598         LOG((CLOG_DEBUG1 "can't get ICCCM time"));
00599         return 0;
00600     }
00601 }
00602 
00603 bool
00604 CXWindowsClipboard::motifLockClipboard() const
00605 {
00606     // fail if anybody owns the lock (even us, so this is non-recursive)
00607     Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
00608     if (lockOwner != None) {
00609         LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner));
00610         return false;
00611     }
00612 
00613     // try to grab the lock
00614     // FIXME -- is this right?  there's a race condition here --
00615     // A grabs successfully, B grabs successfully, A thinks it
00616     // still has the grab until it gets a SelectionClear.
00617     Time time = CXWindowsUtil::getCurrentTime(m_display, m_window);
00618     XSetSelectionOwner(m_display, m_atomMotifClipLock, m_window, time);
00619     lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
00620     if (lockOwner != m_window) {
00621         LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner));
00622         return false;
00623     }
00624 
00625     LOG((CLOG_DEBUG1 "locked motif clipboard"));
00626     return true;
00627 }
00628 
00629 void
00630 CXWindowsClipboard::motifUnlockClipboard() const
00631 {
00632     LOG((CLOG_DEBUG1 "unlocked motif clipboard"));
00633 
00634     // fail if we don't own the lock
00635     Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
00636     if (lockOwner != m_window) {
00637         return;
00638     }
00639 
00640     // release lock
00641     Time time = CXWindowsUtil::getCurrentTime(m_display, m_window);
00642     XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time);
00643 }
00644 
00645 bool
00646 CXWindowsClipboard::motifOwnsClipboard() const
00647 {
00648     // get the current selection owner
00649     // FIXME -- this can't be right.  even if the window is destroyed
00650     // Motif will still have a valid clipboard.  how can we tell if
00651     // some other client owns CLIPBOARD?
00652     Window owner = XGetSelectionOwner(m_display, m_selection);
00653     if (owner == None) {
00654         return false;
00655     }
00656 
00657     // get the Motif clipboard header property from the root window
00658     Atom target;
00659     SInt32 format;
00660     CString data;
00661     Window root = RootWindow(m_display, DefaultScreen(m_display));
00662     if (!CXWindowsUtil::getWindowProperty(m_display, root,
00663                                 m_atomMotifClipHeader,
00664                                 &data, &target, &format, False)) {
00665         return false;
00666     }
00667 
00668     // check the owner window against the current clipboard owner
00669     const CMotifClipHeader* header =
00670                         reinterpret_cast<const CMotifClipHeader*>(data.data());
00671     if (data.size() >= sizeof(CMotifClipHeader) &&
00672         header->m_id == kMotifClipHeader) {
00673         if (static_cast<Window>(header->m_selectionOwner) == owner) {
00674             return true;
00675         }
00676     }
00677 
00678     return false;
00679 }
00680 
00681 void
00682 CXWindowsClipboard::motifFillCache()
00683 {
00684     LOG((CLOG_DEBUG "Motif fill clipboard %d", m_id));
00685 
00686     // get the Motif clipboard header property from the root window
00687     Atom target;
00688     SInt32 format;
00689     CString data;
00690     Window root = RootWindow(m_display, DefaultScreen(m_display));
00691     if (!CXWindowsUtil::getWindowProperty(m_display, root,
00692                                 m_atomMotifClipHeader,
00693                                 &data, &target, &format, False)) {
00694         return;
00695     }
00696 
00697     // check that the header is okay
00698     const CMotifClipHeader* header =
00699                         reinterpret_cast<const CMotifClipHeader*>(data.data());
00700     if (data.size() < sizeof(CMotifClipHeader) ||
00701         header->m_id != kMotifClipHeader ||
00702         header->m_numItems < 1) {
00703         return;
00704     }
00705 
00706     // get the Motif item property from the root window
00707     char name[18 + 20];
00708     sprintf(name, "_MOTIF_CLIP_ITEM_%d", header->m_item);
00709     Atom atomItem = XInternAtom(m_display, name, False);
00710     data = "";
00711     if (!CXWindowsUtil::getWindowProperty(m_display, root,
00712                                 atomItem, &data,
00713                                 &target, &format, False)) {
00714         return;
00715     }
00716 
00717     // check that the item is okay
00718     const CMotifClipItem* item =
00719                     reinterpret_cast<const CMotifClipItem*>(data.data());
00720     if (data.size() < sizeof(CMotifClipItem) ||
00721         item->m_id != kMotifClipItem ||
00722         item->m_numFormats - item->m_numDeletedFormats < 1) {
00723         return;
00724     }
00725 
00726     // format list is after static item structure elements
00727     const SInt32 numFormats = item->m_numFormats - item->m_numDeletedFormats;
00728     const SInt32* formats   = reinterpret_cast<const SInt32*>(item->m_size +
00729                                 reinterpret_cast<const char*>(data.data()));
00730 
00731     // get the available formats
00732     typedef std::map<Atom, CString> CMotifFormatMap;
00733     CMotifFormatMap motifFormats;
00734     for (SInt32 i = 0; i < numFormats; ++i) {
00735         // get Motif format property from the root window
00736         sprintf(name, "_MOTIF_CLIP_ITEM_%d", formats[i]);
00737         Atom atomFormat = XInternAtom(m_display, name, False);
00738         CString data;
00739         if (!CXWindowsUtil::getWindowProperty(m_display, root,
00740                                     atomFormat, &data,
00741                                     &target, &format, False)) {
00742             continue;
00743         }
00744 
00745         // check that the format is okay
00746         const CMotifClipFormat* motifFormat =
00747                         reinterpret_cast<const CMotifClipFormat*>(data.data());
00748         if (data.size() < sizeof(CMotifClipFormat) ||
00749             motifFormat->m_id != kMotifClipFormat ||
00750             motifFormat->m_length < 0 ||
00751             motifFormat->m_type == None ||
00752             motifFormat->m_deleted != 0) {
00753             continue;
00754         }
00755 
00756         // save it
00757         motifFormats.insert(std::make_pair(motifFormat->m_type, data));
00758     }
00759     //const UInt32 numMotifFormats = motifFormats.size();
00760 
00761     // try each converter in order (because they're in order of
00762     // preference).
00763     for (ConverterList::const_iterator index = m_converters.begin();
00764                                 index != m_converters.end(); ++index) {
00765         IXWindowsClipboardConverter* converter = *index;
00766 
00767         // skip already handled targets
00768         if (m_added[converter->getFormat()]) {
00769             continue;
00770         }
00771 
00772         // see if atom is in target list
00773         CMotifFormatMap::const_iterator index2 =
00774                                 motifFormats.find(converter->getAtom());
00775         if (index2 == motifFormats.end()) {
00776             continue;
00777         }
00778 
00779         // get format
00780         const CMotifClipFormat* motifFormat =
00781                                 reinterpret_cast<const CMotifClipFormat*>(
00782                                     index2->second.data());
00783         const Atom target                   = motifFormat->m_type;
00784 
00785         // get the data (finally)
00786         Atom actualTarget;
00787         CString targetData;
00788         if (!motifGetSelection(motifFormat, &actualTarget, &targetData)) {
00789             LOG((CLOG_DEBUG1 "  no data for target %s", CXWindowsUtil::atomToString(m_display, target).c_str()));
00790             continue;
00791         }
00792 
00793         // add to clipboard and note we've done it
00794         IClipboard::EFormat format = converter->getFormat();
00795         m_data[format]  = converter->toIClipboard(targetData);
00796         m_added[format] = true;
00797         LOG((CLOG_DEBUG "  added format %d for target %s", format, CXWindowsUtil::atomToString(m_display, target).c_str()));
00798     }
00799 }
00800 
00801 bool
00802 CXWindowsClipboard::motifGetSelection(const CMotifClipFormat* format,
00803                             Atom* actualTarget, CString* data) const
00804 {
00805     // if the current clipboard owner and the owner indicated by the
00806     // motif clip header are the same then transfer via a property on
00807     // the root window, otherwise transfer as a normal ICCCM client.
00808     if (!motifOwnsClipboard()) {
00809         return icccmGetSelection(format->m_type, actualTarget, data);
00810     }
00811 
00812     // use motif way
00813     // FIXME -- this isn't right.  it'll only work if the data is
00814     // already stored on the root window and only if it fits in a
00815     // property.  motif has some scheme for transferring part by
00816     // part that i don't know.
00817     char name[18 + 20];
00818     sprintf(name, "_MOTIF_CLIP_ITEM_%d", format->m_data);
00819     Atom target = XInternAtom(m_display, name, False);
00820     Window root = RootWindow(m_display, DefaultScreen(m_display));
00821     return CXWindowsUtil::getWindowProperty(m_display, root,
00822                                 target, data,
00823                                 actualTarget, NULL, False);
00824 }
00825 
00826 IClipboard::Time
00827 CXWindowsClipboard::motifGetTime() const
00828 {
00829     return icccmGetTime();
00830 }
00831 
00832 bool
00833 CXWindowsClipboard::insertMultipleReply(Window requestor,
00834                 ::Time time, Atom property)
00835 {
00836     // get the requested targets
00837     Atom target;
00838     SInt32 format;
00839     CString data;
00840     if (!CXWindowsUtil::getWindowProperty(m_display, requestor,
00841                                 property, &data, &target, &format, False)) {
00842         // can't get the requested targets
00843         return false;
00844     }
00845 
00846     // fail if the requested targets isn't of the correct form
00847     if (format != 32 || target != m_atomAtomPair) {
00848         return false;
00849     }
00850 
00851     // data is a list of atom pairs:  target, property
00852     CXWindowsUtil::convertAtomProperty(data);
00853     const Atom* targets = reinterpret_cast<const Atom*>(data.data());
00854     const UInt32 numTargets = data.size() / sizeof(Atom);
00855 
00856     // add replies for each target
00857     bool changed = false;
00858     for (UInt32 i = 0; i < numTargets; i += 2) {
00859         const Atom target   = targets[i + 0];
00860         const Atom property = targets[i + 1];
00861         if (!addSimpleRequest(requestor, target, time, property)) {
00862             // note that we can't perform the requested conversion
00863             CXWindowsUtil::replaceAtomData(data, i, None);
00864             changed = true;
00865         }
00866     }
00867 
00868     // update the targets property if we changed it
00869     if (changed) {
00870         CXWindowsUtil::setWindowProperty(m_display, requestor,
00871                                 property, data.data(), data.size(),
00872                                 target, format);
00873     }
00874 
00875     // add reply for MULTIPLE request
00876     insertReply(new CReply(requestor, m_atomMultiple,
00877                                 time, property, CString(), None, 32));
00878 
00879     return true;
00880 }
00881 
00882 void
00883 CXWindowsClipboard::insertReply(CReply* reply)
00884 {
00885     assert(reply != NULL);
00886 
00887     // note -- we must respond to requests in order if requestor,target,time
00888     // are the same, otherwise we can use whatever order we like with one
00889     // exception:  each reply in a MULTIPLE reply must be handled in order
00890     // as well.  those replies will almost certainly not share targets so
00891     // we can't simply use requestor,target,time as map index.
00892     //
00893     // instead we'll use just the requestor.  that's more restrictive than
00894     // necessary but we're guaranteed to do things in the right order.
00895     // note that we could also include the time in the map index and still
00896     // ensure the right order.  but since that'll just make it harder to
00897     // find the right reply when handling property notify events we stick
00898     // to just the requestor.
00899 
00900     const bool newWindow = (m_replies.count(reply->m_requestor) == 0);
00901     m_replies[reply->m_requestor].push_back(reply);
00902 
00903     // adjust requestor's event mask if we haven't done so already.  we
00904     // want events in case the window is destroyed or any of its
00905     // properties change.
00906     if (newWindow) {
00907         // note errors while we adjust event masks
00908         bool error = false;
00909         {
00910             CXWindowsUtil::CErrorLock lock(m_display, &error);
00911 
00912             // get and save the current event mask
00913             XWindowAttributes attr;
00914             XGetWindowAttributes(m_display, reply->m_requestor, &attr);
00915             m_eventMasks[reply->m_requestor] = attr.your_event_mask;
00916 
00917             // add the events we want
00918             XSelectInput(m_display, reply->m_requestor, attr.your_event_mask |
00919                                     StructureNotifyMask | PropertyChangeMask);
00920         }
00921 
00922         // if we failed then the window has already been destroyed
00923         if (error) {
00924             m_replies.erase(reply->m_requestor);
00925             delete reply;
00926         }
00927     }
00928 }
00929 
00930 void
00931 CXWindowsClipboard::pushReplies()
00932 {
00933     // send the first reply for each window if that reply hasn't
00934     // been sent yet.
00935     for (CReplyMap::iterator index = m_replies.begin();
00936                                 index != m_replies.end(); ) {
00937         assert(!index->second.empty());
00938         if (!index->second.front()->m_replied) {
00939             pushReplies(index, index->second, index->second.begin());
00940         }
00941         else {
00942             ++index;
00943         }
00944     }
00945 }
00946 
00947 void
00948 CXWindowsClipboard::pushReplies(CReplyMap::iterator& mapIndex,
00949                 CReplyList& replies, CReplyList::iterator index)
00950 {
00951     CReply* reply = *index;
00952     while (sendReply(reply)) {
00953         // reply is complete.  discard it and send the next reply,
00954         // if any.
00955         index = replies.erase(index);
00956         delete reply;
00957         if (index == replies.end()) {
00958             break;
00959         }
00960         reply = *index;
00961     }
00962 
00963     // if there are no more replies in the list then remove the list
00964     // and stop watching the requestor for events.
00965     if (replies.empty()) {
00966         CXWindowsUtil::CErrorLock lock(m_display);
00967         Window requestor = mapIndex->first;
00968         XSelectInput(m_display, requestor, m_eventMasks[requestor]);
00969         m_replies.erase(mapIndex++);
00970         m_eventMasks.erase(requestor);
00971     }
00972     else {
00973         ++mapIndex;
00974     }
00975 }
00976 
00977 bool
00978 CXWindowsClipboard::sendReply(CReply* reply)
00979 {
00980     assert(reply != NULL);
00981 
00982     // bail out immediately if reply is done
00983     if (reply->m_done) {
00984         LOG((CLOG_DEBUG1 "clipboard: finished reply to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
00985         return true;
00986     }
00987 
00988     // start in failed state if property is None
00989     bool failed = (reply->m_property == None);
00990     if (!failed) {
00991         LOG((CLOG_DEBUG1 "clipboard: setting property on 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
00992 
00993         // send using INCR if already sending incrementally or if reply
00994         // is too large, otherwise just send it.
00995         const UInt32 maxRequestSize = 3 * XMaxRequestSize(m_display);
00996         const bool useINCR = (reply->m_data.size() > maxRequestSize);
00997 
00998         // send INCR reply if incremental and we haven't replied yet
00999         if (useINCR && !reply->m_replied) {
01000             UInt32 size = reply->m_data.size();
01001             if (!CXWindowsUtil::setWindowProperty(m_display,
01002                                 reply->m_requestor, reply->m_property,
01003                                 &size, 4, m_atomINCR, 32)) {
01004                 failed = true;
01005             }
01006         }
01007 
01008         // send more INCR reply or entire non-incremental reply
01009         else {
01010             // how much more data should we send?
01011             UInt32 size = reply->m_data.size() - reply->m_ptr;
01012             if (size > maxRequestSize)
01013                 size = maxRequestSize;
01014 
01015             // send it
01016             if (!CXWindowsUtil::setWindowProperty(m_display,
01017                                 reply->m_requestor, reply->m_property,
01018                                 reply->m_data.data() + reply->m_ptr,
01019                                 size,
01020                                 reply->m_type, reply->m_format)) {
01021                 failed = true;
01022             }
01023             else {
01024                 reply->m_ptr += size;
01025 
01026                 // we've finished the reply if we just sent the zero
01027                 // size incremental chunk or if we're not incremental.
01028                 reply->m_done = (size == 0 || !useINCR);
01029             }
01030         }
01031     }
01032 
01033     // if we've failed then delete the property and say we're done.
01034     // if we haven't replied yet then we can send a failure notify,
01035     // otherwise we've failed in the middle of an incremental
01036     // transfer;  i don't know how to cancel that so i'll just send
01037     // the final zero-length property.
01038     // FIXME -- how do you gracefully cancel an incremental transfer?
01039     if (failed) {
01040         LOG((CLOG_DEBUG1 "clipboard: sending failure to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
01041         reply->m_done = true;
01042         if (reply->m_property != None) {
01043             CXWindowsUtil::CErrorLock lock(m_display);
01044             XDeleteProperty(m_display, reply->m_requestor, reply->m_property);
01045         }
01046 
01047         if (!reply->m_replied) {
01048             sendNotify(reply->m_requestor, m_selection,
01049                                 reply->m_target, None,
01050                                 reply->m_time);
01051 
01052             // don't wait for any reply (because we're not expecting one)
01053             return true;
01054         }
01055         else {
01056             static const char dummy = 0;
01057             CXWindowsUtil::setWindowProperty(m_display,
01058                                 reply->m_requestor, reply->m_property,
01059                                 &dummy,
01060                                 0,
01061                                 reply->m_type, reply->m_format);
01062 
01063             // wait for delete notify
01064             return false;
01065         }
01066     }
01067 
01068     // send notification if we haven't yet
01069     if (!reply->m_replied) {
01070         LOG((CLOG_DEBUG1 "clipboard: sending notify to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
01071         reply->m_replied = true;
01072 
01073         // dump every property on the requestor window to the debug2
01074         // log.  we've seen what appears to be a bug in lesstif and
01075         // knowing the properties may help design a workaround, if
01076         // it becomes necessary.
01077         if (CLOG->getFilter() >= kDEBUG2) {
01078             CXWindowsUtil::CErrorLock lock(m_display);
01079             int n;
01080             Atom* props = XListProperties(m_display, reply->m_requestor, &n);
01081             LOG((CLOG_DEBUG2 "properties of 0x%08x:", reply->m_requestor));
01082             for (int i = 0; i < n; ++i) {
01083                 Atom target;
01084                 CString data;
01085                 char* name = XGetAtomName(m_display, props[i]);
01086                 if (!CXWindowsUtil::getWindowProperty(m_display,
01087                                 reply->m_requestor,
01088                                 props[i], &data, &target, NULL, False)) {
01089                     LOG((CLOG_DEBUG2 "  %s: <can't read property>", name));
01090                 }
01091                 else {
01092                     // if there are any non-ascii characters in string
01093                     // then print the binary data.
01094                     static const char* hex = "0123456789abcdef";
01095                     for (CString::size_type j = 0; j < data.size(); ++j) {
01096                         if (data[j] < 32 || data[j] > 126) {
01097                             CString tmp;
01098                             tmp.reserve(data.size() * 3);
01099                             for (j = 0; j < data.size(); ++j) {
01100                                 unsigned char v = (unsigned char)data[j];
01101                                 tmp += hex[v >> 16];
01102                                 tmp += hex[v & 15];
01103                                 tmp += ' ';
01104                             }
01105                             data = tmp;
01106                             break;
01107                         }
01108                     }
01109                     char* type = XGetAtomName(m_display, target);
01110                     LOG((CLOG_DEBUG2 "  %s (%s): %s", name, type, data.c_str()));
01111                     if (type != NULL) {
01112                         XFree(type);
01113                     }
01114                 }
01115                 if (name != NULL) {
01116                     XFree(name);
01117                 }
01118             }
01119             if (props != NULL) {
01120                 XFree(props);
01121             }
01122         }
01123 
01124         sendNotify(reply->m_requestor, m_selection,
01125                                 reply->m_target, reply->m_property,
01126                                 reply->m_time);
01127     }
01128 
01129     // wait for delete notify
01130     return false;
01131 }
01132 
01133 void
01134 CXWindowsClipboard::clearReplies()
01135 {
01136     for (CReplyMap::iterator index = m_replies.begin();
01137                                 index != m_replies.end(); ++index) {
01138         clearReplies(index->second);
01139     }
01140     m_replies.clear();
01141     m_eventMasks.clear();
01142 }
01143 
01144 void
01145 CXWindowsClipboard::clearReplies(CReplyList& replies)
01146 {
01147     for (CReplyList::iterator index = replies.begin();
01148                                 index != replies.end(); ++index) {
01149         delete *index;
01150     }
01151     replies.clear();
01152 }
01153 
01154 void
01155 CXWindowsClipboard::sendNotify(Window requestor,
01156                 Atom selection, Atom target, Atom property, Time time)
01157 {
01158     XEvent event;
01159     event.xselection.type      = SelectionNotify;
01160     event.xselection.display   = m_display;
01161     event.xselection.requestor = requestor;
01162     event.xselection.selection = selection;
01163     event.xselection.target    = target;
01164     event.xselection.property  = property;
01165     event.xselection.time      = time;
01166     CXWindowsUtil::CErrorLock lock(m_display);
01167     XSendEvent(m_display, requestor, False, 0, &event);
01168 }
01169 
01170 bool
01171 CXWindowsClipboard::wasOwnedAtTime(::Time time) const
01172 {
01173     // not owned if we've never owned the selection
01174     checkCache();
01175     if (m_timeOwned == 0) {
01176         return false;
01177     }
01178 
01179     // if time is CurrentTime then return true if we still own the
01180     // selection and false if we do not.  else if we still own the
01181     // selection then get the current time, otherwise use
01182     // m_timeLost as the end time.
01183     Time lost = m_timeLost;
01184     if (m_timeLost == 0) {
01185         if (time == CurrentTime) {
01186             return true;
01187         }
01188         else {
01189             lost = CXWindowsUtil::getCurrentTime(m_display, m_window);
01190         }
01191     }
01192     else {
01193         if (time == CurrentTime) {
01194             return false;
01195         }
01196     }
01197 
01198     // compare time to range
01199     Time duration = lost - m_timeOwned;
01200     Time when     = time - m_timeOwned;
01201     return (/*when >= 0 &&*/ when <= duration);
01202 }
01203 
01204 Atom
01205 CXWindowsClipboard::getTargetsData(CString& data, int* format) const
01206 {
01207     assert(format != NULL);
01208 
01209     // add standard targets
01210     CXWindowsUtil::appendAtomData(data, m_atomTargets);
01211     CXWindowsUtil::appendAtomData(data, m_atomMultiple);
01212     CXWindowsUtil::appendAtomData(data, m_atomTimestamp);
01213 
01214     // add targets we can convert to
01215     for (ConverterList::const_iterator index = m_converters.begin();
01216                                 index != m_converters.end(); ++index) {
01217         IXWindowsClipboardConverter* converter = *index;
01218 
01219         // skip formats we don't have
01220         if (m_added[converter->getFormat()]) {
01221             CXWindowsUtil::appendAtomData(data, converter->getAtom());
01222         }
01223     }
01224 
01225     *format = 32;
01226     return m_atomAtom;
01227 }
01228 
01229 Atom
01230 CXWindowsClipboard::getTimestampData(CString& data, int* format) const
01231 {
01232     assert(format != NULL);
01233 
01234     checkCache();
01235     CXWindowsUtil::appendTimeData(data, m_timeOwned);
01236     *format = 32;
01237     return m_atomInteger;
01238 }
01239 
01240 
01241 //
01242 // CXWindowsClipboard::CICCCMGetClipboard
01243 //
01244 
01245 CXWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard(
01246                 Window requestor, Time time, Atom property) :
01247     m_requestor(requestor),
01248     m_time(time),
01249     m_property(property),
01250     m_incr(false),
01251     m_failed(false),
01252     m_done(false),
01253     m_reading(false),
01254     m_data(NULL),
01255     m_actualTarget(NULL),
01256     m_error(false)
01257 {
01258     // do nothing
01259 }
01260 
01261 CXWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard()
01262 {
01263     // do nothing
01264 }
01265 
01266 bool
01267 CXWindowsClipboard::CICCCMGetClipboard::readClipboard(Display* display,
01268                 Atom selection, Atom target, Atom* actualTarget, CString* data)
01269 {
01270     assert(actualTarget != NULL);
01271     assert(data         != NULL);
01272 
01273     LOG((CLOG_DEBUG1 "request selection=%s, target=%s, window=%x", CXWindowsUtil::atomToString(display, selection).c_str(), CXWindowsUtil::atomToString(display, target).c_str(), m_requestor));
01274 
01275     m_atomNone = XInternAtom(display, "NONE", False);
01276     m_atomIncr = XInternAtom(display, "INCR", False);
01277 
01278     // save output pointers
01279     m_actualTarget = actualTarget;
01280     m_data         = data;
01281 
01282     // assume failure
01283     *m_actualTarget = None;
01284     *m_data         = "";
01285 
01286     // delete target property
01287     XDeleteProperty(display, m_requestor, m_property);
01288 
01289     // select window for property changes
01290     XWindowAttributes attr;
01291     XGetWindowAttributes(display, m_requestor, &attr);
01292     XSelectInput(display, m_requestor,
01293                                 attr.your_event_mask | PropertyChangeMask);
01294 
01295     // request data conversion
01296     XConvertSelection(display, selection, target,
01297                                 m_property, m_requestor, m_time);
01298 
01299     // synchronize with server before we start following timeout countdown
01300     XSync(display, False);
01301 
01302     // Xlib inexplicably omits the ability to wait for an event with
01303     // a timeout.  (it's inexplicable because there's no portable way
01304     // to do it.)  we'll poll until we have what we're looking for or
01305     // a timeout expires.  we use a timeout so we don't get locked up
01306     // by badly behaved selection owners.
01307     XEvent xevent;
01308     std::vector<XEvent> events;
01309     CStopwatch timeout(true);
01310     static const double s_timeout = 0.25;   // FIXME -- is this too short?
01311     bool noWait = false;
01312     while (!m_done && !m_failed) {
01313         // fail if timeout has expired
01314         if (timeout.getTime() >= s_timeout) {
01315             m_failed = true;
01316             break;
01317         }
01318 
01319         // process events if any otherwise sleep
01320         if (noWait || XPending(display) > 0) {
01321             while (!m_done && !m_failed && (noWait || XPending(display) > 0)) {
01322                 XNextEvent(display, &xevent);
01323                 if (!processEvent(display, &xevent)) {
01324                     // not processed so save it
01325                     events.push_back(xevent);
01326                 }
01327                 else {
01328                     // reset timer since we've made some progress
01329                     timeout.reset();
01330 
01331                     // don't sleep anymore, just block waiting for events.
01332                     // we're assuming here that the clipboard owner will
01333                     // complete the protocol correctly.  if we continue to
01334                     // sleep we'll get very bad performance.
01335                     noWait = true;
01336                 }
01337             }
01338         }
01339         else {
01340             ARCH->sleep(0.01);
01341         }
01342     }
01343 
01344     // put unprocessed events back
01345     for (UInt32 i = events.size(); i > 0; --i) {
01346         XPutBackEvent(display, &events[i - 1]);
01347     }
01348 
01349     // restore mask
01350     XSelectInput(display, m_requestor, attr.your_event_mask);
01351 
01352     // return success or failure
01353     LOG((CLOG_DEBUG1 "request %s", m_failed ? "failed" : "succeeded"));
01354     return !m_failed;
01355 }
01356 
01357 bool
01358 CXWindowsClipboard::CICCCMGetClipboard::processEvent(
01359                 Display* display, XEvent* xevent)
01360 {
01361     // process event
01362     switch (xevent->type) {
01363     case DestroyNotify:
01364         if (xevent->xdestroywindow.window == m_requestor) {
01365             m_failed = true;
01366             return true;
01367         }
01368 
01369         // not interested
01370         return false;
01371 
01372     case SelectionNotify:
01373         if (xevent->xselection.requestor == m_requestor) {
01374             // done if we can't convert
01375             if (xevent->xselection.property == None ||
01376                 xevent->xselection.property == m_atomNone) {
01377                 m_done = true;
01378                 return true;
01379             }
01380 
01381             // proceed if conversion successful
01382             else if (xevent->xselection.property == m_property) {
01383                 m_reading = true;
01384                 break;
01385             }
01386         }
01387 
01388         // otherwise not interested
01389         return false;
01390 
01391     case PropertyNotify:
01392         // proceed if conversion successful and we're receiving more data
01393         if (xevent->xproperty.window == m_requestor &&
01394             xevent->xproperty.atom   == m_property &&
01395             xevent->xproperty.state  == PropertyNewValue) {
01396             if (!m_reading) {
01397                 // we haven't gotten the SelectionNotify yet
01398                 return true;
01399             }
01400             break;
01401         }
01402 
01403         // otherwise not interested
01404         return false;
01405 
01406     default:
01407         // not interested
01408         return false;
01409     }
01410 
01411     // get the data from the property
01412     Atom target;
01413     const CString::size_type oldSize = m_data->size();
01414     if (!CXWindowsUtil::getWindowProperty(display, m_requestor,
01415                                 m_property, m_data, &target, NULL, True)) {
01416         // unable to read property
01417         m_failed = true;
01418         return true;
01419     }
01420 
01421     // note if incremental.  if we're already incremental then the
01422     // selection owner is busted.  if the INCR property has no size
01423     // then the selection owner is busted.
01424     if (target == m_atomIncr) {
01425         if (m_incr) {
01426             m_failed = true;
01427             m_error  = true;
01428         }
01429         else if (m_data->size() == oldSize) {
01430             m_failed = true;
01431             m_error  = true;
01432         }
01433         else {
01434             m_incr   = true;
01435 
01436             // discard INCR data
01437             *m_data = "";
01438         }
01439     }
01440 
01441     // handle incremental chunks
01442     else if (m_incr) {
01443         // if first incremental chunk then save target
01444         if (oldSize == 0) {
01445             LOG((CLOG_DEBUG1 "  INCR first chunk, target %s", CXWindowsUtil::atomToString(display, target).c_str()));
01446             *m_actualTarget = target;
01447         }
01448 
01449         // secondary chunks must have the same target
01450         else {
01451             if (target != *m_actualTarget) {
01452                 LOG((CLOG_WARN "  INCR target mismatch"));
01453                 m_failed = true;
01454                 m_error  = true;
01455             }
01456         }
01457 
01458         // note if this is the final chunk
01459         if (m_data->size() == oldSize) {
01460             LOG((CLOG_DEBUG1 "  INCR final chunk: %d bytes total", m_data->size()));
01461             m_done = true;
01462         }
01463     }
01464 
01465     // not incremental;  save the target.
01466     else {
01467         LOG((CLOG_DEBUG1 "  target %s", CXWindowsUtil::atomToString(display, target).c_str()));
01468         *m_actualTarget = target;
01469         m_done          = true;
01470     }
01471 
01472     // this event has been processed
01473     LOGC(!m_incr, (CLOG_DEBUG1 "  got data, %d bytes", m_data->size()));
01474     return true;
01475 }
01476 
01477 
01478 //
01479 // CXWindowsClipboard::CReply
01480 //
01481 
01482 CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time) :
01483     m_requestor(requestor),
01484     m_target(target),
01485     m_time(time),
01486     m_property(None),
01487     m_replied(false),
01488     m_done(false),
01489     m_data(),
01490     m_type(None),
01491     m_format(32),
01492     m_ptr(0)
01493 {
01494     // do nothing
01495 }
01496 
01497 CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time,
01498                 Atom property, const CString& data, Atom type, int format) :
01499     m_requestor(requestor),
01500     m_target(target),
01501     m_time(time),
01502     m_property(property),
01503     m_replied(false),
01504     m_done(false),
01505     m_data(data),
01506     m_type(type),
01507     m_format(format),
01508     m_ptr(0)
01509 {
01510     // do nothing
01511 }

Generated on Sun May 26 2013 00:00:05 for Synergy by  doxygen 1.7.1