2
* synergy -- mouse and keyboard sharing utility
3
* Copyright (C) 2002 Chris Schoeneman
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.
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.
15
#include "CXWindowsClipboard.h"
16
#include "CXWindowsClipboardTextConverter.h"
17
#include "CXWindowsClipboardUCS2Converter.h"
18
#include "CXWindowsClipboardUTF8Converter.h"
19
#include "CXWindowsUtil.h"
22
#include "CStopwatch.h"
24
#include "stdvector.h"
26
#include <X11/Xatom.h>
32
CXWindowsClipboard::CXWindowsClipboard(Display* display,
33
Window window, ClipboardID id) :
44
m_atomTargets = XInternAtom(m_display, "TARGETS", False);
45
m_atomMultiple = XInternAtom(m_display, "MULTIPLE", False);
46
m_atomTimestamp = XInternAtom(m_display, "TIMESTAMP", False);
47
m_atomInteger = XInternAtom(m_display, "INTEGER", False);
48
m_atomAtom = XInternAtom(m_display, "ATOM", False);
49
m_atomAtomPair = XInternAtom(m_display, "ATOM_PAIR", False);
50
m_atomData = XInternAtom(m_display, "CLIP_TEMPORARY", False);
51
m_atomINCR = XInternAtom(m_display, "INCR", False);
52
m_atomMotifClipLock = XInternAtom(m_display, "_MOTIF_CLIP_LOCK", False);
53
m_atomMotifClipHeader = XInternAtom(m_display, "_MOTIF_CLIP_HEADER", False);
54
m_atomMotifClipAccess = XInternAtom(m_display,
55
"_MOTIF_CLIP_LOCK_ACCESS_VALID", False);
56
m_atomGDKSelection = XInternAtom(m_display, "GDK_SELECTION", False);
58
// set selection atom based on clipboard id
60
case kClipboardClipboard:
61
m_selection = XInternAtom(m_display, "CLIPBOARD", False);
64
case kClipboardSelection:
66
m_selection = XA_PRIMARY;
70
// add converters, most desired first
71
m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display,
72
"text/plain;charset=UTF-8"));
73
m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display,
75
m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display,
76
"text/plain;charset=ISO-10646-UCS-2"));
77
m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display,
79
m_converters.push_back(new CXWindowsClipboardTextConverter(m_display,
81
m_converters.push_back(new CXWindowsClipboardTextConverter(m_display,
88
CXWindowsClipboard::~CXWindowsClipboard()
95
CXWindowsClipboard::lost(Time time)
97
LOG((CLOG_DEBUG "lost clipboard %d ownership at %d", m_id, time));
106
CXWindowsClipboard::addRequest(Window owner, Window requestor,
107
Atom target, ::Time time, Atom property)
109
// must be for our window and we must have owned the selection
110
// at the given time.
111
bool success = false;
112
if (owner == m_window) {
113
LOG((CLOG_DEBUG1 "request for clipboard %d, target %d by 0x%08x (property=%d)", m_selection, target, requestor, property));
114
if (wasOwnedAtTime(time)) {
115
if (target == m_atomMultiple) {
116
// add a multiple request. property may not be None
117
// according to ICCCM.
118
if (property != None) {
119
success = insertMultipleReply(requestor, time, property);
123
addSimpleRequest(requestor, target, time, property);
125
// addSimpleRequest() will have already handled failure
130
LOG((CLOG_DEBUG1 "failed, not owned at time %d", time));
136
LOG((CLOG_DEBUG1 "failed"));
137
insertReply(new CReply(requestor, target, time));
140
// send notifications that are pending
145
CXWindowsClipboard::addSimpleRequest(Window requestor,
146
Atom target, ::Time time, Atom property)
148
// obsolete requestors may supply a None property. in
149
// that case we use the target as the property to store
151
if (property == None) {
159
if (target == m_atomTargets) {
160
type = getTargetsData(data, &format);
162
else if (target == m_atomTimestamp) {
163
type = getTimestampData(data, &format);
166
IXWindowsClipboardConverter* converter = getConverter(target);
167
if (converter != NULL) {
168
IClipboard::EFormat clipboardFormat = converter->getFormat();
169
if (m_added[clipboardFormat]) {
171
data = converter->fromIClipboard(m_data[clipboardFormat]);
172
format = converter->getDataSize();
173
type = converter->getAtom();
176
// ignore -- cannot convert
184
LOG((CLOG_DEBUG1 "success"));
185
insertReply(new CReply(requestor, target, time,
186
property, data, type, format));
191
LOG((CLOG_DEBUG1 "failed"));
192
insertReply(new CReply(requestor, target, time));
198
CXWindowsClipboard::processRequest(Window requestor,
199
::Time /*time*/, Atom property)
201
CReplyMap::iterator index = m_replies.find(requestor);
202
if (index == m_replies.end()) {
203
// unknown requestor window
206
LOG((CLOG_DEBUG1 "received property %d delete from 0x08%x", property, requestor));
208
// find the property in the known requests. it should be the
209
// first property but we'll check 'em all if we have to.
210
CReplyList& replies = index->second;
211
for (CReplyList::iterator index2 = replies.begin();
212
index2 != replies.end(); ++index2) {
213
CReply* reply = *index2;
214
if (reply->m_replied && reply->m_property == property) {
215
// if reply is complete then remove it and start the
217
pushReplies(index, replies, index2);
226
CXWindowsClipboard::destroyRequest(Window requestor)
228
CReplyMap::iterator index = m_replies.find(requestor);
229
if (index == m_replies.end()) {
230
// unknown requestor window
234
// destroy all replies for this window
235
clearReplies(index->second);
236
m_replies.erase(index);
238
// note -- we don't stop watching the window for events because
239
// we're called in response to the window being destroyed.
245
CXWindowsClipboard::getWindow() const
251
CXWindowsClipboard::getSelection() const
257
CXWindowsClipboard::empty()
261
LOG((CLOG_DEBUG "empty clipboard %d", m_id));
263
// assert ownership of clipboard
264
XSetSelectionOwner(m_display, m_selection, m_window, m_time);
265
if (XGetSelectionOwner(m_display, m_selection) != m_window) {
266
LOG((CLOG_DEBUG "failed to grab clipboard %d", m_id));
270
// clear all data. since we own the data now, the cache is up
275
// FIXME -- actually delete motif clipboard items?
276
// FIXME -- do anything to motif clipboard properties?
279
m_timeOwned = m_time;
282
// we're the owner now
284
LOG((CLOG_DEBUG "grabbed clipboard %d", m_id));
290
CXWindowsClipboard::add(EFormat format, const CString& data)
295
LOG((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format));
297
m_data[format] = data;
298
m_added[format] = true;
300
// FIXME -- set motif clipboard item?
304
CXWindowsClipboard::open(Time time) const
308
LOG((CLOG_DEBUG "open clipboard %d", m_id));
314
if (m_id == kClipboardClipboard) {
315
if (!motifLockClipboard()) {
319
// check if motif owns the selection. unlock motif clipboard
321
m_motif = motifOwnsClipboard();
322
LOG((CLOG_DEBUG1 "motif does %sown clipboard", m_motif ? "" : "not "));
324
motifUnlockClipboard();
332
// be sure to flush the cache later if it's dirty
339
CXWindowsClipboard::close() const
343
LOG((CLOG_DEBUG "close clipboard %d", m_id));
347
motifUnlockClipboard();
355
CXWindowsClipboard::getTime() const
362
CXWindowsClipboard::has(EFormat format) const
367
return m_added[format];
371
CXWindowsClipboard::get(EFormat format) const
376
return m_data[format];
380
CXWindowsClipboard::clearConverters()
382
for (ConverterList::iterator index = m_converters.begin();
383
index != m_converters.end(); ++index) {
386
m_converters.clear();
389
IXWindowsClipboardConverter*
390
CXWindowsClipboard::getConverter(Atom target, bool onlyIfNotAdded) const
392
IXWindowsClipboardConverter* converter = NULL;
393
for (ConverterList::const_iterator index = m_converters.begin();
394
index != m_converters.end(); ++index) {
396
if (converter->getAtom() == target) {
400
if (converter == NULL) {
401
LOG((CLOG_DEBUG1 " no converter for target %d", target));
405
// optionally skip already handled targets
406
if (onlyIfNotAdded) {
407
if (m_added[converter->getFormat()]) {
408
LOG((CLOG_DEBUG1 " skipping handled format %d", converter->getFormat()));
417
CXWindowsClipboard::checkCache() const
422
m_checkCache = false;
424
// get the time the clipboard ownership was taken by the current
427
m_timeOwned = motifGetTime();
430
m_timeOwned = icccmGetTime();
433
// if we can't get the time then use the time passed to us
434
if (m_timeOwned == 0) {
435
m_timeOwned = m_time;
438
// if the cache is dirty then flush it
439
if (m_timeOwned != m_cacheTime) {
445
CXWindowsClipboard::clearCache() const
447
const_cast<CXWindowsClipboard*>(this)->doClearCache();
451
CXWindowsClipboard::doClearCache()
453
m_checkCache = false;
455
for (SInt32 index = 0; index < kNumFormats; ++index) {
457
m_added[index] = false;
462
CXWindowsClipboard::fillCache() const
464
// get the selection data if not already cached
467
const_cast<CXWindowsClipboard*>(this)->doFillCache();
472
CXWindowsClipboard::doFillCache()
480
m_checkCache = false;
482
m_cacheTime = m_timeOwned;
486
CXWindowsClipboard::icccmFillCache()
488
LOG((CLOG_DEBUG "ICCCM fill clipboard %d", m_id));
490
// see if we can get the list of available formats from the selection.
491
// if not then use a default list of formats. note that some clipboard
492
// owners are broken and report TARGETS as the type of the TARGETS data
493
// instead of the correct type ATOM; allow either.
494
const Atom atomTargets = m_atomTargets;
497
if (!icccmGetSelection(atomTargets, &target, &data) ||
498
(target != m_atomAtom && target != m_atomTargets)) {
499
LOG((CLOG_DEBUG1 "selection doesn't support TARGETS"));
503
data.append(reinterpret_cast<char*>(&target), sizeof(target));
506
// try each converter in order (because they're in order of
508
const Atom* targets = reinterpret_cast<const Atom*>(data.data());
509
const UInt32 numTargets = data.size() / sizeof(Atom);
510
for (ConverterList::const_iterator index = m_converters.begin();
511
index != m_converters.end(); ++index) {
512
IXWindowsClipboardConverter* converter = *index;
514
// skip already handled targets
515
if (m_added[converter->getFormat()]) {
519
// see if atom is in target list
521
for (UInt32 i = 0; i < numTargets; ++i) {
522
if (converter->getAtom() == targets[i]) {
527
if (target == None) {
534
if (!icccmGetSelection(target, &actualTarget, &targetData)) {
535
LOG((CLOG_DEBUG1 " no data for target %d", target));
539
// add to clipboard and note we've done it
540
IClipboard::EFormat format = converter->getFormat();
541
m_data[format] = converter->toIClipboard(targetData);
542
m_added[format] = true;
543
LOG((CLOG_DEBUG " added format %d for target %d", format, target));
548
CXWindowsClipboard::icccmGetSelection(Atom target,
549
Atom* actualTarget, CString* data) const
551
assert(actualTarget != NULL);
552
assert(data != NULL);
554
// request data conversion
555
CICCCMGetClipboard getter(m_window, m_time, m_atomData);
556
if (!getter.readClipboard(m_display, m_selection,
557
target, actualTarget, data)) {
558
LOG((CLOG_DEBUG1 "can't get data for selection target %d", target));
559
LOGC(getter.m_error, (CLOG_WARN "ICCCM violation by clipboard owner"));
562
else if (*actualTarget == None) {
563
LOG((CLOG_DEBUG1 "selection conversion failed for target %d", target));
570
CXWindowsClipboard::icccmGetTime() const
574
if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) &&
575
actualTarget == m_atomInteger) {
576
Time time = *reinterpret_cast<const Time*>(data.data());
577
LOG((CLOG_DEBUG1 "got ICCCM time %d", time));
582
LOG((CLOG_DEBUG1 "can't get ICCCM time"));
588
CXWindowsClipboard::motifLockClipboard() const
590
// fail if anybody owns the lock (even us, so this is non-recursive)
591
Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
592
if (lockOwner != None) {
593
LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner));
597
// try to grab the lock
598
// FIXME -- is this right? there's a race condition here --
599
// A grabs successfully, B grabs successfully, A thinks it
600
// still has the grab until it gets a SelectionClear.
601
Time time = CXWindowsUtil::getCurrentTime(m_display, m_window);
602
XSetSelectionOwner(m_display, m_atomMotifClipLock, m_window, time);
603
lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
604
if (lockOwner != m_window) {
605
LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner));
609
LOG((CLOG_DEBUG1 "locked motif clipboard"));
614
CXWindowsClipboard::motifUnlockClipboard() const
616
LOG((CLOG_DEBUG1 "unlocked motif clipboard"));
618
// fail if we don't own the lock
619
Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
620
if (lockOwner != m_window) {
625
Time time = CXWindowsUtil::getCurrentTime(m_display, m_window);
626
XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time);
630
CXWindowsClipboard::motifOwnsClipboard() const
632
// get the current selection owner
633
// FIXME -- this can't be right. even if the window is destroyed
634
// Motif will still have a valid clipboard. how can we tell if
635
// some other client owns CLIPBOARD?
636
Window owner = XGetSelectionOwner(m_display, m_selection);
641
// get the Motif clipboard header property from the root window
645
Window root = RootWindow(m_display, DefaultScreen(m_display));
646
if (!CXWindowsUtil::getWindowProperty(m_display, root,
647
m_atomMotifClipHeader,
648
&data, &target, &format, False)) {
652
// check the owner window against the current clipboard owner
653
const CMotifClipHeader* header =
654
reinterpret_cast<const CMotifClipHeader*>(data.data());
655
if (data.size() >= sizeof(CMotifClipHeader) &&
656
header->m_id == kMotifClipHeader) {
657
if (header->m_selectionOwner == owner) {
666
CXWindowsClipboard::motifFillCache()
668
LOG((CLOG_DEBUG "Motif fill clipboard %d", m_id));
670
// get the Motif clipboard header property from the root window
674
Window root = RootWindow(m_display, DefaultScreen(m_display));
675
if (!CXWindowsUtil::getWindowProperty(m_display, root,
676
m_atomMotifClipHeader,
677
&data, &target, &format, False)) {
681
// check that the header is okay
682
const CMotifClipHeader* header =
683
reinterpret_cast<const CMotifClipHeader*>(data.data());
684
if (data.size() < sizeof(CMotifClipHeader) ||
685
header->m_id != kMotifClipHeader ||
686
header->m_numItems < 1) {
690
// get the Motif item property from the root window
692
sprintf(name, "_MOTIF_CLIP_ITEM_%d", header->m_item);
693
Atom atomItem = XInternAtom(m_display, name, False);
695
if (!CXWindowsUtil::getWindowProperty(m_display, root,
697
&target, &format, False)) {
701
// check that the item is okay
702
const CMotifClipItem* item =
703
reinterpret_cast<const CMotifClipItem*>(data.data());
704
if (data.size() < sizeof(CMotifClipItem) ||
705
item->m_id != kMotifClipItem ||
706
item->m_numFormats - item->m_numDeletedFormats < 1) {
710
// format list is after static item structure elements
711
const SInt32 numFormats = item->m_numFormats - item->m_numDeletedFormats;
712
const SInt32* formats = reinterpret_cast<const SInt32*>(item->m_size +
713
reinterpret_cast<const char*>(data.data()));
715
// get the available formats
716
typedef std::map<Atom, CString> CMotifFormatMap;
717
CMotifFormatMap motifFormats;
718
for (SInt32 i = 0; i < numFormats; ++i) {
719
// get Motif format property from the root window
720
sprintf(name, "_MOTIF_CLIP_ITEM_%d", formats[i]);
721
Atom atomFormat = XInternAtom(m_display, name, False);
723
if (!CXWindowsUtil::getWindowProperty(m_display, root,
725
&target, &format, False)) {
729
// check that the format is okay
730
const CMotifClipFormat* motifFormat =
731
reinterpret_cast<const CMotifClipFormat*>(data.data());
732
if (data.size() < sizeof(CMotifClipFormat) ||
733
motifFormat->m_id != kMotifClipFormat ||
734
motifFormat->m_length < 0 ||
735
motifFormat->m_type == None ||
736
motifFormat->m_deleted != 0) {
741
motifFormats.insert(std::make_pair(motifFormat->m_type, data));
743
const UInt32 numMotifFormats = motifFormats.size();
745
// try each converter in order (because they're in order of
747
for (ConverterList::const_iterator index = m_converters.begin();
748
index != m_converters.end(); ++index) {
749
IXWindowsClipboardConverter* converter = *index;
751
// skip already handled targets
752
if (m_added[converter->getFormat()]) {
756
// see if atom is in target list
757
CMotifFormatMap::const_iterator index2 =
758
motifFormats.find(converter->getAtom());
759
if (index2 == motifFormats.end()) {
764
const CMotifClipFormat* motifFormat =
765
reinterpret_cast<const CMotifClipFormat*>(
766
index2->second.data());
767
const Atom target = motifFormat->m_type;
769
// get the data (finally)
772
if (!motifGetSelection(motifFormat, &actualTarget, &targetData)) {
773
LOG((CLOG_DEBUG1 " no data for target %d", target));
777
// add to clipboard and note we've done it
778
IClipboard::EFormat format = converter->getFormat();
779
m_data[format] = converter->toIClipboard(targetData);
780
m_added[format] = true;
781
LOG((CLOG_DEBUG " added format %d for target %d", format, target));
786
CXWindowsClipboard::motifGetSelection(const CMotifClipFormat* format,
787
Atom* actualTarget, CString* data) const
789
// if the current clipboard owner and the owner indicated by the
790
// motif clip header are the same then transfer via a property on
791
// the root window, otherwise transfer as a normal ICCCM client.
792
if (!motifOwnsClipboard()) {
793
return icccmGetSelection(format->m_type, actualTarget, data);
797
// FIXME -- this isn't right. it'll only work if the data is
798
// already stored on the root window and only if it fits in a
799
// property. motif has some scheme for transferring part by
800
// part that i don't know.
802
sprintf(name, "_MOTIF_CLIP_ITEM_%d", format->m_data);
803
Atom target = XInternAtom(m_display, name, False);
804
Window root = RootWindow(m_display, DefaultScreen(m_display));
805
return CXWindowsUtil::getWindowProperty(m_display, root,
807
actualTarget, NULL, False);
811
CXWindowsClipboard::motifGetTime() const
813
return icccmGetTime();
817
CXWindowsClipboard::insertMultipleReply(Window requestor,
818
::Time time, Atom property)
820
// get the requested targets
824
if (!CXWindowsUtil::getWindowProperty(m_display, requestor,
825
property, &data, &target, &format, False)) {
826
// can't get the requested targets
830
// fail if the requested targets isn't of the correct form
832
target != m_atomAtomPair) {
836
// data is a list of atom pairs: target, property
837
const Atom* targets = reinterpret_cast<const Atom*>(data.data());
838
const UInt32 numTargets = data.size() / sizeof(Atom);
840
// add replies for each target
841
bool changed = false;
842
for (UInt32 i = 0; i < numTargets; i += 2) {
843
const Atom target = targets[i + 0];
844
const Atom property = targets[i + 1];
845
if (!addSimpleRequest(requestor, target, time, property)) {
846
// note that we can't perform the requested conversion
847
static const Atom none = None;
848
data.replace(i * sizeof(Atom), sizeof(Atom),
849
reinterpret_cast<const char*>(&none),
855
// update the targets property if we changed it
857
CXWindowsUtil::setWindowProperty(m_display, requestor,
858
property, data.data(), data.size(),
862
// add reply for MULTIPLE request
863
insertReply(new CReply(requestor, m_atomMultiple,
864
time, property, CString(), None, 32));
870
CXWindowsClipboard::insertReply(CReply* reply)
872
assert(reply != NULL);
874
// note -- we must respond to requests in order if requestor,target,time
875
// are the same, otherwise we can use whatever order we like with one
876
// exception: each reply in a MULTIPLE reply must be handled in order
877
// as well. those replies will almost certainly not share targets so
878
// we can't simply use requestor,target,time as map index.
880
// instead we'll use just the requestor. that's more restrictive than
881
// necessary but we're guaranteed to do things in the right order.
882
// note that we could also include the time in the map index and still
883
// ensure the right order. but since that'll just make it harder to
884
// find the right reply when handling property notify events we stick
885
// to just the requestor.
887
const bool newWindow = (m_replies.count(reply->m_requestor) == 0);
888
m_replies[reply->m_requestor].push_back(reply);
890
// adjust requestor's event mask if we haven't done so already. we
891
// want events in case the window is destroyed or any of its
892
// properties change.
894
// note errors while we adjust event masks
896
CXWindowsUtil::CErrorLock lock(m_display, &error);
898
// get and save the current event mask
899
XWindowAttributes attr;
900
XGetWindowAttributes(m_display, reply->m_requestor, &attr);
901
m_eventMasks[reply->m_requestor] = attr.your_event_mask;
903
// add the events we want
904
XSelectInput(m_display, reply->m_requestor, attr.your_event_mask |
905
StructureNotifyMask | PropertyChangeMask);
907
// if we failed then the window has already been destroyed
909
m_replies.erase(reply->m_requestor);
916
CXWindowsClipboard::pushReplies()
918
// send the first reply for each window if that reply hasn't
920
for (CReplyMap::iterator index = m_replies.begin();
921
index != m_replies.end(); ++index) {
922
assert(!index->second.empty());
923
if (!index->second.front()->m_replied) {
924
pushReplies(index, index->second, index->second.begin());
930
CXWindowsClipboard::pushReplies(CReplyMap::iterator mapIndex,
931
CReplyList& replies, CReplyList::iterator index)
933
CReply* reply = *index;
934
while (sendReply(reply)) {
935
// reply is complete. discard it and send the next reply,
937
index = replies.erase(index);
939
if (index == replies.end()) {
945
// if there are no more replies in the list then remove the list
946
// and stop watching the requestor for events.
947
if (replies.empty()) {
948
CXWindowsUtil::CErrorLock lock(m_display);
949
Window requestor = mapIndex->first;
950
XSelectInput(m_display, requestor, m_eventMasks[requestor]);
951
m_replies.erase(mapIndex);
952
m_eventMasks.erase(requestor);
957
CXWindowsClipboard::sendReply(CReply* reply)
959
assert(reply != NULL);
961
// bail out immediately if reply is done
963
LOG((CLOG_DEBUG1 "clipboard: finished reply to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
967
// start in failed state if property is None
968
bool failed = (reply->m_property == None);
970
LOG((CLOG_DEBUG1 "clipboard: setting property on 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
972
// send using INCR if already sending incrementally or if reply
973
// is too large, otherwise just send it.
974
const UInt32 maxRequestSize = 3 * XMaxRequestSize(m_display);
975
const bool useINCR = (reply->m_data.size() > maxRequestSize);
977
// send INCR reply if incremental and we haven't replied yet
978
if (useINCR && !reply->m_replied) {
979
UInt32 size = reply->m_data.size();
980
if (!CXWindowsUtil::setWindowProperty(m_display,
981
reply->m_requestor, reply->m_property,
982
&size, 4, m_atomINCR, 32)) {
987
// send more INCR reply or entire non-incremental reply
989
// how much more data should we send?
990
UInt32 size = reply->m_data.size() - reply->m_ptr;
991
if (size > maxRequestSize)
992
size = maxRequestSize;
995
if (!CXWindowsUtil::setWindowProperty(m_display,
996
reply->m_requestor, reply->m_property,
997
reply->m_data.data() + reply->m_ptr,
999
reply->m_type, reply->m_format)) {
1003
reply->m_ptr += size;
1005
// we've finished the reply if we just sent the zero
1006
// size incremental chunk or if we're not incremental.
1007
reply->m_done = (size == 0 || !useINCR);
1012
// if we've failed then delete the property and say we're done.
1013
// if we haven't replied yet then we can send a failure notify,
1014
// otherwise we've failed in the middle of an incremental
1015
// transfer; i don't know how to cancel that so i'll just send
1016
// the final zero-length property.
1017
// FIXME -- how do you gracefully cancel an incremental transfer?
1019
LOG((CLOG_DEBUG1 "clipboard: sending failure to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
1020
reply->m_done = true;
1021
if (reply->m_property != None) {
1022
CXWindowsUtil::CErrorLock lock(m_display);
1023
XDeleteProperty(m_display, reply->m_requestor, reply->m_property);
1026
if (!reply->m_replied) {
1027
sendNotify(reply->m_requestor, m_selection,
1028
reply->m_target, None,
1031
// don't wait for any reply (because we're not expecting one)
1035
static const char dummy = 0;
1036
CXWindowsUtil::setWindowProperty(m_display,
1037
reply->m_requestor, reply->m_property,
1040
reply->m_type, reply->m_format);
1042
// wait for delete notify
1047
// send notification if we haven't yet
1048
if (!reply->m_replied) {
1049
LOG((CLOG_DEBUG1 "clipboard: sending notify to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
1050
reply->m_replied = true;
1052
// dump every property on the requestor window to the debug2
1053
// log. we've seen what appears to be a bug in lesstif and
1054
// knowing the properties may help design a workaround, if
1055
// it becomes necessary.
1056
if (CLOG->getFilter() >= CLog::kDEBUG2) {
1057
CXWindowsUtil::CErrorLock lock(m_display);
1059
Atom* props = XListProperties(m_display, reply->m_requestor, &n);
1060
LOG((CLOG_DEBUG2 "properties of 0x%08x:", reply->m_requestor));
1061
for (int i = 0; i < n; ++i) {
1064
char* name = XGetAtomName(m_display, props[i]);
1065
if (!CXWindowsUtil::getWindowProperty(m_display,
1067
props[i], &data, &target, NULL, False)) {
1068
LOG((CLOG_DEBUG2 " %s: <can't read property>", name));
1071
// if there are any non-ascii characters in string
1072
// then print the binary data.
1073
static const char* hex = "0123456789abcdef";
1074
for (CString::size_type j = 0; j < data.size(); ++j) {
1075
if (data[j] < 32 || data[j] > 126) {
1077
tmp.reserve(data.size() * 3);
1078
for (j = 0; j < data.size(); ++j) {
1079
unsigned char v = (unsigned char)data[j];
1080
tmp += hex[v >> 16];
1088
char* type = XGetAtomName(m_display, target);
1089
LOG((CLOG_DEBUG2 " %s (%s): %s", name, type, data.c_str()));
1098
if (props != NULL) {
1103
sendNotify(reply->m_requestor, m_selection,
1104
reply->m_target, reply->m_property,
1108
// wait for delete notify
1113
CXWindowsClipboard::clearReplies()
1115
for (CReplyMap::iterator index = m_replies.begin();
1116
index != m_replies.end(); ++index) {
1117
clearReplies(index->second);
1120
m_eventMasks.clear();
1124
CXWindowsClipboard::clearReplies(CReplyList& replies)
1126
for (CReplyList::iterator index = replies.begin();
1127
index != replies.end(); ++index) {
1134
CXWindowsClipboard::sendNotify(Window requestor,
1135
Atom selection, Atom target, Atom property, Time time)
1138
event.xselection.type = SelectionNotify;
1139
event.xselection.display = m_display;
1140
event.xselection.requestor = requestor;
1141
event.xselection.selection = selection;
1142
event.xselection.target = target;
1143
event.xselection.property = property;
1144
event.xselection.time = time;
1145
CXWindowsUtil::CErrorLock lock(m_display);
1146
XSendEvent(m_display, requestor, False, 0, &event);
1150
CXWindowsClipboard::wasOwnedAtTime(::Time time) const
1152
// not owned if we've never owned the selection
1154
if (m_timeOwned == 0) {
1158
// if time is CurrentTime then return true if we still own the
1159
// selection and false if we do not. else if we still own the
1160
// selection then get the current time, otherwise use
1161
// m_timeLost as the end time.
1162
Time lost = m_timeLost;
1163
if (m_timeLost == 0) {
1164
if (time == CurrentTime) {
1168
lost = CXWindowsUtil::getCurrentTime(m_display, m_window);
1172
if (time == CurrentTime) {
1177
// compare time to range
1178
Time duration = lost - m_timeOwned;
1179
Time when = time - m_timeOwned;
1180
return (/*when >= 0 &&*/ when < duration);
1184
CXWindowsClipboard::getTargetsData(CString& data, int* format) const
1186
assert(format != NULL);
1188
// add standard targets
1190
atom = m_atomTargets;
1191
data.append(reinterpret_cast<char*>(&atom), sizeof(Atom));
1192
atom = m_atomMultiple;
1193
data.append(reinterpret_cast<char*>(&atom), sizeof(Atom));
1194
atom = m_atomTimestamp;
1195
data.append(reinterpret_cast<char*>(&atom), sizeof(Atom));
1197
// add targets we can convert to
1198
for (ConverterList::const_iterator index = m_converters.begin();
1199
index != m_converters.end(); ++index) {
1200
IXWindowsClipboardConverter* converter = *index;
1202
// skip formats we don't have
1203
if (m_added[converter->getFormat()]) {
1204
atom = converter->getAtom();
1205
data.append(reinterpret_cast<char*>(&atom), sizeof(Atom));
1214
CXWindowsClipboard::getTimestampData(CString& data, int* format) const
1216
assert(format != NULL);
1218
assert(sizeof(m_timeOwned) == 4);
1220
data.append(reinterpret_cast<const char*>(&m_timeOwned), 4);
1222
return m_atomInteger;
1227
// CXWindowsClipboard::CICCCMGetClipboard
1230
CXWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard(
1231
Window requestor, Time time, Atom property) :
1232
m_requestor(requestor),
1234
m_property(property),
1240
m_actualTarget(NULL),
1246
CXWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard()
1252
CXWindowsClipboard::CICCCMGetClipboard::readClipboard(Display* display,
1253
Atom selection, Atom target, Atom* actualTarget, CString* data)
1255
assert(actualTarget != NULL);
1256
assert(data != NULL);
1258
LOG((CLOG_DEBUG1 "request selection=%d, target=%d, window=%x", selection, target, m_requestor));
1260
// save output pointers
1261
m_actualTarget = actualTarget;
1265
*m_actualTarget = None;
1268
// delete target property
1269
XDeleteProperty(display, m_requestor, m_property);
1271
// select window for property changes
1272
XWindowAttributes attr;
1273
XGetWindowAttributes(display, m_requestor, &attr);
1274
XSelectInput(display, m_requestor,
1275
attr.your_event_mask | PropertyChangeMask);
1277
// request data conversion
1278
XConvertSelection(display, selection, target,
1279
m_property, m_requestor, m_time);
1281
// synchronize with server before we start following timeout countdown
1282
XSync(display, False);
1284
// Xlib inexplicably omits the ability to wait for an event with
1285
// a timeout. (it's inexplicable because there's no portable way
1286
// to do it.) we'll poll until we have what we're looking for or
1287
// a timeout expires. we use a timeout so we don't get locked up
1288
// by badly behaved selection owners.
1290
std::vector<XEvent> events;
1291
CStopwatch timeout(true);
1292
static const double s_timeout = 0.25; // FIXME -- is this too short?
1293
bool noWait = false;
1294
while (!m_done && !m_failed) {
1295
// fail if timeout has expired
1296
if (timeout.getTime() >= s_timeout) {
1301
// process events if any otherwise sleep
1302
if (noWait || XPending(display) > 0) {
1303
while (!m_done && !m_failed && (noWait || XPending(display) > 0)) {
1304
XNextEvent(display, &xevent);
1305
if (!processEvent(display, &xevent)) {
1306
// not processed so save it
1307
events.push_back(xevent);
1310
// reset timer since we've made some progress
1313
// don't sleep anymore, just block waiting for events.
1314
// we're assuming here that the clipboard owner will
1315
// complete the protocol correctly. if we continue to
1316
// sleep we'll get very bad performance.
1326
// put unprocessed events back
1327
for (UInt32 i = events.size(); i > 0; --i) {
1328
XPutBackEvent(display, &events[i - 1]);
1332
XSelectInput(display, m_requestor, attr.your_event_mask);
1334
// return success or failure
1335
LOG((CLOG_DEBUG1 "request %s", m_failed ? "failed" : "succeeded"));
1340
CXWindowsClipboard::CICCCMGetClipboard::processEvent(
1341
Display* display, XEvent* xevent)
1344
switch (xevent->type) {
1346
if (xevent->xdestroywindow.window == m_requestor) {
1354
case SelectionNotify:
1355
if (xevent->xselection.requestor == m_requestor) {
1356
// done if we can't convert
1357
if (xevent->xselection.property == None) {
1362
// proceed if conversion successful
1363
else if (xevent->xselection.property == m_property) {
1369
// otherwise not interested
1372
case PropertyNotify:
1373
// proceed if conversion successful and we're receiving more data
1374
if (xevent->xproperty.window == m_requestor &&
1375
xevent->xproperty.atom == m_property &&
1376
xevent->xproperty.state == PropertyNewValue) {
1378
// we haven't gotten the SelectionNotify yet
1384
// otherwise not interested
1392
// get the data from the property
1394
const CString::size_type oldSize = m_data->size();
1395
if (!CXWindowsUtil::getWindowProperty(display, m_requestor,
1396
m_property, m_data, &target, NULL, True)) {
1397
// unable to read property
1402
// note if incremental. if we're already incremental then the
1403
// selection owner is busted. if the INCR property has no size
1404
// then the selection owner is busted.
1405
if (target == XInternAtom(display, "INCR", False)) {
1410
else if (m_data->size() == oldSize) {
1417
// discard INCR data
1422
// handle incremental chunks
1424
// if first incremental chunk then save target
1426
LOG((CLOG_DEBUG1 " INCR first chunk, target %d", target));
1427
*m_actualTarget = target;
1430
// secondary chunks must have the same target
1432
if (target != *m_actualTarget) {
1433
LOG((CLOG_WARN " INCR target mismatch"));
1439
// note if this is the final chunk
1440
if (m_data->size() == oldSize) {
1441
LOG((CLOG_DEBUG1 " INCR final chunk: %d bytes total", m_data->size()));
1446
// not incremental; save the target.
1448
LOG((CLOG_DEBUG1 " target %d", target));
1449
*m_actualTarget = target;
1453
// this event has been processed
1454
LOGC(!m_incr, (CLOG_DEBUG1 " got data, %d bytes", m_data->size()));
1460
// CXWindowsClipboard::CReply
1463
CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time) :
1464
m_requestor(requestor),
1478
CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time,
1479
Atom property, const CString& data, Atom type, int format) :
1480
m_requestor(requestor),
1483
m_property(property),