Main Page | Class Hierarchy | Alphabetical List | Class List | File List | Class Members

CXWindowsClipboard.cpp

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

Generated on Fri Nov 6 00:21:14 2009 for synergy-plus by  doxygen 1.3.9.1