source: vendor/synergy/1.3.1/lib/platform/CXWindowsClipboard.cpp@ 2749

Last change on this file since 2749 was 2749, checked in by bird, 19 years ago

synergy v1.3.1 sources (zip).

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