~nobuto/ubuntu/natty/synergy/merge-from-experimental

« back to all changes in this revision

Viewing changes to lib/platform/CXWindowsClipboard.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Daniel Lutz
  • Date: 2003-10-31 19:36:30 UTC
  • Revision ID: james.westby@ubuntu.com-20031031193630-knbv79x5az7qh49y
Tags: upstream-1.0.14
ImportĀ upstreamĀ versionĀ 1.0.14

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 "CXWindowsUtil.h"
 
20
#include "CThread.h"
 
21
#include "CLog.h"
 
22
#include "CStopwatch.h"
 
23
#include "CArch.h"
 
24
#include "stdvector.h"
 
25
#include <cstdio>
 
26
#include <X11/Xatom.h>
 
27
 
 
28
//
 
29
// CXWindowsClipboard
 
30
//
 
31
 
 
32
CXWindowsClipboard::CXWindowsClipboard(Display* display,
 
33
                                Window window, ClipboardID id) :
 
34
        m_display(display),
 
35
        m_window(window),
 
36
        m_id(id),
 
37
        m_open(false),
 
38
        m_time(0),
 
39
        m_owner(false),
 
40
        m_timeOwned(0),
 
41
        m_timeLost(0)
 
42
{
 
43
        // get some atoms
 
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);
 
57
 
 
58
        // set selection atom based on clipboard id
 
59
        switch (id) {
 
60
        case kClipboardClipboard:
 
61
                m_selection = XInternAtom(m_display, "CLIPBOARD", False);
 
62
                break;
 
63
 
 
64
        case kClipboardSelection:
 
65
        default:
 
66
                m_selection = XA_PRIMARY;
 
67
                break;
 
68
        }
 
69
 
 
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,
 
74
                                                                "UTF8_STRING"));
 
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,
 
78
                                                                "text/unicode"));
 
79
        m_converters.push_back(new CXWindowsClipboardTextConverter(m_display,
 
80
                                                                "text/plain"));
 
81
        m_converters.push_back(new CXWindowsClipboardTextConverter(m_display,
 
82
                                                                "STRING"));
 
83
 
 
84
        // we have no data
 
85
        clearCache();
 
86
}
 
87
 
 
88
CXWindowsClipboard::~CXWindowsClipboard()
 
89
{
 
90
        clearReplies();
 
91
        clearConverters();
 
92
}
 
93
 
 
94
void
 
95
CXWindowsClipboard::lost(Time time)
 
96
{
 
97
        LOG((CLOG_DEBUG "lost clipboard %d ownership at %d", m_id, time));
 
98
        if (m_owner) {
 
99
                m_owner    = false;
 
100
                m_timeLost = time;
 
101
                clearCache();
 
102
        }
 
103
}
 
104
 
 
105
void
 
106
CXWindowsClipboard::addRequest(Window owner, Window requestor,
 
107
                                Atom target, ::Time time, Atom property)
 
108
{
 
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);
 
120
                                }
 
121
                        }
 
122
                        else {
 
123
                                addSimpleRequest(requestor, target, time, property);
 
124
 
 
125
                                // addSimpleRequest() will have already handled failure
 
126
                                success = true;
 
127
                        }
 
128
                }
 
129
                else {
 
130
                        LOG((CLOG_DEBUG1 "failed, not owned at time %d", time));
 
131
                }
 
132
        }
 
133
 
 
134
        if (!success) {
 
135
                // send failure
 
136
                LOG((CLOG_DEBUG1 "failed"));
 
137
                insertReply(new CReply(requestor, target, time));
 
138
        }
 
139
 
 
140
        // send notifications that are pending
 
141
        pushReplies();
 
142
}
 
143
 
 
144
bool
 
145
CXWindowsClipboard::addSimpleRequest(Window requestor,
 
146
                                Atom target, ::Time time, Atom property)
 
147
{
 
148
        // obsolete requestors may supply a None property.  in
 
149
        // that case we use the target as the property to store
 
150
        // the conversion.
 
151
        if (property == None) {
 
152
                property = target;
 
153
        }
 
154
 
 
155
        // handle targets
 
156
        CString data;
 
157
        Atom type  = None;
 
158
        int format = 0;
 
159
        if (target == m_atomTargets) {
 
160
                type = getTargetsData(data, &format);
 
161
        }
 
162
        else if (target == m_atomTimestamp) {
 
163
                type = getTimestampData(data, &format);
 
164
        }
 
165
        else {
 
166
                IXWindowsClipboardConverter* converter = getConverter(target);
 
167
                if (converter != NULL) {
 
168
                        IClipboard::EFormat clipboardFormat = converter->getFormat();
 
169
                        if (m_added[clipboardFormat]) {
 
170
                                try {
 
171
                                        data   = converter->fromIClipboard(m_data[clipboardFormat]);
 
172
                                        format = converter->getDataSize();
 
173
                                        type   = converter->getAtom();
 
174
                                }
 
175
                                catch (...) {
 
176
                                        // ignore -- cannot convert
 
177
                                }
 
178
                        }
 
179
                }
 
180
        }
 
181
 
 
182
        if (type != None) {
 
183
                // success
 
184
                LOG((CLOG_DEBUG1 "success"));
 
185
                insertReply(new CReply(requestor, target, time,
 
186
                                                                property, data, type, format));
 
187
                return true;
 
188
        }
 
189
        else {
 
190
                // failure
 
191
                LOG((CLOG_DEBUG1 "failed"));
 
192
                insertReply(new CReply(requestor, target, time));
 
193
                return false;
 
194
        }
 
195
}
 
