source: trunk/src/gui/kernel/qclipboard_x11.cpp@ 187

Last change on this file since 187 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 47.4 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42// #define QCLIPBOARD_DEBUG
43// #define QCLIPBOARD_DEBUG_VERBOSE
44
45#ifdef QCLIPBOARD_DEBUG
46# define DEBUG qDebug
47#else
48# define DEBUG if (false) qDebug
49#endif
50
51#ifdef QCLIPBOARD_DEBUG_VERBOSE
52# define VDEBUG qDebug
53#else
54# define VDEBUG if (false) qDebug
55#endif
56
57#include "qplatformdefs.h"
58
59#include "qclipboard.h"
60#include "qclipboard_p.h"
61
62#ifndef QT_NO_CLIPBOARD
63
64#include "qabstracteventdispatcher.h"
65#include "qapplication.h"
66#include "qdesktopwidget.h"
67#include "qbitmap.h"
68#include "qdatetime.h"
69#include "qiodevice.h"
70#include "qbuffer.h"
71#include "qtextcodec.h"
72#include "qlist.h"
73#include "qmap.h"
74#include "qapplication_p.h"
75#include "qevent.h"
76#include "qt_x11_p.h"
77#include "qx11info_x11.h"
78#include "qimagewriter.h"
79#include "qvariant.h"
80#include "qdnd_p.h"
81
82#ifndef QT_NO_XFIXES
83#include <X11/extensions/Xfixes.h>
84#endif // QT_NO_XFIXES
85
86QT_BEGIN_NAMESPACE
87
88/*****************************************************************************
89 Internal QClipboard functions for X11.
90 *****************************************************************************/
91
92static int clipboard_timeout = 5000; // 5s timeout on clipboard operations
93
94static QWidget * owner = 0;
95static QWidget *requestor = 0;
96static bool timer_event_clear = false;
97static int timer_id = 0;
98
99static int pending_timer_id = 0;
100static bool pending_clipboard_changed = false;
101static bool pending_selection_changed = false;
102
103
104// event capture mechanism for qt_xclb_wait_for_event
105static bool waiting_for_data = false;
106static bool has_captured_event = false;
107static Window capture_event_win = XNone;
108static int capture_event_type = -1;
109static XEvent captured_event;
110
111class QClipboardWatcher; // forward decl
112static QClipboardWatcher *selection_watcher = 0;
113static QClipboardWatcher *clipboard_watcher = 0;
114
115static void cleanup()
116{
117 delete owner;
118 delete requestor;
119 owner = 0;
120 requestor = 0;
121}
122
123static
124void setupOwner()
125{
126 if (owner)
127 return;
128 owner = new QWidget(0);
129 owner->setObjectName(QLatin1String("internal clipboard owner"));
130 owner->createWinId();
131 requestor = new QWidget(0);
132 requestor->createWinId();
133 requestor->setObjectName(QLatin1String("internal clipboard requestor"));
134 qAddPostRoutine(cleanup);
135}
136
137
138class QClipboardWatcher : public QInternalMimeData {
139public:
140 QClipboardWatcher(QClipboard::Mode mode);
141 ~QClipboardWatcher();
142 bool empty() const;
143 virtual bool hasFormat_sys(const QString &mimetype) const;
144 virtual QStringList formats_sys() const;
145
146 QVariant retrieveData_sys(const QString &mimetype, QVariant::Type type) const;
147 QByteArray getDataInFormat(Atom fmtatom) const;
148
149 Atom atom;
150 mutable QStringList formatList;
151 mutable QByteArray format_atoms;
152};
153
154class QClipboardData
155{
156private:
157 QMimeData *&mimeDataRef() const
158 {
159 if(mode == QClipboard::Selection)
160 return selectionData;
161 return clipboardData;
162 }
163
164public:
165 QClipboardData(QClipboard::Mode mode);
166 ~QClipboardData();
167
168 void setSource(QMimeData* s)
169 {
170 if ((mode == QClipboard::Selection && selectionData == s)
171 || clipboardData == s) {
172 return;
173 }
174
175 if (selectionData != clipboardData) {
176 delete mimeDataRef();
177 }
178
179 mimeDataRef() = s;
180 }
181
182 QMimeData *source() const
183 {
184 return mimeDataRef();
185 }
186
187 void clear()
188 {
189 timestamp = CurrentTime;
190 if (selectionData == clipboardData) {
191 mimeDataRef() = 0;
192 } else {
193 QMimeData *&src = mimeDataRef();
194 delete src;
195 src = 0;
196 }
197 }
198
199 static QMimeData *selectionData;
200 static QMimeData *clipboardData;
201 Time timestamp;
202 QClipboard::Mode mode;
203};
204
205QMimeData *QClipboardData::selectionData = 0;
206QMimeData *QClipboardData::clipboardData = 0;
207
208QClipboardData::QClipboardData(QClipboard::Mode clipboardMode)
209{
210 timestamp = CurrentTime;
211 mode = clipboardMode;
212}
213
214QClipboardData::~QClipboardData()
215{ clear(); }
216
217
218static QClipboardData *internalCbData = 0;
219static QClipboardData *internalSelData = 0;
220
221static void cleanupClipboardData()
222{
223 delete internalCbData;
224 internalCbData = 0;
225}
226
227static QClipboardData *clipboardData()
228{
229 if (internalCbData == 0) {
230 internalCbData = new QClipboardData(QClipboard::Clipboard);
231 qAddPostRoutine(cleanupClipboardData);
232 }
233 return internalCbData;
234}
235
236static void cleanupSelectionData()
237{
238 delete internalSelData;
239 internalSelData = 0;
240}
241
242static QClipboardData *selectionData()
243{
244 if (internalSelData == 0) {
245 internalSelData = new QClipboardData(QClipboard::Selection);
246 qAddPostRoutine(cleanupSelectionData);
247 }
248 return internalSelData;
249}
250
251class QClipboardINCRTransaction
252{
253public:
254 QClipboardINCRTransaction(Window w, Atom p, Atom t, int f, QByteArray d, unsigned int i);
255 ~QClipboardINCRTransaction(void);
256
257 int x11Event(XEvent *event);
258
259 Window window;
260 Atom property, target;
261 int format;
262 QByteArray data;
263 unsigned int increment;
264 unsigned int offset;
265};
266
267typedef QMap<Window,QClipboardINCRTransaction*> TransactionMap;
268static TransactionMap *transactions = 0;
269static QApplication::EventFilter prev_event_filter = 0;
270static int incr_timer_id = 0;
271
272static bool qt_x11_incr_event_filter(void *message, long *result)
273{
274 XEvent *event = reinterpret_cast<XEvent *>(message);
275 TransactionMap::Iterator it = transactions->find(event->xany.window);
276 if (it != transactions->end()) {
277 if ((*it)->x11Event(event) != 0)
278 return true;
279 }
280 if (prev_event_filter)
281 return prev_event_filter(event, result);
282 return false;
283}
284
285/*
286 called when no INCR activity has happened for 'clipboard_timeout'
287 milliseconds... we assume that all unfinished transactions have
288 timed out and remove everything from the transaction map
289*/
290static void qt_xclb_incr_timeout(void)
291{
292 qWarning("QClipboard: Timed out while sending data");
293
294 while (transactions)
295 delete *transactions->begin();
296}
297
298QClipboardINCRTransaction::QClipboardINCRTransaction(Window w, Atom p, Atom t, int f,
299 QByteArray d, unsigned int i)
300 : window(w), property(p), target(t), format(f), data(d), increment(i), offset(0u)
301{
302 DEBUG("QClipboard: sending %d bytes (INCR transaction %p)", d.size(), this);
303
304 XSelectInput(X11->display, window, PropertyChangeMask);
305
306 if (! transactions) {
307 VDEBUG("QClipboard: created INCR transaction map");
308 transactions = new TransactionMap;
309 prev_event_filter = qApp->setEventFilter(qt_x11_incr_event_filter);
310 incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout);
311 }
312 transactions->insert(window, this);
313}
314
315QClipboardINCRTransaction::~QClipboardINCRTransaction(void)
316{
317 VDEBUG("QClipboard: destroyed INCR transacton %p", this);
318
319 XSelectInput(X11->display, window, NoEventMask);
320
321 transactions->remove(window);
322 if (transactions->isEmpty()) {
323 VDEBUG("QClipboard: no more INCR transactions");
324 delete transactions;
325 transactions = 0;
326
327 (void)qApp->setEventFilter(prev_event_filter);
328
329 if (incr_timer_id != 0) {
330 QApplication::clipboard()->killTimer(incr_timer_id);
331 incr_timer_id = 0;
332 }
333 }
334}
335
336int QClipboardINCRTransaction::x11Event(XEvent *event)
337{
338 if (event->type != PropertyNotify
339 || (event->xproperty.state != PropertyDelete
340 || event->xproperty.atom != property))
341 return 0;
342
343 // restart the INCR timer
344 if (incr_timer_id) QApplication::clipboard()->killTimer(incr_timer_id);
345 incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout);
346
347 unsigned int bytes_left = data.size() - offset;
348 if (bytes_left > 0) {
349 unsigned int xfer = qMin(increment, bytes_left);
350 VDEBUG("QClipboard: sending %d bytes, %d remaining (INCR transaction %p)",
351 xfer, bytes_left - xfer, this);
352
353 XChangeProperty(X11->display, window, property, target, format,
354 PropModeReplace, (uchar *) data.data() + offset, xfer);
355 offset += xfer;
356 } else {
357 // INCR transaction finished...
358 XChangeProperty(X11->display, window, property, target, format,
359 PropModeReplace, (uchar *) data.data(), 0);
360 delete this;
361 }
362
363 return 1;
364}
365
366
367/*****************************************************************************
368 QClipboard member functions for X11.
369 *****************************************************************************/
370
371struct qt_init_timestamp_data
372{
373 Time timestamp;
374};
375
376#if defined(Q_C_CALLBACKS)
377extern "C" {
378#endif
379
380static Bool qt_init_timestamp_scanner(Display*, XEvent *event, XPointer arg)
381{
382 qt_init_timestamp_data *data =
383 reinterpret_cast<qt_init_timestamp_data*>(arg);
384 switch(event->type)
385 {
386 case ButtonPress:
387 case ButtonRelease:
388 data->timestamp = event->xbutton.time;
389 break;
390 case MotionNotify:
391 data->timestamp = event->xmotion.time;
392 break;
393 case XKeyPress:
394 case XKeyRelease:
395 data->timestamp = event->xkey.time;
396 break;
397 case PropertyNotify:
398 data->timestamp = event->xproperty.time;
399 break;
400 case EnterNotify:
401 case LeaveNotify:
402 data->timestamp = event->xcrossing.time;
403 break;
404 case SelectionClear:
405 data->timestamp = event->xselectionclear.time;
406 break;
407 default:
408 break;
409 }
410#ifndef QT_NO_XFIXES
411 if (X11->use_xfixes && event->type == (X11->xfixes_eventbase + XFixesSelectionNotify)) {
412 XFixesSelectionNotifyEvent *req =
413 reinterpret_cast<XFixesSelectionNotifyEvent *>(event);
414 data->timestamp = req->selection_timestamp;
415 }
416#endif
417 return false;
418}
419
420#if defined(Q_C_CALLBACKS)
421}
422#endif
423
424QClipboard::QClipboard(QObject *parent)
425 : QObject(*new QClipboardPrivate, parent)
426{
427 // create desktop widget since we need it to get PropertyNotify or
428 // XFixesSelectionNotify events when someone changes the
429 // clipboard.
430 (void)QApplication::desktop();
431 if (X11->time == CurrentTime) {
432 // send a dummy event to myself to get the timestamp from X11.
433 qt_init_timestamp_data data;
434 data.timestamp = CurrentTime;
435 XEvent ev;
436 XCheckIfEvent(X11->display, &ev, &qt_init_timestamp_scanner, (XPointer)&data);
437 if (data.timestamp == CurrentTime) {
438 setupOwner();
439 int dummy = 0;
440 Window ownerId = owner->internalWinId();
441 XChangeProperty(X11->display, ownerId,
442 ATOM(CLIP_TEMPORARY), XA_INTEGER, 32,
443 PropModeReplace, (uchar*)&dummy, 1);
444 XWindowEvent(X11->display, ownerId, PropertyChangeMask, &ev);
445 data.timestamp = ev.xproperty.time;
446 XDeleteProperty(X11->display, ownerId, ATOM(CLIP_TEMPORARY));
447 }
448 X11->time = data.timestamp;
449 }
450}
451
452void QClipboard::clear(Mode mode)
453{
454 setMimeData(0, mode);
455}
456
457
458bool QClipboard::supportsMode(Mode mode) const
459{
460 return (mode == Clipboard || mode == Selection);
461}
462
463bool QClipboard::ownsMode(Mode mode) const
464{
465 if (mode == Clipboard)
466 return clipboardData()->timestamp != CurrentTime;
467 else if(mode == Selection)
468 return selectionData()->timestamp != CurrentTime;
469 else
470 return false;
471}
472
473
474// event filter function... captures interesting events while
475// qt_xclb_wait_for_event is running the event loop
476static bool qt_x11_clipboard_event_filter(void *message, long *)
477{
478 XEvent *event = reinterpret_cast<XEvent *>(message);
479 if (event->xany.type == capture_event_type &&
480 event->xany.window == capture_event_win) {
481 VDEBUG("QClipboard: event_filter(): caught event type %d", event->type);
482 has_captured_event = true;
483 captured_event = *event;
484 return true;
485 }
486 return false;
487}
488
489static Bool checkForClipboardEvents(Display *, XEvent *e, XPointer)
490{
491 return ((e->type == SelectionRequest && (e->xselectionrequest.selection == XA_PRIMARY
492 || e->xselectionrequest.selection == ATOM(CLIPBOARD)))
493 || (e->type == SelectionClear && (e->xselectionclear.selection == XA_PRIMARY
494 || e->xselectionclear.selection == ATOM(CLIPBOARD))));
495}
496
497bool QX11Data::clipboardWaitForEvent(Window win, int type, XEvent *event, int timeout)
498{
499 QTime started = QTime::currentTime();
500 QTime now = started;
501
502 if (QAbstractEventDispatcher::instance()->inherits("QtMotif")
503 || QApplication::clipboard()->property("useEventLoopWhenWaiting").toBool()) {
504 if (waiting_for_data) {
505 Q_ASSERT(!"QClipboard: internal error, qt_xclb_wait_for_event recursed");
506 return false;
507 }
508 waiting_for_data = true;
509
510
511 has_captured_event = false;
512 capture_event_win = win;
513 capture_event_type = type;
514
515 QApplication::EventFilter old_event_filter =
516 qApp->setEventFilter(qt_x11_clipboard_event_filter);
517
518 do {
519 if (XCheckTypedWindowEvent(display, win, type, event)) {
520 waiting_for_data = false;
521 qApp->setEventFilter(old_event_filter);
522 return true;
523 }
524
525 XSync(X11->display, false);
526 usleep(50000);
527
528 now = QTime::currentTime();
529 if (started > now) // crossed midnight
530 started = now;
531
532 QEventLoop::ProcessEventsFlags flags(QEventLoop::ExcludeUserInputEvents
533 | QEventLoop::ExcludeSocketNotifiers
534 | QEventLoop::WaitForMoreEvents
535 | QEventLoop::X11ExcludeTimers);
536 QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance();
537 eventDispatcher->processEvents(flags);
538
539 if (has_captured_event) {
540 waiting_for_data = false;
541 *event = captured_event;
542 qApp->setEventFilter(old_event_filter);
543 return true;
544 }
545 } while (started.msecsTo(now) < timeout);
546
547 waiting_for_data = false;
548 qApp->setEventFilter(old_event_filter);
549 } else {
550 do {
551 if (XCheckTypedWindowEvent(X11->display,win,type,event))
552 return true;
553
554 // process other clipboard events, since someone is probably requesting data from us
555 XEvent e;
556 if (XCheckIfEvent(X11->display, &e, checkForClipboardEvents, 0))
557 qApp->x11ProcessEvent(&e);
558
559 now = QTime::currentTime();
560 if ( started > now ) // crossed midnight
561 started = now;
562
563 XFlush(X11->display);
564
565 // sleep 50 ms, so we don't use up CPU cycles all the time.
566 struct timeval usleep_tv;
567 usleep_tv.tv_sec = 0;
568 usleep_tv.tv_usec = 50000;
569 select(0, 0, 0, 0, &usleep_tv);
570 } while (started.msecsTo(now) < timeout);
571 }
572 return false;
573}
574
575
576static inline int maxSelectionIncr(Display *dpy)
577{ return XMaxRequestSize(dpy) > 65536 ? 65536*4 : XMaxRequestSize(dpy)*4 - 100; }
578
579bool QX11Data::clipboardReadProperty(Window win, Atom property, bool deleteProperty,
580 QByteArray *buffer, int *size, Atom *type, int *format, bool nullterm)
581{
582 int maxsize = maxSelectionIncr(display);
583 ulong bytes_left; // bytes_after
584 ulong length; // nitems
585 uchar *data;
586 Atom dummy_type;
587 int dummy_format;
588 int r;
589
590 if (!type) // allow null args
591 type = &dummy_type;
592 if (!format)
593 format = &dummy_format;
594
595 // Don't read anything, just get the size of the property data
596 r = XGetWindowProperty(display, win, property, 0, 0, False,
597 AnyPropertyType, type, format,
598 &length, &bytes_left, &data);
599 if (r != Success || (type && *type == XNone)) {
600 buffer->resize(0);
601 return false;
602 }
603 XFree((char*)data);
604
605 int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
606
607 VDEBUG("QClipboard: read_property(): initial property length: %d", proplen);
608
609 switch (*format) {
610 case 8:
611 default:
612 format_inc = sizeof(char) / 1;
613 break;
614
615 case 16:
616 format_inc = sizeof(short) / 2;
617 proplen *= sizeof(short) / 2;
618 break;
619
620 case 32:
621 format_inc = sizeof(long) / 4;
622 proplen *= sizeof(long) / 4;
623 break;
624 }
625
626 int newSize = proplen + (nullterm ? 1 : 0);
627 buffer->resize(newSize);
628
629 bool ok = (buffer->size() == newSize);
630 VDEBUG("QClipboard: read_property(): buffer resized to %d", buffer->size());
631
632 if (ok) {
633 // could allocate buffer
634
635 while (bytes_left) {
636 // more to read...
637
638 r = XGetWindowProperty(display, win, property, offset, maxsize/4,
639 False, AnyPropertyType, type, format,
640 &length, &bytes_left, &data);
641 if (r != Success || (type && *type == XNone))
642 break;
643
644 offset += length / (32 / *format);
645 length *= format_inc * (*format) / 8;
646
647 // Here we check if we get a buffer overflow and tries to
648 // recover -- this shouldn't normally happen, but it doesn't
649 // hurt to be defensive
650 if ((int)(buffer_offset + length) > buffer->size()) {
651 length = buffer->size() - buffer_offset;
652
653 // escape loop
654 bytes_left = 0;
655 }
656
657 memcpy(buffer->data() + buffer_offset, data, length);
658 buffer_offset += length;
659
660 XFree((char*)data);
661 }
662
663 if (*format == 8 && *type == ATOM(COMPOUND_TEXT)) {
664 // convert COMPOUND_TEXT to a multibyte string
665 XTextProperty textprop;
666 textprop.encoding = *type;
667 textprop.format = *format;
668 textprop.nitems = length;
669 textprop.value = (unsigned char *) buffer->data();
670
671 char **list_ret = 0;
672 int count;
673 if (XmbTextPropertyToTextList(display, &textprop, &list_ret,
674 &count) == Success && count && list_ret) {
675 offset = strlen(list_ret[0]);
676 buffer->resize(offset + (nullterm ? 1 : 0));
677 memcpy(buffer->data(), list_ret[0], offset);
678 }
679 if (list_ret) XFreeStringList(list_ret);
680 }
681
682 // zero-terminate (for text)
683 if (nullterm)
684 buffer->data()[buffer_offset] = '\0';
685 }
686
687 // correct size, not 0-term.
688 if (size)
689 *size = buffer_offset;
690
691 VDEBUG("QClipboard: read_property(): buffer size %d, buffer offset %d, offset %d",
692 buffer->size(), buffer_offset, offset);
693
694 if (deleteProperty)
695 XDeleteProperty(display, win, property);
696
697 XFlush(display);
698
699 return ok;
700}
701
702QByteArray QX11Data::clipboardReadIncrementalProperty(Window win, Atom property, int nbytes, bool nullterm)
703{
704 XEvent event;
705
706 QByteArray buf;
707 QByteArray tmp_buf;
708 bool alloc_error = false;
709 int length;
710 int offset = 0;
711
712 if (nbytes > 0) {
713 // Reserve buffer + zero-terminator (for text data)
714 // We want to complete the INCR transfer even if we cannot
715 // allocate more memory
716 buf.resize(nbytes+1);
717 alloc_error = buf.size() != nbytes+1;
718 }
719
720 for (;;) {
721 XFlush(display);
722 if (!clipboardWaitForEvent(win,PropertyNotify,&event,clipboard_timeout))
723 break;
724 if (event.xproperty.atom != property ||
725 event.xproperty.state != PropertyNewValue)
726 continue;
727 if (X11->clipboardReadProperty(win, property, true, &tmp_buf, &length, 0, 0, false)) {
728 if (length == 0) { // no more data, we're done
729 if (nullterm) {
730 buf.resize(offset+1);
731 buf[offset] = '\0';
732 } else {
733 buf.resize(offset);
734 }
735 return buf;
736 } else if (!alloc_error) {
737 if (offset+length > (int)buf.size()) {
738 buf.resize(offset+length+65535);
739 if (buf.size() != offset+length+65535) {
740 alloc_error = true;
741 length = buf.size() - offset;
742 }
743 }
744 memcpy(buf.data()+offset, tmp_buf.constData(), length);
745 tmp_buf.resize(0);
746 offset += length;
747 }
748 } else {
749 break;
750 }
751 }
752
753 // timed out ... create a new requestor window, otherwise the requestor
754 // could consider next request to be still part of this timed out request
755 delete requestor;
756 requestor = new QWidget(0);
757 requestor->setObjectName(QLatin1String("internal clipboard requestor"));
758
759 return QByteArray();
760}
761
762static Atom send_targets_selection(QClipboardData *d, Window window, Atom property)
763{
764 QVector<Atom> types;
765 QStringList formats = QInternalMimeData::formatsHelper(d->source());
766 for (int i = 0; i < formats.size(); ++i) {
767 QList<Atom> atoms = X11->xdndMimeAtomsForFormat(formats.at(i));
768 for (int j = 0; j < atoms.size(); ++j) {
769 if (!types.contains(atoms.at(j)))
770 types.append(atoms.at(j));
771 }
772 }
773 types.append(ATOM(TARGETS));
774 types.append(ATOM(MULTIPLE));
775 types.append(ATOM(TIMESTAMP));
776
777 XChangeProperty(X11->display, window, property, XA_ATOM, 32,
778 PropModeReplace, (uchar *) types.data(), types.size());
779 return property;
780}
781
782static Atom send_selection(QClipboardData *d, Atom target, Window window, Atom property)
783{
784 Atom atomFormat = target;
785 int dataFormat = 0;
786 QByteArray data;
787
788 QByteArray fmt = X11->xdndAtomToString(target);
789 if (fmt.isEmpty() || !QInternalMimeData::hasFormatHelper(QString::fromAscii(fmt), d->source())) { // Not a MIME type we have
790 DEBUG("QClipboard: send_selection(): converting to type '%s' is not supported", fmt.data());
791 return XNone;
792 }
793 DEBUG("QClipboard: send_selection(): converting to type '%s'", fmt.data());
794
795 if (X11->xdndMimeDataForAtom(target, d->source(), &data, &atomFormat, &dataFormat)) {
796
797 VDEBUG("QClipboard: send_selection():\n"
798 " property type %lx\n"
799 " property name '%s'\n"
800 " format %d\n"
801 " %d bytes\n",
802 target, X11->xdndMimeAtomToString(atomFormat).toLatin1().data(), dataFormat, data.size());
803
804 // don't allow INCR transfers when using MULTIPLE or to
805 // Motif clients (since Motif doesn't support INCR)
806 static Atom motif_clip_temporary = ATOM(CLIP_TEMPORARY);
807 bool allow_incr = property != motif_clip_temporary;
808
809 // X_ChangeProperty protocol request is 24 bytes
810 const int increment = (XMaxRequestSize(X11->display) * 4) - 24;
811 if (data.size() > increment && allow_incr) {
812 long bytes = data.size();
813 XChangeProperty(X11->display, window, property,
814 ATOM(INCR), 32, PropModeReplace, (uchar *) &bytes, 1);
815
816 (void)new QClipboardINCRTransaction(window, property, atomFormat, dataFormat, data, increment);
817 return ATOM(INCR);
818 }
819
820 // make sure we can perform the XChangeProperty in a single request
821 if (data.size() > increment)
822 return XNone; // ### perhaps use several XChangeProperty calls w/ PropModeAppend?
823 int dataSize = data.size() / (dataFormat / 8);
824 // use a single request to transfer data
825 XChangeProperty(X11->display, window, property, atomFormat,
826 dataFormat, PropModeReplace, (uchar *) data.data(),
827 dataSize);
828 }
829 return property;
830}
831
832/*! \internal
833 Internal cleanup for Windows.
834*/
835void QClipboard::ownerDestroyed()
836{ }
837
838
839/*! \internal
840 Internal optimization for Windows.
841*/
842void QClipboard::connectNotify(const char *)
843{ }
844
845
846bool QClipboard::event(QEvent *e)
847{
848 if (e->type() == QEvent::Timer) {
849 QTimerEvent *te = (QTimerEvent *) e;
850
851 if (waiting_for_data) // should never happen
852 return false;
853
854 if (te->timerId() == timer_id) {
855 killTimer(timer_id);
856 timer_id = 0;
857
858 timer_event_clear = true;
859 if (selection_watcher) // clear selection
860 selectionData()->clear();
861 if (clipboard_watcher) // clear clipboard
862 clipboardData()->clear();
863 timer_event_clear = false;
864
865 return true;
866 } else if (te->timerId() == pending_timer_id) {
867 // I hate klipper
868 killTimer(pending_timer_id);
869 pending_timer_id = 0;
870
871 if (pending_clipboard_changed) {
872 pending_clipboard_changed = false;
873 clipboardData()->clear();
874 emitChanged(QClipboard::Clipboard);
875 }
876 if (pending_selection_changed) {
877 pending_selection_changed = false;
878 selectionData()->clear();
879 emitChanged(QClipboard::Selection);
880 }
881
882 return true;
883 } else if (te->timerId() == incr_timer_id) {
884 killTimer(incr_timer_id);
885 incr_timer_id = 0;
886
887 qt_xclb_incr_timeout();
888
889 return true;
890 } else {
891 return QObject::event(e);
892 }
893 } else if (e->type() != QEvent::Clipboard) {
894 return QObject::event(e);
895 }
896
897 XEvent *xevent = (XEvent *)(((QClipboardEvent *)e)->data());
898 Display *dpy = X11->display;
899
900 if (!xevent)
901 return true;
902
903 switch (xevent->type) {
904
905 case SelectionClear:
906 // new selection owner
907 if (xevent->xselectionclear.selection == XA_PRIMARY) {
908 QClipboardData *d = selectionData();
909
910 // ignore the event if it was generated before we gained selection ownership
911 if (d->timestamp != CurrentTime && xevent->xselectionclear.time <= d->timestamp)
912 break;
913
914 DEBUG("QClipboard: new selection owner 0x%lx at time %lx (ours %lx)",
915 XGetSelectionOwner(dpy, XA_PRIMARY),
916 xevent->xselectionclear.time, d->timestamp);
917
918 if (! waiting_for_data) {
919 d->clear();
920 emitChanged(QClipboard::Selection);
921 } else {
922 pending_selection_changed = true;
923 if (! pending_timer_id)
924 pending_timer_id = QApplication::clipboard()->startTimer(0);
925 }
926 } else if (xevent->xselectionclear.selection == ATOM(CLIPBOARD)) {
927 QClipboardData *d = clipboardData();
928
929 // ignore the event if it was generated before we gained selection ownership
930 if (d->timestamp != CurrentTime && xevent->xselectionclear.time <= d->timestamp)
931 break;
932
933 DEBUG("QClipboard: new clipboard owner 0x%lx at time %lx (%lx)",
934 XGetSelectionOwner(dpy, ATOM(CLIPBOARD)),
935 xevent->xselectionclear.time, d->timestamp);
936
937 if (! waiting_for_data) {
938 d->clear();
939 emitChanged(QClipboard::Clipboard);
940 } else {
941 pending_clipboard_changed = true;
942 if (! pending_timer_id)
943 pending_timer_id = QApplication::clipboard()->startTimer(0);
944 }
945 } else {
946 qWarning("QClipboard: Unknown SelectionClear event received");
947 return false;
948 }
949 break;
950
951 case SelectionNotify:
952 /*
953 Something has delivered data to us, but this was not caught
954 by QClipboardWatcher::getDataInFormat()
955
956 Just skip the event to prevent Bad Things (tm) from
957 happening later on...
958 */
959 break;
960
961 case SelectionRequest:
962 {
963 // someone wants our data
964 XSelectionRequestEvent *req = &xevent->xselectionrequest;
965
966 if (requestor && req->requestor == requestor->internalWinId())
967 break;
968
969 XEvent event;
970 event.xselection.type = SelectionNotify;
971 event.xselection.display = req->display;
972 event.xselection.requestor = req->requestor;
973 event.xselection.selection = req->selection;
974 event.xselection.target = req->target;
975 event.xselection.property = XNone;
976 event.xselection.time = req->time;
977
978 DEBUG("QClipboard: SelectionRequest from %lx\n"
979 " selection 0x%lx (%s) target 0x%lx (%s)",
980 req->requestor,
981 req->selection,
982 X11->xdndAtomToString(req->selection).data(),
983 req->target,
984 X11->xdndAtomToString(req->target).data());
985
986 QClipboardData *d;
987 if (req->selection == XA_PRIMARY) {
988 d = selectionData();
989 } else if (req->selection == ATOM(CLIPBOARD)) {
990 d = clipboardData();
991 } else {
992 qWarning("QClipboard: Unknown selection '%lx'", req->selection);
993 XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
994 break;
995 }
996
997 if (! d->source()) {
998 qWarning("QClipboard: Cannot transfer data, no data available");
999 XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
1000 break;
1001 }
1002
1003 DEBUG("QClipboard: SelectionRequest at time %lx (ours %lx)",
1004 req->time, d->timestamp);
1005
1006 if (d->timestamp == CurrentTime // we don't own the selection anymore
1007 || (req->time != CurrentTime && req->time < d->timestamp)) {
1008 DEBUG("QClipboard: SelectionRequest too old");
1009 XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
1010 break;
1011 }
1012
1013 Atom xa_targets = ATOM(TARGETS);
1014 Atom xa_multiple = ATOM(MULTIPLE);
1015 Atom xa_timestamp = ATOM(TIMESTAMP);
1016
1017 struct AtomPair { Atom target; Atom property; } *multi = 0;
1018 Atom multi_type = XNone;
1019 int multi_format = 0;
1020 int nmulti = 0;
1021 int imulti = -1;
1022 bool multi_writeback = false;
1023
1024 if (req->target == xa_multiple) {
1025 QByteArray multi_data;
1026 if (req->property == XNone
1027 || !X11->clipboardReadProperty(req->requestor, req->property, false, &multi_data,
1028 0, &multi_type, &multi_format, 0)
1029 || multi_format != 32) {
1030 // MULTIPLE property not formatted correctly
1031 XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
1032 break;
1033 }
1034 nmulti = multi_data.size()/sizeof(*multi);
1035 multi = new AtomPair[nmulti];
1036 memcpy(multi,multi_data.data(),multi_data.size());
1037 imulti = 0;
1038 }
1039
1040 for (; imulti < nmulti; ++imulti) {
1041 Atom target;
1042 Atom property;
1043
1044 if (multi) {
1045 target = multi[imulti].target;
1046 property = multi[imulti].property;
1047 } else {
1048 target = req->target;
1049 property = req->property;
1050 if (property == XNone) // obsolete client
1051 property = target;
1052 }
1053
1054 Atom ret = XNone;
1055 if (target == XNone || property == XNone) {
1056 ;
1057 } else if (target == xa_timestamp) {
1058 if (d->timestamp != CurrentTime) {
1059 XChangeProperty(dpy, req->requestor, property, XA_INTEGER, 32,
1060 PropModeReplace, (uchar *) &d->timestamp, 1);
1061 ret = property;
1062 } else {
1063 qWarning("QClipboard: Invalid data timestamp");
1064 }
1065 } else if (target == xa_targets) {
1066 ret = send_targets_selection(d, req->requestor, property);
1067 } else {
1068 ret = send_selection(d, target, req->requestor, property);
1069 }
1070
1071 if (nmulti > 0) {
1072 if (ret == XNone) {
1073 multi[imulti].property = XNone;
1074 multi_writeback = true;
1075 }
1076 } else {
1077 event.xselection.property = ret;
1078 break;
1079 }
1080 }
1081
1082 if (nmulti > 0) {
1083 if (multi_writeback) {
1084 // according to ICCCM 2.6.2 says to put None back
1085 // into the original property on the requestor window
1086 XChangeProperty(dpy, req->requestor, req->property, multi_type, 32,
1087 PropModeReplace, (uchar *) multi, nmulti * 2);
1088 }
1089
1090 delete [] multi;
1091 event.xselection.property = req->property;
1092 }
1093
1094 // send selection notify to requestor
1095 XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
1096
1097 DEBUG("QClipboard: SelectionNotify to 0x%lx\n"
1098 " property 0x%lx (%s)",
1099 req->requestor, event.xselection.property,
1100 X11->xdndAtomToString(event.xselection.property).data());
1101 }
1102 break;
1103 }
1104
1105 return true;
1106}
1107
1108
1109
1110
1111
1112
1113QClipboardWatcher::QClipboardWatcher(QClipboard::Mode mode)
1114 : QInternalMimeData()
1115{
1116 switch (mode) {
1117 case QClipboard::Selection:
1118 atom = XA_PRIMARY;
1119 break;
1120
1121 case QClipboard::Clipboard:
1122 atom = ATOM(CLIPBOARD);
1123 break;
1124
1125 default:
1126 qWarning("QClipboardWatcher: Internal error: Unsupported clipboard mode");
1127 break;
1128 }
1129
1130 setupOwner();
1131}
1132
1133QClipboardWatcher::~QClipboardWatcher()
1134{
1135 if(selection_watcher == this)
1136 selection_watcher = 0;
1137 if(clipboard_watcher == this)
1138 clipboard_watcher = 0;
1139}
1140
1141bool QClipboardWatcher::empty() const
1142{
1143 Display *dpy = X11->display;
1144 Window win = XGetSelectionOwner(dpy, atom);
1145
1146 if(win == requestor->internalWinId()) {
1147 qWarning("QClipboardWatcher::empty: Internal error: Application owns the selection");
1148 return true;
1149 }
1150
1151 return win == XNone;
1152}
1153
1154QStringList QClipboardWatcher::formats_sys() const
1155{
1156 if (empty())
1157 return QStringList();
1158
1159 if (!formatList.count()) {
1160 // get the list of targets from the current clipboard owner - we do this
1161 // once so that multiple calls to this function don't require multiple
1162 // server round trips...
1163
1164 format_atoms = getDataInFormat(ATOM(TARGETS));
1165
1166 if (format_atoms.size() > 0) {
1167 Atom *targets = (Atom *) format_atoms.data();
1168 int size = format_atoms.size() / sizeof(Atom);
1169
1170 for (int i = 0; i < size; ++i) {
1171 if (targets[i] == 0)
1172 continue;
1173
1174 QStringList formatsForAtom = X11->xdndMimeFormatsForAtom(targets[i]);
1175 for (int j = 0; j < formatsForAtom.size(); ++j) {
1176 if (!formatList.contains(formatsForAtom.at(j)))
1177 formatList.append(formatsForAtom.at(j));
1178 }
1179 VDEBUG(" format: %s", X11->xdndAtomToString(targets[i]).data());
1180 VDEBUG(" data:\n%s\n", getDataInFormat(targets[i]).data());
1181 }
1182 DEBUG("QClipboardWatcher::format: %d formats available", formatList.count());
1183 }
1184 }
1185
1186 return formatList;
1187}
1188
1189bool QClipboardWatcher::hasFormat_sys(const QString &format) const
1190{
1191 QStringList list = formats();
1192 return list.contains(format);
1193}
1194
1195QVariant QClipboardWatcher::retrieveData_sys(const QString &fmt, QVariant::Type requestedType) const
1196{
1197 if (fmt.isEmpty() || empty())
1198 return QByteArray();
1199
1200 (void)formats(); // trigger update of format list
1201 DEBUG("QClipboardWatcher::data: fetching format '%s'", fmt.toLatin1().data());
1202
1203 QList<Atom> atoms;
1204 Atom *targets = (Atom *) format_atoms.data();
1205 int size = format_atoms.size() / sizeof(Atom);
1206 for (int i = 0; i < size; ++i)
1207 atoms.append(targets[i]);
1208
1209 QByteArray encoding;
1210 Atom fmtatom = X11->xdndMimeAtomForFormat(fmt, requestedType, atoms, &encoding);
1211
1212 if (fmtatom == 0)
1213 return QVariant();
1214
1215 return X11->xdndMimeConvertToFormat(fmtatom, getDataInFormat(fmtatom), fmt, requestedType, encoding);
1216}
1217
1218QByteArray QClipboardWatcher::getDataInFormat(Atom fmtatom) const
1219{
1220 QByteArray buf;
1221
1222 Display *dpy = X11->display;
1223 requestor->createWinId();
1224 Window win = requestor->internalWinId();
1225 Q_ASSERT(requestor->testAttribute(Qt::WA_WState_Created));
1226
1227 DEBUG("QClipboardWatcher::getDataInFormat: selection '%s' format '%s'",
1228 X11->xdndAtomToString(atom).data(), X11->xdndAtomToString(fmtatom).data());
1229
1230 XSelectInput(dpy, win, NoEventMask); // don't listen for any events
1231
1232 XDeleteProperty(dpy, win, ATOM(_QT_SELECTION));
1233 XConvertSelection(dpy, atom, fmtatom, ATOM(_QT_SELECTION), win, X11->time);
1234 XSync(dpy, false);
1235
1236 VDEBUG("QClipboardWatcher::getDataInFormat: waiting for SelectionNotify event");
1237
1238 XEvent xevent;
1239 if (!X11->clipboardWaitForEvent(win,SelectionNotify,&xevent,clipboard_timeout) ||
1240 xevent.xselection.property == XNone) {
1241 DEBUG("QClipboardWatcher::getDataInFormat: format not available");
1242 return buf;
1243 }
1244
1245 VDEBUG("QClipboardWatcher::getDataInFormat: fetching data...");
1246
1247 Atom type;
1248 XSelectInput(dpy, win, PropertyChangeMask);
1249
1250 if (X11->clipboardReadProperty(win, ATOM(_QT_SELECTION), true, &buf, 0, &type, 0, false)) {
1251 if (type == ATOM(INCR)) {
1252 int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
1253 buf = X11->clipboardReadIncrementalProperty(win, ATOM(_QT_SELECTION), nbytes, false);
1254 }
1255 }
1256
1257 XSelectInput(dpy, win, NoEventMask);
1258
1259 DEBUG("QClipboardWatcher::getDataInFormat: %d bytes received", buf.size());
1260
1261 return buf;
1262}
1263
1264
1265const QMimeData* QClipboard::mimeData(Mode mode) const
1266{
1267 QClipboardData *d = 0;
1268 switch (mode) {
1269 case Selection:
1270 d = selectionData();
1271 break;
1272 case Clipboard:
1273 d = clipboardData();
1274 break;
1275 default:
1276 qWarning("QClipboard::mimeData: unsupported mode '%d'", mode);
1277 return 0;
1278 }
1279
1280 if (! d->source() && ! timer_event_clear) {
1281 if (mode == Selection) {
1282 if (! selection_watcher)
1283 selection_watcher = new QClipboardWatcher(mode);
1284 d->setSource(selection_watcher);
1285 } else {
1286 if (! clipboard_watcher)
1287 clipboard_watcher = new QClipboardWatcher(mode);
1288 d->setSource(clipboard_watcher);
1289 }
1290
1291 if (! timer_id) {
1292 // start a zero timer - we will clear cached data when the timer
1293 // times out, which will be the next time we hit the event loop...
1294 // that way, the data is cached long enough for calls within a single
1295 // loop/function, but the data doesn't linger around in case the selection
1296 // changes
1297 QClipboard *that = ((QClipboard *) this);
1298 timer_id = that->startTimer(0);
1299 }
1300 }
1301
1302 return d->source();
1303}
1304
1305
1306void QClipboard::setMimeData(QMimeData* src, Mode mode)
1307{
1308 Atom atom, sentinel_atom;
1309 QClipboardData *d;
1310 switch (mode) {
1311 case Selection:
1312 atom = XA_PRIMARY;
1313 sentinel_atom = ATOM(_QT_SELECTION_SENTINEL);
1314 d = selectionData();
1315 break;
1316
1317 case Clipboard:
1318 atom = ATOM(CLIPBOARD);
1319 sentinel_atom = ATOM(_QT_CLIPBOARD_SENTINEL);
1320 d = clipboardData();
1321 break;
1322
1323 default:
1324 qWarning("QClipboard::setMimeData: unsupported mode '%d'", mode);
1325 return;
1326 }
1327
1328 Display *dpy = X11->display;
1329 Window newOwner;
1330
1331 if (! src) { // no data, clear clipboard contents
1332 newOwner = XNone;
1333 d->clear();
1334 } else {
1335 setupOwner();
1336
1337 newOwner = owner->internalWinId();
1338
1339 d->setSource(src);
1340 d->timestamp = X11->time;
1341 }
1342
1343 Window prevOwner = XGetSelectionOwner(dpy, atom);
1344 // use X11->time, since d->timestamp == CurrentTime when clearing
1345 XSetSelectionOwner(dpy, atom, newOwner, X11->time);
1346
1347 if (mode == Selection)
1348 emitChanged(QClipboard::Selection);
1349 else
1350 emitChanged(QClipboard::Clipboard);
1351
1352 if (XGetSelectionOwner(dpy, atom) != newOwner) {
1353 qWarning("QClipboard::setData: Cannot set X11 selection owner for %s",
1354 X11->xdndAtomToString(atom).data());
1355 d->clear();
1356 return;
1357 }
1358
1359 // Signal to other Qt processes that the selection has changed
1360 Window owners[2];
1361 owners[0] = newOwner;
1362 owners[1] = prevOwner;
1363 XChangeProperty(dpy, QApplication::desktop()->screen(0)->internalWinId(),
1364 sentinel_atom, XA_WINDOW, 32, PropModeReplace,
1365 (unsigned char*)&owners, 2);
1366}
1367
1368
1369/*
1370 Called by the main event loop in qapplication_x11.cpp when either
1371 the _QT_SELECTION_SENTINEL property has been changed (i.e. when some
1372 Qt process has performed QClipboard::setData()) or when Xfixes told
1373 us that an other application changed the selection. If it returns
1374 true, the QClipBoard dataChanged() signal should be emitted.
1375*/
1376
1377bool qt_check_selection_sentinel()
1378{
1379 bool doIt = true;
1380 if (owner && !X11->use_xfixes) {
1381 /*
1382 Since the X selection mechanism cannot give any signal when
1383 the selection has changed, we emulate it (for Qt processes) here.
1384 The notification should be ignored in case of either
1385 a) This is the process that did setData (because setData()
1386 then has already emitted dataChanged())
1387 b) This is the process that owned the selection when dataChanged()
1388 was called (because we have then received a SelectionClear event,
1389 and have already emitted dataChanged() as a result of that)
1390 */
1391
1392 unsigned char *retval;
1393 Atom actualType;
1394 int actualFormat;
1395 ulong nitems;
1396 ulong bytesLeft;
1397
1398 if (XGetWindowProperty(X11->display,
1399 QApplication::desktop()->screen(0)->internalWinId(),
1400 ATOM(_QT_SELECTION_SENTINEL), 0, 2, False, XA_WINDOW,
1401 &actualType, &actualFormat, &nitems,
1402 &bytesLeft, &retval) == Success) {
1403 Window *owners = (Window *)retval;
1404 if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) {
1405 Window win = owner->internalWinId();
1406 if (owners[0] == win || owners[1] == win)
1407 doIt = false;
1408 }
1409
1410 XFree(owners);
1411 }
1412 }
1413
1414 if (doIt) {
1415 if (waiting_for_data) {
1416 pending_selection_changed = true;
1417 if (! pending_timer_id)
1418 pending_timer_id = QApplication::clipboard()->startTimer(0);
1419 doIt = false;
1420 } else {
1421 selectionData()->clear();
1422 }
1423 }
1424
1425 return doIt;
1426}
1427
1428
1429bool qt_check_clipboard_sentinel()
1430{
1431 bool doIt = true;
1432 if (owner && !X11->use_xfixes) {
1433 unsigned char *retval;
1434 Atom actualType;
1435 int actualFormat;
1436 unsigned long nitems, bytesLeft;
1437
1438 if (XGetWindowProperty(X11->display,
1439 QApplication::desktop()->screen(0)->internalWinId(),
1440 ATOM(_QT_CLIPBOARD_SENTINEL), 0, 2, False, XA_WINDOW,
1441 &actualType, &actualFormat, &nitems, &bytesLeft,
1442 &retval) == Success) {
1443 Window *owners = (Window *)retval;
1444 if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) {
1445 Window win = owner->internalWinId();
1446 if (owners[0] == win || owners[1] == win)
1447 doIt = false;
1448 }
1449
1450 XFree(owners);
1451 }
1452 }
1453
1454 if (doIt) {
1455 if (waiting_for_data) {
1456 pending_clipboard_changed = true;
1457 if (! pending_timer_id)
1458 pending_timer_id = QApplication::clipboard()->startTimer(0);
1459 doIt = false;
1460 } else {
1461 clipboardData()->clear();
1462 }
1463 }
1464
1465 return doIt;
1466}
1467
1468bool qt_xfixes_selection_changed(Window selectionOwner, Time timestamp)
1469{
1470 QClipboardData *d = selectionData();
1471#ifdef QCLIPBOARD_DEBUG
1472 DEBUG("qt_xfixes_selection_changed: owner = %u; selectionOwner = %u; internal timestamp = %u; external timestamp = %u",
1473 (unsigned int)(owner ? (int)owner->internalWinId() : 0), (unsigned int)selectionOwner,
1474 (unsigned int)(d ? d->timestamp : 0), (unsigned int)timestamp);
1475#endif
1476 if (!owner || (selectionOwner && selectionOwner != owner->internalWinId()) ||
1477 (!selectionOwner && d->timestamp != CurrentTime && d->timestamp < timestamp))
1478 return qt_check_selection_sentinel();
1479 return false;
1480}
1481
1482bool qt_xfixes_clipboard_changed(Window clipboardOwner, Time timestamp)
1483{
1484 QClipboardData *d = clipboardData();
1485#ifdef QCLIPBOARD_DEBUG
1486 DEBUG("qt_xfixes_clipboard_changed: owner = %u; clipboardOwner = %u; internal timestamp = %u; external timestamp = %u",
1487 (unsigned int)(owner ? (int)owner->internalWinId() : 0), (unsigned int)clipboardOwner,
1488 (unsigned int)(d ? d->timestamp : 0), (unsigned int)timestamp);
1489#endif
1490 if (!owner || (clipboardOwner && clipboardOwner != owner->internalWinId()) ||
1491 (!clipboardOwner && d->timestamp != CurrentTime && d->timestamp < timestamp))
1492 return qt_check_clipboard_sentinel();
1493 return false;
1494}
1495
1496QT_END_NAMESPACE
1497
1498#endif // QT_NO_CLIPBOARD
Note: See TracBrowser for help on using the repository browser.