1
/****************************************************************************
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
5
** This file is part of the gui module of the Qt Toolkit.
7
** This file may be distributed under the terms of the Q Public License
8
** as defined by Trolltech AS of Norway and appearing in the file
9
** LICENSE.QPL included in the packaging of this file.
11
** This file may be distributed and/or modified under the terms of the
12
** GNU General Public License version 2 as published by the Free Software
13
** Foundation and appearing in the file LICENSE.GPL included in the
14
** packaging of this file.
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
17
** information about Qt Commercial License Agreements.
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
21
** Contact info@trolltech.com if any conditions of this licensing are
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27
****************************************************************************/
29
// #define QCLIPBOARD_DEBUG
30
// #define QCLIPBOARD_DEBUG_VERBOSE
32
#ifdef QCLIPBOARD_DEBUG
35
# define DEBUG if (false) qDebug
38
#ifdef QCLIPBOARD_DEBUG_VERBOSE
39
# define VDEBUG qDebug
41
# define VDEBUG if (false) qDebug
44
#include "qplatformdefs.h"
46
#include "qclipboard.h"
48
#ifndef QT_NO_CLIPBOARD
50
#include "qabstracteventdispatcher.h"
51
#include "qapplication.h"
52
#include "qdesktopwidget.h"
54
#include "qdatetime.h"
55
#include "qiodevice.h"
57
#include "qtextcodec.h"
60
#include "qapplication_p.h"
63
#include "qx11info_x11.h"
64
#include "qimagewriter.h"
68
/*****************************************************************************
69
Internal QClipboard functions for X11.
70
*****************************************************************************/
72
static int clipboard_timeout = 5000; // 5s timeout on clipboard operations
74
static QWidget * owner = 0;
75
static QWidget *requestor = 0;
76
static bool timer_event_clear = false;
77
static int timer_id = 0;
79
static int pending_timer_id = 0;
80
static bool pending_clipboard_changed = false;
81
static bool pending_selection_changed = false;
84
// event capture mechanism for qt_xclb_wait_for_event
85
static bool waiting_for_data = false;
86
static bool has_captured_event = false;
87
static Window capture_event_win = XNone;
88
static int capture_event_type = -1;
89
static XEvent captured_event;
91
class QClipboardWatcher; // forward decl
92
static QClipboardWatcher *selection_watcher = 0;
93
static QClipboardWatcher *clipboard_watcher = 0;
108
owner = new QWidget(0);
109
owner->setObjectName("internal clipboard owner");
110
requestor = new QWidget(0);
111
requestor->setObjectName("internal clipboard requestor");
112
qAddPostRoutine(cleanup);
116
int sizeof_format(int format)
121
case 8: sz = sizeof(char); break;
122
case 16: sz = sizeof(short); break;
123
case 32: sz = sizeof(long); break;
128
class QClipboardWatcher : public QInternalMimeData {
130
QClipboardWatcher(QClipboard::Mode mode);
131
~QClipboardWatcher();
133
virtual bool hasFormat_sys(const QString &mimetype) const;
134
virtual QStringList formats_sys() const;
136
QVariant retrieveData_sys(const QString &mimetype, QVariant::Type type) const;
137
QByteArray getDataInFormat(Atom fmtatom) const;
140
mutable QStringList formatList;
141
mutable QByteArray format_atoms;
152
void setSource(QMimeData* s)
158
QMimeData *source() const { return src; }
160
void addTransferredPixmap(QPixmap pm)
162
/* TODO: queue them */
163
transferred[tindex] = pm;
166
void clearTransfers()
168
transferred[0] = QPixmap();
169
transferred[1] = QPixmap();
177
QPixmap transferred[2];
181
QClipboardData::QClipboardData()
184
timestamp = CurrentTime;
188
QClipboardData::~QClipboardData()
191
void QClipboardData::clear()
195
timestamp = CurrentTime;
199
static QClipboardData *internalCbData = 0;
200
static QClipboardData *internalSelData = 0;
202
static void cleanupClipboardData()
204
delete internalCbData;
208
static QClipboardData *clipboardData()
210
if (internalCbData == 0) {
211
internalCbData = new QClipboardData;
212
qAddPostRoutine(cleanupClipboardData);
214
return internalCbData;
217
static void cleanupSelectionData()
219
delete internalSelData;
223
static QClipboardData *selectionData()
225
if (internalSelData == 0) {
226
internalSelData = new QClipboardData;
227
qAddPostRoutine(cleanupSelectionData);
229
return internalSelData;
232
class QClipboardINCRTransaction
235
QClipboardINCRTransaction(Window w, Atom p, Atom t, int f, QByteArray d, unsigned int i);
236
~QClipboardINCRTransaction(void);
238
int x11Event(XEvent *event);
241
Atom property, target;
244
unsigned int increment;
248
typedef QMap<Window,QClipboardINCRTransaction*> TransactionMap;
249
static TransactionMap *transactions = 0;
250
static QApplication::EventFilter prev_event_filter = 0;
251
static int incr_timer_id = 0;
253
static bool qt_x11_incr_event_filter(void *message, long *result)
255
XEvent *event = reinterpret_cast<XEvent *>(message);
256
TransactionMap::Iterator it = transactions->find(event->xany.window);
257
if (it != transactions->end()) {
258
if ((*it)->x11Event(event) != 0)
261
if (prev_event_filter)
262
return prev_event_filter(event, result);
267
called when no INCR activity has happened for 'clipboard_timeout'
268
milliseconds... we assume that all unfinished transactions have
269
timed out and remove everything from the transaction map
271
static void qt_xclb_incr_timeout(void)
273
qWarning("QClipboard: timed out while sending data");
276
delete *transactions->begin();
279
QClipboardINCRTransaction::QClipboardINCRTransaction(Window w, Atom p, Atom t, int f,
280
QByteArray d, unsigned int i)
281
: window(w), property(p), target(t), format(f), data(d), increment(i), offset(0u)
283
DEBUG("QClipboard: sending %d bytes (INCR transaction %p)", d.size(), this);
285
XSelectInput(X11->display, window, PropertyChangeMask);
287
if (! transactions) {
288
VDEBUG("QClipboard: created INCR transaction map");
289
transactions = new TransactionMap;
290
prev_event_filter = qApp->setEventFilter(qt_x11_incr_event_filter);
291
incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout);
293
transactions->insert(window, this);
296
QClipboardINCRTransaction::~QClipboardINCRTransaction(void)
298
VDEBUG("QClipboard: destroyed INCR transacton %p", this);
300
XSelectInput(X11->display, window, NoEventMask);
302
transactions->remove(window);
303
if (transactions->isEmpty()) {
304
VDEBUG("QClipboard: no more INCR transactions");
308
(void)qApp->setEventFilter(prev_event_filter);
310
if (incr_timer_id != 0) {
311
QApplication::clipboard()->killTimer(incr_timer_id);
317
int QClipboardINCRTransaction::x11Event(XEvent *event)
319
if (event->type != PropertyNotify
320
|| (event->xproperty.state != PropertyDelete
321
|| event->xproperty.atom != property))
324
// restart the INCR timer
325
if (incr_timer_id) QApplication::clipboard()->killTimer(incr_timer_id);
326
incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout);
328
unsigned int bytes_left = data.size() - offset;
329
if (bytes_left > 0) {
330
unsigned int xfer = qMin(increment, bytes_left);
331
VDEBUG("QClipboard: sending %d bytes, %d remaining (INCR transaction %p)",
332
xfer, bytes_left - xfer, this);
334
XChangeProperty(X11->display, window, property, target, format,
335
PropModeReplace, (uchar *) data.data() + offset, xfer);
338
// INCR transaction finished...
339
XChangeProperty(X11->display, window, property, target, format,
340
PropModeReplace, (uchar *) data.data(), 0);
348
/*****************************************************************************
349
QClipboard member functions for X11.
350
*****************************************************************************/
353
void QClipboard::clear(Mode mode)
355
setMimeData(0, mode);
360
Returns true if the clipboard supports mouse selection; otherwise
363
bool QClipboard::supportsSelection() const
370
Returns true if this clipboard object owns the mouse selection
371
data; otherwise returns false.
373
bool QClipboard::ownsSelection() const
374
{ return selectionData()->timestamp != CurrentTime; }
377
Returns true if this clipboard object owns the clipboard data;
378
otherwise returns false.
380
bool QClipboard::ownsClipboard() const
381
{ return clipboardData()->timestamp != CurrentTime; }
384
// event filter function... captures interesting events while
385
// qt_xclb_wait_for_event is running the event loop
386
static bool qt_x11_clipboard_event_filter(void *message, long *)
388
XEvent *event = reinterpret_cast<XEvent *>(message);
389
if (event->xany.type == capture_event_type &&
390
event->xany.window == capture_event_win) {
391
VDEBUG("QClipboard: event_filter(): caught event type %d", event->type);
392
has_captured_event = true;
393
captured_event = *event;
399
bool QX11Data::clipboardWaitForEvent(Window win, int type, XEvent *event, int timeout)
401
QTime started = QTime::currentTime();
404
if (QAbstractEventDispatcher::instance()->inherits("QMotif")) {
405
if (waiting_for_data)
406
qFatal("QClipboard: internal error, qt_xclb_wait_for_event recursed");
407
waiting_for_data = true;
410
has_captured_event = false;
411
capture_event_win = win;
412
capture_event_type = type;
414
QApplication::EventFilter old_event_filter =
415
qApp->setEventFilter(qt_x11_clipboard_event_filter);
418
if (XCheckTypedWindowEvent(display, win, type, event)) {
419
waiting_for_data = false;
420
qApp->setEventFilter(old_event_filter);
424
XSync(X11->display, false);
427
now = QTime::currentTime();
428
if (started > now) // crossed midnight
431
// 0x08 == ExcludeTimers for X11 only
432
QEventLoop::ProcessEventsFlags flags(QEventLoop::ExcludeUserInputEvents
433
| QEventLoop::ExcludeSocketNotifiers
434
| QEventLoop::WaitForMoreEvents
436
QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance();
437
eventDispatcher->processEvents(flags);
439
if (has_captured_event) {
440
waiting_for_data = false;
441
*event = captured_event;
442
qApp->setEventFilter(old_event_filter);
445
} while (started.msecsTo(now) < timeout);
447
waiting_for_data = false;
448
qApp->setEventFilter(old_event_filter);
451
if (XCheckTypedWindowEvent(X11->display,win,type,event))
454
now = QTime::currentTime();
455
if ( started > now ) // crossed midnight
458
XFlush(X11->display);
460
// sleep 50ms, so we don't use up CPU cycles all the time.
461
struct timeval usleep_tv;
462
usleep_tv.tv_sec = 0;
463
usleep_tv.tv_usec = 50000;
464
select(0, 0, 0, 0, &usleep_tv);
465
} while (started.msecsTo(now) < timeout);
471
static inline int maxSelectionIncr(Display *dpy)
472
{ return XMaxRequestSize(dpy) > 65536 ? 65536*4 : XMaxRequestSize(dpy)*4 - 100; }
474
bool QX11Data::clipboardReadProperty(Window win, Atom property, bool deleteProperty,
475
QByteArray *buffer, int *size, Atom *type, int *format, bool nullterm)
477
int maxsize = maxSelectionIncr(display);
478
ulong bytes_left; // bytes_after
479
ulong length; // nitems
485
if (!type) // allow null args
488
format = &dummy_format;
490
// Don't read anything, just get the size of the property data
491
r = XGetWindowProperty(display, win, property, 0, 0, False,
492
AnyPropertyType, type, format,
493
&length, &bytes_left, &data);
494
if (r != Success || (type && *type == XNone)) {
500
int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
502
VDEBUG("QClipboard: read_property(): initial property length: %d", proplen);
507
format_inc = sizeof(char) / 1;
511
format_inc = sizeof(short) / 2;
512
proplen *= sizeof(short) / 2;
516
format_inc = sizeof(long) / 4;
517
proplen *= sizeof(long) / 4;
521
int newSize = proplen + (nullterm ? 1 : 0);
522
buffer->resize(newSize);
524
bool ok = (buffer->size() == newSize);
525
VDEBUG("QClipboard: read_property(): buffer resized to %d", buffer->size());
528
// could allocate buffer
533
r = XGetWindowProperty(display, win, property, offset, maxsize/4,
534
False, AnyPropertyType, type, format,
535
&length, &bytes_left, &data);
536
if (r != Success || (type && *type == XNone))
539
offset += length / (32 / *format);
540
length *= format_inc * (*format) / 8;
542
// Here we check if we get a buffer overflow and tries to
543
// recover -- this shouldn't normally happen, but it doesn't
544
// hurt to be defensive
545
if ((int)(buffer_offset + length) > buffer->size()) {
546
length = buffer->size() - buffer_offset;
552
memcpy(buffer->data() + buffer_offset, data, length);
553
buffer_offset += length;
558
if (*format == 8 && *type == ATOM(COMPOUND_TEXT)) {
559
// convert COMPOUND_TEXT to a multibyte string
560
XTextProperty textprop;
561
textprop.encoding = *type;
562
textprop.format = *format;
563
textprop.nitems = length;
564
textprop.value = (unsigned char *) buffer->data();
568
if (XmbTextPropertyToTextList(display, &textprop, &list_ret,
569
&count) == Success &&
571
offset = strlen(list_ret[0]);
572
buffer->resize(offset + (nullterm ? 1 : 0));
573
memcpy(buffer->data(), list_ret[0], offset);
575
if (list_ret) XFreeStringList(list_ret);
578
// zero-terminate (for text)
580
buffer[buffer_offset] = '\0';
583
// correct size, not 0-term.
585
*size = buffer_offset;
587
VDEBUG("QClipboard: read_property(): buffer size %d, buffer offset %d, offset %d",
588
buffer->size(), buffer_offset, offset);
591
XDeleteProperty(display, win, property);
598
QByteArray QX11Data::clipboardReadIncrementalProperty(Window win, Atom property, int nbytes, bool nullterm)
604
bool alloc_error = false;
609
// Reserve buffer + zero-terminator (for text data)
610
// We want to complete the INCR transfer even if we cannot
611
// allocate more memory
612
buf.resize(nbytes+1);
613
alloc_error = buf.size() != nbytes+1;
618
if (!clipboardWaitForEvent(win,PropertyNotify,&event,clipboard_timeout))
620
if (event.xproperty.atom != property ||
621
event.xproperty.state != PropertyNewValue)
623
if (X11->clipboardReadProperty(win, property, true, &tmp_buf, &length, 0, 0, false)) {
624
if (length == 0) { // no more data, we're done
626
buf.resize(offset+1);
632
} else if (!alloc_error) {
633
if (offset+length > (int)buf.size()) {
634
buf.resize(offset+length+65535);
635
if (buf.size() != offset+length+65535) {
637
length = buf.size() - offset;
640
memcpy(buf.data()+offset, tmp_buf.constData(), length);
649
// timed out ... create a new requestor window, otherwise the requestor
650
// could consider next request to be still part of this timed out request
652
requestor = new QWidget(0);
653
requestor->setObjectName("internal clipboard requestor");
658
static Atom send_selection(QClipboardData *d, Atom target, Window window, Atom property,
659
int format = 0, QByteArray data = QByteArray());
661
static Atom send_targets_selection(QClipboardData *d, Window window, Atom property)
663
QStringList formats = QInternalMimeData::formatsHelper(d->source());
664
int atoms = formats.size();
665
if (formats.contains("image/ppm")) atoms++;
666
if (formats.contains("image/pbm")) atoms++;
667
if (formats.contains("text/plain")) atoms+=4;
669
VDEBUG("QClipboard: send_targets_selection(): data provides %d types, mapped to %d provided types", formats.size(), atoms);
671
// for 64 bit cleanness... XChangeProperty expects long* for data with format == 32
673
data.resize((atoms+3) * sizeof(long)); // plus TARGETS, MULTIPLE and TIMESTAMP
674
long *atarget = (long *) data.data();
677
for (n = 0; n < formats.size(); ++n) {
678
VDEBUG(" original format %s", formats.at(n).toLatin1().data());
679
atarget[n] = X11->xdndStringToAtom(formats.at(n).toLatin1().data());
682
if (formats.contains("image/ppm"))
683
atarget[n++] = XA_PIXMAP;
684
if (formats.contains("image/pbm"))
685
atarget[n++] = XA_BITMAP;
686
if (formats.contains("text/plain")) {
687
atarget[n++] = ATOM(UTF8_STRING);
688
atarget[n++] = ATOM(TEXT);
689
atarget[n++] = ATOM(COMPOUND_TEXT);
690
atarget[n++] = XA_STRING;
693
atarget[n++] = ATOM(TARGETS);
694
atarget[n++] = ATOM(MULTIPLE);
695
atarget[n++] = ATOM(TIMESTAMP);
697
#if defined(QCLIPBOARD_DEBUG_VERBOSE)
698
for (int index = 0; index < n; index++) {
699
VDEBUG(" atom %d: 0x%lx (%s)", index, atarget[index],
700
X11->xdndAtomToString(atarget[index]).data());
704
XChangeProperty(X11->display, window, property, XA_ATOM, 32,
705
PropModeReplace, (uchar *) data.data(), n);
709
static Atom send_string_selection(QClipboardData *d, Atom target, Window window, Atom property)
712
DEBUG("QClipboard: send_string_selection():\n"
713
" property type %lx\n"
714
" property name '%s'",
715
target, X11->xdndAtomToString(target).data());
717
if (target == ATOM(TEXT) || target == ATOM(COMPOUND_TEXT)) {
718
// the ICCCM states that TEXT and COMPOUND_TEXT are in the
719
// encoding of choice, so we choose the encoding of the locale
720
QByteArray data = d->source()->text().toLocal8Bit();
721
char *list[] = { data.data(), NULL };
723
XICCEncodingStyle style =
724
(target == ATOM(COMPOUND_TEXT)) ? XCompoundTextStyle : XStdICCTextStyle;
725
XTextProperty textprop;
727
&& XmbTextListToTextProperty(X11->display,
728
list, 1, style, &textprop) == Success) {
729
DEBUG(" textprop type %lx\n"
730
" textprop name '%s'\n"
733
textprop.encoding, X11->xdndAtomToString(textprop.encoding).data(),
734
textprop.format, textprop.nitems);
736
int sz = sizeof_format(textprop.format);
737
data = QByteArray((const char *) textprop.value, textprop.nitems * sz);
738
XFree(textprop.value);
740
return send_selection(d, textprop.encoding, window, property, textprop.format, data);
746
Atom xtarget = XNone;
748
if (target == XA_STRING) {
749
// the ICCCM states that STRING is latin1 plus newline and tab
751
data = d->source()->text().toLatin1();
753
} else if (target == ATOM(UTF8_STRING)) {
754
// proposed UTF8_STRING conversion type
755
data = d->source()->text().toUtf8();
756
xtarget = ATOM(UTF8_STRING);
759
if (xtarget == XNone) // should not happen
762
DEBUG(" format 8\n %d bytes", data.size());
764
return send_selection(d, xtarget, window, property, 8, data);
767
static Atom send_pixmap_selection(QClipboardData *d, Atom target, Window window, Atom property)
771
if (target == XA_PIXMAP) {
772
pm = qvariant_cast<QPixmap>(d->source()->imageData());
773
} else if (target == XA_BITMAP) {
774
pm = qvariant_cast<QPixmap>(d->source()->imageData());
775
QImage img = pm.toImage();
776
if (img.depth() != 1) {
777
img = img.convertToFormat(QImage::Format_MonoLSB);
778
pm = QPixmap::fromImage(img);
782
if (pm.isNull()) // should never happen
785
Pixmap handle = pm.handle();
786
XChangeProperty(X11->display, window, property,
787
target, 32, PropModeReplace, (uchar *) &handle, 1);
788
d->addTransferredPixmap(pm);
793
static Atom send_selection(QClipboardData *d, Atom target, Window window, Atom property,
794
int format, QByteArray data)
796
if (format == 0) format = 8;
798
if (data.isEmpty()) {
799
QByteArray fmt = X11->xdndAtomToString(target);
800
DEBUG("QClipboard: send_selection(): converting to type '%s'", fmt.data());
801
if (fmt.isEmpty() || !QInternalMimeData::hasFormatHelper(fmt, d->source())) // Not a MIME type we have
804
data = QInternalMimeData::renderDataHelper(fmt, d->source());
807
DEBUG("QClipboard: send_selection():\n"
808
" property type %lx\n"
809
" property name '%s'\n"
812
target, X11->xdndAtomToString(target).data(), format, data.size());
814
// don't allow INCR transfers when using MULTIPLE or to
815
// Motif clients (since Motif doesn't support INCR)
816
static Atom motif_clip_temporary = ATOM(CLIP_TEMPORARY);
817
bool allow_incr = property != motif_clip_temporary;
819
// X_ChangeProperty protocol request is 24 bytes
820
const int increment = (XMaxRequestSize(X11->display) * 4) - 24;
821
if (data.size() > increment && allow_incr) {
822
long bytes = data.size();
823
XChangeProperty(X11->display, window, property,
824
ATOM(INCR), 32, PropModeReplace, (uchar *) &bytes, 1);
826
(void)new QClipboardINCRTransaction(window, property, target, format, data, increment);
830
// make sure we can perform the XChangeProperty in a single request
831
if (data.size() > increment)
832
return XNone; // ### perhaps use several XChangeProperty calls w/ PropModeAppend?
834
// use a single request to transfer data
835
XChangeProperty(X11->display, window, property, target,
836
format, PropModeReplace, (uchar *) data.data(),
837
data.size() / sizeof_format(format));
842
Internal cleanup for Windows.
844
void QClipboard::ownerDestroyed()
849
Internal optimization for Windows.
851
void QClipboard::connectNotify(const char *)
857
bool QClipboard::event(QEvent *e)
859
if (e->type() == QEvent::Timer) {
860
QTimerEvent *te = (QTimerEvent *) e;
862
if (waiting_for_data) // should never happen
865
if (te->timerId() == timer_id) {
869
timer_event_clear = true;
870
if (selection_watcher) // clear selection
871
selectionData()->clear();
872
if (clipboard_watcher) // clear clipboard
873
clipboardData()->clear();
874
timer_event_clear = false;
877
} else if (te->timerId() == pending_timer_id) {
879
killTimer(pending_timer_id);
880
pending_timer_id = 0;
882
if (pending_clipboard_changed) {
883
pending_clipboard_changed = false;
884
clipboardData()->clear();
887
if (pending_selection_changed) {
888
pending_selection_changed = false;
889
selectionData()->clear();
890
emit selectionChanged();
894
} else if (te->timerId() == incr_timer_id) {
895
killTimer(incr_timer_id);
898
qt_xclb_incr_timeout();
902
return QObject::event(e);
904
} else if (e->type() != QEvent::Clipboard) {
905
return QObject::event(e);
908
XEvent *xevent = (XEvent *)(((QClipboardEvent *)e)->data());
909
Display *dpy = X11->display;
914
switch (xevent->type) {
917
// new selection owner
918
if (xevent->xselectionclear.selection == XA_PRIMARY) {
919
QClipboardData *d = selectionData();
921
// ignore the event if it was generated before we gained selection ownership
922
if (d->timestamp != CurrentTime && xevent->xselectionclear.time < d->timestamp)
925
DEBUG("QClipboard: new selection owner 0x%lx at time %lx (ours %lx)",
926
XGetSelectionOwner(dpy, XA_PRIMARY),
927
xevent->xselectionclear.time, d->timestamp);
929
if (! waiting_for_data) {
931
emit selectionChanged();
933
pending_selection_changed = true;
934
if (! pending_timer_id)
935
pending_timer_id = QApplication::clipboard()->startTimer(0);
937
} else if (xevent->xselectionclear.selection == ATOM(CLIPBOARD)) {
938
QClipboardData *d = clipboardData();
940
// ignore the event if it was generated before we gained selection ownership
941
if (d->timestamp != CurrentTime && xevent->xselectionclear.time < d->timestamp)
944
DEBUG("QClipboard: new clipboard owner 0x%lx at time %lx (%lx)",
945
XGetSelectionOwner(dpy, ATOM(CLIPBOARD)),
946
xevent->xselectionclear.time, d->timestamp);
948
if (! waiting_for_data) {
952
pending_clipboard_changed = true;
953
if (! pending_timer_id)
954
pending_timer_id = QApplication::clipboard()->startTimer(0);
957
qWarning("QClipboard: Unknown SelectionClear event received.");
962
case SelectionNotify:
964
Something has delivered data to us, but this was not caught
965
by QClipboardWatcher::getDataInFormat()
967
Just skip the event to prevent Bad Things (tm) from
968
happening later on...
972
case SelectionRequest:
974
// someone wants our data
975
XSelectionRequestEvent *req = &xevent->xselectionrequest;
977
if (req->requestor == requestor->winId()) break;
980
event.xselection.type = SelectionNotify;
981
event.xselection.display = req->display;
982
event.xselection.requestor = req->requestor;
983
event.xselection.selection = req->selection;
984
event.xselection.target = req->target;
985
event.xselection.property = XNone;
986
event.xselection.time = req->time;
988
DEBUG("QClipboard: SelectionRequest from %lx\n"
989
" selection 0x%lx (%s) target 0x%lx (%s)",
992
X11->xdndAtomToString(req->selection).data(),
994
X11->xdndAtomToString(req->target).data());
997
if (req->selection == XA_PRIMARY) {
999
} else if (req->selection == ATOM(CLIPBOARD)) {
1000
d = clipboardData();
1002
qWarning("QClipboard: unknown selection '%lx'", req->selection);
1003
XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
1007
if (! d->source()) {
1008
qWarning("QClipboard: cannot transfer data, no data available");
1009
XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
1013
DEBUG("QClipboard: SelectionRequest at time %lx (ours %lx)",
1014
req->time, d->timestamp);
1016
if (d->timestamp == CurrentTime // we don't own the selection anymore
1017
|| (req->time != CurrentTime && req->time < d->timestamp)) {
1018
DEBUG("QClipboard: SelectionRequest too old");
1019
XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
1023
Atom xa_targets = ATOM(TARGETS);
1024
Atom xa_multiple = ATOM(MULTIPLE);
1025
Atom xa_timestamp = ATOM(TIMESTAMP);
1027
struct AtomPair { Atom target; Atom property; } *multi = 0;
1028
Atom multi_type = XNone;
1029
int multi_format = 0;
1032
bool multi_writeback = false;
1034
if (req->target == xa_multiple) {
1035
QByteArray multi_data;
1036
if (req->property == XNone
1037
|| !X11->clipboardReadProperty(req->requestor, req->property, false, &multi_data,
1038
0, &multi_type, &multi_format, 0)
1039
|| multi_format != 32) {
1040
// MULTIPLE property not formatted correctly
1041
XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
1044
nmulti = multi_data.size()/sizeof(*multi);
1045
multi = new AtomPair[nmulti];
1046
memcpy(multi,multi_data.data(),multi_data.size());
1050
for (; imulti < nmulti; ++imulti) {
1055
target = multi[imulti].target;
1056
property = multi[imulti].property;
1058
target = req->target;
1059
property = req->property;
1060
if (property == XNone) // obsolete client
1065
if (target == XNone || property == XNone) {
1067
} else if (target == xa_timestamp) {
1068
if (d->timestamp != CurrentTime) {
1069
XChangeProperty(dpy, req->requestor, property, xa_timestamp, 32,
1070
PropModeReplace, (uchar *) &d->timestamp, 1);
1073
qWarning("QClipboard: invalid data timestamp");
1075
} else if (target == xa_targets) {
1076
ret = send_targets_selection(d, req->requestor, property);
1077
} else if (target == XA_STRING
1078
|| target == ATOM(TEXT)
1079
|| target == ATOM(COMPOUND_TEXT)
1080
|| target == ATOM(UTF8_STRING)) {
1081
ret = send_string_selection(d, target, req->requestor, property);
1082
} else if (target == XA_PIXMAP
1083
|| target == XA_BITMAP) {
1084
ret = send_pixmap_selection(d, target, req->requestor, property);
1086
ret = send_selection(d, target, req->requestor, property);
1091
multi[imulti].property = XNone;
1092
multi_writeback = true;
1095
event.xselection.property = ret;
1101
if (multi_writeback) {
1102
// according to ICCCM 2.6.2 says to put None back
1103
// into the original property on the requestor window
1104
XChangeProperty(dpy, req->requestor, req->property, multi_type, 32,
1105
PropModeReplace, (uchar *) multi, nmulti * 2);
1109
event.xselection.property = req->property;
1112
// send selection notify to requestor
1113
XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
1115
DEBUG("QClipboard: SelectionNotify to 0x%lx\n"
1116
" property 0x%lx (%s)",
1117
req->requestor, event.xselection.property,
1118
X11->xdndAtomToString(event.xselection.property).data());
1131
QClipboardWatcher::QClipboardWatcher(QClipboard::Mode mode)
1132
: QInternalMimeData()
1135
case QClipboard::Selection:
1139
case QClipboard::Clipboard:
1140
atom = ATOM(CLIPBOARD);
1144
qWarning("QClipboardWatcher: internal error, unknown clipboard mode");
1151
QClipboardWatcher::~QClipboardWatcher()
1153
if(selection_watcher == this)
1154
selection_watcher = 0;
1155
if(clipboard_watcher == this)
1156
clipboard_watcher = 0;
1159
bool QClipboardWatcher::empty() const
1161
Display *dpy = X11->display;
1162
Window win = XGetSelectionOwner(dpy, atom);
1164
if(win == requestor->winId()) {
1165
qWarning("QClipboardWatcher::empty: internal error, app owns the selection");
1169
return win == XNone;
1172
QStringList QClipboardWatcher::formats_sys() const
1175
return QStringList();
1177
if (!formatList.count()) {
1178
// get the list of targets from the current clipboard owner - we do this
1179
// once so that multiple calls to this function don't require multiple
1180
// server round trips...
1182
format_atoms = getDataInFormat(ATOM(TARGETS));
1184
if (format_atoms.size() > 0) {
1185
Atom *targets = (Atom *) format_atoms.data();
1186
int size = format_atoms.size() / sizeof(Atom);
1188
for (int i = 0; i < size; ++i) {
1189
if (targets[i] == 0)
1192
VDEBUG(" format: %s", X11->xdndAtomToString(targets[i]).data());
1193
if (targets[i] == XA_PIXMAP)
1194
formatList.append("image/ppm");
1195
else if (targets[i] == XA_STRING
1196
|| targets[i] == ATOM(UTF8_STRING)
1197
|| targets[i] == ATOM(TEXT)
1198
|| targets[i] == ATOM(COMPOUND_TEXT))
1199
formatList.append("text/plain");
1201
formatList.append(X11->xdndAtomToString(targets[i]));
1202
VDEBUG(" data:\n%s\n", getDataInFormat(targets[i]).data());
1205
DEBUG("QClipboardWatcher::format: %d formats available", formatList.count());
1212
bool QClipboardWatcher::hasFormat_sys(const QString &format) const
1214
QStringList list = formats();
1215
return list.contains(format);
1218
QVariant QClipboardWatcher::retrieveData_sys(const QString &fmt, QVariant::Type type) const
1220
if (fmt.isEmpty() || empty())
1221
return QByteArray();
1223
(void)formats(); // trigger update of format list
1224
DEBUG("QClipboardWatcher::data: fetching format '%s'", fmt.toLatin1().data());
1228
if (fmt == QLatin1String("text/plain")) {
1229
Atom *targets = (Atom *) format_atoms.data();
1230
int size = format_atoms.size() / sizeof(Atom);
1232
// find best available text format
1233
for (int i = 0; i < size; ++i) {
1234
VDEBUG(" format: %s", X11->xdndAtomToString(targets[i]).data());
1235
if (targets[i] == XA_STRING) {
1237
fmtatom = targets[i];
1238
} else if (targets[i] == ATOM(TEXT)) {
1239
if (fmtatom == 0 || fmtatom == XA_STRING)
1240
fmtatom = targets[i];
1241
} else if (targets[i] == ATOM(COMPOUND_TEXT)) {
1242
if (fmtatom == 0 || fmtatom == XA_STRING || fmtatom == ATOM(TEXT))
1243
fmtatom = targets[i];
1244
// } else if (targets[i] == ATOM("text/plain;charset=ISO-10646-UCS-2"))) {
1245
} else if (targets[i] == ATOM(UTF8_STRING)) {
1246
if (fmtatom == 0 || fmtatom == XA_STRING || fmtatom == ATOM(TEXT) || fmtatom == ATOM(COMPOUND_TEXT))
1247
fmtatom = targets[i];
1253
QByteArray data = getDataInFormat(fmtatom);
1255
if (fmtatom == XA_STRING)
1256
result = QString::fromLatin1(data);
1257
else if (fmtatom == ATOM(TEXT) || fmtatom == ATOM(COMPOUND_TEXT)) {
1258
// #### might be wrong for COMPUND_TEXT
1259
result = QString::fromLocal8Bit(data);
1260
} else if (fmtatom == ATOM(UTF8_STRING)) {
1261
result = QString::fromUtf8(data);
1263
DEBUG("got plain text '%s'", result.toUtf8().data());
1264
if (type == QVariant::String)
1266
return result.toUtf8();
1268
if (fmt == QLatin1String("image/ppm")) {
1269
fmtatom = XA_PIXMAP;
1270
QByteArray pmd = getDataInFormat(fmtatom);
1271
if (pmd.size() == sizeof(Pixmap)) {
1272
Pixmap xpm = *((Pixmap*)pmd.data());
1273
Display *dpy = X11->display;
1278
return QByteArray();
1279
XGetGeometry(dpy,xpm, &r,&x,&y,&w,&h,&bw,&d);
1280
QImageWriter imageWriter;
1281
GC gc = XCreateGC(dpy, xpm, 0, 0);
1282
QImage imageToWrite;
1285
XCopyArea(dpy,xpm,qbm.handle(),gc,0,0,w,h,0,0);
1286
if (type == QVariant::Bitmap)
1288
if (type == QVariant::Pixmap)
1289
return QPixmap(qbm);
1290
imageWriter.setFormat("PBMRAW");
1291
imageToWrite = qbm.toImage();
1294
XCopyArea(dpy,xpm,qpm.handle(),gc,0,0,w,h,0,0);
1295
if (type == QVariant::Pixmap)
1297
imageWriter.setFormat("PPMRAW");
1298
imageToWrite = qpm.toImage();
1302
buf.open(QIODevice::WriteOnly);
1303
imageWriter.setDevice(&buf);
1304
imageWriter.write(imageToWrite);
1305
return buf.buffer();
1307
fmtatom = X11->xdndStringToAtom(fmt.toLatin1().data());
1310
fmtatom = X11->xdndStringToAtom(fmt.toLatin1().data());
1312
return getDataInFormat(fmtatom);
1315
QByteArray QClipboardWatcher::getDataInFormat(Atom fmtatom) const
1319
Display *dpy = X11->display;
1320
Window win = requestor->winId();
1322
DEBUG("QClipboardWatcher::getDataInFormat: selection '%s' format '%s'",
1323
X11->xdndAtomToString(atom).data(), X11->xdndAtomToString(fmtatom).data());
1325
XSelectInput(dpy, win, NoEventMask); // don't listen for any events
1327
XDeleteProperty(dpy, win, ATOM(_QT_SELECTION));
1328
XConvertSelection(dpy, atom, fmtatom, ATOM(_QT_SELECTION), win, X11->time);
1331
VDEBUG("QClipboardWatcher::getDataInFormat: waiting for SelectionNotify event");
1334
if (!X11->clipboardWaitForEvent(win,SelectionNotify,&xevent,clipboard_timeout) ||
1335
xevent.xselection.property == XNone) {
1336
DEBUG("QClipboardWatcher::getDataInFormat: format not available");
1340
VDEBUG("QClipboardWatcher::getDataInFormat: fetching data...");
1343
XSelectInput(dpy, win, PropertyChangeMask);
1345
if (X11->clipboardReadProperty(win, ATOM(_QT_SELECTION), true, &buf, 0, &type, 0, false)) {
1346
if (type == ATOM(INCR)) {
1347
int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
1348
buf = X11->clipboardReadIncrementalProperty(win, ATOM(_QT_SELECTION), nbytes, false);
1352
XSelectInput(dpy, win, NoEventMask);
1354
DEBUG("QClipboardWatcher::getDataInFormat: %d bytes received", buf.size());
1360
const QMimeData* QClipboard::mimeData(Mode mode) const
1362
QClipboardData *d = 0;
1365
d = selectionData();
1368
d = clipboardData();
1372
if (! d->source() && ! timer_event_clear) {
1373
if (mode == Selection) {
1374
if (! selection_watcher)
1375
selection_watcher = new QClipboardWatcher(mode);
1376
d->setSource(selection_watcher);
1378
if (! clipboard_watcher)
1379
clipboard_watcher = new QClipboardWatcher(mode);
1380
d->setSource(clipboard_watcher);
1384
// start a zero timer - we will clear cached data when the timer
1385
// times out, which will be the next time we hit the event loop...
1386
// that way, the data is cached long enough for calls within a single
1387
// loop/function, but the data doesn't linger around in case the selection
1389
QClipboard *that = ((QClipboard *) this);
1390
timer_id = that->startTimer(0);
1398
void QClipboard::setMimeData(QMimeData* src, Mode mode)
1400
Atom atom, sentinel_atom;
1405
sentinel_atom = ATOM(_QT_SELECTION_SENTINEL);
1406
d = selectionData();
1410
atom = ATOM(CLIPBOARD);
1411
sentinel_atom = ATOM(_QT_CLIPBOARD_SENTINEL);
1412
d = clipboardData();
1416
qWarning("QClipboard::data: invalid mode '%d'", mode);
1420
Display *dpy = X11->display;
1423
if (! src) { // no data, clear clipboard contents
1429
newOwner = owner->winId();
1432
d->timestamp = X11->time;
1435
Window prevOwner = XGetSelectionOwner(dpy, atom);
1436
// use X11->time, since d->timestamp == CurrentTime when clearing
1437
XSetSelectionOwner(dpy, atom, newOwner, X11->time);
1439
if (mode == Selection)
1440
emit selectionChanged();
1444
if (XGetSelectionOwner(dpy, atom) != newOwner) {
1445
qWarning("QClipboard::setData: Cannot set X11 selection owner for %s",
1446
X11->xdndAtomToString(atom).data());
1451
// Signal to other Qt processes that the selection has changed
1453
owners[0] = newOwner;
1454
owners[1] = prevOwner;
1455
XChangeProperty(dpy, QApplication::desktop()->screen(0)->winId(),
1456
sentinel_atom, XA_WINDOW, 32, PropModeReplace,
1457
(unsigned char*)&owners, 2);
1462
Called by the main event loop in qapplication_x11.cpp when the
1463
_QT_SELECTION_SENTINEL property has been changed (i.e. when some Qt
1464
process has performed QClipboard::setData(). If it returns true, the
1465
QClipBoard dataChanged() signal should be emitted.
1468
bool qt_check_selection_sentinel()
1473
Since the X selection mechanism cannot give any signal when
1474
the selection has changed, we emulate it (for Qt processes) here.
1475
The notification should be ignored in case of either
1476
a) This is the process that did setData (because setData()
1477
then has already emitted dataChanged())
1478
b) This is the process that owned the selection when dataChanged()
1479
was called (because we have then received a SelectionClear event,
1480
and have already emitted dataChanged() as a result of that)
1489
if (XGetWindowProperty(X11->display,
1490
QApplication::desktop()->screen(0)->winId(),
1491
ATOM(_QT_SELECTION_SENTINEL), 0, 2, False, XA_WINDOW,
1492
&actualType, &actualFormat, &nitems,
1493
&bytesLeft, (unsigned char**)&owners) == Success) {
1494
if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) {
1495
Window win = owner->winId();
1496
if (owners[0] == win || owners[1] == win)
1505
if (waiting_for_data) {
1506
pending_selection_changed = true;
1507
if (! pending_timer_id)
1508
pending_timer_id = QApplication::clipboard()->startTimer(0);
1511
selectionData()->clear();
1519
bool qt_check_clipboard_sentinel()
1526
unsigned long nitems, bytesLeft;
1528
if (XGetWindowProperty(X11->display,
1529
QApplication::desktop()->screen(0)->winId(),
1530
ATOM(_QT_CLIPBOARD_SENTINEL), 0, 2, False, XA_WINDOW,
1531
&actualType, &actualFormat, &nitems, &bytesLeft,
1532
(unsigned char **) &owners) == Success) {
1533
if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) {
1534
Window win = owner->winId();
1535
if (owners[0] == win || owners[1] == win)
1544
if (waiting_for_data) {
1545
pending_clipboard_changed = true;
1546
if (! pending_timer_id)
1547
pending_timer_id = QApplication::clipboard()->startTimer(0);
1550
clipboardData()->clear();
1557
#endif // QT_NO_CLIPBOARD