diff --git a/qt5-qtwayland.spec b/qt5-qtwayland.spec index 47824f5..cbffc16 100644 --- a/qt5-qtwayland.spec +++ b/qt5-qtwayland.spec @@ -3,7 +3,7 @@ Summary: Qt5 - Wayland platform support and QtCompositor module Name: qt5-%{qt_module} Version: 5.13.2 -Release: 1%{?dist} +Release: 2%{?dist} License: LGPLv3 Url: http://www.qt.io @@ -13,6 +13,10 @@ Source0: https://download.qt.io/official_releases/qt/%{majmin}/%{version}/submod # Upstream patches Patch0: qtwayland-do-not-redraw-decorations-everytime.patch Patch1: qtwayland-fix-100ms-freeze-when-apps-dont-swap-after-deliverupdaterequest.patch +Patch2: qtwayland-fix-inverse-repeat-rate-implementation.patch +Patch3: qtwayland-fix-crash-when-showing-child-window-with-hidden-parent.patch + +Patch10: qtwayland-implement-primary-selection-unstable-v1.patch # Upstreamable patches # https://fedoraproject.org/wiki/Changes/Qt_Wayland_By_Default_On_Gnome @@ -42,6 +46,8 @@ BuildRequires: pkgconfig(libudev) BuildRequires: pkgconfig(libinput) BuildRequires: libXext-devel +BuildRequires: tree + %description %{summary}. @@ -122,6 +128,10 @@ popd %changelog +* Wed Dec 11 2019 Jan Grulich - 5.13.2-2 +- Add support for primary-selection-unstable-v1 protocol +- Fix inverse repeat rate implementation + * Mon Dec 09 2019 Jan Grulich - 5.13.2-1 - 5.13.2 diff --git a/qtwayland-fix-crash-when-showing-child-window-with-hidden-parent.patch b/qtwayland-fix-crash-when-showing-child-window-with-hidden-parent.patch new file mode 100644 index 0000000..07f1a28 --- /dev/null +++ b/qtwayland-fix-crash-when-showing-child-window-with-hidden-parent.patch @@ -0,0 +1,48 @@ +From 962b9be7992cef672cb6307af5653c97382c334f Mon Sep 17 00:00:00 2001 +From: Johan Klokkhammer Helsing +Date: Fri, 1 Nov 2019 11:24:26 +0100 +Subject: Client: Fix crash when showing a child window with a hidden parent + +[ChangeLog][QPA plugin] Fixed a crash when showing a window with a hidden +parent. + +Now we just avoid creating the subsurface, so nothing is shown. Seems to be +the same behavior as on xcb. + +Fixes: QTBUG-79674 +Change-Id: Ia46fcd9a0da5aad4704816a41515cb1e128ac65f +Reviewed-by: Paul Olav Tvete + +diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp +index 78524f6f..27e38ccf 100644 +--- a/src/client/qwaylanddisplay.cpp ++++ b/src/client/qwaylanddisplay.cpp +@@ -109,6 +109,10 @@ struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) + return nullptr; + } + ++ // Make sure we don't pass NULL surfaces to libwayland (crashes) ++ Q_ASSERT(parent->object()); ++ Q_ASSERT(window->object()); ++ + return mSubCompositor->get_subsurface(window->object(), parent->object()); + } + +diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp +index 8d34afd1..7098568b 100644 +--- a/src/client/qwaylandwindow.cpp ++++ b/src/client/qwaylandwindow.cpp +@@ -124,9 +124,10 @@ void QWaylandWindow::initWindow() + if (shouldCreateSubSurface()) { + Q_ASSERT(!mSubSurfaceWindow); + +- QWaylandWindow *p = static_cast(QPlatformWindow::parent()); +- if (::wl_subsurface *ss = mDisplay->createSubSurface(this, p)) { +- mSubSurfaceWindow = new QWaylandSubSurface(this, p, ss); ++ auto *parent = static_cast(QPlatformWindow::parent()); ++ if (parent->object()) { ++ if (::wl_subsurface *subsurface = mDisplay->createSubSurface(this, parent)) ++ mSubSurfaceWindow = new QWaylandSubSurface(this, parent, subsurface); + } + } else if (shouldCreateShellSurface()) { + Q_ASSERT(!mShellSurface); diff --git a/qtwayland-fix-inverse-repeat-rate-implementation.patch b/qtwayland-fix-inverse-repeat-rate-implementation.patch new file mode 100644 index 0000000..66dcad1 --- /dev/null +++ b/qtwayland-fix-inverse-repeat-rate-implementation.patch @@ -0,0 +1,13 @@ +diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp +index eefd048..5819763 100644 +--- a/src/client/qwaylandinputdevice.cpp ++++ b/src/client/qwaylandinputdevice.cpp +@@ -85,7 +85,7 @@ QWaylandInputDevice::Keyboard::Keyboard(QWaylandInputDevice *p) + // or the server didn't send an enter event first. + return; + } +- mRepeatTimer.setInterval(mRepeatRate); ++ mRepeatTimer.setInterval(1000 / mRepeatRate); + handleKey(mRepeatKey.time, QEvent::KeyRelease, mRepeatKey.key, mRepeatKey.modifiers, + mRepeatKey.code, mRepeatKey.nativeVirtualKey, mRepeatKey.nativeModifiers, + mRepeatKey.text, true); diff --git a/qtwayland-implement-primary-selection-unstable-v1.patch b/qtwayland-implement-primary-selection-unstable-v1.patch new file mode 100644 index 0000000..1254a73 --- /dev/null +++ b/qtwayland-implement-primary-selection-unstable-v1.patch @@ -0,0 +1,1630 @@ +diff --git a/src/3rdparty/protocol/qt_attribution.json b/src/3rdparty/protocol/qt_attribution.json +index 7e068f75..e6f90698 100644 +--- a/src/3rdparty/protocol/qt_attribution.json ++++ b/src/3rdparty/protocol/qt_attribution.json +@@ -55,6 +55,23 @@ Copyright © 2012-2013 Collabora, Ltd." + Copyright (c) 2013 BMW Car IT GmbH" + }, + ++ { ++ "Id": "wayland-primary-selection-protocol", ++ "Name": "Wayland Primary Selection Protocol", ++ "QDocModule": "qtwaylandcompositor", ++ "QtUsage": "Used in the Qt Wayland platform plugin", ++ "Files": "wp-primary-selection-unstable-v1.xml", ++ ++ "Description": "The primary selection extension allows copying text by selecting it and pasting it with the middle mouse button.", ++ "Homepage": "https://wayland.freedesktop.org", ++ "Version": "1", ++ "DownloadLocation": "https://cgit.freedesktop.org/wayland/wayland-protocols/plain/unstable/primary-selection/primary-selection-unstable-v1.xml", ++ "LicenseId": "MIT", ++ "License": "MIT License", ++ "LicenseFile": "MIT_LICENSE.txt", ++ "Copyright": "Copyright © 2015 2016 Red Hat" ++ }, ++ + { + "Id": "wayland-scaler-protocol", + "Name": "Wayland Scaler Protocol", +diff --git a/src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml b/src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml +new file mode 100644 +index 00000000..e5a39e34 +--- /dev/null ++++ b/src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml +@@ -0,0 +1,225 @@ ++ ++ ++ ++ Copyright © 2015, 2016 Red Hat ++ ++ Permission is hereby granted, free of charge, to any person obtaining a ++ copy of this software and associated documentation files (the "Software"), ++ to deal in the Software without restriction, including without limitation ++ the rights to use, copy, modify, merge, publish, distribute, sublicense, ++ and/or sell copies of the Software, and to permit persons to whom the ++ Software is furnished to do so, subject to the following conditions: ++ ++ The above copyright notice and this permission notice (including the next ++ paragraph) shall be included in all copies or substantial portions of the ++ Software. ++ ++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ++ DEALINGS IN THE SOFTWARE. ++ ++ ++ ++ This protocol provides the ability to have a primary selection device to ++ match that of the X server. This primary selection is a shortcut to the ++ common clipboard selection, where text just needs to be selected in order ++ to allow copying it elsewhere. The de facto way to perform this action ++ is the middle mouse button, although it is not limited to this one. ++ ++ Clients wishing to honor primary selection should create a primary ++ selection source and set it as the selection through ++ wp_primary_selection_device.set_selection whenever the text selection ++ changes. In order to minimize calls in pointer-driven text selection, ++ it should happen only once after the operation finished. Similarly, ++ a NULL source should be set when text is unselected. ++ ++ wp_primary_selection_offer objects are first announced through the ++ wp_primary_selection_device.data_offer event. Immediately after this event, ++ the primary data offer will emit wp_primary_selection_offer.offer events ++ to let know of the mime types being offered. ++ ++ When the primary selection changes, the client with the keyboard focus ++ will receive wp_primary_selection_device.selection events. Only the client ++ with the keyboard focus will receive such events with a non-NULL ++ wp_primary_selection_offer. Across keyboard focus changes, previously ++ focused clients will receive wp_primary_selection_device.events with a ++ NULL wp_primary_selection_offer. ++ ++ In order to request the primary selection data, the client must pass ++ a recent serial pertaining to the press event that is triggering the ++ operation, if the compositor deems the serial valid and recent, the ++ wp_primary_selection_source.send event will happen in the other end ++ to let the transfer begin. The client owning the primary selection ++ should write the requested data, and close the file descriptor ++ immediately. ++ ++ If the primary selection owner client disappeared during the transfer, ++ the client reading the data will receive a ++ wp_primary_selection_device.selection event with a NULL ++ wp_primary_selection_offer, the client should take this as a hint ++ to finish the reads related to the no longer existing offer. ++ ++ The primary selection owner should be checking for errors during ++ writes, merely cancelling the ongoing transfer if any happened. ++ ++ ++ ++ ++ The primary selection device manager is a singleton global object that ++ provides access to the primary selection. It allows to create ++ wp_primary_selection_source objects, as well as retrieving the per-seat ++ wp_primary_selection_device objects. ++ ++ ++ ++ ++ Create a new primary selection source. ++ ++ ++ ++ ++ ++ ++ Create a new data device for a given seat. ++ ++ ++ ++ ++ ++ ++ ++ Destroy the primary selection device manager. ++ ++ ++ ++ ++ ++ ++ ++ Replaces the current selection. The previous owner of the primary ++ selection will receive a wp_primary_selection_source.cancelled event. ++ ++ To unset the selection, set the source to NULL. ++ ++ ++ ++ ++ ++ ++ ++ Introduces a new wp_primary_selection_offer object that may be used ++ to receive the current primary selection. Immediately following this ++ event, the new wp_primary_selection_offer object will send ++ wp_primary_selection_offer.offer events to describe the offered mime ++ types. ++ ++ ++ ++ ++ ++ ++ The wp_primary_selection_device.selection event is sent to notify the ++ client of a new primary selection. This event is sent after the ++ wp_primary_selection.data_offer event introducing this object, and after ++ the offer has announced its mimetypes through ++ wp_primary_selection_offer.offer. ++ ++ The data_offer is valid until a new offer or NULL is received ++ or until the client loses keyboard focus. The client must destroy the ++ previous selection data_offer, if any, upon receiving this event. ++ ++ ++ ++ ++ ++ ++ Destroy the primary selection device. ++ ++ ++ ++ ++ ++ ++ A wp_primary_selection_offer represents an offer to transfer the contents ++ of the primary selection clipboard to the client. Similar to ++ wl_data_offer, the offer also describes the mime types that the data can ++ be converted to and provides the mechanisms for transferring the data ++ directly to the client. ++ ++ ++ ++ ++ To transfer the contents of the primary selection clipboard, the client ++ issues this request and indicates the mime type that it wants to ++ receive. The transfer happens through the passed file descriptor ++ (typically created with the pipe system call). The source client writes ++ the data in the mime type representation requested and then closes the ++ file descriptor. ++ ++ The receiving client reads from the read end of the pipe until EOF and ++ closes its end, at which point the transfer is complete. ++ ++ ++ ++ ++ ++ ++ ++ Destroy the primary selection offer. ++ ++ ++ ++ ++ ++ Sent immediately after creating announcing the ++ wp_primary_selection_offer through ++ wp_primary_selection_device.data_offer. One event is sent per offered ++ mime type. ++ ++ ++ ++ ++ ++ ++ ++ The source side of a wp_primary_selection_offer, it provides a way to ++ describe the offered data and respond to requests to transfer the ++ requested contents of the primary selection clipboard. ++ ++ ++ ++ ++ This request adds a mime type to the set of mime types advertised to ++ targets. Can be called several times to offer multiple types. ++ ++ ++ ++ ++ ++ ++ Destroy the primary selection source. ++ ++ ++ ++ ++ ++ Request for the current primary selection contents from the client. ++ Send the specified mime type over the passed file descriptor, then ++ close it. ++ ++ ++ ++ ++ ++ ++ ++ This primary selection source is no longer valid. The client should ++ clean up and destroy this primary selection source. ++ ++ ++ ++ +diff --git a/src/client/client.pro b/src/client/client.pro +index 4233ac95..3793cd8e 100644 +--- a/src/client/client.pro ++++ b/src/client/client.pro +@@ -31,6 +31,7 @@ WAYLANDCLIENTSOURCES += \ + ../extensions/touch-extension.xml \ + ../extensions/qt-key-unstable-v1.xml \ + ../extensions/qt-windowmanager.xml \ ++ ../3rdparty/protocol/wp-primary-selection-unstable-v1.xml \ + ../3rdparty/protocol/text-input-unstable-v2.xml \ + ../3rdparty/protocol/xdg-output-unstable-v1.xml \ + ../3rdparty/protocol/wayland.xml +@@ -116,6 +117,11 @@ qtConfig(wayland-datadevice) { + qwaylanddatasource.cpp + } + ++qtConfig(wayland-client-primary-selection) { ++ HEADERS += qwaylandprimaryselectionv1_p.h ++ SOURCES += qwaylandprimaryselectionv1.cpp ++} ++ + qtConfig(draganddrop) { + HEADERS += \ + qwaylanddnd_p.h +diff --git a/src/client/configure.json b/src/client/configure.json +index 91024c9d..b63031f2 100644 +--- a/src/client/configure.json ++++ b/src/client/configure.json +@@ -93,6 +93,11 @@ + "condition": "features.draganddrop || features.clipboard", + "output": [ "privateFeature" ] + }, ++ "wayland-client-primary-selection": { ++ "label": "primary-selection clipboard", ++ "condition": "features.clipboard", ++ "output": [ "privateFeature" ] ++ }, + "wayland-client-fullscreen-shell-v1": { + "label": "fullscreen-shell-v1", + "condition": "features.wayland-client", +diff --git a/src/client/qwaylandclipboard.cpp b/src/client/qwaylandclipboard.cpp +index 60820da9..369c6ec0 100644 +--- a/src/client/qwaylandclipboard.cpp ++++ b/src/client/qwaylandclipboard.cpp +@@ -43,6 +43,9 @@ + #include "qwaylanddataoffer_p.h" + #include "qwaylanddatasource_p.h" + #include "qwaylanddatadevice_p.h" ++#if QT_CONFIG(wayland_client_primary_selection) ++#include "qwaylandprimaryselectionv1_p.h" ++#endif + + QT_BEGIN_NAMESPACE + +@@ -59,44 +62,74 @@ QWaylandClipboard::~QWaylandClipboard() + + QMimeData *QWaylandClipboard::mimeData(QClipboard::Mode mode) + { +- if (mode != QClipboard::Clipboard) ++ auto *seat = mDisplay->currentInputDevice(); ++ if (!seat) + return &m_emptyData; + +- QWaylandInputDevice *inputDevice = mDisplay->currentInputDevice(); +- if (!inputDevice || !inputDevice->dataDevice()) ++ switch (mode) { ++ case QClipboard::Clipboard: ++ if (auto *dataDevice = seat->dataDevice()) { ++ if (auto *source = dataDevice->selectionSource()) ++ return source->mimeData(); ++ if (auto *offer = dataDevice->selectionOffer()) ++ return offer->mimeData(); ++ } ++ return &m_emptyData; ++ case QClipboard::Selection: ++#if QT_CONFIG(wayland_client_primary_selection) ++ if (auto *selectionDevice = seat->primarySelectionDevice()) { ++ if (auto *source = selectionDevice->selectionSource()) ++ return source->mimeData(); ++ if (auto *offer = selectionDevice->selectionOffer()) ++ return offer->mimeData(); ++ } ++#endif ++ return &m_emptyData; ++ default: + return &m_emptyData; +- +- QWaylandDataSource *source = inputDevice->dataDevice()->selectionSource(); +- if (source) { +- return source->mimeData(); + } +- +- if (inputDevice->dataDevice()->selectionOffer()) +- return inputDevice->dataDevice()->selectionOffer()->mimeData(); +- +- return &m_emptyData; + } + + void QWaylandClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) + { +- if (mode != QClipboard::Clipboard) +- return; +- +- QWaylandInputDevice *inputDevice = mDisplay->currentInputDevice(); +- if (!inputDevice || !inputDevice->dataDevice()) ++ auto *seat = mDisplay->currentInputDevice(); ++ if (!seat) + return; + + static const QString plain = QStringLiteral("text/plain"); + static const QString utf8 = QStringLiteral("text/plain;charset=utf-8"); ++ + if (data && data->hasFormat(plain) && !data->hasFormat(utf8)) + data->setData(utf8, data->data(plain)); +- inputDevice->dataDevice()->setSelectionSource(data ? new QWaylandDataSource(mDisplay->dndSelectionHandler(), data) : nullptr); + +- emitChanged(mode); ++ switch (mode) { ++ case QClipboard::Clipboard: ++ if (auto *dataDevice = seat->dataDevice()) { ++ dataDevice->setSelectionSource(data ? new QWaylandDataSource(mDisplay->dndSelectionHandler(), data) : nullptr); ++ emitChanged(mode); ++ } ++ break; ++ case QClipboard::Selection: ++#if QT_CONFIG(wayland_client_primary_selection) ++ if (auto *selectionDevice = seat->primarySelectionDevice()) { ++ selectionDevice->setSelectionSource(data ? new QWaylandPrimarySelectionSourceV1(mDisplay->primarySelectionManager(), data) : nullptr); ++ emitChanged(mode); ++ } ++#endif ++ break; ++ default: ++ break; ++ } + } + + bool QWaylandClipboard::supportsMode(QClipboard::Mode mode) const + { ++#if QT_CONFIG(wayland_client_primary_selection) ++ if (mode == QClipboard::Selection) { ++ auto *seat = mDisplay->currentInputDevice(); ++ return seat && seat->primarySelectionDevice(); ++ } ++#endif + return mode == QClipboard::Clipboard; + } + +diff --git a/src/client/qwaylanddataoffer.cpp b/src/client/qwaylanddataoffer.cpp +index 3da16ed0..4c06277f 100644 +--- a/src/client/qwaylanddataoffer.cpp ++++ b/src/client/qwaylanddataoffer.cpp +@@ -58,7 +58,8 @@ static QString utf8Text() + + QWaylandDataOffer::QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer) + : QtWayland::wl_data_offer(offer) +- , m_mimeData(new QWaylandMimeData(this, display)) ++ , m_display(display) ++ , m_mimeData(new QWaylandMimeData(this)) + { + } + +@@ -81,14 +82,19 @@ QMimeData *QWaylandDataOffer::mimeData() + return m_mimeData.data(); + } + ++void QWaylandDataOffer::startReceiving(const QString &mimeType, int fd) ++{ ++ receive(mimeType, fd); ++ wl_display_flush(m_display->wl_display()); ++} ++ + void QWaylandDataOffer::data_offer_offer(const QString &mime_type) + { + m_mimeData->appendFormat(mime_type); + } + +-QWaylandMimeData::QWaylandMimeData(QWaylandDataOffer *dataOffer, QWaylandDisplay *display) ++QWaylandMimeData::QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer) + : m_dataOffer(dataOffer) +- , m_display(display) + { + } + +@@ -140,8 +146,7 @@ QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QVariant::T + return QVariant(); + } + +- m_dataOffer->receive(mime, pipefd[1]); +- wl_display_flush(m_display->wl_display()); ++ m_dataOffer->startReceiving(mime, pipefd[1]); + + close(pipefd[1]); + +diff --git a/src/client/qwaylanddataoffer_p.h b/src/client/qwaylanddataoffer_p.h +index 5412400a..9cf1483c 100644 +--- a/src/client/qwaylanddataoffer_p.h ++++ b/src/client/qwaylanddataoffer_p.h +@@ -65,27 +65,40 @@ namespace QtWaylandClient { + class QWaylandDisplay; + class QWaylandMimeData; + +-class Q_WAYLAND_CLIENT_EXPORT QWaylandDataOffer : public QtWayland::wl_data_offer ++class QWaylandAbstractDataOffer ++{ ++public: ++ virtual void startReceiving(const QString &mimeType, int fd) = 0; ++ virtual QMimeData *mimeData() = 0; ++ ++ virtual ~QWaylandAbstractDataOffer() = default; ++}; ++ ++class Q_WAYLAND_CLIENT_EXPORT QWaylandDataOffer ++ : public QtWayland::wl_data_offer // needs to be the first because we do static casts from the user pointer to the wrapper ++ , public QWaylandAbstractDataOffer + { + public: + explicit QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer); + ~QWaylandDataOffer() override; ++ QMimeData *mimeData() override; + + QString firstFormat() const; + +- QMimeData *mimeData(); ++ void startReceiving(const QString &mimeType, int fd) override; + + protected: + void data_offer_offer(const QString &mime_type) override; + + private: ++ QWaylandDisplay *m_display = nullptr; + QScopedPointer m_mimeData; + }; + + + class QWaylandMimeData : public QInternalMimeData { + public: +- explicit QWaylandMimeData(QWaylandDataOffer *dataOffer, QWaylandDisplay *display); ++ explicit QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer); + ~QWaylandMimeData() override; + + void appendFormat(const QString &mimeType); +@@ -98,13 +111,12 @@ class QWaylandMimeData : public QInternalMimeData { + private: + int readData(int fd, QByteArray &data) const; + +- mutable QWaylandDataOffer *m_dataOffer = nullptr; +- QWaylandDisplay *m_display = nullptr; ++ QWaylandAbstractDataOffer *m_dataOffer = nullptr; + mutable QStringList m_types; + mutable QHash m_data; + }; + +-} ++} // namespace QtWaylandClient + + QT_END_NAMESPACE + #endif +diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp +index 78524f6f..a1c177ec 100644 +--- a/src/client/qwaylanddisplay.cpp ++++ b/src/client/qwaylanddisplay.cpp +@@ -51,7 +51,10 @@ + #if QT_CONFIG(wayland_datadevice) + #include "qwaylanddatadevicemanager_p.h" + #include "qwaylanddatadevice_p.h" +-#endif ++#endif // QT_CONFIG(wayland_datadevice) ++#if QT_CONFIG(wayland_client_primary_selection) ++#include "qwaylandprimaryselectionv1_p.h" ++#endif // QT_CONFIG(wayland_client_primary_selection) + #if QT_CONFIG(cursor) + #include + #endif +@@ -68,6 +71,7 @@ + #include "qwaylandqtkey_p.h" + + #include ++#include + + #include + +@@ -307,6 +311,10 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin + mTouchExtension.reset(new QWaylandTouchExtension(this, id)); + } else if (interface == QStringLiteral("zqt_key_v1")) { + mQtKeyExtension.reset(new QWaylandQtKeyExtension(this, id)); ++#if QT_CONFIG(wayland_client_primary_selection) ++ } else if (interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) { ++ mPrimarySelectionManager.reset(new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1)); ++#endif + } else if (interface == QStringLiteral("zwp_text_input_manager_v2") && !mClientSideInputContextRequested) { + mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) +diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h +index 7cfbc19b..a525817d 100644 +--- a/src/client/qwaylanddisplay_p.h ++++ b/src/client/qwaylanddisplay_p.h +@@ -93,6 +93,9 @@ class QWaylandScreen; + class QWaylandClientBufferIntegration; + class QWaylandWindowManagerIntegration; + class QWaylandDataDeviceManager; ++#if QT_CONFIG(wayland_client_primary_selection) ++class QWaylandPrimarySelectionDeviceManagerV1; ++#endif + class QWaylandTouchExtension; + class QWaylandQtKeyExtension; + class QWaylandWindow; +@@ -148,6 +151,9 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandDisplay : public QObject, public QtWayland + QWaylandInputDevice *currentInputDevice() const { return defaultInputDevice(); } + #if QT_CONFIG(wayland_datadevice) + QWaylandDataDeviceManager *dndSelectionHandler() const { return mDndSelectionHandler.data(); } ++#endif ++#if QT_CONFIG(wayland_client_primary_selection) ++ QWaylandPrimarySelectionDeviceManagerV1 *primarySelectionManager() const { return mPrimarySelectionManager.data(); } + #endif + QtWayland::qt_surface_extension *windowExtension() const { return mWindowExtension.data(); } + QWaylandTouchExtension *touchExtension() const { return mTouchExtension.data(); } +@@ -236,6 +242,9 @@ public slots: + QScopedPointer mTouchExtension; + QScopedPointer mQtKeyExtension; + QScopedPointer mWindowManagerIntegration; ++#if QT_CONFIG(wayland_client_primary_selection) ++ QScopedPointer mPrimarySelectionManager; ++#endif + QScopedPointer mTextInputManager; + QScopedPointer mHardwareIntegration; + QScopedPointer mXdgOutputManager; +diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp +index 8f3df8e4..eefd0488 100644 +--- a/src/client/qwaylandinputdevice.cpp ++++ b/src/client/qwaylandinputdevice.cpp +@@ -46,6 +46,9 @@ + #include "qwaylanddatadevice_p.h" + #include "qwaylanddatadevicemanager_p.h" + #endif ++#if QT_CONFIG(wayland_client_primary_selection) ++#include "qwaylandprimaryselectionv1_p.h" ++#endif + #include "qwaylandtouch_p.h" + #include "qwaylandscreen_p.h" + #include "qwaylandcursor_p.h" +@@ -364,6 +367,12 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, + } + #endif + ++#if QT_CONFIG(wayland_client_primary_selection) ++ // TODO: Could probably decouple this more if there was a signal for new seat added ++ if (auto *psm = mQDisplay->primarySelectionManager()) ++ setPrimarySelectionDevice(psm->createDevice(this)); ++#endif ++ + if (mQDisplay->textInputManager()) + mTextInput.reset(new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat()))); + +@@ -447,6 +456,18 @@ QWaylandDataDevice *QWaylandInputDevice::dataDevice() const + } + #endif + ++#if QT_CONFIG(wayland_client_primary_selection) ++void QWaylandInputDevice::setPrimarySelectionDevice(QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice) ++{ ++ mPrimarySelectionDevice.reset(primarySelectionDevice); ++} ++ ++QWaylandPrimarySelectionDeviceV1 *QWaylandInputDevice::primarySelectionDevice() const ++{ ++ return mPrimarySelectionDevice.data(); ++} ++#endif ++ + void QWaylandInputDevice::setTextInput(QWaylandTextInput *textInput) + { + mTextInput.reset(textInput); +@@ -937,6 +958,10 @@ void QWaylandInputDevice::Keyboard::handleFocusLost() + #if QT_CONFIG(clipboard) + if (auto *dataDevice = mParent->dataDevice()) + dataDevice->invalidateSelectionOffer(); ++#endif ++#if QT_CONFIG(wayland_client_primary_selection) ++ if (auto *device = mParent->primarySelectionDevice()) ++ device->invalidateSelectionOffer(); + #endif + mParent->mQDisplay->handleKeyboardFocusChanged(mParent); + mRepeatTimer.stop(); +diff --git a/src/client/qwaylandinputdevice_p.h b/src/client/qwaylandinputdevice_p.h +index 143e1122..cfaa5d7b 100644 +--- a/src/client/qwaylandinputdevice_p.h ++++ b/src/client/qwaylandinputdevice_p.h +@@ -77,11 +77,17 @@ struct wl_cursor_image; + + QT_BEGIN_NAMESPACE + ++namespace QtWayland { ++class zwp_primary_selection_device_v1; ++} //namespace QtWayland ++ + namespace QtWaylandClient { + +-class QWaylandWindow; +-class QWaylandDisplay; + class QWaylandDataDevice; ++class QWaylandDisplay; ++#if QT_CONFIG(wayland_client_primary_selection) ++class QWaylandPrimarySelectionDeviceV1; ++#endif + class QWaylandTextInput; + #if QT_CONFIG(cursor) + class QWaylandCursorTheme; +@@ -115,6 +121,11 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice + QWaylandDataDevice *dataDevice() const; + #endif + ++#if QT_CONFIG(wayland_client_primary_selection) ++ void setPrimarySelectionDevice(QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice); ++ QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice() const; ++#endif ++ + void setTextInput(QWaylandTextInput *textInput); + QWaylandTextInput *textInput() const; + +@@ -157,6 +168,10 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice + QWaylandDataDevice *mDataDevice = nullptr; + #endif + ++#if QT_CONFIG(wayland_client_primary_selection) ++ QScopedPointer mPrimarySelectionDevice; ++#endif ++ + Keyboard *mKeyboard = nullptr; + Pointer *mPointer = nullptr; + Touch *mTouch = nullptr; +diff --git a/src/client/qwaylandprimaryselectionv1.cpp b/src/client/qwaylandprimaryselectionv1.cpp +new file mode 100644 +index 00000000..3ddf6dac +--- /dev/null ++++ b/src/client/qwaylandprimaryselectionv1.cpp +@@ -0,0 +1,162 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2019 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the plugins of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:LGPL$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU Lesser General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU Lesser ++** General Public License version 3 as published by the Free Software ++** Foundation and appearing in the file LICENSE.LGPL3 included in the ++** packaging of this file. Please review the following information to ++** ensure the GNU Lesser General Public License version 3 requirements ++** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 2.0 or (at your option) the GNU General ++** Public license version 3 or any later version approved by the KDE Free ++** Qt Foundation. The licenses are as published by the Free Software ++** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-2.0.html and ++** https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#include "qwaylandprimaryselectionv1_p.h" ++#include "qwaylandinputdevice_p.h" ++#include "qwaylanddisplay_p.h" ++#include "qwaylandmimehelper_p.h" ++ ++#include ++ ++#include ++ ++QT_BEGIN_NAMESPACE ++ ++namespace QtWaylandClient { ++ ++QWaylandPrimarySelectionDeviceManagerV1::QWaylandPrimarySelectionDeviceManagerV1(QWaylandDisplay *display, uint id, uint version) ++ : zwp_primary_selection_device_manager_v1(display->wl_registry(), id, qMin(version, uint(1))) ++ , m_display(display) ++{ ++ // Create devices for all seats. ++ // This only works if we get the global before all devices ++ const auto seats = m_display->inputDevices(); ++ for (auto *seat : seats) ++ seat->setPrimarySelectionDevice(createDevice(seat)); ++} ++ ++QWaylandPrimarySelectionDeviceV1 *QWaylandPrimarySelectionDeviceManagerV1::createDevice(QWaylandInputDevice *seat) ++{ ++ return new QWaylandPrimarySelectionDeviceV1(this, seat); ++} ++ ++QWaylandPrimarySelectionOfferV1::QWaylandPrimarySelectionOfferV1(QWaylandDisplay *display, ::zwp_primary_selection_offer_v1 *offer) ++ : zwp_primary_selection_offer_v1(offer) ++ , m_display(display) ++ , m_mimeData(new QWaylandMimeData(this)) ++{} ++ ++void QWaylandPrimarySelectionOfferV1::startReceiving(const QString &mimeType, int fd) ++{ ++ receive(mimeType, fd); ++ wl_display_flush(m_display->wl_display()); ++} ++ ++void QWaylandPrimarySelectionOfferV1::zwp_primary_selection_offer_v1_offer(const QString &mime_type) ++{ ++ m_mimeData->appendFormat(mime_type); ++} ++ ++QWaylandPrimarySelectionDeviceV1::QWaylandPrimarySelectionDeviceV1( ++ QWaylandPrimarySelectionDeviceManagerV1 *manager, QWaylandInputDevice *seat) ++ : QtWayland::zwp_primary_selection_device_v1(manager->get_device(seat->wl_seat())) ++ , m_display(manager->display()) ++ , m_seat(seat) ++{ ++} ++ ++QWaylandPrimarySelectionDeviceV1::~QWaylandPrimarySelectionDeviceV1() ++{ ++ destroy(); ++} ++ ++void QWaylandPrimarySelectionDeviceV1::setSelectionSource(QWaylandPrimarySelectionSourceV1 *source) ++{ ++ if (source) { ++ connect(source, &QWaylandPrimarySelectionSourceV1::cancelled, this, [this]() { ++ m_selectionSource.reset(); ++ QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); ++ }); ++ } ++ set_selection(source ? source->object() : nullptr, m_seat->serial()); ++ m_selectionSource.reset(source); ++} ++ ++void QWaylandPrimarySelectionDeviceV1::zwp_primary_selection_device_v1_data_offer(zwp_primary_selection_offer_v1 *offer) ++{ ++ new QWaylandPrimarySelectionOfferV1(m_display, offer); ++} ++ ++void QWaylandPrimarySelectionDeviceV1::zwp_primary_selection_device_v1_selection(zwp_primary_selection_offer_v1 *id) ++{ ++ ++ if (id) ++ m_selectionOffer.reset(static_cast(zwp_primary_selection_offer_v1_get_user_data(id))); ++ else ++ m_selectionOffer.reset(); ++ ++ QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); ++} ++ ++QWaylandPrimarySelectionSourceV1::QWaylandPrimarySelectionSourceV1(QWaylandPrimarySelectionDeviceManagerV1 *manager, QMimeData *mimeData) ++ : QtWayland::zwp_primary_selection_source_v1(manager->create_source()) ++ , m_mimeData(mimeData) ++{ ++ if (!mimeData) ++ return; ++ for (auto &format : mimeData->formats()) ++ offer(format); ++} ++ ++QWaylandPrimarySelectionSourceV1::~QWaylandPrimarySelectionSourceV1() ++{ ++ destroy(); ++} ++ ++void QWaylandPrimarySelectionSourceV1::zwp_primary_selection_source_v1_send(const QString &mime_type, int32_t fd) ++{ ++ QByteArray content = QWaylandMimeHelper::getByteArray(m_mimeData, mime_type); ++ if (!content.isEmpty()) { ++ // Create a sigpipe handler that does nothing, or clients may be forced to terminate ++ // if the pipe is closed in the other end. ++ struct sigaction action, oldAction; ++ action.sa_handler = SIG_IGN; ++ sigemptyset (&action.sa_mask); ++ action.sa_flags = 0; ++ ++ sigaction(SIGPIPE, &action, &oldAction); ++ write(fd, content.constData(), size_t(content.size())); ++ sigaction(SIGPIPE, &oldAction, nullptr); ++ } ++ close(fd); ++} ++ ++} // namespace QtWaylandClient ++ ++QT_END_NAMESPACE +diff --git a/src/client/qwaylandprimaryselectionv1_p.h b/src/client/qwaylandprimaryselectionv1_p.h +new file mode 100644 +index 00000000..b165c51b +--- /dev/null ++++ b/src/client/qwaylandprimaryselectionv1_p.h +@@ -0,0 +1,148 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2019 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the plugins of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:LGPL$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU Lesser General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU Lesser ++** General Public License version 3 as published by the Free Software ++** Foundation and appearing in the file LICENSE.LGPL3 included in the ++** packaging of this file. Please review the following information to ++** ensure the GNU Lesser General Public License version 3 requirements ++** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 2.0 or (at your option) the GNU General ++** Public license version 3 or any later version approved by the KDE Free ++** Qt Foundation. The licenses are as published by the Free Software ++** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-2.0.html and ++** https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#ifndef QWAYLANDPRIMARYSELECTIONV1_P_H ++#define QWAYLANDPRIMARYSELECTIONV1_P_H ++ ++// ++// W A R N I N G ++// ------------- ++// ++// This file is not part of the Qt API. It exists purely as an ++// implementation detail. This header file may change from version to ++// version without notice, or even be removed. ++// ++// We mean it. ++// ++ ++#include ++ ++#include ++#include ++ ++#include ++ ++QT_REQUIRE_CONFIG(wayland_client_primary_selection); ++ ++QT_BEGIN_NAMESPACE ++ ++class QMimeData; ++ ++namespace QtWaylandClient { ++ ++class QWaylandInputDevice; ++class QWaylandPrimarySelectionDeviceV1; ++ ++class QWaylandPrimarySelectionDeviceManagerV1 : public QtWayland::zwp_primary_selection_device_manager_v1 ++{ ++public: ++ explicit QWaylandPrimarySelectionDeviceManagerV1(QWaylandDisplay *display, uint id, uint version); ++ QWaylandPrimarySelectionDeviceV1 *createDevice(QWaylandInputDevice *seat); ++ QWaylandDisplay *display() const { return m_display; } ++ ++private: ++ QWaylandDisplay *m_display = nullptr; ++}; ++ ++class QWaylandPrimarySelectionOfferV1 : public QtWayland::zwp_primary_selection_offer_v1, public QWaylandAbstractDataOffer ++{ ++public: ++ explicit QWaylandPrimarySelectionOfferV1(QWaylandDisplay *display, ::zwp_primary_selection_offer_v1 *offer); ++ ~QWaylandPrimarySelectionOfferV1() override { destroy(); } ++ void startReceiving(const QString &mimeType, int fd) override; ++ QMimeData *mimeData() override { return m_mimeData.data(); } ++ ++protected: ++ void zwp_primary_selection_offer_v1_offer(const QString &mime_type) override; ++ ++private: ++ QWaylandDisplay *m_display = nullptr; ++ QScopedPointer m_mimeData; ++}; ++ ++class Q_WAYLAND_CLIENT_EXPORT QWaylandPrimarySelectionSourceV1 : public QObject, public QtWayland::zwp_primary_selection_source_v1 ++{ ++ Q_OBJECT ++public: ++ explicit QWaylandPrimarySelectionSourceV1(QWaylandPrimarySelectionDeviceManagerV1 *manager, QMimeData *mimeData); ++ ~QWaylandPrimarySelectionSourceV1() override; ++ ++ QMimeData *mimeData() const { return m_mimeData; } ++ ++signals: ++ void cancelled(); ++ ++protected: ++ void zwp_primary_selection_source_v1_send(const QString &mime_type, int32_t fd) override; ++ void zwp_primary_selection_source_v1_cancelled() override { emit cancelled(); } ++ ++private: ++ QWaylandDisplay *m_display = nullptr; ++ QMimeData *m_mimeData = nullptr; ++}; ++ ++class QWaylandPrimarySelectionDeviceV1 : public QObject, public QtWayland::zwp_primary_selection_device_v1 ++{ ++ Q_OBJECT ++ QWaylandPrimarySelectionDeviceV1(QWaylandPrimarySelectionDeviceManagerV1 *manager, QWaylandInputDevice *seat); ++ ++public: ++ ~QWaylandPrimarySelectionDeviceV1() override; ++ QWaylandPrimarySelectionOfferV1 *selectionOffer() const { return m_selectionOffer.data(); } ++ void invalidateSelectionOffer() { m_selectionOffer.reset(); } ++ QWaylandPrimarySelectionSourceV1 *selectionSource() const { return m_selectionSource.data(); } ++ void setSelectionSource(QWaylandPrimarySelectionSourceV1 *source); ++ ++protected: ++ void zwp_primary_selection_device_v1_data_offer(struct ::zwp_primary_selection_offer_v1 *offer) override; ++ void zwp_primary_selection_device_v1_selection(struct ::zwp_primary_selection_offer_v1 *id) override; ++ ++private: ++ QWaylandDisplay *m_display = nullptr; ++ QWaylandInputDevice *m_seat = nullptr; ++ QScopedPointer m_selectionOffer; ++ QScopedPointer m_selectionSource; ++ friend class QWaylandPrimarySelectionDeviceManagerV1; ++}; ++ ++} // namespace QtWaylandClient ++ ++QT_END_NAMESPACE ++ ++#endif // QWAYLANDPRIMARYSELECTIONV1_P_H +diff --git a/sync.profile b/sync.profile +index 087bfcf5..6bbb18ef 100644 +--- a/sync.profile ++++ b/sync.profile +@@ -27,6 +27,7 @@ + "^qwayland-text-input-unstable-v2.h", + "^qwayland-touch-extension.h", + "^qwayland-wayland.h", ++ "^qwayland-wp-primary-selection-unstable-v1.h", + "^qwayland-xdg-output-unstable-v1.h", + "^wayland-hardware-integration-client-protocol.h", + "^wayland-qt-windowmanager-client-protocol.h", +@@ -36,6 +37,7 @@ + "^wayland-text-input-unstable-v2-client-protocol.h", + "^wayland-touch-extension-client-protocol.h", + "^wayland-wayland-client-protocol.h", ++ "^wayland-wp-primary-selection-unstable-v1-client-protocol.h", + "^wayland-xdg-output-unstable-v1-client-protocol.h", + ], + "$basedir/src/plugins/shellintegration/xdg-shell" => [ +diff --git a/tests/auto/client/client.pro b/tests/auto/client/client.pro +index 06c1cb87..80694273 100644 +--- a/tests/auto/client/client.pro ++++ b/tests/auto/client/client.pro +@@ -6,6 +6,7 @@ SUBDIRS += \ + fullscreenshellv1 \ + iviapplication \ + output \ ++ primaryselectionv1 \ + seatv4 \ + surface \ + wl_connect \ +diff --git a/tests/auto/client/primaryselectionv1/primaryselectionv1.pro b/tests/auto/client/primaryselectionv1/primaryselectionv1.pro +new file mode 100644 +index 00000000..9d00562d +--- /dev/null ++++ b/tests/auto/client/primaryselectionv1/primaryselectionv1.pro +@@ -0,0 +1,7 @@ ++include (../shared/shared.pri) ++ ++WAYLANDSERVERSOURCES += \ ++ $$PWD/../../../../src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml ++ ++TARGET = tst_primaryselectionv1 ++SOURCES += tst_primaryselectionv1.cpp +diff --git a/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp b/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp +new file mode 100644 +index 00000000..281e4c5d +--- /dev/null ++++ b/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp +@@ -0,0 +1,466 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2018 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the test suite of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3 as published by the Free Software ++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#include "mockcompositor.h" ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++using namespace MockCompositor; ++ ++constexpr int primarySelectionVersion = 1; // protocol VERSION, not the name suffix (_v1) ++ ++class PrimarySelectionDeviceV1; ++class PrimarySelectionDeviceManagerV1; ++ ++class PrimarySelectionOfferV1 : public QObject, public QtWaylandServer::zwp_primary_selection_offer_v1 ++{ ++ Q_OBJECT ++public: ++ explicit PrimarySelectionOfferV1(PrimarySelectionDeviceV1 *device, wl_client *client, int version) ++ : zwp_primary_selection_offer_v1(client, 0, version) ++ , m_device(device) ++ {} ++ void send_offer() = delete; ++ void sendOffer(const QString &offer) ++ { ++ zwp_primary_selection_offer_v1::send_offer(offer); ++ m_mimeTypes << offer; ++ } ++ ++ PrimarySelectionDeviceV1 *m_device = nullptr; ++ QStringList m_mimeTypes; ++ ++signals: ++ void receive(QString mimeType, int fd); ++ ++protected: ++ void zwp_primary_selection_offer_v1_destroy_resource(Resource *resource) override ++ { ++ Q_UNUSED(resource); ++ delete this; ++ } ++ ++ void zwp_primary_selection_offer_v1_receive(Resource *resource, const QString &mime_type, int32_t fd) override ++ { ++ Q_UNUSED(resource); ++ QTRY_VERIFY(m_mimeTypes.contains(mime_type)); ++ emit receive(mime_type, fd); ++ } ++ ++ void zwp_primary_selection_offer_v1_destroy(Resource *resource) override; ++}; ++ ++class PrimarySelectionSourceV1 : public QObject, public QtWaylandServer::zwp_primary_selection_source_v1 ++{ ++ Q_OBJECT ++public: ++ explicit PrimarySelectionSourceV1(wl_client *client, int id, int version) ++ : zwp_primary_selection_source_v1(client, id, version) ++ { ++ } ++ QStringList m_offers; ++protected: ++ void zwp_primary_selection_source_v1_destroy_resource(Resource *resource) override ++ { ++ Q_UNUSED(resource); ++ delete this; ++ } ++ void zwp_primary_selection_source_v1_offer(Resource *resource, const QString &mime_type) override ++ { ++ Q_UNUSED(resource); ++ m_offers << mime_type; ++ } ++ void zwp_primary_selection_source_v1_destroy(Resource *resource) override ++ { ++ wl_resource_destroy(resource->handle); ++ } ++}; ++ ++class PrimarySelectionDeviceV1 : public QObject, public QtWaylandServer::zwp_primary_selection_device_v1 ++{ ++ Q_OBJECT ++public: ++ explicit PrimarySelectionDeviceV1(PrimarySelectionDeviceManagerV1 *manager, Seat *seat) ++ : m_manager(manager) ++ , m_seat(seat) ++ {} ++ ++ void send_data_offer(::wl_resource *resource) = delete; ++ ++ PrimarySelectionOfferV1 *sendDataOffer(::wl_client *client, const QStringList &mimeTypes = {}); ++ ++ PrimarySelectionOfferV1 *sendDataOffer(const QStringList &mimeTypes = {}) // creates a new offer for the focused surface and sends it ++ { ++ Q_ASSERT(m_seat->m_capabilities & Seat::capability_keyboard); ++ Q_ASSERT(m_seat->m_keyboard->m_enteredSurface); ++ auto *client = m_seat->m_keyboard->m_enteredSurface->resource()->client(); ++ return sendDataOffer(client, mimeTypes); ++ } ++ ++ void send_selection(::wl_resource *resource) = delete; ++ void sendSelection(PrimarySelectionOfferV1 *offer) ++ { ++ auto *client = offer->resource()->client(); ++ for (auto *resource : resourceMap().values(client)) ++ zwp_primary_selection_device_v1::send_selection(resource->handle, offer->resource()->handle); ++ m_sentSelectionOffers << offer; ++ } ++ ++ PrimarySelectionDeviceManagerV1 *m_manager = nullptr; ++ Seat *m_seat = nullptr; ++ QVector m_sentSelectionOffers; ++ PrimarySelectionSourceV1 *m_selectionSource = nullptr; ++ uint m_serial = 0; ++ ++protected: ++ void zwp_primary_selection_device_v1_set_selection(Resource *resource, ::wl_resource *source, uint32_t serial) override ++ { ++ Q_UNUSED(resource); ++ m_selectionSource = fromResource(source); ++ m_serial = serial; ++ } ++ void zwp_primary_selection_device_v1_destroy(Resource *resource) override ++ { ++ wl_resource_destroy(resource->handle); ++ } ++ void zwp_primary_selection_device_v1_destroy_resource(Resource *resource) override ++ { ++ Q_UNUSED(resource); ++ delete this; ++ } ++}; ++ ++class PrimarySelectionDeviceManagerV1 : public Global, public QtWaylandServer::zwp_primary_selection_device_manager_v1 ++{ ++ Q_OBJECT ++public: ++ explicit PrimarySelectionDeviceManagerV1(CoreCompositor *compositor, int version = 1) ++ : QtWaylandServer::zwp_primary_selection_device_manager_v1(compositor->m_display, version) ++ , m_version(version) ++ {} ++ bool isClean() override ++ { ++ for (auto *device : qAsConst(m_devices)) { ++ // The client should not leak selection offers, i.e. if this fails, there is a missing ++ // zwp_primary_selection_offer_v1.destroy request ++ if (!device->m_sentSelectionOffers.empty()) ++ return false; ++ } ++ return true; ++ } ++ ++ PrimarySelectionDeviceV1 *deviceFor(Seat *seat) ++ { ++ Q_ASSERT(seat); ++ if (auto *device = m_devices.value(seat, nullptr)) ++ return device; ++ ++ auto *device = new PrimarySelectionDeviceV1(this, seat); ++ m_devices[seat] = device; ++ return device; ++ } ++ ++ int m_version = 1; // TODO: Remove on libwayland upgrade ++ QMap m_devices; ++ QVector m_sources; ++protected: ++ void zwp_primary_selection_device_manager_v1_destroy(Resource *resource) override ++ { ++ // The protocol doesn't say whether managed objects should be destroyed as well, ++ // so leave them alone, they'll be cleaned up in the destructor anyway ++ wl_resource_destroy(resource->handle); ++ } ++ ++ void zwp_primary_selection_device_manager_v1_create_source(Resource *resource, uint32_t id) override ++ { ++ int version = m_version; ++ m_sources << new PrimarySelectionSourceV1(resource->client(), id, version); ++ } ++ void zwp_primary_selection_device_manager_v1_get_device(Resource *resource, uint32_t id, ::wl_resource *seatResource) override ++ { ++ auto *seat = fromResource(seatResource); ++ QVERIFY(seat); ++ auto *device = deviceFor(seat); ++ device->add(resource->client(), id, resource->version()); ++ } ++}; ++ ++PrimarySelectionOfferV1 *PrimarySelectionDeviceV1::sendDataOffer(wl_client *client, const QStringList &mimeTypes) ++{ ++ Q_ASSERT(client); ++ auto *offer = new PrimarySelectionOfferV1(this, client, m_manager->m_version); ++ for (auto *resource : resourceMap().values(client)) ++ zwp_primary_selection_device_v1::send_data_offer(resource->handle, offer->resource()->handle); ++ for (const auto &mimeType : mimeTypes) ++ offer->sendOffer(mimeType); ++ return offer; ++} ++ ++void PrimarySelectionOfferV1::zwp_primary_selection_offer_v1_destroy(QtWaylandServer::zwp_primary_selection_offer_v1::Resource *resource) ++{ ++ bool removed = m_device->m_sentSelectionOffers.removeOne(this); ++ QVERIFY(removed); ++ wl_resource_destroy(resource->handle); ++} ++ ++class PrimarySelectionCompositor : public DefaultCompositor { ++public: ++ explicit PrimarySelectionCompositor() ++ { ++ exec([this] { ++ m_config.autoConfigure = true; ++ add(primarySelectionVersion); ++ }); ++ } ++ PrimarySelectionDeviceV1 *primarySelectionDevice(int i = 0) { ++ return get()->deviceFor(get(i)); ++ } ++}; ++ ++class tst_primaryselectionv1 : public QObject, private PrimarySelectionCompositor ++{ ++ Q_OBJECT ++private slots: ++ void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } ++ void initTestCase(); ++ void bindsToManager(); ++ void createsPrimaryDevice(); ++ void createsPrimaryDeviceForNewSeats(); ++ void pasteAscii(); ++ void pasteUtf8(); ++ void destroysPreviousSelection(); ++ void copy(); ++}; ++ ++void tst_primaryselectionv1::initTestCase() ++{ ++ QCOMPOSITOR_TRY_VERIFY(pointer()); ++ QCOMPOSITOR_TRY_VERIFY(!pointer()->resourceMap().empty()); ++ QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 4); ++ ++ QCOMPOSITOR_TRY_VERIFY(keyboard()); ++} ++ ++void tst_primaryselectionv1::bindsToManager() ++{ ++ QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); ++ QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().first()->version(), primarySelectionVersion); ++} ++ ++void tst_primaryselectionv1::createsPrimaryDevice() ++{ ++ QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()); ++ QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()->resourceMap().contains(client())); ++ QCOMPOSITOR_TRY_COMPARE(primarySelectionDevice()->resourceMap().value(client())->version(), primarySelectionVersion); ++ QTRY_VERIFY(QGuiApplication::clipboard()->supportsSelection()); ++} ++ ++void tst_primaryselectionv1::createsPrimaryDeviceForNewSeats() ++{ ++ exec([=] { add(); }); ++ QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice(1)); ++} ++ ++void tst_primaryselectionv1::pasteAscii() ++{ ++ class Window : public QRasterWindow { ++ public: ++ void mousePressEvent(QMouseEvent *event) override ++ { ++ Q_UNUSED(event); ++ auto *mimeData = QGuiApplication::clipboard()->mimeData(QClipboard::Selection); ++ m_formats = mimeData->formats(); ++ m_text = QGuiApplication::clipboard()->text(QClipboard::Selection); ++ } ++ QStringList m_formats; ++ QString m_text; ++ }; ++ ++ Window window; ++ window.resize(64, 64); ++ window.show(); ++ ++ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); ++ exec([&] { ++ auto *surface = xdgSurface()->m_surface; ++ keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol ++ ++ auto *device = primarySelectionDevice(); ++ auto *offer = device->sendDataOffer({"text/plain"}); ++ connect(offer, &PrimarySelectionOfferV1::receive, [](QString mimeType, int fd) { ++ QFile file; ++ file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle); ++ QCOMPARE(mimeType, "text/plain"); ++ file.write(QByteArray("normal ascii")); ++ file.close(); ++ }); ++ device->sendSelection(offer); ++ ++ pointer()->sendEnter(surface, {32, 32}); ++ pointer()->sendButton(client(), BTN_MIDDLE, 1); ++ pointer()->sendButton(client(), BTN_MIDDLE, 0); ++ }); ++ QTRY_COMPARE(window.m_formats, QStringList{"text/plain"}); ++ QTRY_COMPARE(window.m_text, "normal ascii"); ++} ++ ++void tst_primaryselectionv1::pasteUtf8() ++{ ++ class Window : public QRasterWindow { ++ public: ++ void mousePressEvent(QMouseEvent *event) override ++ { ++ Q_UNUSED(event); ++ auto *mimeData = QGuiApplication::clipboard()->mimeData(QClipboard::Selection); ++ m_formats = mimeData->formats(); ++ m_text = QGuiApplication::clipboard()->text(QClipboard::Selection); ++ } ++ QStringList m_formats; ++ QString m_text; ++ }; ++ ++ Window window; ++ window.resize(64, 64); ++ window.show(); ++ ++ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); ++ exec([&] { ++ auto *surface = xdgSurface()->m_surface; ++ keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol ++ ++ auto *device = primarySelectionDevice(); ++ auto *offer = device->sendDataOffer({"text/plain", "text/plain;charset=utf-8"}); ++ connect(offer, &PrimarySelectionOfferV1::receive, [](QString mimeType, int fd) { ++ QFile file; ++ file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle); ++ QCOMPARE(mimeType, "text/plain;charset=utf-8"); ++ file.write(QByteArray("face with tears of joy: 😂")); ++ file.close(); ++ }); ++ device->sendSelection(offer); ++ ++ pointer()->sendEnter(surface, {32, 32}); ++ pointer()->sendButton(client(), BTN_MIDDLE, 1); ++ pointer()->sendButton(client(), BTN_MIDDLE, 0); ++ }); ++ QTRY_COMPARE(window.m_formats, QStringList({"text/plain", "text/plain;charset=utf-8"})); ++ QTRY_COMPARE(window.m_text, "face with tears of joy: 😂"); ++} ++ ++void tst_primaryselectionv1::destroysPreviousSelection() ++{ ++ QRasterWindow window; ++ window.resize(64, 64); ++ window.show(); ++ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); ++ ++ // When the client receives a selection event, it is required to destroy the previous offer ++ exec([&] { ++ auto *surface = xdgSurface()->m_surface; ++ keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol ++ ++ auto *offer = primarySelectionDevice()->sendDataOffer({"text/plain"}); ++ primarySelectionDevice()->sendSelection(offer); ++ }); ++ ++ exec([&] { ++ auto *offer = primarySelectionDevice()->sendDataOffer({"text/plain"}); ++ primarySelectionDevice()->sendSelection(offer); ++ QCOMPARE(primarySelectionDevice()->m_sentSelectionOffers.size(), 2); ++ }); ++ ++ // Verify the first offer gets destroyed ++ QCOMPOSITOR_TRY_COMPARE(primarySelectionDevice()->m_sentSelectionOffers.size(), 1); ++} ++ ++void tst_primaryselectionv1::copy() ++{ ++ class Window : public QRasterWindow { ++ public: ++ void mousePressEvent(QMouseEvent *event) override ++ { ++ Q_UNUSED(event); ++ QGuiApplication::clipboard()->setText("face with tears of joy: 😂", QClipboard::Selection); ++ } ++ QStringList m_formats; ++ QString m_text; ++ }; ++ ++ Window window; ++ window.resize(64, 64); ++ window.show(); ++ ++ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); ++ QVector mouseSerials; ++ exec([&] { ++ auto *surface = xdgSurface()->m_surface; ++ keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol ++ pointer()->sendEnter(surface, {32, 32}); ++ mouseSerials << pointer()->sendButton(client(), BTN_MIDDLE, 1); ++ mouseSerials << pointer()->sendButton(client(), BTN_MIDDLE, 0); ++ }); ++ QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()->m_selectionSource); ++ QCOMPOSITOR_TRY_VERIFY(mouseSerials.contains(primarySelectionDevice()->m_serial)); ++ QByteArray pastedBuf; ++ exec([&](){ ++ auto *source = primarySelectionDevice()->m_selectionSource; ++ QCOMPARE(source->m_offers, QStringList({"text/plain", "text/plain;charset=utf-8"})); ++ int fd[2]; ++ if (pipe(fd) == -1) ++ QSKIP("Failed to create pipe"); ++ fcntl(fd[0], F_SETFL, fcntl(fd[0], F_GETFL, 0) | O_NONBLOCK); ++ source->send_send("text/plain;charset=utf-8", fd[1]); ++ auto *notifier = new QSocketNotifier(fd[0], QSocketNotifier::Read, this); ++ connect(notifier, &QSocketNotifier::activated, this, [&](int fd) { ++ exec([&]{ ++ static char buf[1024]; ++ int n = QT_READ(fd, buf, sizeof buf); ++ if (n <= 0) { ++ delete notifier; ++ close(fd); ++ } else { ++ pastedBuf.append(buf, n); ++ } ++ }); ++ }); ++ }); ++ ++ QCOMPOSITOR_TRY_VERIFY(pastedBuf.size()); // this assumes we got everything in one read ++ auto pasted = QString::fromUtf8(pastedBuf); ++ QCOMPARE(pasted, "face with tears of joy: 😂"); ++} ++ ++QCOMPOSITOR_TEST_MAIN(tst_primaryselectionv1) ++#include "tst_primaryselectionv1.moc" +diff --git a/tests/auto/client/shared/corecompositor.h b/tests/auto/client/shared/corecompositor.h +index 875b7d05..254465ee 100644 +--- a/tests/auto/client/shared/corecompositor.h ++++ b/tests/auto/client/shared/corecompositor.h +@@ -124,6 +124,23 @@ class CoreCompositor + return nullptr; + } + ++ /*! ++ * \brief Returns the nth global with the given type, if any ++ */ ++ template ++ global_type *get(int index) ++ { ++ warnIfNotLockedByThread(Q_FUNC_INFO); ++ for (auto *global : qAsConst(m_globals)) { ++ if (auto *casted = qobject_cast(global)) { ++ if (index--) ++ continue; ++ return casted; ++ } ++ } ++ return nullptr; ++ } ++ + /*! + * \brief Returns all globals with the given type, if any + */ +diff --git a/tests/auto/client/shared/coreprotocol.cpp b/tests/auto/client/shared/coreprotocol.cpp +index 729d481f..8f79124c 100644 +--- a/tests/auto/client/shared/coreprotocol.cpp ++++ b/tests/auto/client/shared/coreprotocol.cpp +@@ -345,6 +345,7 @@ uint Keyboard::sendEnter(Surface *surface) + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_enter(r->handle, serial, surface->resource()->handle, QByteArray()); ++ m_enteredSurface = surface; + return serial; + } + +@@ -355,6 +356,7 @@ uint Keyboard::sendLeave(Surface *surface) + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_leave(r->handle, serial, surface->resource()->handle); ++ m_enteredSurface = nullptr; + return serial; + } + +diff --git a/tests/auto/client/shared/coreprotocol.h b/tests/auto/client/shared/coreprotocol.h +index 5cef476c..0ee8d76c 100644 +--- a/tests/auto/client/shared/coreprotocol.h ++++ b/tests/auto/client/shared/coreprotocol.h +@@ -236,7 +236,7 @@ class Seat : public Global, public QtWaylandServer::wl_seat + { + Q_OBJECT + public: +- explicit Seat(CoreCompositor *compositor, uint capabilities, int version = 4); ++ explicit Seat(CoreCompositor *compositor, uint capabilities = Seat::capability_pointer | Seat::capability_keyboard, int version = 4); + ~Seat() override; + void send_capabilities(Resource *resource, uint capabilities) = delete; // Use wrapper instead + void send_capabilities(uint capabilities) = delete; // Use wrapper instead +@@ -313,6 +313,7 @@ class Keyboard : public QObject, public QtWaylandServer::wl_keyboard + uint sendLeave(Surface *surface); + uint sendKey(wl_client *client, uint key, uint state); + Seat *m_seat = nullptr; ++ Surface *m_enteredSurface = nullptr; + }; + + class Shm : public Global, public QtWaylandServer::wl_shm +diff --git a/tests/auto/client/shared/mockcompositor.h b/tests/auto/client/shared/mockcompositor.h +index 05bf32c8..b8094a17 100644 +--- a/tests/auto/client/shared/mockcompositor.h ++++ b/tests/auto/client/shared/mockcompositor.h +@@ -36,10 +36,16 @@ + + #include + +-#ifndef BTN_LEFT + // As defined in linux/input-event-codes.h ++#ifndef BTN_LEFT + #define BTN_LEFT 0x110 + #endif ++#ifndef BTN_RIGHT ++#define BTN_RIGHT 0x111 ++#endif ++#ifndef BTN_MIDDLE ++#define BTN_MIDDLE 0x112 ++#endif + + namespace MockCompositor { + +diff --git a/include/QtWaylandClient/headers.pri b/include/QtWaylandClient/headers.pri +index 635f03c..1c7f2eb 100644 +--- a/include/QtWaylandClient/headers.pri ++++ b/include/QtWaylandClient/headers.pri +@@ -3,4 +3,4 @@ SYNCQT.GENERATED_HEADER_FILES = QWaylandClientExtension QWaylandClientExtensionT + SYNCQT.PRIVATE_HEADER_FILES = qtwaylandclientglobal_p.h qwaylandabstractdecoration_p.h qwaylandbuffer_p.h qwaylandclipboard_p.h qwaylandcursor_p.h qwaylanddatadevice_p.h qwaylanddatadevicemanager_p.h qwaylanddataoffer_p.h qwaylanddatasource_p.h qwaylanddecorationfactory_p.h qwaylanddecorationplugin_p.h qwaylanddisplay_p.h qwaylanddnd_p.h qwaylandextendedsurface_p.h qwaylandinputcontext_p.h qwaylandinputdevice_p.h qwaylandintegration_p.h qwaylandnativeinterface_p.h qwaylandqtkey_p.h qwaylandscreen_p.h qwaylandshellsurface_p.h qwaylandshm_p.h qwaylandshmbackingstore_p.h qwaylandshmwindow_p.h qwaylandsubsurface_p.h qwaylandtouch_p.h qwaylandwindow_p.h qwaylandwindowmanagerintegration_p.h global/qwaylandclientextension_p.h hardwareintegration/qwaylandclientbufferintegration_p.h hardwareintegration/qwaylandclientbufferintegrationfactory_p.h hardwareintegration/qwaylandclientbufferintegrationplugin_p.h hardwareintegration/qwaylandhardwareintegration_p.h hardwareintegration/qwaylandserverbufferintegration_p.h hardwareintegration/qwaylandserverbufferintegrationfactory_p.h hardwareintegration/qwaylandserverbufferintegrationplugin_p.h inputdeviceintegration/qwaylandinputdeviceintegration_p.h inputdeviceintegration/qwaylandinputdeviceintegrationfactory_p.h inputdeviceintegration/qwaylandinputdeviceintegrationplugin_p.h shellintegration/qwaylandshellintegration_p.h shellintegration/qwaylandshellintegrationfactory_p.h shellintegration/qwaylandshellintegrationplugin_p.h + SYNCQT.QPA_HEADER_FILES = + SYNCQT.CLEAN_HEADER_FILES = qtwaylandclientglobal.h global/qwaylandclientextension.h +-SYNCQT.INJECTIONS = src/client/qwayland-hardware-integration.h:^5.13.2/QtWaylandClient/private/qwayland-hardware-integration.h src/client/qwayland-qt-windowmanager.h:^5.13.2/QtWaylandClient/private/qwayland-qt-windowmanager.h src/client/qwayland-qt-key-unstable-v1.h:^5.13.2/QtWaylandClient/private/qwayland-qt-key-unstable-v1.h src/client/qwayland-server-buffer-extension.h:^5.13.2/QtWaylandClient/private/qwayland-server-buffer-extension.h src/client/qwayland-surface-extension.h:^5.13.2/QtWaylandClient/private/qwayland-surface-extension.h src/client/qwayland-text-input-unstable-v2.h:^5.13.2/QtWaylandClient/private/qwayland-text-input-unstable-v2.h src/client/qwayland-touch-extension.h:^5.13.2/QtWaylandClient/private/qwayland-touch-extension.h src/client/qwayland-wayland.h:^5.13.2/QtWaylandClient/private/qwayland-wayland.h src/client/qwayland-xdg-output-unstable-v1.h:^5.13.2/QtWaylandClient/private/qwayland-xdg-output-unstable-v1.h src/client/wayland-hardware-integration-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-hardware-integration-client-protocol.h src/client/wayland-qt-windowmanager-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-qt-windowmanager-client-protocol.h src/client/wayland-qt-key-unstable-v1-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-qt-key-unstable-v1-client-protocol.h src/client/wayland-server-buffer-extension-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-server-buffer-extension-client-protocol.h src/client/wayland-surface-extension-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-surface-extension-client-protocol.h src/client/wayland-text-input-unstable-v2-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-text-input-unstable-v2-client-protocol.h src/client/wayland-touch-extension-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-touch-extension-client-protocol.h src/client/wayland-wayland-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-wayland-client-protocol.h src/client/wayland-xdg-output-unstable-v1-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-xdg-output-unstable-v1-client-protocol.h ++SYNCQT.INJECTIONS = src/client/qwayland-wp-primary-selection-unstable-v1.h:^5.13.2/QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h src/client/wayland-wp-primary-selection-unstable-v1-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-wp-primary-selection-unstable-v1-client-protocol.h src/client/qwayland-hardware-integration.h:^5.13.2/QtWaylandClient/private/qwayland-hardware-integration.h src/client/qwayland-qt-windowmanager.h:^5.13.2/QtWaylandClient/private/qwayland-qt-windowmanager.h src/client/qwayland-qt-key-unstable-v1.h:^5.13.2/QtWaylandClient/private/qwayland-qt-key-unstable-v1.h src/client/qwayland-server-buffer-extension.h:^5.13.2/QtWaylandClient/private/qwayland-server-buffer-extension.h src/client/qwayland-surface-extension.h:^5.13.2/QtWaylandClient/private/qwayland-surface-extension.h src/client/qwayland-text-input-unstable-v2.h:^5.13.2/QtWaylandClient/private/qwayland-text-input-unstable-v2.h src/client/qwayland-touch-extension.h:^5.13.2/QtWaylandClient/private/qwayland-touch-extension.h src/client/qwayland-wayland.h:^5.13.2/QtWaylandClient/private/qwayland-wayland.h src/client/qwayland-xdg-output-unstable-v1.h:^5.13.2/QtWaylandClient/private/qwayland-xdg-output-unstable-v1.h src/client/wayland-hardware-integration-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-hardware-integration-client-protocol.h src/client/wayland-qt-windowmanager-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-qt-windowmanager-client-protocol.h src/client/wayland-qt-key-unstable-v1-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-qt-key-unstable-v1-client-protocol.h src/client/wayland-server-buffer-extension-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-server-buffer-extension-client-protocol.h src/client/wayland-surface-extension-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-surface-extension-client-protocol.h src/client/wayland-text-input-unstable-v2-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-text-input-unstable-v2-client-protocol.h src/client/wayland-touch-extension-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-touch-extension-client-protocol.h src/client/wayland-wayland-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-wayland-client-protocol.h src/client/wayland-xdg-output-unstable-v1-client-protocol.h:^5.13.2/QtWaylandClient/private/wayland-xdg-output-unstable-v1-client-protocol.h