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

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