196
 
 
197
bool
 
198
CXWindowsClipboard::processRequest(Window requestor,
 
199
                                ::Time /*time*/, Atom property)
 
200
{
 
201
        CReplyMap::iterator index = m_replies.find(requestor);
 
202
        if (index == m_replies.end()) {
 
203
                // unknown requestor window
 
204
                return false;
 
205
        }
 
206
        LOG((CLOG_DEBUG1 "received property %d delete from 0x08%x", property, requestor));
 
207
 
 
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
 
216
                        // next one.
 
217
                        pushReplies(index, replies, index2);
 
218
                        return true;
 
219
                }
 
220
        }
 
221
 
 
222
        return false;
 
223
}
 
224
 
 
225
bool
 
226
CXWindowsClipboard::destroyRequest(Window requestor)
 
227
{
 
228
        CReplyMap::iterator index = m_replies.find(requestor);
 
229
        if (index == m_replies.end()) {
 
230
                // unknown requestor window
 
231
                return false;
 
232
        }
 
233
 
 
234
        // destroy all replies for this window
 
235
        clearReplies(index->second);
 
236
        m_replies.erase(index);
 
237
 
 
238
        // note -- we don't stop watching the window for events because
 
239
        // we're called in response to the window being destroyed.
 
240
 
 
241
        return true;
 
242
}
 
243
 
 
244
Window
 
245
CXWindowsClipboard::getWindow() const
 
246
{
 
247
        return m_window;
 
248
}
 
249
 
 
250
Atom
 
251
CXWindowsClipboard::getSelection() const
 
252
{
 
253
        return m_selection;
 
254
}
 
255
 
 
256
bool
 
257
CXWindowsClipboard::empty()
 
258
{
 
259
        assert(m_open);
 
260
 
 
261
        LOG((CLOG_DEBUG "empty clipboard %d", m_id));
 
262
 
 
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));
 
267
                return false;
 
268
        }
 
269
 
 
270
        // clear all data.  since we own the data now, the cache is up
 
271
        // to date.
 
272
        clearCache();
 
273
        m_cached = true;
 
274
 
 
275
        // FIXME -- actually delete motif clipboard items?
 
276
        // FIXME -- do anything to motif clipboard properties?
 
277
 
 
278
        // save time
 
279
        m_timeOwned = m_time;
 
280
        m_timeLost  = 0;
 
281
 
 
282
        // we're the owner now
 
283
        m_owner = true;
 
284
        LOG((CLOG_DEBUG "grabbed clipboard %d", m_id));
 
285
 
 
286
        return true;
 
287
}
 
288
 
 
289
void
 
290
CXWindowsClipboard::add(EFormat format, const CString& data)
 
291
{
 
292
        assert(m_open);
 
293
        assert(m_owner);
 
294
 
 
295
        LOG((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format));
 
296
 
 
297
        m_data[format]  = data;
 
298
        m_added[format] = true;
 
299
 
 
300
        // FIXME -- set motif clipboard item?
 
301
}
 
302
 
 
303
bool
 
304
CXWindowsClipboard::open(Time time) const
 
305
{
 
306
        assert(!m_open);
 
307
 
 
308
        LOG((CLOG_DEBUG "open clipboard %d", m_id));
 
309
 
 
310
        // assume not motif
 
311
        m_motif = false;
 
312
 
 
313
        // lock clipboard
 
314
        if (m_id == kClipboardClipboard) {
 
315
                if (!motifLockClipboard()) {
 
316
                        return false;
 
317
                }
 
318
 
 
319
                // check if motif owns the selection.  unlock motif clipboard
 
320
                // if it does not.
 
321
                m_motif = motifOwnsClipboard();
 
322
                LOG((CLOG_DEBUG1 "motif does %sown clipboard", m_motif ? "" : "not "));
 
323
                if (!m_motif) {
 
324
                        motifUnlockClipboard();
 
325
                }
 
326
        }
 
327
 
 
328
        // now open
 
329
        m_open = true;
 
330
        m_time = time;
 
331
 
 
332
        // be sure to flush the cache later if it's dirty
 
333
        m_checkCache = true;
 
334
 
 
335
        return true;
 
336
}
 
337
 
 
338
void
 
339
CXWindowsClipboard::close() const
 
340
{
 
341
        assert(m_open);
 
342
 
 
343
        LOG((CLOG_DEBUG "close clipboard %d", m_id));
 
344
 
 
345
        // unlock clipboard
 
346
        if (m_motif) {
 
347
                motifUnlockClipboard();
 
348
        }
 
349
 
 
350
        m_motif = false;
 
351
        m_open  = false;
 
352
}
 
