Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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
17
18#ifndef QT_NO_CLIPBOARD
19
21{
23public:
25 : QXcbMime()
26 , m_clipboard(clipboard)
27 {
28 switch (mode) {
30 modeAtom = XCB_ATOM_PRIMARY;
31 break;
32
34 modeAtom = m_clipboard->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(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, m_clipboard->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(m_clipboard->connection(), targets[i]);
75 if (!formatList.contains(format))
76 that->formatList.append(format);
77 }
78 }
79 }
80
81 return formatList;
82 }
83
84 bool hasFormat_sys(const QString &format) const override
85 {
87 return list.contains(format);
88 }
89
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(size);
102 for (int i = 0; i < size; ++i)
103 atoms.append(targets[i]);
104
105 bool hasUtf8 = false;
106 xcb_atom_t fmtatom = mimeAtomForFormat(m_clipboard->connection(), fmt, requestedType, atoms, &hasUtf8);
107
108 if (fmtatom == 0)
109 return QVariant();
110
111 return mimeConvertToFormat(m_clipboard->connection(), fmtatom, m_clipboard->getDataInFormat(modeAtom, fmtatom), fmt, requestedType, hasUtf8);
112 }
113private:
114
115 xcb_atom_t modeAtom;
116 QXcbClipboard *m_clipboard;
117 QStringList formatList;
118 QByteArray format_atoms;
119};
120
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(m_clipboard->xcb_connection(), m_window,
127 XCB_CW_EVENT_MASK, values);
128
129 m_abortTimerId = startTimer(std::chrono::milliseconds{m_clipboard->clipboardTimeout()});
130}
131
133{
134 if (m_abortTimerId)
135 killTimer(m_abortTimerId);
136 m_abortTimerId = 0;
137 m_clipboard->removeTransaction(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(m_abortTimerId);
148 m_abortTimerId = startTimer(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(uint(increment), 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(m_clipboard->xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
160 m_property, m_target, m_format, dataSize, 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(m_clipboard->xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
166 m_property, m_target, m_format, 0, nullptr);
167
168 const quint32 values[] = { XCB_EVENT_MASK_NO_EVENT };
169 xcb_change_window_attributes(m_clipboard->xcb_connection(), m_window,
170 XCB_CW_EVENT_MASK, values);
171 delete this; // self destroy
172 }
173 return true;
174}
175
176
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
191{
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(xcb_connection(), connection()->qtSelectionOwner(),
204 XCB_ATOM_PRIMARY, mask);
205 xcb_xfixes_select_selection_input_checked(xcb_connection(), connection()->qtSelectionOwner(),
207 }
208
209 // xcb_change_property_request_t and xcb_get_property_request_t are the same size
210 m_maxPropertyRequestDataBytes = connection()->maxRequestDataBytes(sizeof(xcb_change_property_request_t));
211}
212
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(QXcbAtom::AtomCLIPBOARD_MANAGER)) != XCB_NONE) {
222 // we delete the property so the manager saves all TARGETS.
223 xcb_delete_property(xcb_connection(), connection()->qtSelectionOwner(),
225 xcb_convert_selection(xcb_connection(), connection()->qtSelectionOwner(),
228 connection()->sync();
229
230 // waiting until the clipboard manager fetches the content.
231 if (auto event = waitForClipboardEvent(connection()->qtSelectionOwner(),
232 XCB_SELECTION_NOTIFY, true)) {
233 free(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(propertyNotify->window);
253 if (it == m_transactions.constEnd())
254 return false;
255
256 return (*it)->updateIncrementalProperty(propertyNotify);
257}
258
259xcb_atom_t QXcbClipboard::atomForMode(QClipboard::Mode mode) const
260{
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)
274 // not supported enum value, used to detect errors
276}
277
278
280{
282 return nullptr;
283
284 xcb_window_t clipboardOwner = connection()->selectionOwner(atomForMode(mode));
285 if (clipboardOwner == connection()->qtSelectionOwner()) {
286 return m_clientClipboard[mode];
287 } else {
288 if (!m_xClipboard[mode])
289 m_xClipboard[mode].reset(new QXcbClipboardMime(mode, this));
290
291 return m_xClipboard[mode].data();
292 }
293}
294
296{
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 *>(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(xcb_connection(), newOwner, modeAtom, connection()->time());
334
335 if (connection()->selectionOwner(modeAtom) != newOwner) {
336 qCWarning(lcQpaClipboard, "QXcbClipboard::setMimeData: Cannot set X11 selection owner");
337 }
338
340}
341
343{
345 return true;
346 return false;
347}
348
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
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(xcb_connection());
374 xcb_create_window(xcb_connection(),
375 XCB_COPY_FROM_PARENT, // depth -- same as root
376 window, // window id
377 platformScreen->screen()->root, // parent window id
378 x, y, w, h,
379 0, // border width
380 XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class
381 platformScreen->screen()->root_visual, // visual
382 0, // value mask
383 nullptr); // value list
384
386 QStringLiteral("Qt Clipboard Requestor Window"));
387
388 uint32_t mask = XCB_EVENT_MASK_PROPERTY_CHANGE;
389 xcb_change_window_attributes(xcb_connection(), window, XCB_CW_EVENT_MASK, &mask);
390
391 that->setRequestor(window);
392 }
393 return m_requestor;
394}
395
397{
398 if (m_requestor != XCB_NONE) {
399 xcb_destroy_window(xcb_connection(), 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;
408 for (int i = 0; i < formats.size(); ++i) {
409 QList<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(connection(), formats.at(i));
410 for (int j = 0; j < atoms.size(); ++j) {
411 if (!types.contains(atoms.at(j)))
412 types.append(atoms.at(j));
413 }
414 }
419
420 xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_ATOM,
421 32, types.size(), (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;
430
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(), target, d, &data, &atomFormat, &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(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(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property,
451 atom(QXcbAtom::AtomINCR), 32, 1, (const void *)&bytes);
452 auto transaction = new QXcbClipboardTransaction(this, window, property, data, atomFormat, dataFormat);
453 m_transactions.insert(window, 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(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, atomFormat,
463 dataFormat, dataSize, (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(event->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(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(req->selection);
514 qCWarning(lcQpaClipboard, "QXcbClipboard: Unknown selection %s",
515 connection()->atomName(req->selection).constData());
516 xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_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(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_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(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
532 return;
533 }
534
535 xcb_atom_t targetsAtom = atom(QXcbAtom::AtomTARGETS);
536 xcb_atom_t multipleAtom = atom(QXcbAtom::AtomMULTIPLE);
537 xcb_atom_t timestampAtom = 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(req->requestor, req->property, false, &multi_data,
550 nullptr, &multi_type, &multi_format)
551 || multi_format != 32) {
552 // MULTIPLE property not formatted correctly
553 xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
554 return;
555 }
556 nmulti = multi_data.size()/sizeof(*multi);
557 multi = new AtomPair[nmulti];
558 memcpy(multi,multi_data.data(),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(xcb_connection(), XCB_PROP_MODE_REPLACE, req->requestor,
582 property, XCB_ATOM_INTEGER, 32, 1, &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, req->requestor, property);
589 } else {
590 ret = sendSelection(d, target, 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(xcb_connection(), XCB_PROP_MODE_REPLACE, req->requestor, req->property,
609 multi_type, 32, nmulti*2, (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(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
618}
619
620void QXcbClipboard::handleXFixesSelectionRequest(xcb_xfixes_selection_notify_event_t *event)
621{
622 QClipboard::Mode mode = modeForAtom(event->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(new QXcbClipboardMime(mode, this));
632 } else {
633 m_xClipboard[mode]->reset();
634 }
636 } else if (event->subtype == XCB_XFIXES_SELECTION_EVENT_SELECTION_CLIENT_CLOSE ||
637 event->subtype == XCB_XFIXES_SELECTION_EVENT_SELECTION_WINDOW_DESTROY)
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(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(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(reply.get());
684 int length = xcb_get_property_value_length(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(buffer->data() + buffer_offset, data, 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;
712 m_incr_receive_time = connection()->getTimestamp();
713 if (deleteProperty)
714 xcb_delete_property(xcb_connection(), 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{
724 timer.start();
726 do {
727 auto e = queue->peek([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) {
751 return nullptr;
752 }
753
754 // process other clipboard events, since someone is probably requesting data from us
755 auto clipboardAtom = atom(QXcbAtom::AtomCLIPBOARD);
756 e = queue->peek([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) {
766 free(e);
767 }
768
769 connection()->flush();
770
771 const auto elapsed = timer.elapsed();
772 if (elapsed < clipboard_timeout)
773 queue->waitForNewEvents(flushedTailNode, 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{
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(nbytes+1);
793 alloc_error = buf.size() != nbytes+1;
794 }
795
797 timer.start();
798 for (;;) {
799 connection()->flush();
800 xcb_generic_event_t *ge = waitForClipboardEvent(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, true, &tmp_buf, &length, nullptr, nullptr)) {
813 if (length == 0) { // no more data, we're done
814 if (nullterm) {
815 buf.resize(offset+1);
816 buf[offset] = '\0';
817 } else {
818 buf.resize(offset);
819 }
820 return buf;
821 } else if (!alloc_error) {
822 if (offset+length > (int)buf.size()) {
823 buf.resize(offset+length+65535);
824 if (buf.size() != offset+length+65535) {
825 alloc_error = true;
826 length = buf.size() - offset;
827 }
828 }
829 memcpy(buf.data()+offset, tmp_buf.constData(), length);
830 tmp_buf.resize(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(modeAtom, fmtAtom, 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{
855 xcb_window_t win = requestor();
856
857 if (time == 0) time = connection()->time();
858
859 xcb_delete_property(xcb_connection(), win, property);
860 xcb_convert_selection(xcb_connection(), win, selection, target, property, time);
861
862 connection()->sync();
863
864 xcb_generic_event_t *ge = waitForClipboardEvent(win, XCB_SELECTION_NOTIFY);
865 bool no_selection = !ge || ((xcb_selection_notify_event_t *)ge)->property == XCB_NONE;
866 free(ge);
867
868 if (no_selection)
869 return buf;
870
871 xcb_atom_t type;
872 if (clipboardReadProperty(win, property, true, &buf, nullptr, &type, nullptr)) {
873 if (type == atom(QXcbAtom::AtomINCR)) {
874 int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
876 }
877 }
878
879 return buf;
880}
881
882#endif // QT_NO_CLIPBOARD
883
885
886#include "moc_qxcbclipboard.cpp"
887#include "qxcbclipboard.moc"
NSData * m_data
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
Mode
\keyword clipboard mode
Definition qclipboard.h:27
\inmodule QtCore
QStringList formats() const override
Returns a list of formats supported by the object.
static QStringList formatsHelper(const QMimeData *data)
iterator insert(const Key &key, const T &value)
Definition qmap.h:688
iterator find(const Key &key)
Definition qmap.h:641
bool isEmpty() const
Definition qmap.h:269
const_iterator constEnd() const
Definition qmap.h:604
\inmodule QtCore
Definition qmetatype.h:341
\inmodule QtCore
Definition qmimedata.h:16
int startTimer(int interval, Qt::TimerType timerType=Qt::CoarseTimer)
This is an overloaded function that will start a timer of type timerType and a timeout of interval mi...
Definition qobject.cpp:1817
void killTimer(int id)
Kills the timer with timer identifier, id.
Definition qobject.cpp:1912
The QPlatformClipboard class provides an abstraction for the system clipboard.
void emitChanged(QClipboard::Mode mode)
T * data() const noexcept
Returns the value of the pointer referenced by this object.
void reset(T *other=nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval< T * >())))
Deletes the existing object it is pointing to (if any), and sets its pointer to other.
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
\inmodule QtCore
Definition qcoreevent.h:366
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
\inmodule QtCore
Definition qvariant.h:65
@ Atom_QT_SELECTION
Definition qxcbatom.h:43
@ AtomSAVE_TARGETS
Definition qxcbatom.h:41
@ AtomCLIPBOARD_MANAGER
Definition qxcbatom.h:46
@ AtomINCR
Definition qxcbatom.h:37
@ AtomTIMESTAMP
Definition qxcbatom.h:40
@ AtomCLIP_TEMPORARY
Definition qxcbatom.h:42
@ AtomMULTIPLE
Definition qxcbatom.h:39
@ AtomTARGETS
Definition qxcbatom.h:38
@ AtomCLIPBOARD
Definition qxcbatom.h:36
size_t maxRequestDataBytes(size_t requestSize) const
bool hasFormat_sys(const QString &format) const override
QVariant retrieveData_sys(const QString &fmt, QMetaType type) const override
QXcbClipboardMime(QClipboard::Mode mode, QXcbClipboard *clipboard)
bool isEmpty() const
QStringList formats_sys() const override
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.
QXcbClipboardTransaction(QXcbClipboard *clipboard, xcb_window_t w, xcb_atom_t p, QByteArray d, xcb_atom_t t, int f)
int clipboardTimeout() const
QByteArray getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t=0)
bool clipboardReadProperty(xcb_window_t win, xcb_atom_t property, bool deleteProperty, QByteArray *buffer, int *size, xcb_atom_t *type, int *format)
QByteArray getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtatom)
QByteArray clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm)
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
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)
xcb_window_t selectionOwner(xcb_atom_t atom) const
void setTime(xcb_timestamp_t t)
xcb_timestamp_t time() const
QXcbScreen * primaryScreen() const
xcb_window_t qtSelectionOwner()
QXcbEventQueue * eventQueue() const
xcb_timestamp_t getTimestamp()
void handleXcbEvent(xcb_generic_event_t *event)
static QVariant mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a, const QByteArray &data, const QString &format, QMetaType requestedType, bool hasUtf8)
Definition qxcbmime.cpp:124
static QList< xcb_atom_t > mimeAtomsForFormat(QXcbConnection *connection, const QString &format)
Definition qxcbmime.cpp:96
static xcb_atom_t mimeAtomForFormat(QXcbConnection *connection, const QString &format, QMetaType requestedType, const QList< xcb_atom_t > &atoms, bool *hasUtf8)
Definition qxcbmime.cpp:222
static bool mimeDataForAtom(QXcbConnection *connection, xcb_atom_t a, QMimeData *mimeData, QByteArray *data, xcb_atom_t *atomFormat, int *dataFormat)
Definition qxcbmime.cpp:47
static QString mimeAtomToString(QXcbConnection *connection, xcb_atom_t a)
Definition qxcbmime.cpp:23
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
xcb_screen_t * screen() const
Definition qxcbscreen.h:152
void setWindowTitle(const QString &title) override
Reimplement to set the window title to title.
QSet< QString >::iterator it
EGLint EGLint * formats
Combined button and popup list for selecting options.
QList< QString > QStringList
Constructs a string list that contains the given string, str.
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
#define qCWarning(category,...)
#define qCDebug(category,...)
return ret
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
GLenum GLsizei GLsizei GLint * values
[15]
GLint GLint GLint GLint GLint x
[0]
GLenum mode
GLfloat GLfloat GLfloat w
[0]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLsizei GLenum GLenum * types
GLenum GLsizei dataSize
GLenum GLuint GLenum GLsizei length
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLfloat GLfloat f
GLenum GLuint buffer
GLenum type
GLenum GLuint GLenum GLsizei const GLchar * buf
GLenum target
GLenum GLuint GLintptr offset
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
GLint GLsizei GLsizei GLenum format
GLint y
GLfloat GLfloat GLfloat GLfloat h
struct _cl_event * event
const GLubyte * c
GLdouble GLdouble t
Definition qopenglext.h:243
GLfloat GLfloat p
[1]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QStringLiteral(str)
static double elapsed(qint64 after, qint64 before)
#define Q_OBJECT
unsigned int quint32
Definition qtypes.h:50
unsigned int uint
Definition qtypes.h:34
QVideoFrameFormat::PixelFormat fmt
const char property[13]
Definition qwizard.cpp:101
#define Q_XCB_REPLY(call,...)
QWidget * win
Definition settings.cpp:6
QList< int > list
[14]
QtConcurrent::task([]{ qDebug("Hello, world!");}).spawn(FutureResult void increment(QPromise< int > &promise, int i)
[10]
QMimeData * mimeData
QTimer * timer
[3]
QQueue< int > queue
[0]
QItemSelection * selection
[0]
aWidget window() -> setWindowTitle("New Window Title")
[2]
QNetworkReply * reply
bool contains(const AT &t) const noexcept
Definition qlist.h:45