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