353
 
 
354
IClipboard::Time
 
355
CXWindowsClipboard::getTime() const
 
356
{
 
357
        checkCache();
 
358
        return m_timeOwned;
 
359
}
 
360
 
 
361
bool
 
362
CXWindowsClipboard::has(EFormat format) const
 
363
{
 
364
        assert(m_open);
 
365
 
 
366
        fillCache();
 
367
        return m_added[format];
 
368
}
 
369
 
 
370
CString
 
371
CXWindowsClipboard::get(EFormat format) const
 
372
{
 
373
        assert(m_open);
 
374
 
 
375
        fillCache();
 
376
        return m_data[format];
 
377
}
 
378
 
 
379
void
 
380
CXWindowsClipboard::clearConverters()
 
381
{
 
382
        for (ConverterList::iterator index = m_converters.begin();
 
383
                                                                index != m_converters.end(); ++index) {
 
384
                delete *index;
 
385
        }
 
386
        m_converters.clear();
 
387
}
 
388
 
 
389
IXWindowsClipboardConverter*
 
390
CXWindowsClipboard::getConverter(Atom target, bool onlyIfNotAdded) const
 
391
{
 
392
        IXWindowsClipboardConverter* converter = NULL;
 
393
        for (ConverterList::const_iterator index = m_converters.begin();
 
394
                                                                index != m_converters.end(); ++index) {
 
395
                converter = *index;
 
396
                if (converter->getAtom() == target) {
 
397
                        break;
 
398
                }
 
399
        }
 
400
        if (converter == NULL) {
 
401
                LOG((CLOG_DEBUG1 "  no converter for target %d", target));
 
402
                return NULL;
 
403
        }
 
404
 
 
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()));
 
409
                        return NULL;
 
410
                }
 
411
        }
 
412
 
 
413
        return converter;
 
414
}
 
415
 
 
416
void
 
417
CXWindowsClipboard::checkCache() const
 
418
{
 
419
        if (!m_checkCache) {
 
420
                return;
 
421
        }
 
422
        m_checkCache = false;
 
423
 
 
424
        // get the time the clipboard ownership was taken by the current
 
425
        // owner.
 
426
        if (m_motif) {
 
427
                m_timeOwned = motifGetTime();
 
428
        }
 
429
        else {
 
430
                m_timeOwned = icccmGetTime();
 
431
        }
 
432
 
 
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;
 
436
        }
 
437
 
 
438
        // if the cache is dirty then flush it
 
439
        if (m_timeOwned != m_cacheTime) {
 
440
                clearCache();
 
441
        }
 
442
}
 
443
 
 
444
void
 
445
CXWindowsClipboard::clearCache() const
 
446
{
 
447
        const_cast<CXWindowsClipboard*>(this)->doClearCache();
 
448
}
 
449
 
 
450
void
 
451
CXWindowsClipboard::doClearCache()
 
452
{
 
453
        m_checkCache = false;
 
454
        m_cached     = false;
 
455
        for (SInt32 index = 0; index < kNumFormats; ++index) {
 
456
                m_data[index]  = "";
 
457
                m_added[index] = false;
 
458
        }
 
459
}
 
460
 
 
461
void
 
462
CXWindowsClipboard::fillCache() const
 
463
{
 
464
        // get the selection data if not already cached
 
465
        checkCache();
 
466
        if (!m_cached) {
 
467
                const_cast<CXWindowsClipboard*>(this)->doFillCache();
 
468
        }
 
469
}
 
470
 
 
471
void
 
472
CXWindowsClipboard::doFillCache()
 
473
{
 
474
        if (m_motif) {
 
475
                motifFillCache();
 
476
        }
 
477
        else {
 
478
                icccmFillCache();
 
479
        }
 
480
        m_checkCache = false;
 
481
        m_cached     = true;
 
482
        m_cacheTime  = m_timeOwned;
 
483
}
 
484
 
 
485
void
 
486
CXWindowsClipboard::icccmFillCache()
 
487
{
 
488
        LOG((CLOG_DEBUG "ICCCM fill clipboard %d", m_id));
 
489
 
 
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;
 
495
        Atom target;
 
496
        CString data;
 
497
        if (!icccmGetSelection(atomTargets, &target, &data) ||
 
498
                (target != m_atomAtom && target != m_atomTargets)) {
 
499
                LOG((CLOG_DEBUG1 "selection doesn't support TARGETS"));
 
500
                data = "";
 
501
 
 
502
                target = XA_STRING;
 
503
                data.append(reinterpret_cast<char*>(&target), sizeof(target));
 
504
        }
 
505
 
 
506
        // try each converter in order (because they're in order of
 
507
        // preference).
 
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;
 
513
 
 
514
                // skip already handled targets
 
515
                if (m_added[converter->getFormat()]) {
 
516
                        continue;
 
517
                }
 
518
 
 
519
                // see if atom is in target list
 
520
                Atom target = None;
 
521
                for (UInt32 i = 0; i < numTargets; ++i) {
 
522
                        if (converter->getAtom() == targets[i]) {
 
523
                                target = targets[i];
 
524
                                break;
 
525
                        }
 
526
                }
 
527
                if (target == None) {
 
528
                        continue;
 
529
                }
 
530
 
 
531
                // get the data
 
532
                Atom actualTarget;
 
533
                CString targetData;
 
534
                if (!icccmGetSelection(target, &actualTarget, &targetData)) {
 
535
                        LOG((CLOG_DEBUG1 "  no data for target %d", target));
 
536
                        continue;
 
537
                }
 
538
 
 
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));
 
