1/*
2 SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
3 SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "waylandclipboard_p.h"
8
9#include <QBuffer>
10#include <QFile>
11#include <QGuiApplication>
12#include <QImageReader>
13#include <QImageWriter>
14#include <QMimeData>
15#include <QMutex>
16#include <QPointer>
17#include <QThread>
18#include <QTimer>
19#include <QWaylandClientExtension>
20#include <QWindow>
21#include <QtWaylandClientVersion>
22
23#include <errno.h>
24#include <fcntl.h>
25#include <poll.h>
26#include <signal.h>
27#include <string.h>
28#include <unistd.h>
29
30#include "qwayland-wayland.h"
31#include "qwayland-ext-data-control-v1.h"
32
33/**
34 * Wayland clipboard wraps ext_data_control an additional is used as we need to avoid
35 * deadlocks if an application tries to read the normal clipboard whilst it owns the data control.
36 *
37 * To solve this all ext_data_control classes live on another thread which dispatches events on a separate
38 * queue. A recursive mutex allows the main thread to read mimedata and no wayland events which change the mimedata process until this is complete.
39 */
40
41static inline QString applicationQtXImageLiteral()
42{
43 return QStringLiteral("application/x-qt-image");
44}
45
46// copied from https://code.woboq.org/qt5/qtbase/src/gui/kernel/qinternalmimedata.cpp.html
47static QString utf8Text()
48{
49 return QStringLiteral("text/plain;charset=utf-8");
50}
51
52static QStringList imageMimeFormats(const QList<QByteArray> &imageFormats)
53{
54 QStringList formats;
55 formats.reserve(asize: imageFormats.size());
56 for (const auto &format : imageFormats)
57 formats.append(t: QLatin1String("image/") + QLatin1String(format.toLower()));
58 // put png at the front because it is best
59 int pngIndex = formats.indexOf(needle: QLatin1String("image/png"));
60 if (pngIndex != -1 && pngIndex != 0)
61 formats.move(from: pngIndex, to: 0);
62 return formats;
63}
64
65static inline QStringList imageReadMimeFormats()
66{
67 return imageMimeFormats(imageFormats: QImageReader::supportedImageFormats());
68}
69
70static inline QStringList imageWriteMimeFormats()
71{
72 return imageMimeFormats(imageFormats: QImageWriter::supportedImageFormats());
73}
74// end copied
75
76class DataControlDeviceManager : public QWaylandClientExtensionTemplate<DataControlDeviceManager>, public QtWayland::ext_data_control_manager_v1
77{
78 Q_OBJECT
79public:
80 DataControlDeviceManager()
81 : QWaylandClientExtensionTemplate<DataControlDeviceManager>(1)
82 {
83 }
84
85 void instantiate()
86 {
87 initialize();
88 }
89
90 ~DataControlDeviceManager()
91 {
92 if (isInitialized()) {
93 destroy();
94 }
95 }
96};
97
98class DataControlOffer : public QMimeData, public QtWayland::ext_data_control_offer_v1
99{
100 Q_OBJECT
101public:
102 DataControlOffer(struct ::ext_data_control_offer_v1 *id)
103 : QtWayland::ext_data_control_offer_v1(id)
104 {
105 }
106
107 ~DataControlOffer()
108 {
109 destroy();
110 }
111
112 QStringList formats() const override
113 {
114 return m_receivedFormats;
115 }
116
117 bool containsImageData() const
118 {
119 if (m_receivedFormats.contains(str: applicationQtXImageLiteral())) {
120 return true;
121 }
122 const auto formats = imageReadMimeFormats();
123 for (const auto &receivedFormat : m_receivedFormats) {
124 if (formats.contains(str: receivedFormat)) {
125 return true;
126 }
127 }
128 return false;
129 }
130
131 bool hasFormat(const QString &mimeType) const override
132 {
133 if (mimeType == QStringLiteral("text/plain") && m_receivedFormats.contains(str: utf8Text())) {
134 return true;
135 }
136 if (m_receivedFormats.contains(str: mimeType)) {
137 return true;
138 }
139
140 // If we have image data
141 if (containsImageData()) {
142 // is the requested output mimeType supported ?
143 const QStringList imageFormats = imageWriteMimeFormats();
144 for (const QString &imageFormat : imageFormats) {
145 if (imageFormat == mimeType) {
146 return true;
147 }
148 }
149 if (mimeType == applicationQtXImageLiteral()) {
150 return true;
151 }
152 }
153
154 return false;
155 }
156
157protected:
158 void ext_data_control_offer_v1_offer(const QString &mime_type) override
159 {
160 if (!m_receivedFormats.contains(str: mime_type)) {
161 m_receivedFormats << mime_type;
162 }
163 }
164
165 QVariant retrieveData(const QString &mimeType, QMetaType type) const override;
166
167private:
168 /* reads data from a file descriptor with a timeout of 1 second
169 * true if data is read successfully
170 */
171 static bool readData(int fd, QByteArray &data, const QString &mimeType);
172 QStringList m_receivedFormats;
173 mutable QHash<QString, QVariant> m_data;
174};
175
176QVariant DataControlOffer::retrieveData(const QString &mimeType, QMetaType type) const
177{
178 Q_UNUSED(type);
179
180 auto it = m_data.constFind(key: mimeType);
181 if (it != m_data.constEnd())
182 return *it;
183
184 QString mime;
185 if (!m_receivedFormats.contains(str: mimeType)) {
186 if (mimeType == QStringLiteral("text/plain") && m_receivedFormats.contains(str: utf8Text())) {
187 mime = utf8Text();
188 } else if (mimeType == applicationQtXImageLiteral()) {
189 const auto writeFormats = imageWriteMimeFormats();
190 for (const auto &receivedFormat : m_receivedFormats) {
191 if (writeFormats.contains(str: receivedFormat)) {
192 mime = receivedFormat;
193 break;
194 }
195 }
196 if (mime.isEmpty()) {
197 // default exchange format
198 mime = QStringLiteral("image/png");
199 }
200 }
201
202 if (mime.isEmpty()) {
203 return QVariant();
204 }
205 } else {
206 mime = mimeType;
207 }
208
209 int pipeFds[2];
210 if (pipe(pipedes: pipeFds) != 0) {
211 return QVariant();
212 }
213
214 auto t = const_cast<DataControlOffer *>(this);
215 t->receive(mime_type: mime, fd: pipeFds[1]);
216
217 close(fd: pipeFds[1]);
218
219 /*
220 * Ideally we need to introduce a non-blocking QMimeData object
221 * Or a non-blocking constructor to QMimeData with the mimetypes that are relevant
222 *
223 * However this isn't actually any worse than X.
224 */
225
226 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
227 auto display = waylandApp->display();
228
229 wl_display_flush(display);
230
231 QFile readPipe;
232 if (readPipe.open(fd: pipeFds[0], ioFlags: QIODevice::ReadOnly)) {
233 QByteArray data;
234 if (readData(fd: pipeFds[0], data, mimeType: mime)) {
235 close(fd: pipeFds[0]);
236
237 if (mimeType == applicationQtXImageLiteral()) {
238 QImage img = QImage::fromData(data, format: mime.mid(position: mime.indexOf(ch: QLatin1Char('/')) + 1).toLatin1().toUpper().data());
239 if (!img.isNull()) {
240 m_data.insert(key: mimeType, value: img);
241 return img;
242 }
243 } else if (data.size() > 1 && mimeType == u"text/uri-list") {
244 const auto urls = data.split(sep: '\n');
245 QVariantList list;
246 list.reserve(asize: urls.size());
247 for (const QByteArray &s : urls) {
248 if (QUrl url(QUrl::fromEncoded(input: QByteArrayView(s).trimmed())); url.isValid()) {
249 list.emplace_back(args: std::move(url));
250 }
251 }
252 m_data.insert(key: mimeType, value: list);
253 return list;
254 }
255 m_data.insert(key: mimeType, value: data);
256 return data;
257 }
258 close(fd: pipeFds[0]);
259 }
260
261 return QVariant();
262}
263
264bool DataControlOffer::readData(int fd, QByteArray &data, const QString &mimeType)
265{
266 pollfd pfds[1];
267 pfds[0].fd = fd;
268 pfds[0].events = POLLIN;
269
270 while (true) {
271 const int ready = poll(fds: pfds, nfds: 1, timeout: 1000);
272 if (ready < 0) {
273 if (errno != EINTR) {
274 qWarning(msg: "DataControlOffer: poll() failed for mimeType %s: %s", qPrintable(mimeType), strerror(errno));
275 return false;
276 }
277 } else if (ready == 0) {
278 qWarning(msg: "DataControlOffer: timeout reading from pipe for mimeType %s", qPrintable(mimeType));
279 return false;
280 } else {
281 char buf[4096];
282 int n = read(fd: fd, buf: buf, nbytes: sizeof buf);
283
284 if (n < 0) {
285 qWarning(msg: "DataControlOffer: read() failed for mimeType %s: %s", qPrintable(mimeType), strerror(errno));
286 return false;
287 } else if (n == 0) {
288 return true;
289 } else if (n > 0) {
290 data.append(s: buf, len: n);
291 }
292 }
293 }
294}
295
296class DataControlSource : public QObject, public QtWayland::ext_data_control_source_v1
297{
298 Q_OBJECT
299public:
300 DataControlSource(struct ::ext_data_control_source_v1 *id, QMimeData *mimeData);
301 DataControlSource() = default;
302 ~DataControlSource()
303 {
304 destroy();
305 }
306
307 QMimeData *mimeData()
308 {
309 return m_mimeData.get();
310 }
311
312Q_SIGNALS:
313 void cancelled();
314
315protected:
316 void ext_data_control_source_v1_send(const QString &mime_type, int32_t fd) override;
317 void ext_data_control_source_v1_cancelled() override;
318
319private:
320 std::unique_ptr<QMimeData> m_mimeData;
321};
322
323DataControlSource::DataControlSource(struct ::ext_data_control_source_v1 *id, QMimeData *mimeData)
324 : QtWayland::ext_data_control_source_v1(id)
325 , m_mimeData(mimeData)
326{
327 const auto formats = mimeData->formats();
328 for (const QString &format : formats) {
329 offer(mime_type: format);
330 }
331 if (mimeData->hasText() && !m_mimeData->hasFormat(mimetype: utf8Text())) {
332 // ensure GTK applications get this mimetype to avoid them discarding the offer
333 offer(QStringLiteral("text/plain;charset=utf-8"));
334 }
335
336 if (mimeData->hasImage()) {
337 const QStringList imageFormats = imageWriteMimeFormats();
338 for (const QString &imageFormat : imageFormats) {
339 if (!formats.contains(str: imageFormat)) {
340 offer(mime_type: imageFormat);
341 }
342 }
343 }
344}
345
346void DataControlSource::ext_data_control_source_v1_send(const QString &mime_type, int32_t fd)
347{
348 QString send_mime_type = mime_type;
349 if (send_mime_type == utf8Text() && !m_mimeData->hasFormat(mimetype: utf8Text())) {
350 // if we get a request on the fallback mime, send the data from the original mime type
351 send_mime_type = QStringLiteral("text/plain");
352 }
353
354 QByteArray ba;
355
356 // adapted from QInternalMimeData::renderDataHelper
357 if (mime_type == applicationQtXImageLiteral() || mime_type.startsWith(s: QLatin1String("image/"))) {
358 if (m_mimeData->hasImage()) {
359 const QImage image = qvariant_cast<QImage>(v: m_mimeData->imageData());
360 QBuffer buf(&ba);
361 buf.open(openMode: QBuffer::WriteOnly);
362 if (mime_type == applicationQtXImageLiteral()) {
363 // would there not be PNG ??
364 image.save(device: &buf, format: "PNG");
365 } else {
366 image.save(device: &buf, format: mime_type.mid(position: mime_type.indexOf(ch: QLatin1Char('/')) + 1).toLatin1().toUpper().data());
367 }
368 }
369 } else { // end adapted
370 ba = m_mimeData->data(mimetype: send_mime_type);
371 }
372
373 QFile c;
374 if (!c.open(fd, ioFlags: QFile::WriteOnly, handleFlags: QFile::AutoCloseHandle)) {
375 return;
376 }
377 // Create a sigpipe handler that does nothing, or clients may be forced to terminate
378 // if the pipe is closed in the other end.
379 struct sigaction action, oldAction;
380 action.sa_handler = SIG_IGN;
381 sigemptyset(set: &action.sa_mask);
382 action.sa_flags = 0;
383 sigaction(SIGPIPE, act: &action, oact: &oldAction);
384 const int flags = fcntl(fd: fd, F_GETFL, 0);
385 if (flags & O_NONBLOCK) {
386 fcntl(fd: fd, F_SETFL, flags & ~O_NONBLOCK); // Unset O_NONBLOCK to fix pasting to XWayland windows
387 }
388 const qint64 written = c.write(data: ba);
389 sigaction(SIGPIPE, act: &oldAction, oact: nullptr);
390
391 if (written != ba.size()) {
392 qWarning() << "Failed to send all clipobard data; sent" << written << "bytes out of" << ba.size();
393 }
394}
395
396void DataControlSource::ext_data_control_source_v1_cancelled()
397{
398 Q_EMIT cancelled();
399}
400
401class DataControlDevice : public QObject, public QtWayland::ext_data_control_device_v1
402{
403 Q_OBJECT
404public:
405 DataControlDevice(struct ::ext_data_control_device_v1 *id)
406 : QtWayland::ext_data_control_device_v1(id)
407 {
408 }
409
410 ~DataControlDevice()
411 {
412 destroy();
413 }
414
415 void setSelection(std::unique_ptr<DataControlSource> selection);
416 QMimeData *receivedSelection()
417 {
418 return m_receivedSelection.get();
419 }
420 QMimeData *selection()
421 {
422 return m_selection ? m_selection->mimeData() : nullptr;
423 }
424
425 void setPrimarySelection(std::unique_ptr<DataControlSource> selection);
426 QMimeData *receivedPrimarySelection()
427 {
428 return m_receivedPrimarySelection.get();
429 }
430 QMimeData *primarySelection()
431 {
432 return m_primarySelection ? m_primarySelection->mimeData() : nullptr;
433 }
434
435Q_SIGNALS:
436 void receivedSelectionChanged();
437 void selectionChanged();
438
439 void receivedPrimarySelectionChanged();
440 void primarySelectionChanged();
441
442protected:
443 void ext_data_control_device_v1_data_offer(struct ::ext_data_control_offer_v1 *id) override
444 {
445 // this will become memory managed when we retrieve the selection event
446 // a compositor calling data_offer without doing that would be a bug
447 new DataControlOffer(id);
448 }
449
450 void ext_data_control_device_v1_selection(struct ::ext_data_control_offer_v1 *id) override
451 {
452 QMutexLocker locker(&m_selectionLock);
453 if (!id) {
454 m_receivedSelection.reset();
455 } else {
456 auto derivated = QtWayland::ext_data_control_offer_v1::fromObject(object: id);
457 auto offer = dynamic_cast<DataControlOffer *>(derivated); // dynamic because of the dual inheritance
458 m_receivedSelection.reset(p: offer);
459 }
460 Q_EMIT receivedSelectionChanged();
461 }
462
463 void ext_data_control_device_v1_primary_selection(struct ::ext_data_control_offer_v1 *id) override
464 {
465 QMutexLocker locker(&m_primarySelectionLock);
466 if (!id) {
467 m_receivedPrimarySelection.reset();
468 } else {
469 auto derivated = QtWayland::ext_data_control_offer_v1::fromObject(object: id);
470 auto offer = dynamic_cast<DataControlOffer *>(derivated); // dynamic because of the dual inheritance
471 m_receivedPrimarySelection.reset(p: offer);
472 }
473 Q_EMIT receivedPrimarySelectionChanged();
474 }
475
476private:
477 QRecursiveMutex m_selectionLock;
478 std::unique_ptr<DataControlSource> m_selection; // selection set locally
479 std::unique_ptr<DataControlOffer> m_receivedSelection; // latest selection set from externally to here
480
481 QRecursiveMutex m_primarySelectionLock;
482 std::unique_ptr<DataControlSource> m_primarySelection; // selection set locally
483 std::unique_ptr<DataControlOffer> m_receivedPrimarySelection; // latest selection set from externally to here
484 friend WaylandClipboard;
485};
486
487void DataControlDevice::setSelection(std::unique_ptr<DataControlSource> selection)
488{
489 {
490 QMutexLocker locker(&m_selectionLock);
491 set_selection(selection->object());
492
493 // Note the previous selection is destroyed after the set_selection request.
494 m_selection = std::move(selection);
495 connect(sender: m_selection.get(), signal: &DataControlSource::cancelled, context: this, slot: [this]() {
496 m_selection.reset();
497 });
498 }
499
500 Q_EMIT selectionChanged();
501}
502
503void DataControlDevice::setPrimarySelection(std::unique_ptr<DataControlSource> selection)
504{
505 {
506 QMutexLocker locker(&m_primarySelectionLock);
507 set_primary_selection(selection->object());
508
509 // Note the previous selection is destroyed after the set_primary_selection request.
510 m_primarySelection = std::move(selection);
511 connect(sender: m_primarySelection.get(), signal: &DataControlSource::cancelled, context: this, slot: [this]() {
512 m_primarySelection.reset();
513 });
514 }
515
516 Q_EMIT primarySelectionChanged();
517}
518
519class ClipboardThread : public QThread
520{
521public:
522 ClipboardThread(wl_display *display)
523 : m_display(display)
524 {
525#if QT_VERSION_CHECK(WAYLAND_VERSION_MAJOR, WAYLAND_VERSION_MINOR, WAYLAND_VERSION_MICRO) >= QT_VERSION_CHECK(1, 23, 0)
526 m_queue = wl_display_create_queue_with_name(display: m_display, name: "ksystemclipboard queue");
527#else
528 m_queue = wl_display_create_queue(m_display);
529#endif
530 }
531
532 ~ClipboardThread()
533 {
534 if (m_callback) {
535 wl_callback_destroy(wl_callback: m_callback);
536 }
537 wl_event_queue_destroy(queue: m_queue);
538 }
539
540 void syncQueue()
541 {
542 // wl_display_dispatch_queue will block until it gets an event, as KSystemClipboard
543 // has the lifespan of the QApplication and the QPA is still alive
544 // we need something to explicitly wake up the event queue up before the main thread
545 // blocks waiting for this thread.
546 auto wrapped_display = wl_proxy_create_wrapper(proxy: m_display);
547 wl_proxy_set_queue(proxy: static_cast<wl_proxy *>(wrapped_display), queue: m_queue);
548 m_callback = wl_display_sync(wl_display: static_cast<struct wl_display *>(wrapped_display));
549 wl_display_flush(display: m_display);
550 wl_proxy_wrapper_destroy(proxy_wrapper: wrapped_display);
551 }
552
553 void run() override
554 {
555 int ret = 0;
556 while (ret >= 0 && !qGuiApp->closingDown()) {
557 ret = wl_display_dispatch_queue(display: m_display, queue: m_queue);
558 }
559 }
560 wl_callback *m_callback = nullptr;
561 wl_event_queue *m_queue = nullptr;
562 wl_display *m_display = nullptr;
563};
564
565WaylandClipboard::WaylandClipboard(QObject *parent)
566 : KSystemClipboard(parent)
567 , m_manager(new DataControlDeviceManager)
568{
569 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
570 if (!waylandApp) {
571 return;
572 }
573
574 connect(sender: m_manager.get(), signal: &DataControlDeviceManager::activeChanged, context: this, slot: [this]() {
575 if (m_manager->isActive()) {
576 auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
577 m_thread = std::make_unique<ClipboardThread>(args: waylandApp->display());
578
579 if (!waylandApp) {
580 return;
581 }
582 auto seat = waylandApp->seat();
583
584 if (!seat) {
585 return;
586 }
587 wl_proxy_set_queue(proxy: reinterpret_cast<wl_proxy *>(m_manager->object()), queue: m_thread->m_queue);
588 m_device.reset(p: new DataControlDevice(m_manager->get_data_device(seat)));
589 m_device->moveToThread(thread: m_thread.get());
590
591 connect(sender: m_device.get(), signal: &DataControlDevice::receivedSelectionChanged, context: this, slot: [this]() {
592 // When our source is still valid, so the offer is for setting it or we emit changed when it is cancelled
593 if (!m_device->selection()) {
594 Q_EMIT changed(mode: QClipboard::Clipboard);
595 }
596 });
597 connect(sender: m_device.get(), signal: &DataControlDevice::selectionChanged, context: this, slot: [this]() {
598 Q_EMIT changed(mode: QClipboard::Clipboard);
599 });
600
601 connect(sender: m_device.get(), signal: &DataControlDevice::receivedPrimarySelectionChanged, context: this, slot: [this]() {
602 // When our source is still valid, so the offer is for setting it or we emit changed when it is cancelled
603 if (!m_device->primarySelection()) {
604 Q_EMIT changed(mode: QClipboard::Selection);
605 }
606 });
607 connect(sender: m_device.get(), signal: &DataControlDevice::primarySelectionChanged, context: this, slot: [this]() {
608 Q_EMIT changed(mode: QClipboard::Selection);
609 });
610 m_thread->start();
611
612 } else {
613 m_device.reset();
614 m_thread->wait();
615 m_thread.reset();
616 }
617 });
618
619 m_manager->instantiate();
620}
621
622WaylandClipboard::~WaylandClipboard()
623{
624 if (m_thread && m_thread->isRunning()) {
625 m_thread->syncQueue();
626 m_thread->wait();
627 }
628}
629
630WaylandClipboard *WaylandClipboard::create(QObject *parent)
631{
632 auto clipboard = new WaylandClipboard(parent);
633 if (clipboard->isValid()) {
634 return clipboard;
635 }
636 delete clipboard;
637 return nullptr;
638}
639
640bool WaylandClipboard::isValid()
641{
642 return m_manager && m_manager->isInitialized();
643}
644
645void WaylandClipboard::setMimeData(QMimeData *mime, QClipboard::Mode mode)
646{
647 if (!m_device) {
648 return;
649 }
650
651 auto source = std::make_unique<DataControlSource>(args: m_manager->create_data_source(), args&: mime);
652 source->moveToThread(thread: m_thread.get());
653 if (mode == QClipboard::Clipboard) {
654 m_device->setSelection(std::move(source));
655 } else if (mode == QClipboard::Selection) {
656 m_device->setPrimarySelection(std::move(source));
657 }
658}
659
660void WaylandClipboard::clear(QClipboard::Mode mode)
661{
662 if (!m_device) {
663 return;
664 }
665
666 if (mode == QClipboard::Clipboard) {
667 QMutexLocker locker(&m_device->m_selectionLock);
668 m_device->set_selection(nullptr);
669 m_device->m_selection.reset();
670 } else if (mode == QClipboard::Selection) {
671 QMutexLocker locker(&m_device->m_primarySelectionLock);
672 m_device->set_primary_selection(nullptr);
673 m_device->m_primarySelection.reset();
674 }
675}
676
677const QMimeData *WaylandClipboard::mimeData(QClipboard::Mode mode) const
678{
679 if (!m_device) {
680 return nullptr;
681 }
682
683 // WaylandClipboard owns the mimedata, but the caller can read it until the next event loop runs.
684 auto lockWithUnlockLater = [this](QRecursiveMutex &mutex) {
685 mutex.lock();
686 QTimer::singleShot(interval: 0, receiver: this, slot: [&mutex, guard = QPointer<DataControlDevice>(m_device.get())] {
687 if (!guard) {
688 return;
689 }
690 mutex.unlock();
691 });
692 };
693
694 // return our locally set selection if it's not cancelled to avoid copying data to ourselves
695 if (mode == QClipboard::Clipboard) {
696 QMutexLocker lock(&m_device->m_selectionLock);
697 if (m_device->selection()) {
698 return m_device->selection();
699 }
700
701 // This application owns the clipboard via the regular data_device, use it so we don't block ourselves
702 if (QGuiApplication::clipboard()->ownsClipboard()) {
703 return QGuiApplication::clipboard()->mimeData(mode);
704 }
705 lockWithUnlockLater(m_device->m_selectionLock);
706 return m_device->receivedSelection();
707 } else if (mode == QClipboard::Selection) {
708 QMutexLocker lock(&m_device->m_primarySelectionLock);
709 if (m_device->primarySelection()) {
710 return m_device->primarySelection();
711 }
712
713 // This application owns the primary selection via the regular primary_selection_device, use it so we don't block ourselves
714 if (QGuiApplication::clipboard()->ownsSelection()) {
715 return QGuiApplication::clipboard()->mimeData(mode);
716 }
717
718 lockWithUnlockLater(m_device->m_primarySelectionLock);
719 return m_device->receivedPrimarySelection();
720 }
721 return nullptr;
722}
723
724#include "moc_waylandclipboard_p.cpp"
725#include "waylandclipboard.moc"
726

source code of kguiaddons/src/systemclipboard/waylandclipboard.cpp