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