1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qxcbclipboard.h"
41 
42 #include "qxcbconnection.h"
43 #include "qxcbscreen.h"
44 #include "qxcbmime.h"
45 #include "qxcbwindow.h"
46 
47 #include <private/qguiapplication_p.h>
48 #include <QElapsedTimer>
49 
50 #include <QtCore/QDebug>
51 
52 QT_BEGIN_NAMESPACE
53 
54 #ifndef QT_NO_CLIPBOARD
55 
56 class QXcbClipboardMime : public QXcbMime
57 {
58     Q_OBJECT
59 public:
QXcbClipboardMime(QClipboard::Mode mode,QXcbClipboard * clipboard)60     QXcbClipboardMime(QClipboard::Mode mode, QXcbClipboard *clipboard)
61         : QXcbMime()
62         , m_clipboard(clipboard)
63     {
64         switch (mode) {
65         case QClipboard::Selection:
66             modeAtom = XCB_ATOM_PRIMARY;
67             break;
68 
69         case QClipboard::Clipboard:
70             modeAtom = m_clipboard->atom(QXcbAtom::CLIPBOARD);
71             break;
72 
73         default:
74             qWarning("QXcbClipboardMime: Internal error: Unsupported clipboard mode");
75             break;
76         }
77     }
78 
reset()79     void reset()
80     {
81         formatList.clear();
82     }
83 
isEmpty() const84     bool isEmpty() const
85     {
86         return m_clipboard->getSelectionOwner(modeAtom) == XCB_NONE;
87     }
88 
89 protected:
formats_sys() const90     QStringList formats_sys() const override
91     {
92         if (isEmpty())
93             return QStringList();
94 
95         if (!formatList.count()) {
96             QXcbClipboardMime *that = const_cast<QXcbClipboardMime *>(this);
97             // get the list of targets from the current clipboard owner - we do this
98             // once so that multiple calls to this function don't require multiple
99             // server round trips...
100             that->format_atoms = m_clipboard->getDataInFormat(modeAtom, m_clipboard->atom(QXcbAtom::TARGETS));
101 
102             if (format_atoms.size() > 0) {
103                 const xcb_atom_t *targets = (const xcb_atom_t *) format_atoms.data();
104                 int size = format_atoms.size() / sizeof(xcb_atom_t);
105 
106                 for (int i = 0; i < size; ++i) {
107                     if (targets[i] == 0)
108                         continue;
109 
110                     QString format = mimeAtomToString(m_clipboard->connection(), targets[i]);
111                     if (!formatList.contains(format))
112                         that->formatList.append(format);
113                 }
114             }
115         }
116 
117         return formatList;
118     }
119 
hasFormat_sys(const QString & format) const120     bool hasFormat_sys(const QString &format) const override
121     {
122         QStringList list = formats();
123         return list.contains(format);
124     }
125 
retrieveData_sys(const QString & fmt,QVariant::Type type) const126     QVariant retrieveData_sys(const QString &fmt, QVariant::Type type) const override
127     {
128         auto requestedType = QMetaType::Type(type);
129         if (fmt.isEmpty() || isEmpty())
130             return QByteArray();
131 
132         (void)formats(); // trigger update of format list
133 
134         QVector<xcb_atom_t> atoms;
135         const xcb_atom_t *targets = (const xcb_atom_t *) format_atoms.data();
136         int size = format_atoms.size() / sizeof(xcb_atom_t);
137         atoms.reserve(size);
138         for (int i = 0; i < size; ++i)
139             atoms.append(targets[i]);
140 
141         QByteArray encoding;
142         xcb_atom_t fmtatom = mimeAtomForFormat(m_clipboard->connection(), fmt, requestedType, atoms, &encoding);
143 
144         if (fmtatom == 0)
145             return QVariant();
146 
147         return mimeConvertToFormat(m_clipboard->connection(), fmtatom, m_clipboard->getDataInFormat(modeAtom, fmtatom), fmt, requestedType, encoding);
148     }
149 private:
150 
151     xcb_atom_t modeAtom;
152     QXcbClipboard *m_clipboard;
153     QStringList formatList;
154     QByteArray format_atoms;
155 };
156 
QXcbClipboardTransaction(QXcbClipboard * clipboard,xcb_window_t w,xcb_atom_t p,QByteArray d,xcb_atom_t t,int f)157 QXcbClipboardTransaction::QXcbClipboardTransaction(QXcbClipboard *clipboard, xcb_window_t w,
158                                                xcb_atom_t p, QByteArray d, xcb_atom_t t, int f)
159     : m_clipboard(clipboard), m_window(w), m_property(p), m_data(d), m_target(t), m_format(f)
160 {
161     const quint32 values[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
162     xcb_change_window_attributes(m_clipboard->xcb_connection(), m_window,
163                                  XCB_CW_EVENT_MASK, values);
164 
165     m_abortTimerId = startTimer(m_clipboard->clipboardTimeout());
166 }
167 
~QXcbClipboardTransaction()168 QXcbClipboardTransaction::~QXcbClipboardTransaction()
169 {
170     if (m_abortTimerId)
171         killTimer(m_abortTimerId);
172     m_abortTimerId = 0;
173     m_clipboard->removeTransaction(m_window);
174 }
175 
updateIncrementalProperty(const xcb_property_notify_event_t * event)176 bool QXcbClipboardTransaction::updateIncrementalProperty(const xcb_property_notify_event_t *event)
177 {
178     if (event->atom != m_property || event->state != XCB_PROPERTY_DELETE)
179         return false;
180 
181     // restart the timer
182     if (m_abortTimerId)
183         killTimer(m_abortTimerId);
184     m_abortTimerId = startTimer(m_clipboard->clipboardTimeout());
185 
186     uint bytes_left = uint(m_data.size()) - m_offset;
187     if (bytes_left > 0) {
188         int increment = m_clipboard->increment();
189         uint bytes_to_send = qMin(uint(increment), bytes_left);
190 
191         qCDebug(lcQpaClipboard, "sending %d bytes, %d remaining, transaction: %p)",
192                 bytes_to_send, bytes_left - bytes_to_send, this);
193 
194         uint32_t dataSize = bytes_to_send / (m_format / 8);
195         xcb_change_property(m_clipboard->xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
196                             m_property, m_target, m_format, dataSize, m_data.constData() + m_offset);
197         m_offset += bytes_to_send;
198     } else {
199         qCDebug(lcQpaClipboard, "transaction %p completed", this);
200 
201         xcb_change_property(m_clipboard->xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
202                             m_property, m_target, m_format, 0, nullptr);
203 
204         const quint32 values[] = { XCB_EVENT_MASK_NO_EVENT };
205         xcb_change_window_attributes(m_clipboard->xcb_connection(), m_window,
206                                      XCB_CW_EVENT_MASK, values);
207         delete this; // self destroy
208     }
209     return true;
210 }
211 
212 
timerEvent(QTimerEvent * ev)213 void QXcbClipboardTransaction::timerEvent(QTimerEvent *ev)
214 {
215     if (ev->timerId() == m_abortTimerId) {
216         // this can happen when the X client we are sending data
217         // to decides to exit (normally or abnormally)
218         qCDebug(lcQpaClipboard, "timed out while sending data to %p", this);
219         delete this; // self destroy
220     }
221 }
222 
223 const int QXcbClipboard::clipboard_timeout = 5000;
224 
QXcbClipboard(QXcbConnection * c)225 QXcbClipboard::QXcbClipboard(QXcbConnection *c)
226     : QXcbObject(c), QPlatformClipboard()
227 {
228     Q_ASSERT(QClipboard::Clipboard == 0);
229     Q_ASSERT(QClipboard::Selection == 1);
230     m_clientClipboard[QClipboard::Clipboard] = nullptr;
231     m_clientClipboard[QClipboard::Selection] = nullptr;
232     m_timestamp[QClipboard::Clipboard] = XCB_CURRENT_TIME;
233     m_timestamp[QClipboard::Selection] = XCB_CURRENT_TIME;
234     m_owner = connection()->getQtSelectionOwner();
235 
236     if (connection()->hasXFixes()) {
237         const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
238                 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
239                 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
240         xcb_xfixes_select_selection_input_checked(xcb_connection(), m_owner, XCB_ATOM_PRIMARY, mask);
241         xcb_xfixes_select_selection_input_checked(xcb_connection(), m_owner, atom(QXcbAtom::CLIPBOARD), mask);
242     }
243 
244     // xcb_change_property_request_t and xcb_get_property_request_t are the same size
245     m_maxPropertyRequestDataBytes = connection()->maxRequestDataBytes(sizeof(xcb_change_property_request_t));
246 }
247 
~QXcbClipboard()248 QXcbClipboard::~QXcbClipboard()
249 {
250     m_clipboard_closing = true;
251     // Transfer the clipboard content to the clipboard manager if we own a selection
252     if (m_timestamp[QClipboard::Clipboard] != XCB_CURRENT_TIME ||
253             m_timestamp[QClipboard::Selection] != XCB_CURRENT_TIME) {
254 
255         // First we check if there is a clipboard manager.
256         auto reply = Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom(QXcbAtom::CLIPBOARD_MANAGER));
257         if (reply && reply->owner != XCB_NONE) {
258             // we delete the property so the manager saves all TARGETS.
259             xcb_delete_property(xcb_connection(), m_owner, atom(QXcbAtom::_QT_SELECTION));
260             xcb_convert_selection(xcb_connection(), m_owner, atom(QXcbAtom::CLIPBOARD_MANAGER), atom(QXcbAtom::SAVE_TARGETS),
261                                   atom(QXcbAtom::_QT_SELECTION), connection()->time());
262             connection()->sync();
263 
264             // waiting until the clipboard manager fetches the content.
265             if (auto event = waitForClipboardEvent(m_owner, XCB_SELECTION_NOTIFY, true)) {
266                 free(event);
267             } else {
268                 qWarning("QXcbClipboard: Unable to receive an event from the "
269                          "clipboard manager in a reasonable time");
270             }
271         }
272     }
273 
274     if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection])
275         delete m_clientClipboard[QClipboard::Clipboard];
276     delete m_clientClipboard[QClipboard::Selection];
277 }
278 
handlePropertyNotify(const xcb_generic_event_t * event)279 bool QXcbClipboard::handlePropertyNotify(const xcb_generic_event_t *event)
280 {
281     if (m_transactions.isEmpty() || event->response_type != XCB_PROPERTY_NOTIFY)
282         return false;
283 
284     auto propertyNotify = reinterpret_cast<const xcb_property_notify_event_t *>(event);
285     TransactionMap::Iterator it = m_transactions.find(propertyNotify->window);
286     if (it == m_transactions.constEnd())
287         return false;
288 
289     return (*it)->updateIncrementalProperty(propertyNotify);
290 }
291 
getSelectionOwner(xcb_atom_t atom) const292 xcb_window_t QXcbClipboard::getSelectionOwner(xcb_atom_t atom) const
293 {
294     return connection()->getSelectionOwner(atom);
295 }
296 
atomForMode(QClipboard::Mode mode) const297 xcb_atom_t QXcbClipboard::atomForMode(QClipboard::Mode mode) const
298 {
299     if (mode == QClipboard::Clipboard)
300         return atom(QXcbAtom::CLIPBOARD);
301     if (mode == QClipboard::Selection)
302         return XCB_ATOM_PRIMARY;
303     return XCB_NONE;
304 }
305 
modeForAtom(xcb_atom_t a) const306 QClipboard::Mode QXcbClipboard::modeForAtom(xcb_atom_t a) const
307 {
308     if (a == XCB_ATOM_PRIMARY)
309         return QClipboard::Selection;
310     if (a == atom(QXcbAtom::CLIPBOARD))
311         return QClipboard::Clipboard;
312     // not supported enum value, used to detect errors
313     return QClipboard::FindBuffer;
314 }
315 
316 
mimeData(QClipboard::Mode mode)317 QMimeData * QXcbClipboard::mimeData(QClipboard::Mode mode)
318 {
319     if (mode > QClipboard::Selection)
320         return nullptr;
321 
322     xcb_window_t clipboardOwner = getSelectionOwner(atomForMode(mode));
323     if (clipboardOwner == owner()) {
324         return m_clientClipboard[mode];
325     } else {
326         if (!m_xClipboard[mode])
327             m_xClipboard[mode].reset(new QXcbClipboardMime(mode, this));
328 
329         return m_xClipboard[mode].data();
330     }
331 }
332 
setMimeData(QMimeData * data,QClipboard::Mode mode)333 void QXcbClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
334 {
335     if (mode > QClipboard::Selection)
336         return;
337 
338     QXcbClipboardMime *xClipboard = nullptr;
339     // verify if there is data to be cleared on global X Clipboard.
340     if (!data) {
341         xClipboard = qobject_cast<QXcbClipboardMime *>(mimeData(mode));
342         if (xClipboard) {
343             if (xClipboard->isEmpty())
344                 return;
345         }
346     }
347 
348     if (!xClipboard && (m_clientClipboard[mode] == data))
349         return;
350 
351     xcb_atom_t modeAtom = atomForMode(mode);
352     xcb_window_t newOwner = XCB_NONE;
353 
354     if (m_clientClipboard[mode]) {
355         if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection])
356             delete m_clientClipboard[mode];
357         m_clientClipboard[mode] = nullptr;
358         m_timestamp[mode] = XCB_CURRENT_TIME;
359     }
360 
361     if (connection()->time() == XCB_CURRENT_TIME)
362         connection()->setTime(connection()->getTimestamp());
363 
364     if (data) {
365         newOwner = owner();
366 
367         m_clientClipboard[mode] = data;
368         m_timestamp[mode] = connection()->time();
369     }
370 
371     xcb_set_selection_owner(xcb_connection(), newOwner, modeAtom, connection()->time());
372 
373     if (getSelectionOwner(modeAtom) != newOwner) {
374         qWarning("QXcbClipboard::setMimeData: Cannot set X11 selection owner");
375     }
376 
377     emitChanged(mode);
378 }
379 
supportsMode(QClipboard::Mode mode) const380 bool QXcbClipboard::supportsMode(QClipboard::Mode mode) const
381 {
382     if (mode <= QClipboard::Selection)
383         return true;
384     return false;
385 }
386 
ownsMode(QClipboard::Mode mode) const387 bool QXcbClipboard::ownsMode(QClipboard::Mode mode) const
388 {
389     if (m_owner == XCB_NONE || mode > QClipboard::Selection)
390         return false;
391 
392     Q_ASSERT(m_timestamp[mode] == XCB_CURRENT_TIME || getSelectionOwner(atomForMode(mode)) == m_owner);
393 
394     return m_timestamp[mode] != XCB_CURRENT_TIME;
395 }
396 
screen() const397 QXcbScreen *QXcbClipboard::screen() const
398 {
399     return connection()->primaryScreen();
400 }
401 
requestor() const402 xcb_window_t QXcbClipboard::requestor() const
403 {
404      QXcbScreen *platformScreen = screen();
405 
406     if (!m_requestor && platformScreen) {
407         const int x = 0, y = 0, w = 3, h = 3;
408         QXcbClipboard *that = const_cast<QXcbClipboard *>(this);
409 
410         xcb_window_t window = xcb_generate_id(xcb_connection());
411         xcb_create_window(xcb_connection(),
412                           XCB_COPY_FROM_PARENT,                  // depth -- same as root
413                           window,                                // window id
414                           platformScreen->screen()->root,        // parent window id
415                           x, y, w, h,
416                           0,                                     // border width
417                           XCB_WINDOW_CLASS_INPUT_OUTPUT,         // window class
418                           platformScreen->screen()->root_visual, // visual
419                           0,                                     // value mask
420                           nullptr);                                    // value list
421 
422         QXcbWindow::setWindowTitle(connection(), window,
423                                    QStringLiteral("Qt Clipboard Requestor Window"));
424 
425         uint32_t mask = XCB_EVENT_MASK_PROPERTY_CHANGE;
426         xcb_change_window_attributes(xcb_connection(), window, XCB_CW_EVENT_MASK, &mask);
427 
428         that->setRequestor(window);
429     }
430     return m_requestor;
431 }
432 
setRequestor(xcb_window_t window)433 void QXcbClipboard::setRequestor(xcb_window_t window)
434 {
435     if (m_requestor != XCB_NONE) {
436         xcb_destroy_window(xcb_connection(), m_requestor);
437     }
438     m_requestor = window;
439 }
440 
owner() const441 xcb_window_t QXcbClipboard::owner() const
442 {
443     return m_owner;
444 }
445 
sendTargetsSelection(QMimeData * d,xcb_window_t window,xcb_atom_t property)446 xcb_atom_t QXcbClipboard::sendTargetsSelection(QMimeData *d, xcb_window_t window, xcb_atom_t property)
447 {
448     QVector<xcb_atom_t> types;
449     QStringList formats = QInternalMimeData::formatsHelper(d);
450     for (int i = 0; i < formats.size(); ++i) {
451         QVector<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(connection(), formats.at(i));
452         for (int j = 0; j < atoms.size(); ++j) {
453             if (!types.contains(atoms.at(j)))
454                 types.append(atoms.at(j));
455         }
456     }
457     types.append(atom(QXcbAtom::TARGETS));
458     types.append(atom(QXcbAtom::MULTIPLE));
459     types.append(atom(QXcbAtom::TIMESTAMP));
460     types.append(atom(QXcbAtom::SAVE_TARGETS));
461 
462     xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_ATOM,
463                         32, types.size(), (const void *)types.constData());
464     return property;
465 }
466 
sendSelection(QMimeData * d,xcb_atom_t target,xcb_window_t window,xcb_atom_t property)467 xcb_atom_t QXcbClipboard::sendSelection(QMimeData *d, xcb_atom_t target, xcb_window_t window, xcb_atom_t property)
468 {
469     xcb_atom_t atomFormat = target;
470     int dataFormat = 0;
471     QByteArray data;
472 
473     QString fmt = QXcbMime::mimeAtomToString(connection(), target);
474     if (fmt.isEmpty()) { // Not a MIME type we have
475 //        qDebug() << "QClipboard: send_selection(): converting to type" << connection()->atomName(target) << "is not supported";
476         return XCB_NONE;
477     }
478 //    qDebug() << "QClipboard: send_selection(): converting to type" << fmt;
479 
480     if (QXcbMime::mimeDataForAtom(connection(), target, d, &data, &atomFormat, &dataFormat)) {
481 
482          // don't allow INCR transfers when using MULTIPLE or to
483         // Motif clients (since Motif doesn't support INCR)
484         static xcb_atom_t motif_clip_temporary = atom(QXcbAtom::CLIP_TEMPORARY);
485         bool allow_incr = property != motif_clip_temporary;
486         // This 'bool' can be removed once there is a proper fix for QTBUG-32853
487         if (m_clipboard_closing)
488             allow_incr = false;
489 
490         if (data.size() > m_maxPropertyRequestDataBytes && allow_incr) {
491             long bytes = data.size();
492             xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property,
493                                 atom(QXcbAtom::INCR), 32, 1, (const void *)&bytes);
494             auto transaction = new QXcbClipboardTransaction(this, window, property, data, atomFormat, dataFormat);
495             m_transactions.insert(window, transaction);
496             return property;
497         }
498 
499         // make sure we can perform the XChangeProperty in a single request
500         if (data.size() > m_maxPropertyRequestDataBytes)
501             return XCB_NONE; // ### perhaps use several XChangeProperty calls w/ PropModeAppend?
502         int dataSize = data.size() / (dataFormat / 8);
503         // use a single request to transfer data
504         xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, atomFormat,
505                             dataFormat, dataSize, (const void *)data.constData());
506     }
507     return property;
508 }
509 
handleSelectionClearRequest(xcb_selection_clear_event_t * event)510 void QXcbClipboard::handleSelectionClearRequest(xcb_selection_clear_event_t *event)
511 {
512     QClipboard::Mode mode = modeForAtom(event->selection);
513     if (mode > QClipboard::Selection)
514         return;
515 
516     // ignore the event if it was generated before we gained selection ownership
517     if (m_timestamp[mode] != XCB_CURRENT_TIME && event->time <= m_timestamp[mode])
518         return;
519 
520 //    DEBUG("QClipboard: new selection owner 0x%lx at time %lx (ours %lx)",
521 //          XGetSelectionOwner(dpy, XA_PRIMARY),
522 //          xevent->xselectionclear.time, d->timestamp);
523 
524     xcb_window_t newOwner = getSelectionOwner(event->selection);
525 
526     /* If selection ownership was given up voluntarily from QClipboard::clear(), then we do nothing here
527     since its already handled in setMimeData. Otherwise, the event must have come from another client
528     as a result of a call to xcb_set_selection_owner in which case we need to delete the local mime data
529     */
530     if (newOwner != XCB_NONE) {
531         if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection])
532             delete m_clientClipboard[mode];
533         m_clientClipboard[mode] = nullptr;
534         m_timestamp[mode] = XCB_CURRENT_TIME;
535     }
536 }
537 
handleSelectionRequest(xcb_selection_request_event_t * req)538 void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
539 {
540     if (requestor() && req->requestor == requestor()) {
541         qWarning("QXcbClipboard: Selection request should be caught before");
542         return;
543     }
544 
545     q_padded_xcb_event<xcb_selection_notify_event_t> event = {};
546     event.response_type = XCB_SELECTION_NOTIFY;
547     event.requestor = req->requestor;
548     event.selection = req->selection;
549     event.target    = req->target;
550     event.property  = XCB_NONE;
551     event.time      = req->time;
552 
553     QMimeData *d;
554     QClipboard::Mode mode = modeForAtom(req->selection);
555     if (mode > QClipboard::Selection) {
556         qWarning() << "QXcbClipboard: Unknown selection" << connection()->atomName(req->selection);
557         xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
558         return;
559     }
560 
561     d = m_clientClipboard[mode];
562 
563     if (!d) {
564         qWarning("QXcbClipboard: Cannot transfer data, no data available");
565         xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
566         return;
567     }
568 
569     if (m_timestamp[mode] == XCB_CURRENT_TIME // we don't own the selection anymore
570             || (req->time != XCB_CURRENT_TIME && req->time < m_timestamp[mode])) {
571         qWarning("QXcbClipboard: SelectionRequest too old");
572         xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
573         return;
574     }
575 
576     xcb_atom_t targetsAtom = atom(QXcbAtom::TARGETS);
577     xcb_atom_t multipleAtom = atom(QXcbAtom::MULTIPLE);
578     xcb_atom_t timestampAtom = atom(QXcbAtom::TIMESTAMP);
579 
580     struct AtomPair { xcb_atom_t target; xcb_atom_t property; } *multi = nullptr;
581     xcb_atom_t multi_type = XCB_NONE;
582     int multi_format = 0;
583     int nmulti = 0;
584     int imulti = -1;
585     bool multi_writeback = false;
586 
587     if (req->target == multipleAtom) {
588         QByteArray multi_data;
589         if (req->property == XCB_NONE
590             || !clipboardReadProperty(req->requestor, req->property, false, &multi_data,
591                                            nullptr, &multi_type, &multi_format)
592             || multi_format != 32) {
593             // MULTIPLE property not formatted correctly
594             xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
595             return;
596         }
597         nmulti = multi_data.size()/sizeof(*multi);
598         multi = new AtomPair[nmulti];
599         memcpy(multi,multi_data.data(),multi_data.size());
600         imulti = 0;
601     }
602 
603     for (; imulti < nmulti; ++imulti) {
604         xcb_atom_t target;
605         xcb_atom_t property;
606 
607         if (multi) {
608             target = multi[imulti].target;
609             property = multi[imulti].property;
610         } else {
611             target = req->target;
612             property = req->property;
613             if (property == XCB_NONE) // obsolete client
614                 property = target;
615         }
616 
617         xcb_atom_t ret = XCB_NONE;
618         if (target == XCB_NONE || property == XCB_NONE) {
619             ;
620         } else if (target == timestampAtom) {
621             if (m_timestamp[mode] != XCB_CURRENT_TIME) {
622                 xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, req->requestor,
623                                     property, XCB_ATOM_INTEGER, 32, 1, &m_timestamp[mode]);
624                 ret = property;
625             } else {
626                 qWarning("QXcbClipboard: Invalid data timestamp");
627             }
628         } else if (target == targetsAtom) {
629             ret = sendTargetsSelection(d, req->requestor, property);
630         } else {
631             ret = sendSelection(d, target, req->requestor, property);
632         }
633 
634         if (nmulti > 0) {
635             if (ret == XCB_NONE) {
636                 multi[imulti].property = XCB_NONE;
637                 multi_writeback = true;
638             }
639         } else {
640             event.property = ret;
641             break;
642         }
643     }
644 
645     if (nmulti > 0) {
646         if (multi_writeback) {
647             // according to ICCCM 2.6.2 says to put None back
648             // into the original property on the requestor window
649             xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, req->requestor, req->property,
650                                 multi_type, 32, nmulti*2, (const void *)multi);
651         }
652 
653         delete [] multi;
654         event.property = req->property;
655     }
656 
657     // send selection notify to requestor
658     xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
659 }
660 
handleXFixesSelectionRequest(xcb_xfixes_selection_notify_event_t * event)661 void QXcbClipboard::handleXFixesSelectionRequest(xcb_xfixes_selection_notify_event_t *event)
662 {
663     QClipboard::Mode mode = modeForAtom(event->selection);
664     if (mode > QClipboard::Selection)
665         return;
666 
667     // Note1: Here we care only about the xfixes events that come from other processes.
668     // Note2: If the QClipboard::clear() is issued, event->owner is XCB_NONE,
669     // so we check selection_timestamp to not handle our own QClipboard::clear().
670     if (event->owner != owner() && event->selection_timestamp > m_timestamp[mode]) {
671         if (!m_xClipboard[mode]) {
672             m_xClipboard[mode].reset(new QXcbClipboardMime(mode, this));
673         } else {
674             m_xClipboard[mode]->reset();
675         }
676         emitChanged(mode);
677     } else if (event->subtype == XCB_XFIXES_SELECTION_EVENT_SELECTION_CLIENT_CLOSE ||
678                event->subtype == XCB_XFIXES_SELECTION_EVENT_SELECTION_WINDOW_DESTROY)
679         emitChanged(mode);
680 }
681 
clipboardReadProperty(xcb_window_t win,xcb_atom_t property,bool deleteProperty,QByteArray * buffer,int * size,xcb_atom_t * type,int * format)682 bool QXcbClipboard::clipboardReadProperty(xcb_window_t win, xcb_atom_t property, bool deleteProperty, QByteArray *buffer, int *size, xcb_atom_t *type, int *format)
683 {
684     xcb_atom_t   dummy_type;
685     int    dummy_format;
686 
687     if (!type)                                // allow null args
688         type = &dummy_type;
689     if (!format)
690         format = &dummy_format;
691 
692     // Don't read anything, just get the size of the property data
693     auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, win, property, XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
694     if (!reply || reply->type == XCB_NONE) {
695         buffer->resize(0);
696         return false;
697     }
698     *type = reply->type;
699     *format = reply->format;
700 
701     auto bytes_left = reply->bytes_after;
702 
703     int  offset = 0, buffer_offset = 0;
704 
705     int newSize = bytes_left;
706     buffer->resize(newSize);
707 
708     bool ok = (buffer->size() == newSize);
709 
710     if (ok && newSize) {
711         // could allocate buffer
712 
713         while (bytes_left) {
714             // more to read...
715 
716             reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, win, property,
717                                 XCB_GET_PROPERTY_TYPE_ANY, offset, m_maxPropertyRequestDataBytes / 4);
718             if (!reply || reply->type == XCB_NONE)
719                 break;
720 
721             *type = reply->type;
722             *format = reply->format;
723             bytes_left = reply->bytes_after;
724             char *data = (char *)xcb_get_property_value(reply.get());
725             int length = xcb_get_property_value_length(reply.get());
726 
727             // Here we check if we get a buffer overflow and tries to
728             // recover -- this shouldn't normally happen, but it doesn't
729             // hurt to be defensive
730             if ((int)(buffer_offset + length) > buffer->size()) {
731                 qWarning("QXcbClipboard: buffer overflow");
732                 length = buffer->size() - buffer_offset;
733 
734                 // escape loop
735                 bytes_left = 0;
736             }
737 
738             memcpy(buffer->data() + buffer_offset, data, length);
739             buffer_offset += length;
740 
741             if (bytes_left) {
742                 // offset is specified in 32-bit multiples
743                 offset += length / 4;
744             }
745         }
746     }
747 
748 
749     // correct size, not 0-term.
750     if (size)
751         *size = buffer_offset;
752     if (*type == atom(QXcbAtom::INCR))
753         m_incr_receive_time = connection()->getTimestamp();
754     if (deleteProperty)
755         xcb_delete_property(xcb_connection(), win, property);
756 
757     connection()->flush();
758 
759     return ok;
760 }
761 
waitForClipboardEvent(xcb_window_t window,int type,bool checkManager)762 xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t window, int type, bool checkManager)
763 {
764     QElapsedTimer timer;
765     timer.start();
766     QXcbEventQueue *queue = connection()->eventQueue();
767     do {
768         auto e = queue->peek([window, type](xcb_generic_event_t *event, int eventType) {
769             if (eventType != type)
770                 return false;
771             if (eventType == XCB_PROPERTY_NOTIFY) {
772                 auto propertyNotify = reinterpret_cast<xcb_property_notify_event_t *>(event);
773                 return propertyNotify->window == window;
774             }
775             if (eventType == XCB_SELECTION_NOTIFY) {
776                 auto selectionNotify = reinterpret_cast<xcb_selection_notify_event_t *>(event);
777                 return selectionNotify->requestor == window;
778             }
779             return false;
780         });
781         if (e) // found the waited for event
782             return e;
783 
784         // It is safe to assume here that the pointed to node won't be re-used
785         // while we are holding the pointer to it. The nodes can be recycled
786         // only when they are dequeued, which is done only by
787         // QXcbConnection::processXcbEvents().
788         const QXcbEventNode *flushedTailNode = queue->flushedTail();
789 
790         if (checkManager) {
791             auto reply = Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom(QXcbAtom::CLIPBOARD_MANAGER));
792             if (!reply || reply->owner == XCB_NONE)
793                 return nullptr;
794         }
795 
796         // process other clipboard events, since someone is probably requesting data from us
797         auto clipboardAtom = atom(QXcbAtom::CLIPBOARD);
798         e = queue->peek([clipboardAtom](xcb_generic_event_t *event, int type) {
799             xcb_atom_t selection = XCB_ATOM_NONE;
800             if (type == XCB_SELECTION_REQUEST)
801                 selection = reinterpret_cast<xcb_selection_request_event_t *>(event)->selection;
802             else if (type == XCB_SELECTION_CLEAR)
803                 selection = reinterpret_cast<xcb_selection_clear_event_t *>(event)->selection;
804             return selection == XCB_ATOM_PRIMARY || selection == clipboardAtom;
805         });
806         if (e) {
807             connection()->handleXcbEvent(e);
808             free(e);
809         }
810 
811         connection()->flush();
812 
813         const auto elapsed = timer.elapsed();
814         if (elapsed < clipboard_timeout)
815             queue->waitForNewEvents(flushedTailNode, clipboard_timeout - elapsed);
816     } while (timer.elapsed() < clipboard_timeout);
817 
818     return nullptr;
819 }
820 
clipboardReadIncrementalProperty(xcb_window_t win,xcb_atom_t property,int nbytes,bool nullterm)821 QByteArray QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm)
822 {
823     QByteArray buf;
824     QByteArray tmp_buf;
825     bool alloc_error = false;
826     int  length;
827     int  offset = 0;
828     xcb_timestamp_t prev_time = m_incr_receive_time;
829 
830     if (nbytes > 0) {
831         // Reserve buffer + zero-terminator (for text data)
832         // We want to complete the INCR transfer even if we cannot
833         // allocate more memory
834         buf.resize(nbytes+1);
835         alloc_error = buf.size() != nbytes+1;
836     }
837 
838     QElapsedTimer timer;
839     timer.start();
840     for (;;) {
841         connection()->flush();
842         xcb_generic_event_t *ge = waitForClipboardEvent(win, XCB_PROPERTY_NOTIFY);
843         if (!ge)
844             break;
845         xcb_property_notify_event_t *event = (xcb_property_notify_event_t *)ge;
846         QScopedPointer<xcb_property_notify_event_t, QScopedPointerPodDeleter> guard(event);
847 
848         if (event->atom != property
849                 || event->state != XCB_PROPERTY_NEW_VALUE
850                     || event->time < prev_time)
851             continue;
852         prev_time = event->time;
853 
854         if (clipboardReadProperty(win, property, true, &tmp_buf, &length, nullptr, nullptr)) {
855             if (length == 0) {                // no more data, we're done
856                 if (nullterm) {
857                     buf.resize(offset+1);
858                     buf[offset] = '\0';
859                 } else {
860                     buf.resize(offset);
861                 }
862                 return buf;
863             } else if (!alloc_error) {
864                 if (offset+length > (int)buf.size()) {
865                     buf.resize(offset+length+65535);
866                     if (buf.size() != offset+length+65535) {
867                         alloc_error = true;
868                         length = buf.size() - offset;
869                     }
870                 }
871                 memcpy(buf.data()+offset, tmp_buf.constData(), length);
872                 tmp_buf.resize(0);
873                 offset += length;
874             }
875         }
876 
877         const auto elapsed = timer.elapsed();
878         if (elapsed > clipboard_timeout)
879             break;
880     }
881 
882     // timed out ... create a new requestor window, otherwise the requestor
883     // could consider next request to be still part of this timed out request
884     setRequestor(0);
885 
886     return QByteArray();
887 }
888 
getDataInFormat(xcb_atom_t modeAtom,xcb_atom_t fmtAtom)889 QByteArray QXcbClipboard::getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtAtom)
890 {
891     return getSelection(modeAtom, fmtAtom, atom(QXcbAtom::_QT_SELECTION));
892 }
893 
getSelection(xcb_atom_t selection,xcb_atom_t target,xcb_atom_t property,xcb_timestamp_t time)894 QByteArray QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time)
895 {
896     QByteArray buf;
897     xcb_window_t win = requestor();
898 
899     if (time == 0) time = connection()->time();
900 
901     xcb_delete_property(xcb_connection(), win, property);
902     xcb_convert_selection(xcb_connection(), win, selection, target, property, time);
903 
904     connection()->sync();
905 
906     xcb_generic_event_t *ge = waitForClipboardEvent(win, XCB_SELECTION_NOTIFY);
907     bool no_selection = !ge || ((xcb_selection_notify_event_t *)ge)->property == XCB_NONE;
908     free(ge);
909 
910     if (no_selection)
911         return buf;
912 
913     xcb_atom_t type;
914     if (clipboardReadProperty(win, property, true, &buf, nullptr, &type, nullptr)) {
915         if (type == atom(QXcbAtom::INCR)) {
916             int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
917             buf = clipboardReadIncrementalProperty(win, property, nbytes, false);
918         }
919     }
920 
921     return buf;
922 }
923 
924 #endif // QT_NO_CLIPBOARD
925 
926 QT_END_NAMESPACE
927 
928 #include "qxcbclipboard.moc"
929