544
        }
 
545
}
 
546
 
 
547
bool
 
548
CXWindowsClipboard::icccmGetSelection(Atom target,
 
549
                                Atom* actualTarget, CString* data) const
 
550
{
 
551
        assert(actualTarget != NULL);
 
552
        assert(data         != NULL);
 
553
 
 
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"));
 
560
                return false;
 
561
        }
 
562
        else if (*actualTarget == None) {
 
563
                LOG((CLOG_DEBUG1 "selection conversion failed for target %d", target));
 
564
                return false;
 
565
        }
 
566
        return true;
 
567
}
 
568
 
 
569
IClipboard::Time
 
570
CXWindowsClipboard::icccmGetTime() const
 
571
{
 
572
        Atom actualTarget;
 
573
        CString data;
 
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));
 
578
                return time;
 
579
        }
 
580
        else {
 
581
                // no timestamp
 
582
                LOG((CLOG_DEBUG1 "can't get ICCCM time"));
 
583
                return 0;
 
584
        }
 
585
}
 
586
 
 
587
bool
 
588
CXWindowsClipboard::motifLockClipboard() const
 
589
{
 
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));
 
594
                return false;
 
595
        }
 
596
 
 
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));
 
606
                return false;
 
607
        }
 
608
 
 
609
        LOG((CLOG_DEBUG1 "locked motif clipboard"));
 
610
        return true;
 
611
}
 
612
 
 
613
void
 
614
CXWindowsClipboard::motifUnlockClipboard() const
 
615
{
 
616
        LOG((CLOG_DEBUG1 "unlocked motif clipboard"));
 
617
 
 
618
        // fail if we don't own the lock
 
619
        Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
 
620
        if (lockOwner != m_window) {
 
621
                return;
 
622
        }
 
623
 
 
624
        // release lock
 
625
        Time time = CXWindowsUtil::getCurrentTime(m_display, m_window);
 
626
        XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time);
 
627
}
 
628
 
 
629
bool
 
630
CXWindowsClipboard::motifOwnsClipboard() const
 
631
{
 
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);
 
637
        if (owner == None) {
 
638
                return false;
 
639
        }
 
640
 
 
641
        // get the Motif clipboard header property from the root window
 
642
        Atom target;
 
643
        SInt32 format;
 
644
        CString data;
 
645
        Window root = RootWindow(m_display, DefaultScreen(m_display));
 
646
        if (!CXWindowsUtil::getWindowProperty(m_display, root,
 
647
                                                                m_atomMotifClipHeader,
 
648
                                                                &data, &target, &format, False)) {
 
649
                return false;
 
650
        }
 
651
 
 
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) {
 
658
                        return true;
 
659
                }
 
660
        }
 
661
 
 
662
        return false;
 
663
}
 
664
 
 
665
void
 
666
CXWindowsClipboard::motifFillCache()
 
667
{
 
668
        LOG((CLOG_DEBUG "Motif fill clipboard %d", m_id));
 
669
 
 
670
        // get the Motif clipboard header property from the root window
 
671
        Atom target;
 
672
        SInt32 format;
 
673
        CString data;
 
674
        Window root = RootWindow(m_display, DefaultScreen(m_display));
 
675
        if (!CXWindowsUtil::getWindowProperty(m_display, root,
 
676
                                                                m_atomMotifClipHeader,
 
677
                                                                &data, &target, &format, False)) {
 
678
                return;
 
679
        }
 
680
 
 
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) {
 
687
                return;
 
688
        }
 
689
 
 
690
        // get the Motif item property from the root window
 
691
        char name[18 + 20];
 
692
        sprintf(name, "_MOTIF_CLIP_ITEM_%d", header->m_item);
 
693
    Atom atomItem = XInternAtom(m_display, name, False);
 
694
        data = "";
 
695
        if (!CXWindowsUtil::getWindowProperty(m_display, root,
 
696
                                                                atomItem, &data,
 
697
                                                                &target, &format, False)) {
 
698
                return;
 
699
        }
 
700
 
 
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) {
 
707
                return;
 
708
        }
 
709
 
 
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()));
 
714
 
 
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);
 
722
                CString data;
 
723
                if (!CXWindowsUtil::getWindowProperty(m_display, root,
 
724
                                                                        atomFormat, &data,
 
725
                                                                        &target, &format, False)) {
 
726
                        continue;
 
727
                }
 
728
 
 
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) {
 
737
                        continue;
 
738
                }
 
739
 
 
740
                // save it
 
741
                motifFormats.insert(std::make_pair(motifFormat->m_type, data));
 
