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