add an xcb based clipboard implementation
authorLars Knoll <lars.knoll@nokia.com>
Wed, 25 May 2011 23:35:29 +0000 (01:35 +0200)
committerLars Knoll <lars.knoll@nokia.com>
Mon, 30 May 2011 12:44:12 +0000 (14:44 +0200)
Copying from the app to somewhere else works
fine, but pasting into the app is still broken.

src/plugins/platforms/xcb/qxcbclipboard.cpp [new file with mode: 0644]
src/plugins/platforms/xcb/qxcbclipboard.h [new file with mode: 0644]
src/plugins/platforms/xcb/qxcbconnection.cpp
src/plugins/platforms/xcb/qxcbconnection.h
src/plugins/platforms/xcb/qxcbintegration.cpp
src/plugins/platforms/xcb/qxcbintegration.h
src/plugins/platforms/xcb/xcb.pro

diff --git a/src/plugins/platforms/xcb/qxcbclipboard.cpp b/src/plugins/platforms/xcb/qxcbclipboard.cpp
new file mode 100644 (file)
index 0000000..b69642b
--- /dev/null
@@ -0,0 +1,782 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the plugins 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 Technology Preview License Agreement accompanying
+** this package.
+**
+** 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.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qxcbclipboard.h"
+
+#include "qxcbconnection.h"
+#include "qxcbscreen.h"
+#include "qxcbmime.h"
+
+#include <private/qguiapplication_p.h>
+#include <QElapsedTimer>
+
+#include <QtCore/QDebug>
+
+#include <xcb/xcb_icccm.h>
+
+class QXcbClipboardMime : public QXcbMime
+{
+    Q_OBJECT
+public:
+    QXcbClipboardMime(QClipboard::Mode mode, QXcbClipboard *clipboard)
+        : QXcbMime()
+        , m_clipboard(clipboard)
+    {
+        switch (mode) {
+        case QClipboard::Selection:
+            modeAtom = QXcbAtom::XA_PRIMARY;
+            break;
+
+        case QClipboard::Clipboard:
+            modeAtom = m_clipboard->connection()->atom(QXcbAtom::CLIPBOARD);
+            break;
+
+        default:
+            qWarning("QTestLiteMime: Internal error: Unsupported clipboard mode");
+            break;
+        }
+    }
+
+protected:
+    QStringList formats_sys() const
+    {
+        if (empty())
+            return QStringList();
+
+        if (!formatList.count()) {
+            QXcbClipboardMime *that = const_cast<QXcbClipboardMime *>(this);
+            // 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...
+            that->format_atoms = m_clipboard->getDataInFormat(modeAtom, m_clipboard->connection()->atom(QXcbAtom::TARGETS));
+
+            if (format_atoms.size() > 0) {
+                xcb_atom_t *targets = (xcb_atom_t *) format_atoms.data();
+                int size = format_atoms.size() / sizeof(xcb_atom_t);
+
+                for (int i = 0; i < size; ++i) {
+                    if (targets[i] == 0)
+                        continue;
+
+                    QString format = mimeAtomToString(m_clipboard->connection(), targets[i]);
+                    if (!formatList.contains(format))
+                        that->formatList.append(format);
+                }
+            }
+        }
+
+        return formatList;
+    }
+
+    bool hasFormat_sys(const QString &format) const
+    {
+        QStringList list = formats();
+        return list.contains(format);
+    }
+
+    QVariant retrieveData_sys(const QString &fmt, QVariant::Type requestedType) const
+    {
+        if (fmt.isEmpty() || empty())
+            return QByteArray();
+
+        (void)formats(); // trigger update of format list
+
+        QList<xcb_atom_t> atoms;
+        xcb_atom_t *targets = (xcb_atom_t *) format_atoms.data();
+        int size = format_atoms.size() / sizeof(xcb_atom_t);
+        for (int i = 0; i < size; ++i)
+            atoms.append(targets[i]);
+
+        QByteArray encoding;
+        xcb_atom_t fmtatom = mimeAtomForFormat(m_clipboard->connection(), fmt, requestedType, atoms, &encoding);
+
+        if (fmtatom == 0)
+            return QVariant();
+
+        return mimeConvertToFormat(m_clipboard->connection(), fmtatom, m_clipboard->getDataInFormat(modeAtom, fmtatom), fmt, requestedType, encoding);
+    }
+private:
+    bool empty() const
+    {
+        return m_clipboard->getSelectionOwner(modeAtom) == XCB_NONE;
+    }
+
+
+    xcb_atom_t modeAtom;
+    QXcbClipboard *m_clipboard;
+    QStringList formatList;
+    QByteArray format_atoms;
+};
+
+const int QXcbClipboard::clipboard_timeout = 5000;
+
+QXcbClipboard::QXcbClipboard(QXcbConnection *connection)
+    : QPlatformClipboard()
+    , m_connection(connection)
+    , m_xClipboard(0)
+    , m_clientClipboard(0)
+    , m_xSelection(0)
+    , m_clientSelection(0)
+    , m_requestor(XCB_NONE)
+    , m_owner(XCB_NONE)
+{
+    m_screen = m_connection->screens().at(m_connection->primaryScreen());
+}
+
+xcb_window_t QXcbClipboard::getSelectionOwner(xcb_atom_t atom) const
+{
+    xcb_connection_t *c = m_connection->xcb_connection();
+    xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(c, atom);
+    xcb_get_selection_owner_reply_t *reply;
+    reply = xcb_get_selection_owner_reply(c, cookie, 0);
+    xcb_window_t win = reply->owner;
+    free(reply);
+    return win;
+}
+
+QMimeData * QXcbClipboard::mimeData(QClipboard::Mode mode)
+{
+    if (mode == QClipboard::Clipboard) {
+        if (!m_xClipboard) {
+            m_xClipboard = new QXcbClipboardMime(mode, this);
+        }
+        xcb_window_t clipboardOwner = getSelectionOwner(m_connection->atom(QXcbAtom::CLIPBOARD));
+        if (clipboardOwner == owner()) {
+            return m_clientClipboard;
+        } else {
+            return m_xClipboard;
+        }
+    } else if (mode == QClipboard::Selection) {
+        if (!m_xSelection) {
+            m_xSelection = new QXcbClipboardMime(mode, this);
+        }
+        xcb_window_t clipboardOwner = getSelectionOwner(QXcbAtom::XA_PRIMARY);
+        if (clipboardOwner == owner()) {
+            return m_clientSelection;
+        } else {
+            return m_xSelection;
+        }
+    }
+    return 0;
+}
+
+void QXcbClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
+{
+    xcb_atom_t modeAtom;
+    QMimeData **d;
+    switch (mode) {
+    case QClipboard::Selection:
+        modeAtom = QXcbAtom::XA_PRIMARY;
+        d = &m_clientSelection;
+        break;
+
+    case QClipboard::Clipboard:
+        modeAtom = m_connection->atom(QXcbAtom::CLIPBOARD);
+        d = &m_clientClipboard;
+        break;
+
+    default:
+        qWarning("QClipboard::setMimeData: unsupported mode '%d'", mode);
+        return;
+    }
+
+    xcb_window_t newOwner;
+
+    if (! data) { // no data, clear clipboard contents
+        newOwner = XCB_NONE;
+    } else {
+        newOwner = owner();
+
+        *d = data;
+    }
+
+    xcb_set_selection_owner(m_connection->xcb_connection(), newOwner, modeAtom, XCB_CURRENT_TIME);
+
+    if (getSelectionOwner(modeAtom) != newOwner) {
+        qWarning("QClipboard::setData: Cannot set X11 selection owner");
+    }
+
+}
+
+bool QXcbClipboard::supportsMode(QClipboard::Mode mode) const
+{
+    if (mode == QClipboard::Clipboard || mode == QClipboard::Selection)
+        return true;
+    return false;
+}
+
+xcb_window_t QXcbClipboard::requestor() const
+{
+    if (!m_requestor) {
+        const int x = 0, y = 0, w = 3, h = 3;
+        QXcbClipboard *that = const_cast<QXcbClipboard *>(this);
+
+        xcb_window_t window = xcb_generate_id(m_connection->xcb_connection());
+        Q_XCB_CALL(xcb_create_window(m_connection->xcb_connection(),
+                                     XCB_COPY_FROM_PARENT,            // depth -- same as root
+                                     window,                        // window id
+                                     m_screen->screen()->root,                   // parent window id
+                                     x, y, w, h,
+                                     0,                               // border width
+                                     XCB_WINDOW_CLASS_INPUT_OUTPUT,   // window class
+                                     m_screen->screen()->root_visual, // visual
+                                     0,                               // value mask
+                                     0));                             // value list
+
+        that->setRequestor(window);
+    }
+    return m_requestor;
+}
+
+void QXcbClipboard::setRequestor(xcb_window_t window)
+{
+    if (m_requestor != XCB_NONE) {
+        xcb_destroy_window(m_connection->xcb_connection(), m_requestor);
+    }
+    m_requestor = window;
+}
+
+xcb_window_t QXcbClipboard::owner() const
+{
+    if (!m_owner) {
+        int x = 0, y = 0, w = 3, h = 3;
+        QXcbClipboard *that = const_cast<QXcbClipboard *>(this);
+
+        xcb_window_t window = xcb_generate_id(m_connection->xcb_connection());
+        Q_XCB_CALL(xcb_create_window(m_connection->xcb_connection(),
+                                     XCB_COPY_FROM_PARENT,            // depth -- same as root
+                                     window,                        // window id
+                                     m_screen->screen()->root,                   // parent window id
+                                     x, y, w, h,
+                                     0,                               // border width
+                                     XCB_WINDOW_CLASS_INPUT_OUTPUT,   // window class
+                                     m_screen->screen()->root_visual, // visual
+                                     0,                               // value mask
+                                     0));                             // value list
+
+        that->setOwner(window);
+    }
+    return m_owner;
+}
+
+void QXcbClipboard::setOwner(xcb_window_t window)
+{
+    if (m_owner != XCB_NONE){
+        xcb_destroy_window(m_connection->xcb_connection(), m_owner);
+    }
+    m_owner = window;
+}
+
+xcb_atom_t QXcbClipboard::sendTargetsSelection(QMimeData *d, xcb_window_t window, xcb_atom_t property)
+{
+    QVector<xcb_atom_t> types;
+    QStringList formats = QInternalMimeData::formatsHelper(d);
+    for (int i = 0; i < formats.size(); ++i) {
+        QList<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(m_connection, formats.at(i));
+        for (int j = 0; j < atoms.size(); ++j) {
+            if (!types.contains(atoms.at(j)))
+                types.append(atoms.at(j));
+        }
+    }
+    types.append(m_connection->atom(QXcbAtom::TARGETS));
+    types.append(m_connection->atom(QXcbAtom::MULTIPLE));
+    types.append(m_connection->atom(QXcbAtom::TIMESTAMP));
+    types.append(m_connection->atom(QXcbAtom::SAVE_TARGETS));
+
+    xcb_change_property(m_connection->xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, QXcbAtom::XA_ATOM,
+                        32, types.size(), (const void *)types.constData());
+    return property;
+}
+
+xcb_atom_t QXcbClipboard::sendSelection(QMimeData *d, xcb_atom_t target, xcb_window_t window, xcb_atom_t property)
+{
+    xcb_atom_t atomFormat = target;
+    int dataFormat = 0;
+    QByteArray data;
+
+    QString fmt = QXcbMime::mimeAtomToString(m_connection, target);
+    if (fmt.isEmpty()) { // Not a MIME type we have
+        qDebug() << "QClipboard: send_selection(): converting to type '%s' is not supported" << fmt.data();
+        return XCB_NONE;
+    }
+    qDebug() << "QClipboard: send_selection(): converting to type '%s'" << fmt.data();
+
+    if (QXcbMime::mimeDataForAtom(m_connection, target, d, &data, &atomFormat, &dataFormat)) {
+
+         // don't allow INCR transfers when using MULTIPLE or to
+        // Motif clients (since Motif doesn't support INCR)
+        static xcb_atom_t motif_clip_temporary = m_connection->atom(QXcbAtom::CLIP_TEMPORARY);
+        bool allow_incr = property != motif_clip_temporary;
+
+        // X_ChangeProperty protocol request is 24 bytes
+        const int increment = (xcb_get_maximum_request_length(m_connection->xcb_connection()) * 4) - 24;
+        if (data.size() > increment && allow_incr) {
+            long bytes = data.size();
+            xcb_change_property(m_connection->xcb_connection(), XCB_PROP_MODE_REPLACE, window, property,
+                                m_connection->atom(QXcbAtom::INCR), 32, 1, (const void *)&bytes);
+
+//            (void)new QClipboardINCRTransaction(window, property, atomFormat, dataFormat, data, increment);
+            qDebug() << "not implemented INCRT just YET!";
+            return property;
+        }
+
+        // make sure we can perform the XChangeProperty in a single request
+        if (data.size() > increment)
+            return XCB_NONE; // ### perhaps use several XChangeProperty calls w/ PropModeAppend?
+        int dataSize = data.size() / (dataFormat / 8);
+        // use a single request to transfer data
+        xcb_change_property(m_connection->xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, atomFormat,
+                            dataFormat, dataSize, (const void *)data.constData());
+    }
+    return property;
+}
+
+void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
+{
+    if (requestor() && req->requestor == requestor()) {
+        qDebug() << "This should be caught before";
+        return;
+    }
+
+    xcb_selection_notify_event_t event;
+    event.response_type = XCB_SELECTION_NOTIFY;
+    event.requestor = req->requestor;
+    event.selection = req->selection;
+    event.target    = req->target;
+    event.property  = XCB_NONE;
+    event.time      = req->time;
+
+    QMimeData *d;
+    if (req->selection == QXcbAtom::XA_PRIMARY) {
+        d = m_clientSelection;
+    } else if (req->selection == m_connection->atom(QXcbAtom::CLIPBOARD)) {
+        d = m_clientClipboard;
+    } else {
+        qWarning("QClipboard: Unknown selection '%lx'", (long)req->selection);
+        xcb_send_event(m_connection->xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
+        return;
+    }
+
+    if (!d) {
+        qWarning("QClipboard: Cannot transfer data, no data available");
+        xcb_send_event(m_connection->xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
+        return;
+    }
+
+    xcb_atom_t xa_targets = m_connection->atom(QXcbAtom::TARGETS);
+    xcb_atom_t xa_multiple = m_connection->atom(QXcbAtom::MULTIPLE);
+    xcb_atom_t xa_timestamp = m_connection->atom(QXcbAtom::TIMESTAMP);
+
+    struct AtomPair { xcb_atom_t target; xcb_atom_t property; } *multi = 0;
+    xcb_atom_t multi_type = XCB_NONE;
+    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 == XCB_NONE
+            || !clipboardReadProperty(req->requestor, req->property, false, &multi_data,
+                                           0, &multi_type, &multi_format)
+            || multi_format != 32) {
+            // MULTIPLE property not formatted correctly
+            xcb_send_event(m_connection->xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
+            return;
+        }
+        nmulti = multi_data.size()/sizeof(*multi);
+        multi = new AtomPair[nmulti];
+        memcpy(multi,multi_data.data(),multi_data.size());
+        imulti = 0;
+    }
+
+    for (; imulti < nmulti; ++imulti) {
+        xcb_atom_t target;
+        xcb_atom_t property;
+
+        if (multi) {
+            target = multi[imulti].target;
+            property = multi[imulti].property;
+        } else {
+            target = req->target;
+            property = req->property;
+            if (property == XCB_NONE) // obsolete client
+                property = target;
+        }
+
+        xcb_atom_t ret = XCB_NONE;
+        if (target == XCB_NONE || property == XCB_NONE) {
+            ;
+        } else if (target == xa_timestamp) {
+//            if (d->timestamp != CurrentTime) {
+//                XChangeProperty(DISPLAY_FROM_XCB(m_connection), req->requestor, property, QXcbAtom::XA_INTEGER, 32,
+//                                PropModeReplace, CurrentTime, 1);
+//                ret = property;
+//            } else {
+//                qWarning("QClipboard: Invalid data timestamp");
+//            }
+        } else if (target == xa_targets) {
+            ret = sendTargetsSelection(d, req->requestor, property);
+        } else {
+            ret = sendSelection(d, target, req->requestor, property);
+        }
+
+        if (nmulti > 0) {
+            if (ret == XCB_NONE) {
+                multi[imulti].property = XCB_NONE;
+                multi_writeback = true;
+            }
+        } else {
+            event.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
+            xcb_change_property(m_connection->xcb_connection(), XCB_PROP_MODE_REPLACE, req->requestor, req->property,
+                                multi_type, 32, nmulti*2, (const void *)multi);
+        }
+
+        delete [] multi;
+        event.property = req->property;
+    }
+
+    // send selection notify to requestor
+    xcb_send_event(m_connection->xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
+}
+
+static inline int maxSelectionIncr(xcb_connection_t *c)
+{
+    int l = xcb_get_maximum_request_length(c);
+    return l > 65536 ? 65536*4 : l*4 - 100;
+}
+
+bool QXcbClipboard::clipboardReadProperty(xcb_window_t win, xcb_atom_t property, bool deleteProperty, QByteArray *buffer, int *size, xcb_atom_t *type, int *format) const
+{
+    int    maxsize = maxSelectionIncr(m_connection->xcb_connection());
+    ulong  bytes_left; // bytes_after
+    ulong  length;     // nitems
+    xcb_atom_t   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
+    xcb_get_property_cookie_t cookie = xcb_get_property(m_connection->xcb_connection(), false, win, property, XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
+    xcb_get_property_reply_t *reply = xcb_get_property_reply(m_connection->xcb_connection(), cookie, 0);
+    if (!reply || reply->type == XCB_NONE) {
+        buffer->resize(0);
+        return false;
+    }
+    *type = reply->type;
+    length = reply->length;
+    *format = reply->format;
+    bytes_left = reply->bytes_after;
+    free(reply);
+
+    int  offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
+
+    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;
+    buffer->resize(newSize);
+
+    bool ok = (buffer->size() == newSize);
+
+    if (ok && newSize) {
+        // could allocate buffer
+
+        while (bytes_left) {
+            // more to read...
+
+            xcb_get_property_cookie_t cookie = xcb_get_property(m_connection->xcb_connection(), false, win, property, XCB_GET_PROPERTY_TYPE_ANY, offset, maxsize/4);
+            xcb_get_property_reply_t *reply = xcb_get_property_reply(m_connection->xcb_connection(), cookie, 0);
+            if (!reply || reply->type == XCB_NONE)
+                break;
+            *type = reply->type;
+            length = reply->length;
+            *format = reply->format;
+            bytes_left = reply->bytes_after;
+            char *data = (char *)xcb_get_property_value(reply);
+
+            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;
+
+            free(reply);
+        }
+
+/* ###### FIXME
+        if (*format == 8 && type == m_connection->atom(QXcbAtom::COMPOUND_TEXT)) {
+            // convert COMPOUND_TEXT to a multibyte string
+            XTextProperty textprop;
+            textprop.encoding = type;
+            textprop.format = *format;
+            textprop.nitems = buffer_offset;
+            textprop.value = (unsigned char *) buffer->data();
+
+            char **list_ret = 0;
+            int count;
+            if (XmbTextPropertyToTextList(DISPLAY_FROM_XCB(m_connection), &textprop, &list_ret,
+                         &count) == Success && count && list_ret) {
+                offset = buffer_offset = strlen(list_ret[0]);
+                buffer->resize(offset);
+                memcpy(buffer->data(), list_ret[0], offset);
+            }
+            if (list_ret) XFreeStringList(list_ret);
+        }
+*/
+    }
+
+
+    // correct size, not 0-term.
+    if (size)
+        *size = buffer_offset;
+
+    if (deleteProperty)
+        xcb_delete_property(m_connection->xcb_connection(), win, property);
+
+    m_connection->flush();
+
+    return ok;
+}
+
+
+namespace
+{
+    class Notify {
+    public:
+        Notify(xcb_window_t win, int t)
+            : window(win), type(t) {}
+        xcb_window_t window;
+        int type;
+        bool check(xcb_generic_event_t *event) const {
+            if (event->response_type != type)
+                return false;
+            if (event->response_type == XCB_PROPERTY_NOTIFY) {
+                xcb_property_notify_event_t *pn = (xcb_property_notify_event_t *)event;
+                if (pn->window == window)
+                    return true;
+            } else if (event->response_type == XCB_SELECTION_NOTIFY) {
+                xcb_selection_notify_event_t *sn = (xcb_selection_notify_event_t *)event;
+                if (sn->requestor == window)
+                    return true;
+            }
+            return false;
+        }
+    };
+    class ClipboardEvent {
+    public:
+        ClipboardEvent(QXcbConnection *c)
+        { clipboard = c->internAtom("CLIPBOARD"); }
+        xcb_atom_t clipboard;
+        bool check(xcb_generic_event_t *e) const {
+            if (e->response_type == XCB_SELECTION_REQUEST) {
+                xcb_selection_request_event_t *sr = (xcb_selection_request_event_t *)e;
+                return sr->selection == QXcbAtom::XA_PRIMARY || sr->selection == clipboard;
+            } else if (e->response_type == XCB_SELECTION_CLEAR) {
+                xcb_selection_clear_event_t *sc = (xcb_selection_clear_event_t *)e;
+                return sc->selection == QXcbAtom::XA_PRIMARY || sc->selection == clipboard;
+            }
+            return false;
+        }
+    };
+}
+
+static xcb_generic_event_t *waitForClipboardEvent(QXcbConnection *connection, xcb_window_t win, int type, int timeout)
+{
+    QElapsedTimer timer;
+    timer.start();
+    do {
+        Notify notify(win, type);
+        xcb_generic_event_t *e = connection->checkEvent(notify);
+        if (e)
+            return e;
+
+        // process other clipboard events, since someone is probably requesting data from us
+        ClipboardEvent clipboard(connection);
+        e = connection->checkEvent(clipboard);
+        if (e) {
+            connection->handleXcbEvent(e);
+            free(e);
+        }
+
+        connection->flush();
+
+        // 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 (timer.elapsed() < timeout);
+
+    return 0;
+}
+
+QByteArray QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm)
+{
+    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 (;;) {
+        m_connection->flush();
+        xcb_generic_event_t *ge = ::waitForClipboardEvent(m_connection, win, XCB_PROPERTY_NOTIFY, clipboard_timeout);
+        if (!ge)
+            break;
+
+        xcb_property_notify_event_t *event = (xcb_property_notify_event_t *)ge;
+        if (event->atom != property || event->state != XCB_PROPERTY_NEW_VALUE)
+            continue;
+        if (clipboardReadProperty(win, property, true, &tmp_buf, &length, 0, 0)) {
+            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
+    setRequestor(0);
+
+    return QByteArray();
+}
+
+QByteArray QXcbClipboard::getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtAtom)
+{
+    QByteArray buf;
+
+    xcb_window_t win = requestor();
+
+    uint32_t mask = XCB_EVENT_MASK_NO_EVENT;
+    xcb_change_window_attributes(m_connection->xcb_connection(), win, XCB_CW_EVENT_MASK, &mask);
+
+    xcb_delete_property(m_connection->xcb_connection(), win, m_connection->atom(QXcbAtom::_QT_SELECTION));
+    xcb_convert_selection(m_connection->xcb_connection(), win, modeAtom, fmtAtom,
+                          m_connection->atom(QXcbAtom::_QT_SELECTION), XCB_CURRENT_TIME);
+
+    m_connection->sync();
+
+    xcb_generic_event_t *ge = waitForClipboardEvent(m_connection, win, XCB_SELECTION_NOTIFY, clipboard_timeout);
+    if (!ge || ((xcb_selection_notify_event_t *)ge)->property == XCB_NONE)
+        return buf;
+
+    mask = XCB_EVENT_MASK_PROPERTY_CHANGE;
+    xcb_change_window_attributes(m_connection->xcb_connection(), win, XCB_CW_EVENT_MASK, &mask);
+
+    xcb_atom_t type;
+    if (clipboardReadProperty(win, m_connection->atom(QXcbAtom::_QT_SELECTION), true, &buf, 0, &type, 0)) {
+        if (type == m_connection->atom(QXcbAtom::INCR)) {
+            int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
+            buf = clipboardReadIncrementalProperty(win, m_connection->atom(QXcbAtom::_QT_SELECTION), nbytes, false);
+        }
+    }
+
+    mask = XCB_EVENT_MASK_NO_EVENT;
+    xcb_change_window_attributes(m_connection->xcb_connection(), win, XCB_CW_EVENT_MASK, &mask);
+
+    return buf;
+}
+
+#include "qxcbclipboard.moc"
diff --git a/src/plugins/platforms/xcb/qxcbclipboard.h b/src/plugins/platforms/xcb/qxcbclipboard.h
new file mode 100644 (file)
index 0000000..57900f4
--- /dev/null
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the plugins 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 Technology Preview License Agreement accompanying
+** this package.
+**
+** 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.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QXCBCLIPBOARD_H
+#define QXCBCLIPBOARD_H
+
+#include <QPlatformClipboard>
+
+#include <xcb/xcb.h>
+
+class QXcbConnection;
+class QXcbScreen;
+
+class QXcbClipboard : public QPlatformClipboard
+{
+public:
+    QXcbClipboard(QXcbConnection *connection);
+
+    QMimeData *mimeData(QClipboard::Mode mode);
+    void setMimeData(QMimeData *data, QClipboard::Mode mode);
+
+    bool supportsMode(QClipboard::Mode mode) const;
+
+    QXcbConnection *connection() const { return m_connection; }
+    QXcbScreen *screen() const { return m_screen; }
+
+    xcb_window_t requestor() const;
+    void setRequestor(xcb_window_t window);
+
+    xcb_window_t owner() const;
+
+    void handleSelectionRequest(xcb_selection_request_event_t *event);
+
+    bool clipboardReadProperty(xcb_window_t win, xcb_atom_t property, bool deleteProperty, QByteArray *buffer, int *size, xcb_atom_t *type, int *format) const;
+    QByteArray clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm);
+
+    QByteArray getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtatom);
+
+    xcb_window_t getSelectionOwner(xcb_atom_t atom) const;
+
+private:
+    void setOwner(xcb_window_t window);
+
+
+    xcb_atom_t sendTargetsSelection(QMimeData *d, xcb_window_t window, xcb_atom_t property);
+    xcb_atom_t sendSelection(QMimeData *d, xcb_atom_t target, xcb_window_t window, xcb_atom_t property);
+
+    QXcbConnection *m_connection;
+    QXcbScreen *m_screen;
+
+    QMimeData *m_xClipboard;
+    QMimeData *m_clientClipboard;
+
+    QMimeData *m_xSelection;
+    QMimeData *m_clientSelection;
+
+    xcb_window_t m_requestor;
+    xcb_window_t m_owner;
+
+    static const int clipboard_timeout;
+
+};
+
+#endif // QXCBCLIPBOARD_H
index 22cbe10..9fe1730 100644 (file)
@@ -43,6 +43,7 @@
 #include "qxcbkeyboard.h"
 #include "qxcbscreen.h"
 #include "qxcbwindow.h"
+#include "qxcbclipboard.h"
 
 #include <QtAlgorithms>
 #include <QSocketNotifier>
@@ -116,6 +117,7 @@ QXcbConnection::QXcbConnection(const char *displayName)
     }
 
     m_keyboard = new QXcbKeyboard(this);
+    m_clipboard = new QXcbClipboard(this);
 
 #ifdef XCB_USE_DRI2
     initializeDri2();
@@ -141,6 +143,7 @@ QXcbConnection::~QXcbConnection()
 #endif
 
     delete m_keyboard;
+    delete m_clipboard;
 }
 
 void QXcbConnection::addWindow(xcb_window_t id, QXcbWindow *window)
