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

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

trunk: Merged in qt 4.6.2 sources.

File size: 49.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this 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 have questions regarding the use of this file, please contact
37** Nokia 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#include <private/qwidget_p.h>
82
83#ifndef QT_NO_XFIXES
84#include <X11/extensions/Xfixes.h>
85#endif // QT_NO_XFIXES
86
87QT_BEGIN_NAMESPACE
88
89/*****************************************************************************
90 Internal QClipboard functions for X11.
91 *****************************************************************************/
92
93static int clipboard_timeout = 5000; // 5s timeout on clipboard operations
94
95static QWidget * owner = 0;
96static QWidget *requestor = 0;
97static bool timer_event_clear = false;
98static int timer_id = 0;
99
100static int pending_timer_id = 0;
101static bool pending_clipboard_changed = false;
102static bool pending_selection_changed = false;
103
104
105// event capture mechanism for qt_xclb_wait_for_event
106static bool waiting_for_data = false;
107static bool has_captured_event = false;
108static Window capture_event_win = XNone;
109static int capture_event_type = -1;
110static XEvent captured_event;
111
112class QClipboardWatcher; // forward decl
113static QClipboardWatcher *selection_watcher = 0;
114static QClipboardWatcher *clipboard_watcher = 0;
115
116static void cleanup()
117{
118 delete owner;
119 delete requestor;
120 owner = 0;
121 requestor = 0;
122}
123
124static
125void setupOwner()
126{
127 if (owner)
128 return;
129 owner = new QWidget(0);
130 owner->setObjectName(QLatin1String("internal clipboard owner"));
131 owner->createWinId();
132 requestor = new QWidget(0);
133 requestor->createWinId();
134 requestor->setObjectName(QLatin1String("internal clipboard requestor"));
135 // We dont need this internal widgets to appear in QApplication::topLevelWidgets()
136 if (QWidgetPrivate::allWidgets) {
137 QWidgetPrivate::allWidgets->remove(owner);
138 QWidgetPrivate::allWidgets->remove(requestor);
139 }
140 qAddPostRoutine(cleanup);
141}
142
143
144class QClipboardWatcher : public QInternalMimeData {
145public:
146 QClipboardWatcher(QClipboard::Mode mode);
147 ~QClipboardWatcher();
148 bool empty() const;
149 virtual bool hasFormat_sys(const QString &mimetype) const;
150 virtual QStringList formats_sys() const;
151
152 QVariant retrieveData_sys(const QString &mimetype, QVariant::Type type) const;
153 QByteArray getDataInFormat(Atom fmtatom) const;
154
155 Atom atom;
156 mutable QStringList formatList;
157 mutable QByteArray format_atoms;
158};
159
160class QClipboardData
161{
162private:
163 QMimeData *&mimeDataRef() const
164 {
165 if(mode == QClipboard::Selection)
166 return selectionData;
167 return clipboardData;
168 }
169
170public:
171 QClipboardData(QClipboard::Mode mode);
172 ~QClipboardData();
173
174 void setSource(QMimeData* s)
175 {
176 if ((mode == QClipboard::Selection && selectionData == s)
177 || clipboardData == s) {
178 return;
179 }
180
181 if (selectionData != clipboardData) {
182 delete mimeDataRef();
183 }
184
185 mimeDataRef() = s;
186 }
187
188 QMimeData *source() const
189 {
190 return mimeDataRef();
191 }
192
193 void clear()
194 {
195 timestamp = CurrentTime;
196 if (selectionData == clipboardData) {
197 mimeDataRef() = 0;
198 } else {
199 QMimeData *&src = mimeDataRef();
200 delete src;
201 src = 0;
202 }
203 }
204
205 static QMimeData *selectionData;
206 static QMimeData *clipboardData;
207 Time timestamp;
208 QClipboard::Mode mode;
209};
210
211QMimeData *QClipboardData::selectionData = 0;
212QMimeData *QClipboardData::clipboardData = 0;
213
214QClipboardData::QClipboardData(QClipboard::Mode clipboardMode)
215{
216 timestamp = CurrentTime;
217 mode = clipboardMode;
218}
219
220QClipboardData::~QClipboardData()
221{ clear(); }
222
223
224static QClipboardData *internalCbData = 0;
225static QClipboardData *internalSelData = 0;
226
227static void cleanupClipboardData()
228{
229 delete internalCbData;
230 internalCbData = 0;
231}
232
233static QClipboardData *clipboardData()
234{
235 if (internalCbData == 0) {
236 internalCbData = new QClipboardData(QClipboard::Clipboard);
237 qAddPostRoutine(cleanupClipboardData);
238 }
239 return internalCbData;
240}
241
242static void cleanupSelectionData()
243{
244 delete internalSelData;
245 internalSelData = 0;
246}
247
248static QClipboardData *selectionData()
249{
250 if (internalSelData == 0) {
251 internalSelData = new QClipboardData(QClipboard::Selection);
252 qAddPostRoutine(cleanupSelectionData);
253 }
254 return internalSelData;
255}
256
257class QClipboardINCRTransaction
258{
259public:
260 QClipboardINCRTransaction(Window w, Atom p, Atom t, int f, QByteArray d, unsigned int i);
261 ~QClipboardINCRTransaction(void);
262
263 int x11Event(XEvent *event);
264
265 Window window;
266 Atom property, target;
267 int format;
268 QByteArray data;
269 unsigned int increment;
270 unsigned int offset;
271};
272
273typedef QMap<Window,QClipboardINCRTransaction*> TransactionMap;
274static TransactionMap *transactions = 0;
275static QApplication::EventFilter prev_event_filter = 0;
276static int incr_timer_id = 0;
277
278static bool qt_x11_incr_event_filter(void *message, long *result)
279{
280 XEvent *event = reinterpret_cast<XEvent *>(message);
281 TransactionMap::Iterator it = transactions->find(event->xany.window);
282 if (it != transactions->end()) {
283 if ((*it)->x11Event(event) != 0)
284 return true;
285 }
286 if (prev_event_filter)
287 return prev_event_filter(event, result);
288 return false;
289}
290
291/*
292 called when no INCR activity has happened for 'clipboard_timeout'
293 milliseconds... we assume that all unfinished transactions have
294 timed out and remove everything from the transaction map
295*/
296static void qt_xclb_incr_timeout(void)
297{
298 qWarning("QClipboard: Timed out while sending data");
299
300 while (transactions)
301 delete *transactions->begin();
302}
303
304QClipboardINCRTransaction::QClipboardINCRTransaction(Window w, Atom p, Atom t, int f,
305 QByteArray d, unsigned int i)
306 : window(w), property(p), target(t), format(f), data(d), increment(i), offset(0u)
307{
308 DEBUG("QClipboard: sending %d bytes (INCR transaction %p)", d.size(), this);
309
310 XSelectInput(X11->display, window, PropertyChangeMask);
311
312 if (! transactions) {
313 VDEBUG("QClipboard: created INCR transaction map");
314 transactions = new TransactionMap;
315 prev_event_filter = qApp->setEventFilter(qt_x11_incr_event_filter);
316 incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout);
317 }
318 transactions->insert(window, this);
319}
320
321QClipboardINCRTransaction::~QClipboardINCRTransaction(void)
322{
323 VDEBUG("QClipboard: destroyed INCR transacton %p", this);
324
325 XSelectInput(X11->display, window, NoEventMask);
326
327 transactions->remove(window);
328 if (transactions->isEmpty()) {
329 VDEBUG("QClipboard: no more INCR transactions");
330 delete transactions;
331 transactions = 0;
332
333 (void)qApp->setEventFilter(prev_event_filter);
334
335 if (incr_timer_id != 0) {
336 QApplication::clipboard()->killTimer(incr_timer_id);
337 incr_timer_id = 0;
338 }
339 }
340}
341
342int QClipboardINCRTransaction::x11Event(XEvent *event)
343{
344 if (event->type != PropertyNotify
345 || (event->xproperty.state != PropertyDelete
346 || event->xproperty.atom != property))
347 return 0;
348
349 // restart the INCR timer
350 if (incr_timer_id) QApplication::clipboard()->killTimer(incr_timer_id);
351 incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout);
352
353 unsigned int bytes_left = data.size() - offset;
354 if (bytes_left > 0) {
355 unsigned int xfer = qMin(increment, bytes_left);
356 VDEBUG("QClipboard: sending %d bytes, %d remaining (INCR transaction %p)",
357 xfer, bytes_left - xfer, this);
358
359 XChangeProperty(X11->display, window, property, target, format,
360 PropModeReplace, (uchar *) data.data() + offset, xfer);
361 offset += xfer;
362 } else {
363 // INCR transaction finished...
364 XChangeProperty(X11->display, window, property, target, format,
365 PropModeReplace, (uchar *) data.data(), 0);
366 delete this;
367 }
368
369 return 1;
370}
371
372
373/*****************************************************************************
374 QClipboard member functions for X11.
375 *****************************************************************************/
376
377struct qt_init_timestamp_data
378{
379 Time timestamp;
380};
381
382#if defined(Q_C_CALLBACKS)
383extern "C" {
384#endif
385
386static Bool qt_init_timestamp_scanner(Display*, XEvent *event, XPointer arg)
387{
388 qt_init_timestamp_data *data =
389 reinterpret_cast<qt_init_timestamp_data*>(arg);
390 switch(event->type)
391 {
392 case ButtonPress:
393 case ButtonRelease:
394 data->timestamp = event->xbutton.time;
395 break;
396 case MotionNotify:
397 data->timestamp = event->xmotion.time;
398 break;
399 case XKeyPress:
400 case XKeyRelease:
401 data->timestamp = event->xkey.time;
402 break;
403 case PropertyNotify:
404 data->timestamp = event->xproperty.time;
405 break;
406 case EnterNotify:
407 case LeaveNotify:
408 data->timestamp = event->xcrossing.time;
409 break;
410 case SelectionClear:
411 data->timestamp = event->xselectionclear.time;
412 break;
413 default:
414 break;
415 }
416#ifndef QT_NO_XFIXES
417 if (X11->use_xfixes && event->type == (X11->xfixes_eventbase + XFixesSelectionNotify)) {
418 XFixesSelectionNotifyEvent *req =
419 reinterpret_cast<XFixesSelectionNotifyEvent *>(event);
420 data->timestamp = req->selection_timestamp;
421 }
422#endif
423 return false;
424}
425
426#if defined(Q_C_CALLBACKS)
427}
428#endif
429
430QClipboard::QClipboard(QObject *parent)
431 : QObject(*new QClipboardPrivate, parent)
432{
433 // create desktop widget since we need it to get PropertyNotify or
434 // XFixesSelectionNotify events when someone changes the
435 // clipboard.
436 (void)QApplication::desktop();
437
438#ifndef QT_NO_XFIXES
439 if (X11->use_xfixes && X11->ptrXFixesSelectSelectionInput) {
440 const unsigned long eventMask =
441 XFixesSetSelectionOwnerNotifyMask | XFixesSelectionWindowDestroyNotifyMask | XFixesSelectionClientCloseNotifyMask;
442 for (int i = 0; i < X11->screenCount; ++i) {
443 X11->ptrXFixesSelectSelectionInput(X11->display, QX11Info::appRootWindow(i),
444 XA_PRIMARY, eventMask);
445 X11->ptrXFixesSelectSelectionInput(X11->display, QX11Info::appRootWindow(i),
446 ATOM(CLIPBOARD), eventMask);
447 }
448 }
449#endif // QT_NO_XFIXES
450
451 if (X11->time == CurrentTime) {
452 // send a dummy event to myself to get the timestamp from X11.
453 qt_init_timestamp_data data;
454 data.timestamp = CurrentTime;
455 XEvent ev;
456 XCheckIfEvent(X11->display, &ev, &qt_init_timestamp_scanner, (XPointer)&data);
457 if (data.timestamp == CurrentTime) {
458 setupOwner();
459 int dummy = 0;
460 Window ownerId = owner->internalWinId();
461 XChangeProperty(X11->display, ownerId,
462 ATOM(CLIP_TEMPORARY), XA_INTEGER, 32,
463 PropModeReplace, (uchar*)&dummy, 1);
464 XWindowEvent(X11->display, ownerId, PropertyChangeMask, &ev);
465 data.timestamp = ev.xproperty.time;
466 XDeleteProperty(X11->display, ownerId, ATOM(CLIP_TEMPORARY));
467 }
468 X11->time = data.timestamp;
469 }
470}
471
472void QClipboard::clear(Mode mode)
473{
474 setMimeData(0, mode);
475}
476
477
478bool QClipboard::supportsMode(Mode mode) const
479{
480 return (mode == Clipboard || mode == Selection);
481}
482
483bool QClipboard::ownsMode(Mode mode) const
484{
485 if (mode == Clipboard)
486 return clipboardData()->timestamp != CurrentTime;
487 else if(mode == Selection)
488 return selectionData()->timestamp != CurrentTime;
489 else
490 return false;
491}
492
493
494// event filter function... captures interesting events while
495// qt_xclb_wait_for_event is running the event loop
496static bool qt_x11_clipboard_event_filter(void *message, long *)
497{
498 XEvent *event = reinterpret_cast<XEvent *>(message);
499 if (event->xany.type == capture_event_type &&
500 event->xany.window == capture_event_win) {
501 VDEBUG("QClipboard: event_filter(): caught event type %d", event->type);
502 has_captured_event = true;
503 captured_event = *event;
504 return true;
505 }
506 return false;
507}
508
509static Bool checkForClipboardEvents(Display *, XEvent *e, XPointer)
510{
511 return ((e->type == SelectionRequest && (e->xselectionrequest.selection == XA_PRIMARY
512 || e->xselectionrequest.selection == ATOM(CLIPBOARD)))
513 || (e->type == SelectionClear && (e->xselectionclear.selection == XA_PRIMARY
514 || e->xselectionclear.selection == ATOM(CLIPBOARD))));
515}
516
517bool QX11Data::clipboardWaitForEvent(Window win, int type, XEvent *event, int timeout)
518{
519 QTime started = QTime::currentTime();
520 QTime now = started;
521
522 if (QAbstractEventDispatcher::instance()->inherits("QtMotif")
523 || QApplication::clipboard()->property("useEventLoopWhenWaiting").toBool()) {
524 if (waiting_for_data) {
525 Q_ASSERT(!"QClipboard: internal error, qt_xclb_wait_for_event recursed");
526 return false;
527 }
528 waiting_for_data = true;
529
530
531 has_captured_event = false;
532 capture_event_win = win;
533 capture_event_type = type;
534
535 QApplication::EventFilter old_event_filter =
536 qApp->setEventFilter(qt_x11_clipboard_event_filter);
537
538 do {
539 if (XCheckTypedWindowEvent(display, win, type, event)) {
540 waiting_for_data = false;
541 qApp->setEventFilter(old_event_filter);
542 return true;
543 }
544
545 XSync(X11->display, false);
546 usleep(50000);
547
548 now = QTime::currentTime();
549 if (started > now) // crossed midnight
550 started = now;
551
552 QEventLoop::ProcessEventsFlags flags(QEventLoop::ExcludeUserInputEvents
553 | QEventLoop::ExcludeSocketNotifiers
554 | QEventLoop::WaitForMoreEvents
555 | QEventLoop::X11ExcludeTimers);
556 QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance();
557 eventDispatcher->processEvents(flags);
558
559 if (has_captured_event) {
560 waiting_for_data = false;
561 *event = captured_event;
562 qApp->setEventFilter(old_event_filter);
563 return true;
564 }
565 } while (started.msecsTo(now) < timeout);
566
567 waiting_for_data = false;
568 qApp->setEventFilter(old_event_filter);
569 } else {
570 do {
571 if (XCheckTypedWindowEvent(X11->display,win,type,event))
572 return true;
573
574 // process other clipboard events, since someone is probably requesting data from us
575 XEvent e;
576 if (XCheckIfEvent(X11->display, &e, checkForClipboardEvents, 0))
577 qApp->x11ProcessEvent(&e);
578
579 now = QTime::currentTime();
580 if ( started > now ) // crossed midnight
581 started = now;
582
583 XFlush(X11->display);
584
585 // sleep 50 ms, so we don't use up CPU cycles all the time.
586 struct timeval usleep_tv;
587 usleep_tv.tv_sec = 0;
588 usleep_tv.tv_usec = 50000;
589 select(0, 0, 0, 0, &usleep_tv);
590 } while (started.msecsTo(now) < timeout);
591 }
592 return false;
593}
594
595
596static inline int maxSelectionIncr(Display *dpy)
597{ return XMaxRequestSize(dpy) > 65536 ? 65536*4 : XMaxRequestSize(dpy)*4 - 100; }
598
599bool QX11Data::clipboardReadProperty(Window win, Atom property, bool deleteProperty,
600 QByteArray *buffer, int *size, Atom *type, int *format, bool nullterm)
601{
602 int maxsize = maxSelectionIncr(display);
603 ulong bytes_left; // bytes_after
604 ulong length; // nitems
605 uchar *data;
606 Atom dummy_type;
607 int dummy_format;
608 int r;
609
610 if (!type) // allow null args
611 type = &dummy_type;
612 if (!format)
613 format = &dummy_format;
614
615 // Don't read anything, just get the size of the property data
616 r = XGetWindowProperty(display, win, property, 0, 0, False,
617 AnyPropertyType, type, format,
618 &length, &bytes_left, &data);
619 if (r != Success || (type && *type == XNone)) {
620 buffer->resize(0);
621 return false;
622 }
623 XFree((char*)data);
624
625 int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
626
627 VDEBUG("QClipboard: read_property(): initial property length: %d", proplen);
628
629 switch (*format) {
630 case 8:
631 default:
632 format_inc = sizeof(char) / 1;
633 break;
634
635 case 16:
636 format_inc = sizeof(short) / 2;
637 proplen *= sizeof(short) / 2;
638 break;
639
640 case 32:
641 format_inc = sizeof(long) / 4;
642 proplen *= sizeof(long) / 4;
643 break;
644 }
645
646 int newSize = proplen + (nullterm ? 1 : 0);
647 buffer->resize(newSize);
648
649 bool ok = (buffer->size() == newSize);
650 VDEBUG("QClipboard: read_property(): buffer resized to %d", buffer->size());
651
652 if (ok) {
653 // could allocate buffer
654
655 while (bytes_left) {
656 // more to read...
657
658 r = XGetWindowProperty(display, win, property, offset, maxsize/4,
659 False, AnyPropertyType, type, format,
660 &length, &bytes_left, &data);
661 if (r != Success || (type && *type == XNone))
662 break;
663
664 offset += length / (32 / *format);
665 length *= format_inc * (*format) / 8;
666
667 // Here we check if we get a buffer overflow and tries to
668 // recover -- this shouldn't normally happen, but it doesn't
669 // hurt to be defensive
670 if ((int)(buffer_offset + length) > buffer->size()) {
671 length = buffer->size() - buffer_offset;
672
673 // escape loop
674 bytes_left = 0;
675 }
676
677 memcpy(buffer->data() + buffer_offset, data, length);
678 buffer_offset += length;
679
680 XFree((char*)data);
681 }
682
683 if (*format == 8 && *type == ATOM(COMPOUND_TEXT)) {
684 // convert COMPOUND_TEXT to a multibyte string
685 XTextProperty textprop;
686 textprop.encoding = *type;
687 textprop.format = *format;
688 textprop.nitems = length;
689 textprop.value = (unsigned char *) buffer->data();
690
691 char **list_ret = 0;
692 int count;
693 if (XmbTextPropertyToTextList(display, &textprop, &list_ret,
694 &count) == Success && count && list_ret) {
695 offset = strlen(list_ret[0]);
696 buffer->resize(offset + (nullterm ? 1 : 0));
697 memcpy(buffer->data(), list_ret[0], offset);
698 }
699 if (list_ret) XFreeStringList(list_ret);
700 }
701
702 // zero-terminate (for text)
703 if (nullterm)
704 buffer->data()[buffer_offset] = '\0';
705 }
706
707 // correct size, not 0-term.
708 if (size)
709 *size = buffer_offset;
710
711 VDEBUG("QClipboard: read_property(): buffer size %d, buffer offset %d, offset %d",
712 buffer->size(), buffer_offset, offset);
713
714 if (deleteProperty)
715 XDeleteProperty(display, win, property);
716
717 XFlush(display);
718
719 return ok;
720}
721
722QByteArray QX11Data::clipboardReadIncrementalProperty(Window win, Atom property, int nbytes, bool nullterm)
723{
724 XEvent event;
725
726 QByteArray buf;
727 QByteArray tmp_buf;