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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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