742
        }
 
743
        const UInt32 numMotifFormats = motifFormats.size();
 
744
 
 
745
        // try each converter in order (because they're in order of
 
746
        // preference).
 
747
        for (ConverterList::const_iterator index = m_converters.begin();
 
748
                                                                index != m_converters.end(); ++index) {
 
749
                IXWindowsClipboardConverter* converter = *index;
 
750
 
 
751
                // skip already handled targets
 
752
                if (m_added[converter->getFormat()]) {
 
753
                        continue;
 
754
                }
 
755
 
 
756
                // see if atom is in target list
 
757
                CMotifFormatMap::const_iterator index2 =
 
758
                                                                motifFormats.find(converter->getAtom());
 
759
                if (index2 == motifFormats.end()) {
 
760
                        continue;
 
761
                }
 
762
 
 
763
                // get format
 
764
                const CMotifClipFormat* motifFormat =
 
765
                                                                reinterpret_cast<const CMotifClipFormat*>(
 
766
                                                                        index2->second.data());
 
767
                const Atom target                   = motifFormat->m_type;
 
768
 
 
769
                // get the data (finally)
 
770
                Atom actualTarget;
 
771
                CString targetData;
 
772
                if (!motifGetSelection(motifFormat, &actualTarget, &targetData)) {
 
773
                        LOG((CLOG_DEBUG1 "  no data for target %d", target));
 
774
                        continue;
 
775
                }
 
776
 
 
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));
 
782
        }
 
783
}
 
784
 
 
785
bool
 
786
CXWindowsClipboard::motifGetSelection(const CMotifClipFormat* format,
 
787
                                                        Atom* actualTarget, CString* data) const
 
788
{
 
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);
 
794
        }
 
795
 
 
796
        // use motif way
 
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.
 
801
        char name[18 + 20];
 
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,
 
806
                                                                target, data,
 
807
                                                                actualTarget, NULL, False);
 
808
}
 
809
 
 
810
IClipboard::Time
 
811
CXWindowsClipboard::motifGetTime() const
 
812
{
 
813
        return icccmGetTime();
 
814
}
 
815
 
 
816
bool
 
817
CXWindowsClipboard::insertMultipleReply(Window requestor,
 
818
                                ::Time time, Atom property)
 
819
{
 
820
        // get the requested targets
 
821
        Atom target;
 
822
        SInt32 format;
 
823
        CString data;
 
824
        if (!CXWindowsUtil::getWindowProperty(m_display, requestor,
 
825
                                                                property, &data, &target, &format, False)) {
 
826
                // can't get the requested targets
 
827
                return false;
 
828
        }
 
829
 
 
830
        // fail if the requested targets isn't of the correct form
 
831
        if (format != 32 ||
 
832
                target != m_atomAtomPair) {
 
833
                return false;
 
834
        }
 
835
 
 
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);
 
839
 
 
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),
 
850
                                                                sizeof(Atom));
 
851
                        changed = true;
 
852
                }
 
853
        }
 
854
 
 
855
        // update the targets property if we changed it
 
856
        if (changed) {
 
857
                CXWindowsUtil::setWindowProperty(m_display, requestor,
 
858
                                                                property, data.data(), data.size(),
 
859
                                                                target, format);
 
860
        }
 
861
 
 
862
        // add reply for MULTIPLE request
 
863
        insertReply(new CReply(requestor, m_atomMultiple,
 
864
                                                                time, property, CString(), None, 32));
 
865
 
 
866
        return true;
 
867
}
 
868
 
 
869
void
 
870
CXWindowsClipboard::insertReply(CReply* reply)
 
871
{
 
872
        assert(reply != NULL);
 
873
 
 
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.
 
879
        //
 
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.
 
886
 
 
887
        const bool newWindow = (m_replies.count(reply->m_requestor) == 0);
 
888
        m_replies[reply->m_requestor].push_back(reply);
 
889
 
 
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.
 
893
        if (newWindow) {
 
894
                // note errors while we adjust event masks
 
895
                bool error = false;
 
896
                CXWindowsUtil::CErrorLock lock(m_display, &error);
 
897
 
 
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;
 
902
 
 
903
                // add the events we want
 
904
                XSelectInput(m_display, reply->m_requestor, attr.your_event_mask |
 
905
                                                                StructureNotifyMask | PropertyChangeMask);
 
906
 
 
907
                // if we failed then the window has already been destroyed
 
908
                if (error) {
 
909
                        m_replies.erase(reply->m_requestor);
 
910
                        delete reply;
 
911
                }
 
912
        }
 
913
}
 
914
 
 
915
void
 
916
CXWindowsClipboard::pushReplies()
 
917
{
 
918
        // send the first reply for each window if that reply hasn't
 
919
        // been sent yet.
 
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());
 
925
                }
 
926
        }
 
927
}
 
928
 
 
929
void
 
930
CXWindowsClipboard::pushReplies(CReplyMap::iterator mapIndex,
 
931
                                CReplyList& replies, CReplyList::iterator index)
 
