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

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