12#include <private/qguiapplication_p.h>
13#include <QElapsedTimer>
15#include <QtCore/QDebug>
17using namespace std::chrono_literals;
21#ifndef QT_NO_CLIPBOARD
23class QXcbClipboardMime :
public QXcbMime
27 QXcbClipboardMime(QClipboard::Mode mode, QXcbClipboard *clipboard)
29 , m_clipboard(clipboard)
32 case QClipboard::Selection:
33 modeAtom = XCB_ATOM_PRIMARY;
36 case QClipboard::Clipboard:
37 modeAtom = m_clipboard->atom(QXcbAtom::AtomCLIPBOARD);
41 qCWarning(lcQpaClipboard,
"QXcbClipboardMime: Internal error: Unsupported clipboard mode");
53 return m_clipboard->connection()->selectionOwner(modeAtom) == XCB_NONE;
57 QStringList formats_sys()
const override
62 if (!formatList.size()) {
63 QXcbClipboardMime *that =
const_cast<QXcbClipboardMime *>(
this);
67 that->format_atoms = m_clipboard->getDataInFormat(modeAtom, m_clipboard->atom(QXcbAtom::AtomTARGETS)).value_or(QByteArray());
69 if (format_atoms.size() > 0) {
70 const xcb_atom_t *targets = (
const xcb_atom_t *) format_atoms.data();
71 int size = format_atoms.size() /
sizeof(xcb_atom_t);
73 for (
int i = 0; i < size; ++i) {
77 QString format = mimeAtomToString(m_clipboard->connection(), targets[i]);
78 if (!formatList.contains(format))
79 that->formatList.append(format);
87 bool hasFormat_sys(
const QString &format)
const override
89 QStringList list = formats();
90 return list.contains(format);
93 QVariant retrieveData_sys(
const QString &fmt, QMetaType type)
const override
95 auto requestedType = type;
96 if (fmt.isEmpty() || isEmpty())
101 QList<xcb_atom_t> atoms;
102 const xcb_atom_t *targets = (
const xcb_atom_t *) format_atoms.data();
103 int size = format_atoms.size() /
sizeof(xcb_atom_t);
105 for (
int i = 0; i < size; ++i)
106 atoms.append(targets[i]);
108 bool hasUtf8 =
false;
109 xcb_atom_t fmtatom = mimeAtomForFormat(m_clipboard->connection(), fmt, requestedType, atoms, &hasUtf8);
114 const std::optional<QByteArray> result = m_clipboard->getDataInFormat(modeAtom, fmtatom);
115 if (!result.has_value())
118 return mimeConvertToFormat(m_clipboard->connection(), fmtatom, result.value(), fmt, requestedType, hasUtf8);
123 QXcbClipboard *m_clipboard;
124 QStringList formatList;
125 QByteArray format_atoms;
129 xcb_atom_t p, QByteArray d, xcb_atom_t t,
int f)
130 : m_clipboard(clipboard), m_window(w), m_property(p), m_data(d), m_target(t), m_format(f)
132 const quint32 values[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
134 XCB_CW_EVENT_MASK, values);
136 m_abortTimer.start(m_clipboard->clipboardTimeout() * 1ms,
this);
147 if (event->atom != m_property || event->state != XCB_PROPERTY_DELETE)
151 m_abortTimer.start(m_clipboard->clipboardTimeout() * 1ms,
this);
153 uint bytes_left = uint(m_data.size()) - m_offset;
154 if (bytes_left > 0) {
156 uint bytes_to_send = qMin(uint(increment), bytes_left);
158 qCDebug(lcQpaClipboard,
"sending %d bytes, %d remaining, transaction: %p)",
159 bytes_to_send, bytes_left - bytes_to_send,
this);
161 uint32_t dataSize = bytes_to_send / (m_format / 8);
162 xcb_change_property(m_clipboard->xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
163 m_property, m_target, m_format, dataSize, m_data.constData() + m_offset);
164 m_offset += bytes_to_send;
166 qCDebug(lcQpaClipboard,
"transaction %p completed",
this);
168 xcb_change_property(m_clipboard
->xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
169 m_property, m_target, m_format, 0,
nullptr);
171 const quint32 values[] = { XCB_EVENT_MASK_NO_EVENT };
173 XCB_CW_EVENT_MASK, values);
182 if (ev->id() == m_abortTimer.id()) {
185 qCDebug(lcQpaClipboard,
"timed out while sending data to %p",
this);
195 Q_ASSERT(QClipboard::Clipboard == 0);
196 Q_ASSERT(QClipboard::Selection == 1);
197 m_clientClipboard[QClipboard::Clipboard] =
nullptr;
198 m_clientClipboard[QClipboard::Selection] =
nullptr;
199 m_timestamp[QClipboard::Clipboard] = XCB_CURRENT_TIME;
200 m_timestamp[QClipboard::Selection] = XCB_CURRENT_TIME;
203 const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
204 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
205 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
207 XCB_ATOM_PRIMARY, mask);
213 m_maxPropertyRequestDataBytes =
connection()->maxRequestDataBytes(
sizeof(xcb_change_property_request_t));
218 m_clipboard_closing =
true;
220 if (m_timestamp[QClipboard::Clipboard] != XCB_CURRENT_TIME ||
221 m_timestamp[QClipboard::Selection] != XCB_CURRENT_TIME) {
234 if (
auto event = waitForClipboardEvent(connection()->qtSelectionOwner(),
235 XCB_SELECTION_NOTIFY,
true)) {
238 qCWarning(lcQpaClipboard,
"QXcbClipboard: Unable to receive an event from the "
239 "clipboard manager in a reasonable time");
244 if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection])
245 delete m_clientClipboard[QClipboard::Clipboard];
246 delete m_clientClipboard[QClipboard::Selection];
251 if (m_transactions.isEmpty() || event->response_type != XCB_PROPERTY_NOTIFY)
254 auto propertyNotify =
reinterpret_cast<
const xcb_property_notify_event_t *>(event);
255 TransactionMap::Iterator it = m_transactions.find(propertyNotify->window);
256 if (it == m_transactions.constEnd())
259 return (*it)->updateIncrementalProperty(propertyNotify);
262xcb_atom_t
QXcbClipboard::atomForMode(QClipboard::Mode mode)
const
264 if (mode == QClipboard::Clipboard)
266 if (mode == QClipboard::Selection)
267 return XCB_ATOM_PRIMARY;
271QClipboard::Mode
QXcbClipboard::modeForAtom(xcb_atom_t a)
const
273 if (a == XCB_ATOM_PRIMARY)
274 return QClipboard::Selection;
275 if (a == atom(QXcbAtom::AtomCLIPBOARD))
276 return QClipboard::Clipboard;
278 return QClipboard::FindBuffer;
284 if (mode > QClipboard::Selection)
287 xcb_window_t clipboardOwner = connection()->selectionOwner(atomForMode(mode));
288 if (clipboardOwner ==
connection()->qtSelectionOwner()) {
289 return m_clientClipboard[mode];
291 if (!m_xClipboard[mode])
292 m_xClipboard[mode].reset(
new QXcbClipboardMime(mode,
this));
294 return m_xClipboard[mode].data();
300 if (mode > QClipboard::Selection)
303 QXcbClipboardMime *xClipboard =
nullptr;
306 xClipboard = qobject_cast<QXcbClipboardMime *>(mimeData(mode));
308 if (xClipboard->isEmpty())
313 if (!xClipboard && (m_clientClipboard[mode] == data))
316 xcb_atom_t modeAtom = atomForMode(mode);
317 xcb_window_t newOwner = XCB_NONE;
319 if (m_clientClipboard[mode]) {
320 if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection])
321 delete m_clientClipboard[mode];
322 m_clientClipboard[mode] =
nullptr;
323 m_timestamp[mode] = XCB_CURRENT_TIME;
332 m_clientClipboard[mode] = data;
338 if (
connection()->selectionOwner(modeAtom) != newOwner) {
339 qCWarning(lcQpaClipboard,
"QXcbClipboard::setMimeData: Cannot set X11 selection owner");
347 if (mode <= QClipboard::Selection)
354 if (connection()->qtSelectionOwner() == XCB_NONE || mode > QClipboard::Selection)
357 Q_ASSERT(m_timestamp[mode] == XCB_CURRENT_TIME
358 || connection()->selectionOwner(atomForMode(mode)) == connection()->qtSelectionOwner());
360 return m_timestamp[mode] != XCB_CURRENT_TIME;
370 QXcbScreen *platformScreen =
screen();
372 if (!m_requestor && platformScreen) {
373 const int x = 0, y = 0, w = 3, h = 3;
378 XCB_COPY_FROM_PARENT,
380 platformScreen->screen()->root,
383 XCB_WINDOW_CLASS_INPUT_OUTPUT,
384 platformScreen->screen()->root_visual,
388 QXcbWindow::setWindowTitle(connection(), window,
389 QStringLiteral(
"Qt Clipboard Requestor Window"));
391 uint32_t mask = XCB_EVENT_MASK_PROPERTY_CHANGE;
392 xcb_change_window_attributes(
xcb_connection(), window, XCB_CW_EVENT_MASK, &mask);
401 if (m_requestor != XCB_NONE) {
404 m_requestor = window;
407xcb_atom_t
QXcbClipboard::sendTargetsSelection(QMimeData *d, xcb_window_t window, xcb_atom_t property)
409 QList<xcb_atom_t> types;
410 QStringList formats = QInternalMimeData::formatsHelper(d);
411 for (
int i = 0; i < formats.size(); ++i) {
412 QList<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(
connection(), formats.at(i));
413 for (
int j = 0; j < atoms.size(); ++j) {
414 if (!types.contains(atoms.at(j)))
415 types.append(atoms.at(j));
423 xcb_change_property(
xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_ATOM,
424 32, types.size(), (
const void *)types.constData());
428xcb_atom_t
QXcbClipboard::sendSelection(QMimeData *d, xcb_atom_t target, xcb_window_t window, xcb_atom_t property)
430 xcb_atom_t atomFormat = target;
434 QString fmt = QXcbMime::mimeAtomToString(
connection(), target);
441 if (QXcbMime::mimeDataForAtom(
connection(), target, d, &data, &atomFormat, &dataFormat)) {
446 bool allow_incr = property != motif_clip_temporary;
448 if (m_clipboard_closing)
451 if (data.size() > m_maxPropertyRequestDataBytes && allow_incr) {
452 long bytes = data.size();
453 xcb_change_property(
xcb_connection(), XCB_PROP_MODE_REPLACE, window, property,
456 m_transactions.insert(window, transaction);
461 if (data.size() > m_maxPropertyRequestDataBytes)
463 int dataSize = data.size() / (dataFormat / 8);
465 xcb_change_property(
xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, atomFormat,
466 dataFormat, dataSize, (
const void *)data.constData());
473 QClipboard::Mode mode = modeForAtom(event->selection);
474 if (mode > QClipboard::Selection)
478 if (m_timestamp[mode] != XCB_CURRENT_TIME && event->time <= m_timestamp[mode])
485 xcb_window_t newOwner =
connection()->selectionOwner(event->selection);
488
489
490
491 if (newOwner != XCB_NONE) {
492 if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection])
493 delete m_clientClipboard[mode];
494 m_clientClipboard[mode] =
nullptr;
495 m_timestamp[mode] = XCB_CURRENT_TIME;
502 qCWarning(lcQpaClipboard,
"QXcbClipboard: Selection request should be caught before");
507 event.response_type = XCB_SELECTION_NOTIFY;
508 event.requestor = req->requestor;
509 event.selection = req->selection;
510 event.target = req->target;
511 event.property = XCB_NONE;
512 event.time = req->time;
515 QClipboard::Mode mode = modeForAtom(req->selection);
516 if (mode > QClipboard::Selection) {
517 qCWarning(lcQpaClipboard,
"QXcbClipboard: Unknown selection %s",
518 connection()->atomName(req->selection).constData());
519 xcb_send_event(
xcb_connection(),
false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (
const char *)&event);
523 d = m_clientClipboard[mode];
526 qCWarning(lcQpaClipboard,
"QXcbClipboard: Cannot transfer data, no data available");
527 xcb_send_event(
xcb_connection(),
false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (
const char *)&event);
531 if (m_timestamp[mode] == XCB_CURRENT_TIME
532 || (req->time != XCB_CURRENT_TIME && req->time < m_timestamp[mode])) {
533 qCDebug(lcQpaClipboard,
"QXcbClipboard: SelectionRequest too old");
534 xcb_send_event(
xcb_connection(),
false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (
const char *)&event);
542 struct AtomPair { xcb_atom_t target; xcb_atom_t property; } *multi =
nullptr;
543 xcb_atom_t multi_type = XCB_NONE;
544 int multi_format = 0;
547 bool multi_writeback =
false;
549 if (req->target == multipleAtom) {
550 QByteArray multi_data;
551 if (req->property == XCB_NONE
552 || !clipboardReadProperty(req->requestor, req->property,
false, &multi_data,
553 nullptr, &multi_type, &multi_format)
554 || multi_format != 32) {
556 xcb_send_event(
xcb_connection(),
false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (
const char *)&event);
559 nmulti = multi_data.size()/
sizeof(*multi);
560 multi =
new AtomPair[nmulti];
561 memcpy(multi,multi_data.data(),multi_data.size());
565 for (; imulti < nmulti; ++imulti) {
570 target = multi[imulti].target;
571 property = multi[imulti].property;
573 target = req->target;
574 property = req->property;
575 if (property == XCB_NONE)
579 xcb_atom_t ret = XCB_NONE;
580 if (target == XCB_NONE || property == XCB_NONE) {
582 }
else if (target == timestampAtom) {
583 if (m_timestamp[mode] != XCB_CURRENT_TIME) {
584 xcb_change_property(
xcb_connection(), XCB_PROP_MODE_REPLACE, req->requestor,
585 property, XCB_ATOM_INTEGER, 32, 1, &m_timestamp[mode]);
588 qCWarning(lcQpaClipboard,
"QXcbClipboard: Invalid data timestamp");
590 }
else if (target == targetsAtom) {
591 ret = sendTargetsSelection(d, req->requestor, property);
593 ret = sendSelection(d, target, req->requestor, property);
597 if (ret == XCB_NONE) {
598 multi[imulti].property = XCB_NONE;
599 multi_writeback =
true;
602 event.property = ret;
608 if (multi_writeback) {
611 xcb_change_property(
xcb_connection(), XCB_PROP_MODE_REPLACE, req->requestor, req->property,
612 multi_type, 32, nmulti*2, (
const void *)multi);
616 event.property = req->property;
620 xcb_send_event(
xcb_connection(),
false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (
const char *)&event);
625 QClipboard::Mode mode = modeForAtom(event->selection);
626 if (mode > QClipboard::Selection)
632 if (event->owner !=
connection()->qtSelectionOwner() && event->selection_timestamp > m_timestamp[mode]) {
633 if (!m_xClipboard[mode]) {
634 m_xClipboard[mode].reset(
new QXcbClipboardMime(mode,
this));
636 m_xClipboard[mode]->reset();
639 }
else if (event->subtype == XCB_XFIXES_SELECTION_EVENT_SELECTION_CLIENT_CLOSE ||
640 event->subtype == XCB_XFIXES_SELECTION_EVENT_SELECTION_WINDOW_DESTROY)
646 xcb_atom_t dummy_type;
652 format = &dummy_format;
656 if (!reply || reply->type == XCB_NONE) {
661 *format = reply->format;
663 auto bytes_left = reply->bytes_after;
665 int offset = 0, buffer_offset = 0;
667 int newSize = bytes_left;
668 buffer->resize(newSize);
670 bool ok = (buffer->size() == newSize);
679 XCB_GET_PROPERTY_TYPE_ANY, offset, m_maxPropertyRequestDataBytes / 4);
680 if (!reply || reply->type == XCB_NONE)
684 *format = reply->format;
685 bytes_left = reply->bytes_after;
686 char *data = (
char *)xcb_get_property_value(reply.get());
687 int length = xcb_get_property_value_length(reply.get());
692 if ((
int)(buffer_offset + length) > buffer->size()) {
693 qCWarning(lcQpaClipboard,
"QXcbClipboard: buffer overflow");
694 length = buffer->size() - buffer_offset;
700 memcpy(buffer->data() + buffer_offset, data, length);
701 buffer_offset += length;
705 offset += length / 4;
713 *size = buffer_offset;
715 m_incr_receive_time =
connection()->getTimestamp();
724xcb_generic_event_t *
QXcbClipboard::waitForClipboardEvent(xcb_window_t window,
int type,
bool checkManager)
730 auto e = queue->peek([window, type](xcb_generic_event_t *event,
int eventType) {
731 if (eventType != type)
733 if (eventType == XCB_PROPERTY_NOTIFY) {
734 auto propertyNotify =
reinterpret_cast<xcb_property_notify_event_t *>(event);
735 return propertyNotify->window == window;
737 if (eventType == XCB_SELECTION_NOTIFY) {
738 auto selectionNotify =
reinterpret_cast<xcb_selection_notify_event_t *>(event);
739 return selectionNotify->requestor == window;
759 e = queue->peek([clipboardAtom](xcb_generic_event_t *event,
int type) {
760 xcb_atom_t selection = XCB_ATOM_NONE;
761 if (type == XCB_SELECTION_REQUEST)
762 selection =
reinterpret_cast<xcb_selection_request_event_t *>(event)->selection;
763 else if (type == XCB_SELECTION_CLEAR)
764 selection =
reinterpret_cast<xcb_selection_clear_event_t *>(event)->selection;
765 return selection == XCB_ATOM_PRIMARY || selection == clipboardAtom;
774 const auto elapsed = timer.elapsed();
775 if (elapsed < clipboard_timeout)
777 }
while (timer.elapsed() < clipboard_timeout);
786 bool alloc_error =
false;
789 xcb_timestamp_t prev_time = m_incr_receive_time;
795 buf.resize(nbytes+1);
796 alloc_error = buf.size() != nbytes+1;
803 xcb_generic_event_t *ge = waitForClipboardEvent(win, XCB_PROPERTY_NOTIFY);
806 xcb_property_notify_event_t *event = (xcb_property_notify_event_t *)ge;
807 QScopedPointer<xcb_property_notify_event_t, QScopedPointerPodDeleter> guard(event);
809 if (event->atom != property
810 || event->state != XCB_PROPERTY_NEW_VALUE
811 || event->time < prev_time)
813 prev_time = event->time;
815 if (clipboardReadProperty(win, property,
true, &tmp_buf, &length,
nullptr,
nullptr)) {
818 buf.resize(offset+1);
824 }
else if (!alloc_error) {
825 if (offset+length > (
int)buf.size()) {
826 buf.resize(offset+length+65535);
827 if (buf.size() != offset+length+65535) {
829 length = buf.size() - offset;
832 memcpy(buf.data()+offset, tmp_buf.constData(), length);
838 const auto elapsed = timer.elapsed();
839 if (elapsed > clipboard_timeout)
852 return getSelection(modeAtom, fmtAtom, atom(QXcbAtom::Atom_QT_SELECTION));
862 xcb_convert_selection(
xcb_connection(), win, selection, target, property, time);
866 xcb_generic_event_t *ge = waitForClipboardEvent(win, XCB_SELECTION_NOTIFY);
867 bool no_selection = !ge || ((xcb_selection_notify_event_t *)ge)->property == XCB_NONE;
875 if (clipboardReadProperty(win, property,
true, &buf,
nullptr, &type,
nullptr)) {
877 int nbytes = buf.size() >= 4 ? *((
int*)buf.data()) : 0;
878 return clipboardReadIncrementalProperty(win, property, nbytes,
false);
890#include "moc_qxcbclipboard.cpp"
891#include "qxcbclipboard.moc"
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()
std::optional< QByteArray > getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtatom)
bool clipboardReadProperty(xcb_window_t win, xcb_atom_t property, bool deleteProperty, QByteArray *buffer, int *size, xcb_atom_t *type, int *format)
void removeTransaction(xcb_window_t window)
void handleSelectionClearRequest(xcb_selection_clear_event_t *event)
bool supportsMode(QClipboard::Mode mode) const override
void setRequestor(xcb_window_t window)
std::optional< QByteArray > getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t=0)
bool handlePropertyNotify(const xcb_generic_event_t *event)
QXcbClipboard(QXcbConnection *connection)
QMimeData * mimeData(QClipboard::Mode mode) override
QXcbScreen * screen() const
bool ownsMode(QClipboard::Mode mode) const override
xcb_window_t requestor() const
void setMimeData(QMimeData *data, QClipboard::Mode mode) override
void handleSelectionRequest(xcb_selection_request_event_t *event)
void handleXFixesSelectionRequest(xcb_xfixes_selection_notify_event_t *event)
std::optional< QByteArray > clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm)
const QXcbEventNode * flushedTail() const
void waitForNewEvents(const QXcbEventNode *sinceFlushedTail, unsigned long time=(std::numeric_limits< unsigned long >::max)())
QXcbConnection * connection() const
xcb_connection_t * xcb_connection() const
xcb_atom_t atom(QXcbAtom::Atom atom) const
QXcbObject(QXcbConnection *connection=nullptr)
#define Q_XCB_REPLY(call,...)