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