932
{
 
933
        CReply* reply = *index;
 
934
        while (sendReply(reply)) {
 
935
                // reply is complete.  discard it and send the next reply,
 
936
                // if any.
 
937
                index = replies.erase(index);
 
938
                delete reply;
 
939
                if (index == replies.end()) {
 
940
                        break;
 
941
                }
 
942
                reply = *index;
 
943
        }
 
944
 
 
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);
 
953
        }
 
954
}
 
955
 
 
956
bool
 
957
CXWindowsClipboard::sendReply(CReply* reply)
 
958
{
 
959
        assert(reply != NULL);
 
960
 
 
961
        // bail out immediately if reply is done
 
962
        if (reply->m_done) {
 
963
                LOG((CLOG_DEBUG1 "clipboard: finished reply to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
 
964
                return true;
 
965
        }
 
966
 
 
967
        // start in failed state if property is None
 
968
        bool failed = (reply->m_property == None);
 
969
        if (!failed) {
 
970
                LOG((CLOG_DEBUG1 "clipboard: setting property on 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
 
971
 
 
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);
 
976
 
 
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)) {
 
983
                                failed = true;
 
984
                        }
 
985
                }
 
986
 
 
987
                // send more INCR reply or entire non-incremental reply
 
988
                else {
 
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;
 
993
 
 
994
                        // send it
 
995
                        if (!CXWindowsUtil::setWindowProperty(m_display,
 
996
                                                                reply->m_requestor, reply->m_property,
 
997
                                                                reply->m_data.data() + reply->m_ptr,
 
998
                                                                size,
 
999
                                                                reply->m_type, reply->m_format)) {
 
1000
                                failed = true;
 
1001
                        }
 
1002
                        else {
 
1003
                                reply->m_ptr += size;
 
1004
 
 
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);
 
1008
                        }
 
1009
                }
 
1010
        }
 
1011
 
 
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?
 
1018
        if (failed) {
 
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);
 
1024
                }
 
1025
 
 
1026
                if (!reply->m_replied) {
 
1027
                        sendNotify(reply->m_requestor, m_selection,
 
1028
                                                                reply->m_target, None,
 
1029
                                                                reply->m_time);
 
1030
 
 
1031
                        // don't wait for any reply (because we're not expecting one)
 
1032
                        return true;
 
1033
                }
 
1034
                else {
 
1035
                        static const char dummy = 0;
 
1036
                        CXWindowsUtil::setWindowProperty(m_display,
 
1037
                                                                reply->m_requestor, reply->m_property,
 
1038
                                                                &dummy,
 
1039
                                                                0,
 
1040
                                                                reply->m_type, reply->m_format);
 
1041
 
 
1042
                        // wait for delete notify
 
1043
                        return false;
 
1044
                }
 
1045
        }
 
1046
 
 
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;
 
1051
 
 
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);
 
1058
                        int n;
 
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) {
 
1062
                                Atom target;
 
1063
                                CString data;
 
1064
                                char* name = XGetAtomName(m_display, props[i]);
 
1065
                                if (!CXWindowsUtil::getWindowProperty(m_display,
 
1066
                                                                reply->m_requestor,
 
1067
                                                                props[i], &data, &target, NULL, False)) {
 
1068
                                        LOG((CLOG_DEBUG2 "  %s: <can't read property>", name));
 
1069
                                }
 
1070
                                else {
 
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) {
 
1076
                                                        CString tmp;
 
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];
 
1081
                                                                tmp += hex[v & 15];
 
1082
                                                                tmp += ' ';
 
1083
                                                        }
 
1084
                                                        data = tmp;
 
1085
                                                        break;
 
1086
                                                }
 
1087
                                        }
 
1088
                                        char* type = XGetAtomName(m_display, target);
 
1089
                                        LOG((CLOG_DEBUG2 "  %s (%s): %s", name, type, data.c_str()));
 
1090
                                        if (type != NULL) {
 
1091
                                                XFree(type);
 
1092
                                        }
 
1093
                                }
 
1094
                                if (name != NULL) {
 
1095
                                        XFree(name);
 
1096
                                }
 
1097
                        }
 
1098
                        if (props != NULL) {
 
1099
                                XFree(props);
 
1100
                        }
 
1101
                }
 
1102
 
 
1103
                sendNotify(reply->m_requestor, m_selection,
 
1104
                                                                reply->m_target, reply->m_property,
 
1105
                                                                reply->m_time);
 
1106
        }
 
1107
 
 
1108
        // wait for delete notify
 
1109
        return false;
 
1110
}
 
1111
 
 
1112
void
 
1113
CXWindowsClipboard::clearReplies()
 
1114
{
 
1115
        for (CReplyMap::iterator index = m_replies.begin();
 
1116
                                                                index != m_replies.end(); ++index) {
 
1117
                clearReplies(index->second);
 
1118
        }
 
1119
        m_replies.clear();
 
1120
        m_eventMasks.clear();
 
1121
}
 
1122
 
 
1123
void
 
1124
CXWindowsClipboard::clearReplies(CReplyList& replies)
 