@@ -413,71 +416,104 @@ void QXcbConnection::handleXcbError(xcb_generic_error_t *error)
 #endif
 }
 
+void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
+{
+#ifdef Q_XCB_DEBUG
+    {
+        int i = 0;
+        for (; i < m_callLog.size(); ++i)
+            if (m_callLog.at(i).sequence >= event->sequence)
+                break;
+        m_callLog.remove(0, i);
+    }
+#endif
+    bool handled = true;
+
+    uint response_type = event->response_type & ~0x80;
+
+    switch (response_type) {
+    case XCB_EXPOSE:
+        HANDLE_PLATFORM_WINDOW_EVENT(xcb_expose_event_t, window, handleExposeEvent);
+    case XCB_BUTTON_PRESS:
+        HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_press_event_t, event, handleButtonPressEvent);
+    case XCB_BUTTON_RELEASE:
+        HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_release_event_t, event, handleButtonReleaseEvent);
+    case XCB_MOTION_NOTIFY:
+        HANDLE_PLATFORM_WINDOW_EVENT(xcb_motion_notify_event_t, event, handleMotionNotifyEvent);
+    case XCB_CONFIGURE_NOTIFY:
+        HANDLE_PLATFORM_WINDOW_EVENT(xcb_configure_notify_event_t, event, handleConfigureNotifyEvent);
+    case XCB_CLIENT_MESSAGE:
+        HANDLE_PLATFORM_WINDOW_EVENT(xcb_client_message_event_t, window, handleClientMessageEvent);
+    case XCB_ENTER_NOTIFY:
+        HANDLE_PLATFORM_WINDOW_EVENT(xcb_enter_notify_event_t, event, handleEnterNotifyEvent);
+    case XCB_LEAVE_NOTIFY:
+        HANDLE_PLATFORM_WINDOW_EVENT(xcb_leave_notify_event_t, event, handleLeaveNotifyEvent);
+    case XCB_FOCUS_IN:
+        HANDLE_PLATFORM_WINDOW_EVENT(xcb_focus_in_event_t, event, handleFocusInEvent);
+    case XCB_FOCUS_OUT:
+        HANDLE_PLATFORM_WINDOW_EVENT(xcb_focus_out_event_t, event, handleFocusOutEvent);
+    case XCB_KEY_PRESS:
+        HANDLE_KEYBOARD_EVENT(xcb_key_press_event_t, handleKeyPressEvent);
+    case XCB_KEY_RELEASE:
+        HANDLE_KEYBOARD_EVENT(xcb_key_release_event_t, handleKeyReleaseEvent);
+    case XCB_MAPPING_NOTIFY:
+        m_keyboard->handleMappingNotifyEvent((xcb_mapping_notify_event_t *)event);
+        break;
+    case XCB_SELECTION_REQUEST:
+        m_clipboard->handleSelectionRequest((xcb_selection_request_event_t *)event);
+    case XCB_SELECTION_CLEAR:
+    case XCB_SELECTION_NOTIFY:
+    default:
+        handled = false;
+        break;
+    }
+    if (handled)
+        printXcbEvent("Handled XCB event", event);
+    else
+        printXcbEvent("Unhandled XCB event", event);
+}
+
 void QXcbConnection::processXcbEvents()
 {
-    while (xcb_generic_event_t *event = xcb_poll_for_event(xcb_connection())) {
-        bool handled = true;
+    while (xcb_generic_event_t *event = xcb_poll_for_event(xcb_connection()))
+        eventqueue.append(event);
+
+    for(int i = 0; i < eventqueue.size(); ++i) {
+        xcb_generic_event_t *event = eventqueue.at(i);
+        if (!event)
+            continue;
 
         uint response_type = event->response_type & ~0x80;
 
         if (!response_type) {
             handleXcbError((xcb_generic_error_t *)event);
-            continue;
+        } else {
+            handleXcbEvent(event);
         }
 
-#ifdef Q_XCB_DEBUG
-        {
-            int i = 0;
-            for (; i < m_callLog.size(); ++i)
-                if (m_callLog.at(i).sequence >= event->sequence)
-                    break;
-            m_callLog.remove(0, i);
-        }
-#endif
-
-        switch (response_type) {
-        case XCB_EXPOSE:
-            HANDLE_PLATFORM_WINDOW_EVENT(xcb_expose_event_t, window, handleExposeEvent);
-        case XCB_BUTTON_PRESS:
-            HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_press_event_t, event, handleButtonPressEvent);
-        case XCB_BUTTON_RELEASE:
-            HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_release_event_t, event, handleButtonReleaseEvent);
-        case XCB_MOTION_NOTIFY:
-            HANDLE_PLATFORM_WINDOW_EVENT(xcb_motion_notify_event_t, event, handleMotionNotifyEvent);
-        case XCB_CONFIGURE_NOTIFY:
-            HANDLE_PLATFORM_WINDOW_EVENT(xcb_configure_notify_event_t, event, handleConfigureNotifyEvent);
-        case XCB_CLIENT_MESSAGE:
-            HANDLE_PLATFORM_WINDOW_EVENT(xcb_client_message_event_t, window, handleClientMessageEvent);
-        case XCB_ENTER_NOTIFY:
-            HANDLE_PLATFORM_WINDOW_EVENT(xcb_enter_notify_event_t, event, handleEnterNotifyEvent);
-        case XCB_LEAVE_NOTIFY:
-            HANDLE_PLATFORM_WINDOW_EVENT(xcb_leave_notify_event_t, event, handleLeaveNotifyEvent);
-        case XCB_FOCUS_IN:
-            HANDLE_PLATFORM_WINDOW_EVENT(xcb_focus_in_event_t, event, handleFocusInEvent);
-        case XCB_FOCUS_OUT:
-            HANDLE_PLATFORM_WINDOW_EVENT(xcb_focus_out_event_t, event, handleFocusOutEvent);
-        case XCB_KEY_PRESS:
-            HANDLE_KEYBOARD_EVENT(xcb_key_press_event_t, handleKeyPressEvent);
-        case XCB_KEY_RELEASE:
-            HANDLE_KEYBOARD_EVENT(xcb_key_release_event_t, handleKeyReleaseEvent);
-        case XCB_MAPPING_NOTIFY:
-            m_keyboard->handleMappingNotifyEvent((xcb_mapping_notify_event_t *)event);
-            break;
-        default:
-            handled = false;
-            break;
-        }
-        if (handled)
-            printXcbEvent("Handled XCB event", event);
-        else
-            printXcbEvent("Unhandled XCB event", event);
-
         free(event);
     }
 
