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(atom: QXcbAtom::CLIPBOARD);
71 break;
72
73 default:
74 qCWarning(lcQpaClipboard, "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(atom: 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, fmtatom: m_clipboard->atom(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(connection: m_clipboard->connection(), a: targets[i]);
111 if (!formatList.contains(str: format))
112 that->formatList.append(t: 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(str: format);
124 }
125
126 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(t: targets[i]);
140
141 QByteArray encoding;
142 xcb_atom_t fmtatom = mimeAtomForFormat(connection: m_clipboard->connection(), format: fmt, requestedType, atoms, requestedEncoding: &encoding);
143
144 if (fmtatom == 0)
145 return QVariant();
146
147 return mimeConvertToFormat(connection: m_clipboard->connection(), a: fmtatom, data: m_clipboard->getDataInFormat(modeAtom, fmtatom), format: fmt, requestedType, encoding);
148 }
149private:
150
151 xcb_atom_t modeAtom;
152 QXcbClipboard *m_clipboard;
153 QStringList formatList;
154 QByteArray format_atoms;
155};
156
157QXcbClipboardTransaction::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(c: m_clipboard->xcb_connection(), window: m_window,
163 value_mask: XCB_CW_EVENT_MASK, value_list: values);
164
165 m_abortTimerId = startTimer(interval: m_clipboard->clipboardTimeout());
166}
167
168QXcbClipboardTransaction::~QXcbClipboardTransaction()
169{
170 if (m_abortTimerId)
171 killTimer(id: m_abortTimerId);
172 m_abortTimerId = 0;
173 m_clipboard->removeTransaction(window: m_window);
174}
175
176bool 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(id: m_abortTimerId);
184 m_abortTimerId = startTimer(interval: 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(a: uint(increment), b: 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(c: m_clipboard->xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window,
196 property: m_property, type: m_target, format: m_format, data_len: dataSize, data: 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(c: m_clipboard->xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window,
202 property: m_property, type: m_target, format: m_format, data_len: 0, data: nullptr);
203
204 const quint32 values[] = { XCB_EVENT_MASK_NO_EVENT };
205 xcb_change_window_attributes(c: m_clipboard->xcb_connection(), window: m_window,
206 value_mask: XCB_CW_EVENT_MASK, value_list: values);
207 delete this; // self destroy
208 }
209 return true;
210}
211
212
213void 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
223const int QXcbClipboard::clipboard_timeout = 5000;
224
225QXcbClipboard::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(c: xcb_connection(), window: m_owner, selection: XCB_ATOM_PRIMARY, event_mask: mask);
241 xcb_xfixes_select_selection_input_checked(c: xcb_connection(), window: m_owner, selection: atom(atom: QXcbAtom::CLIPBOARD), event_mask: mask);
242 }
243
244 // xcb_change_property_request_t and xcb_get_property_request_t are the same size
245 m_maxPropertyRequestDataBytes = connection()->maxRequestDataBytes(requestSize: sizeof(xcb_change_property_request_t));
246}
247
248QXcbClipboard::~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(c: xcb_connection(), window: m_owner, property: atom(atom: QXcbAtom::_QT_SELECTION));
260 xcb_convert_selection(c: xcb_connection(), requestor: m_owner, selection: atom(atom: QXcbAtom::CLIPBOARD_MANAGER), target: atom(atom: QXcbAtom::SAVE_TARGETS),
261 property: atom(atom: QXcbAtom::_QT_SELECTION), time: connection()->time());
262 connection()->sync();
263
264 // waiting until the clipboard manager fetches the content.
265 if (auto event = waitForClipboardEvent(window: m_owner, XCB_SELECTION_NOTIFY, checkManager: true)) {
266 free(ptr: event);
267 } else {
268 qCWarning(lcQpaClipboard, "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
279bool 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(key: propertyNotify->window);
286 if (it == m_transactions.constEnd())
287 return false;
288
289 return (*it)->updateIncrementalProperty(event: propertyNotify);
290}
291
292xcb_window_t QXcbClipboard::getSelectionOwner(xcb_atom_t atom) const
293{
294 return connection()->getSelectionOwner(atom);
295}
296
297xcb_atom_t QXcbClipboard::atomForMode(QClipboard::Mode mode) const
298{
299 if (mode == QClipboard::Clipboard)
300 return atom(atom: QXcbAtom::CLIPBOARD);
301 if (mode == QClipboard::Selection)
302 return XCB_ATOM_PRIMARY;
303 return XCB_NONE;
304}
305
306QClipboard::Mode QXcbClipboard::modeForAtom(xcb_atom_t a) const
307{
308 if (a == XCB_ATOM_PRIMARY)
309 return QClipboard::Selection;
310 if (a == atom(atom: QXcbAtom::CLIPBOARD))
311 return QClipboard::Clipboard;
312 // not supported enum value, used to detect errors
313 return QClipboard::FindBuffer;
314}
315
316
317QMimeData * QXcbClipboard::mimeData(QClipboard::Mode mode)
318{
319 if (mode > QClipboard::Selection)
320 return nullptr;
321
322 xcb_window_t clipboardOwner = getSelectionOwner(atom: atomForMode(mode));
323 if (clipboardOwner == owner()) {
324 return m_clientClipboard[mode];
325 } else {
326 if (!m_xClipboard[mode])
327 m_xClipboard[mode].reset(other: new QXcbClipboardMime(mode, this));
328
329 return m_xClipboard[mode].data();
330 }
331}
332
333void 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 *>(object: 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(c: xcb_connection(), owner: newOwner, selection: modeAtom, time: connection()->time());
372
373 if (getSelectionOwner(atom: modeAtom) != newOwner) {
374 qCWarning(lcQpaClipboard, "QXcbClipboard::setMimeData: Cannot set X11 selection owner");
375 }
376
377 emitChanged(mode);
378}
379
380bool QXcbClipboard::supportsMode(QClipboard::Mode mode) const
381{
382 if (mode <= QClipboard::Selection)
383 return true;
384 return false;
385}
386
387bool 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
397QXcbScreen *QXcbClipboard::screen() const
398{
399 return connection()->primaryScreen();
400}
401
402xcb_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(c: xcb_connection());
411 xcb_create_window(c: xcb_connection(),
412 XCB_COPY_FROM_PARENT, // depth -- same as root
413 wid: window, // window id
414 parent: platformScreen->screen()->root, // parent window id
415 x, y, width: w, height: h,
416 border_width: 0, // border width
417 class: XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class
418 visual: platformScreen->screen()->root_visual, // visual
419 value_mask: 0, // value mask
420 value_list: nullptr); // value list
421
422 QXcbWindow::setWindowTitle(conn: connection(), window,
423 QStringLiteral("Qt Clipboard Requestor Window"));
424
425 uint32_t mask = XCB_EVENT_MASK_PROPERTY_CHANGE;
426 xcb_change_window_attributes(c: xcb_connection(), window, value_mask: XCB_CW_EVENT_MASK, value_list: &mask);
427
428 that->setRequestor(window);
429 }
430 return m_requestor;
431}
432
433void QXcbClipboard::setRequestor(xcb_window_t window)
434{
435 if (m_requestor != XCB_NONE) {
436 xcb_destroy_window(c: xcb_connection(), window: m_requestor);
437 }
438 m_requestor = window;
439}
440
441xcb_window_t QXcbClipboard::owner() const
442{
443 return m_owner;
444}
445
446xcb_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(data: d);
450 for (int i = 0; i < formats.size(); ++i) {
451 QVector<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(connection: connection(), format: formats.at(i));
452 for (int j = 0; j < atoms.size(); ++j) {
453 if (!types.contains(t: atoms.at(i: j)))
454 types.append(t: atoms.at(i: j));
455 }
456 }
457 types.append(t: atom(atom: QXcbAtom::TARGETS));
458 types.append(t: atom(atom: QXcbAtom::MULTIPLE));
459 types.append(t: atom(atom: QXcbAtom::TIMESTAMP));
460 types.append(t: atom(atom: QXcbAtom::SAVE_TARGETS));
461
462 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window, property, type: XCB_ATOM_ATOM,
463 format: 32, data_len: types.size(), data: (const void *)types.constData());
464 return property;
465}
466
467xcb_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: connection(), a: 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: connection(), a: target, mimeData: d, data: &data, atomFormat: &atomFormat, dataFormat: &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(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(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window, property,
493 type: atom(atom: QXcbAtom::INCR), format: 32, data_len: 1, data: (const void *)&bytes);
494 auto transaction = new QXcbClipboardTransaction(this, window, property, data, atomFormat, dataFormat);
495 m_transactions.insert(key: window, value: 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(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window, property, type: atomFormat,
505 format: dataFormat, data_len: dataSize, data: (const void *)data.constData());
506 }
507 return property;
508}
509
510void QXcbClipboard::handleSelectionClearRequest(xcb_selection_clear_event_t *event)
511{
512 QClipboard::Mode mode = modeForAtom(a: 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(atom: 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
538void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
539{
540 if (requestor() && req->requestor == requestor()) {
541 qCWarning(lcQpaClipboard, "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(a: req->selection);
555 if (mode > QClipboard::Selection) {
556 qCWarning(lcQpaClipboard, "QXcbClipboard: Unknown selection %s",
557 connection()->atomName(req->selection).constData());
558 xcb_send_event(c: xcb_connection(), propagate: false, destination: req->requestor, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&event);
559 return;
560 }
561
562 d = m_clientClipboard[mode];
563
564 if (!d) {
565 qCWarning(lcQpaClipboard, "QXcbClipboard: Cannot transfer data, no data available");
566 xcb_send_event(c: xcb_connection(), propagate: false, destination: req->requestor, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&event);
567 return;
568 }
569
570 if (m_timestamp[mode] == XCB_CURRENT_TIME // we don't own the selection anymore
571 || (req->time != XCB_CURRENT_TIME && req->time < m_timestamp[mode])) {
572 qCDebug(lcQpaClipboard, "QXcbClipboard: SelectionRequest too old");
573 xcb_send_event(c: xcb_connection(), propagate: false, destination: req->requestor, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&event);
574 return;
575 }
576
577 xcb_atom_t targetsAtom = atom(atom: QXcbAtom::TARGETS);
578 xcb_atom_t multipleAtom = atom(atom: QXcbAtom::MULTIPLE);
579 xcb_atom_t timestampAtom = atom(atom: QXcbAtom::TIMESTAMP);
580
581 struct AtomPair { xcb_atom_t target; xcb_atom_t property; } *multi = nullptr;
582 xcb_atom_t multi_type = XCB_NONE;
583 int multi_format = 0;
584 int nmulti = 0;
585 int imulti = -1;
586 bool multi_writeback = false;
587
588 if (req->target == multipleAtom) {
589 QByteArray multi_data;
590 if (req->property == XCB_NONE
591 || !clipboardReadProperty(win: req->requestor, property: req->property, deleteProperty: false, buffer: &multi_data,
592 size: nullptr, type: &multi_type, format: &multi_format)
593 || multi_format != 32) {
594 // MULTIPLE property not formatted correctly
595 xcb_send_event(c: xcb_connection(), propagate: false, destination: req->requestor, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&event);
596 return;
597 }
598 nmulti = multi_data.size()/sizeof(*multi);
599 multi = new AtomPair[nmulti];
600 memcpy(dest: multi,src: multi_data.data(),n: multi_data.size());
601 imulti = 0;
602 }
603
604 for (; imulti < nmulti; ++imulti) {
605 xcb_atom_t target;
606 xcb_atom_t property;
607
608 if (multi) {
609 target = multi[imulti].target;
610 property = multi[imulti].property;
611 } else {
612 target = req->target;
613 property = req->property;
614 if (property == XCB_NONE) // obsolete client
615 property = target;
616 }
617
618 xcb_atom_t ret = XCB_NONE;
619 if (target == XCB_NONE || property == XCB_NONE) {
620 ;
621 } else if (target == timestampAtom) {
622 if (m_timestamp[mode] != XCB_CURRENT_TIME) {
623 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: req->requestor,
624 property, type: XCB_ATOM_INTEGER, format: 32, data_len: 1, data: &m_timestamp[mode]);
625 ret = property;
626 } else {
627 qCWarning(lcQpaClipboard, "QXcbClipboard: Invalid data timestamp");
628 }
629 } else if (target == targetsAtom) {
630 ret = sendTargetsSelection(d, window: req->requestor, property);
631 } else {
632 ret = sendSelection(d, target, window: req->requestor, property);
633 }
634
635 if (nmulti > 0) {
636 if (ret == XCB_NONE) {
637 multi[imulti].property = XCB_NONE;
638 multi_writeback = true;
639 }
640 } else {
641 event.property = ret;
642 break;
643 }
644 }
645
646 if (nmulti > 0) {
647 if (multi_writeback) {
648 // according to ICCCM 2.6.2 says to put None back
649 // into the original property on the requestor window
650 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: req->requestor, property: req->property,
651 type: multi_type, format: 32, data_len: nmulti*2, data: (const void *)multi);
652 }
653
654 delete [] multi;
655 event.property = req->property;
656 }
657
658 // send selection notify to requestor
659 xcb_send_event(c: xcb_connection(), propagate: false, destination: req->requestor, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&event);
660}
661
662void QXcbClipboard::handleXFixesSelectionRequest(xcb_xfixes_selection_notify_event_t *event)
663{
664 QClipboard::Mode mode = modeForAtom(a: event->selection);
665 if (mode > QClipboard::Selection)
666 return;
667
668 // Note1: Here we care only about the xfixes events that come from other processes.
669 // Note2: If the QClipboard::clear() is issued, event->owner is XCB_NONE,
670 // so we check selection_timestamp to not handle our own QClipboard::clear().
671 if (event->owner != owner() && event->selection_timestamp > m_timestamp[mode]) {
672 if (!m_xClipboard[mode]) {
673 m_xClipboard[mode].reset(other: new QXcbClipboardMime(mode, this));
674 } else {
675 m_xClipboard[mode]->reset();
676 }
677 emitChanged(mode);
678 } else if (event->subtype == XCB_XFIXES_SELECTION_EVENT_SELECTION_CLIENT_CLOSE ||
679 event->subtype == XCB_XFIXES_SELECTION_EVENT_SELECTION_WINDOW_DESTROY)
680 emitChanged(mode);
681}
682
683bool QXcbClipboard::clipboardReadProperty(xcb_window_t win, xcb_atom_t property, bool deleteProperty, QByteArray *buffer, int *size, xcb_atom_t *type, int *format)
684{
685 xcb_atom_t dummy_type;
686 int dummy_format;
687
688 if (!type) // allow null args
689 type = &dummy_type;
690 if (!format)
691 format = &dummy_format;
692
693 // Don't read anything, just get the size of the property data
694 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, win, property, XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
695 if (!reply || reply->type == XCB_NONE) {
696 buffer->resize(size: 0);
697 return false;
698 }
699 *type = reply->type;
700 *format = reply->format;
701
702 auto bytes_left = reply->bytes_after;
703
704 int offset = 0, buffer_offset = 0;
705
706 int newSize = bytes_left;
707 buffer->resize(size: newSize);
708
709 bool ok = (buffer->size() == newSize);
710
711 if (ok && newSize) {
712 // could allocate buffer
713
714 while (bytes_left) {
715 // more to read...
716
717 reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, win, property,
718 XCB_GET_PROPERTY_TYPE_ANY, offset, m_maxPropertyRequestDataBytes / 4);
719 if (!reply || reply->type == XCB_NONE)
720 break;
721
722 *type = reply->type;
723 *format = reply->format;
724 bytes_left = reply->bytes_after;
725 char *data = (char *)xcb_get_property_value(R: reply.get());
726 int length = xcb_get_property_value_length(R: reply.get());
727
728 // Here we check if we get a buffer overflow and tries to
729 // recover -- this shouldn't normally happen, but it doesn't
730 // hurt to be defensive
731 if ((int)(buffer_offset + length) > buffer->size()) {
732 qCWarning(lcQpaClipboard, "QXcbClipboard: buffer overflow");
733 length = buffer->size() - buffer_offset;
734
735 // escape loop
736 bytes_left = 0;
737 }
738
739 memcpy(dest: buffer->data() + buffer_offset, src: data, n: length);
740 buffer_offset += length;
741
742 if (bytes_left) {
743 // offset is specified in 32-bit multiples
744 offset += length / 4;
745 }
746 }
747 }
748
749
750 // correct size, not 0-term.
751 if (size)
752 *size = buffer_offset;
753 if (*type == atom(atom: QXcbAtom::INCR))
754 m_incr_receive_time = connection()->getTimestamp();
755 if (deleteProperty)
756 xcb_delete_property(c: xcb_connection(), window: win, property);
757
758 connection()->flush();
759
760 return ok;
761}
762
763xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t window, int type, bool checkManager)
764{
765 QElapsedTimer timer;
766 timer.start();
767 QXcbEventQueue *queue = connection()->eventQueue();
768 do {
769 auto e = queue->peek(peeker: [window, type](xcb_generic_event_t *event, int eventType) {
770 if (eventType != type)
771 return false;
772 if (eventType == XCB_PROPERTY_NOTIFY) {
773 auto propertyNotify = reinterpret_cast<xcb_property_notify_event_t *>(event);
774 return propertyNotify->window == window;
775 }
776 if (eventType == XCB_SELECTION_NOTIFY) {
777 auto selectionNotify = reinterpret_cast<xcb_selection_notify_event_t *>(event);
778 return selectionNotify->requestor == window;
779 }
780 return false;
781 });
782 if (e) // found the waited for event
783 return e;
784
785 // It is safe to assume here that the pointed to node won't be re-used
786 // while we are holding the pointer to it. The nodes can be recycled
787 // only when they are dequeued, which is done only by
788 // QXcbConnection::processXcbEvents().
789 const QXcbEventNode *flushedTailNode = queue->flushedTail();
790
791 if (checkManager) {
792 auto reply = Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom(QXcbAtom::CLIPBOARD_MANAGER));
793 if (!reply || reply->owner == XCB_NONE)
794 return nullptr;
795 }
796
797 // process other clipboard events, since someone is probably requesting data from us
798 auto clipboardAtom = atom(atom: QXcbAtom::CLIPBOARD);
799 e = queue->peek(peeker: [clipboardAtom](xcb_generic_event_t *event, int type) {
800 xcb_atom_t selection = XCB_ATOM_NONE;
801 if (type == XCB_SELECTION_REQUEST)
802 selection = reinterpret_cast<xcb_selection_request_event_t *>(event)->selection;
803 else if (type == XCB_SELECTION_CLEAR)
804 selection = reinterpret_cast<xcb_selection_clear_event_t *>(event)->selection;
805 return selection == XCB_ATOM_PRIMARY || selection == clipboardAtom;
806 });
807 if (e) {
808 connection()->handleXcbEvent(event: e);
809 free(ptr: e);
810 }
811
812 connection()->flush();
813
814 const auto elapsed = timer.elapsed();
815 if (elapsed < clipboard_timeout)
816 queue->waitForNewEvents(sinceFlushedTail: flushedTailNode, time: clipboard_timeout - elapsed);
817 } while (timer.elapsed() < clipboard_timeout);
818
819 return nullptr;
820}
821
822QByteArray QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm)
823{
824 QByteArray buf;
825 QByteArray tmp_buf;
826 bool alloc_error = false;
827 int length;
828 int offset = 0;
829 xcb_timestamp_t prev_time = m_incr_receive_time;
830
831 if (nbytes > 0) {
832 // Reserve buffer + zero-terminator (for text data)
833 // We want to complete the INCR transfer even if we cannot
834 // allocate more memory
835 buf.resize(size: nbytes+1);
836 alloc_error = buf.size() != nbytes+1;
837 }
838
839 QElapsedTimer timer;
840 timer.start();
841 for (;;) {
842 connection()->flush();
843 xcb_generic_event_t *ge = waitForClipboardEvent(window: win, XCB_PROPERTY_NOTIFY);
844 if (!ge)
845 break;
846 xcb_property_notify_event_t *event = (xcb_property_notify_event_t *)ge;
847 QScopedPointer<xcb_property_notify_event_t, QScopedPointerPodDeleter> guard(event);
848
849 if (event->atom != property
850 || event->state != XCB_PROPERTY_NEW_VALUE
851 || event->time < prev_time)
852 continue;
853 prev_time = event->time;
854
855 if (clipboardReadProperty(win, property, deleteProperty: true, buffer: &tmp_buf, size: &length, type: nullptr, format: nullptr)) {
856 if (length == 0) { // no more data, we're done
857 if (nullterm) {
858 buf.resize(size: offset+1);
859 buf[offset] = '\0';
860 } else {
861 buf.resize(size: offset);
862 }
863 return buf;
864 } else if (!alloc_error) {
865 if (offset+length > (int)buf.size()) {
866 buf.resize(size: offset+length+65535);
867 if (buf.size() != offset+length+65535) {
868 alloc_error = true;
869 length = buf.size() - offset;
870 }
871 }
872 memcpy(dest: buf.data()+offset, src: tmp_buf.constData(), n: length);
873 tmp_buf.resize(size: 0);
874 offset += length;
875 }
876 }
877
878 const auto elapsed = timer.elapsed();
879 if (elapsed > clipboard_timeout)
880 break;
881 }
882
883 // timed out ... create a new requestor window, otherwise the requestor
884 // could consider next request to be still part of this timed out request
885 setRequestor(0);
886
887 return QByteArray();
888}
889
890QByteArray QXcbClipboard::getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtAtom)
891{
892 return getSelection(selection: modeAtom, target: fmtAtom, property: atom(atom: QXcbAtom::_QT_SELECTION));
893}
894
895QByteArray QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time)
896{
897 QByteArray buf;
898 xcb_window_t win = requestor();
899
900 if (time == 0) time = connection()->time();
901
902 xcb_delete_property(c: xcb_connection(), window: win, property);
903 xcb_convert_selection(c: xcb_connection(), requestor: win, selection, target, property, time);
904
905 connection()->sync();
906
907 xcb_generic_event_t *ge = waitForClipboardEvent(window: win, XCB_SELECTION_NOTIFY);
908 bool no_selection = !ge || ((xcb_selection_notify_event_t *)ge)->property == XCB_NONE;
909 free(ptr: ge);
910
911 if (no_selection)
912 return buf;
913
914 xcb_atom_t type;
915 if (clipboardReadProperty(win, property, deleteProperty: true, buffer: &buf, size: nullptr, type: &type, format: nullptr)) {
916 if (type == atom(atom: QXcbAtom::INCR)) {
917 int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
918 buf = clipboardReadIncrementalProperty(win, property, nbytes, nullterm: false);
919 }
920 }
921
922 return buf;
923}
924
925#endif // QT_NO_CLIPBOARD
926
927QT_END_NAMESPACE
928
929#include "qxcbclipboard.moc"
930

source code of qtbase/src/plugins/platforms/xcb/qxcbclipboard.cpp