1125
{
 
1126
        for (CReplyList::iterator index = replies.begin();
 
1127
                                                                index != replies.end(); ++index) {
 
1128
                delete *index;
 
1129
        }
 
1130
        replies.clear();
 
1131
}
 
1132
 
 
1133
void
 
1134
CXWindowsClipboard::sendNotify(Window requestor,
 
1135
                                Atom selection, Atom target, Atom property, Time time)
 
1136
{
 
1137
        XEvent event;
 
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);
 
1147
}
 
1148
 
 
1149
bool
 
1150
CXWindowsClipboard::wasOwnedAtTime(::Time time) const
 
1151
{
 
1152
        // not owned if we've never owned the selection
 
1153
        checkCache();
 
1154
        if (m_timeOwned == 0) {
 
1155
                return false;
 
1156
        }
 
1157
 
 
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) {
 
1165
                        return true;
 
1166
                }
 
1167
                else {
 
1168
                        lost = CXWindowsUtil::getCurrentTime(m_display, m_window);
 
1169
                }
 
1170
        }
 
1171
        else {
 
1172
                if (time == CurrentTime) {
 
1173
                        return false;
 
1174
                }
 
1175
        }
 
1176
 
 
1177
        // compare time to range
 
1178
        Time duration = lost - m_timeOwned;
 
1179
        Time when     = time - m_timeOwned;
 
1180
        return (/*when >= 0 &&*/ when < duration);
 
1181
}
 
1182
 
 
1183
Atom
 
1184
CXWindowsClipboard::getTargetsData(CString& data, int* format) const
 
1185
{
 
1186
        assert(format != NULL);
 
1187
 
 
1188
        // add standard targets
 
1189
        Atom atom;
 
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));
 
1196
 
 
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;
 
1201
 
 
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));
 
1206
                }
 
1207
        }
 
1208
 
 
1209
        *format = 32;
 
1210
        return m_atomAtom;
 
1211
}
 
1212
 
 
1213
Atom
 
1214
CXWindowsClipboard::getTimestampData(CString& data, int* format) const
 
1215
{
 
1216
        assert(format != NULL);
 
1217
 
 
1218
        assert(sizeof(m_timeOwned) == 4);
 
1219
        checkCache();
 
1220
        data.append(reinterpret_cast<const char*>(&m_timeOwned), 4);
 
1221
        *format = 32;
 
1222
        return m_atomInteger;
 
1223
}
 
1224
 
 
1225
 
 
1226
//
 
1227
// CXWindowsClipboard::CICCCMGetClipboard
 
1228
//
 
1229
 
 
1230
CXWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard(
 
1231
                                Window requestor, Time time, Atom property) :
 
1232
        m_requestor(requestor),
 
1233
        m_time(time),
 
1234
        m_property(property),
 
1235
        m_incr(false),
 
1236
        m_failed(false),
 
1237
        m_done(false),
 
1238
        m_reading(false),
 
1239
        m_data(NULL),
 
1240
        m_actualTarget(NULL),
 
1241
        m_error(false)
 
1242
{
 
1243
        // do nothing
 
1244
}
 
1245
 
 
1246
CXWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard()
 
1247
{
 
1248
        // do nothing
 
1249
}
 
1250
 
 
1251
bool
 
1252
CXWindowsClipboard::CICCCMGetClipboard::readClipboard(Display* display,
 
1253
                                Atom selection, Atom target, Atom* actualTarget, CString* data)
 
1254
{
 
1255
        assert(actualTarget != NULL);
 
1256
        assert(data         != NULL);
 
1257
 
 
1258
        LOG((CLOG_DEBUG1 "request selection=%d, target=%d, window=%x", selection, target, m_requestor));
 
1259
 
 
1260
        // save output pointers
 
1261
        m_actualTarget = actualTarget;
 
1262
        m_data         = data;
 
1263
 
 
1264
        // assume failure
 
1265
        *m_actualTarget = None;
 
1266
        *m_data         = "";
 
1267
 
 
1268
        // delete target property
 
1269
        XDeleteProperty(display, m_requestor, m_property);
 
1270
 
 
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);
 
1276
 
 
1277
        // request data conversion
 
1278
        XConvertSelection(display, selection, target,
 
1279
                                                                m_property, m_requestor, m_time);
 
1280
 
 
1281
        // synchronize with server before we start following timeout countdown
 
1282
        XSync(display, False);
 
1283
 
 
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.
 
1289
        XEvent xevent;
 
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) {
 
1297
                        m_failed = true;
 
1298
                        break;
 
1299
                }
 
1300
 
 
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);
 
1308
                                }
 
1309
                                else {
 
1310
                                        // reset timer since we've made some progress
 
1311
                                        timeout.reset();
 
1312
 
 
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.
 
1317
                                        noWait = true;
 
1318
                                }
 
1319
                        }
 
1320
                }
 
1321
                else {
 
1322
                        ARCH->sleep(0.01);
 
1323
                }
 
1324
        }
 
1325
 
 
1326
        // put unprocessed events back
 
1327
        for (UInt32 i = events.size(); i > 0; --i) {
 
1328
                XPutBackEvent(display, &events[i - 1]);
 
1329
        }
 
