Blob Blame History Raw
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wp_primary_selection_unstable_v1">
+  <copyright>
+    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.
+  </copyright>
+
+  <description summary="Primary selection protocol">
+    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.
+  </description>
+
+  <interface name="zwp_primary_selection_device_manager_v1" version="1">
+    <description summary="X primary selection emulation">
+      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.
+    </description>
+
+    <request name="create_source">
+      <description summary="create a new primary selection source">
+        Create a new primary selection source.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_primary_selection_source_v1"/>
+    </request>
+
+    <request name="get_device">
+      <description summary="create a new primary selection device">
+        Create a new data device for a given seat.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_primary_selection_device_v1"/>
+      <arg name="seat" type="object" interface="wl_seat"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection device manager">
+        Destroy the primary selection device manager.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="zwp_primary_selection_device_v1" version="1">
+    <request name="set_selection">
+      <description summary="set the primary selection">
+        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.
+      </description>
+      <arg name="source" type="object" interface="zwp_primary_selection_source_v1" allow-null="true"/>
+      <arg name="serial" type="uint" summary="serial of the event that triggered this request"/>
+    </request>
+
+    <event name="data_offer">
+      <description summary="introduce a new wp_primary_selection_offer">
+        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.
+      </description>
+      <arg name="offer" type="new_id" interface="zwp_primary_selection_offer_v1"/>
+    </event>
+
+    <event name="selection">
+      <description summary="advertise a new primary selection">
+        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.
+      </description>
+      <arg name="id" type="object" interface="zwp_primary_selection_offer_v1" allow-null="true"/>
+    </event>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection device">
+        Destroy the primary selection device.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="zwp_primary_selection_offer_v1" version="1">
+    <description summary="offer to transfer primary selection contents">
+      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.
+    </description>
+
+    <request name="receive">
+      <description summary="request that the data is transferred">
+        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.
+      </description>
+      <arg name="mime_type" type="string"/>
+      <arg name="fd" type="fd"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection offer">
+        Destroy the primary selection offer.
+      </description>
+    </request>
+
+    <event name="offer">
+      <description summary="advertise offered mime type">
+        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.
+      </description>
+      <arg name="mime_type" type="string"/>
+    </event>
+  </interface>
+
+  <interface name="zwp_primary_selection_source_v1" version="1">
+    <description summary="offer to replace the contents of the primary selection">
+      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.
+    </description>
+
+    <request name="offer">
+      <description summary="add an offered mime type">
+        This request adds a mime type to the set of mime types advertised to
+        targets. Can be called several times to offer multiple types.
+      </description>
+      <arg name="mime_type" type="string"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection source">
+        Destroy the primary selection source.
+      </description>
+    </request>
+
+    <event name="send">
+      <description summary="send the primary selection contents">
+        Request for the current primary selection contents from the client.
+        Send the specified mime type over the passed file descriptor, then
+        close it.
+      </description>
+      <arg name="mime_type" type="string"/>
+      <arg name="fd" type="fd"/>
+    </event>
+
+    <event name="cancelled">
+      <description summary="request for primary selection contents was canceled">
+        This primary selection source is no longer valid. The client should
+        clean up and destroy this primary selection source.
+      </description>
+    </event>
+  </interface>
+</protocol>
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<QWaylandMimeData> 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<QString, QByteArray> 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 <wayland-cursor.h>
 #endif
@@ -68,6 +71,7 @@
 #include "qwaylandqtkey_p.h"

 #include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h>
+#include <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h>

 #include <QtCore/private/qcore_unix_p.h>

@@ -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<QWaylandTouchExtension> mTouchExtension;
     QScopedPointer<QWaylandQtKeyExtension> mQtKeyExtension;
     QScopedPointer<QWaylandWindowManagerIntegration> mWindowManagerIntegration;
+#if QT_CONFIG(wayland_client_primary_selection)
+    QScopedPointer<QWaylandPrimarySelectionDeviceManagerV1> mPrimarySelectionManager;
+#endif
     QScopedPointer<QtWayland::zwp_text_input_manager_v2> mTextInputManager;
     QScopedPointer<QWaylandHardwareIntegration> mHardwareIntegration;
     QScopedPointer<QtWayland::zxdg_output_manager_v1> 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<QWaylandPrimarySelectionDeviceV1> 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 <QtGui/private/qguiapplication_p.h>
+
+#include <qpa/qplatformclipboard.h>
+
+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<QWaylandPrimarySelectionOfferV1 *>(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 <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h>
+
+#include <QtWaylandClient/private/qtwaylandclientglobal_p.h>
+#include <QtWaylandClient/private/qwaylanddataoffer_p.h>
+
+#include <QtCore/QObject>
+
+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<QWaylandMimeData> 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<QWaylandPrimarySelectionOfferV1> m_selectionOffer;
+    QScopedPointer<QWaylandPrimarySelectionSourceV1> 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 <qwayland-server-wp-primary-selection-unstable-v1.h>
+
+#include <QtGui/QRasterWindow>
+#include <QtGui/QOpenGLWindow>
+#include <QtGui/QClipboard>
+#include <QtCore/private/qcore_unix_p.h>
+
+#include <fcntl.h>
+
+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<PrimarySelectionOfferV1 *> 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<PrimarySelectionSourceV1>(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<Seat *, PrimarySelectionDeviceV1 *> m_devices;
+    QVector<PrimarySelectionSourceV1 *> 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<Seat>(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<PrimarySelectionDeviceManagerV1>(primarySelectionVersion);
+        });
+    }
+    PrimarySelectionDeviceV1 *primarySelectionDevice(int i = 0) {
+        return get<PrimarySelectionDeviceManagerV1>()->deviceFor(get<Seat>(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<PrimarySelectionDeviceManagerV1>()->resourceMap().size(), 1);
+    QCOMPOSITOR_TRY_COMPARE(get<PrimarySelectionDeviceManagerV1>()->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<Seat>(); });
+    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<uint> 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<typename global_type>
+    global_type *get(int index)
+    {
+        warnIfNotLockedByThread(Q_FUNC_INFO);
+        for (auto *global : qAsConst(m_globals)) {
+            if (auto *casted = qobject_cast<global_type *>(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 <QtGui/QGuiApplication>

-#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