KGuiAddons

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

KDE's Doxygen guidelines are available online.