1 | /* |
2 | SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org> |
3 | SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #include "waylandclipboard_p.h" |
9 | |
10 | #include <QBuffer> |
11 | #include <QFile> |
12 | #include <QGuiApplication> |
13 | #include <QImageReader> |
14 | #include <QImageWriter> |
15 | #include <QMimeData> |
16 | #include <QPointer> |
17 | #include <QWaylandClientExtension> |
18 | #include <QWindow> |
19 | #include <QtWaylandClientVersion> |
20 | #include <qpa/qplatformnativeinterface.h> |
21 | |
22 | #include <errno.h> |
23 | #include <poll.h> |
24 | #include <signal.h> |
25 | #include <string.h> |
26 | #include <unistd.h> |
27 | |
28 | #include "qwayland-wayland.h" |
29 | #include "qwayland-wlr-data-control-unstable-v1.h" |
30 | |
31 | static inline QString applicationQtXImageLiteral() |
32 | { |
33 | return QStringLiteral("application/x-qt-image" ); |
34 | } |
35 | |
36 | // copied from https://code.woboq.org/qt5/qtbase/src/gui/kernel/qinternalmimedata.cpp.html |
37 | static QString utf8Text() |
38 | { |
39 | return QStringLiteral("text/plain;charset=utf-8" ); |
40 | } |
41 | |
42 | static QStringList imageMimeFormats(const QList<QByteArray> &imageFormats) |
43 | { |
44 | QStringList formats; |
45 | formats.reserve(asize: imageFormats.size()); |
46 | for (const auto &format : imageFormats) |
47 | formats.append(t: QLatin1String("image/" ) + QLatin1String(format.toLower())); |
48 | // put png at the front because it is best |
49 | int pngIndex = formats.indexOf(t: QLatin1String("image/png" )); |
50 | if (pngIndex != -1 && pngIndex != 0) |
51 | formats.move(from: pngIndex, to: 0); |
52 | return formats; |
53 | } |
54 | |
55 | static inline QStringList imageReadMimeFormats() |
56 | { |
57 | return imageMimeFormats(imageFormats: QImageReader::supportedImageFormats()); |
58 | } |
59 | |
60 | static inline QStringList imageWriteMimeFormats() |
61 | { |
62 | return imageMimeFormats(imageFormats: QImageWriter::supportedImageFormats()); |
63 | } |
64 | // end copied |
65 | |
66 | class DataControlDeviceManager : public QWaylandClientExtensionTemplate<DataControlDeviceManager>, public QtWayland::zwlr_data_control_manager_v1 |
67 | { |
68 | Q_OBJECT |
69 | public: |
70 | DataControlDeviceManager() |
71 | : QWaylandClientExtensionTemplate<DataControlDeviceManager>(2) |
72 | { |
73 | } |
74 | |
75 | void instantiate() |
76 | { |
77 | initialize(); |
78 | } |
79 | |
80 | ~DataControlDeviceManager() |
81 | { |
82 | if (isInitialized()) { |
83 | destroy(); |
84 | } |
85 | } |
86 | }; |
87 | |
88 | class DataControlOffer : public QMimeData, public QtWayland::zwlr_data_control_offer_v1 |
89 | { |
90 | Q_OBJECT |
91 | public: |
92 | DataControlOffer(struct ::zwlr_data_control_offer_v1 *id) |
93 | : QtWayland::zwlr_data_control_offer_v1(id) |
94 | { |
95 | } |
96 | |
97 | ~DataControlOffer() |
98 | { |
99 | destroy(); |
100 | } |
101 | |
102 | QStringList formats() const override |
103 | { |
104 | return m_receivedFormats; |
105 | } |
106 | |
107 | bool containsImageData() const |
108 | { |
109 | if (m_receivedFormats.contains(str: applicationQtXImageLiteral())) { |
110 | return true; |
111 | } |
112 | const auto formats = imageReadMimeFormats(); |
113 | for (const auto &receivedFormat : m_receivedFormats) { |
114 | if (formats.contains(str: receivedFormat)) { |
115 | return true; |
116 | } |
117 | } |
118 | return false; |
119 | } |
120 | |
121 | bool hasFormat(const QString &mimeType) const override |
122 | { |
123 | if (mimeType == QStringLiteral("text/plain" ) && m_receivedFormats.contains(str: utf8Text())) { |
124 | return true; |
125 | } |
126 | if (m_receivedFormats.contains(str: mimeType)) { |
127 | return true; |
128 | } |
129 | |
130 | // If we have image data |
131 | if (containsImageData()) { |
132 | // is the requested output mimeType supported ? |
133 | const QStringList imageFormats = imageWriteMimeFormats(); |
134 | for (const QString &imageFormat : imageFormats) { |
135 | if (imageFormat == mimeType) { |
136 | return true; |
137 | } |
138 | } |
139 | if (mimeType == applicationQtXImageLiteral()) { |
140 | return true; |
141 | } |
142 | } |
143 | |
144 | return false; |
145 | } |
146 | |
147 | protected: |
148 | void zwlr_data_control_offer_v1_offer(const QString &mime_type) override |
149 | { |
150 | m_receivedFormats << mime_type; |
151 | } |
152 | |
153 | QVariant retrieveData(const QString &mimeType, QMetaType type) const override; |
154 | |
155 | private: |
156 | /** reads data from a file descriptor with a timeout of 1 second |
157 | * true if data is read successfully |
158 | */ |
159 | static bool readData(int fd, QByteArray &data); |
160 | QStringList m_receivedFormats; |
161 | }; |
162 | |
163 | QVariant DataControlOffer::retrieveData(const QString &mimeType, QMetaType type) const |
164 | { |
165 | Q_UNUSED(type); |
166 | |
167 | QString mime; |
168 | if (!m_receivedFormats.contains(str: mimeType)) { |
169 | if (mimeType == QStringLiteral("text/plain" ) && m_receivedFormats.contains(str: utf8Text())) { |
170 | mime = utf8Text(); |
171 | } else if (mimeType == applicationQtXImageLiteral()) { |
172 | const auto writeFormats = imageWriteMimeFormats(); |
173 | for (const auto &receivedFormat : m_receivedFormats) { |
174 | if (writeFormats.contains(str: receivedFormat)) { |
175 | mime = receivedFormat; |
176 | break; |
177 | } |
178 | } |
179 | if (mime.isEmpty()) { |
180 | // default exchange format |
181 | mime = QStringLiteral("image/png" ); |
182 | } |
183 | } |
184 | |
185 | if (mime.isEmpty()) { |
186 | return QVariant(); |
187 | } |
188 | } else { |
189 | mime = mimeType; |
190 | } |
191 | |
192 | int pipeFds[2]; |
193 | if (pipe(pipedes: pipeFds) != 0) { |
194 | return QVariant(); |
195 | } |
196 | |
197 | auto t = const_cast<DataControlOffer *>(this); |
198 | t->receive(mime_type: mime, fd: pipeFds[1]); |
199 | |
200 | close(fd: pipeFds[1]); |
201 | |
202 | /* |
203 | * Ideally we need to introduce a non-blocking QMimeData object |
204 | * Or a non-blocking constructor to QMimeData with the mimetypes that are relevant |
205 | * |
206 | * However this isn't actually any worse than X. |
207 | */ |
208 | |
209 | QPlatformNativeInterface *native = qGuiApp->platformNativeInterface(); |
210 | auto display = static_cast<struct ::wl_display *>(native->nativeResourceForIntegration(resource: "wl_display" )); |
211 | wl_display_flush(display); |
212 | |
213 | QFile readPipe; |
214 | if (readPipe.open(fd: pipeFds[0], ioFlags: QIODevice::ReadOnly)) { |
215 | QByteArray data; |
216 | if (readData(fd: pipeFds[0], data)) { |
217 | close(fd: pipeFds[0]); |
218 | |
219 | if (mimeType == applicationQtXImageLiteral()) { |
220 | QImage img = QImage::fromData(data, format: mime.mid(position: mime.indexOf(c: QLatin1Char('/')) + 1).toLatin1().toUpper().data()); |
221 | if (!img.isNull()) { |
222 | return img; |
223 | } |
224 | } |
225 | return data; |
226 | } |
227 | close(fd: pipeFds[0]); |
228 | } |
229 | return QVariant(); |
230 | } |
231 | |
232 | bool DataControlOffer::readData(int fd, QByteArray &data) |
233 | { |
234 | pollfd pfds[1]; |
235 | pfds[0].fd = fd; |
236 | pfds[0].events = POLLIN; |
237 | |
238 | while (true) { |
239 | const int ready = poll(fds: pfds, nfds: 1, timeout: 1000); |
240 | if (ready < 0) { |
241 | if (errno != EINTR) { |
242 | qWarning(msg: "DataControlOffer: poll() failed: %s" , strerror(errno)); |
243 | return false; |
244 | } |
245 | } else if (ready == 0) { |
246 | qWarning(msg: "DataControlOffer: timeout reading from pipe" ); |
247 | return false; |
248 | } else { |
249 | char buf[4096]; |
250 | int n = read(fd: fd, buf: buf, nbytes: sizeof buf); |
251 | |
252 | if (n < 0) { |
253 | qWarning(msg: "DataControlOffer: read() failed: %s" , strerror(errno)); |
254 | return false; |
255 | } else if (n == 0) { |
256 | return true; |
257 | } else if (n > 0) { |
258 | data.append(s: buf, len: n); |
259 | } |
260 | } |
261 | } |
262 | } |
263 | |
264 | class DataControlSource : public QObject, public QtWayland::zwlr_data_control_source_v1 |
265 | { |
266 | Q_OBJECT |
267 | public: |
268 | DataControlSource(struct ::zwlr_data_control_source_v1 *id, QMimeData *mimeData); |
269 | DataControlSource() = default; |
270 | ~DataControlSource() |
271 | { |
272 | destroy(); |
273 | } |
274 | |
275 | QMimeData *mimeData() |
276 | { |
277 | return m_mimeData.get(); |
278 | } |
279 | std::unique_ptr<QMimeData> releaseMimeData() |
280 | { |
281 | return std::move(m_mimeData); |
282 | } |
283 | |
284 | Q_SIGNALS: |
285 | void cancelled(); |
286 | |
287 | protected: |
288 | void zwlr_data_control_source_v1_send(const QString &mime_type, int32_t fd) override; |
289 | void zwlr_data_control_source_v1_cancelled() override; |
290 | |
291 | private: |
292 | std::unique_ptr<QMimeData> m_mimeData; |
293 | }; |
294 | |
295 | DataControlSource::DataControlSource(struct ::zwlr_data_control_source_v1 *id, QMimeData *mimeData) |
296 | : QtWayland::zwlr_data_control_source_v1(id) |
297 | , m_mimeData(mimeData) |
298 | { |
299 | const auto formats = mimeData->formats(); |
300 | for (const QString &format : formats) { |
301 | offer(mime_type: format); |
302 | } |
303 | if (mimeData->hasText()) { |
304 | // ensure GTK applications get this mimetype to avoid them discarding the offer |
305 | offer(QStringLiteral("text/plain;charset=utf-8" )); |
306 | } |
307 | |
308 | if (mimeData->hasImage()) { |
309 | const QStringList imageFormats = imageWriteMimeFormats(); |
310 | for (const QString &imageFormat : imageFormats) { |
311 | if (!formats.contains(str: imageFormat)) { |
312 | offer(mime_type: imageFormat); |
313 | } |
314 | } |
315 | } |
316 | } |
317 | |
318 | void DataControlSource::zwlr_data_control_source_v1_send(const QString &mime_type, int32_t fd) |
319 | { |
320 | QString send_mime_type = mime_type; |
321 | if (send_mime_type == QStringLiteral("text/plain;charset=utf-8" )) { |
322 | // if we get a request on the fallback mime, send the data from the original mime type |
323 | send_mime_type = QStringLiteral("text/plain" ); |
324 | } |
325 | |
326 | QByteArray ba; |
327 | if (m_mimeData->hasImage()) { |
328 | // adapted from QInternalMimeData::renderDataHelper |
329 | if (mime_type == applicationQtXImageLiteral()) { |
330 | QImage image = qvariant_cast<QImage>(v: m_mimeData->imageData()); |
331 | QBuffer buf(&ba); |
332 | buf.open(openMode: QBuffer::WriteOnly); |
333 | // would there not be PNG ?? |
334 | image.save(device: &buf, format: "PNG" ); |
335 | |
336 | } else if (mime_type.startsWith(s: QLatin1String("image/" ))) { |
337 | QImage image = qvariant_cast<QImage>(v: m_mimeData->imageData()); |
338 | QBuffer buf(&ba); |
339 | buf.open(openMode: QBuffer::WriteOnly); |
340 | image.save(device: &buf, format: mime_type.mid(position: mime_type.indexOf(c: QLatin1Char('/')) + 1).toLatin1().toUpper().data()); |
341 | } |
342 | // end adapted |
343 | } else { |
344 | ba = m_mimeData->data(mimetype: send_mime_type); |
345 | } |
346 | |
347 | // Create a sigpipe handler that does nothing, or clients may be forced to terminate |
348 | // if the pipe is closed in the other end. |
349 | struct sigaction action, oldAction; |
350 | action.sa_handler = SIG_IGN; |
351 | sigemptyset(set: &action.sa_mask); |
352 | action.sa_flags = 0; |
353 | sigaction(SIGPIPE, act: &action, oact: &oldAction); |
354 | write(fd: fd, buf: ba.constData(), n: ba.size()); |
355 | sigaction(SIGPIPE, act: &oldAction, oact: nullptr); |
356 | close(fd: fd); |
357 | } |
358 | |
359 | void DataControlSource::zwlr_data_control_source_v1_cancelled() |
360 | { |
361 | Q_EMIT cancelled(); |
362 | } |
363 | |
364 | class DataControlDevice : public QObject, public QtWayland::zwlr_data_control_device_v1 |
365 | { |
366 | Q_OBJECT |
367 | public: |
368 | DataControlDevice(struct ::zwlr_data_control_device_v1 *id) |
369 | : QtWayland::zwlr_data_control_device_v1(id) |
370 | { |
371 | } |
372 | |
373 | ~DataControlDevice() |
374 | { |
375 | destroy(); |
376 | } |
377 | |
378 | void setSelection(std::unique_ptr<DataControlSource> selection); |
379 | QMimeData *receivedSelection() |
380 | { |
381 | return m_receivedSelection.get(); |
382 | } |
383 | QMimeData *selection() |
384 | { |
385 | return m_selection ? m_selection->mimeData() : nullptr; |
386 | } |
387 | |
388 | void setPrimarySelection(std::unique_ptr<DataControlSource> selection); |
389 | QMimeData *receivedPrimarySelection() |
390 | { |
391 | return m_receivedPrimarySelection.get(); |
392 | } |
393 | QMimeData *primarySelection() |
394 | { |
395 | return m_primarySelection ? m_primarySelection->mimeData() : nullptr; |
396 | } |
397 | |
398 | Q_SIGNALS: |
399 | void receivedSelectionChanged(); |
400 | void selectionChanged(); |
401 | |
402 | void receivedPrimarySelectionChanged(); |
403 | void primarySelectionChanged(); |
404 | |
405 | protected: |
406 | void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *id) override |
407 | { |
408 | // this will become memory managed when we retrieve the selection event |
409 | // a compositor calling data_offer without doing that would be a bug |
410 | new DataControlOffer(id); |
411 | } |
412 | |
413 | void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *id) override |
414 | { |
415 | if (!id) { |
416 | m_receivedSelection.reset(); |
417 | } else { |
418 | auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(object: id); |
419 | auto offer = dynamic_cast<DataControlOffer *>(derivated); // dynamic because of the dual inheritance |
420 | m_receivedSelection.reset(p: offer); |
421 | } |
422 | Q_EMIT receivedSelectionChanged(); |
423 | } |
424 | |
425 | void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *id) override |
426 | { |
427 | if (!id) { |
428 | m_receivedPrimarySelection.reset(); |
429 | } else { |
430 | auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(object: id); |
431 | auto offer = dynamic_cast<DataControlOffer *>(derivated); // dynamic because of the dual inheritance |
432 | m_receivedPrimarySelection.reset(p: offer); |
433 | } |
434 | Q_EMIT receivedPrimarySelectionChanged(); |
435 | } |
436 | |
437 | private: |
438 | std::unique_ptr<DataControlSource> m_selection; // selection set locally |
439 | std::unique_ptr<DataControlOffer> m_receivedSelection; // latest selection set from externally to here |
440 | |
441 | std::unique_ptr<DataControlSource> m_primarySelection; // selection set locally |
442 | std::unique_ptr<DataControlOffer> m_receivedPrimarySelection; // latest selection set from externally to here |
443 | friend WaylandClipboard; |
444 | }; |
445 | |
446 | void DataControlDevice::setSelection(std::unique_ptr<DataControlSource> selection) |
447 | { |
448 | m_selection = std::move(selection); |
449 | connect(sender: m_selection.get(), signal: &DataControlSource::cancelled, context: this, slot: [this]() { |
450 | m_selection.reset(); |
451 | }); |
452 | set_selection(m_selection->object()); |
453 | Q_EMIT selectionChanged(); |
454 | } |
455 | |
456 | void DataControlDevice::setPrimarySelection(std::unique_ptr<DataControlSource> selection) |
457 | { |
458 | m_primarySelection = std::move(selection); |
459 | connect(sender: m_primarySelection.get(), signal: &DataControlSource::cancelled, context: this, slot: [this]() { |
460 | m_primarySelection.reset(); |
461 | }); |
462 | |
463 | if (zwlr_data_control_device_v1_get_version(zwlr_data_control_device_v1: object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) { |
464 | set_primary_selection(m_primarySelection->object()); |
465 | Q_EMIT primarySelectionChanged(); |
466 | } |
467 | } |
468 | class Keyboard; |
469 | // We are binding to Seat/Keyboard manually because we want to react to gaining focus but inside Qt the events are Qt and arrive to late |
470 | class KeyboardFocusWatcher : public QWaylandClientExtensionTemplate<KeyboardFocusWatcher>, public QtWayland::wl_seat |
471 | { |
472 | Q_OBJECT |
473 | public: |
474 | KeyboardFocusWatcher() |
475 | : QWaylandClientExtensionTemplate(5) |
476 | { |
477 | initialize(); |
478 | auto native = qGuiApp->platformNativeInterface(); |
479 | auto display = static_cast<struct ::wl_display *>(native->nativeResourceForIntegration(resource: "wl_display" )); |
480 | // so we get capabilities |
481 | wl_display_roundtrip(display); |
482 | } |
483 | ~KeyboardFocusWatcher() override |
484 | { |
485 | if (isActive()) { |
486 | release(); |
487 | } |
488 | } |
489 | void seat_capabilities(uint32_t capabilities) override |
490 | { |
491 | const bool hasKeyboard = capabilities & capability_keyboard; |
492 | if (hasKeyboard && !m_keyboard) { |
493 | m_keyboard = std::make_unique<Keyboard>(args: get_keyboard(), args&: *this); |
494 | } else if (!hasKeyboard && m_keyboard) { |
495 | m_keyboard.reset(); |
496 | } |
497 | } |
498 | bool hasFocus() const |
499 | { |
500 | return m_focus; |
501 | } |
502 | Q_SIGNALS: |
503 | void keyboardEntered(); |
504 | |
505 | private: |
506 | friend Keyboard; |
507 | bool m_focus = false; |
508 | std::unique_ptr<Keyboard> m_keyboard; |
509 | }; |
510 | |
511 | class Keyboard : public QtWayland::wl_keyboard |
512 | { |
513 | public: |
514 | Keyboard(::wl_keyboard *keyboard, KeyboardFocusWatcher &seat) |
515 | : wl_keyboard(keyboard) |
516 | , m_seat(seat) |
517 | { |
518 | } |
519 | ~Keyboard() |
520 | { |
521 | release(); |
522 | } |
523 | |
524 | private: |
525 | void keyboard_enter([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface, [[maybe_unused]] wl_array *keys) override |
526 | { |
527 | m_seat.m_focus = true; |
528 | Q_EMIT m_seat.keyboardEntered(); |
529 | } |
530 | void keyboard_leave([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface) override |
531 | { |
532 | m_seat.m_focus = false; |
533 | } |
534 | KeyboardFocusWatcher &m_seat; |
535 | }; |
536 | |
537 | WaylandClipboard::WaylandClipboard(QObject *parent) |
538 | : KSystemClipboard(parent) |
539 | , m_keyboardFocusWatcher(new KeyboardFocusWatcher) |
540 | , m_manager(new DataControlDeviceManager) |
541 | { |
542 | connect(sender: m_manager.get(), signal: &DataControlDeviceManager::activeChanged, context: this, slot: [this]() { |
543 | if (m_manager->isActive()) { |
544 | QPlatformNativeInterface *native = qApp->platformNativeInterface(); |
545 | if (!native) { |
546 | return; |
547 | } |
548 | auto seat = static_cast<struct ::wl_seat *>(native->nativeResourceForIntegration(resource: "wl_seat" )); |
549 | if (!seat) { |
550 | return; |
551 | } |
552 | m_device.reset(p: new DataControlDevice(m_manager->get_data_device(seat))); |
553 | |
554 | connect(sender: m_device.get(), signal: &DataControlDevice::receivedSelectionChanged, context: this, slot: [this]() { |
555 | // When our source is still valid, so the offer is for setting it or we emit changed when it is cancelled |
556 | if (!m_device->selection()) { |
557 | Q_EMIT changed(mode: QClipboard::Clipboard); |
558 | } |
559 | }); |
560 | connect(sender: m_device.get(), signal: &DataControlDevice::selectionChanged, context: this, slot: [this]() { |
561 | Q_EMIT changed(mode: QClipboard::Clipboard); |
562 | }); |
563 | |
564 | connect(sender: m_device.get(), signal: &DataControlDevice::receivedPrimarySelectionChanged, context: this, slot: [this]() { |
565 | // When our source is still valid, so the offer is for setting it or we emit changed when it is cancelled |
566 | if (!m_device->primarySelection()) { |
567 | Q_EMIT changed(mode: QClipboard::Selection); |
568 | } |
569 | }); |
570 | connect(sender: m_device.get(), signal: &DataControlDevice::primarySelectionChanged, context: this, slot: [this]() { |
571 | Q_EMIT changed(mode: QClipboard::Selection); |
572 | }); |
573 | |
574 | } else { |
575 | m_device.reset(); |
576 | } |
577 | }); |
578 | |
579 | m_manager->instantiate(); |
580 | } |
581 | |
582 | WaylandClipboard::~WaylandClipboard() = default; |
583 | |
584 | bool WaylandClipboard::isValid() |
585 | { |
586 | return m_manager && m_manager->isInitialized(); |
587 | } |
588 | |
589 | void WaylandClipboard::setMimeData(QMimeData *mime, QClipboard::Mode mode) |
590 | { |
591 | if (!m_device) { |
592 | return; |
593 | } |
594 | |
595 | // roundtrip to have accurate focus state when losing focus but setting mime data before processing wayland events. |
596 | auto native = qGuiApp->platformNativeInterface(); |
597 | auto display = static_cast<struct ::wl_display *>(native->nativeResourceForIntegration(resource: "wl_display" )); |
598 | wl_display_roundtrip(display); |
599 | |
600 | // If the application is focused, use the normal mechanism so a future paste will not deadlock itselfs |
601 | if (m_keyboardFocusWatcher->hasFocus()) { |
602 | QGuiApplication::clipboard()->setMimeData(data: mime, mode); |
603 | return; |
604 | } |
605 | // If not, set the clipboard once the app receives focus to avoid the deadlock |
606 | connect(sender: m_keyboardFocusWatcher.get(), signal: &KeyboardFocusWatcher::keyboardEntered, context: this, slot: &WaylandClipboard::gainedFocus, type: Qt::UniqueConnection); |
607 | auto source = std::make_unique<DataControlSource>(args: m_manager->create_data_source(), args&: mime); |
608 | if (mode == QClipboard::Clipboard) { |
609 | m_device->setSelection(std::move(source)); |
610 | } else if (mode == QClipboard::Selection) { |
611 | m_device->setPrimarySelection(std::move(source)); |
612 | } |
613 | } |
614 | |
615 | void WaylandClipboard::gainedFocus() |
616 | { |
617 | disconnect(sender: m_keyboardFocusWatcher.get(), signal: &KeyboardFocusWatcher::keyboardEntered, receiver: this, zero: nullptr); |
618 | // QClipboard takes ownership of the QMimeData so we need to transfer and unset our selections |
619 | if (auto &selection = m_device->m_selection) { |
620 | std::unique_ptr<QMimeData> data = selection->releaseMimeData(); |
621 | WaylandClipboard::clear(mode: QClipboard::Clipboard); |
622 | QGuiApplication::clipboard()->setMimeData(data: data.release(), mode: QClipboard::Clipboard); |
623 | } |
624 | if (auto &primarySelection = m_device->m_primarySelection) { |
625 | std::unique_ptr<QMimeData> data = primarySelection->releaseMimeData(); |
626 | WaylandClipboard::clear(mode: QClipboard::Selection); |
627 | QGuiApplication::clipboard()->setMimeData(data: data.release(), mode: QClipboard::Selection); |
628 | } |
629 | } |
630 | |
631 | void WaylandClipboard::clear(QClipboard::Mode mode) |
632 | { |
633 | if (!m_device) { |
634 | return; |
635 | } |
636 | if (mode == QClipboard::Clipboard) { |
637 | m_device->set_selection(nullptr); |
638 | m_device->m_selection.reset(); |
639 | } else if (mode == QClipboard::Selection) { |
640 | if (zwlr_data_control_device_v1_get_version(zwlr_data_control_device_v1: m_device->object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) { |
641 | m_device->set_primary_selection(nullptr); |
642 | m_device->m_primarySelection.reset(); |
643 | } |
644 | } |
645 | } |
646 | |
647 | const QMimeData *WaylandClipboard::mimeData(QClipboard::Mode mode) const |
648 | { |
649 | if (!m_device) { |
650 | return nullptr; |
651 | } |
652 | |
653 | // return our locally set selection if it's not cancelled to avoid copying data to ourselves |
654 | if (mode == QClipboard::Clipboard) { |
655 | if (m_device->selection()) { |
656 | return m_device->selection(); |
657 | } |
658 | // This application owns the clipboard via the regular data_device, use it so we don't block ourselves |
659 | if (QGuiApplication::clipboard()->ownsClipboard()) { |
660 | return QGuiApplication::clipboard()->mimeData(mode); |
661 | } |
662 | return m_device->receivedSelection(); |
663 | } else if (mode == QClipboard::Selection) { |
664 | if (m_device->primarySelection()) { |
665 | return m_device->primarySelection(); |
666 | } |
667 | // This application owns the primary selection via the regular primary_selection_device, use it so we don't block ourselves |
668 | if (QGuiApplication::clipboard()->ownsSelection()) { |
669 | return QGuiApplication::clipboard()->mimeData(mode); |
670 | } |
671 | return m_device->receivedPrimarySelection(); |
672 | } |
673 | return nullptr; |
674 | } |
675 | |
676 | #include "waylandclipboard.moc" |
677 | |