summaryrefslogtreecommitdiffstats
path: root/src/gui/kernel/qclipboard_x11.cpp
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@nokia.com>2009-03-23 09:34:13 (GMT)
committerSimon Hausmann <simon.hausmann@nokia.com>2009-03-23 09:34:13 (GMT)
commit67ad0519fd165acee4a4d2a94fa502e9e4847bd0 (patch)
tree1dbf50b3dff8d5ca7e9344733968c72704eb15ff /src/gui/kernel/qclipboard_x11.cpp
downloadQt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.zip
Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.tar.gz
Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.tar.bz2
Long live Qt!
Diffstat (limited to 'src/gui/kernel/qclipboard_x11.cpp')
-rw-r--r--src/gui/kernel/qclipboard_x11.cpp1498
1 files changed, 1498 insertions, 0 deletions
diff --git a/src/gui/kernel/qclipboard_x11.cpp b/src/gui/kernel/qclipboard_x11.cpp
new file mode 100644
index 0000000..089cc43
--- /dev/null
+++ b/src/gui/kernel/qclipboard_x11.cpp
@@ -0,0 +1,1498 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// #define QCLIPBOARD_DEBUG
+// #define QCLIPBOARD_DEBUG_VERBOSE
+
+#ifdef QCLIPBOARD_DEBUG
+# define DEBUG qDebug
+#else
+# define DEBUG if (false) qDebug
+#endif
+
+#ifdef QCLIPBOARD_DEBUG_VERBOSE
+# define VDEBUG qDebug
+#else
+# define VDEBUG if (false) qDebug
+#endif
+
+#include "qplatformdefs.h"
+
+#include "qclipboard.h"
+#include "qclipboard_p.h"
+
+#ifndef QT_NO_CLIPBOARD
+
+#include "qabstracteventdispatcher.h"
+#include "qapplication.h"
+#include "qdesktopwidget.h"
+#include "qbitmap.h"
+#include "qdatetime.h"
+#include "qiodevice.h"
+#include "qbuffer.h"
+#include "qtextcodec.h"
+#include "qlist.h"
+#include "qmap.h"
+#include "qapplication_p.h"
+#include "qevent.h"
+#include "qt_x11_p.h"
+#include "qx11info_x11.h"
+#include "qimagewriter.h"
+#include "qvariant.h"
+#include "qdnd_p.h"
+
+#ifndef QT_NO_XFIXES
+#include <X11/extensions/Xfixes.h>
+#endif // QT_NO_XFIXES
+
+QT_BEGIN_NAMESPACE
+
+/*****************************************************************************
+ Internal QClipboard functions for X11.
+ *****************************************************************************/
+
+static int clipboard_timeout = 5000; // 5s timeout on clipboard operations
+
+static QWidget * owner = 0;
+static QWidget *requestor = 0;
+static bool timer_event_clear = false;
+static int timer_id = 0;
+
+static int pending_timer_id = 0;
+static bool pending_clipboard_changed = false;
+static bool pending_selection_changed = false;
+
+
+// event capture mechanism for qt_xclb_wait_for_event
+static bool waiting_for_data = false;
+static bool has_captured_event = false;
+static Window capture_event_win = XNone;
+static int capture_event_type = -1;
+static XEvent captured_event;
+
+class QClipboardWatcher; // forward decl
+static QClipboardWatcher *selection_watcher = 0;
+static QClipboardWatcher *clipboard_watcher = 0;
+
+static void cleanup()
+{
+ delete owner;
+ delete requestor;
+ owner = 0;
+ requestor = 0;
+}
+
+static
+void setupOwner()
+{
+ if (owner)
+ return;
+ owner = new QWidget(0);
+ owner->setObjectName(QLatin1String("internal clipboard owner"));
+ owner->createWinId();
+ requestor = new QWidget(0);
+ requestor->createWinId();
+ requestor->setObjectName(QLatin1String("internal clipboard requestor"));
+ qAddPostRoutine(cleanup);
+}
+
+
+class QClipboardWatcher : public QInternalMimeData {
+public:
+ QClipboardWatcher(QClipboard::Mode mode);
+ ~QClipboardWatcher();
+ bool empty() const;
+ virtual bool hasFormat_sys(const QString &mimetype) const;
+ virtual QStringList formats_sys() const;
+
+ QVariant retrieveData_sys(const QString &mimetype, QVariant::Type type) const;
+ QByteArray getDataInFormat(Atom fmtatom) const;
+
+ Atom atom;
+ mutable QStringList formatList;
+ mutable QByteArray format_atoms;
+};
+
+class QClipboardData
+{
+private:
+ QMimeData *&mimeDataRef() const
+ {
+ if(mode == QClipboard::Selection)
+ return selectionData;
+ return clipboardData;
+ }
+
+public:
+ QClipboardData(QClipboard::Mode mode);
+ ~QClipboardData();
+
+ void setSource(QMimeData* s)
+ {
+ if ((mode == QClipboard::Selection && selectionData == s)
+ || clipboardData == s) {
+ return;
+ }
+
+ if (selectionData != clipboardData) {
+ delete mimeDataRef();
+ }
+
+ mimeDataRef() = s;
+ }
+
+ QMimeData *source() const
+ {
+ return mimeDataRef();
+ }
+
+ void clear()
+ {
+ timestamp = CurrentTime;
+ if (selectionData == clipboardData) {
+ mimeDataRef() = 0;
+ } else {
+ QMimeData *&src = mimeDataRef();
+ delete src;
+ src = 0;
+ }
+ }
+
+ static QMimeData *selectionData;
+ static QMimeData *clipboardData;
+ Time timestamp;
+ QClipboard::Mode mode;
+};
+
+QMimeData *QClipboardData::selectionData = 0;
+QMimeData *QClipboardData::clipboardData = 0;
+
+QClipboardData::QClipboardData(QClipboard::Mode clipboardMode)
+{
+ timestamp = CurrentTime;
+ mode = clipboardMode;
+}
+
+QClipboardData::~QClipboardData()
+{ clear(); }
+
+
+static QClipboardData *internalCbData = 0;
+static QClipboardData *internalSelData = 0;
+
+static void cleanupClipboardData()
+{
+ delete internalCbData;
+ internalCbData = 0;
+}
+
+static QClipboardData *clipboardData()
+{
+ if (internalCbData == 0) {
+ internalCbData = new QClipboardData(QClipboard::Clipboard);
+ qAddPostRoutine(cleanupClipboardData);
+ }
+ return internalCbData;
+}
+
+static void cleanupSelectionData()
+{
+ delete internalSelData;
+ internalSelData = 0;
+}
+
+static QClipboardData *selectionData()
+{
+ if (internalSelData == 0) {
+ internalSelData = new QClipboardData(QClipboard::Selection);
+ qAddPostRoutine(cleanupSelectionData);
+ }
+ return internalSelData;
+}
+
+class QClipboardINCRTransaction
+{
+public:
+ QClipboardINCRTransaction(Window w, Atom p, Atom t, int f, QByteArray d, unsigned int i);
+ ~QClipboardINCRTransaction(void);
+
+ int x11Event(XEvent *event);
+
+ Window window;
+ Atom property, target;
+ int format;
+ QByteArray data;
+ unsigned int increment;
+ unsigned int offset;
+};
+
+typedef QMap<Window,QClipboardINCRTransaction*> TransactionMap;
+static TransactionMap *transactions = 0;
+static QApplication::EventFilter prev_event_filter = 0;
+static int incr_timer_id = 0;
+
+static bool qt_x11_incr_event_filter(void *message, long *result)
+{
+ XEvent *event = reinterpret_cast<XEvent *>(message);
+ TransactionMap::Iterator it = transactions->find(event->xany.window);
+ if (it != transactions->end()) {
+ if ((*it)->x11Event(event) != 0)
+ return true;
+ }
+ if (prev_event_filter)
+ return prev_event_filter(event, result);
+ return false;
+}
+
+/*
+ called when no INCR activity has happened for 'clipboard_timeout'
+ milliseconds... we assume that all unfinished transactions have
+ timed out and remove everything from the transaction map
+*/
+static void qt_xclb_incr_timeout(void)
+{
+ qWarning("QClipboard: Timed out while sending data");
+
+ while (transactions)
+ delete *transactions->begin();
+}
+
+QClipboardINCRTransaction::QClipboardINCRTransaction(Window w, Atom p, Atom t, int f,
+ QByteArray d, unsigned int i)
+ : window(w), property(p), target(t), format(f), data(d), increment(i), offset(0u)
+{
+ DEBUG("QClipboard: sending %d bytes (INCR transaction %p)", d.size(), this);
+
+ XSelectInput(X11->display, window, PropertyChangeMask);
+
+ if (! transactions) {
+ VDEBUG("QClipboard: created INCR transaction map");
+ transactions = new TransactionMap;
+ prev_event_filter = qApp->setEventFilter(qt_x11_incr_event_filter);
+ incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout);
+ }
+ transactions->insert(window, this);
+}
+
+QClipboardINCRTransaction::~QClipboardINCRTransaction(void)
+{
+ VDEBUG("QClipboard: destroyed INCR transacton %p", this);
+
+ XSelectInput(X11->display, window, NoEventMask);
+
+ transactions->remove(window);
+ if (transactions->isEmpty()) {
+ VDEBUG("QClipboard: no more INCR transactions");
+ delete transactions;
+ transactions = 0;
+
+ (void)qApp->setEventFilter(prev_event_filter);
+
+ if (incr_timer_id != 0) {
+ QApplication::clipboard()->killTimer(incr_timer_id);
+ incr_timer_id = 0;
+ }
+ }
+}
+
+int QClipboardINCRTransaction::x11Event(XEvent *event)
+{
+ if (event->type != PropertyNotify
+ || (event->xproperty.state != PropertyDelete
+ || event->xproperty.atom != property))
+ return 0;
+
+ // restart the INCR timer
+ if (incr_timer_id) QApplication::clipboard()->killTimer(incr_timer_id);
+ incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout);
+
+ unsigned int bytes_left = data.size() - offset;
+ if (bytes_left > 0) {
+ unsigned int xfer = qMin(increment, bytes_left);
+ VDEBUG("QClipboard: sending %d bytes, %d remaining (INCR transaction %p)",
+ xfer, bytes_left - xfer, this);
+
+ XChangeProperty(X11->display, window, property, target, format,
+ PropModeReplace, (uchar *) data.data() + offset, xfer);
+ offset += xfer;
+ } else {
+ // INCR transaction finished...
+ XChangeProperty(X11->display, window, property, target, format,
+ PropModeReplace, (uchar *) data.data(), 0);
+ delete this;
+ }
+
+ return 1;
+}
+
+
+/*****************************************************************************
+ QClipboard member functions for X11.
+ *****************************************************************************/
+
+struct qt_init_timestamp_data
+{
+ Time timestamp;
+};
+
+#if defined(Q_C_CALLBACKS)
+extern "C" {
+#endif
+
+static Bool qt_init_timestamp_scanner(Display*, XEvent *event, XPointer arg)
+{
+ qt_init_timestamp_data *data =
+ reinterpret_cast<qt_init_timestamp_data*>(arg);
+ switch(event->type)
+ {
+ case ButtonPress:
+ case ButtonRelease:
+ data->timestamp = event->xbutton.time;
+ break;
+ case MotionNotify:
+ data->timestamp = event->xmotion.time;
+ break;
+ case XKeyPress:
+ case XKeyRelease:
+ data->timestamp = event->xkey.time;
+ break;
+ case PropertyNotify:
+ data->timestamp = event->xproperty.time;
+ break;
+ case EnterNotify:
+ case LeaveNotify:
+ data->timestamp = event->xcrossing.time;
+ break;
+ case SelectionClear:
+ data->timestamp = event->xselectionclear.time;
+ break;
+ default:
+ break;
+ }
+#ifndef QT_NO_XFIXES
+ if (X11->use_xfixes && event->type == (X11->xfixes_eventbase + XFixesSelectionNotify)) {
+ XFixesSelectionNotifyEvent *req =
+ reinterpret_cast<XFixesSelectionNotifyEvent *>(event);
+ data->timestamp = req->selection_timestamp;
+ }
+#endif
+ return false;
+}
+
+#if defined(Q_C_CALLBACKS)
+}
+#endif
+
+QClipboard::QClipboard(QObject *parent)
+ : QObject(*new QClipboardPrivate, parent)
+{
+ // create desktop widget since we need it to get PropertyNotify or
+ // XFixesSelectionNotify events when someone changes the
+ // clipboard.
+ (void)QApplication::desktop();
+ if (X11->time == CurrentTime) {
+ // send a dummy event to myself to get the timestamp from X11.
+ qt_init_timestamp_data data;
+ data.timestamp = CurrentTime;
+ XEvent ev;
+ XCheckIfEvent(X11->display, &ev, &qt_init_timestamp_scanner, (XPointer)&data);
+ if (data.timestamp == CurrentTime) {
+ setupOwner();
+ int dummy = 0;
+ Window ownerId = owner->internalWinId();
+ XChangeProperty(X11->display, ownerId,
+ ATOM(CLIP_TEMPORARY), XA_INTEGER, 32,
+ PropModeReplace, (uchar*)&dummy, 1);
+ XWindowEvent(X11->display, ownerId, PropertyChangeMask, &ev);
+ data.timestamp = ev.xproperty.time;
+ XDeleteProperty(X11->display, ownerId, ATOM(CLIP_TEMPORARY));
+ }
+ X11->time = data.timestamp;
+ }
+}
+
+void QClipboard::clear(Mode mode)
+{
+ setMimeData(0, mode);
+}
+
+
+bool QClipboard::supportsMode(Mode mode) const
+{
+ return (mode == Clipboard || mode == Selection);
+}
+
+bool QClipboard::ownsMode(Mode mode) const
+{
+ if (mode == Clipboard)
+ return clipboardData()->timestamp != CurrentTime;
+ else if(mode == Selection)
+ return selectionData()->timestamp != CurrentTime;
+ else
+ return false;
+}
+
+
+// event filter function... captures interesting events while
+// qt_xclb_wait_for_event is running the event loop
+static bool qt_x11_clipboard_event_filter(void *message, long *)
+{
+ XEvent *event = reinterpret_cast<XEvent *>(message);
+ if (event->xany.type == capture_event_type &&
+ event->xany.window == capture_event_win) {
+ VDEBUG("QClipboard: event_filter(): caught event type %d", event->type);
+ has_captured_event = true;
+ captured_event = *event;
+ return true;
+ }
+ return false;
+}
+
+static Bool checkForClipboardEvents(Display *, XEvent *e, XPointer)
+{
+ return ((e->type == SelectionRequest && (e->xselectionrequest.selection == XA_PRIMARY
+ || e->xselectionrequest.selection == ATOM(CLIPBOARD)))
+ || (e->type == SelectionClear && (e->xselectionclear.selection == XA_PRIMARY
+ || e->xselectionclear.selection == ATOM(CLIPBOARD))));
+}
+
+bool QX11Data::clipboardWaitForEvent(Window win, int type, XEvent *event, int timeout)
+{
+ QTime started = QTime::currentTime();
+ QTime now = started;
+
+ if (QAbstractEventDispatcher::instance()->inherits("QtMotif")
+ || QApplication::clipboard()->property("useEventLoopWhenWaiting").toBool()) {
+ if (waiting_for_data) {
+ Q_ASSERT(!"QClipboard: internal error, qt_xclb_wait_for_event recursed");
+ return false;
+ }
+ waiting_for_data = true;
+
+
+ has_captured_event = false;
+ capture_event_win = win;
+ capture_event_type = type;
+
+ QApplication::EventFilter old_event_filter =
+ qApp->setEventFilter(qt_x11_clipboard_event_filter);
+
+ do {
+ if (XCheckTypedWindowEvent(display, win, type, event)) {
+ waiting_for_data = false;
+ qApp->setEventFilter(old_event_filter);
+ return true;
+ }
+
+ XSync(X11->display, false);
+ usleep(50000);
+
+ now = QTime::currentTime();
+ if (started > now) // crossed midnight
+ started = now;
+
+ QEventLoop::ProcessEventsFlags flags(QEventLoop::ExcludeUserInputEvents
+ | QEventLoop::ExcludeSocketNotifiers
+ | QEventLoop::WaitForMoreEvents
+ | QEventLoop::X11ExcludeTimers);
+ QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance();
+ eventDispatcher->processEvents(flags);
+
+ if (has_captured_event) {
+ waiting_for_data = false;
+ *event = captured_event;
+ qApp->setEventFilter(old_event_filter);
+ return true;
+ }
+ } while (started.msecsTo(now) < timeout);
+
+ waiting_for_data = false;
+ qApp->setEventFilter(old_event_filter);
+ } else {
+ do {
+ if (XCheckTypedWindowEvent(X11->display,win,type,event))
+ return true;
+
+ // process other clipboard events, since someone is probably requesting data from us
+ XEvent e;
+ if (XCheckIfEvent(X11->display, &e, checkForClipboardEvents, 0))
+ qApp->x11ProcessEvent(&e);
+
+ now = QTime::currentTime();
+ if ( started > now ) // crossed midnight
+ started = now;
+
+ XFlush(X11->display);
+
+ // sleep 50 ms, so we don't use up CPU cycles all the time.
+ struct timeval usleep_tv;
+ usleep_tv.tv_sec = 0;
+ usleep_tv.tv_usec = 50000;
+ select(0, 0, 0, 0, &usleep_tv);
+ } while (started.msecsTo(now) < timeout);
+ }
+ return false;
+}
+
+
+static inline int maxSelectionIncr(Display *dpy)
+{ return XMaxRequestSize(dpy) > 65536 ? 65536*4 : XMaxRequestSize(dpy)*4 - 100; }
+
+bool QX11Data::clipboardReadProperty(Window win, Atom property, bool deleteProperty,
+ QByteArray *buffer, int *size, Atom *type, int *format, bool nullterm)
+{
+ int maxsize = maxSelectionIncr(display);
+ ulong bytes_left; // bytes_after
+ ulong length; // nitems
+ uchar *data;
+ Atom dummy_type;
+ int dummy_format;
+ int r;
+
+ if (!type) // allow null args
+ type = &dummy_type;
+ if (!format)
+ format = &dummy_format;
+
+ // Don't read anything, just get the size of the property data
+ r = XGetWindowProperty(display, win, property, 0, 0, False,
+ AnyPropertyType, type, format,
+ &length, &bytes_left, &data);
+ if (r != Success || (type && *type == XNone)) {
+ buffer->resize(0);
+ return false;
+ }
+ XFree((char*)data);
+
+ int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
+
+ VDEBUG("QClipboard: read_property(): initial property length: %d", proplen);
+
+ switch (*format) {
+ case 8:
+ default:
+ format_inc = sizeof(char) / 1;
+ break;
+
+ case 16:
+ format_inc = sizeof(short) / 2;
+ proplen *= sizeof(short) / 2;
+ break;
+
+ case 32:
+ format_inc = sizeof(long) / 4;
+ proplen *= sizeof(long) / 4;
+ break;
+ }
+
+ int newSize = proplen + (nullterm ? 1 : 0);
+ buffer->resize(newSize);
+
+ bool ok = (buffer->size() == newSize);
+ VDEBUG("QClipboard: read_property(): buffer resized to %d", buffer->size());
+
+ if (ok) {
+ // could allocate buffer
+
+ while (bytes_left) {
+ // more to read...
+
+ r = XGetWindowProperty(display, win, property, offset, maxsize/4,
+ False, AnyPropertyType, type, format,
+ &length, &bytes_left, &data);
+ if (r != Success || (type && *type == XNone))
+ break;
+
+ offset += length / (32 / *format);
+ length *= format_inc * (*format) / 8;
+
+ // Here we check if we get a buffer overflow and tries to
+ // recover -- this shouldn't normally happen, but it doesn't
+ // hurt to be defensive
+ if ((int)(buffer_offset + length) > buffer->size()) {
+ length = buffer->size() - buffer_offset;
+
+ // escape loop
+ bytes_left = 0;
+ }
+
+ memcpy(buffer->data() + buffer_offset, data, length);
+ buffer_offset += length;
+
+ XFree((char*)data);
+ }
+
+ if (*format == 8 && *type == ATOM(COMPOUND_TEXT)) {
+ // convert COMPOUND_TEXT to a multibyte string
+ XTextProperty textprop;
+ textprop.encoding = *type;
+ textprop.format = *format;
+ textprop.nitems = length;
+ textprop.value = (unsigned char *) buffer->data();
+
+ char **list_ret = 0;
+ int count;
+ if (XmbTextPropertyToTextList(display, &textprop, &list_ret,
+ &count) == Success && count && list_ret) {
+ offset = strlen(list_ret[0]);
+ buffer->resize(offset + (nullterm ? 1 : 0));
+ memcpy(buffer->data(), list_ret[0], offset);
+ }
+ if (list_ret) XFreeStringList(list_ret);
+ }
+
+ // zero-terminate (for text)
+ if (nullterm)
+ buffer->data()[buffer_offset] = '\0';
+ }
+
+ // correct size, not 0-term.
+ if (size)
+ *size = buffer_offset;
+
+ VDEBUG("QClipboard: read_property(): buffer size %d, buffer offset %d, offset %d",
+ buffer->size(), buffer_offset, offset);
+
+ if (deleteProperty)
+ XDeleteProperty(display, win, property);
+
+ XFlush(display);
+
+ return ok;
+}
+
+QByteArray QX11Data::clipboardReadIncrementalProperty(Window win, Atom property, int nbytes, bool nullterm)
+{
+ XEvent event;
+
+ QByteArray buf;
+ QByteArray tmp_buf;
+ bool alloc_error = false;
+ int length;
+ int offset = 0;
+
+ if (nbytes > 0) {
+ // Reserve buffer + zero-terminator (for text data)
+ // We want to complete the INCR transfer even if we cannot
+ // allocate more memory
+ buf.resize(nbytes+1);
+ alloc_error = buf.size() != nbytes+1;
+ }
+
+ for (;;) {
+ XFlush(display);
+ if (!clipboardWaitForEvent(win,PropertyNotify,&event,clipboard_timeout))
+ break;
+ if (event.xproperty.atom != property ||
+ event.xproperty.state != PropertyNewValue)
+ continue;
+ if (X11->clipboardReadProperty(win, property, true, &tmp_buf, &length, 0, 0, false)) {
+ if (length == 0) { // no more data, we're done
+ if (nullterm) {
+ buf.resize(offset+1);
+ buf[offset] = '\0';
+ } else {
+ buf.resize(offset);
+ }
+ return buf;
+ } else if (!alloc_error) {
+ if (offset+length > (int)buf.size()) {
+ buf.resize(offset+length+65535);
+ if (buf.size() != offset+length+65535) {
+ alloc_error = true;
+ length = buf.size() - offset;
+ }
+ }
+ memcpy(buf.data()+offset, tmp_buf.constData(), length);
+ tmp_buf.resize(0);
+ offset += length;
+ }
+ } else {
+ break;
+ }
+ }
+
+ // timed out ... create a new requestor window, otherwise the requestor
+ // could consider next request to be still part of this timed out request
+ delete requestor;
+ requestor = new QWidget(0);
+ requestor->setObjectName(QLatin1String("internal clipboard requestor"));
+
+ return QByteArray();
+}
+
+static Atom send_targets_selection(QClipboardData *d, Window window, Atom property)
+{
+ QVector<Atom> types;
+ QStringList formats = QInternalMimeData::formatsHelper(d->source());
+ for (int i = 0; i < formats.size(); ++i) {
+ QList<Atom> atoms = X11->xdndMimeAtomsForFormat(formats.at(i));
+ for (int j = 0; j < atoms.size(); ++j) {
+ if (!types.contains(atoms.at(j)))
+ types.append(atoms.at(j));
+ }
+ }
+ types.append(ATOM(TARGETS));
+ types.append(ATOM(MULTIPLE));
+ types.append(ATOM(TIMESTAMP));
+
+ XChangeProperty(X11->display, window, property, XA_ATOM, 32,
+ PropModeReplace, (uchar *) types.data(), types.size());
+ return property;
+}
+
+static Atom send_selection(QClipboardData *d, Atom target, Window window, Atom property)
+{
+ Atom atomFormat = target;
+ int dataFormat = 0;
+ QByteArray data;
+
+ QByteArray fmt = X11->xdndAtomToString(target);
+ if (fmt.isEmpty() || !QInternalMimeData::hasFormatHelper(QString::fromAscii(fmt), d->source())) { // Not a MIME type we have
+ DEBUG("QClipboard: send_selection(): converting to type '%s' is not supported", fmt.data());
+ return XNone;
+ }
+ DEBUG("QClipboard: send_selection(): converting to type '%s'", fmt.data());
+
+ if (X11->xdndMimeDataForAtom(target, d->source(), &data, &atomFormat, &dataFormat)) {
+
+ VDEBUG("QClipboard: send_selection():\n"
+ " property type %lx\n"
+ " property name '%s'\n"
+ " format %d\n"
+ " %d bytes\n",
+ target, X11->xdndMimeAtomToString(atomFormat).toLatin1().data(), dataFormat, data.size());
+
+ // don't allow INCR transfers when using MULTIPLE or to
+ // Motif clients (since Motif doesn't support INCR)
+ static Atom motif_clip_temporary = ATOM(CLIP_TEMPORARY);
+ bool allow_incr = property != motif_clip_temporary;
+
+ // X_ChangeProperty protocol request is 24 bytes
+ const int increment = (XMaxRequestSize(X11->display) * 4) - 24;
+ if (data.size() > increment && allow_incr) {
+ long bytes = data.size();
+ XChangeProperty(X11->display, window, property,
+ ATOM(INCR), 32, PropModeReplace, (uchar *) &bytes, 1);
+
+ (void)new QClipboardINCRTransaction(window, property, atomFormat, dataFormat, data, increment);
+ return ATOM(INCR);
+ }
+
+ // make sure we can perform the XChangeProperty in a single request
+ if (data.size() > increment)
+ return XNone; // ### perhaps use several XChangeProperty calls w/ PropModeAppend?
+ int dataSize = data.size() / (dataFormat / 8);
+ // use a single request to transfer data
+ XChangeProperty(X11->display, window, property, atomFormat,
+ dataFormat, PropModeReplace, (uchar *) data.data(),
+ dataSize);
+ }
+ return property;
+}
+
+/*! \internal
+ Internal cleanup for Windows.
+*/
+void QClipboard::ownerDestroyed()
+{ }
+
+
+/*! \internal
+ Internal optimization for Windows.
+*/
+void QClipboard::connectNotify(const char *)
+{ }
+
+
+bool QClipboard::event(QEvent *e)
+{
+ if (e->type() == QEvent::Timer) {
+ QTimerEvent *te = (QTimerEvent *) e;
+
+ if (waiting_for_data) // should never happen
+ return false;
+
+ if (te->timerId() == timer_id) {
+ killTimer(timer_id);
+ timer_id = 0;
+
+ timer_event_clear = true;
+ if (selection_watcher) // clear selection
+ selectionData()->clear();
+ if (clipboard_watcher) // clear clipboard
+ clipboardData()->clear();
+ timer_event_clear = false;
+
+ return true;
+ } else if (te->timerId() == pending_timer_id) {
+ // I hate klipper
+ killTimer(pending_timer_id);
+ pending_timer_id = 0;
+
+ if (pending_clipboard_changed) {
+ pending_clipboard_changed = false;
+ clipboardData()->clear();
+ emitChanged(QClipboard::Clipboard);
+ }
+ if (pending_selection_changed) {
+ pending_selection_changed = false;
+ selectionData()->clear();
+ emitChanged(QClipboard::Selection);
+ }
+
+ return true;
+ } else if (te->timerId() == incr_timer_id) {
+ killTimer(incr_timer_id);
+ incr_timer_id = 0;
+
+ qt_xclb_incr_timeout();
+
+ return true;
+ } else {
+ return QObject::event(e);
+ }
+ } else if (e->type() != QEvent::Clipboard) {
+ return QObject::event(e);
+ }
+
+ XEvent *xevent = (XEvent *)(((QClipboardEvent *)e)->data());
+ Display *dpy = X11->display;
+
+ if (!xevent)
+ return true;
+
+ switch (xevent->type) {
+
+ case SelectionClear:
+ // new selection owner
+ if (xevent->xselectionclear.selection == XA_PRIMARY) {
+ QClipboardData *d = selectionData();
+
+ // ignore the event if it was generated before we gained selection ownership
+ if (d->timestamp != CurrentTime && xevent->xselectionclear.time <= d->timestamp)
+ break;
+
+ DEBUG("QClipboard: new selection owner 0x%lx at time %lx (ours %lx)",
+ XGetSelectionOwner(dpy, XA_PRIMARY),
+ xevent->xselectionclear.time, d->timestamp);
+
+ if (! waiting_for_data) {
+ d->clear();
+ emitChanged(QClipboard::Selection);
+ } else {
+ pending_selection_changed = true;
+ if (! pending_timer_id)
+ pending_timer_id = QApplication::clipboard()->startTimer(0);
+ }
+ } else if (xevent->xselectionclear.selection == ATOM(CLIPBOARD)) {
+ QClipboardData *d = clipboardData();
+
+ // ignore the event if it was generated before we gained selection ownership
+ if (d->timestamp != CurrentTime && xevent->xselectionclear.time <= d->timestamp)
+ break;
+
+ DEBUG("QClipboard: new clipboard owner 0x%lx at time %lx (%lx)",
+ XGetSelectionOwner(dpy, ATOM(CLIPBOARD)),
+ xevent->xselectionclear.time, d->timestamp);
+
+ if (! waiting_for_data) {
+ d->clear();
+ emitChanged(QClipboard::Clipboard);
+ } else {
+ pending_clipboard_changed = true;
+ if (! pending_timer_id)
+ pending_timer_id = QApplication::clipboard()->startTimer(0);
+ }
+ } else {
+ qWarning("QClipboard: Unknown SelectionClear event received");
+ return false;
+ }
+ break;
+
+ case SelectionNotify:
+ /*
+ Something has delivered data to us, but this was not caught
+ by QClipboardWatcher::getDataInFormat()
+
+ Just skip the event to prevent Bad Things (tm) from
+ happening later on...
+ */
+ break;
+
+ case SelectionRequest:
+ {
+ // someone wants our data
+ XSelectionRequestEvent *req = &xevent->xselectionrequest;
+
+ if (requestor && req->requestor == requestor->internalWinId())
+ break;
+
+ XEvent event;
+ event.xselection.type = SelectionNotify;
+ event.xselection.display = req->display;
+ event.xselection.requestor = req->requestor;
+ event.xselection.selection = req->selection;
+ event.xselection.target = req->target;
+ event.xselection.property = XNone;
+ event.xselection.time = req->time;
+
+ DEBUG("QClipboard: SelectionRequest from %lx\n"
+ " selection 0x%lx (%s) target 0x%lx (%s)",
+ req->requestor,
+ req->selection,
+ X11->xdndAtomToString(req->selection).data(),
+ req->target,
+ X11->xdndAtomToString(req->target).data());
+
+ QClipboardData *d;
+ if (req->selection == XA_PRIMARY) {
+ d = selectionData();
+ } else if (req->selection == ATOM(CLIPBOARD)) {
+ d = clipboardData();
+ } else {
+ qWarning("QClipboard: Unknown selection '%lx'", req->selection);
+ XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
+ break;
+ }
+
+ if (! d->source()) {
+ qWarning("QClipboard: Cannot transfer data, no data available");
+ XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
+ break;
+ }
+
+ DEBUG("QClipboard: SelectionRequest at time %lx (ours %lx)",
+ req->time, d->timestamp);
+
+ if (d->timestamp == CurrentTime // we don't own the selection anymore
+ || (req->time != CurrentTime && req->time < d->timestamp)) {
+ DEBUG("QClipboard: SelectionRequest too old");
+ XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
+ break;
+ }
+
+ Atom xa_targets = ATOM(TARGETS);
+ Atom xa_multiple = ATOM(MULTIPLE);
+ Atom xa_timestamp = ATOM(TIMESTAMP);
+
+ struct AtomPair { Atom target; Atom property; } *multi = 0;
+ Atom multi_type = XNone;
+ int multi_format = 0;
+ int nmulti = 0;
+ int imulti = -1;
+ bool multi_writeback = false;
+
+ if (req->target == xa_multiple) {
+ QByteArray multi_data;
+ if (req->property == XNone
+ || !X11->clipboardReadProperty(req->requestor, req->property, false, &multi_data,
+ 0, &multi_type, &multi_format, 0)
+ || multi_format != 32) {
+ // MULTIPLE property not formatted correctly
+ XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
+ break;
+ }
+ nmulti = multi_data.size()/sizeof(*multi);
+ multi = new AtomPair[nmulti];
+ memcpy(multi,multi_data.data(),multi_data.size());
+ imulti = 0;
+ }
+
+ for (; imulti < nmulti; ++imulti) {
+ Atom target;
+ Atom property;
+
+ if (multi) {
+ target = multi[imulti].target;
+ property = multi[imulti].property;
+ } else {
+ target = req->target;
+ property = req->property;
+ if (property == XNone) // obsolete client
+ property = target;
+ }
+
+ Atom ret = XNone;
+ if (target == XNone || property == XNone) {
+ ;
+ } else if (target == xa_timestamp) {
+ if (d->timestamp != CurrentTime) {
+ XChangeProperty(dpy, req->requestor, property, XA_INTEGER, 32,
+ PropModeReplace, (uchar *) &d->timestamp, 1);
+ ret = property;
+ } else {
+ qWarning("QClipboard: Invalid data timestamp");
+ }
+ } else if (target == xa_targets) {
+ ret = send_targets_selection(d, req->requestor, property);
+ } else {
+ ret = send_selection(d, target, req->requestor, property);
+ }
+
+ if (nmulti > 0) {
+ if (ret == XNone) {
+ multi[imulti].property = XNone;
+ multi_writeback = true;
+ }
+ } else {
+ event.xselection.property = ret;
+ break;
+ }
+ }
+
+ if (nmulti > 0) {
+ if (multi_writeback) {
+ // according to ICCCM 2.6.2 says to put None back
+ // into the original property on the requestor window
+ XChangeProperty(dpy, req->requestor, req->property, multi_type, 32,
+ PropModeReplace, (uchar *) multi, nmulti * 2);
+ }
+
+ delete [] multi;
+ event.xselection.property = req->property;
+ }
+
+ // send selection notify to requestor
+ XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
+
+ DEBUG("QClipboard: SelectionNotify to 0x%lx\n"
+ " property 0x%lx (%s)",
+ req->requestor, event.xselection.property,
+ X11->xdndAtomToString(event.xselection.property).data());
+ }
+ break;
+ }
+
+ return true;
+}
+
+
+
+
+
+
+QClipboardWatcher::QClipboardWatcher(QClipboard::Mode mode)
+ : QInternalMimeData()
+{
+ switch (mode) {
+ case QClipboard::Selection:
+ atom = XA_PRIMARY;
+ break;
+
+ case QClipboard::Clipboard:
+ atom = ATOM(CLIPBOARD);
+ break;
+
+ default:
+ qWarning("QClipboardWatcher: Internal error: Unsupported clipboard mode");
+ break;
+ }
+
+ setupOwner();
+}
+
+QClipboardWatcher::~QClipboardWatcher()
+{
+ if(selection_watcher == this)
+ selection_watcher = 0;
+ if(clipboard_watcher == this)
+ clipboard_watcher = 0;
+}
+
+bool QClipboardWatcher::empty() const
+{
+ Display *dpy = X11->display;
+ Window win = XGetSelectionOwner(dpy, atom);
+
+ if(win == requestor->internalWinId()) {
+ qWarning("QClipboardWatcher::empty: Internal error: Application owns the selection");
+ return true;
+ }
+
+ return win == XNone;
+}
+
+QStringList QClipboardWatcher::formats_sys() const
+{
+ if (empty())
+ return QStringList();
+
+ if (!formatList.count()) {
+ // get the list of targets from the current clipboard owner - we do this
+ // once so that multiple calls to this function don't require multiple
+ // server round trips...
+
+ format_atoms = getDataInFormat(ATOM(TARGETS));
+
+ if (format_atoms.size() > 0) {
+ Atom *targets = (Atom *) format_atoms.data();
+ int size = format_atoms.size() / sizeof(Atom);
+
+ for (int i = 0; i < size; ++i) {
+ if (targets[i] == 0)
+ continue;
+
+ QStringList formatsForAtom = X11->xdndMimeFormatsForAtom(targets[i]);
+ for (int j = 0; j < formatsForAtom.size(); ++j) {
+ if (!formatList.contains(formatsForAtom.at(j)))
+ formatList.append(formatsForAtom.at(j));
+ }
+ VDEBUG(" format: %s", X11->xdndAtomToString(targets[i]).data());
+ VDEBUG(" data:\n%s\n", getDataInFormat(targets[i]).data());
+ }
+ DEBUG("QClipboardWatcher::format: %d formats available", formatList.count());
+ }
+ }
+
+ return formatList;
+}
+
+bool QClipboardWatcher::hasFormat_sys(const QString &format) const
+{
+ QStringList list = formats();
+ return list.contains(format);
+}
+
+QVariant QClipboardWatcher::retrieveData_sys(const QString &fmt, QVariant::Type requestedType) const
+{
+ if (fmt.isEmpty() || empty())
+ return QByteArray();
+
+ (void)formats(); // trigger update of format list
+ DEBUG("QClipboardWatcher::data: fetching format '%s'", fmt.toLatin1().data());
+
+ QList<Atom> atoms;
+ Atom *targets = (Atom *) format_atoms.data();
+ int size = format_atoms.size() / sizeof(Atom);
+ for (int i = 0; i < size; ++i)
+ atoms.append(targets[i]);
+
+ QByteArray encoding;
+ Atom fmtatom = X11->xdndMimeAtomForFormat(fmt, requestedType, atoms, &encoding);
+
+ if (fmtatom == 0)
+ return QVariant();
+
+ return X11->xdndMimeConvertToFormat(fmtatom, getDataInFormat(fmtatom), fmt, requestedType, encoding);
+}
+
+QByteArray QClipboardWatcher::getDataInFormat(Atom fmtatom) const
+{
+ QByteArray buf;
+
+ Display *dpy = X11->display;
+ requestor->createWinId();
+ Window win = requestor->internalWinId();
+ Q_ASSERT(requestor->testAttribute(Qt::WA_WState_Created));
+
+ DEBUG("QClipboardWatcher::getDataInFormat: selection '%s' format '%s'",
+ X11->xdndAtomToString(atom).data(), X11->xdndAtomToString(fmtatom).data());
+
+ XSelectInput(dpy, win, NoEventMask); // don't listen for any events
+
+ XDeleteProperty(dpy, win, ATOM(_QT_SELECTION));
+ XConvertSelection(dpy, atom, fmtatom, ATOM(_QT_SELECTION), win, X11->time);
+ XSync(dpy, false);
+
+ VDEBUG("QClipboardWatcher::getDataInFormat: waiting for SelectionNotify event");
+
+ XEvent xevent;
+ if (!X11->clipboardWaitForEvent(win,SelectionNotify,&xevent,clipboard_timeout) ||
+ xevent.xselection.property == XNone) {
+ DEBUG("QClipboardWatcher::getDataInFormat: format not available");
+ return buf;
+ }
+
+ VDEBUG("QClipboardWatcher::getDataInFormat: fetching data...");
+
+ Atom type;
+ XSelectInput(dpy, win, PropertyChangeMask);
+
+ if (X11->clipboardReadProperty(win, ATOM(_QT_SELECTION), true, &buf, 0, &type, 0, false)) {
+ if (type == ATOM(INCR)) {
+ int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
+ buf = X11->clipboardReadIncrementalProperty(win, ATOM(_QT_SELECTION), nbytes, false);
+ }
+ }
+
+ XSelectInput(dpy, win, NoEventMask);
+
+ DEBUG("QClipboardWatcher::getDataInFormat: %d bytes received", buf.size());
+
+ return buf;
+}
+
+
+const QMimeData* QClipboard::mimeData(Mode mode) const
+{
+ QClipboardData *d = 0;
+ switch (mode) {
+ case Selection:
+ d = selectionData();
+ break;
+ case Clipboard:
+ d = clipboardData();
+ break;
+ default:
+ qWarning("QClipboard::mimeData: unsupported mode '%d'", mode);
+ return 0;
+ }
+
+ if (! d->source() && ! timer_event_clear) {
+ if (mode == Selection) {
+ if (! selection_watcher)
+ selection_watcher = new QClipboardWatcher(mode);
+ d->setSource(selection_watcher);
+ } else {
+ if (! clipboard_watcher)
+ clipboard_watcher = new QClipboardWatcher(mode);
+ d->setSource(clipboard_watcher);
+ }
+
+ if (! timer_id) {
+ // start a zero timer - we will clear cached data when the timer
+ // times out, which will be the next time we hit the event loop...
+ // that way, the data is cached long enough for calls within a single
+ // loop/function, but the data doesn't linger around in case the selection
+ // changes
+ QClipboard *that = ((QClipboard *) this);
+ timer_id = that->startTimer(0);
+ }
+ }
+
+ return d->source();
+}
+
+
+void QClipboard::setMimeData(QMimeData* src, Mode mode)
+{
+ Atom atom, sentinel_atom;
+ QClipboardData *d;
+ switch (mode) {
+ case Selection:
+ atom = XA_PRIMARY;
+ sentinel_atom = ATOM(_QT_SELECTION_SENTINEL);
+ d = selectionData();
+ break;
+
+ case Clipboard:
+ atom = ATOM(CLIPBOARD);
+ sentinel_atom = ATOM(_QT_CLIPBOARD_SENTINEL);
+ d = clipboardData();
+ break;
+
+ default:
+ qWarning("QClipboard::setMimeData: unsupported mode '%d'", mode);
+ return;
+ }
+
+ Display *dpy = X11->display;
+ Window newOwner;
+
+ if (! src) { // no data, clear clipboard contents
+ newOwner = XNone;
+ d->clear();
+ } else {
+ setupOwner();
+
+ newOwner = owner->internalWinId();
+
+ d->setSource(src);
+ d->timestamp = X11->time;
+ }
+
+ Window prevOwner = XGetSelectionOwner(dpy, atom);
+ // use X11->time, since d->timestamp == CurrentTime when clearing
+ XSetSelectionOwner(dpy, atom, newOwner, X11->time);
+
+ if (mode == Selection)
+ emitChanged(QClipboard::Selection);
+ else
+ emitChanged(QClipboard::Clipboard);
+
+ if (XGetSelectionOwner(dpy, atom) != newOwner) {
+ qWarning("QClipboard::setData: Cannot set X11 selection owner for %s",
+ X11->xdndAtomToString(atom).data());
+ d->clear();
+ return;
+ }
+
+ // Signal to other Qt processes that the selection has changed
+ Window owners[2];
+ owners[0] = newOwner;
+ owners[1] = prevOwner;
+ XChangeProperty(dpy, QApplication::desktop()->screen(0)->internalWinId(),
+ sentinel_atom, XA_WINDOW, 32, PropModeReplace,
+ (unsigned char*)&owners, 2);
+}
+
+
+/*
+ Called by the main event loop in qapplication_x11.cpp when either
+ the _QT_SELECTION_SENTINEL property has been changed (i.e. when some
+ Qt process has performed QClipboard::setData()) or when Xfixes told
+ us that an other application changed the selection. If it returns
+ true, the QClipBoard dataChanged() signal should be emitted.
+*/
+
+bool qt_check_selection_sentinel()
+{
+ bool doIt = true;
+ if (owner && !X11->use_xfixes) {
+ /*
+ Since the X selection mechanism cannot give any signal when
+ the selection has changed, we emulate it (for Qt processes) here.
+ The notification should be ignored in case of either
+ a) This is the process that did setData (because setData()
+ then has already emitted dataChanged())
+ b) This is the process that owned the selection when dataChanged()
+ was called (because we have then received a SelectionClear event,
+ and have already emitted dataChanged() as a result of that)
+ */
+
+ unsigned char *retval;
+ Atom actualType;
+ int actualFormat;
+ ulong nitems;
+ ulong bytesLeft;
+
+ if (XGetWindowProperty(X11->display,
+ QApplication::desktop()->screen(0)->internalWinId(),
+ ATOM(_QT_SELECTION_SENTINEL), 0, 2, False, XA_WINDOW,
+ &actualType, &actualFormat, &nitems,
+ &bytesLeft, &retval) == Success) {
+ Window *owners = (Window *)retval;
+ if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) {
+ Window win = owner->internalWinId();
+ if (owners[0] == win || owners[1] == win)
+ doIt = false;
+ }
+
+ XFree(owners);
+ }
+ }
+
+ if (doIt) {
+ if (waiting_for_data) {
+ pending_selection_changed = true;
+ if (! pending_timer_id)
+ pending_timer_id = QApplication::clipboard()->startTimer(0);
+ doIt = false;
+ } else {
+ selectionData()->clear();
+ }
+ }
+
+ return doIt;
+}
+
+
+bool qt_check_clipboard_sentinel()
+{
+ bool doIt = true;
+ if (owner && !X11->use_xfixes) {
+ unsigned char *retval;
+ Atom actualType;
+ int actualFormat;
+ unsigned long nitems, bytesLeft;
+
+ if (XGetWindowProperty(X11->display,
+ QApplication::desktop()->screen(0)->internalWinId(),
+ ATOM(_QT_CLIPBOARD_SENTINEL), 0, 2, False, XA_WINDOW,
+ &actualType, &actualFormat, &nitems, &bytesLeft,
+ &retval) == Success) {
+ Window *owners = (Window *)retval;
+ if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) {
+ Window win = owner->internalWinId();
+ if (owners[0] == win || owners[1] == win)
+ doIt = false;
+ }
+
+ XFree(owners);
+ }
+ }
+
+ if (doIt) {
+ if (waiting_for_data) {
+ pending_clipboard_changed = true;
+ if (! pending_timer_id)
+ pending_timer_id = QApplication::clipboard()->startTimer(0);
+ doIt = false;
+ } else {
+ clipboardData()->clear();
+ }
+ }
+
+ return doIt;
+}
+
+bool qt_xfixes_selection_changed(Window selectionOwner, Time timestamp)
+{
+ QClipboardData *d = selectionData();
+#ifdef QCLIPBOARD_DEBUG
+ DEBUG("qt_xfixes_selection_changed: owner = %u; selectionOwner = %u; internal timestamp = %u; external timestamp = %u",
+ (unsigned int)(owner ? (int)owner->internalWinId() : 0), (unsigned int)selectionOwner,
+ (unsigned int)(d ? d->timestamp : 0), (unsigned int)timestamp);
+#endif
+ if (!owner || (selectionOwner && selectionOwner != owner->internalWinId()) ||
+ (!selectionOwner && d->timestamp != CurrentTime && d->timestamp < timestamp))
+ return qt_check_selection_sentinel();
+ return false;
+}
+
+bool qt_xfixes_clipboard_changed(Window clipboardOwner, Time timestamp)
+{
+ QClipboardData *d = clipboardData();
+#ifdef QCLIPBOARD_DEBUG
+ DEBUG("qt_xfixes_clipboard_changed: owner = %u; clipboardOwner = %u; internal timestamp = %u; external timestamp = %u",
+ (unsigned int)(owner ? (int)owner->internalWinId() : 0), (unsigned int)clipboardOwner,
+ (unsigned int)(d ? d->timestamp : 0), (unsigned int)timestamp);
+#endif
+ if (!owner || (clipboardOwner && clipboardOwner != owner->internalWinId()) ||
+ (!clipboardOwner && d->timestamp != CurrentTime && d->timestamp < timestamp))
+ return qt_check_clipboard_sentinel();
+ return false;
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_CLIPBOARD