+    eventqueue.clear();
+
     xcb_flush(xcb_connection());
 }
 
+xcb_generic_event_t *QXcbConnection::checkEvent(int type)
+{
+    while (xcb_generic_event_t *event = xcb_poll_for_event(xcb_connection()))
+        eventqueue.append(event);
+
+    for (int i = 0; i < eventqueue.size(); ++i) {
+        xcb_generic_event_t *event = eventqueue.at(i);
+        if (event->response_type == type) {
+            eventqueue[i] = 0;
+            return event;
+        }
+    }
+    return 0;
+}
+
 static const char * xcb_atomnames = {
     // window-manager <-> client protocols
     "WM_PROTOCOLS\0"
index 07c3f75..6782d70 100644 (file)
@@ -59,6 +59,7 @@ typedef QHash<xcb_window_t, QXcbWindow *> WindowMapper;
 namespace QXcbAtom {
     static const xcb_atom_t XA_PRIMARY = 1;
     static const xcb_atom_t XA_SECONDARY = 2;
+    static const xcb_atom_t XA_ATOM = 4;
     static const xcb_atom_t XA_PIXMAP = 20;
     static const xcb_atom_t XA_BITMAP = 5;
     static const xcb_atom_t XA_STRING = 32;
@@ -226,6 +227,7 @@ namespace QXcbAtom {
 }
 
 class QXcbKeyboard;
+class QXcbClipboard;
 
 class QXcbConnection : public QObject
 {
@@ -249,6 +251,8 @@ public:
 
     QXcbKeyboard *keyboard() const { return m_keyboard; }
 
+    QXcbClipboard *clipboard() const { return m_clipboard; }
+
 #ifdef XCB_USE_XLIB
     void *xlib_display() const { return m_xlib_display; }
 #endif
@@ -265,11 +269,18 @@ public:
 #endif
 
     void sync();
+    void flush() { xcb_flush(m_connection); }
+
     void handleXcbError(xcb_generic_error_t *error);
+    void handleXcbEvent(xcb_generic_event_t *event);
 
     void addWindow(xcb_window_t id, QXcbWindow *window);
     void removeWindow(xcb_window_t id);
 
+    xcb_generic_event_t *checkEvent(int type);
+    template<typename T>
+    inline xcb_generic_event_t *checkEvent(const T &checker);
+
 private slots:
     void processXcbEvents();
 
@@ -292,6 +303,7 @@ private:
     QByteArray m_displayName;
 
     QXcbKeyboard *m_keyboard;
+    QXcbClipboard *m_clipboard;
 
 #if defined(XCB_USE_XLIB)
     void *m_xlib_display;
@@ -319,12 +331,30 @@ private:
     template <typename cookie_t>
     friend cookie_t q_xcb_call_template(const cookie_t &cookie, QXcbConnection *connection, const char *file, int line);
 #endif
+    QVector<xcb_generic_event_t *> eventqueue;
 
     WindowMapper m_mapper;
 };
 
 #define DISPLAY_FROM_XCB(object) ((Display *)(object->connection()->xlib_display()))
 
+template<typename T>
+xcb_generic_event_t *QXcbConnection::checkEvent(const T &checker)
+{
+    while (xcb_generic_event_t *event = xcb_poll_for_event(xcb_connection()))
+        eventqueue.append(event);
+
+    for (int i = 0; i < eventqueue.size(); ++i) {
+        xcb_generic_event_t *event = eventqueue.at(i);
+        if (checker.check(event)) {
+            eventqueue[i] = 0;
+            return event;
+        }
+    }
+    return 0;
+}
+
+
 #ifdef Q_XCB_DEBUG
 template <typename cookie_t>
 cookie_t q_xcb_call_template(const cookie_t &cookie, QXcbConnection *connection, const char *file, int line)
index d47bec6..7bf2c84 100644 (file)
@@ -45,6 +45,7 @@
 #include "qxcbwindow.h"
 #include "qxcbwindowsurface.h"
 #include "qxcbnativeinterface.h"
+#include "qxcbclipboard.h"
 
 #include <qgenericunixprintersupport.h>
 
@@ -155,3 +156,8 @@ QPlatformPrinterSupport *QXcbIntegration::printerSupport() const
 {
     return m_printerSupport;
 }
+
+QPlatformClipboard *QXcbIntegration::clipboard() const
+{
+    return m_connection->clipboard();
+}
index 36c792f..65e2906 100644 (file)
@@ -70,6 +70,7 @@ public:
     QPlatformNativeInterface *nativeInterface()const;
 
     QPlatformPrinterSupport *printerSupport() const;
+    QPlatformClipboard *clipboard() const;
 
 private:
     bool hasOpenGL() const;
index bc44bcf..84d3d53 100644 (file)
@@ -6,7 +6,7 @@ QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/platforms
 QT += core-private gui-private
 
 SOURCES = \
-#        qxcbclipboard.cpp \
+        qxcbclipboard.cpp \
         qxcbconnection.cpp \
         qxcbintegration.cpp \
         qxcbkeyboard.cpp \
@@ -18,7 +18,7 @@ SOURCES = \
         qxcbnativeinterface.cpp
 
 HEADERS = \
-#        qxcbclipboard.h \
+        qxcbclipboard.h \
         qxcbconnection.h \
         qxcbintegration.h \
         qxcbkeyboard.h \