1330
 
 
1331
        // restore mask
 
1332
        XSelectInput(display, m_requestor, attr.your_event_mask);
 
1333
 
 
1334
        // return success or failure
 
1335
        LOG((CLOG_DEBUG1 "request %s", m_failed ? "failed" : "succeeded"));
 
1336
        return !m_failed;
 
1337
}
 
1338
 
 
1339
bool
 
1340
CXWindowsClipboard::CICCCMGetClipboard::processEvent(
 
1341
                                Display* display, XEvent* xevent)
 
1342
{
 
1343
        // process event
 
1344
        switch (xevent->type) {
 
1345
        case DestroyNotify:
 
1346
                if (xevent->xdestroywindow.window == m_requestor) {
 
1347
                        m_failed = true;
 
1348
                        return true;
 
1349
                }
 
1350
 
 
1351
                // not interested
 
1352
                return false;
 
1353
 
 
1354
        case SelectionNotify:
 
1355
                if (xevent->xselection.requestor == m_requestor) {
 
1356
                        // done if we can't convert
 
1357
                        if (xevent->xselection.property == None) {
 
1358
                                m_done = true;
 
1359
                                return true;
 
1360
                        }
 
1361
 
 
1362
                        // proceed if conversion successful
 
1363
                        else if (xevent->xselection.property == m_property) {
 
1364
                                m_reading = true;
 
1365
                                break;
 
1366
                        }
 
1367
                }
 
1368
 
 
1369
                // otherwise not interested
 
1370
                return false;
 
1371
 
 
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) {
 
1377
                        if (!m_reading) {
 
1378
                                // we haven't gotten the SelectionNotify yet
 
1379
                                return true;
 
1380
                        }
 
1381
                        break;
 
1382
                }
 
1383
 
 
1384
                // otherwise not interested
 
1385
                return false;
 
1386
 
 
1387
        default:
 
1388
                // not interested
 
1389
                return false;
 
1390
        }
 
1391
 
 
1392
        // get the data from the property
 
1393
        Atom target;
 
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
 
1398
                m_failed = true;
 
1399
                return true;
 
1400
        }
 
1401
 
 
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)) {
 
1406
                if (m_incr) {
 
1407
                        m_failed = true;
 
1408
                        m_error  = true;
 
1409
                }
 
1410
                else if (m_data->size() == oldSize) {
 
1411
                        m_failed = true;
 
1412
                        m_error  = true;
 
1413
                }
 
1414
                else {
 
1415
                        m_incr   = true;
 
1416
 
 
1417
                        // discard INCR data
 
1418
                        *m_data = "";
 
1419
                }
 
1420
        }
 
1421
 
 
1422
        // handle incremental chunks
 
1423
        else if (m_incr) {
 
1424
                // if first incremental chunk then save target
 
1425
                if (oldSize == 0) {
 
1426
                        LOG((CLOG_DEBUG1 "  INCR first chunk, target %d", target));
 
1427
                        *m_actualTarget = target;
 
1428
                }
 
1429
 
 
1430
                // secondary chunks must have the same target
 
1431
                else {
 
1432
                        if (target != *m_actualTarget) {
 
1433
                                LOG((CLOG_WARN "  INCR target mismatch"));
 
1434
                                m_failed = true;
 
1435
                                m_error  = true;
 
1436
                        }
 
1437
                }
 
1438
 
 
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()));
 
1442
                        m_done = true;
 
1443
                }
 
1444
        }
 
1445
 
 
1446
        // not incremental;  save the target.
 
1447
        else {
 
1448
                LOG((CLOG_DEBUG1 "  target %d", target));
 
1449
                *m_actualTarget = target;
 
1450
                m_done          = true;
 
1451
        }
 
1452
 
 
1453
        // this event has been processed
 
1454
        LOGC(!m_incr, (CLOG_DEBUG1 "  got data, %d bytes", m_data->size()));
 
1455
        return true;
 
1456
}
 
1457
 
 
1458
 
 
1459
//
 
1460
// CXWindowsClipboard::CReply
 
1461
//
 
1462
 
 
1463
CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time) :
 
1464
        m_requestor(requestor),
 
1465
        m_target(target),
 
1466
        m_time(time),
 
1467
        m_property(None),
 
1468
        m_replied(false),
 
1469
        m_done(false),
 
1470
        m_data(),
 
1471
        m_type(None),
 
1472
        m_format(32),
 
1473
        m_ptr(0)
 
1474
{
 
1475
        // do nothing
 
1476
}
 
1477
 
 
1478
CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time,
 
1479
                                Atom property, const CString& data, Atom type, int format) :
 
1480
        m_requestor(requestor),
 
1481
        m_target(target),
 
1482
        m_time(time),
 
1483
        m_property(property),
 
1484
        m_replied(false),
 
1485
        m_done(false),
 
1486
        m_data(data),
 
1487
        m_type(type),
 
1488
        m_format(format),
 
1489
        m_ptr(0)
 
1490
{
 
1491
        // do nothing
 
1492
}