63ce73993fcf8028fed0f68f91ae277325eace6d
[profile/ivi/qtbase.git] / src / plugins / platforms / xcb / qxcbclipboard.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qxcbclipboard.h"
43
44 #include "qxcbconnection.h"
45 #include "qxcbscreen.h"
46 #include "qxcbmime.h"
47
48 #include <private/qguiapplication_p.h>
49 #include <QElapsedTimer>
50
51 #include <QtCore/QDebug>
52
53 #define class class_name // Workaround XCB-ICCCM 3.8 breakage
54 #include <xcb/xcb_icccm.h>
55 #undef class
56
57 QT_BEGIN_NAMESPACE
58
59 class QXcbClipboardMime : public QXcbMime
60 {
61     Q_OBJECT
62 public:
63     QXcbClipboardMime(QClipboard::Mode mode, QXcbClipboard *clipboard)
64         : QXcbMime()
65         , m_clipboard(clipboard)
66     {
67         switch (mode) {
68         case QClipboard::Selection:
69             modeAtom = XCB_ATOM_PRIMARY;
70             break;
71
72         case QClipboard::Clipboard:
73             modeAtom = m_clipboard->atom(QXcbAtom::CLIPBOARD);
74             break;
75
76         default:
77             qWarning("QXcbClipboardMime: Internal error: Unsupported clipboard mode");
78             break;
79         }
80     }
81
82 protected:
83     QStringList formats_sys() const
84     {
85         if (empty())
86             return QStringList();
87
88         if (!formatList.count()) {
89             QXcbClipboardMime *that = const_cast<QXcbClipboardMime *>(this);
90             // get the list of targets from the current clipboard owner - we do this
91             // once so that multiple calls to this function don't require multiple
92             // server round trips...
93             that->format_atoms = m_clipboard->getDataInFormat(modeAtom, m_clipboard->atom(QXcbAtom::TARGETS));
94
95             if (format_atoms.size() > 0) {
96                 xcb_atom_t *targets = (xcb_atom_t *) format_atoms.data();
97                 int size = format_atoms.size() / sizeof(xcb_atom_t);
98
99                 for (int i = 0; i < size; ++i) {
100                     if (targets[i] == 0)
101                         continue;
102
103                     QString format = mimeAtomToString(m_clipboard->connection(), targets[i]);
104                     if (!formatList.contains(format))
105                         that->formatList.append(format);
106                 }
107             }
108         }
109
110         return formatList;
111     }
112
113     bool hasFormat_sys(const QString &format) const
114     {
115         QStringList list = formats();
116         return list.contains(format);
117     }
118
119     QVariant retrieveData_sys(const QString &fmt, QVariant::Type requestedType) const
120     {
121         if (fmt.isEmpty() || empty())
122             return QByteArray();
123
124         (void)formats(); // trigger update of format list
125
126         QList<xcb_atom_t> atoms;
127         xcb_atom_t *targets = (xcb_atom_t *) format_atoms.data();
128         int size = format_atoms.size() / sizeof(xcb_atom_t);
129         for (int i = 0; i < size; ++i)
130             atoms.append(targets[i]);
131
132         QByteArray encoding;
133         xcb_atom_t fmtatom = mimeAtomForFormat(m_clipboard->connection(), fmt, requestedType, atoms, &encoding);
134
135         if (fmtatom == 0)
136             return QVariant();
137
138         return mimeConvertToFormat(m_clipboard->connection(), fmtatom, m_clipboard->getDataInFormat(modeAtom, fmtatom), fmt, requestedType, encoding);
139     }
140 private:
141     bool empty() const
142     {
143         return m_clipboard->getSelectionOwner(modeAtom) == XCB_NONE;
144     }
145
146
147     xcb_atom_t modeAtom;
148     QXcbClipboard *m_clipboard;
149     QStringList formatList;
150     QByteArray format_atoms;
151 };
152
153 const int QXcbClipboard::clipboard_timeout = 5000;
154
155 QXcbClipboard::QXcbClipboard(QXcbConnection *c)
156     : QXcbObject(c), QPlatformClipboard()
157     , m_requestor(XCB_NONE)
158     , m_owner(XCB_NONE)
159 {
160     Q_ASSERT(QClipboard::Clipboard == 0);
161     Q_ASSERT(QClipboard::Selection == 1);
162     m_xClipboard[QClipboard::Clipboard] = 0;
163     m_xClipboard[QClipboard::Selection] = 0;
164     m_clientClipboard[QClipboard::Clipboard] = 0;
165     m_clientClipboard[QClipboard::Selection] = 0;
166     m_timestamp[QClipboard::Clipboard] = XCB_CURRENT_TIME;
167     m_timestamp[QClipboard::Selection] = XCB_CURRENT_TIME;
168
169     m_screen = connection()->screens().at(connection()->primaryScreen());
170
171     int x = 0, y = 0, w = 3, h = 3;
172
173     m_owner = xcb_generate_id(xcb_connection());
174     Q_XCB_CALL(xcb_create_window(xcb_connection(),
175                                  XCB_COPY_FROM_PARENT,            // depth -- same as root
176                                  m_owner,                        // window id
177                                  m_screen->screen()->root,                   // parent window id
178                                  x, y, w, h,
179                                  0,                               // border width
180                                  XCB_WINDOW_CLASS_INPUT_OUTPUT,   // window class
181                                  m_screen->screen()->root_visual, // visual
182                                  0,                               // value mask
183                                  0));                             // value list
184
185     if (connection()->hasXFixes()) {
186         const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
187                 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
188                 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
189         Q_XCB_CALL(xcb_xfixes_select_selection_input_checked(xcb_connection(), m_owner, XCB_ATOM_PRIMARY, mask));
190         Q_XCB_CALL(xcb_xfixes_select_selection_input_checked(xcb_connection(), m_owner, atom(QXcbAtom::CLIPBOARD), mask));
191     }
192 }
193
194 QXcbClipboard::~QXcbClipboard()
195 {
196     // Transfer the clipboard content to the clipboard manager if we own a selection
197     if (m_timestamp[QClipboard::Clipboard] != XCB_CURRENT_TIME ||
198             m_timestamp[QClipboard::Selection] != XCB_CURRENT_TIME) {
199
200         // First we check if there is a clipboard manager.
201         xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(xcb_connection(), atom(QXcbAtom::CLIPBOARD_MANAGER));
202         xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(xcb_connection(), cookie, 0);
203         if (reply && reply->owner != XCB_NONE) {
204             // we delete the property so the manager saves all TARGETS.
205             xcb_delete_property(xcb_connection(), m_owner, atom(QXcbAtom::_QT_SELECTION));
206             xcb_convert_selection(xcb_connection(), m_owner, atom(QXcbAtom::CLIPBOARD_MANAGER), atom(QXcbAtom::SAVE_TARGETS),
207                                   atom(QXcbAtom::_QT_SELECTION), connection()->time());
208             connection()->sync();
209
210             // waiting until the clipboard manager fetches the content.
211             if (!waitForClipboardEvent(m_owner, XCB_SELECTION_NOTIFY, clipboard_timeout, true)) {
212                 qWarning("QXcbClipboard: Unable to receive an event from the "
213                          "clipboard manager in a reasonable time");
214             }
215         }
216         free(reply);
217     }
218 }
219
220
221 xcb_window_t QXcbClipboard::getSelectionOwner(xcb_atom_t atom) const
222 {
223     xcb_connection_t *c = xcb_connection();
224     xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(c, atom);
225     xcb_get_selection_owner_reply_t *reply;
226     reply = xcb_get_selection_owner_reply(c, cookie, 0);
227     xcb_window_t win = reply->owner;
228     free(reply);
229     return win;
230 }
231
232 xcb_atom_t QXcbClipboard::atomForMode(QClipboard::Mode mode) const
233 {
234     if (mode == QClipboard::Clipboard)
235         return atom(QXcbAtom::CLIPBOARD);
236     if (mode == QClipboard::Selection)
237         return XCB_ATOM_PRIMARY;
238     return XCB_NONE;
239 }
240
241 QClipboard::Mode QXcbClipboard::modeForAtom(xcb_atom_t a) const
242 {
243     if (a == XCB_ATOM_PRIMARY)
244         return QClipboard::Selection;
245     if (a == atom(QXcbAtom::CLIPBOARD))
246         return QClipboard::Clipboard;
247     // not supported enum value, used to detect errors
248     return QClipboard::FindBuffer;
249 }
250
251
252 QMimeData * QXcbClipboard::mimeData(QClipboard::Mode mode)
253 {
254     if (mode > QClipboard::Selection)
255         return 0;
256
257     xcb_window_t clipboardOwner = getSelectionOwner(atomForMode(mode));
258     if (clipboardOwner == owner()) {
259         return m_clientClipboard[mode];
260     } else {
261         if (!m_xClipboard[mode])
262             m_xClipboard[mode] = new QXcbClipboardMime(mode, this);
263
264         return m_xClipboard[mode];
265     }
266 }
267
268 void QXcbClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
269 {
270     if (mode > QClipboard::Selection)
271         return;
272
273     xcb_atom_t modeAtom = atomForMode(mode);
274
275     if (m_clientClipboard[mode] == data)
276         return;
277
278     xcb_window_t newOwner = XCB_NONE;
279
280     if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection])
281         delete m_clientClipboard[mode];
282     m_clientClipboard[mode] = 0;
283     m_timestamp[mode] = XCB_CURRENT_TIME;
284
285     if (data) {
286         newOwner = owner();
287
288         m_clientClipboard[mode] = data;
289         m_timestamp[mode] = connection()->time();
290     }
291
292     xcb_set_selection_owner(xcb_connection(), newOwner, modeAtom, connection()->time());
293
294     if (getSelectionOwner(modeAtom) != newOwner) {
295         qWarning("QXcbClipboard::setData: Cannot set X11 selection owner");
296     }
297
298     emitChanged(mode);
299 }
300
301 bool QXcbClipboard::supportsMode(QClipboard::Mode mode) const
302 {
303     if (mode <= QClipboard::Selection)
304         return true;
305     return false;
306 }
307
308 bool QXcbClipboard::ownsMode(QClipboard::Mode mode) const
309 {
310     if (m_owner == XCB_NONE || mode > QClipboard::Selection)
311         return false;
312
313     Q_ASSERT(m_timestamp[mode] == XCB_CURRENT_TIME || getSelectionOwner(atomForMode(mode)) == m_owner);
314
315     return m_timestamp[mode] != XCB_CURRENT_TIME;
316 }
317
318 xcb_window_t QXcbClipboard::requestor() const
319 {
320     if (!m_requestor) {
321         const int x = 0, y = 0, w = 3, h = 3;
322         QXcbClipboard *that = const_cast<QXcbClipboard *>(this);
323
324         xcb_window_t window = xcb_generate_id(xcb_connection());
325         Q_XCB_CALL(xcb_create_window(xcb_connection(),
326                                      XCB_COPY_FROM_PARENT,            // depth -- same as root
327                                      window,                        // window id
328                                      m_screen->screen()->root,                   // parent window id
329                                      x, y, w, h,
330                                      0,                               // border width
331                                      XCB_WINDOW_CLASS_INPUT_OUTPUT,   // window class
332                                      m_screen->screen()->root_visual, // visual
333                                      0,                               // value mask
334                                      0));                             // value list
335
336         uint32_t mask = XCB_EVENT_MASK_PROPERTY_CHANGE;
337         xcb_change_window_attributes(xcb_connection(), window, XCB_CW_EVENT_MASK, &mask);
338
339         that->setRequestor(window);
340     }
341     return m_requestor;
342 }
343
344 void QXcbClipboard::setRequestor(xcb_window_t window)
345 {
346     if (m_requestor != XCB_NONE) {
347         xcb_destroy_window(xcb_connection(), m_requestor);
348     }
349     m_requestor = window;
350 }
351
352 xcb_window_t QXcbClipboard::owner() const
353 {
354     return m_owner;
355 }
356
357 xcb_atom_t QXcbClipboard::sendTargetsSelection(QMimeData *d, xcb_window_t window, xcb_atom_t property)
358 {
359     QVector<xcb_atom_t> types;
360     QStringList formats = QInternalMimeData::formatsHelper(d);
361     for (int i = 0; i < formats.size(); ++i) {
362         QList<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(connection(), formats.at(i));
363         for (int j = 0; j < atoms.size(); ++j) {
364             if (!types.contains(atoms.at(j)))
365                 types.append(atoms.at(j));
366         }
367     }
368     types.append(atom(QXcbAtom::TARGETS));
369     types.append(atom(QXcbAtom::MULTIPLE));
370     types.append(atom(QXcbAtom::TIMESTAMP));
371     types.append(atom(QXcbAtom::SAVE_TARGETS));
372
373     xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_ATOM,
374                         32, types.size(), (const void *)types.constData());
375     return property;
376 }
377
378 xcb_atom_t QXcbClipboard::sendSelection(QMimeData *d, xcb_atom_t target, xcb_window_t window, xcb_atom_t property)
379 {
380     xcb_atom_t atomFormat = target;
381     int dataFormat = 0;
382     QByteArray data;
383
384     QString fmt = QXcbMime::mimeAtomToString(connection(), target);
385     if (fmt.isEmpty()) { // Not a MIME type we have
386 //        qDebug() << "QClipboard: send_selection(): converting to type" << connection()->atomName(target) << "is not supported";
387         return XCB_NONE;
388     }
389 //    qDebug() << "QClipboard: send_selection(): converting to type" << fmt;
390
391     if (QXcbMime::mimeDataForAtom(connection(), target, d, &data, &atomFormat, &dataFormat)) {
392
393          // don't allow INCR transfers when using MULTIPLE or to
394         // Motif clients (since Motif doesn't support INCR)
395         static xcb_atom_t motif_clip_temporary = atom(QXcbAtom::CLIP_TEMPORARY);
396         bool allow_incr = property != motif_clip_temporary;
397
398         // X_ChangeProperty protocol request is 24 bytes
399         const int increment = (xcb_get_maximum_request_length(xcb_connection()) * 4) - 24;
400         if (data.size() > increment && allow_incr) {
401             long bytes = data.size();
402             xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property,
403                                 atom(QXcbAtom::INCR), 32, 1, (const void *)&bytes);
404
405 //            (void)new QClipboardINCRTransaction(window, property, atomFormat, dataFormat, data, increment);
406             qWarning("QXcbClipboard: INCR is unimplemented");
407             return property;
408         }
409
410         // make sure we can perform the XChangeProperty in a single request
411         if (data.size() > increment)
412             return XCB_NONE; // ### perhaps use several XChangeProperty calls w/ PropModeAppend?
413         int dataSize = data.size() / (dataFormat / 8);
414         // use a single request to transfer data
415         xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, atomFormat,
416                             dataFormat, dataSize, (const void *)data.constData());
417     }
418     return property;
419 }
420
421 void QXcbClipboard::handleSelectionClearRequest(xcb_selection_clear_event_t *event)
422 {
423     QClipboard::Mode mode = modeForAtom(event->selection);
424     if (mode > QClipboard::Selection)
425         return;
426
427     // ignore the event if it was generated before we gained selection ownership
428     if (m_timestamp[mode] != XCB_CURRENT_TIME && event->time <= m_timestamp[mode])
429         return;
430
431 //    DEBUG("QClipboard: new selection owner 0x%lx at time %lx (ours %lx)",
432 //          XGetSelectionOwner(dpy, XA_PRIMARY),
433 //          xevent->xselectionclear.time, d->timestamp);
434
435 //    if (!waiting_for_data) {
436     setMimeData(0, mode);
437     emitChanged(mode);
438 //    } else {
439 //        pending_selection_changed = true;
440 //        if (! pending_timer_id)
441 //            pending_timer_id = QApplication::clipboard()->startTimer(0);
442 //    }
443 }
444
445 void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
446 {
447     if (requestor() && req->requestor == requestor()) {
448         qWarning("QXcbClipboard: Selection request should be caught before");
449         return;
450     }
451
452     xcb_selection_notify_event_t event;
453     event.response_type = XCB_SELECTION_NOTIFY;
454     event.requestor = req->requestor;
455     event.selection = req->selection;
456     event.target    = req->target;
457     event.property  = XCB_NONE;
458     event.time      = req->time;
459
460     QMimeData *d;
461     QClipboard::Mode mode = modeForAtom(req->selection);
462     if (mode > QClipboard::Selection) {
463         qWarning() << "QXcbClipboard: Unknown selection" << connection()->atomName(req->selection);
464         xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
465         return;
466     }
467
468     d = m_clientClipboard[mode];
469
470     if (!d) {
471         qWarning("QXcbClipboard: Cannot transfer data, no data available");
472         xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
473         return;
474     }
475
476     if (m_timestamp[mode] == XCB_CURRENT_TIME // we don't own the selection anymore
477             || (req->time != XCB_CURRENT_TIME && req->time < m_timestamp[mode])) {
478         qWarning("QXcbClipboard: SelectionRequest too old");
479         xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
480         return;
481     }
482
483     xcb_atom_t xa_targets = atom(QXcbAtom::TARGETS);
484     xcb_atom_t xa_multiple = atom(QXcbAtom::MULTIPLE);
485     xcb_atom_t xa_timestamp = atom(QXcbAtom::TIMESTAMP);
486
487     struct AtomPair { xcb_atom_t target; xcb_atom_t property; } *multi = 0;
488     xcb_atom_t multi_type = XCB_NONE;
489     int multi_format = 0;
490     int nmulti = 0;
491     int imulti = -1;
492     bool multi_writeback = false;
493
494     if (req->target == xa_multiple) {
495         QByteArray multi_data;
496         if (req->property == XCB_NONE
497             || !clipboardReadProperty(req->requestor, req->property, false, &multi_data,
498                                            0, &multi_type, &multi_format)
499             || multi_format != 32) {
500             // MULTIPLE property not formatted correctly
501             xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
502             return;
503         }
504         nmulti = multi_data.size()/sizeof(*multi);
505         multi = new AtomPair[nmulti];
506         memcpy(multi,multi_data.data(),multi_data.size());
507         imulti = 0;
508     }
509
510     for (; imulti < nmulti; ++imulti) {
511         xcb_atom_t target;
512         xcb_atom_t property;
513
514         if (multi) {
515             target = multi[imulti].target;
516             property = multi[imulti].property;
517         } else {
518             target = req->target;
519             property = req->property;
520             if (property == XCB_NONE) // obsolete client
521                 property = target;
522         }
523
524         xcb_atom_t ret = XCB_NONE;
525         if (target == XCB_NONE || property == XCB_NONE) {
526             ;
527         } else if (target == xa_timestamp) {
528             if (m_timestamp[mode] != XCB_CURRENT_TIME) {
529                 xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, req->requestor,
530                                     property, XCB_ATOM_INTEGER, 32, 1, &m_timestamp[mode]);
531                 ret = property;
532             } else {
533                 qWarning("QXcbClipboard: Invalid data timestamp");
534             }
535         } else if (target == xa_targets) {
536             ret = sendTargetsSelection(d, req->requestor, property);
537         } else {
538             ret = sendSelection(d, target, req->requestor, property);
539         }
540
541         if (nmulti > 0) {
542             if (ret == XCB_NONE) {
543                 multi[imulti].property = XCB_NONE;
544                 multi_writeback = true;
545             }
546         } else {
547             event.property = ret;
548             break;
549         }
550     }
551
552     if (nmulti > 0) {
553         if (multi_writeback) {
554             // according to ICCCM 2.6.2 says to put None back
555             // into the original property on the requestor window
556             xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, req->requestor, req->property,
557                                 multi_type, 32, nmulti*2, (const void *)multi);
558         }
559
560         delete [] multi;
561         event.property = req->property;
562     }
563
564     // send selection notify to requestor
565     xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
566 }
567
568 void QXcbClipboard::handleXFixesSelectionRequest(xcb_xfixes_selection_notify_event_t *event)
569 {
570     QClipboard::Mode mode = modeForAtom(event->selection);
571     if (event->owner != owner() && m_clientClipboard[mode] && m_timestamp[mode] < event->selection_timestamp) {
572         setMimeData(0, mode);
573         emitChanged(mode);
574     }
575 }
576
577
578 static inline int maxSelectionIncr(xcb_connection_t *c)
579 {
580     int l = xcb_get_maximum_request_length(c);
581     return (l > 65536 ? 65536*4 : l*4) - 100;
582 }
583
584 bool QXcbClipboard::clipboardReadProperty(xcb_window_t win, xcb_atom_t property, bool deleteProperty, QByteArray *buffer, int *size, xcb_atom_t *type, int *format) const
585 {
586     int    maxsize = maxSelectionIncr(xcb_connection());
587     ulong  bytes_left; // bytes_after
588     xcb_atom_t   dummy_type;
589     int    dummy_format;
590
591     if (!type)                                // allow null args
592         type = &dummy_type;
593     if (!format)
594         format = &dummy_format;
595
596     // Don't read anything, just get the size of the property data
597     xcb_get_property_cookie_t cookie = Q_XCB_CALL(xcb_get_property(xcb_connection(), false, win, property, XCB_GET_PROPERTY_TYPE_ANY, 0, 0));
598     xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0);
599     if (!reply || reply->type == XCB_NONE) {
600         free(reply);
601         buffer->resize(0);
602         return false;
603     }
604     *type = reply->type;
605     *format = reply->format;
606     bytes_left = reply->bytes_after;
607     free(reply);
608
609     int  offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
610
611     switch (*format) {
612     case 8:
613     default:
614         format_inc = sizeof(char) / 1;
615         break;
616
617     case 16:
618         format_inc = sizeof(short) / 2;
619         proplen *= sizeof(short) / 2;
620         break;
621
622     case 32:
623         format_inc = sizeof(long) / 4;
624         proplen *= sizeof(long) / 4;
625         break;
626     }
627
628     int newSize = proplen;
629     buffer->resize(newSize);
630
631     bool ok = (buffer->size() == newSize);
632
633     if (ok && newSize) {
634         // could allocate buffer
635
636         while (bytes_left) {
637             // more to read...
638
639             xcb_get_property_cookie_t cookie = Q_XCB_CALL(xcb_get_property(xcb_connection(), false, win, property, XCB_GET_PROPERTY_TYPE_ANY, offset, maxsize/4));
640             reply = xcb_get_property_reply(xcb_connection(), cookie, 0);
641             if (!reply || reply->type == XCB_NONE) {
642                 free(reply);
643                 break;
644             }
645             *type = reply->type;
646             *format = reply->format;
647             bytes_left = reply->bytes_after;
648             char *data = (char *)xcb_get_property_value(reply);
649             int length = xcb_get_property_value_length(reply);
650
651             offset += length / (32 / *format);
652             length *= format_inc * (*format) / 8;
653
654             // Here we check if we get a buffer overflow and tries to
655             // recover -- this shouldn't normally happen, but it doesn't
656             // hurt to be defensive
657             if ((int)(buffer_offset + length) > buffer->size()) {
658                 length = buffer->size() - buffer_offset;
659
660                 // escape loop
661                 bytes_left = 0;
662             }
663
664             memcpy(buffer->data() + buffer_offset, data, length);
665             buffer_offset += length;
666
667             free(reply);
668         }
669     }
670
671
672     // correct size, not 0-term.
673     if (size)
674         *size = buffer_offset;
675
676     if (deleteProperty)
677         xcb_delete_property(xcb_connection(), win, property);
678
679     connection()->flush();
680
681     return ok;
682 }
683
684
685 namespace
686 {
687     class Notify {
688     public:
689         Notify(xcb_window_t win, int t)
690             : window(win), type(t) {}
691         xcb_window_t window;
692         int type;
693         bool checkEvent(xcb_generic_event_t *event) const {
694             if (!event)
695                 return false;
696             int t = event->response_type & 0x7f;
697             if (t != type)
698                 return false;
699             if (t == XCB_PROPERTY_NOTIFY) {
700                 xcb_property_notify_event_t *pn = (xcb_property_notify_event_t *)event;
701                 if (pn->window == window)
702                     return true;
703             } else if (t == XCB_SELECTION_NOTIFY) {
704                 xcb_selection_notify_event_t *sn = (xcb_selection_notify_event_t *)event;
705                 if (sn->requestor == window)
706                     return true;
707             }
708             return false;
709         }
710     };
711     class ClipboardEvent {
712     public:
713         ClipboardEvent(QXcbConnection *c)
714         { clipboard = c->internAtom("CLIPBOARD"); }
715         xcb_atom_t clipboard;
716         bool checkEvent(xcb_generic_event_t *e) const {
717             if (!e)
718                 return false;
719             int type = e->response_type & 0x7f;
720             if (type == XCB_SELECTION_REQUEST) {
721                 xcb_selection_request_event_t *sr = (xcb_selection_request_event_t *)e;
722                 return sr->selection == XCB_ATOM_PRIMARY || sr->selection == clipboard;
723             } else if (type == XCB_SELECTION_CLEAR) {
724                 xcb_selection_clear_event_t *sc = (xcb_selection_clear_event_t *)e;
725                 return sc->selection == XCB_ATOM_PRIMARY || sc->selection == clipboard;
726             }
727             return false;
728         }
729     };
730 }
731
732 xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t win, int type, int timeout, bool checkManager)
733 {
734     QElapsedTimer timer;
735     timer.start();
736     do {
737         Notify notify(win, type);
738         xcb_generic_event_t *e = connection()->checkEvent(notify);
739         if (e)
740             return e;
741
742         if (checkManager) {
743             xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(xcb_connection(), atom(QXcbAtom::CLIPBOARD_MANAGER));
744             xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(xcb_connection(), cookie, 0);
745             if (!reply || reply->owner == XCB_NONE) {
746                 free(reply);
747                 return 0;
748             }
749             free(reply);
750         }
751
752         // process other clipboard events, since someone is probably requesting data from us
753         ClipboardEvent clipboard(connection());
754         e = connection()->checkEvent(clipboard);
755         if (e) {
756             connection()->handleXcbEvent(e);
757             free(e);
758         }
759
760         connection()->flush();
761
762         // sleep 50 ms, so we don't use up CPU cycles all the time.
763         struct timeval usleep_tv;
764         usleep_tv.tv_sec = 0;
765         usleep_tv.tv_usec = 50000;
766         select(0, 0, 0, 0, &usleep_tv);
767     } while (timer.elapsed() < timeout);
768
769     return 0;
770 }
771
772 QByteArray QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm)
773 {
774     QByteArray buf;
775     QByteArray tmp_buf;
776     bool alloc_error = false;
777     int  length;
778     int  offset = 0;
779
780     if (nbytes > 0) {
781         // Reserve buffer + zero-terminator (for text data)
782         // We want to complete the INCR transfer even if we cannot
783         // allocate more memory
784         buf.resize(nbytes+1);
785         alloc_error = buf.size() != nbytes+1;
786     }
787
788     for (;;) {
789         connection()->flush();
790         xcb_generic_event_t *ge = waitForClipboardEvent(win, XCB_PROPERTY_NOTIFY, clipboard_timeout);
791         if (!ge)
792             break;
793
794         xcb_property_notify_event_t *event = (xcb_property_notify_event_t *)ge;
795         if (event->atom != property || event->state != XCB_PROPERTY_NEW_VALUE)
796             continue;
797         if (clipboardReadProperty(win, property, true, &tmp_buf, &length, 0, 0)) {
798             if (length == 0) {                // no more data, we're done
799                 if (nullterm) {
800                     buf.resize(offset+1);
801                     buf[offset] = '\0';
802                 } else {
803                     buf.resize(offset);
804                 }
805                 return buf;
806             } else if (!alloc_error) {
807                 if (offset+length > (int)buf.size()) {
808                     buf.resize(offset+length+65535);
809                     if (buf.size() != offset+length+65535) {
810                         alloc_error = true;
811                         length = buf.size() - offset;
812                     }
813                 }
814                 memcpy(buf.data()+offset, tmp_buf.constData(), length);
815                 tmp_buf.resize(0);
816                 offset += length;
817             }
818         } else {
819             break;
820         }
821
822         free(ge);
823     }
824
825     // timed out ... create a new requestor window, otherwise the requestor
826     // could consider next request to be still part of this timed out request
827     setRequestor(0);
828
829     return QByteArray();
830 }
831
832 QByteArray QXcbClipboard::getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtAtom)
833 {
834     return getSelection(modeAtom, fmtAtom, atom(QXcbAtom::_QT_SELECTION));
835 }
836
837 QByteArray QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property)
838 {
839     QByteArray buf;
840     xcb_window_t win = requestor();
841
842     xcb_delete_property(xcb_connection(), win, property);
843     xcb_convert_selection(xcb_connection(), win, selection, target, property, connection()->time());
844
845     connection()->sync();
846
847     xcb_generic_event_t *ge = waitForClipboardEvent(win, XCB_SELECTION_NOTIFY, clipboard_timeout);
848     bool no_selection = !ge || ((xcb_selection_notify_event_t *)ge)->property == XCB_NONE;
849     free(ge);
850
851     if (no_selection)
852         return buf;
853
854     xcb_atom_t type;
855     if (clipboardReadProperty(win, property, true, &buf, 0, &type, 0)) {
856         if (type == atom(QXcbAtom::INCR)) {
857             int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
858             buf = clipboardReadIncrementalProperty(win, property, nbytes, false);
859         }
860     }
861
862     return buf;
863 }
864
865 QT_END_NAMESPACE
866
867 #include "qxcbclipboard.moc"