1 /*
2  * barrier -- mouse and keyboard sharing utility
3  * Copyright (C) 2012-2016 Symless Ltd.
4  * Copyright (C) 2002 Chris Schoeneman
5  *
6  * This package is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * found in the file LICENSE that should have accompanied this file.
9  *
10  * This package is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "platform/XWindowsClipboard.h"
20 
21 #include "platform/XWindowsClipboardTextConverter.h"
22 #include "platform/XWindowsClipboardUCS2Converter.h"
23 #include "platform/XWindowsClipboardUTF8Converter.h"
24 #include "platform/XWindowsClipboardHTMLConverter.h"
25 #include "platform/XWindowsClipboardBMPConverter.h"
26 #include "platform/XWindowsUtil.h"
27 #include "mt/Thread.h"
28 #include "arch/Arch.h"
29 #include "base/Log.h"
30 #include "base/Stopwatch.h"
31 #include "common/stdvector.h"
32 
33 #include <cstdio>
34 #include <cstring>
35 #include <X11/Xatom.h>
36 
37 //
38 // XWindowsClipboard
39 //
40 
XWindowsClipboard(IXWindowsImpl * impl,Display * display,Window window,ClipboardID id)41 XWindowsClipboard::XWindowsClipboard(IXWindowsImpl* impl, Display* display,
42                 Window window, ClipboardID id) :
43     m_display(display),
44     m_window(window),
45     m_id(id),
46     m_open(false),
47     m_time(0),
48     m_owner(false),
49     m_timeOwned(0),
50     m_timeLost(0)
51 {
52     m_impl = impl;
53     // get some atoms
54     m_atomTargets         = m_impl->XInternAtom(m_display, "TARGETS", False);
55     m_atomMultiple        = m_impl->XInternAtom(m_display, "MULTIPLE", False);
56     m_atomTimestamp       = m_impl->XInternAtom(m_display, "TIMESTAMP", False);
57     m_atomInteger         = m_impl->XInternAtom(m_display, "INTEGER", False);
58     m_atomAtom            = m_impl->XInternAtom(m_display, "ATOM", False);
59     m_atomAtomPair        = m_impl->XInternAtom(m_display, "ATOM_PAIR", False);
60     m_atomData            = m_impl->XInternAtom(m_display, "CLIP_TEMPORARY",
61                                                 False);
62     m_atomINCR            = m_impl->XInternAtom(m_display, "INCR", False);
63     m_atomMotifClipLock   = m_impl->XInternAtom(m_display, "_MOTIF_CLIP_LOCK",
64                                                 False);
65     m_atomMotifClipHeader = m_impl->XInternAtom(m_display, "_MOTIF_CLIP_HEADER",
66                                                 False);
67     m_atomMotifClipAccess = m_impl->XInternAtom(m_display,
68                                 "_MOTIF_CLIP_LOCK_ACCESS_VALID", False);
69     m_atomGDKSelection    = m_impl->XInternAtom(m_display, "GDK_SELECTION",
70                                                 False);
71 
72     // set selection atom based on clipboard id
73     switch (id) {
74     case kClipboardClipboard:
75         m_selection = m_impl->XInternAtom(m_display, "CLIPBOARD", False);
76         break;
77 
78     case kClipboardSelection:
79     default:
80         m_selection = XA_PRIMARY;
81         break;
82     }
83 
84     // add converters, most desired first
85     m_converters.push_back(new XWindowsClipboardHTMLConverter(m_display,
86                                 "text/html"));
87     m_converters.push_back(new XWindowsClipboardBMPConverter(m_display));
88     m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display,
89                                 "text/plain;charset=UTF-8"));
90     m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display,
91                                 "UTF8_STRING"));
92     m_converters.push_back(new XWindowsClipboardUCS2Converter(m_display,
93                                 "text/plain;charset=ISO-10646-UCS-2"));
94     m_converters.push_back(new XWindowsClipboardUCS2Converter(m_display,
95                                 "text/unicode"));
96     m_converters.push_back(new XWindowsClipboardTextConverter(m_display,
97                                 "text/plain"));
98     m_converters.push_back(new XWindowsClipboardTextConverter(m_display,
99                                 "STRING"));
100 
101     // we have no data
102     clearCache();
103 }
104 
~XWindowsClipboard()105 XWindowsClipboard::~XWindowsClipboard()
106 {
107     clearReplies();
108     clearConverters();
109 }
110 
111 void
lost(Time time)112 XWindowsClipboard::lost(Time time)
113 {
114     LOG((CLOG_DEBUG "lost clipboard %d ownership at %d", m_id, time));
115     if (m_owner) {
116         m_owner    = false;
117         m_timeLost = time;
118         clearCache();
119     }
120 }
121 
122 void
addRequest(Window owner,Window requestor,Atom target,::Time time,Atom property)123 XWindowsClipboard::addRequest(Window owner, Window requestor,
124                 Atom target, ::Time time, Atom property)
125 {
126     // must be for our window and we must have owned the selection
127     // at the given time.
128     bool success = false;
129     if (owner == m_window) {
130         LOG((CLOG_DEBUG1 "request for clipboard %d, target %s by 0x%08x (property=%s)", m_selection, XWindowsUtil::atomToString(m_display, target).c_str(), requestor, XWindowsUtil::atomToString(m_display, property).c_str()));
131         if (wasOwnedAtTime(time)) {
132             if (target == m_atomMultiple) {
133                 // add a multiple request.  property may not be None
134                 // according to ICCCM.
135                 if (property != None) {
136                     success = insertMultipleReply(requestor, time, property);
137                 }
138             }
139             else {
140                 addSimpleRequest(requestor, target, time, property);
141 
142                 // addSimpleRequest() will have already handled failure
143                 success = true;
144             }
145         }
146         else {
147             LOG((CLOG_DEBUG1 "failed, not owned at time %d", time));
148         }
149     }
150 
151     if (!success) {
152         // send failure
153         LOG((CLOG_DEBUG1 "failed"));
154         insertReply(new Reply(requestor, target, time));
155     }
156 
157     // send notifications that are pending
158     pushReplies();
159 }
160 
161 bool
addSimpleRequest(Window requestor,Atom target,::Time time,Atom property)162 XWindowsClipboard::addSimpleRequest(Window requestor,
163                 Atom target, ::Time time, Atom property)
164 {
165     // obsolete requestors may supply a None property.  in
166     // that case we use the target as the property to store
167     // the conversion.
168     if (property == None) {
169         property = target;
170     }
171 
172     // handle targets
173     std::string data;
174     Atom type  = None;
175     int format = 0;
176     if (target == m_atomTargets) {
177         type = getTargetsData(data, &format);
178     }
179     else if (target == m_atomTimestamp) {
180         type = getTimestampData(data, &format);
181     }
182     else {
183         IXWindowsClipboardConverter* converter = getConverter(target);
184         if (converter != NULL) {
185             IClipboard::EFormat clipboardFormat = converter->getFormat();
186             if (m_added[clipboardFormat]) {
187                 try {
188                     data   = converter->fromIClipboard(m_data[clipboardFormat]);
189                     format = converter->getDataSize();
190                     type   = converter->getAtom();
191                 }
192                 catch (...) {
193                     // ignore -- cannot convert
194                 }
195             }
196         }
197     }
198 
199     if (type != None) {
200         // success
201         LOG((CLOG_DEBUG1 "success"));
202         insertReply(new Reply(requestor, target, time,
203                                 property, data, type, format));
204         return true;
205     }
206     else {
207         // failure
208         LOG((CLOG_DEBUG1 "failed"));
209         insertReply(new Reply(requestor, target, time));
210         return false;
211     }
212 }
213 
214 bool
processRequest(Window requestor,::Time,Atom property)215 XWindowsClipboard::processRequest(Window requestor,
216                 ::Time /*time*/, Atom property)
217 {
218     ReplyMap::iterator index = m_replies.find(requestor);
219     if (index == m_replies.end()) {
220         // unknown requestor window
221         return false;
222     }
223     LOG((CLOG_DEBUG1 "received property %s delete from 0x08%x", XWindowsUtil::atomToString(m_display, property).c_str(), requestor));
224 
225     // find the property in the known requests.  it should be the
226     // first property but we'll check 'em all if we have to.
227     ReplyList& replies = index->second;
228     for (ReplyList::iterator index2 = replies.begin();
229                                 index2 != replies.end(); ++index2) {
230         Reply* reply = *index2;
231         if (reply->m_replied && reply->m_property == property) {
232             // if reply is complete then remove it and start the
233             // next one.
234             pushReplies(index, replies, index2);
235             return true;
236         }
237     }
238 
239     return false;
240 }
241 
242 bool
destroyRequest(Window requestor)243 XWindowsClipboard::destroyRequest(Window requestor)
244 {
245     ReplyMap::iterator index = m_replies.find(requestor);
246     if (index == m_replies.end()) {
247         // unknown requestor window
248         return false;
249     }
250 
251     // destroy all replies for this window
252     clearReplies(index->second);
253     m_replies.erase(index);
254 
255     // note -- we don't stop watching the window for events because
256     // we're called in response to the window being destroyed.
257 
258     return true;
259 }
260 
261 Window
getWindow() const262 XWindowsClipboard::getWindow() const
263 {
264     return m_window;
265 }
266 
267 Atom
getSelection() const268 XWindowsClipboard::getSelection() const
269 {
270     return m_selection;
271 }
272 
273 bool
empty()274 XWindowsClipboard::empty()
275 {
276     assert(m_open);
277 
278     LOG((CLOG_DEBUG "empty clipboard %d", m_id));
279 
280     // assert ownership of clipboard
281     m_impl->XSetSelectionOwner(m_display, m_selection, m_window, m_time);
282     if (m_impl->XGetSelectionOwner(m_display, m_selection) != m_window) {
283         LOG((CLOG_DEBUG "failed to grab clipboard %d", m_id));
284         return false;
285     }
286 
287     // clear all data.  since we own the data now, the cache is up
288     // to date.
289     clearCache();
290     m_cached = true;
291 
292     // FIXME -- actually delete motif clipboard items?
293     // FIXME -- do anything to motif clipboard properties?
294 
295     // save time
296     m_timeOwned = m_time;
297     m_timeLost  = 0;
298 
299     // we're the owner now
300     m_owner = true;
301     LOG((CLOG_DEBUG "grabbed clipboard %d", m_id));
302 
303     return true;
304 }
305 
add(EFormat format,const std::string & data)306 void XWindowsClipboard::add(EFormat format, const std::string& data)
307 {
308     assert(m_open);
309     assert(m_owner);
310 
311     LOG((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format));
312 
313     m_data[format]  = data;
314     m_added[format] = true;
315 
316     // FIXME -- set motif clipboard item?
317 }
318 
319 bool
open(Time time) const320 XWindowsClipboard::open(Time time) const
321 {
322     if (m_open) {
323         LOG((CLOG_DEBUG "failed to open clipboard: already opened"));
324         return false;
325     }
326 
327     LOG((CLOG_DEBUG "open clipboard %d", m_id));
328 
329     // assume not motif
330     m_motif = false;
331 
332     // lock clipboard
333     if (m_id == kClipboardClipboard) {
334         if (!motifLockClipboard()) {
335             return false;
336         }
337 
338         // check if motif owns the selection.  unlock motif clipboard
339         // if it does not.
340         m_motif = motifOwnsClipboard();
341         LOG((CLOG_DEBUG1 "motif does %sown clipboard", m_motif ? "" : "not "));
342         if (!m_motif) {
343             motifUnlockClipboard();
344         }
345     }
346 
347     // now open
348     m_open = true;
349     m_time = time;
350 
351     // be sure to flush the cache later if it's dirty
352     m_checkCache = true;
353 
354     return true;
355 }
356 
357 void
close() const358 XWindowsClipboard::close() const
359 {
360     assert(m_open);
361 
362     LOG((CLOG_DEBUG "close clipboard %d", m_id));
363 
364     // unlock clipboard
365     if (m_motif) {
366         motifUnlockClipboard();
367     }
368 
369     m_motif = false;
370     m_open  = false;
371 }
372 
373 IClipboard::Time
getTime() const374 XWindowsClipboard::getTime() const
375 {
376     checkCache();
377     return m_timeOwned;
378 }
379 
380 bool
has(EFormat format) const381 XWindowsClipboard::has(EFormat format) const
382 {
383     assert(m_open);
384 
385     fillCache();
386     return m_added[format];
387 }
388 
get(EFormat format) const389 std::string XWindowsClipboard::get(EFormat format) const
390 {
391     assert(m_open);
392 
393     fillCache();
394     return m_data[format];
395 }
396 
397 void
clearConverters()398 XWindowsClipboard::clearConverters()
399 {
400     for (ConverterList::iterator index = m_converters.begin();
401                                 index != m_converters.end(); ++index) {
402         delete *index;
403     }
404     m_converters.clear();
405 }
406 
407 IXWindowsClipboardConverter*
getConverter(Atom target,bool onlyIfNotAdded) const408 XWindowsClipboard::getConverter(Atom target, bool onlyIfNotAdded) const
409 {
410     IXWindowsClipboardConverter* converter = NULL;
411     for (ConverterList::const_iterator index = m_converters.begin();
412                                 index != m_converters.end(); ++index) {
413         converter = *index;
414         if (converter->getAtom() == target) {
415             break;
416         }
417     }
418     if (converter == NULL) {
419         LOG((CLOG_DEBUG1 "  no converter for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
420         return NULL;
421     }
422 
423     // optionally skip already handled targets
424     if (onlyIfNotAdded) {
425         if (m_added[converter->getFormat()]) {
426             LOG((CLOG_DEBUG1 "  skipping handled format %d", converter->getFormat()));
427             return NULL;
428         }
429     }
430 
431     return converter;
432 }
433 
434 void
checkCache() const435 XWindowsClipboard::checkCache() const
436 {
437     if (!m_checkCache) {
438         return;
439     }
440     m_checkCache = false;
441 
442     // get the time the clipboard ownership was taken by the current
443     // owner.
444     if (m_motif) {
445         m_timeOwned = motifGetTime();
446     }
447     else {
448         m_timeOwned = icccmGetTime();
449     }
450 
451     // if we can't get the time then use the time passed to us
452     if (m_timeOwned == 0) {
453         m_timeOwned = m_time;
454     }
455 
456     // if the cache is dirty then flush it
457     if (m_timeOwned != m_cacheTime) {
458         clearCache();
459     }
460 }
461 
462 void
clearCache() const463 XWindowsClipboard::clearCache() const
464 {
465     const_cast<XWindowsClipboard*>(this)->doClearCache();
466 }
467 
468 void
doClearCache()469 XWindowsClipboard::doClearCache()
470 {
471     m_checkCache = false;
472     m_cached     = false;
473     for (SInt32 index = 0; index < kNumFormats; ++index) {
474         m_data[index]  = "";
475         m_added[index] = false;
476     }
477 }
478 
479 void
fillCache() const480 XWindowsClipboard::fillCache() const
481 {
482     // get the selection data if not already cached
483     checkCache();
484     if (!m_cached) {
485         const_cast<XWindowsClipboard*>(this)->doFillCache();
486     }
487 }
488 
489 void
doFillCache()490 XWindowsClipboard::doFillCache()
491 {
492     if (m_motif) {
493         motifFillCache();
494     }
495     else {
496         icccmFillCache();
497     }
498     m_checkCache = false;
499     m_cached     = true;
500     m_cacheTime  = m_timeOwned;
501 }
502 
503 void
icccmFillCache()504 XWindowsClipboard::icccmFillCache()
505 {
506     LOG((CLOG_DEBUG "ICCCM fill clipboard %d", m_id));
507 
508     // see if we can get the list of available formats from the selection.
509     // if not then use a default list of formats.  note that some clipboard
510     // owners are broken and report TARGETS as the type of the TARGETS data
511     // instead of the correct type ATOM;  allow either.
512     const Atom atomTargets = m_atomTargets;
513     Atom target;
514     std::string data;
515     if (!icccmGetSelection(atomTargets, &target, &data) ||
516         (target != m_atomAtom && target != m_atomTargets)) {
517         LOG((CLOG_DEBUG1 "selection doesn't support TARGETS"));
518         data = "";
519         XWindowsUtil::appendAtomData(data, XA_STRING);
520     }
521 
522     XWindowsUtil::convertAtomProperty(data);
523     const Atom* targets = reinterpret_cast<const Atom*>(data.data()); // TODO: Safe?
524     const UInt32 numTargets = data.size() / sizeof(Atom);
525     LOG((CLOG_DEBUG "  available targets: %s", XWindowsUtil::atomsToString(m_display, targets, numTargets).c_str()));
526 
527     // try each converter in order (because they're in order of
528     // preference).
529     for (ConverterList::const_iterator index = m_converters.begin();
530                                 index != m_converters.end(); ++index) {
531         IXWindowsClipboardConverter* converter = *index;
532 
533         // skip already handled targets
534         if (m_added[converter->getFormat()]) {
535             continue;
536         }
537 
538         // see if atom is in target list
539         Atom target = None;
540         // XXX -- just ask for the converter's target to see if it's
541         // available rather than checking TARGETS.  i've seen clipboard
542         // owners that don't report all the targets they support.
543         target = converter->getAtom();
544         /*
545         for (UInt32 i = 0; i < numTargets; ++i) {
546             if (converter->getAtom() == targets[i]) {
547                 target = targets[i];
548                 break;
549             }
550         }
551         */
552         if (target == None) {
553             continue;
554         }
555 
556         // get the data
557         Atom actualTarget;
558         std::string targetData;
559         if (!icccmGetSelection(target, &actualTarget, &targetData)) {
560             LOG((CLOG_DEBUG1 "  no data for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
561             continue;
562         }
563 
564         // add to clipboard and note we've done it
565         IClipboard::EFormat format = converter->getFormat();
566         m_data[format]  = converter->toIClipboard(targetData);
567         m_added[format] = true;
568         LOG((CLOG_DEBUG "added format %d for target %s (%u %s)", format, XWindowsUtil::atomToString(m_display, target).c_str(), targetData.size(), targetData.size() == 1 ? "byte" : "bytes"));
569     }
570 }
571 
572 bool
icccmGetSelection(Atom target,Atom * actualTarget,std::string * data) const573 XWindowsClipboard::icccmGetSelection(Atom target,
574                 Atom* actualTarget, std::string* data) const
575 {
576     assert(actualTarget != NULL);
577     assert(data         != NULL);
578 
579     // request data conversion
580     CICCCMGetClipboard getter(m_window, m_time, m_atomData);
581     if (!getter.readClipboard(m_display, m_selection,
582                                 target, actualTarget, data)) {
583         LOG((CLOG_DEBUG1 "can't get data for selection target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
584         LOGC(getter.m_error, (CLOG_WARN "ICCCM violation by clipboard owner"));
585         return false;
586     }
587     else if (*actualTarget == None) {
588         LOG((CLOG_DEBUG1 "selection conversion failed for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
589         return false;
590     }
591     return true;
592 }
593 
594 IClipboard::Time
icccmGetTime() const595 XWindowsClipboard::icccmGetTime() const
596 {
597     Atom actualTarget;
598     std::string data;
599     if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) &&
600         actualTarget == m_atomInteger) {
601         Time time = *reinterpret_cast<const Time*>(data.data());
602         LOG((CLOG_DEBUG1 "got ICCCM time %d", time));
603         return time;
604     }
605     else {
606         // no timestamp
607         LOG((CLOG_DEBUG1 "can't get ICCCM time"));
608         return 0;
609     }
610 }
611 
612 bool
motifLockClipboard() const613 XWindowsClipboard::motifLockClipboard() const
614 {
615     // fail if anybody owns the lock (even us, so this is non-recursive)
616     Window lockOwner = m_impl->XGetSelectionOwner(m_display, m_atomMotifClipLock);
617     if (lockOwner != None) {
618         LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner));
619         return false;
620     }
621 
622     // try to grab the lock
623     // FIXME -- is this right?  there's a race condition here --
624     // A grabs successfully, B grabs successfully, A thinks it
625     // still has the grab until it gets a SelectionClear.
626     Time time = XWindowsUtil::getCurrentTime(m_display, m_window);
627     m_impl->XSetSelectionOwner(m_display, m_atomMotifClipLock, m_window, time);
628     lockOwner = m_impl->XGetSelectionOwner(m_display, m_atomMotifClipLock);
629     if (lockOwner != m_window) {
630         LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner));
631         return false;
632     }
633 
634     LOG((CLOG_DEBUG1 "locked motif clipboard"));
635     return true;
636 }
637 
638 void
motifUnlockClipboard() const639 XWindowsClipboard::motifUnlockClipboard() const
640 {
641     LOG((CLOG_DEBUG1 "unlocked motif clipboard"));
642 
643     // fail if we don't own the lock
644     Window lockOwner = m_impl->XGetSelectionOwner(m_display, m_atomMotifClipLock);
645     if (lockOwner != m_window) {
646         return;
647     }
648 
649     // release lock
650     Time time = XWindowsUtil::getCurrentTime(m_display, m_window);
651     m_impl->XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time);
652 }
653 
654 bool
motifOwnsClipboard() const655 XWindowsClipboard::motifOwnsClipboard() const
656 {
657     // get the current selection owner
658     // FIXME -- this can't be right.  even if the window is destroyed
659     // Motif will still have a valid clipboard.  how can we tell if
660     // some other client owns CLIPBOARD?
661     Window owner = m_impl->XGetSelectionOwner(m_display, m_selection);
662     if (owner == None) {
663         return false;
664     }
665 
666     // get the Motif clipboard header property from the root window
667     Atom target;
668     SInt32 format;
669     std::string data;
670     Window root = RootWindow(m_display, DefaultScreen(m_display));
671     if (!XWindowsUtil::getWindowProperty(m_display, root,
672                                 m_atomMotifClipHeader,
673                                 &data, &target, &format, False)) {
674         return false;
675     }
676 
677     // check the owner window against the current clipboard owner
678     if (data.size() >= sizeof(MotifClipHeader)) {
679         MotifClipHeader header;
680         std::memcpy (&header, data.data(), sizeof(header));
681         if ((header.m_id == kMotifClipHeader) &&
682             (static_cast<Window>(header.m_selectionOwner) == owner)) {
683             return true;
684         }
685     }
686 
687     return false;
688 }
689 
690 void
motifFillCache()691 XWindowsClipboard::motifFillCache()
692 {
693     LOG((CLOG_DEBUG "Motif fill clipboard %d", m_id));
694 
695     // get the Motif clipboard header property from the root window
696     Atom target;
697     SInt32 format;
698     std::string data;
699     Window root = RootWindow(m_display, DefaultScreen(m_display));
700     if (!XWindowsUtil::getWindowProperty(m_display, root,
701                                 m_atomMotifClipHeader,
702                                 &data, &target, &format, False)) {
703         return;
704     }
705 
706     MotifClipHeader header;
707     if (data.size() < sizeof(header)) { // check that the header is okay
708         return;
709     }
710     std::memcpy (&header, data.data(), sizeof(header));
711     if (header.m_id != kMotifClipHeader || header.m_numItems < 1) {
712         return;
713     }
714 
715     // get the Motif item property from the root window
716     char name[18 + 20];
717     sprintf(name, "_MOTIF_CLIP_ITEM_%d", header.m_item);
718     Atom atomItem = m_impl->XInternAtom(m_display, name, False);
719     data = "";
720     if (!XWindowsUtil::getWindowProperty(m_display, root,
721                                 atomItem, &data,
722                                 &target, &format, False)) {
723         return;
724     }
725 
726     MotifClipItem item;
727     if (data.size() < sizeof(item)) { // check that the item is okay
728         return;
729     }
730     std::memcpy (&item, data.data(), sizeof(item));
731     if (item.m_id != kMotifClipItem ||
732         item.m_numFormats - item.m_numDeletedFormats < 1) {
733         return;
734     }
735 
736     // format list is after static item structure elements
737     const SInt32 numFormats = item.m_numFormats - item.m_numDeletedFormats;
738     const SInt32* formats   = reinterpret_cast<const SInt32*>(item.m_size +
739                                 static_cast<const char*>(data.data()));
740 
741     // get the available formats
742     typedef std::map<Atom, std::string> MotifFormatMap;
743     MotifFormatMap motifFormats;
744     for (SInt32 i = 0; i < numFormats; ++i) {
745         // get Motif format property from the root window
746         sprintf(name, "_MOTIF_CLIP_ITEM_%d", formats[i]);
747         Atom atomFormat = m_impl->XInternAtom(m_display, name, False);
748         std::string data;
749         if (!XWindowsUtil::getWindowProperty(m_display, root,
750                                     atomFormat, &data,
751                                     &target, &format, False)) {
752             continue;
753         }
754 
755         // check that the format is okay
756         MotifClipFormat motifFormat;
757         if (data.size() < sizeof(motifFormat)) {
758             continue;
759         }
760         std::memcpy (&motifFormat, data.data(), sizeof(motifFormat));
761         if (motifFormat.m_id != kMotifClipFormat ||
762             motifFormat.m_length < 0 ||
763             motifFormat.m_type == None ||
764             motifFormat.m_deleted != 0) {
765             continue;
766         }
767 
768         // save it
769         motifFormats.insert(std::make_pair(motifFormat.m_type, data));
770     }
771     //const UInt32 numMotifFormats = motifFormats.size();
772 
773     // try each converter in order (because they're in order of
774     // preference).
775     for (ConverterList::const_iterator index = m_converters.begin();
776                                 index != m_converters.end(); ++index) {
777         IXWindowsClipboardConverter* converter = *index;
778 
779         // skip already handled targets
780         if (m_added[converter->getFormat()]) {
781             continue;
782         }
783 
784         // see if atom is in target list
785         MotifFormatMap::const_iterator index2 =
786                                 motifFormats.find(converter->getAtom());
787         if (index2 == motifFormats.end()) {
788             continue;
789         }
790 
791         // get format
792         MotifClipFormat motifFormat;
793         std::memcpy (&motifFormat, index2->second.data(), sizeof(motifFormat));
794         const Atom target = motifFormat.m_type;
795 
796         // get the data (finally)
797         Atom actualTarget;
798         std::string targetData;
799         if (!motifGetSelection(&motifFormat, &actualTarget, &targetData)) {
800             LOG((CLOG_DEBUG1 "  no data for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
801             continue;
802         }
803 
804         // add to clipboard and note we've done it
805         IClipboard::EFormat format = converter->getFormat();
806         m_data[format]  = converter->toIClipboard(targetData);
807         m_added[format] = true;
808         LOG((CLOG_DEBUG "added format %d for target %s", format, XWindowsUtil::atomToString(m_display, target).c_str()));
809     }
810 }
811 
812 bool
motifGetSelection(const MotifClipFormat * format,Atom * actualTarget,std::string * data) const813 XWindowsClipboard::motifGetSelection(const MotifClipFormat* format,
814                             Atom* actualTarget, std::string* data) const
815 {
816     // if the current clipboard owner and the owner indicated by the
817     // motif clip header are the same then transfer via a property on
818     // the root window, otherwise transfer as a normal ICCCM client.
819     if (!motifOwnsClipboard()) {
820         return icccmGetSelection(format->m_type, actualTarget, data);
821     }
822 
823     // use motif way
824     // FIXME -- this isn't right.  it'll only work if the data is
825     // already stored on the root window and only if it fits in a
826     // property.  motif has some scheme for transferring part by
827     // part that i don't know.
828     char name[18 + 20];
829     sprintf(name, "_MOTIF_CLIP_ITEM_%d", format->m_data);
830        Atom target = m_impl->XInternAtom(m_display, name, False);
831     Window root = RootWindow(m_display, DefaultScreen(m_display));
832     return XWindowsUtil::getWindowProperty(m_display, root,
833                                 target, data,
834                                 actualTarget, NULL, False);
835 }
836 
837 IClipboard::Time
motifGetTime() const838 XWindowsClipboard::motifGetTime() const
839 {
840     return icccmGetTime();
841 }
842 
843 bool
insertMultipleReply(Window requestor,::Time time,Atom property)844 XWindowsClipboard::insertMultipleReply(Window requestor,
845                 ::Time time, Atom property)
846 {
847     // get the requested targets
848     Atom target;
849     SInt32 format;
850     std::string data;
851     if (!XWindowsUtil::getWindowProperty(m_display, requestor,
852                                 property, &data, &target, &format, False)) {
853         // can't get the requested targets
854         return false;
855     }
856 
857     // fail if the requested targets isn't of the correct form
858     if (format != 32 || target != m_atomAtomPair) {
859         return false;
860     }
861 
862     // data is a list of atom pairs:  target, property
863     XWindowsUtil::convertAtomProperty(data);
864     const Atom* targets = reinterpret_cast<const Atom*>(data.data());
865     const UInt32 numTargets = data.size() / sizeof(Atom);
866 
867     // add replies for each target
868     bool changed = false;
869     for (UInt32 i = 0; i < numTargets; i += 2) {
870         const Atom target   = targets[i + 0];
871         const Atom property = targets[i + 1];
872         if (!addSimpleRequest(requestor, target, time, property)) {
873             // note that we can't perform the requested conversion
874             XWindowsUtil::replaceAtomData(data, i, None);
875             changed = true;
876         }
877     }
878 
879     // update the targets property if we changed it
880     if (changed) {
881         XWindowsUtil::setWindowProperty(m_display, requestor,
882                                 property, data.data(), data.size(),
883                                 target, format);
884     }
885 
886     // add reply for MULTIPLE request
887     insertReply(new Reply(requestor, m_atomMultiple,
888                                 time, property, std::string(), None, 32));
889 
890     return true;
891 }
892 
893 void
insertReply(Reply * reply)894 XWindowsClipboard::insertReply(Reply* reply)
895 {
896     assert(reply != NULL);
897 
898     // note -- we must respond to requests in order if requestor,target,time
899     // are the same, otherwise we can use whatever order we like with one
900     // exception:  each reply in a MULTIPLE reply must be handled in order
901     // as well.  those replies will almost certainly not share targets so
902     // we can't simply use requestor,target,time as map index.
903     //
904     // instead we'll use just the requestor.  that's more restrictive than
905     // necessary but we're guaranteed to do things in the right order.
906     // note that we could also include the time in the map index and still
907     // ensure the right order.  but since that'll just make it harder to
908     // find the right reply when handling property notify events we stick
909     // to just the requestor.
910 
911     const bool newWindow = (m_replies.count(reply->m_requestor) == 0);
912     m_replies[reply->m_requestor].push_back(reply);
913 
914     // adjust requestor's event mask if we haven't done so already.  we
915     // want events in case the window is destroyed or any of its
916     // properties change.
917     if (newWindow) {
918         // note errors while we adjust event masks
919         bool error = false;
920         {
921             XWindowsUtil::ErrorLock lock(m_display, &error);
922 
923             // get and save the current event mask
924             XWindowAttributes attr;
925             m_impl->XGetWindowAttributes(m_display, reply->m_requestor, &attr);
926             m_eventMasks[reply->m_requestor] = attr.your_event_mask;
927 
928             // add the events we want
929             m_impl->XSelectInput(m_display, reply->m_requestor, attr.your_event_mask |
930                                     StructureNotifyMask | PropertyChangeMask);
931         }
932 
933         // if we failed then the window has already been destroyed
934         if (error) {
935             m_replies.erase(reply->m_requestor);
936             delete reply;
937         }
938     }
939 }
940 
941 void
pushReplies()942 XWindowsClipboard::pushReplies()
943 {
944     // send the first reply for each window if that reply hasn't
945     // been sent yet.
946     for (ReplyMap::iterator index = m_replies.begin();
947                                 index != m_replies.end(); ) {
948         assert(!index->second.empty());
949         ReplyList::iterator listit = index->second.begin();
950         while (listit != index->second.end()) {
951             if (!(*listit)->m_replied)
952                 break;
953             ++listit;
954         }
955         if (listit != index->second.end() && !(*listit)->m_replied) {
956             pushReplies(index, index->second, listit);
957         }
958         else {
959             ++index;
960         }
961     }
962 }
963 
964 void
pushReplies(ReplyMap::iterator & mapIndex,ReplyList & replies,ReplyList::iterator index)965 XWindowsClipboard::pushReplies(ReplyMap::iterator& mapIndex,
966                 ReplyList& replies, ReplyList::iterator index)
967 {
968     Reply* reply = *index;
969     while (sendReply(reply)) {
970         // reply is complete.  discard it and send the next reply,
971         // if any.
972         index = replies.erase(index);
973         delete reply;
974         if (index == replies.end()) {
975             break;
976         }
977         reply = *index;
978     }
979 
980     // if there are no more replies in the list then remove the list
981     // and stop watching the requestor for events.
982     if (replies.empty()) {
983         XWindowsUtil::ErrorLock lock(m_display);
984         Window requestor = mapIndex->first;
985         m_impl->XSelectInput(m_display, requestor, m_eventMasks[requestor]);
986         m_replies.erase(mapIndex++);
987         m_eventMasks.erase(requestor);
988     }
989     else {
990         ++mapIndex;
991     }
992 }
993 
994 bool
sendReply(Reply * reply)995 XWindowsClipboard::sendReply(Reply* reply)
996 {
997     assert(reply != NULL);
998 
999     // bail out immediately if reply is done
1000     if (reply->m_done) {
1001         LOG((CLOG_DEBUG1 "clipboard: finished reply to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
1002         return true;
1003     }
1004 
1005     // start in failed state if property is None
1006     bool failed = (reply->m_property == None);
1007     if (!failed) {
1008         LOG((CLOG_DEBUG1 "clipboard: setting property on 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
1009 
1010         // send using INCR if already sending incrementally or if reply
1011         // is too large, otherwise just send it.
1012         const UInt32 maxRequestSize = 3 * XMaxRequestSize(m_display);
1013         const bool useINCR = (reply->m_data.size() > maxRequestSize);
1014 
1015         // send INCR reply if incremental and we haven't replied yet
1016         if (useINCR && !reply->m_replied) {
1017             UInt32 size = reply->m_data.size();
1018             if (!XWindowsUtil::setWindowProperty(m_display,
1019                                 reply->m_requestor, reply->m_property,
1020                                 &size, 4, m_atomINCR, 32)) {
1021                 failed = true;
1022             }
1023         }
1024 
1025         // send more INCR reply or entire non-incremental reply
1026         else {
1027             // how much more data should we send?
1028             UInt32 size = reply->m_data.size() - reply->m_ptr;
1029             if (size > maxRequestSize)
1030                 size = maxRequestSize;
1031 
1032             // send it
1033             if (!XWindowsUtil::setWindowProperty(m_display,
1034                                 reply->m_requestor, reply->m_property,
1035                                 reply->m_data.data() + reply->m_ptr,
1036                                 size,
1037                                 reply->m_type, reply->m_format)) {
1038                 failed = true;
1039             }
1040             else {
1041                 reply->m_ptr += size;
1042 
1043                 // we've finished the reply if we just sent the zero
1044                 // size incremental chunk or if we're not incremental.
1045                 reply->m_done = (size == 0 || !useINCR);
1046             }
1047         }
1048     }
1049 
1050     // if we've failed then delete the property and say we're done.
1051     // if we haven't replied yet then we can send a failure notify,
1052     // otherwise we've failed in the middle of an incremental
1053     // transfer;  i don't know how to cancel that so i'll just send
1054     // the final zero-length property.
1055     // FIXME -- how do you gracefully cancel an incremental transfer?
1056     if (failed) {
1057         LOG((CLOG_DEBUG1 "clipboard: sending failure to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
1058         reply->m_done = true;
1059         if (reply->m_property != None) {
1060             XWindowsUtil::ErrorLock lock(m_display);
1061             m_impl->XDeleteProperty(m_display, reply->m_requestor, reply->m_property);
1062         }
1063 
1064         if (!reply->m_replied) {
1065             sendNotify(reply->m_requestor, m_selection,
1066                                 reply->m_target, None,
1067                                 reply->m_time);
1068 
1069             // don't wait for any reply (because we're not expecting one)
1070             return true;
1071         }
1072         else {
1073             static const char dummy = 0;
1074             XWindowsUtil::setWindowProperty(m_display,
1075                                 reply->m_requestor, reply->m_property,
1076                                 &dummy,
1077                                 0,
1078                                 reply->m_type, reply->m_format);
1079 
1080             // wait for delete notify
1081             return false;
1082         }
1083     }
1084 
1085     // send notification if we haven't yet
1086     if (!reply->m_replied) {
1087         LOG((CLOG_DEBUG1 "clipboard: sending notify to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
1088         reply->m_replied = true;
1089 
1090         // dump every property on the requestor window to the debug2
1091         // log.  we've seen what appears to be a bug in lesstif and
1092         // knowing the properties may help design a workaround, if
1093         // it becomes necessary.
1094         if (CLOG->getFilter() >= kDEBUG2) {
1095             XWindowsUtil::ErrorLock lock(m_display);
1096             int n;
1097             Atom* props = m_impl->XListProperties(m_display, reply->m_requestor,
1098                                                   &n);
1099             LOG((CLOG_DEBUG2 "properties of 0x%08x:", reply->m_requestor));
1100             for (int i = 0; i < n; ++i) {
1101                 Atom target;
1102                 std::string data;
1103                 char* name = m_impl->XGetAtomName(m_display, props[i]);
1104                 if (!XWindowsUtil::getWindowProperty(m_display,
1105                                 reply->m_requestor,
1106                                 props[i], &data, &target, NULL, False)) {
1107                     LOG((CLOG_DEBUG2 "  %s: <can't read property>", name));
1108                 }
1109                 else {
1110                     // if there are any non-ascii characters in string
1111                     // then print the binary data.
1112                     static const char* hex = "0123456789abcdef";
1113                     for (std::string::size_type j = 0; j < data.size(); ++j) {
1114                         if (data[j] < 32 || data[j] > 126) {
1115                             std::string tmp;
1116                             tmp.reserve(data.size() * 3);
1117                             for (j = 0; j < data.size(); ++j) {
1118                                 unsigned char v = (unsigned char)data[j];
1119                                 tmp += hex[v >> 16];
1120                                 tmp += hex[v & 15];
1121                                 tmp += ' ';
1122                             }
1123                             data = tmp;
1124                             break;
1125                         }
1126                     }
1127                     char* type = m_impl->XGetAtomName(m_display, target);
1128                     LOG((CLOG_DEBUG2 "  %s (%s): %s", name, type, data.c_str()));
1129                     if (type != NULL) {
1130                         m_impl->XFree(type);
1131                     }
1132                 }
1133                 if (name != NULL) {
1134                     m_impl->XFree(name);
1135                 }
1136             }
1137             if (props != NULL) {
1138                 m_impl->XFree(props);
1139             }
1140         }
1141 
1142         sendNotify(reply->m_requestor, m_selection,
1143                                 reply->m_target, reply->m_property,
1144                                 reply->m_time);
1145     }
1146 
1147     // wait for delete notify
1148     return false;
1149 }
1150 
1151 void
clearReplies()1152 XWindowsClipboard::clearReplies()
1153 {
1154     for (ReplyMap::iterator index = m_replies.begin();
1155                                 index != m_replies.end(); ++index) {
1156         clearReplies(index->second);
1157     }
1158     m_replies.clear();
1159     m_eventMasks.clear();
1160 }
1161 
1162 void
clearReplies(ReplyList & replies)1163 XWindowsClipboard::clearReplies(ReplyList& replies)
1164 {
1165     for (ReplyList::iterator index = replies.begin();
1166                                 index != replies.end(); ++index) {
1167         delete *index;
1168     }
1169     replies.clear();
1170 }
1171 
1172 void
sendNotify(Window requestor,Atom selection,Atom target,Atom property,Time time)1173 XWindowsClipboard::sendNotify(Window requestor,
1174                 Atom selection, Atom target, Atom property, Time time)
1175 {
1176     XEvent event;
1177     event.xselection.type      = SelectionNotify;
1178     event.xselection.display   = m_display;
1179     event.xselection.requestor = requestor;
1180     event.xselection.selection = selection;
1181     event.xselection.target    = target;
1182     event.xselection.property  = property;
1183     event.xselection.time      = time;
1184     XWindowsUtil::ErrorLock lock(m_display);
1185     m_impl->XSendEvent(m_display, requestor, False, 0, &event);
1186 }
1187 
1188 bool
wasOwnedAtTime(::Time time) const1189 XWindowsClipboard::wasOwnedAtTime(::Time time) const
1190 {
1191     // not owned if we've never owned the selection
1192     checkCache();
1193     if (m_timeOwned == 0) {
1194         return false;
1195     }
1196 
1197     // if time is CurrentTime then return true if we still own the
1198     // selection and false if we do not.  else if we still own the
1199     // selection then get the current time, otherwise use
1200     // m_timeLost as the end time.
1201     Time lost = m_timeLost;
1202     if (m_timeLost == 0) {
1203         if (time == CurrentTime) {
1204             return true;
1205         }
1206         else {
1207             lost = XWindowsUtil::getCurrentTime(m_display, m_window);
1208         }
1209     }
1210     else {
1211         if (time == CurrentTime) {
1212             return false;
1213         }
1214     }
1215 
1216     // compare time to range
1217     Time duration = lost - m_timeOwned;
1218     Time when     = time - m_timeOwned;
1219     return (/*when >= 0 &&*/ when <= duration);
1220 }
1221 
getTargetsData(std::string & data,int * format) const1222 Atom XWindowsClipboard::getTargetsData(std::string& data, int* format) const
1223 {
1224     assert(format != NULL);
1225 
1226     // add standard targets
1227     XWindowsUtil::appendAtomData(data, m_atomTargets);
1228     XWindowsUtil::appendAtomData(data, m_atomMultiple);
1229     XWindowsUtil::appendAtomData(data, m_atomTimestamp);
1230 
1231     // add targets we can convert to
1232     for (ConverterList::const_iterator index = m_converters.begin();
1233                                 index != m_converters.end(); ++index) {
1234         IXWindowsClipboardConverter* converter = *index;
1235 
1236         // skip formats we don't have
1237         if (m_added[converter->getFormat()]) {
1238             XWindowsUtil::appendAtomData(data, converter->getAtom());
1239         }
1240     }
1241 
1242     *format = 32;
1243     return m_atomAtom;
1244 }
1245 
getTimestampData(std::string & data,int * format) const1246 Atom XWindowsClipboard::getTimestampData(std::string& data, int* format) const
1247 {
1248     assert(format != NULL);
1249 
1250     checkCache();
1251     XWindowsUtil::appendTimeData(data, m_timeOwned);
1252     *format = 32;
1253     return m_atomInteger;
1254 }
1255 
1256 
1257 //
1258 // XWindowsClipboard::CICCCMGetClipboard
1259 //
1260 
CICCCMGetClipboard(Window requestor,Time time,Atom property)1261 XWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard(
1262                 Window requestor, Time time, Atom property) :
1263     m_requestor(requestor),
1264     m_time(time),
1265     m_property(property),
1266     m_incr(false),
1267     m_failed(false),
1268     m_done(false),
1269     m_reading(false),
1270     m_data(NULL),
1271     m_actualTarget(NULL),
1272     m_error(false)
1273 {
1274     // do nothing
1275 }
1276 
~CICCCMGetClipboard()1277 XWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard()
1278 {
1279     // do nothing
1280 }
1281 
1282 bool
readClipboard(Display * display,Atom selection,Atom target,Atom * actualTarget,std::string * data)1283 XWindowsClipboard::CICCCMGetClipboard::readClipboard(Display* display,
1284                 Atom selection, Atom target, Atom* actualTarget, std::string* data)
1285 {
1286     assert(actualTarget != NULL);
1287     assert(data         != NULL);
1288 
1289     LOG((CLOG_DEBUG1 "request selection=%s, target=%s, window=%x", XWindowsUtil::atomToString(display, selection).c_str(), XWindowsUtil::atomToString(display, target).c_str(), m_requestor));
1290 
1291     m_atomNone = XInternAtom(display, "NONE", False);
1292     m_atomIncr = XInternAtom(display, "INCR", False);
1293 
1294     // save output pointers
1295     m_actualTarget = actualTarget;
1296     m_data         = data;
1297 
1298     // assume failure
1299     *m_actualTarget = None;
1300     *m_data         = "";
1301 
1302     // delete target property
1303     XDeleteProperty(display, m_requestor, m_property);
1304 
1305     // select window for property changes
1306     XWindowAttributes attr;
1307     XGetWindowAttributes(display, m_requestor, &attr);
1308     XSelectInput(display, m_requestor,
1309                                 attr.your_event_mask | PropertyChangeMask);
1310 
1311     // request data conversion
1312     XConvertSelection(display, selection, target,
1313                                 m_property, m_requestor, m_time);
1314 
1315     // synchronize with server before we start following timeout countdown
1316     XSync(display, False);
1317 
1318     // Xlib inexplicably omits the ability to wait for an event with
1319     // a timeout.  (it's inexplicable because there's no portable way
1320     // to do it.)  we'll poll until we have what we're looking for or
1321     // a timeout expires.  we use a timeout so we don't get locked up
1322     // by badly behaved selection owners.
1323     XEvent xevent;
1324     std::vector<XEvent> events;
1325     Stopwatch timeout(false);    // timer not stopped, not triggered
1326     static const double s_timeout = 0.25;    // FIXME -- is this too short?
1327     bool noWait = false;
1328     while (!m_done && !m_failed) {
1329         // fail if timeout has expired
1330         if (timeout.getTime() >= s_timeout) {
1331             m_failed = true;
1332             break;
1333         }
1334 
1335         // process events if any otherwise sleep
1336         if (noWait || XPending(display) > 0) {
1337             while (!m_done && !m_failed && (noWait || XPending(display) > 0)) {
1338                 XNextEvent(display, &xevent);
1339                 if (!processEvent(display, &xevent)) {
1340                     // not processed so save it
1341                     events.push_back(xevent);
1342                 }
1343                 else {
1344                     // reset timer since we've made some progress
1345                     timeout.reset();
1346 
1347                     // don't sleep anymore, just block waiting for events.
1348                     // we're assuming here that the clipboard owner will
1349                     // complete the protocol correctly.  if we continue to
1350                     // sleep we'll get very bad performance.
1351                     noWait = true;
1352                 }
1353             }
1354         }
1355         else {
1356             ARCH->sleep(0.01);
1357         }
1358     }
1359 
1360     // put unprocessed events back
1361     for (UInt32 i = events.size(); i > 0; --i) {
1362         XPutBackEvent(display, &events[i - 1]);
1363     }
1364 
1365     // restore mask
1366     XSelectInput(display, m_requestor, attr.your_event_mask);
1367 
1368     // return success or failure
1369     LOG((CLOG_DEBUG1 "request %s after %fs", m_failed ? "failed" : "succeeded", timeout.getTime()));
1370     return !m_failed;
1371 }
1372 
1373 bool
processEvent(Display * display,XEvent * xevent)1374 XWindowsClipboard::CICCCMGetClipboard::processEvent(
1375                 Display* display, XEvent* xevent)
1376 {
1377     // process event
1378     switch (xevent->type) {
1379     case DestroyNotify:
1380         if (xevent->xdestroywindow.window == m_requestor) {
1381             m_failed = true;
1382             return true;
1383         }
1384 
1385         // not interested
1386         return false;
1387 
1388     case SelectionNotify:
1389         if (xevent->xselection.requestor == m_requestor) {
1390             // done if we can't convert
1391             if (xevent->xselection.property == None ||
1392                 xevent->xselection.property == m_atomNone) {
1393                 m_done = true;
1394                 return true;
1395             }
1396 
1397             // proceed if conversion successful
1398             else if (xevent->xselection.property == m_property) {
1399                 m_reading = true;
1400                 break;
1401             }
1402         }
1403 
1404         // otherwise not interested
1405         return false;
1406 
1407     case PropertyNotify:
1408         // proceed if conversion successful and we're receiving more data
1409         if (xevent->xproperty.window == m_requestor &&
1410             xevent->xproperty.atom   == m_property &&
1411             xevent->xproperty.state  == PropertyNewValue) {
1412             if (!m_reading) {
1413                 // we haven't gotten the SelectionNotify yet
1414                 return true;
1415             }
1416             break;
1417         }
1418 
1419         // otherwise not interested
1420         return false;
1421 
1422     default:
1423         // not interested
1424         return false;
1425     }
1426 
1427     // get the data from the property
1428     Atom target;
1429     const std::string::size_type oldSize = m_data->size();
1430     if (!XWindowsUtil::getWindowProperty(display, m_requestor,
1431                                 m_property, m_data, &target, NULL, True)) {
1432         // unable to read property
1433         m_failed = true;
1434         return true;
1435     }
1436 
1437     // note if incremental.  if we're already incremental then the
1438     // selection owner is busted.  if the INCR property has no size
1439     // then the selection owner is busted.
1440     if (target == m_atomIncr) {
1441         if (m_incr) {
1442             m_failed = true;
1443             m_error  = true;
1444         }
1445         else if (m_data->size() == oldSize) {
1446             m_failed = true;
1447             m_error  = true;
1448         }
1449         else {
1450             m_incr   = true;
1451 
1452             // discard INCR data
1453             *m_data = "";
1454         }
1455     }
1456 
1457     // handle incremental chunks
1458     else if (m_incr) {
1459         // if first incremental chunk then save target
1460         if (oldSize == 0) {
1461             LOG((CLOG_DEBUG1 "  INCR first chunk, target %s", XWindowsUtil::atomToString(display, target).c_str()));
1462             *m_actualTarget = target;
1463         }
1464 
1465         // secondary chunks must have the same target
1466         else {
1467             if (target != *m_actualTarget) {
1468                 LOG((CLOG_WARN "  INCR target mismatch"));
1469                 m_failed = true;
1470                 m_error  = true;
1471             }
1472         }
1473 
1474         // note if this is the final chunk
1475         if (m_data->size() == oldSize) {
1476             LOG((CLOG_DEBUG1 "  INCR final chunk: %d bytes total", m_data->size()));
1477             m_done = true;
1478         }
1479     }
1480 
1481     // not incremental;  save the target.
1482     else {
1483         LOG((CLOG_DEBUG1 "  target %s", XWindowsUtil::atomToString(display, target).c_str()));
1484         *m_actualTarget = target;
1485         m_done          = true;
1486     }
1487 
1488     // this event has been processed
1489     LOGC(!m_incr, (CLOG_DEBUG1 "  got data, %d bytes", m_data->size()));
1490     return true;
1491 }
1492 
1493 
1494 //
1495 // XWindowsClipboard::Reply
1496 //
1497 
Reply(Window requestor,Atom target,::Time time)1498 XWindowsClipboard::Reply::Reply(Window requestor, Atom target, ::Time time) :
1499     m_requestor(requestor),
1500     m_target(target),
1501     m_time(time),
1502     m_property(None),
1503     m_replied(false),
1504     m_done(false),
1505     m_data(),
1506     m_type(None),
1507     m_format(32),
1508     m_ptr(0)
1509 {
1510     // do nothing
1511 }
1512 
Reply(Window requestor,Atom target,::Time time,Atom property,const std::string & data,Atom type,int format)1513 XWindowsClipboard::Reply::Reply(Window requestor, Atom target, ::Time time,
1514                 Atom property, const std::string& data, Atom type, int format) :
1515     m_requestor(requestor),
1516     m_target(target),
1517     m_time(time),
1518     m_property(property),
1519     m_replied(false),
1520     m_done(false),
1521     m_data(data),
1522     m_type(type),
1523     m_format(format),
1524     m_ptr(0)
1525 {
1526     // do nothing
1527 }
1528