diff --git a/.gitignore b/.gitignore
index a4fb4fb1259d0ad25a8cb9e10cda46a64d51d2e0..3975cf26dfd44c6e66f835586a8fdb8a111c2f6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,6 @@
 build/
 .cache/
+
+# Arch Linux stuff
+PKGBUILD
+pkg/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index aeef691fb15925916a69c1ba36fdd4d59acb43f6..58f849507f24a7d94904bc18e3186d212bbfb1ee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -724,7 +724,8 @@ set(kwin_XWAYLAND_SRCS
    ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag_wl.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag_x.cpp
-   ${CMAKE_CURRENT_SOURCE_DIR}/xwl/selection.cpp
+   ${CMAKE_CURRENT_SOURCE_DIR}/xwl/primary_selection.cpp
+   ${CMAKE_CURRENT_SOURCE_DIR}/xwl/selection.h
    ${CMAKE_CURRENT_SOURCE_DIR}/xwl/selection_source.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/xwl/transfer.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/xwl/xwayland.cpp
diff --git a/atoms.cpp b/atoms.cpp
index 84e8f2b974dc4727c555b16babc998a63248ba25..02293af38e5c4ff95da7fab9753b4b7c8d023a88 100644
--- a/atoms.cpp
+++ b/atoms.cpp
@@ -79,6 +79,7 @@ Atoms::Atoms()
     , delete_atom(QByteArrayLiteral("DELETE"))
     , incr(QByteArrayLiteral("INCR"))
     , wl_selection(QByteArrayLiteral("WL_SELECTION"))
+    , primary_selection(QByteArrayLiteral("PRIMARY"))
     , xwayland_randr_emu_monitor_rects(QByteArrayLiteral("_XWAYLAND_RANDR_EMU_MONITOR_RECTS"))
     , m_dtSmWindowInfo(QByteArrayLiteral("_DT_SM_WINDOW_INFO"))
     , m_motifSupport(QByteArrayLiteral("_MOTIF_WM_INFO"))
diff --git a/atoms.h b/atoms.h
index 693498cd599e5825282c57d0cd172db1defe2cc4..f275292f0b75919814dda1ab2ec8552baf3863aa 100644
--- a/atoms.h
+++ b/atoms.h
@@ -88,6 +88,7 @@ public:
     Xcb::Atom delete_atom;
     Xcb::Atom incr;
     Xcb::Atom wl_selection;
+    Xcb::Atom primary_selection;
     Xcb::Atom xwayland_randr_emu_monitor_rects;
 
     /**
diff --git a/autotests/integration/helper/copy.cpp b/autotests/integration/helper/copy.cpp
index 9fa0af59dcb8d43e4bd010281c4d60631f175393..f98fe1606b0ad19bb0b750638a93676cd96b9101 100644
--- a/autotests/integration/helper/copy.cpp
+++ b/autotests/integration/helper/copy.cpp
@@ -27,16 +27,20 @@ class Window : public QRasterWindow
 {
     Q_OBJECT
 public:
-    explicit Window();
+    explicit Window(QClipboard::Mode mode);
     ~Window() override;
 
 protected:
     void paintEvent(QPaintEvent *event) override;
     void focusInEvent(QFocusEvent *event) override;
+
+private:
+    QClipboard::Mode m_mode;
 };
 
-Window::Window()
+Window::Window(QClipboard::Mode mode)
     : QRasterWindow()
+    , m_mode(mode)
 {
 }
 
@@ -53,15 +57,20 @@ void Window::focusInEvent(QFocusEvent *event)
 {
     QRasterWindow::focusInEvent(event);
     // TODO: make it work without singleshot
-    QTimer::singleShot(100,[] {
-        qApp->clipboard()->setText(QStringLiteral("test"));
+    QTimer::singleShot(100,[this] {
+        qApp->clipboard()->setText(QStringLiteral("test"), m_mode);
     });
 }
 
 int main(int argc, char *argv[])
 {
+    QClipboard::Mode mode = QClipboard::Clipboard;
+    if (argv && !strcmp(argv[argc-1], "Selection")) {
+        mode = QClipboard::Selection;
+    }
+
     QGuiApplication app(argc, argv);
-    std::unique_ptr<Window> w(new Window);
+    std::unique_ptr<Window> w(new Window(mode));
     w->setGeometry(QRect(0, 0, 100, 200));
     w->show();
 
diff --git a/autotests/integration/helper/paste.cpp b/autotests/integration/helper/paste.cpp
index 1246b1e17e30c02fbfccfe5405cbe98a13e143af..524d283c117d396b913111ca27e563f92d3c34de 100644
--- a/autotests/integration/helper/paste.cpp
+++ b/autotests/integration/helper/paste.cpp
@@ -50,10 +50,15 @@ void Window::paintEvent(QPaintEvent *event)
 
 int main(int argc, char *argv[])
 {
+    QClipboard::Mode mode = QClipboard::Clipboard;
+    if (argv && !strcmp(argv[argc-1], "Selection")) {
+        mode = QClipboard::Selection;
+    }
+
     QGuiApplication app(argc, argv);
     QObject::connect(app.clipboard(), &QClipboard::changed, &app,
-        [] {
-            if (qApp->clipboard()->text() == QLatin1String("test")) {
+        [mode] {
+            if (qApp->clipboard()->text(mode) == QLatin1String("test")) {
                 QTimer::singleShot(100, qApp, &QCoreApplication::quit);
             }
         }
diff --git a/autotests/integration/xwayland_selections_test.cpp b/autotests/integration/xwayland_selections_test.cpp
index 2826f3ab2932cc0c2bfc4da13cf92dde36d209da..f448ba4d3d8a379582b505b776ad59f1e091cfc1 100644
--- a/autotests/integration/xwayland_selections_test.cpp
+++ b/autotests/integration/xwayland_selections_test.cpp
@@ -29,6 +29,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include "win/x11/window.h"
 
 #include <Wrapland/Server/data_device.h>
+#include <Wrapland/Server/data_device_manager.h>
+#include <Wrapland/Server/data_source.h>
+#include <Wrapland/Server/primary_selection.h>
 
 #include <QProcess>
 #include <QProcessEnvironment>
@@ -56,6 +59,8 @@ void XwaylandSelectionsTest::initTestCase()
     qRegisterMetaType<win::wayland::window*>();
     qRegisterMetaType<win::x11::window*>();
     qRegisterMetaType<QProcess::ExitStatus>();
+    qRegisterMetaType<Wrapland::Server::DataDevice*>();
+    qRegisterMetaType<Wrapland::Server::DataSource*>();
 
     QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated);
     QVERIFY(workspaceCreatedSpy.isValid());
@@ -98,15 +103,34 @@ void XwaylandSelectionsTest::cleanup()
 
 void XwaylandSelectionsTest::testSync_data()
 {
+    QTest::addColumn<QString>("clipboardMode");
     QTest::addColumn<QString>("copyPlatform");
     QTest::addColumn<QString>("pastePlatform");
 
-    QTest::newRow("x11->wayland") << QStringLiteral("xcb") << QStringLiteral("wayland");
-    QTest::newRow("wayland->x11") << QStringLiteral("wayland") << QStringLiteral("xcb");
+    QTest::newRow("Clipboard x11->wayland") << QStringLiteral("Clipboard")
+                                            << QStringLiteral("xcb")
+                                            << QStringLiteral("wayland");
+    QTest::newRow("Clipboard wayland->x11") << QStringLiteral("Clipboard")
+                                            << QStringLiteral("wayland")
+                                            << QStringLiteral("xcb");
+    QTest::newRow("primary_selection x11->wayland") << QStringLiteral("Selection")
+                                                    << QStringLiteral("xcb")
+                                                    << QStringLiteral("wayland");
+    QTest::newRow("primary_selection wayland->x11") << QStringLiteral("Selection")
+                                                    << QStringLiteral("wayland")
+                                                    << QStringLiteral("xcb");
 }
 
 void XwaylandSelectionsTest::testSync()
 {
+    QFETCH(QString, clipboardMode);
+    if (clipboardMode == "Clipboard") {
+        QVERIFY(Xwl::DataBridge::self()->dataDeviceIface() != nullptr);
+    }
+    if (clipboardMode == "Selection"){
+        QVERIFY(Xwl::DataBridge::self()->primarySelectionDeviceIface() != nullptr);
+    }
+
     // this test verifies the syncing of X11 to Wayland clipboard
     const QString copy = QFINDTESTDATA(QStringLiteral("copy"));
     QVERIFY(!copy.isEmpty());
@@ -117,7 +141,18 @@ void XwaylandSelectionsTest::testSync()
     QVERIFY(clientAddedSpy.isValid());
     QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::window_added);
     QVERIFY(shellClientAddedSpy.isValid());
-    QSignalSpy clipboardChangedSpy(Xwl::DataBridge::self()->dataDeviceIface(), &Wrapland::Server::DataDevice::selectionChanged);
+
+    QSignalSpy clipboardChangedSpy = [clipboardMode]() {
+        if (clipboardMode == "Clipboard") {
+            return QSignalSpy(Xwl::DataBridge::self()->dataDeviceIface(),
+                              &Wrapland::Server::DataDevice::selectionChanged);
+        }
+        if (clipboardMode == "Selection") {
+            return QSignalSpy(Xwl::DataBridge::self()->primarySelectionDeviceIface(),
+                              &Wrapland::Server::PrimarySelectionDevice::selectionChanged);
+        }
+        throw;
+    }();
     QVERIFY(clipboardChangedSpy.isValid());
 
     QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
@@ -130,6 +165,7 @@ void XwaylandSelectionsTest::testSync()
     m_copyProcess->setProcessEnvironment(environment);
     m_copyProcess->setProcessChannelMode(QProcess::ForwardedChannels);
     m_copyProcess->setProgram(copy);
+    m_copyProcess->setArguments({clipboardMode});
     m_copyProcess->start();
     QVERIFY(m_copyProcess->waitForStarted());
 
@@ -165,6 +201,7 @@ void XwaylandSelectionsTest::testSync()
     m_pasteProcess->setProcessEnvironment(environment);
     m_pasteProcess->setProcessChannelMode(QProcess::ForwardedChannels);
     m_pasteProcess->setProgram(paste);
+    m_pasteProcess->setArguments({clipboardMode});
     m_pasteProcess->start();
     QVERIFY(m_pasteProcess->waitForStarted());
 
diff --git a/tooling/analysis/clang-format.sh b/tooling/analysis/clang-format.sh
index 85cc484f9a8ecd6e0fb021248f2837cad5e1952d..1d75757e020749abb96a3355f4422ebeda06a995 100755
--- a/tooling/analysis/clang-format.sh
+++ b/tooling/analysis/clang-format.sh
@@ -14,4 +14,5 @@ python <(curl -s $RUN_SCRIPT_URL) -r \
     ${SOURCE_DIR}/render \
     ${SOURCE_DIR}/rules \
     ${SOURCE_DIR}/seat \
-    ${SOURCE_DIR}/cmake
+    ${SOURCE_DIR}/cmake \
+    ${SOURCE_DIR}/xwl
diff --git a/wayland_server.cpp b/wayland_server.cpp
index 65357e945700d8318eac10d5540280bdb5f8654e..abec85160e2f1cfbea844ca93b2fc7185ef708b3 100644
--- a/wayland_server.cpp
+++ b/wayland_server.cpp
@@ -38,6 +38,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include <Wrapland/Client/compositor.h>
 #include <Wrapland/Client/seat.h>
 #include <Wrapland/Client/datadevicemanager.h>
+#include <Wrapland/Client/primary_selection.h>
 #include <Wrapland/Client/shm_pool.h>
 #include <Wrapland/Client/surface.h>
 // Server
@@ -59,6 +60,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include <Wrapland/Server/pointer_constraints_v1.h>
 #include <Wrapland/Server/pointer_gestures_v1.h>
 #include <Wrapland/Server/presentation_time.h>
+#include <Wrapland/Server/primary_selection.h>
 #include <Wrapland/Server/seat.h>
 #include <Wrapland/Server/server_decoration_palette.h>
 #include <Wrapland/Server/shadow.h>
@@ -214,6 +216,7 @@ void WaylandServer::destroyInternalConnection()
         delete m_internalConnection.compositor;
         delete m_internalConnection.seat;
         delete m_internalConnection.ddm;
+        delete m_internalConnection.psdm;
         delete m_internalConnection.shm;
         dispatch();
         delete m_internalConnection.queue;
@@ -350,7 +353,7 @@ bool WaylandServer::init(InitializationFlags flags)
     m_display->createPointerGestures(m_display);
     m_display->createPointerConstraints(m_display);
     m_dataDeviceManager = m_display->createDataDeviceManager(m_display);
-
+    m_primarySelectionDeviceManager = m_display->createPrimarySelectionDeviceManager(m_display);
     m_idle = m_display->createIdle(m_display);
 
     auto idleInhibition = new IdleInhibition(m_idle);
@@ -746,6 +749,10 @@ void WaylandServer::createInternalConnection()
                     if (ddmInterface.name != 0) {
                         m_internalConnection.ddm = registry->createDataDeviceManager(ddmInterface.name, ddmInterface.version, this);
                     }
+                    const auto psdmInterface = registry->interface(Registry::Interface::PrimarySelectionDeviceManager);
+                    if (psdmInterface.name != 0) {
+                        m_internalConnection.psdm = registry->createPrimarySelectionDeviceManager(psdmInterface.name, psdmInterface.version, this);
+                    }
                 }
             );
             registry->setup();
diff --git a/wayland_server.h b/wayland_server.h
index 43b385ba741c5208271aaf356ef297dd2a591932..9726f6c844adc0540516e5cfd14db297e83f767a 100644
--- a/wayland_server.h
+++ b/wayland_server.h
@@ -39,6 +39,7 @@ class Registry;
 class Compositor;
 class Seat;
 class DataDeviceManager;
+class PrimarySelectionDeviceManager;
 class ShmPool;
 class Surface;
 }
@@ -60,6 +61,7 @@ class PlasmaShellSurface;
 class PlasmaVirtualDesktopManager;
 class PlasmaWindowManager;
 class PresentationManager;
+class PrimarySelectionDeviceManager;
 class QtSurfaceExtension;
 class OutputManagementV1;
 class OutputConfigurationV1;
@@ -126,6 +128,9 @@ public:
     Wrapland::Server::DataDeviceManager *dataDeviceManager() {
         return m_dataDeviceManager;
     }
+    Wrapland::Server::PrimarySelectionDeviceManager *primarySelectionDeviceManager() const {
+        return m_primarySelectionDeviceManager;
+    }
     Wrapland::Server::PlasmaVirtualDesktopManager *virtualDesktopManagement() {
         return m_virtualDesktopManagement;
     }
@@ -204,6 +209,9 @@ public:
     Wrapland::Client::DataDeviceManager *internalDataDeviceManager() {
         return m_internalConnection.ddm;
     }
+    Wrapland::Client::PrimarySelectionDeviceManager *internalPrimarySelectionDeviceManager() {
+        return m_internalConnection.psdm;
+    }
     Wrapland::Client::ShmPool *internalShmPool() {
         return m_internalConnection.shm;
     }
@@ -276,6 +284,7 @@ private:
     Wrapland::Server::PlasmaWindowManager *m_windowManagement = nullptr;
     Wrapland::Server::PlasmaVirtualDesktopManager *m_virtualDesktopManagement = nullptr;
     Wrapland::Server::PresentationManager *m_presentationManager = nullptr;
+    Wrapland::Server::PrimarySelectionDeviceManager *m_primarySelectionDeviceManager = nullptr;
     Wrapland::Server::OutputManagementV1 *m_outputManagement = nullptr;
     Wrapland::Server::AppmenuManager *m_appmenuManager = nullptr;
     Wrapland::Server::ServerSideDecorationPaletteManager *m_paletteManager = nullptr;
@@ -299,6 +308,7 @@ private:
         Wrapland::Client::EventQueue *queue = nullptr;
         Wrapland::Client::Seat *seat = nullptr;
         Wrapland::Client::DataDeviceManager *ddm = nullptr;
+        Wrapland::Client::PrimarySelectionDeviceManager *psdm = nullptr;
         Wrapland::Client::ShmPool *shm = nullptr;
         bool interfacesAnnounced = false;
 
diff --git a/xwl/clipboard.cpp b/xwl/clipboard.cpp
index a6412ff403a3bdb4800f75c12248f82dea709508..a61ce26c71b54044200cdb74909b2a81933b5b06 100644
--- a/xwl/clipboard.cpp
+++ b/xwl/clipboard.cpp
@@ -19,7 +19,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *********************************************************************/
 #include "clipboard.h"
 
-#include "databridge.h"
 #include "selection_source.h"
 #include "transfer.h"
 #include "xwayland.h"
@@ -29,7 +28,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include "win/x11/window.h"
 
-#include <Wrapland/Client/connection_thread.h>
 #include <Wrapland/Client/datadevice.h>
 #include <Wrapland/Client/datasource.h>
 
@@ -47,146 +45,31 @@ namespace KWin
 namespace Xwl
 {
 
-Clipboard::Clipboard(xcb_atom_t atom, QObject *parent)
-    : Selection(atom, parent)
+Clipboard::Clipboard(xcb_atom_t atom, srv_data_device* srv_dev, clt_data_device* clt_dev)
 {
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-
-    const uint32_t clipboardValues[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
-                                   XCB_EVENT_MASK_PROPERTY_CHANGE };
-    xcb_create_window(xcbConn,
-                      XCB_COPY_FROM_PARENT,
-                      window(),
-                      kwinApp()->x11RootWindow(),
-                      0, 0,
-                      10, 10,
-                      0,
-                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                      Xwayland::self()->xcbScreen()->root_visual,
-                      XCB_CW_EVENT_MASK,
-                      clipboardValues);
-    registerXfixes();
-    xcb_flush(xcbConn);
-
-    connect(waylandServer()->seat(), &Wrapland::Server::Seat::selectionChanged,
-            this, &Clipboard::wlSelectionChanged);
-}
+    data = create_selection_data(atom, srv_dev, clt_dev);
 
-void Clipboard::wlSelectionChanged(Wrapland::Server::DataDevice *ddi)
-{
-    if (ddi && ddi != DataBridge::self()->dataDeviceIface()) {
-        // Wayland native client provides new selection
-        if (!m_checkConnection) {
-            m_checkConnection = connect(workspace(), &Workspace::clientActivated,
-                                        this, [this](Toplevel* ac) {
-                                            Q_UNUSED(ac);
-                                            checkWlSource();
-                                        });
-        }
-        // remove previous source so checkWlSource() can create a new one
-        setWlSource(nullptr);
-    }
-    checkWlSource();
+    register_x11_selection(this, QSize(10, 10));
+
+    QObject::connect(waylandServer()->seat(),
+                     &Wrapland::Server::Seat::selectionChanged,
+                     data.qobject.get(),
+                     [this] { handle_wl_selection_change(this); });
 }
 
-void Clipboard::checkWlSource()
+Clipboard::srv_data_device* Clipboard::get_current_device() const
 {
-    auto ddi = waylandServer()->seat()->selection();
-    auto removeSource = [this] {
-        if (wlSource()) {
-            setWlSource(nullptr);
-            ownSelection(false);
-        }
-    };
-
-    // Wayland source gets created when:
-    // - the Wl selection exists,
-    // - its source is not Xwayland,
-    // - a client is active,
-    // - this client is an Xwayland one.
-    //
-    // Otherwise the Wayland source gets destroyed to shield
-    // against snooping X clients.
-
-    if (!ddi || DataBridge::self()->dataDeviceIface() == ddi) {
-        // Xwayland source or no source
-        disconnect(m_checkConnection);
-        m_checkConnection = QMetaObject::Connection();
-        removeSource();
-        return;
-    }
-    if (!workspace()->activeClient() || !workspace()->activeClient()->inherits("KWin::win::x11::window")) {
-        // no active client or active client is Wayland native
-        removeSource();
-        return;
-    }
-    // Xwayland client is active and we need a Wayland source
-    if (wlSource()) {
-        // source already exists, nothing more to do
-        return;
-    }
-    auto *wls = new WlSource(this, ddi);
-    setWlSource(wls);
-    auto *dsi = ddi->selection();
-    if (dsi) {
-        wls->setDataSourceIface(dsi);
-    }
-    connect(ddi, &Wrapland::Server::DataDevice::selectionChanged,
-            wls, &WlSource::setDataSourceIface);
-    ownSelection(true);
+    return waylandServer()->seat()->selection();
 }
 
-void Clipboard::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
+Wrapland::Client::DataDeviceManager* Clipboard::get_internal_device_manager() const
 {
-    createX11Source(nullptr);
-
-    auto const& client = workspace()->activeClient();
-    if (!qobject_cast<win::x11::window const*>(client)) {
-        // clipboard is only allowed to be acquired when Xwayland has focus
-        // TODO: can we make this stronger (window id comparison)?
-        return;
-    }
-
-    createX11Source(event);
-
-    if (X11Source *source = x11Source()) {
-        source->getTargets();
-    }
+    return waylandServer()->internalDataDeviceManager();
 }
 
-void Clipboard::x11OffersChanged(const QStringList &added, const QStringList &removed)
+std::function<void(Clipboard::srv_data_device*)> Clipboard::get_selection_setter() const
 {
-    X11Source *source = x11Source();
-    if (!source) {
-        return;
-    }
-
-    const Mimes offers = source->offers();
-
-    if (!offers.isEmpty()) {
-        if (!source->dataSource() || !removed.isEmpty()) {
-            // create new Wl DataSource if there is none or when types
-            // were removed (Wl Data Sources can only add types)
-            Wrapland::Client::DataDeviceManager *dataDeviceManager =
-                waylandServer()->internalDataDeviceManager();
-            Wrapland::Client::DataSource *dataSource =
-                dataDeviceManager->createDataSource(source);
-
-            // also offers directly the currently available types
-            source->setDataSource(dataSource);
-            DataBridge::self()->dataDevice()->setSelection(0, dataSource);
-            waylandServer()->seat()->setSelection(DataBridge::self()->dataDeviceIface());
-        } else if (auto *dataSource = source->dataSource()) {
-            for (const QString &mime : added) {
-                dataSource->offer(mime);
-            }
-        }
-    } else {
-        waylandServer()->seat()->setSelection(nullptr);
-    }
-
-    waylandServer()->internalClientConection()->flush();
-    waylandServer()->dispatch();
+    return [](srv_data_device* dev) { waylandServer()->seat()->setSelection(dev); };
 }
 
 } // namespace Xwl
diff --git a/xwl/clipboard.h b/xwl/clipboard.h
index 6d4340c18490afa86cf1c80adc1ff59bf288b036..4bc395afcf88ee69a803470b6ad00ab13550b7e6 100644
--- a/xwl/clipboard.h
+++ b/xwl/clipboard.h
@@ -22,49 +22,42 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include "selection.h"
 
-namespace Wrapland
-{
-namespace Server
-{
-class DataDevice;
-}
-}
+#include <Wrapland/Client/datadevice.h>
+#include <Wrapland/Client/datasource.h>
+#include <Wrapland/Server/data_device.h>
+#include <Wrapland/Server/data_source.h>
 
-namespace KWin
-{
-namespace Xwl
+#include <functional>
+
+namespace KWin::Xwl
 {
+class Clipboard;
 
 /**
  * Represents the X clipboard, which is on Wayland side just called
  * @e selection.
  */
-class Clipboard : public Selection
+class Clipboard
 {
-    Q_OBJECT
-
 public:
-    Clipboard(xcb_atom_t atom, QObject *parent);
+    using srv_data_device = Wrapland::Server::DataDevice;
+    using clt_data_device = Wrapland::Client::DataDevice;
+    using srv_data_source = srv_data_device::source_t;
+    using clt_source_t = clt_data_device::source_t;
 
-private:
-    void doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) override;
-    void x11OffersChanged(const QStringList &added, const QStringList &removed) override;
-    /**
-     * React to Wl selection change.
-     */
-    void wlSelectionChanged(Wrapland::Server::DataDevice *ddi);
-    /**
-     * Check the current state of the selection and if a source needs
-     * to be created or destroyed.
-     */
-    void checkWlSource();
+    selection_data<srv_data_device, clt_data_device> data;
+    QMetaObject::Connection source_check_connection;
+
+    Clipboard(xcb_atom_t atom, srv_data_device* srv_dev, clt_data_device* clt_dev);
 
-    QMetaObject::Connection m_checkConnection;
+    srv_data_device* get_current_device() const;
+    Wrapland::Client::DataDeviceManager* get_internal_device_manager() const;
+    std::function<void(srv_data_device*)> get_selection_setter() const;
 
+private:
     Q_DISABLE_COPY(Clipboard)
 };
 
-} // namespace Xwl
-} // namespace KWin
+}
 
 #endif
diff --git a/xwl/databridge.cpp b/xwl/databridge.cpp
index 1a003f704763a05dcd471c30086081e6c3f77708..8403d065edd4e5992cd72ef57ebaf668ec23f8d5 100644
--- a/xwl/databridge.cpp
+++ b/xwl/databridge.cpp
@@ -20,6 +20,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include "databridge.h"
 #include "clipboard.h"
 #include "dnd.h"
+#include "primary_selection.h"
 #include "selection.h"
 #include "xwayland.h"
 
@@ -29,10 +30,12 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include "workspace.h"
 
 #include <Wrapland/Client/datadevicemanager.h>
+#include <Wrapland/Client/primary_selection.h>
 #include <Wrapland/Client/seat.h>
 
-#include <Wrapland/Server/data_device_manager.h>
 #include <Wrapland/Server/data_device.h>
+#include <Wrapland/Server/data_device_manager.h>
+#include <Wrapland/Server/primary_selection.h>
 #include <Wrapland/Server/seat.h>
 
 using namespace Wrapland::Client;
@@ -42,40 +45,68 @@ namespace KWin
 namespace Xwl
 {
 
-static DataBridge *s_self = nullptr;
+static DataBridge* s_self = nullptr;
 
-DataBridge *DataBridge::self()
+DataBridge* DataBridge::self()
 {
     return s_self;
 }
 
-DataBridge::DataBridge(QObject *parent)
+DataBridge::DataBridge(QObject* parent)
     : QObject(parent)
 {
     s_self = this;
 
-    auto dataDeviceManager = waylandServer()->internalDataDeviceManager();
-    auto seat = waylandServer()->internalSeat();
-    m_dataDevice = dataDeviceManager->getDataDevice(seat, this);
+    m_dataDevice = waylandServer()->internalDataDeviceManager()->getDevice(
+        waylandServer()->internalSeat(), this);
+    m_primarySelectionDevice = waylandServer()->internalPrimarySelectionDeviceManager()->getDevice(
+        waylandServer()->internalSeat(), this);
+
     waylandServer()->dispatch();
 
-    auto dataDeviceManagerInterface = waylandServer()->dataDeviceManager();
-
-    auto *dc = new QMetaObject::Connection();
-    *dc = connect(dataDeviceManagerInterface, &Wrapland::Server::DataDeviceManager::dataDeviceCreated, this,
-        [this, dc](Wrapland::Server::DataDevice *dataDeviceInterface) {
-            if (m_dataDeviceInterface) {
-                return;
-            }
-            if (dataDeviceInterface->client() != waylandServer()->internalConnection()) {
-                return;
-            }
-            QObject::disconnect(*dc);
-            delete dc;
-            m_dataDeviceInterface = dataDeviceInterface;
-            init();
-        }
-    );
+    auto* dc = new QMetaObject::Connection();
+    *dc = connect(waylandServer()->dataDeviceManager(),
+                  &Wrapland::Server::DataDeviceManager::deviceCreated,
+                  this,
+                  [this, dc](auto srv_dev) {
+                      if (srv_dev->client() != waylandServer()->internalConnection()) {
+                          return;
+                      }
+
+                      QObject::disconnect(*dc);
+                      delete dc;
+
+                      assert(!m_dataDeviceInterface);
+                      m_dataDeviceInterface = srv_dev;
+
+                      assert(!m_clipboard);
+                      assert(!m_dnd);
+                      m_clipboard.reset(new Clipboard(atoms->clipboard, srv_dev, m_dataDevice));
+                      m_dnd.reset(new Dnd(atoms->xdnd_selection, srv_dev, m_dataDevice));
+
+                      waylandServer()->dispatch();
+                  });
+
+    auto* pc = new QMetaObject::Connection();
+    *pc = connect(waylandServer()->primarySelectionDeviceManager(),
+                  &Wrapland::Server::PrimarySelectionDeviceManager::deviceCreated,
+                  this,
+                  [this, pc](auto srv_dev) {
+                      if (srv_dev->client() != waylandServer()->internalConnection()) {
+                          return;
+                      }
+
+                      QObject::disconnect(*pc);
+                      delete pc;
+
+                      assert(!m_primarySelectionDeviceInterface);
+                      m_primarySelectionDeviceInterface = srv_dev;
+
+                      assert(!m_primarySelection);
+                      m_primarySelection.reset(new primary_selection(
+                          atoms->primary_selection, srv_dev, m_primarySelectionDevice));
+                      waylandServer()->dispatch();
+                  });
 }
 
 DataBridge::~DataBridge()
@@ -83,45 +114,39 @@ DataBridge::~DataBridge()
     s_self = nullptr;
 }
 
-void DataBridge::init()
-{
-    m_clipboard = new Clipboard(atoms->clipboard, this);
-    m_dnd = new Dnd(atoms->xdnd_selection, this);
-    waylandServer()->dispatch();
-}
-
-bool DataBridge::filterEvent(xcb_generic_event_t *event)
+bool DataBridge::filterEvent(xcb_generic_event_t* event)
 {
-    if (m_clipboard->filterEvent(event)) {
+    if (filter_event(m_clipboard.get(), event)) {
         return true;
     }
-    if (m_dnd->filterEvent(event)) {
+    if (filter_event(m_dnd.get(), event)) {
         return true;
     }
-    if (event->response_type - Xwayland::self()->xfixes()->first_event == XCB_XFIXES_SELECTION_NOTIFY) {
-        return handleXfixesNotify((xcb_xfixes_selection_notify_event_t *)event);
+    if (filter_event(m_primarySelection.get(), event)) {
+        return true;
+    }
+    if (event->response_type - Xwayland::self()->xfixes()->first_event
+        == XCB_XFIXES_SELECTION_NOTIFY) {
+        return handleXfixesNotify((xcb_xfixes_selection_notify_event_t*)event);
     }
     return false;
 }
 
-bool DataBridge::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
+bool DataBridge::handleXfixesNotify(xcb_xfixes_selection_notify_event_t* event)
 {
-    Selection *selection = nullptr;
-
     if (event->selection == atoms->clipboard) {
-        selection = m_clipboard;
-    } else if (event->selection == atoms->xdnd_selection) {
-        selection = m_dnd;
+        return handle_xfixes_notify(m_clipboard.get(), event);
     }
-
-    if (!selection) {
-        return false;
+    if (event->selection == atoms->primary_selection) {
+        return handle_xfixes_notify(m_primarySelection.get(), event);
     }
-
-    return selection->handleXfixesNotify(event);
+    if (event->selection == atoms->xdnd_selection) {
+        return handle_xfixes_notify(m_dnd.get(), event);
+    }
+    return false;
 }
 
-DragEventReply DataBridge::dragMoveFilter(Toplevel *target, const QPoint &pos)
+DragEventReply DataBridge::dragMoveFilter(Toplevel* target, const QPoint& pos)
 {
     if (!m_dnd) {
         return DragEventReply::Wayland;
diff --git a/xwl/databridge.h b/xwl/databridge.h
index 6fef522492a0cc8aa570e05179c922e110050d50..d0fc7f7c7b7dbba6daef7360b9ffde092b7f5f30 100644
--- a/xwl/databridge.h
+++ b/xwl/databridge.h
@@ -25,6 +25,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include <QObject>
 #include <QPoint>
 
+#include <memory>
 #include <xcb/xcb.h>
 
 struct xcb_xfixes_selection_notify_event_t;
@@ -34,10 +35,12 @@ namespace Wrapland
 namespace Client
 {
 class DataDevice;
+class PrimarySelectionDevice;
 }
 namespace Server
 {
 class DataDevice;
+class PrimarySelectionDevice;
 class Surface;
 }
 }
@@ -52,6 +55,7 @@ class Xwayland;
 class Clipboard;
 class Dnd;
 enum class DragEventReply;
+class primary_selection;
 
 /**
  * Interface class for all data sharing in the context of X selections
@@ -64,38 +68,47 @@ class KWIN_EXPORT DataBridge : public QObject
     Q_OBJECT
 
 public:
-    static DataBridge *self();
+    static DataBridge* self();
 
-    explicit DataBridge(QObject *parent = nullptr);
+    explicit DataBridge(QObject* parent = nullptr);
     ~DataBridge() override;
 
-    bool filterEvent(xcb_generic_event_t *event);
-    DragEventReply dragMoveFilter(Toplevel *target, const QPoint &pos);
+    bool filterEvent(xcb_generic_event_t* event);
+    DragEventReply dragMoveFilter(Toplevel* target, const QPoint& pos);
 
-    Wrapland::Client::DataDevice *dataDevice() const
+    Wrapland::Client::DataDevice* dataDevice() const
     {
         return m_dataDevice;
     }
-    Wrapland::Server::DataDevice *dataDeviceIface() const
+    Wrapland::Server::DataDevice* dataDeviceIface() const
     {
         return m_dataDeviceInterface;
     }
-    Dnd *dnd() const
+    Dnd* dnd() const
     {
-        return m_dnd;
+        return m_dnd.get();
+    }
+    Wrapland::Client::PrimarySelectionDevice* primarySelectionDevice() const
+    {
+        return m_primarySelectionDevice;
+    }
+    Wrapland::Server::PrimarySelectionDevice* primarySelectionDeviceIface() const
+    {
+        return m_primarySelectionDeviceInterface;
     }
 
 private:
-    void init();
-
-    bool handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event);
+    bool handleXfixesNotify(xcb_xfixes_selection_notify_event_t* event);
 
-    Clipboard *m_clipboard = nullptr;
-    Dnd *m_dnd = nullptr;
+    std::unique_ptr<Clipboard> m_clipboard;
+    std::unique_ptr<Dnd> m_dnd;
+    std::unique_ptr<primary_selection> m_primarySelection;
 
     /* Internal data device interface */
-    Wrapland::Client::DataDevice *m_dataDevice = nullptr;
-    Wrapland::Server::DataDevice *m_dataDeviceInterface = nullptr;
+    Wrapland::Client::DataDevice* m_dataDevice = nullptr;
+    Wrapland::Server::DataDevice* m_dataDeviceInterface = nullptr;
+    Wrapland::Client::PrimarySelectionDevice* m_primarySelectionDevice = nullptr;
+    Wrapland::Server::PrimarySelectionDevice* m_primarySelectionDeviceInterface = nullptr;
 
     Q_DISABLE_COPY(DataBridge)
 };
diff --git a/xwl/dnd.cpp b/xwl/dnd.cpp
index 7e4d40e340b44244aa66530fac0d6034ab9ae371..31a565ca8e3faa773ecb116e7c2c698447a287bf 100644
--- a/xwl/dnd.cpp
+++ b/xwl/dnd.cpp
@@ -19,7 +19,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *********************************************************************/
 #include "dnd.h"
 
-#include "databridge.h"
 #include "drag_wl.h"
 #include "drag_x.h"
 #include "selection_source.h"
@@ -47,98 +46,22 @@ namespace KWin
 namespace Xwl
 {
 
-// version of DnD support in X
-const static uint32_t s_version = 5;
-uint32_t Dnd::version()
-{
-    return s_version;
-}
-
-Dnd::Dnd(xcb_atom_t atom, QObject *parent)
-    : Selection(atom, parent)
+template<>
+void do_handle_xfixes_notify(Dnd* sel, xcb_xfixes_selection_notify_event_t* event)
 {
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-
-    const uint32_t dndValues[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
-                                   XCB_EVENT_MASK_PROPERTY_CHANGE };
-    xcb_create_window(xcbConn,
-                      XCB_COPY_FROM_PARENT,
-                      window(),
-                      kwinApp()->x11RootWindow(),
-                      0, 0,
-                      8192, 8192,           // TODO: get current screen size and connect to changes
-                      0,
-                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                      Xwayland::self()->xcbScreen()->root_visual,
-                      XCB_CW_EVENT_MASK,
-                      dndValues);
-    registerXfixes();
-
-    xcb_change_property(xcbConn,
-                        XCB_PROP_MODE_REPLACE,
-                        window(),
-                        atoms->xdnd_aware,
-                        XCB_ATOM_ATOM,
-                        32, 1, &s_version);
-    xcb_flush(xcbConn);
-
-    connect(waylandServer()->seat(), &Wrapland::Server::Seat::dragStarted, this, &Dnd::startDrag);
-    connect(waylandServer()->seat(), &Wrapland::Server::Seat::dragEnded, this, &Dnd::endDrag);
-
-    const auto *comp = waylandServer()->compositor();
-    m_surface = waylandServer()->internalCompositor()->createSurface(this);
-    m_surface->setInputRegion(nullptr);
-    m_surface->commit(Wrapland::Client::Surface::CommitFlag::None);
-    auto *dc = new QMetaObject::Connection();
-    *dc = connect(comp, &Wrapland::Server::Compositor::surfaceCreated, this,
-                 [this, dc](Wrapland::Server::Surface *si) {
-                    // TODO: how to make sure that it is the iface of m_surface?
-                    if (m_surfaceIface || si->client() != waylandServer()->internalConnection()) {
-                        return;
-                    }
-                    QObject::disconnect(*dc);
-                    delete dc;
-                    m_surfaceIface = si;
-                    connect(workspace(), &Workspace::clientActivated, this,
-                        [this](Toplevel *ac) {
-                            if (!ac || !ac->inherits("KWin::X11Client")) {
-                                return;
-                            }
-                            auto *surface = ac->surface();
-                            if (surface) {
-                                surface->setDataProxy(m_surfaceIface);
-                            } else {
-                                auto *dc = new QMetaObject::Connection();
-                                *dc = connect(ac, &Toplevel::surfaceChanged, this, [this, ac, dc] {
-                                        if (auto *surface = ac->surface()) {
-                                            surface->setDataProxy(m_surfaceIface);
-                                            QObject::disconnect(*dc);
-                                            delete dc;
-                                        }
-                                      }
-                                );
-                            }
-                    });
-                }
-    );
-    waylandServer()->dispatch();
-}
-
-void Dnd::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
-{
-    if (qobject_cast<XToWlDrag *>(m_currentDrag)) {
+    if (qobject_cast<XToWlDrag*>(sel->m_currentDrag)) {
         // X drag is in progress, rogue X client took over the selection.
         return;
     }
-    if (m_currentDrag) {
+    if (sel->m_currentDrag) {
         // Wl drag is in progress - don't overwrite by rogue X client,
         // get it back instead!
-        ownSelection(true);
+        own_selection(sel, true);
         return;
     }
-    createX11Source(nullptr);
-    const auto *seat = waylandServer()->seat();
-    auto *originSurface = seat->focusedPointerSurface();
+    create_x11_source(sel, nullptr);
+    auto const seat = waylandServer()->seat();
+    auto originSurface = seat->focusedPointerSurface();
     if (!originSurface) {
         return;
     }
@@ -152,36 +75,113 @@ void Dnd::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
         // pressed for now
         return;
     }
-    createX11Source(event);
-    X11Source *source = x11Source();
-    if (!source) {
+    create_x11_source(sel, event);
+    if (!sel->data.x11_source) {
         return;
     }
-    DataBridge::self()->dataDeviceIface()->updateProxy(originSurface);
-    m_currentDrag = new XToWlDrag(source);
-}
-
-void Dnd::x11OffersChanged(const QStringList &added, const QStringList &removed)
-{
-    Q_UNUSED(added);
-    Q_UNUSED(removed);
-    // TODO: handled internally
+    sel->data.srv_device->updateProxy(originSurface);
+    sel->m_currentDrag = new XToWlDrag(sel->data.x11_source, sel);
 }
 
-bool Dnd::handleClientMessage(xcb_client_message_event_t *event)
+template<>
+bool handle_client_message(Dnd* sel, xcb_client_message_event_t* event)
 {
-    for (Drag *drag : m_oldDrags) {
+    for (auto& drag : sel->m_oldDrags) {
         if (drag->handleClientMessage(event)) {
             return true;
         }
     }
-    if (m_currentDrag && m_currentDrag->handleClientMessage(event)) {
+    if (sel->m_currentDrag && sel->m_currentDrag->handleClientMessage(event)) {
         return true;
     }
     return false;
 }
 
-DragEventReply Dnd::dragMoveFilter(Toplevel *target, const QPoint &pos)
+template<>
+void handle_x11_offer_change([[maybe_unused]] Dnd* sel,
+                             [[maybe_unused]] QStringList const& added,
+                             [[maybe_unused]] QStringList const& removed)
+{
+    // Handled internally.
+}
+
+// version of DnD support in X
+const static uint32_t s_version = 5;
+uint32_t Dnd::version()
+{
+    return s_version;
+}
+
+Dnd::Dnd(xcb_atom_t atom, srv_data_device* srv_dev, clt_data_device* clt_dev)
+{
+    data = create_selection_data(atom, srv_dev, clt_dev);
+
+    // TODO(romangg): for window size get current screen size and connect to changes.
+    register_x11_selection(this, QSize(8192, 8192));
+    register_xfixes(this);
+
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
+    xcb_change_property(xcbConn,
+                        XCB_PROP_MODE_REPLACE,
+                        data.window,
+                        atoms->xdnd_aware,
+                        XCB_ATOM_ATOM,
+                        32,
+                        1,
+                        &s_version);
+    xcb_flush(xcbConn);
+
+    QObject::connect(waylandServer()->seat(),
+                     &Wrapland::Server::Seat::dragStarted,
+                     data.qobject.get(),
+                     [this]() { startDrag(); });
+    QObject::connect(waylandServer()->seat(),
+                     &Wrapland::Server::Seat::dragEnded,
+                     data.qobject.get(),
+                     [this]() { endDrag(); });
+
+    const auto* comp = waylandServer()->compositor();
+    m_surface = waylandServer()->internalCompositor()->createSurface(data.qobject.get());
+    m_surface->setInputRegion(nullptr);
+    m_surface->commit(Wrapland::Client::Surface::CommitFlag::None);
+    auto* dc = new QMetaObject::Connection();
+    *dc = QObject::connect(
+        comp,
+        &Wrapland::Server::Compositor::surfaceCreated,
+        data.qobject.get(),
+        [this, dc](Wrapland::Server::Surface* si) {
+            // TODO: how to make sure that it is the iface of m_surface?
+            if (m_surfaceIface || si->client() != waylandServer()->internalConnection()) {
+                return;
+            }
+            QObject::disconnect(*dc);
+            delete dc;
+            m_surfaceIface = si;
+            QObject::connect(
+                workspace(), &Workspace::clientActivated, data.qobject.get(), [this](Toplevel* ac) {
+                    if (!ac || !ac->inherits("KWin::X11Client")) {
+                        return;
+                    }
+                    auto* surface = ac->surface();
+                    if (surface) {
+                        surface->setDataProxy(m_surfaceIface);
+                    } else {
+                        auto* dc = new QMetaObject::Connection();
+                        *dc = QObject::connect(
+                            ac, &Toplevel::surfaceChanged, data.qobject.get(), [this, ac, dc] {
+                                if (auto* surface = ac->surface()) {
+                                    surface->setDataProxy(m_surfaceIface);
+                                    QObject::disconnect(*dc);
+                                    delete dc;
+                                }
+                            });
+                    }
+                });
+        });
+    waylandServer()->dispatch();
+}
+
+DragEventReply Dnd::dragMoveFilter(Toplevel* target, const QPoint& pos)
 {
     // This filter only is used when a drag is in process.
     Q_ASSERT(m_currentDrag);
@@ -190,8 +190,8 @@ DragEventReply Dnd::dragMoveFilter(Toplevel *target, const QPoint &pos)
 
 void Dnd::startDrag()
 {
-    auto *ddi = waylandServer()->seat()->dragSource();
-    if (ddi == DataBridge::self()->dataDeviceIface()) {
+    auto srv_dev = waylandServer()->seat()->dragSource();
+    if (srv_dev == data.srv_device) {
         // X to Wl drag, started by us, is in progress.
         Q_ASSERT(m_currentDrag);
         return;
@@ -201,11 +201,11 @@ void Dnd::startDrag()
     Q_ASSERT(!m_currentDrag);
 
     // New Wl to X drag, init drag and Wl source.
-    m_currentDrag = new WlToXDrag();
-    auto source = new WlSource(this, ddi);
-    source->setDataSourceIface(ddi->dragSource());
-    setWlSource(source);
-    ownSelection(true);
+    m_currentDrag = new WlToXDrag(this);
+    auto source = new WlSource<Wrapland::Server::DataDevice, Wrapland::Server::DataSource>(srv_dev);
+    source->setSourceIface(srv_dev->dragSource());
+    set_wl_source(this, source);
+    own_selection(this, true);
 }
 
 void Dnd::endDrag()
@@ -215,13 +215,15 @@ void Dnd::endDrag()
     if (m_currentDrag->end()) {
         delete m_currentDrag;
     } else {
-        connect(m_currentDrag, &Drag::finish, this, &Dnd::clearOldDrag);
+        QObject::connect(m_currentDrag, &Drag::finish, data.qobject.get(), [this](auto drag) {
+            clearOldDrag(drag);
+        });
         m_oldDrags << m_currentDrag;
     }
     m_currentDrag = nullptr;
 }
 
-void Dnd::clearOldDrag(Drag *drag)
+void Dnd::clearOldDrag(Drag* drag)
 {
     m_oldDrags.removeOne(drag);
     delete drag;
diff --git a/xwl/dnd.h b/xwl/dnd.h
index 6c0d21874c4d6436ace78daf66a30ce0c09c81de..05fbd72845640ee9bc571fe925d391939c68aa78 100644
--- a/xwl/dnd.h
+++ b/xwl/dnd.h
@@ -20,21 +20,15 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #ifndef KWIN_XWL_DND
 #define KWIN_XWL_DND
 
+#include "drag.h"
 #include "selection.h"
 
-#include <QPoint>
+#include <Wrapland/Client/datadevice.h>
+#include <Wrapland/Client/datasource.h>
+#include <Wrapland/Server/data_device.h>
+#include <Wrapland/Server/data_source.h>
 
-namespace Wrapland
-{
-namespace Client
-{
-class Surface;
-}
-namespace Server
-{
-class Surface;
-}
-}
+#include <QPoint>
 
 namespace KWin
 {
@@ -42,32 +36,44 @@ class Toplevel;
 
 namespace Xwl
 {
-class Drag;
+class Dnd;
 enum class DragEventReply;
 
+template<>
+void do_handle_xfixes_notify(Dnd* sel, xcb_xfixes_selection_notify_event_t* event);
+template<>
+bool handle_client_message(Dnd* sel, xcb_client_message_event_t* event);
+template<>
+void handle_x11_offer_change(Dnd* sel, QStringList const& added, QStringList const& removed);
+
 /**
  * Represents the drag and drop mechanism, on X side this is the XDND protocol.
  * For more information on XDND see: https://johnlindal.wixsite.com/xdnd
  */
-class Dnd : public Selection
+class Dnd
 {
-    Q_OBJECT
+    using srv_data_device = Wrapland::Server::DataDevice;
+    using clt_data_device = Wrapland::Client::DataDevice;
 
 public:
-    explicit Dnd(xcb_atom_t atom, QObject *parent);
+    selection_data<srv_data_device, clt_data_device> data;
 
-    static uint32_t version();
+    // active drag or null when no drag active
+    Drag* m_currentDrag = nullptr;
+    QVector<Drag*> m_oldDrags;
+
+    explicit Dnd(xcb_atom_t atom, srv_data_device* srv_dev, clt_data_device* clt_dev);
 
-    void doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) override;
-    void x11OffersChanged(const QStringList &added, const QStringList &removed) override;
-    bool handleClientMessage(xcb_client_message_event_t *event) override;
+    static uint32_t version();
 
-    DragEventReply dragMoveFilter(Toplevel *target, const QPoint &pos);
+    DragEventReply dragMoveFilter(Toplevel* target, const QPoint& pos);
 
-    Wrapland::Server::Surface *surfaceIface() const {
+    Wrapland::Server::Surface* surfaceIface() const
+    {
         return m_surfaceIface;
     }
-    Wrapland::Client::Surface *surface() const {
+    Wrapland::Client::Surface* surface() const
+    {
         return m_surface;
     }
 
@@ -75,14 +81,10 @@ private:
     // start and end Wl native client drags (Wl -> Xwl)
     void startDrag();
     void endDrag();
-    void clearOldDrag(Drag *drag);
-
-    // active drag or null when no drag active
-    Drag *m_currentDrag = nullptr;
-    QVector<Drag *> m_oldDrags;
+    void clearOldDrag(Drag* drag);
 
-    Wrapland::Client::Surface *m_surface;
-    Wrapland::Server::Surface *m_surfaceIface = nullptr;
+    Wrapland::Client::Surface* m_surface;
+    Wrapland::Server::Surface* m_surfaceIface = nullptr;
 
     Q_DISABLE_COPY(Dnd)
 };
diff --git a/xwl/drag.cpp b/xwl/drag.cpp
index 0877da775ec3c56f3b5e374b741e67f2e180bfec..2487dbcf4b8f16345ad59c9eeb56381024e42e19 100644
--- a/xwl/drag.cpp
+++ b/xwl/drag.cpp
@@ -26,8 +26,9 @@ namespace KWin
 namespace Xwl
 {
 
-Drag::Drag(QObject *parent)
+Drag::Drag(Dnd* dnd, QObject* parent)
     : QObject(parent)
+    , dnd(dnd)
 {
 }
 
@@ -35,23 +36,20 @@ Drag::~Drag()
 {
 }
 
-void Drag::sendClientMessage(xcb_window_t target, xcb_atom_t type, xcb_client_message_data_t *data)
+void Drag::sendClientMessage(xcb_window_t target, xcb_atom_t type, xcb_client_message_data_t* data)
 {
-    xcb_client_message_event_t event {
+    xcb_client_message_event_t event{
         XCB_CLIENT_MESSAGE, // response_type
-        32,         // format
-        0,          // sequence
-        target,     // window
-        type,       // type
-        *data,      // data
+        32,                 // format
+        0,                  // sequence
+        target,             // window
+        type,               // type
+        *data,              // data
     };
 
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-    xcb_send_event(xcbConn,
-                   0,
-                   target,
-                   XCB_EVENT_MASK_NO_EVENT,
-                   reinterpret_cast<const char *>(&event));
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
+    xcb_send_event(
+        xcbConn, 0, target, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char*>(&event));
     xcb_flush(xcbConn);
 }
 
@@ -64,7 +62,7 @@ DnDAction Drag::atomToClientAction(xcb_atom_t atom)
     } else if (atom == atoms->xdnd_action_ask) {
         // we currently do not support it - need some test client first
         return DnDAction::None;
-//        return DnDAction::Ask;
+        //        return DnDAction::Ask;
     }
     return DnDAction::None;
 }
@@ -78,7 +76,7 @@ xcb_atom_t Drag::clientActionToAtom(DnDAction action)
     } else if (action == DnDAction::Ask) {
         // we currently do not support it - need some test client first
         return XCB_ATOM_NONE;
-//        return atoms->xdnd_action_ask;
+        //        return atoms->xdnd_action_ask;
     }
     return XCB_ATOM_NONE;
 }
diff --git a/xwl/drag.h b/xwl/drag.h
index 4cef345af7422523e3ff3572e3dd5f605ff95be8..e4e3f71db0525f4acc54bb855c1ce1846f0301a9 100644
--- a/xwl/drag.h
+++ b/xwl/drag.h
@@ -32,6 +32,7 @@ class Toplevel;
 
 namespace Xwl
 {
+class Dnd;
 enum class DragEventReply;
 
 using DnDAction = Wrapland::Client::DataDeviceManager::DnDAction;
@@ -44,20 +45,23 @@ class Drag : public QObject
     Q_OBJECT
 
 public:
-    explicit Drag(QObject *parent = nullptr);
+    Dnd* dnd;
+
+    explicit Drag(Dnd* dnd, QObject* parent = nullptr);
     ~Drag() override;
 
-    static void sendClientMessage(xcb_window_t target, xcb_atom_t type, xcb_client_message_data_t *data);
+    static void
+    sendClientMessage(xcb_window_t target, xcb_atom_t type, xcb_client_message_data_t* data);
     static DnDAction atomToClientAction(xcb_atom_t atom);
     static xcb_atom_t clientActionToAtom(DnDAction action);
 
-    virtual bool handleClientMessage(xcb_client_message_event_t *event) = 0;
-    virtual DragEventReply moveFilter(Toplevel *target, const QPoint &pos) = 0;
+    virtual bool handleClientMessage(xcb_client_message_event_t* event) = 0;
+    virtual DragEventReply moveFilter(Toplevel* target, const QPoint& pos) = 0;
 
     virtual bool end() = 0;
 
 Q_SIGNALS:
-    void finish(Drag *self);
+    void finish(Drag* self);
 
 private:
     Q_DISABLE_COPY(Drag)
diff --git a/xwl/drag_wl.cpp b/xwl/drag_wl.cpp
index 1b78547e8b2c4205682d335ac8aa82cd9f29f11b..27b812bb01e262fc0a31ef3f88f0f609b02d7096 100644
--- a/xwl/drag_wl.cpp
+++ b/xwl/drag_wl.cpp
@@ -19,8 +19,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *********************************************************************/
 #include "drag_wl.h"
 
-#include "databridge.h"
 #include "dnd.h"
+#include "selection.h"
 #include "xwayland.h"
 
 #include "atoms.h"
@@ -45,14 +45,15 @@ namespace KWin
 namespace Xwl
 {
 
-WlToXDrag::WlToXDrag()
+WlToXDrag::WlToXDrag(Dnd* dnd)
+    : Drag(dnd)
 {
     m_dsi = waylandServer()->seat()->dragSource()->dragSource();
 }
 
-DragEventReply WlToXDrag::moveFilter(Toplevel *target, const QPoint &pos)
+DragEventReply WlToXDrag::moveFilter(Toplevel* target, const QPoint& pos)
 {
-    auto *seat = waylandServer()->seat();
+    auto* seat = waylandServer()->seat();
     if (m_visit && m_visit->target() == target) {
         // no target change
         return DragEventReply::Take;
@@ -71,12 +72,12 @@ DragEventReply WlToXDrag::moveFilter(Toplevel *target, const QPoint &pos)
     }
     // new target
     workspace()->activateClient(target, false);
-    seat->setDragTarget(DataBridge::self()->dnd()->surfaceIface(), pos, target->input_transform());
+    seat->setDragTarget(dnd->surfaceIface(), pos, target->input_transform());
     m_visit = new Xvisit(this, target);
     return DragEventReply::Take;
 }
 
-bool WlToXDrag::handleClientMessage(xcb_client_message_event_t *event)
+bool WlToXDrag::handleClientMessage(xcb_client_message_event_t* event)
 {
     if (m_visit && m_visit->handleClientMessage(event)) {
         return true;
@@ -91,7 +92,7 @@ bool WlToXDrag::end()
         m_visit = nullptr;
         return true;
     }
-    connect(m_visit, &Xvisit::finish, this, [this](Xvisit *visit) {
+    connect(m_visit, &Xvisit::finish, this, [this](Xvisit* visit) {
         Q_ASSERT(m_visit == visit);
         delete visit;
         m_visit = nullptr;
@@ -101,20 +102,16 @@ bool WlToXDrag::end()
     return false;
 }
 
-Xvisit::Xvisit(WlToXDrag *drag, Toplevel* target)
-    : QObject(drag),
-      m_drag(drag),
-      m_target(target)
+Xvisit::Xvisit(WlToXDrag* drag, Toplevel* target)
+    : QObject(drag)
+    , m_drag(drag)
+    , m_target(target)
 {
     // first check supported DND version
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-    xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn,
-                                                        0,
-                                                        m_target->xcb_window(),
-                                                        atoms->xdnd_aware,
-                                                        XCB_GET_PROPERTY_TYPE_ANY,
-                                                        0, 1);
-    auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
+    xcb_get_property_cookie_t cookie = xcb_get_property(
+        xcbConn, 0, m_target->xcb_window(), atoms->xdnd_aware, XCB_GET_PROPERTY_TYPE_ANY, 0, 1);
+    auto* reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
     if (!reply) {
         doFinish();
         return;
@@ -124,7 +121,7 @@ Xvisit::Xvisit(WlToXDrag *drag, Toplevel* target)
         free(reply);
         return;
     }
-    xcb_atom_t *value = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
+    xcb_atom_t* value = static_cast<xcb_atom_t*>(xcb_get_property_value(reply));
     m_version = qMin(*value, Dnd::version());
     if (m_version < 1) {
         // minimal version we accept is 1
@@ -134,15 +131,14 @@ Xvisit::Xvisit(WlToXDrag *drag, Toplevel* target)
     }
     free(reply);
 
-    const auto *dd = DataBridge::self()->dataDevice();
+    auto const dd = drag->dnd->data.clt_device;
     // proxy drop
-    m_enterConnection = connect(dd, &Wrapland::Client::DataDevice::dragEntered,
-                         this, &Xvisit::receiveOffer);
-    m_dropConnection = connect(dd, &Wrapland::Client::DataDevice::dropped,
-                        this, &Xvisit::drop);
+    m_enterConnection
+        = connect(dd, &Wrapland::Client::DataDevice::dragEntered, this, &Xvisit::receiveOffer);
+    m_dropConnection = connect(dd, &Wrapland::Client::DataDevice::dropped, this, &Xvisit::drop);
 }
 
-bool Xvisit::handleClientMessage(xcb_client_message_event_t *event)
+bool Xvisit::handleClientMessage(xcb_client_message_event_t* event)
 {
     if (event->type == atoms->xdnd_status) {
         return handleStatus(event);
@@ -152,9 +148,9 @@ bool Xvisit::handleClientMessage(xcb_client_message_event_t *event)
     return false;
 }
 
-bool Xvisit::handleStatus(xcb_client_message_event_t *event)
+bool Xvisit::handleStatus(xcb_client_message_event_t* event)
 {
-    xcb_client_message_data_t *data = &event->data;
+    xcb_client_message_data_t* data = &event->data;
     if (data->data32[0] != m_target->xcb_window()) {
         // wrong target window
         return false;
@@ -186,9 +182,9 @@ bool Xvisit::handleStatus(xcb_client_message_event_t *event)
     return true;
 }
 
-bool Xvisit::handleFinished(xcb_client_message_event_t *event)
+bool Xvisit::handleFinished(xcb_client_message_event_t* event)
 {
-    xcb_client_message_data_t *data = &event->data;
+    xcb_client_message_data_t* data = &event->data;
 
     if (data->data32[0] != m_target->xcb_window()) {
         // different target window
@@ -202,8 +198,8 @@ bool Xvisit::handleFinished(xcb_client_message_event_t *event)
     }
 
     const bool success = m_version > 4 ? data->data32[1] & 1 : true;
-    const xcb_atom_t usedActionAtom = m_version > 4 ? data->data32[2] :
-                                                      static_cast<uint32_t>(XCB_ATOM_NONE);
+    const xcb_atom_t usedActionAtom
+        = m_version > 4 ? data->data32[2] : static_cast<uint32_t>(XCB_ATOM_NONE);
     Q_UNUSED(success);
     Q_UNUSED(usedActionAtom);
 
@@ -217,7 +213,7 @@ bool Xvisit::handleFinished(xcb_client_message_event_t *event)
     return true;
 }
 
-void Xvisit::sendPosition(const QPointF &globalPos)
+void Xvisit::sendPosition(const QPointF& globalPos)
 {
     const int16_t x = globalPos.x();
     const int16_t y = globalPos.y();
@@ -229,8 +225,8 @@ void Xvisit::sendPosition(const QPointF &globalPos)
     }
     m_pos.pending = true;
 
-    xcb_client_message_data_t data = {0};
-    data.data32[0] = DataBridge::self()->dnd()->window();
+    xcb_client_message_data_t data = {{0}};
+    data.data32[0] = m_drag->dnd->data.window;
     data.data32[2] = (x << 16) | y;
     data.data32[3] = XCB_CURRENT_TIME;
     data.data32[4] = Drag::clientActionToAtom(m_proposedAction);
@@ -260,12 +256,14 @@ void Xvisit::receiveOffer()
     }
 
     Q_ASSERT(m_dataOffer.isNull());
-    m_dataOffer = DataBridge::self()->dataDevice()->dragOffer();
+    m_dataOffer = m_drag->dnd->data.clt_device->dragOffer();
     Q_ASSERT(!m_dataOffer.isNull());
 
     retrieveSupportedActions();
-    m_actionConnection = connect(m_dataOffer, &Wrapland::Client::DataOffer::sourceDragAndDropActionsChanged,
-                          this, &Xvisit::retrieveSupportedActions);
+    m_actionConnection = connect(m_dataOffer,
+                                 &Wrapland::Client::DataOffer::sourceDragAndDropActionsChanged,
+                                 this,
+                                 &Xvisit::retrieveSupportedActions);
     enter();
 }
 
@@ -278,14 +276,15 @@ void Xvisit::enter()
 
     // proxy future pointer position changes
     m_motionConnection = connect(waylandServer()->seat(),
-                          &Wrapland::Server::Seat::pointerPosChanged,
-                          this, &Xvisit::sendPosition);
+                                 &Wrapland::Server::Seat::pointerPosChanged,
+                                 this,
+                                 &Xvisit::sendPosition);
 }
 
 void Xvisit::sendEnter()
 {
-    xcb_client_message_data_t data = {0};
-    data.data32[0] = DataBridge::self()->dnd()->window();
+    xcb_client_message_data_t data = {{0}};
+    data.data32[0] = m_drag->dnd->data.window;
     data.data32[1] = m_version << 24;
 
     // TODO: replace this with the mime type getter from m_dataOffer,
@@ -294,12 +293,12 @@ void Xvisit::sendEnter()
     const int mimesCount = mimeTypesNames.size();
     size_t cnt = 0;
     size_t totalCnt = 0;
-    for (const auto mimeName : mimeTypesNames) {
+    for (const auto& mimeName : mimeTypesNames) {
         // 3 mimes and less can be sent directly in the XdndEnter message
         if (totalCnt == 3) {
             break;
         }
-        const auto atom = Selection::mimeTypeToAtom(mimeName.c_str());
+        const auto atom = mimeTypeToAtom(mimeName.c_str());
 
         if (atom != XCB_ATOM_NONE) {
             data.data32[cnt + 2] = atom;
@@ -319,8 +318,8 @@ void Xvisit::sendEnter()
         targets.resize(mimesCount);
 
         size_t cnt = 0;
-        for (const auto mimeName : mimeTypesNames) {
-            const auto atom = Selection::mimeTypeToAtom(mimeName.c_str());
+        for (const auto& mimeName : mimeTypesNames) {
+            const auto atom = mimeTypeToAtom(mimeName.c_str());
             if (atom != XCB_ATOM_NONE) {
                 targets[cnt] = atom;
                 cnt++;
@@ -329,18 +328,20 @@ void Xvisit::sendEnter()
 
         xcb_change_property(kwinApp()->x11Connection(),
                             XCB_PROP_MODE_REPLACE,
-                            DataBridge::self()->dnd()->window(),
+                            m_drag->dnd->data.window,
                             atoms->xdnd_type_list,
                             XCB_ATOM_ATOM,
-                            32, cnt, targets.data());
+                            32,
+                            cnt,
+                            targets.data());
     }
     Drag::sendClientMessage(m_target->xcb_window(), atoms->xdnd_enter, &data);
 }
 
 void Xvisit::sendDrop(uint32_t time)
 {
-    xcb_client_message_data_t data = {0};
-    data.data32[0] = DataBridge::self()->dnd()->window();
+    xcb_client_message_data_t data = {{0}};
+    data.data32[0] = m_drag->dnd->data.window;
     data.data32[2] = time;
 
     Drag::sendClientMessage(m_target->xcb_window(), atoms->xdnd_drop, &data);
@@ -352,8 +353,8 @@ void Xvisit::sendDrop(uint32_t time)
 
 void Xvisit::sendLeave()
 {
-    xcb_client_message_data_t data = {0};
-    data.data32[0] = DataBridge::self()->dnd()->window();
+    xcb_client_message_data_t data = {{0}};
+    data.data32[0] = m_drag->dnd->data.window;
     Drag::sendClientMessage(m_target->xcb_window(), atoms->xdnd_leave, &data);
 }
 
@@ -385,8 +386,7 @@ void Xvisit::requestDragAndDropAction()
     if (m_dataOffer.isNull()) {
         return;
     }
-    const auto pref = m_preferredAction != DnDAction::None ? m_preferredAction:
-                                                           DnDAction::Copy;
+    const auto pref = m_preferredAction != DnDAction::None ? m_preferredAction : DnDAction::Copy;
     // we assume the X client supports Move, but this might be wrong - then
     // the drag just cancels, if the user tries to force it.
 
diff --git a/xwl/drag_wl.h b/xwl/drag_wl.h
index b83cd119d4d571d2cd464b93c980378efc87e756..a75451b5cad2cb3a9aa308040459a6827b48cf9c 100644
--- a/xwl/drag_wl.h
+++ b/xwl/drag_wl.h
@@ -22,8 +22,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include "drag.h"
 
-#include <Wrapland/Client/dataoffer.h>
-
 #include <QPoint>
 #include <QPointer>
 #include <QVector>
@@ -32,6 +30,7 @@ namespace Wrapland
 {
 namespace Client
 {
+class DataOffer;
 class Surface;
 }
 namespace Server
@@ -48,7 +47,6 @@ class Toplevel;
 
 namespace Xwl
 {
-class X11Source;
 enum class DragEventReply;
 class Xvisit;
 
@@ -59,20 +57,21 @@ class WlToXDrag : public Drag
     Q_OBJECT
 
 public:
-    explicit WlToXDrag();
+    explicit WlToXDrag(Dnd* dnd);
 
-    DragEventReply moveFilter(Toplevel *target, const QPoint &pos) override;
-    bool handleClientMessage(xcb_client_message_event_t *event) override;
+    DragEventReply moveFilter(Toplevel* target, const QPoint& pos) override;
+    bool handleClientMessage(xcb_client_message_event_t* event) override;
 
     bool end() override;
 
-    Wrapland::Server::DataSource *dataSourceIface() const {
+    Wrapland::Server::DataSource* dataSourceIface() const
+    {
         return m_dsi;
     }
 
 private:
-    Wrapland::Server::DataSource *m_dsi;
-    Xvisit *m_visit = nullptr;
+    Wrapland::Server::DataSource* m_dsi;
+    Xvisit* m_visit = nullptr;
 
     Q_DISABLE_COPY(WlToXDrag)
 };
@@ -85,24 +84,26 @@ class Xvisit : public QObject
 public:
     // TODO: handle ask action
 
-    Xvisit(WlToXDrag *drag, Toplevel *target);
+    Xvisit(WlToXDrag* drag, Toplevel* target);
 
-    bool handleClientMessage(xcb_client_message_event_t *event);
-    bool handleStatus(xcb_client_message_event_t *event);
-    bool handleFinished(xcb_client_message_event_t *event);
+    bool handleClientMessage(xcb_client_message_event_t* event);
+    bool handleStatus(xcb_client_message_event_t* event);
+    bool handleFinished(xcb_client_message_event_t* event);
 
-    void sendPosition(const QPointF &globalPos);
+    void sendPosition(const QPointF& globalPos);
     void leave();
 
-    bool finished() const {
+    bool finished() const
+    {
         return m_state.finished;
     }
-    Toplevel *target() const {
+    Toplevel* target() const
+    {
         return m_target;
     }
 
 Q_SIGNALS:
-    void finish(Xvisit *self);
+    void finish(Xvisit* self);
 
 private:
     void sendEnter();
@@ -122,8 +123,8 @@ private:
     void doFinish();
     void stopConnections();
 
-    WlToXDrag *m_drag;
-    Toplevel *m_target;
+    WlToXDrag* m_drag;
+    Toplevel* m_target;
     uint32_t m_version = 0;
 
     QMetaObject::Connection m_enterConnection;
diff --git a/xwl/drag_x.cpp b/xwl/drag_x.cpp
index a5f2ac678e7454b0f1781073237f3dd9d0f2dc26..e4a1768a61fbae18a9f2fcfb2aec2475ace4ddd0 100644
--- a/xwl/drag_x.cpp
+++ b/xwl/drag_x.cpp
@@ -19,8 +19,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *********************************************************************/
 #include "drag_x.h"
 
-#include "databridge.h"
 #include "dnd.h"
+#include "selection.h"
 #include "selection_source.h"
 #include "xwayland.h"
 
@@ -45,56 +45,45 @@ namespace KWin
 namespace Xwl
 {
 
-static QStringList atomToMimeTypes(xcb_atom_t atom)
+XToWlDrag::XToWlDrag(DataX11Source* source, Dnd* dnd)
+    : Drag(dnd)
+    , m_source(source)
 {
-    QStringList mimeTypes;
-
-    if (atom == atoms->utf8_string) {
-        mimeTypes << QString::fromLatin1("text/plain;charset=utf-8");
-    } else if (atom == atoms->text) {
-        mimeTypes << QString::fromLatin1("text/plain");
-    } else if (atom == atoms->uri_list || atom == atoms->netscape_url || atom == atoms->moz_url) {
-    // We identify netscape and moz format as less detailed formats text/uri-list,
-    // text/x-uri and accept the information loss.
-        mimeTypes << QString::fromLatin1("text/uri-list") << QString::fromLatin1("text/x-uri");
-    } else {
-        mimeTypes << Selection::atomName(atom);
-    }
-    return mimeTypes;
-}
-
-XToWlDrag::XToWlDrag(X11Source *source)
-    : m_source(source)
-{
-    connect(DataBridge::self()->dnd(), &Dnd::transferFinished, this, [this](xcb_timestamp_t eventTime) {
-        // we use this mechanism, because the finished call is not
-        // reliable done by Wayland clients
-        auto it = std::find_if(m_dataRequests.begin(), m_dataRequests.end(), [eventTime](const QPair<xcb_timestamp_t, bool> &req) {
-            return req.first == eventTime && req.second == false;
+    connect(dnd->data.qobject.get(),
+            &q_selection::transferFinished,
+            this,
+            [this](xcb_timestamp_t eventTime) {
+                // we use this mechanism, because the finished call is not
+                // reliable done by Wayland clients
+                auto it = std::find_if(m_dataRequests.begin(),
+                                       m_dataRequests.end(),
+                                       [eventTime](const QPair<xcb_timestamp_t, bool>& req) {
+                                           return req.first == eventTime && req.second == false;
+                                       });
+                if (it == m_dataRequests.end()) {
+                    // transfer finished for a different drag
+                    return;
+                }
+                (*it).second = true;
+                checkForFinished();
+            });
+    connect(
+        source->qobject(), &qX11Source::transferReady, this, [this](xcb_atom_t target, qint32 fd) {
+            Q_UNUSED(target);
+            Q_UNUSED(fd);
+            m_dataRequests << QPair<xcb_timestamp_t, bool>(m_source->timestamp(), false);
         });
-        if (it == m_dataRequests.end()) {
-            // transfer finished for a different drag
-            return;
-        }
-        (*it).second = true;
-        checkForFinished();
-    });
-    connect(source, &X11Source::transferReady, this, [this](xcb_atom_t target, qint32 fd) {
-        Q_UNUSED(target);
-        Q_UNUSED(fd);
-        m_dataRequests << QPair<xcb_timestamp_t, bool>(m_source->timestamp(), false);
-    });
-    auto *ddm = waylandServer()->internalDataDeviceManager();
-    m_dataSource = ddm->createDataSource(this);
+    auto* ddm = waylandServer()->internalDataDeviceManager();
+    m_dataSource = ddm->createSource(this);
     connect(m_dataSource, &Wrapland::Client::DataSource::dragAndDropPerformed, this, [this] {
         m_performed = true;
         if (m_visit) {
-            connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) {
+            connect(m_visit, &WlVisit::finish, this, [this](WlVisit* visit) {
                 Q_UNUSED(visit);
                 checkForFinished();
             });
 
-            QTimer::singleShot(2000, this, [this]{
+            QTimer::singleShot(2000, this, [this] {
                 if (!m_visit->entered() || !m_visit->dropHandled()) {
                     // X client timed out
                     Q_EMIT finish(this);
@@ -113,28 +102,31 @@ XToWlDrag::XToWlDrag(X11Source *source)
     });
 
     // source does _not_ take ownership of m_dataSource
-    source->setDataSource(m_dataSource);
-
-    auto *dc = new QMetaObject::Connection();
-    *dc = connect(waylandServer()->dataDeviceManager(), &Wrapland::Server::DataDeviceManager::dataSourceCreated, this,
-                 [this, dc](Wrapland::Server::DataSource *dsi) {
-                    Q_ASSERT(dsi);
-                    if (dsi->client() != waylandServer()->internalConnection()) {
-                        return;
-                    }
-                    QObject::disconnect(*dc);
-                    delete dc;
-                    connect(dsi, &Wrapland::Server::DataSource::mimeTypeOffered, this, &XToWlDrag::offerCallback);
-                }
-    );
+    source->setSource(m_dataSource);
+
+    auto* dc = new QMetaObject::Connection();
+    *dc = connect(waylandServer()->dataDeviceManager(),
+                  &Wrapland::Server::DataDeviceManager::sourceCreated,
+                  this,
+                  [this, dc](Wrapland::Server::DataSource* dsi) {
+                      Q_ASSERT(dsi);
+                      if (dsi->client() != waylandServer()->internalConnection()) {
+                          return;
+                      }
+                      QObject::disconnect(*dc);
+                      delete dc;
+                      connect(dsi,
+                              &Wrapland::Server::DataSource::mimeTypeOffered,
+                              this,
+                              &XToWlDrag::offerCallback);
+                  });
     // Start drag with serial of last left pointer button press.
     // This means X to Wl drags can only be executed with the left pointer button being pressed.
     // For touch and (maybe) other pointer button drags we have to revisit this.
     //
     // Until then we accept the restriction for Xwayland clients.
-    DataBridge::self()->dataDevice()->startDrag(waylandServer()->seat()->pointerButtonSerial(Qt::LeftButton),
-                                                m_dataSource,
-                                                DataBridge::self()->dnd()->surface());
+    dnd->data.clt_device->startDrag(
+        waylandServer()->seat()->pointerButtonSerial(Qt::LeftButton), m_dataSource, dnd->surface());
     waylandServer()->dispatch();
 }
 
@@ -144,11 +136,11 @@ XToWlDrag::~XToWlDrag()
     m_dataSource = nullptr;
 }
 
-DragEventReply XToWlDrag::moveFilter(Toplevel* target, const QPoint &pos)
+DragEventReply XToWlDrag::moveFilter(Toplevel* target, const QPoint& pos)
 {
     Q_UNUSED(pos);
 
-    auto *seat = waylandServer()->seat();
+    auto* seat = waylandServer()->seat();
 
     if (m_visit && m_visit->target() == target) {
         // still same Wl target, wait for X events
@@ -158,7 +150,7 @@ DragEventReply XToWlDrag::moveFilter(Toplevel* target, const QPoint &pos)
         if (m_visit->leave()) {
             delete m_visit;
         } else {
-            connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) {
+            connect(m_visit, &WlVisit::finish, this, [this](WlVisit* visit) {
                 m_oldVisits.removeOne(visit);
                 delete visit;
             });
@@ -168,8 +160,8 @@ DragEventReply XToWlDrag::moveFilter(Toplevel* target, const QPoint &pos)
     const bool hasCurrent = m_visit;
     m_visit = nullptr;
 
-    if (!target || !target->surface() ||
-            target->surface()->client() == waylandServer()->xWaylandConnection()) {
+    if (!target || !target->surface()
+        || target->surface()->client() == waylandServer()->xWaylandConnection()) {
         // currently there is no target or target is an Xwayland window
         // handled here and by X directly
         if (target->control) {
@@ -190,9 +182,9 @@ DragEventReply XToWlDrag::moveFilter(Toplevel* target, const QPoint &pos)
     return DragEventReply::Ignore;
 }
 
-bool XToWlDrag::handleClientMessage(xcb_client_message_event_t *event)
+bool XToWlDrag::handleClientMessage(xcb_client_message_event_t* event)
 {
-    for (auto *visit : m_oldVisits) {
+    for (auto* visit : m_oldVisits) {
         if (visit->handleClientMessage(event)) {
             return true;
         }
@@ -219,7 +211,7 @@ DnDAction XToWlDrag::selectedDragAndDropAction()
     return m_lastSelectedDragAndDropAction;
 }
 
-void XToWlDrag::setOffers(const Mimes &offers)
+void XToWlDrag::setOffers(const Mimes& offers)
 {
     m_source->setOffers(offers);
     if (offers.isEmpty()) {
@@ -238,17 +230,19 @@ void XToWlDrag::setOffers(const Mimes &offers)
     // TODO: make sure that offers are not changed in between visits
 
     m_offersPending = m_offers = offers;
-    for (const auto mimePair : offers) {
+    for (const auto& mimePair : offers) {
         m_dataSource->offer(mimePair.first);
     }
 }
 
 using Mime = QPair<QString, xcb_atom_t>;
 
-void XToWlDrag::offerCallback(std::string const &mime)
+void XToWlDrag::offerCallback(std::string const& mime)
 {
-    m_offersPending.erase(std::remove_if(m_offersPending.begin(), m_offersPending.end(),
-                   [mime](const Mime &m) { return m.first == mime.c_str(); }));
+    m_offersPending.erase(
+        std::remove_if(m_offersPending.begin(), m_offersPending.end(), [mime](const Mime& m) {
+            return m.first == mime.c_str();
+        }));
     if (m_offersPending.isEmpty() && m_visit && m_visit->entered()) {
         setDragTarget();
     }
@@ -256,7 +250,7 @@ void XToWlDrag::offerCallback(std::string const &mime)
 
 void XToWlDrag::setDragTarget()
 {
-    auto *ac = m_visit->target();
+    auto* ac = m_visit->target();
     workspace()->activateClient(ac);
     waylandServer()->seat()->setDragTarget(ac->surface(), ac->input_transform());
 }
@@ -275,8 +269,10 @@ bool XToWlDrag::checkForFinished()
         // need to wait for first data request
         return false;
     }
-    const bool transfersFinished = std::all_of(m_dataRequests.begin(), m_dataRequests.end(),
-                                               [](QPair<xcb_timestamp_t, bool> req) { return req.second; });
+    const bool transfersFinished
+        = std::all_of(m_dataRequests.begin(),
+                      m_dataRequests.end(),
+                      [](QPair<xcb_timestamp_t, bool> req) { return req.second; });
     if (transfersFinished) {
         m_visit->sendFinished();
         Q_EMIT finish(this);
@@ -284,24 +280,26 @@ bool XToWlDrag::checkForFinished()
     return transfersFinished;
 }
 
-WlVisit::WlVisit(Toplevel* target, XToWlDrag *drag)
-    : QObject(drag),
-      m_target(target),
-      m_drag(drag)
+WlVisit::WlVisit(Toplevel* target, XToWlDrag* drag)
+    : QObject(drag)
+    , m_target(target)
+    , m_drag(drag)
 {
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
 
     m_window = xcb_generate_id(xcbConn);
-    DataBridge::self()->dnd()->overwriteRequestorWindow(m_window);
+    overwrite_requestor_window(drag->dnd, m_window);
 
-    const uint32_t dndValues[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
-                                   XCB_EVENT_MASK_PROPERTY_CHANGE };
+    const uint32_t dndValues[]
+        = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE};
     xcb_create_window(xcbConn,
                       XCB_COPY_FROM_PARENT,
                       m_window,
                       kwinApp()->x11RootWindow(),
-                      0, 0,
-                      8192, 8192,           // TODO: get current screen size and connect to changes
+                      0,
+                      0,
+                      8192,
+                      8192, // TODO: get current screen size and connect to changes
                       0,
                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
                       Xwayland::self()->xcbScreen()->root_visual,
@@ -314,7 +312,9 @@ WlVisit::WlVisit(Toplevel* target, XToWlDrag *drag)
                         m_window,
                         atoms->xdnd_aware,
                         XCB_ATOM_ATOM,
-                        32, 1, &version);
+                        32,
+                        1,
+                        &version);
 
     xcb_map_window(xcbConn, m_window);
     workspace()->stacking_order->add_manual_overlay(m_window);
@@ -326,19 +326,19 @@ WlVisit::WlVisit(Toplevel* target, XToWlDrag *drag)
 
 WlVisit::~WlVisit()
 {
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
     xcb_destroy_window(xcbConn, m_window);
     xcb_flush(xcbConn);
 }
 
 bool WlVisit::leave()
 {
-    DataBridge::self()->dnd()->overwriteRequestorWindow(XCB_WINDOW_NONE);
+    overwrite_requestor_window(m_drag->dnd, XCB_WINDOW_NONE);
     unmapProxyWindow();
     return m_finished;
 }
 
-bool WlVisit::handleClientMessage(xcb_client_message_event_t *event)
+bool WlVisit::handleClientMessage(xcb_client_message_event_t* event)
 {
     if (event->window != m_window) {
         // different window
@@ -357,13 +357,13 @@ bool WlVisit::handleClientMessage(xcb_client_message_event_t *event)
     return false;
 }
 
-static bool hasMimeName(const Mimes &mimes, const QString &name)
+static bool hasMimeName(const Mimes& mimes, const QString& name)
 {
-    return std::any_of(mimes.begin(), mimes.end(),
-                       [name](const Mime &m) { return m.first == name; });
+    return std::any_of(
+        mimes.begin(), mimes.end(), [name](const Mime& m) { return m.first == name; });
 }
 
-bool WlVisit::handleEnter(xcb_client_message_event_t *event)
+bool WlVisit::handleEnter(xcb_client_message_event_t* event)
 {
     if (m_entered) {
         // a drag already entered
@@ -371,7 +371,7 @@ bool WlVisit::handleEnter(xcb_client_message_event_t *event)
     }
     m_entered = true;
 
-    xcb_client_message_data_t *data = &event->data;
+    xcb_client_message_data_t* data = &event->data;
     m_srcWindow = data->data32[0];
     m_version = data->data32[1] >> 24;
 
@@ -382,7 +382,7 @@ bool WlVisit::handleEnter(xcb_client_message_event_t *event)
         for (size_t i = 0; i < 3; i++) {
             xcb_atom_t mimeAtom = data->data32[2 + i];
             const auto mimeStrings = atomToMimeTypes(mimeAtom);
-            for (const auto mime : mimeStrings ) {
+            for (const auto& mime : mimeStrings) {
                 if (!hasMimeName(offers, mime)) {
                     offers << Mime(mime, mimeAtom);
                 }
@@ -397,17 +397,13 @@ bool WlVisit::handleEnter(xcb_client_message_event_t *event)
     return true;
 }
 
-void WlVisit::getMimesFromWinProperty(Mimes &offers)
+void WlVisit::getMimesFromWinProperty(Mimes& offers)
 {
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-    auto cookie = xcb_get_property(xcbConn,
-                                   0,
-                                   m_srcWindow,
-                                   atoms->xdnd_type_list,
-                                   XCB_GET_PROPERTY_TYPE_ANY,
-                                   0, 0x1fffffff);
-
-    auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
+    auto cookie = xcb_get_property(
+        xcbConn, 0, m_srcWindow, atoms->xdnd_type_list, XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff);
+
+    auto* reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
     if (reply == nullptr) {
         return;
     }
@@ -417,10 +413,10 @@ void WlVisit::getMimesFromWinProperty(Mimes &offers)
         return;
     }
 
-    xcb_atom_t *mimeAtoms = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
+    xcb_atom_t* mimeAtoms = static_cast<xcb_atom_t*>(xcb_get_property_value(reply));
     for (size_t i = 0; i < reply->value_len; ++i) {
         const auto mimeStrings = atomToMimeTypes(mimeAtoms[i]);
-        for (const auto mime : mimeStrings) {
+        for (const auto& mime : mimeStrings) {
             if (!hasMimeName(offers, mime)) {
                 offers << Mime(mime, mimeAtoms[i]);
             }
@@ -429,9 +425,9 @@ void WlVisit::getMimesFromWinProperty(Mimes &offers)
     free(reply);
 }
 
-bool WlVisit::handlePosition(xcb_client_message_event_t *event)
+bool WlVisit::handlePosition(xcb_client_message_event_t* event)
 {
-    xcb_client_message_data_t *data = &event->data;
+    xcb_client_message_data_t* data = &event->data;
     m_srcWindow = data->data32[0];
 
     if (!m_target) {
@@ -447,8 +443,7 @@ bool WlVisit::handlePosition(xcb_client_message_event_t *event)
     const xcb_timestamp_t timestamp = data->data32[3];
     m_drag->x11Source()->setTimestamp(timestamp);
 
-    xcb_atom_t actionAtom = m_version > 1 ? data->data32[4] :
-                                            atoms->xdnd_action_copy;
+    xcb_atom_t actionAtom = m_version > 1 ? data->data32[4] : atoms->xdnd_action_copy;
     auto action = Drag::atomToClientAction(actionAtom);
 
     if (action == DnDAction::None) {
@@ -467,11 +462,11 @@ bool WlVisit::handlePosition(xcb_client_message_event_t *event)
     return true;
 }
 
-bool WlVisit::handleDrop(xcb_client_message_event_t *event)
+bool WlVisit::handleDrop(xcb_client_message_event_t* event)
 {
     m_dropHandled = true;
 
-    xcb_client_message_data_t *data = &event->data;
+    xcb_client_message_data_t* data = &event->data;
     m_srcWindow = data->data32[0];
     const xcb_timestamp_t timestamp = data->data32[2];
     m_drag->x11Source()->setTimestamp(timestamp);
@@ -489,10 +484,10 @@ void WlVisit::doFinish()
     Q_EMIT finish(this);
 }
 
-bool WlVisit::handleLeave(xcb_client_message_event_t *event)
+bool WlVisit::handleLeave(xcb_client_message_event_t* event)
 {
     m_entered = false;
-    xcb_client_message_data_t *data = &event->data;
+    xcb_client_message_data_t* data = &event->data;
     m_srcWindow = data->data32[0];
     doFinish();
     return true;
@@ -506,7 +501,7 @@ void WlVisit::sendStatus()
         // accept the drop
         flags |= (1 << 0);
     }
-    xcb_client_message_data_t data = {0};
+    xcb_client_message_data_t data = {{0}};
     data.data32[0] = m_window;
     data.data32[1] = flags;
     data.data32[4] = flags & (1 << 0) ? m_actionAtom : static_cast<uint32_t>(XCB_ATOM_NONE);
@@ -516,7 +511,7 @@ void WlVisit::sendStatus()
 void WlVisit::sendFinished()
 {
     const bool accepted = m_entered && m_action != DnDAction::None;
-    xcb_client_message_data_t data = {0};
+    xcb_client_message_data_t data = {{0}};
     data.data32[0] = m_window;
     data.data32[1] = accepted;
     data.data32[2] = accepted ? m_actionAtom : static_cast<uint32_t>(XCB_ATOM_NONE);
@@ -537,7 +532,7 @@ void WlVisit::unmapProxyWindow()
     if (!m_mapped) {
         return;
     }
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
     xcb_unmap_window(xcbConn, m_window);
     workspace()->stacking_order->remove_manual_overlay(m_window);
     workspace()->stacking_order->update(true);
diff --git a/xwl/drag_x.h b/xwl/drag_x.h
index bd3c749a83ee3446bfd82d85a5440031984a4278..7c7b5f66e6214628ea46f9cf169c48cf5e3e104b 100644
--- a/xwl/drag_x.h
+++ b/xwl/drag_x.h
@@ -22,22 +22,14 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include "drag.h"
 
-#include <Wrapland/Client/datadevicemanager.h>
-#include <Wrapland/Client/dataoffer.h>
-
-#include <Wrapland/Server/data_device_manager.h>
-
 #include <QPoint>
 #include <QPointer>
 #include <QVector>
 
-namespace Wrapland
-{
-namespace Client
+namespace Wrapland::Client
 {
 class DataSource;
 }
-}
 
 namespace KWin
 {
@@ -45,50 +37,54 @@ class Toplevel;
 
 namespace Xwl
 {
-class X11Source;
 enum class DragEventReply;
 class WlVisit;
+template<typename>
+class X11Source;
 
-using Mimes = QVector<QPair<QString, xcb_atom_t> >;
+using Mimes = QVector<QPair<QString, xcb_atom_t>>;
+using DataX11Source = X11Source<Wrapland::Client::DataSource>;
 
 class XToWlDrag : public Drag
 {
     Q_OBJECT
 
 public:
-    explicit XToWlDrag(X11Source *source);
+    explicit XToWlDrag(DataX11Source* source, Dnd* dnd);
     ~XToWlDrag() override;
 
-    DragEventReply moveFilter(Toplevel* target, const QPoint &pos) override;
-    bool handleClientMessage(xcb_client_message_event_t *event) override;
+    DragEventReply moveFilter(Toplevel* target, const QPoint& pos) override;
+    bool handleClientMessage(xcb_client_message_event_t* event) override;
 
     void setDragAndDropAction(DnDAction action);
     DnDAction selectedDragAndDropAction();
 
-    bool end() override {
+    bool end() override
+    {
         return false;
     }
-    X11Source *x11Source() const {
+    DataX11Source* x11Source() const
+    {
         return m_source;
     }
 
 private:
-    void setOffers(const Mimes &offers);
-    void offerCallback(const std::string &mime);
+    void setOffers(const Mimes& offers);
+    void offerCallback(const std::string& mime);
     void setDragTarget();
 
     bool checkForFinished();
 
-    Wrapland::Client::DataSource *m_dataSource;
+    Wrapland::Client::DataSource* m_dataSource;
 
     Mimes m_offers;
     Mimes m_offersPending;
 
-    X11Source *m_source;
-    QVector<QPair<xcb_timestamp_t, bool> > m_dataRequests;
+    DataX11Source* m_source;
+    QVector<QPair<xcb_timestamp_t, bool>> m_dataRequests;
 
-    WlVisit *m_visit = nullptr;
-    QVector<WlVisit *> m_oldVisits;
+    WlVisit* m_visit = nullptr;
+    QVector<WlVisit*> m_oldVisits;
 
     bool m_performed = false;
     DnDAction m_lastSelectedDragAndDropAction = DnDAction::None;
@@ -101,42 +97,47 @@ class WlVisit : public QObject
     Q_OBJECT
 
 public:
-    WlVisit(Toplevel* target, XToWlDrag *drag);
+    WlVisit(Toplevel* target, XToWlDrag* drag);
     ~WlVisit() override;
 
-    bool handleClientMessage(xcb_client_message_event_t *event);
+    bool handleClientMessage(xcb_client_message_event_t* event);
     bool leave();
 
-    Toplevel* target() const {
+    Toplevel* target() const
+    {
         return m_target;
     }
-    xcb_window_t window() const {
+    xcb_window_t window() const
+    {
         return m_window;
     }
-    bool entered() const {
+    bool entered() const
+    {
         return m_entered;
     }
-    bool dropHandled() const {
+    bool dropHandled() const
+    {
         return m_dropHandled;
     }
-    bool finished() const {
+    bool finished() const
+    {
         return m_finished;
     }
     void sendFinished();
 
 Q_SIGNALS:
-    void offersReceived(const Mimes &offers);
-    void finish(WlVisit *self);
+    void offersReceived(const Mimes& offers);
+    void finish(WlVisit* self);
 
 private:
-    bool handleEnter(xcb_client_message_event_t *event);
-    bool handlePosition(xcb_client_message_event_t *event);
-    bool handleDrop(xcb_client_message_event_t *event);
-    bool handleLeave(xcb_client_message_event_t *event);
+    bool handleEnter(xcb_client_message_event_t* event);
+    bool handlePosition(xcb_client_message_event_t* event);
+    bool handleDrop(xcb_client_message_event_t* event);
+    bool handleLeave(xcb_client_message_event_t* event);
 
     void sendStatus();
 
-    void getMimesFromWinProperty(Mimes &offers);
+    void getMimesFromWinProperty(Mimes& offers);
 
     bool targetAcceptsAction() const;
 
@@ -147,7 +148,7 @@ private:
     xcb_window_t m_window;
 
     xcb_window_t m_srcWindow = XCB_WINDOW_NONE;
-    XToWlDrag *m_drag;
+    XToWlDrag* m_drag;
 
     uint32_t m_version = 0;
 
diff --git a/xwl/primary_selection.cpp b/xwl/primary_selection.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5ae0153103472f426fb75651a5be176bc2a6c5f3
--- /dev/null
+++ b/xwl/primary_selection.cpp
@@ -0,0 +1,44 @@
+/*
+    SPDX-FileCopyrightText: 2021 Roman Gilg <subdiff@gmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+#include "primary_selection.h"
+
+#include <Wrapland/Server/seat.h>
+
+namespace KWin::Xwl
+{
+
+primary_selection::primary_selection(xcb_atom_t atom,
+                                     srv_data_device* srv_dev,
+                                     clt_data_device* clt_dev)
+{
+    data = create_selection_data(atom, srv_dev, clt_dev);
+
+    register_x11_selection(this, QSize(10, 10));
+
+    QObject::connect(waylandServer()->seat(),
+                     &Wrapland::Server::Seat::primarySelectionChanged,
+                     data.qobject.get(),
+                     [this] { handle_wl_selection_change(this); });
+}
+
+primary_selection::srv_data_device* primary_selection::get_current_device() const
+{
+    return waylandServer()->seat()->primarySelection();
+}
+
+Wrapland::Client::PrimarySelectionDeviceManager*
+primary_selection::get_internal_device_manager() const
+{
+    return waylandServer()->internalPrimarySelectionDeviceManager();
+}
+
+std::function<void(primary_selection::srv_data_device*)>
+primary_selection::get_selection_setter() const
+{
+    return [](srv_data_device* dev) { waylandServer()->seat()->setPrimarySelection(dev); };
+}
+
+}
diff --git a/xwl/primary_selection.h b/xwl/primary_selection.h
new file mode 100644
index 0000000000000000000000000000000000000000..2ed4ff0dc1b75b250e55b06ad117515518692a34
--- /dev/null
+++ b/xwl/primary_selection.h
@@ -0,0 +1,39 @@
+/*
+    SPDX-FileCopyrightText: 2021 Roman Gilg <subdiff@gmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+#pragma once
+
+#include "selection.h"
+
+#include <Wrapland/Client/primary_selection.h>
+#include <Wrapland/Server/primary_selection.h>
+
+#include <functional>
+
+namespace KWin::Xwl
+{
+
+class primary_selection
+{
+public:
+    using srv_data_device = Wrapland::Server::PrimarySelectionDevice;
+    using clt_data_device = Wrapland::Client::PrimarySelectionDevice;
+    using srv_data_source = srv_data_device::source_t;
+    using clt_source_t = clt_data_device::source_t;
+
+    selection_data<srv_data_device, clt_data_device> data;
+    QMetaObject::Connection source_check_connection;
+
+    primary_selection(xcb_atom_t atom, srv_data_device* srv_dev, clt_data_device* clt_dev);
+
+    srv_data_device* get_current_device() const;
+    Wrapland::Client::PrimarySelectionDeviceManager* get_internal_device_manager() const;
+    std::function<void(srv_data_device*)> get_selection_setter() const;
+
+private:
+    Q_DISABLE_COPY(primary_selection)
+};
+
+}
diff --git a/xwl/selection.cpp b/xwl/selection.cpp
deleted file mode 100644
index 20b94e77141c06d00e550576142c67db8ed5e22a..0000000000000000000000000000000000000000
--- a/xwl/selection.cpp
+++ /dev/null
@@ -1,360 +0,0 @@
-/********************************************************************
- KWin - the KDE window manager
- This file is part of the KDE project.
-
-Copyright 2019 Roman Gilg <subdiff@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*********************************************************************/
-#include "selection.h"
-#include "databridge.h"
-#include "selection_source.h"
-#include "transfer.h"
-
-#include "atoms.h"
-#include "workspace.h"
-
-#include "win/x11/window.h"
-
-#include <xcb/xcb_event.h>
-#include <xcb/xfixes.h>
-
-#include <QTimer>
-
-namespace KWin
-{
-namespace Xwl
-{
-
-xcb_atom_t Selection::mimeTypeToAtom(const QString &mimeType)
-{
-    if (mimeType == QLatin1String("text/plain;charset=utf-8")) {
-        return atoms->utf8_string;
-    }
-    if (mimeType == QLatin1String("text/plain")) {
-        return atoms->text;
-    }
-    if (mimeType == QLatin1String("text/x-uri")) {
-        return atoms->uri_list;
-    }
-    return mimeTypeToAtomLiteral(mimeType);
-}
-
-xcb_atom_t Selection::mimeTypeToAtomLiteral(const QString &mimeType)
-{
-    return Xcb::Atom(mimeType.toLatin1(), false, kwinApp()->x11Connection());
-}
-
-QString Selection::atomName(xcb_atom_t atom)
-{
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-    xcb_get_atom_name_cookie_t nameCookie = xcb_get_atom_name(xcbConn, atom);
-    xcb_get_atom_name_reply_t *nameReply = xcb_get_atom_name_reply(xcbConn, nameCookie, nullptr);
-    if (!nameReply) {
-        return QString();
-    }
-
-    const size_t length = xcb_get_atom_name_name_length(nameReply);
-    QString name = QString::fromLatin1(xcb_get_atom_name_name(nameReply), length);
-    free(nameReply);
-    return name;
-}
-
-QStringList Selection::atomToMimeTypes(xcb_atom_t atom)
-{
-    QStringList mimeTypes;
-
-    if (atom == atoms->utf8_string) {
-        mimeTypes << QString::fromLatin1("text/plain;charset=utf-8");
-    } else if (atom == atoms->text) {
-        mimeTypes << QString::fromLatin1("text/plain");
-    } else if (atom == atoms->uri_list) {
-        mimeTypes << "text/uri-list" << "text/x-uri";
-    } else {
-        mimeTypes << atomName(atom);
-    }
-    return mimeTypes;
-}
-
-Selection::Selection(xcb_atom_t atom, QObject *parent)
-    : QObject(parent)
-    , m_atom(atom)
-{
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-    m_window = xcb_generate_id(kwinApp()->x11Connection());
-    m_requestorWindow = m_window;
-    xcb_flush(xcbConn);
-}
-
-bool Selection::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
-{
-    if (event->window != m_window) {
-        return false;
-    }
-    if (event->selection != m_atom) {
-        return false;
-    }
-    if (m_disownPending) {
-        // notify of our own disown - ignore it
-        m_disownPending = false;
-        return true;
-    }
-    if (event->owner == m_window && m_waylandSource) {
-        // When we claim a selection we must use XCB_TIME_CURRENT,
-        // grab the actual timestamp here to answer TIMESTAMP requests
-        // correctly
-        m_waylandSource->setTimestamp(event->timestamp);
-        m_timestamp = event->timestamp;
-        return true;
-    }
-
-    // Being here means some other X window has claimed the selection.
-    doHandleXfixesNotify(event);
-    return true;
-}
-
-bool Selection::filterEvent(xcb_generic_event_t *event)
-{
-    switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
-    case XCB_SELECTION_NOTIFY:
-        return handleSelectionNotify(reinterpret_cast<xcb_selection_notify_event_t *>(event));
-    case XCB_PROPERTY_NOTIFY:
-        return handlePropertyNotify(reinterpret_cast<xcb_property_notify_event_t *>(event));
-    case XCB_SELECTION_REQUEST:
-        return handleSelectionRequest(reinterpret_cast<xcb_selection_request_event_t *>(event));
-    case XCB_CLIENT_MESSAGE:
-        return handleClientMessage(reinterpret_cast<xcb_client_message_event_t *>(event));
-    default:
-        return false;
-    }
-}
-
-void Selection::sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
-{
-    xcb_selection_notify_event_t notify;
-    notify.response_type = XCB_SELECTION_NOTIFY;
-    notify.sequence = 0;
-    notify.time = event->time;
-    notify.requestor = event->requestor;
-    notify.selection = event->selection;
-    notify.target = event->target;
-    notify.property = success ? event->property : xcb_atom_t(XCB_ATOM_NONE);
-
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-    xcb_send_event(xcbConn,
-                   0,
-                   event->requestor,
-                   XCB_EVENT_MASK_NO_EVENT,
-                   (const char *)&notify);
-    xcb_flush(xcbConn);
-}
-
-void Selection::registerXfixes()
-{
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-    const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
-            XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
-            XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
-    xcb_xfixes_select_selection_input(kwinApp()->x11Connection(),
-                                      m_window,
-                                      m_atom,
-                                      mask);
-    xcb_flush(xcbConn);
-}
-
-void Selection::setWlSource(WlSource *source)
-{
-    delete m_waylandSource;
-    delete m_xSource;
-    m_waylandSource = nullptr;
-    m_xSource = nullptr;
-    if (source) {
-        m_waylandSource = source;
-        connect(source, &WlSource::transferReady, this, &Selection::startTransferToX);
-    }
-}
-
-void Selection::createX11Source(xcb_xfixes_selection_notify_event_t *event)
-{
-    delete m_waylandSource;
-    delete m_xSource;
-    m_waylandSource = nullptr;
-    m_xSource = nullptr;
-    if (!event || event->owner == XCB_WINDOW_NONE) {
-        return;
-    }
-    m_xSource = new X11Source(this, event);
-
-    connect(m_xSource, &X11Source::offersChanged, this, &Selection::x11OffersChanged);
-    connect(m_xSource, &X11Source::transferReady, this, &Selection::startTransferToWayland);
-}
-
-void Selection::ownSelection(bool own)
-{
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-    if (own) {
-        xcb_set_selection_owner(xcbConn,
-                                m_window,
-                                m_atom,
-                                XCB_TIME_CURRENT_TIME);
-    } else {
-        m_disownPending = true;
-        xcb_set_selection_owner(xcbConn,
-                                XCB_WINDOW_NONE,
-                                m_atom,
-                                m_timestamp);
-    }
-    xcb_flush(xcbConn);
-}
-
-void Selection::overwriteRequestorWindow(xcb_window_t window)
-{
-    Q_ASSERT(m_xSource);
-    if (window == XCB_WINDOW_NONE) {
-        // reset
-        window = m_window;
-    }
-    m_requestorWindow = window;
-    m_xSource->setRequestor(window);
-}
-
-bool Selection::handleSelectionRequest(xcb_selection_request_event_t *event)
-{
-    if (event->selection != m_atom) {
-        return false;
-    }
-
-    if (qobject_cast<win::x11::window*>(workspace()->activeClient()) == nullptr) {
-        // Receiving Wayland selection not allowed when no Xwayland surface active
-        // filter the event, but don't act upon it
-        sendSelectionNotify(event, false);
-        return true;
-    }
-
-    if (m_window != event->owner || !m_waylandSource) {
-        if (event->time < m_timestamp) {
-            // cancel earlier attempts at receiving a selection
-            // TODO: is this for sure without problems?
-            sendSelectionNotify(event, false);
-            return true;
-        }
-        return false;
-    }
-    return m_waylandSource->handleSelectionRequest(event);
-}
-
-bool Selection::handleSelectionNotify(xcb_selection_notify_event_t *event)
-{
-    if (m_xSource && m_xSource->handleSelectionNotify(event)) {
-        return true;
-    }
-    for (TransferXtoWl *transfer : m_xToWlTransfers) {
-        if (transfer->handleSelectionNotify(event)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-bool Selection::handlePropertyNotify(xcb_property_notify_event_t *event)
-{
-    for (TransferXtoWl *transfer : m_xToWlTransfers) {
-        if (transfer->handlePropertyNotify(event)) {
-            return true;
-        }
-    }
-    for (TransferWltoX *transfer : m_wlToXTransfers) {
-        if (transfer->handlePropertyNotify(event)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void Selection::startTransferToWayland(xcb_atom_t target, qint32 fd)
-{
-    // create new x to wl data transfer object
-    auto *transfer = new TransferXtoWl(m_atom, target, fd, m_xSource->timestamp(), m_requestorWindow, this);
-    m_xToWlTransfers << transfer;
-
-    connect(transfer, &TransferXtoWl::finished, this, [this, transfer]() {
-        Q_EMIT transferFinished(transfer->timestamp());
-        delete transfer;
-        m_xToWlTransfers.removeOne(transfer);
-        endTimeoutTransfersTimer();
-    });
-    startTimeoutTransfersTimer();
-}
-
-void Selection::startTransferToX(xcb_selection_request_event_t *event, qint32 fd)
-{
-    // create new wl to x data transfer object
-    auto *transfer = new TransferWltoX(m_atom, event, fd, this);
-
-    connect(transfer, &TransferWltoX::selectionNotify, this, &Selection::sendSelectionNotify);
-    connect(transfer, &TransferWltoX::finished, this, [this, transfer]() {
-        Q_EMIT transferFinished(transfer->timestamp());
-
-        // TODO: serialize? see comment below.
-//        const bool wasActive = (transfer == m_wlToXTransfers[0]);
-        delete transfer;
-        m_wlToXTransfers.removeOne(transfer);
-        endTimeoutTransfersTimer();
-//        if (wasActive && !m_wlToXTransfers.isEmpty()) {
-//            m_wlToXTransfers[0]->startTransferFromSource();
-//        }
-    });
-
-    // add it to list of queued transfers
-    m_wlToXTransfers.append(transfer);
-
-    // TODO: Do we need to serialize the transfers, or can we do
-    //       them in parallel as we do it right now?
-    transfer->startTransferFromSource();
-//    if (m_wlToXTransfers.size() == 1) {
-//        transfer->startTransferFromSource();
-//    }
-    startTimeoutTransfersTimer();
-}
-
-void Selection::startTimeoutTransfersTimer()
-{
-    if (m_timeoutTransfers) {
-        return;
-    }
-    m_timeoutTransfers = new QTimer(this);
-    connect(m_timeoutTransfers, &QTimer::timeout, this, &Selection::timeoutTransfers);
-    m_timeoutTransfers->start(5000);
-}
-
-void Selection::endTimeoutTransfersTimer()
-{
-    if (m_xToWlTransfers.isEmpty() && m_wlToXTransfers.isEmpty()) {
-        delete m_timeoutTransfers;
-        m_timeoutTransfers = nullptr;
-    }
-}
-
-void Selection::timeoutTransfers()
-{
-    for (TransferXtoWl *transfer : m_xToWlTransfers) {
-        transfer->timeout();
-    }
-    for (TransferWltoX *transfer : m_wlToXTransfers) {
-        transfer->timeout();
-    }
-}
-
-} // namespace Xwl
-} // namespace KWin
diff --git a/xwl/selection.h b/xwl/selection.h
index f3916798325ed247545f78668cd8fa959d415e61..f91ac63294610393cb2e41055274b7209db13574 100644
--- a/xwl/selection.h
+++ b/xwl/selection.h
@@ -1,144 +1,651 @@
-/********************************************************************
- KWin - the KDE window manager
- This file is part of the KDE project.
+/*
+    SPDX-FileCopyrightText: 2019-2021 Roman Gilg <subdiff@gmail.com>
+    SPDX-FileCopyrightText: 2021 Francesco Sorrentino <francesco.sorr@gmail.com>
 
-Copyright 2019 Roman Gilg <subdiff@gmail.com>
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+#pragma once
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+#include "selection_source.h"
+#include "transfer.h"
+#include "xwayland.h"
 
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*********************************************************************/
-#ifndef KWIN_XWL_SELECTION
-#define KWIN_XWL_SELECTION
+#include "atoms.h"
+#include "wayland_server.h"
+#include "win/x11/window.h"
+#include "workspace.h"
 
 #include <QObject>
+#include <QString>
+#include <QStringList>
+#include <QTimer>
 #include <QVector>
 
 #include <xcb/xcb.h>
+#include <xcb/xcb_event.h>
+#include <xcb/xfixes.h>
+#include <xcbutils.h>
 
-struct xcb_xfixes_selection_notify_event_t;
+#include <Wrapland/Client/connection_thread.h>
 
-class QTimer;
+#include <memory>
 
-namespace KWin
-{
-namespace Xwl
+namespace KWin::Xwl
 {
 class TransferWltoX;
 class TransferXtoWl;
+template<typename, typename>
 class WlSource;
+template<typename>
 class X11Source;
 
+/*
+ * QObject attribute of a Selection.
+ * This is a hack around having a template QObject.
+ */
+class q_selection : public QObject
+{
+    Q_OBJECT
+
+public:
+Q_SIGNALS:
+    void transferFinished(xcb_timestamp_t eventTime);
+};
+
 /**
- * Base class representing generic X selections and their respective
- * Wayland counter-parts.
- *
- * The class needs to be subclassed and adjusted according to the
- * selection, but provides common fucntionality to be expected of all
- * selections.
+ * Data needed by X selections and their Wayland counter-parts.
  *
  * A selection should exist through the whole runtime of an Xwayland
  * session.
+ * Each selection holds an independent instance of this class,
+ * containing the source and the active transfers.
  *
- * Independently of each other the class holds the currently active
- * source instance and active transfers relative to the represented
- * selection.
+ * This class can be specialized to support the core Wayland protocol
+ * (clipboard and dnd) as well as primary selection.
  */
-class Selection : public QObject
+template<typename server_device, typename client_device>
+struct selection_data {
+    using srv_data_source = typename server_device::source_t;
+    using clt_data_source = typename client_device::source_t;
+
+    std::unique_ptr<q_selection> qobject;
+    server_device* srv_device{nullptr};
+    client_device* clt_device{nullptr};
+
+    xcb_atom_t atom{XCB_ATOM_NONE};
+    xcb_window_t window{XCB_WINDOW_NONE};
+
+    bool disown_pending{false};
+    xcb_timestamp_t timestamp;
+    xcb_window_t requestor_window{XCB_WINDOW_NONE};
+
+    // Active source, if any. Only one of them at max can exist
+    // at the same time.
+    WlSource<server_device, srv_data_source>* wayland_source{nullptr};
+    X11Source<clt_data_source>* x11_source{nullptr};
+
+    // active transfers
+    struct {
+        QVector<TransferWltoX*> wl_to_x11;
+        QVector<TransferXtoWl*> x11_to_wl;
+        QTimer* timeout{nullptr};
+    } transfers;
+
+    selection_data() = default;
+    selection_data(selection_data const&) = delete;
+    selection_data& operator=(selection_data const&) = delete;
+    selection_data(selection_data&&) noexcept = default;
+    selection_data& operator=(selection_data&&) noexcept = default;
+
+    ~selection_data()
+    {
+        delete wayland_source;
+        delete x11_source;
+        wayland_source = nullptr;
+        x11_source = nullptr;
+        clt_device = nullptr;
+        srv_device = nullptr;
+    }
+};
+
+template<typename srv_data_source, typename client_data_device>
+auto create_selection_data(xcb_atom_t atom, srv_data_source* sdev, client_data_device* cdev)
 {
-    Q_OBJECT
+    selection_data<srv_data_source, client_data_device> sel;
 
-public:
-    static xcb_atom_t mimeTypeToAtom(const QString &mimeType);
-    static xcb_atom_t mimeTypeToAtomLiteral(const QString &mimeType);
-    static QStringList atomToMimeTypes(xcb_atom_t atom);
-    static QString atomName(xcb_atom_t atom);
-    static void sendSelectionNotify(xcb_selection_request_event_t *event, bool success);
+    sel.qobject.reset(new q_selection());
+    sel.atom = atom;
+    sel.srv_device = sdev;
+    sel.clt_device = cdev;
 
-    // on selection owner changes by X clients (Xwl -> Wl)
-    bool handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event);
-    bool filterEvent(xcb_generic_event_t *event);
+    auto xcb_con = kwinApp()->x11Connection();
+    sel.window = xcb_generate_id(kwinApp()->x11Connection());
+    sel.requestor_window = sel.window;
+    xcb_flush(xcb_con);
 
-    xcb_atom_t atom() const {
-        return m_atom;
+    return sel;
+}
+
+inline void sendSelectionNotify(xcb_selection_request_event_t* event, bool success)
+{
+    xcb_selection_notify_event_t notify;
+    notify.response_type = XCB_SELECTION_NOTIFY;
+    notify.sequence = 0;
+    notify.time = event->time;
+    notify.requestor = event->requestor;
+    notify.selection = event->selection;
+    notify.target = event->target;
+    notify.property = success ? event->property : xcb_atom_t(XCB_ATOM_NONE);
+
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
+    xcb_send_event(xcbConn, 0, event->requestor, XCB_EVENT_MASK_NO_EVENT, (const char*)&notify);
+    xcb_flush(xcbConn);
+}
+
+template<typename Selection>
+void register_xfixes(Selection* sel)
+{
+    auto xcb_conn = kwinApp()->x11Connection();
+    const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER
+        | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY
+        | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
+    xcb_xfixes_select_selection_input(
+        kwinApp()->x11Connection(), sel->data.window, sel->data.atom, mask);
+    xcb_flush(xcb_conn);
+}
+
+// on selection owner changes by X clients (Xwl -> Wl)
+template<typename Selection>
+bool handle_xfixes_notify(Selection* sel, xcb_xfixes_selection_notify_event_t* event)
+{
+    if (!sel) {
+        return false;
     }
-    xcb_window_t window() const {
-        return m_window;
+    if (event->window != sel->data.window) {
+        return false;
+    }
+    if (event->selection != sel->data.atom) {
+        return false;
+    }
+    if (sel->data.disown_pending) {
+        // notify of our own disown - ignore it
+        sel->data.disown_pending = false;
+        return true;
+    }
+    if (event->owner == sel->data.window && sel->data.wayland_source) {
+        // When we claim a selection we must use XCB_TIME_CURRENT,
+        // grab the actual timestamp here to answer TIMESTAMP requests
+        // correctly
+        sel->data.wayland_source->setTimestamp(event->timestamp);
+        sel->data.timestamp = event->timestamp;
+        return true;
     }
-    void overwriteRequestorWindow(xcb_window_t window);
 
-Q_SIGNALS:
-    void transferFinished(xcb_timestamp_t eventTime);
+    // Being here means some other X window has claimed the selection.
+    do_handle_xfixes_notify(sel, event);
+    return true;
+}
+
+template<typename Selection>
+void do_handle_xfixes_notify(Selection* sel, xcb_xfixes_selection_notify_event_t* event)
+{
+    create_x11_source(sel, nullptr);
+
+    auto const& client = workspace()->activeClient();
+    if (!qobject_cast<win::x11::window const*>(client)) {
+        // clipboard is only allowed to be acquired when Xwayland has focus
+        // TODO: can we make this stronger (window id comparison)?
+        return;
+    }
 
-protected:
-    Selection(xcb_atom_t atom, QObject *parent);
-    void registerXfixes();
+    create_x11_source(sel, event);
 
-    virtual void doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) = 0;
-    virtual void x11OffersChanged(const QStringList &added, const QStringList &removed) = 0;
+    if (auto const& source = sel->data.x11_source) {
+        source->getTargets(sel->data.requestor_window, sel->data.atom);
+    }
+}
 
-    virtual bool handleClientMessage(xcb_client_message_event_t *event) {
-        Q_UNUSED(event);
+template<typename Selection>
+bool handle_client_message([[maybe_unused]] Selection* sel,
+                           [[maybe_unused]] xcb_client_message_event_t* event)
+{
+    return false;
+}
+
+template<typename Selection>
+bool filter_event(Selection* sel, xcb_generic_event_t* event)
+{
+    switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
+    case XCB_SELECTION_NOTIFY:
+        return handle_selection_notify(sel, reinterpret_cast<xcb_selection_notify_event_t*>(event));
+    case XCB_PROPERTY_NOTIFY:
+        return handle_property_notify(sel, reinterpret_cast<xcb_property_notify_event_t*>(event));
+    case XCB_SELECTION_REQUEST:
+        return handle_selection_request(sel,
+                                        reinterpret_cast<xcb_selection_request_event_t*>(event));
+    case XCB_CLIENT_MESSAGE:
+        return handle_client_message(sel, reinterpret_cast<xcb_client_message_event_t*>(event));
+    default:
         return false;
     }
-    // sets the current provider of the selection
-    void setWlSource(WlSource *source);
-    WlSource *wlSource() const {
-        return m_waylandSource;
+}
+
+template<typename Selection>
+bool handle_selection_request(Selection* sel, xcb_selection_request_event_t* event)
+{
+    if (event->selection != sel->data.atom) {
+        return false;
     }
-    void createX11Source(xcb_xfixes_selection_notify_event_t *event);
-    X11Source *x11Source() const {
-        return m_xSource;
+
+    if (qobject_cast<win::x11::window*>(workspace()->activeClient()) == nullptr) {
+        // Receiving Wayland selection not allowed when no Xwayland surface active
+        // filter the event, but don't act upon it
+        sendSelectionNotify(event, false);
+        return true;
     }
-    // must be called in order to provide data from Wl to X
-    void ownSelection(bool own);
-    void setWindow(xcb_window_t window) {
-        m_window = window;
+
+    if (sel->data.window != event->owner || !sel->data.wayland_source) {
+        if (event->time < sel->data.timestamp) {
+            // cancel earlier attempts at receiving a selection
+            // TODO: is this for sure without problems?
+            sendSelectionNotify(event, false);
+            return true;
+        }
+        return false;
     }
+    return sel->data.wayland_source->handleSelectionRequest(event);
+}
 
-private:
-    bool handleSelectionRequest(xcb_selection_request_event_t *event);
-    bool handleSelectionNotify(xcb_selection_notify_event_t *event);
-    bool handlePropertyNotify(xcb_property_notify_event_t *event);
+template<typename Selection>
+bool handle_selection_notify(Selection* sel, xcb_selection_notify_event_t* event)
+{
+    if (sel->data.x11_source && event->requestor == sel->data.requestor_window
+        && event->selection == sel->data.atom) {
+        if (sel->data.x11_source->handleSelectionNotify(event)) {
+            return true;
+        }
+    }
+    for (auto& transfer : sel->data.transfers.x11_to_wl) {
+        if (transfer->handleSelectionNotify(event)) {
+            return true;
+        }
+    }
+    return false;
+}
 
-    void startTransferToWayland(xcb_atom_t target, qint32 fd);
-    void startTransferToX(xcb_selection_request_event_t *event, qint32 fd);
+template<typename Selection>
+bool handle_property_notify(Selection* sel, xcb_property_notify_event_t* event)
+{
+    for (auto& transfer : sel->data.transfers.x11_to_wl) {
+        if (transfer->handlePropertyNotify(event)) {
+            return true;
+        }
+    }
+    for (auto& transfer : sel->data.transfers.wl_to_x11) {
+        if (transfer->handlePropertyNotify(event)) {
+            return true;
+        }
+    }
+    return false;
+}
 
-    // Timeout transfers, which have become inactive due to client errors.
-    void timeoutTransfers();
-    void startTimeoutTransfersTimer();
-    void endTimeoutTransfersTimer();
+// must be called in order to provide data from Wl to X
+template<typename Selection>
+void own_selection(Selection* sel, bool own)
+{
+    auto xcb_conn = kwinApp()->x11Connection();
+    if (own) {
+        xcb_set_selection_owner(xcb_conn, sel->data.window, sel->data.atom, XCB_TIME_CURRENT_TIME);
+    } else {
+        sel->data.disown_pending = true;
+        xcb_set_selection_owner(xcb_conn, XCB_WINDOW_NONE, sel->data.atom, sel->data.timestamp);
+    }
+    xcb_flush(xcb_conn);
+}
 
-    xcb_atom_t m_atom = XCB_ATOM_NONE;
-    xcb_window_t m_window = XCB_WINDOW_NONE;
-    xcb_window_t m_requestorWindow = XCB_WINDOW_NONE;
-    xcb_timestamp_t m_timestamp;
+template<typename Selection>
+void overwrite_requestor_window(Selection* sel, xcb_window_t window)
+{
+    assert(sel->data.x11_source);
+    sel->data.requestor_window = window == XCB_WINDOW_NONE ? sel->data.window : window;
+}
 
-    // Active source, if any. Only one of them at max can exist
-    // at the same time.
-    WlSource *m_waylandSource = nullptr;
-    X11Source *m_xSource = nullptr;
+// sets the current provider of the selection
+template<typename Selection, typename srv_data_device, typename srv_data_source>
+void set_wl_source(Selection* sel, WlSource<srv_data_device, srv_data_source>* source)
+{
+    delete sel->data.wayland_source;
+    delete sel->data.x11_source;
+    sel->data.wayland_source = nullptr;
+    sel->data.x11_source = nullptr;
+    if (source) {
+        sel->data.wayland_source = source;
+        QObject::connect(source->qobject(),
+                         &qWlSource::transferReady,
+                         sel->data.qobject.get(),
+                         [sel](auto event, auto fd) { start_transfer_to_x11(sel, event, fd); });
+    }
+}
 
-    // active transfers
-    QVector<TransferWltoX *> m_wlToXTransfers;
-    QVector<TransferXtoWl *> m_xToWlTransfers;
-    QTimer *m_timeoutTransfers = nullptr;
+template<typename Selection>
+void create_x11_source(Selection* sel, xcb_xfixes_selection_notify_event_t* event)
+{
+    delete sel->data.wayland_source;
+    delete sel->data.x11_source;
+    sel->data.wayland_source = nullptr;
+    sel->data.x11_source = nullptr;
+    if (!event || event->owner == XCB_WINDOW_NONE) {
+        return;
+    }
 
-    bool m_disownPending = false;
+    using clt_data_source = typename decltype(sel->data)::clt_data_source;
+    sel->data.x11_source = new X11Source<clt_data_source>(event);
 
-    Q_DISABLE_COPY(Selection)
-};
+    QObject::connect(sel->data.x11_source->qobject(),
+                     &qX11Source::offersChanged,
+                     sel->data.qobject.get(),
+                     [sel](auto const& added, auto const& removed) {
+                         handle_x11_offer_change(sel, added, removed);
+                     });
+    QObject::connect(sel->data.x11_source->qobject(),
+                     &qX11Source::transferReady,
+                     sel->data.qobject.get(),
+                     [sel](auto target, auto fd) { start_transfer_to_wayland(sel, target, fd); });
+}
+
+template<typename Selection>
+void start_transfer_to_wayland(Selection* sel, xcb_atom_t target, qint32 fd)
+{
+    // create new x to wl data transfer object
+    auto transfer = new TransferXtoWl(sel->data.atom,
+                                      target,
+                                      fd,
+                                      sel->data.x11_source->timestamp(),
+                                      sel->data.requestor_window,
+                                      sel->data.qobject.get());
+    sel->data.transfers.x11_to_wl << transfer;
+
+    QObject::connect(
+        transfer, &TransferXtoWl::finished, sel->data.qobject.get(), [sel, transfer]() {
+            Q_EMIT sel->data.qobject->transferFinished(transfer->timestamp());
+            delete transfer;
+            sel->data.transfers.x11_to_wl.removeOne(transfer);
+            end_timeout_transfers_timer(sel);
+        });
+    start_timeout_transfers_timer(sel);
+}
+
+template<typename Selection>
+void start_transfer_to_x11(Selection* sel, xcb_selection_request_event_t* event, qint32 fd)
+{
+    // create new wl to x data transfer object
+    auto transfer = new TransferWltoX(sel->data.atom, event, fd, sel->data.qobject.get());
+
+    QObject::connect(
+        transfer, &TransferWltoX::selectionNotify, sel->data.qobject.get(), &sendSelectionNotify);
+    QObject::connect(
+        transfer, &TransferWltoX::finished, sel->data.qobject.get(), [sel, transfer]() {
+            Q_EMIT sel->data.qobject->transferFinished(transfer->timestamp());
+
+            // TODO: serialize? see comment below.
+            //        const bool wasActive = (transfer == m_wlToXTransfers[0]);
+            delete transfer;
+            sel->data.transfers.wl_to_x11.removeOne(transfer);
+            end_timeout_transfers_timer(sel);
+            //        if (wasActive && !m_wlToXTransfers.isEmpty()) {
+            //            m_wlToXTransfers[0]->startTransferFromSource();
+            //        }
+        });
+
+    // add it to list of queued transfers
+    sel->data.transfers.wl_to_x11.append(transfer);
+
+    // TODO: Do we need to serialize the transfers, or can we do
+    //       them in parallel as we do it right now?
+    transfer->startTransferFromSource();
+    //    if (m_wlToXTransfers.size() == 1) {
+    //        transfer->startTransferFromSource();
+    //    }
+    start_timeout_transfers_timer(sel);
+}
+
+// Timeout transfers, which have become inactive due to client errors.
+template<typename Selection>
+void timeout_transfers(Selection* sel)
+{
+    for (auto& transfer : sel->data.transfers.x11_to_wl) {
+        transfer->timeout();
+    }
+    for (auto& transfer : sel->data.transfers.wl_to_x11) {
+        transfer->timeout();
+    }
+}
+
+template<typename Selection>
+void start_timeout_transfers_timer(Selection* sel)
+{
+    if (sel->data.transfers.timeout) {
+        return;
+    }
+    sel->data.transfers.timeout = new QTimer(sel->data.qobject.get());
+    QObject::connect(sel->data.transfers.timeout,
+                     &QTimer::timeout,
+                     sel->data.qobject.get(),
+                     [sel]() { timeout_transfers(sel); });
+    sel->data.transfers.timeout->start(5000);
+}
+
+template<typename Selection>
+void end_timeout_transfers_timer(Selection* sel)
+{
+    if (sel->data.transfers.x11_to_wl.isEmpty() && sel->data.transfers.wl_to_x11.isEmpty()) {
+        delete sel->data.transfers.timeout;
+        sel->data.transfers.timeout = nullptr;
+    }
+}
+
+inline xcb_atom_t mimeTypeToAtomLiteral(const QString& mimeType)
+{
+    return Xcb::Atom(mimeType.toLatin1(), false, kwinApp()->x11Connection());
+}
+
+inline xcb_atom_t mimeTypeToAtom(const QString& mimeType)
+{
+    if (mimeType == QLatin1String("text/plain;charset=utf-8")) {
+        return atoms->utf8_string;
+    }
+    if (mimeType == QLatin1String("text/plain")) {
+        return atoms->text;
+    }
+    if (mimeType == QLatin1String("text/x-uri")) {
+        return atoms->uri_list;
+    }
+    return mimeTypeToAtomLiteral(mimeType);
+}
+
+inline QString atomName(xcb_atom_t atom)
+{
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
+    xcb_get_atom_name_cookie_t nameCookie = xcb_get_atom_name(xcbConn, atom);
+    xcb_get_atom_name_reply_t* nameReply = xcb_get_atom_name_reply(xcbConn, nameCookie, nullptr);
+    if (!nameReply) {
+        return QString();
+    }
+
+    const size_t length = xcb_get_atom_name_name_length(nameReply);
+    QString name = QString::fromLatin1(xcb_get_atom_name_name(nameReply), length);
+    free(nameReply);
+    return name;
+}
+
+inline QStringList atomToMimeTypes(xcb_atom_t atom)
+{
+    QStringList mimeTypes;
+
+    if (atom == atoms->utf8_string) {
+        mimeTypes << QString::fromLatin1("text/plain;charset=utf-8");
+    } else if (atom == atoms->text) {
+        mimeTypes << QString::fromLatin1("text/plain");
+    } else if (atom == atoms->uri_list || atom == atoms->netscape_url || atom == atoms->moz_url) {
+        // We identify netscape and moz format as less detailed formats text/uri-list,
+        // text/x-uri and accept the information loss.
+        mimeTypes << QString::fromLatin1("text/uri-list") << QString::fromLatin1("text/x-uri");
+    } else {
+        mimeTypes << atomName(atom);
+    }
+    return mimeTypes;
+}
+
+template<typename Selection>
+void register_x11_selection(Selection* sel, QSize const& window_size)
+{
+    auto xcbConn = kwinApp()->x11Connection();
+
+    uint32_t const values[] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE};
+    xcb_create_window(xcbConn,
+                      XCB_COPY_FROM_PARENT,
+                      sel->data.window,
+                      kwinApp()->x11RootWindow(),
+                      0,
+                      0,
+                      window_size.width(),
+                      window_size.height(),
+                      0,
+                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
+                      Xwayland::self()->xcbScreen()->root_visual,
+                      XCB_CW_EVENT_MASK,
+                      values);
+    register_xfixes(sel);
+    xcb_flush(xcbConn);
+}
+
+/**
+ * Check the current state of the selection and if a source needs to be created or destroyed.
+ */
+template<typename Selection>
+void check_wl_source(Selection* sel)
+{
+    using srv_data_device = typename Selection::srv_data_device;
+    using srv_data_source = typename srv_data_device::source_t;
+
+    auto remove_source = [sel] {
+        if (sel->data.wayland_source) {
+            set_wl_source<Selection, srv_data_device, srv_data_source>(sel, nullptr);
+            own_selection(sel, false);
+        }
+    };
+
+    auto srv_dev = sel->get_current_device();
+    static_assert(std::is_same_v<srv_data_device, std::remove_pointer_t<decltype(srv_dev)>>,
+                  "get current device type mismatch");
+
+    // Wayland source gets created when:
+    // - the Wl selection exists,
+    // - its source is not Xwayland,
+    // - a client is active,
+    // - this client is an Xwayland one.
+    //
+    // Otherwise the Wayland source gets destroyed to shield against snooping X clients.
+
+    if (!srv_dev || sel->data.srv_device == srv_dev) {
+        // That means there is either no source or it's an Xwayland source.
+        QObject::disconnect(sel->source_check_connection);
+        sel->source_check_connection = QMetaObject::Connection();
+        remove_source();
+        return;
+    }
+
+    if (!workspace()->activeClient()
+        || !workspace()->activeClient()->inherits("KWin::win::x11::window")) {
+        // No active client or active client is Wayland native.
+        remove_source();
+        return;
+    }
+
+    // Xwayland client is active and we need a Wayland source
+    if (sel->data.wayland_source) {
+        // source already exists, nothing more to do
+        return;
+    }
+
+    auto wls = new WlSource<srv_data_device, srv_data_source>(srv_dev);
+    set_wl_source(sel, wls);
+
+    auto srv_src = srv_dev->selection();
+    if (srv_src) {
+        wls->setSourceIface(srv_src);
+    }
+    QObject::connect(srv_dev,
+                     &srv_data_device::selectionChanged,
+                     wls->qobject(),
+                     [wls](auto srv_src) { wls->setSourceIface(srv_src); });
+    own_selection(sel, true);
+}
+
+/**
+ * React to Wl selection change.
+ */
+template<typename Selection>
+void handle_wl_selection_change(Selection* sel)
+{
+    using srv_data_device = typename Selection::srv_data_device;
+    using srv_data_source = typename srv_data_device::source_t;
+
+    auto srv_dev = sel->get_current_device();
+    static_assert(std::is_same_v<srv_data_device, std::remove_pointer_t<decltype(srv_dev)>>,
+                  "get current device type mismatch");
+
+    if (srv_dev && srv_dev != sel->data.srv_device) {
+        // Wayland native client provides new selection.
+        if (!sel->source_check_connection) {
+            sel->source_check_connection = QObject::connect(workspace(),
+                                                            &Workspace::clientActivated,
+                                                            sel->data.qobject.get(),
+                                                            [sel] { check_wl_source(sel); });
+        }
+        // Remove previous source so checkWlSource() can create a new one.
+        set_wl_source<Selection, srv_data_device, srv_data_source>(sel, nullptr);
+    }
+    check_wl_source(sel);
+}
+
+template<typename Selection>
+void handle_x11_offer_change(Selection* sel, QStringList const& added, QStringList const& removed)
+{
+    auto source = sel->data.x11_source;
+    if (!source) {
+        return;
+    }
+
+    auto flush_and_dispatch = [] {
+        waylandServer()->internalClientConection()->flush();
+        waylandServer()->dispatch();
+    };
+
+    const Mimes offers = source->offers();
+    if (offers.isEmpty()) {
+        sel->get_selection_setter()(nullptr);
+        flush_and_dispatch();
+        return;
+    }
+
+    if (!source->source() || !removed.isEmpty()) {
+        // create new Wl DataSource if there is none or when types
+        // were removed (Wl Data Sources can only add types)
+        auto dataDeviceManager = sel->get_internal_device_manager();
+        auto dataSource = dataDeviceManager->createSource(source->qobject());
+
+        // also offers directly the currently available types
+        source->setSource(dataSource);
+        sel->data.clt_device->setSelection(0, dataSource);
+        sel->get_selection_setter()(sel->data.srv_device);
+    } else if (auto dataSource = source->source()) {
+        for (const QString& mime : added) {
+            dataSource->offer(mime);
+        }
+    }
 
-} // namespace Xwl
-} // namespace KWin
+    flush_and_dispatch();
+}
 
-#endif
+}
diff --git a/xwl/selection_source.cpp b/xwl/selection_source.cpp
index cf524fb11f26377389abb8a3a0bfbfc5119ffbd8..66c19f7b8e0e90a08edc8fc4537add324e00655c 100644
--- a/xwl/selection_source.cpp
+++ b/xwl/selection_source.cpp
@@ -18,70 +18,66 @@ You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *********************************************************************/
 #include "selection_source.h"
+
 #include "selection.h"
 #include "transfer.h"
 
 #include "atoms.h"
 #include "wayland_server.h"
 
-#include <Wrapland/Client/connection_thread.h>
-#include <Wrapland/Client/datadevicemanager.h>
 #include <Wrapland/Client/datadevice.h>
 #include <Wrapland/Client/datasource.h>
+#include <Wrapland/Client/primary_selection.h>
 
 #include <Wrapland/Server/data_device.h>
 #include <Wrapland/Server/data_source.h>
-#include <Wrapland/Server/seat.h>
+#include <Wrapland/Server/primary_selection.h>
 
-#include <unistd.h>
 #include <string>
+#include <unistd.h>
 
 #include <xwayland_logging.h>
 
-namespace KWin
-{
-namespace Xwl
+namespace KWin::Xwl
 {
 
-SelectionSource::SelectionSource(Selection *selection)
-    : QObject(selection)
-    , m_selection(selection)
-    , m_window(selection->window())
+template<typename DeviceIface, typename SourceIface>
+WlSource<DeviceIface, SourceIface>::WlSource(DeviceIface* di)
+    : m_di(di)
+    , m_qobject(new qWlSource)
 {
+    Q_ASSERT(di);
 }
 
-WlSource::WlSource(Selection *selection, Wrapland::Server::DataDevice *ddi)
-    : SelectionSource(selection)
-    , m_ddi(ddi)
+template<typename DeviceIface, typename SourceIface>
+WlSource<DeviceIface, SourceIface>::~WlSource()
 {
-    Q_ASSERT(ddi);
+    delete m_qobject;
 }
 
-void WlSource::setDataSourceIface(Wrapland::Server::DataSource *dsi)
+template<typename DeviceIface, typename SourceIface>
+void WlSource<DeviceIface, SourceIface>::setSourceIface(SourceIface* si)
 {
-    if (m_dsi == dsi) {
+    if (m_si == si) {
         return;
     }
-    for (const auto &mime : dsi->mimeTypes()) {
+    for (const auto& mime : si->mimeTypes()) {
         m_offers << QString::fromStdString(mime);
     }
-    m_offerConnection = connect(dsi,
-                         &Wrapland::Server::DataSource::mimeTypeOffered,
-                         this, &WlSource::receiveOffer);
-    m_dsi = dsi;
+    m_offerConnection = QObject::connect(
+        si, &SourceIface::mimeTypeOffered, qobject(), [this](auto mime) { receiveOffer(mime); });
+    m_si = si;
 }
 
-void WlSource::receiveOffer(std::string const &mime)
+template<typename DeviceIface, typename SourceIface>
+void WlSource<DeviceIface, SourceIface>::receiveOffer(std::string const& mime)
 {
     m_offers << QString::fromStdString(mime);
 }
 
-void WlSource::sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
-{
-    Selection::sendSelectionNotify(event, success);
-}
-
-bool WlSource::handleSelectionRequest(xcb_selection_request_event_t *event)
+template<typename DeviceIface, typename SourceIface>
+bool WlSource<DeviceIface, SourceIface>::handleSelectionRequest(
+    xcb_selection_request_event_t* event)
 {
     if (event->target == atoms->targets) {
         sendTargets(event);
@@ -98,7 +94,8 @@ bool WlSource::handleSelectionRequest(xcb_selection_request_event_t *event)
     return true;
 }
 
-void WlSource::sendTargets(xcb_selection_request_event_t *event)
+template<typename DeviceIface, typename SourceIface>
+void WlSource<DeviceIface, SourceIface>::sendTargets(xcb_selection_request_event_t* event)
 {
     QVector<xcb_atom_t> targets;
     targets.resize(m_offers.size() + 2);
@@ -106,8 +103,8 @@ void WlSource::sendTargets(xcb_selection_request_event_t *event)
     targets[1] = atoms->targets;
 
     size_t cnt = 2;
-    for (const auto mime : m_offers) {
-        targets[cnt] = Selection::mimeTypeToAtom(mime);
+    for (const auto& mime : m_offers) {
+        targets[cnt] = mimeTypeToAtom(mime);
         cnt++;
     }
 
@@ -116,11 +113,14 @@ void WlSource::sendTargets(xcb_selection_request_event_t *event)
                         event->requestor,
                         event->property,
                         XCB_ATOM_ATOM,
-                        32, cnt, targets.data());
+                        32,
+                        cnt,
+                        targets.data());
     sendSelectionNotify(event, true);
 }
 
-void WlSource::sendTimestamp(xcb_selection_request_event_t *event)
+template<typename DeviceIface, typename SourceIface>
+void WlSource<DeviceIface, SourceIface>::sendTimestamp(xcb_selection_request_event_t* event)
 {
     const xcb_timestamp_t time = timestamp();
     xcb_change_property(kwinApp()->x11Connection(),
@@ -128,26 +128,29 @@ void WlSource::sendTimestamp(xcb_selection_request_event_t *event)
                         event->requestor,
                         event->property,
                         XCB_ATOM_INTEGER,
-                        32, 1, &time);
+                        32,
+                        1,
+                        &time);
 
     sendSelectionNotify(event, true);
 }
 
-bool WlSource::checkStartTransfer(xcb_selection_request_event_t *event)
+template<typename DeviceIface, typename SourceIface>
+bool WlSource<DeviceIface, SourceIface>::checkStartTransfer(xcb_selection_request_event_t* event)
 {
     // check interfaces available
-    if (!m_ddi || !m_dsi) {
+    if (!m_di || !m_si) {
         return false;
     }
 
-    const auto targets = Selection::atomToMimeTypes(event->target);
+    const auto targets = atomToMimeTypes(event->target);
     if (targets.isEmpty()) {
         qCDebug(KWIN_XWL) << "Unknown selection atom. Ignoring request.";
         return false;
     }
     const std::string firstTarget = targets[0].toUtf8().constData();
 
-    auto cmp = [firstTarget](std::string const &b) {
+    auto cmp = [firstTarget](std::string const& b) {
         if (firstTarget == "text/uri-list") {
             // Wayland sources might announce the old mime or the new standard
             return firstTarget == b || b == "text/x-uri";
@@ -155,7 +158,7 @@ bool WlSource::checkStartTransfer(xcb_selection_request_event_t *event)
         return firstTarget == b;
     };
     // check supported mimes
-    const auto offers = m_dsi->mimeTypes();
+    const auto offers = m_si->mimeTypes();
     const auto mimeIt = std::find_if(offers.begin(), offers.end(), cmp);
     if (mimeIt == offers.end()) {
         // Requested Mime not supported. Not sending selection.
@@ -168,48 +171,46 @@ bool WlSource::checkStartTransfer(xcb_selection_request_event_t *event)
         return false;
     }
 
-    m_dsi->requestData(*mimeIt, p[1]);
+    m_si->requestData(*mimeIt, p[1]);
     waylandServer()->dispatch();
 
-    Q_EMIT transferReady(new xcb_selection_request_event_t(*event), p[0]);
+    Q_EMIT qobject()->transferReady(new xcb_selection_request_event_t(*event), p[0]);
     return true;
 }
 
-X11Source::X11Source(Selection *selection, xcb_xfixes_selection_notify_event_t *event)
-    : SelectionSource(selection)
-    , m_owner(event->owner)
+template<typename DataSource>
+X11Source<DataSource>::X11Source(xcb_xfixes_selection_notify_event_t* event)
+    : m_owner(event->owner)
+    , m_timestamp(event->timestamp)
+    , m_qobject(new qX11Source)
 {
-    setTimestamp(event->timestamp);
 }
 
-void X11Source::getTargets()
+template<typename DataSource>
+X11Source<DataSource>::~X11Source()
 {
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
+    delete m_qobject;
+}
+
+template<typename DataSource>
+void X11Source<DataSource>::getTargets(xcb_window_t const window, xcb_atom_t const atom) const
+{
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
     /* will lead to a selection request event for the new owner */
-    xcb_convert_selection(xcbConn,
-                          window(),
-                          selection()->atom(),
-                          atoms->targets,
-                          atoms->wl_selection,
-                          timestamp());
+    xcb_convert_selection(xcbConn, window, atom, atoms->targets, atoms->wl_selection, timestamp());
     xcb_flush(xcbConn);
 }
 
 using Mime = QPair<QString, xcb_atom_t>;
 
-void X11Source::handleTargets()
+template<typename DataSource>
+void X11Source<DataSource>::handleTargets(xcb_window_t const requestor)
 {
     // receive targets
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-    xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn,
-                                                        1,
-                                                        window(),
-                                                        atoms->wl_selection,
-                                                        XCB_GET_PROPERTY_TYPE_ANY,
-                                                        0,
-                                                        4096
-                                                        );
-    auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
+    xcb_get_property_cookie_t cookie = xcb_get_property(
+        xcbConn, 1, requestor, atoms->wl_selection, XCB_GET_PROPERTY_TYPE_ANY, 0, 4096);
+    auto* reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
     if (!reply) {
         return;
     }
@@ -222,24 +223,22 @@ void X11Source::handleTargets()
     QStringList removed;
 
     Mimes all;
-    xcb_atom_t *value = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
+    xcb_atom_t* value = static_cast<xcb_atom_t*>(xcb_get_property_value(reply));
     for (uint32_t i = 0; i < reply->value_len; i++) {
         if (value[i] == XCB_ATOM_NONE) {
             continue;
         }
 
-        const auto mimeStrings = Selection::atomToMimeTypes(value[i]);
+        const auto mimeStrings = atomToMimeTypes(value[i]);
         if (mimeStrings.isEmpty()) {
             // TODO: this should never happen? assert?
             continue;
         }
 
-
-        const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(),
-            [value, i](const Mime &mime) {
-                return mime.second == value[i];
-            }
-        );
+        const auto mimeIt
+            = std::find_if(m_offers.begin(), m_offers.end(), [value, i](const Mime& mime) {
+                  return mime.second == value[i];
+              });
 
         auto mimePair = Mime(mimeStrings[0], value[i]);
         if (mimeIt == m_offers.end()) {
@@ -250,75 +249,79 @@ void X11Source::handleTargets()
         all << mimePair;
     }
     // all left in m_offers are not in the updated targets
-    for (const auto mimePair : m_offers) {
+    for (const auto& mimePair : m_offers) {
         removed << mimePair.first;
     }
     m_offers = all;
 
     if (!added.isEmpty() || !removed.isEmpty()) {
-        Q_EMIT offersChanged(added, removed);
+        Q_EMIT qobject()->offersChanged(added, removed);
     }
 
     free(reply);
 }
 
-void X11Source::setDataSource(Wrapland::Client::DataSource *dataSource)
+template<typename DataSource>
+void X11Source<DataSource>::setSource(DataSource* src)
 {
-    Q_ASSERT(dataSource);
-    if (m_dataSource) {
-        delete m_dataSource;
+    Q_ASSERT(src);
+    if (m_source) {
+        delete m_source;
     }
 
-    m_dataSource = dataSource;
+    m_source = src;
 
-    for (const Mime &offer : m_offers) {
-        dataSource->offer(offer.first);
+    for (const Mime& offer : m_offers) {
+        src->offer(offer.first);
     }
 
-    connect(dataSource, &Wrapland::Client::DataSource::sendDataRequested,
-        this, &X11Source::startTransfer);
+    QObject::connect(src,
+                     &DataSource::sendDataRequested,
+                     qobject(),
+                     [this](auto const& mimeName, auto fd) { startTransfer(mimeName, fd); });
 }
 
-void X11Source::setOffers(const Mimes &offers)
+template<typename DataSource>
+void X11Source<DataSource>::setOffers(const Mimes& offers)
 {
     // TODO: share code with handleTargets and emit signals accordingly?
     m_offers = offers;
 }
 
-bool X11Source::handleSelectionNotify(xcb_selection_notify_event_t *event)
+template<typename DataSource>
+bool X11Source<DataSource>::handleSelectionNotify(xcb_selection_notify_event_t* event)
 {
-    if (event->requestor != window()) {
-        return false;
-    }
-    if (event->selection != selection()->atom()) {
-        return false;
-    }
     if (event->property == XCB_ATOM_NONE) {
         qCWarning(KWIN_XWL) << "Incoming X selection conversion failed";
         return true;
     }
     if (event->target == atoms->targets) {
-        handleTargets();
+        handleTargets(event->requestor);
         return true;
     }
     return false;
 }
 
-void X11Source::startTransfer(const QString &mimeName, qint32 fd)
+template<typename DataSource>
+void X11Source<DataSource>::startTransfer(const QString& mimeName, qint32 fd)
 {
-    const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(),
-        [mimeName](const Mime &mime) {
-            return mime.first == mimeName;
-        }
-    );
+    const auto mimeIt
+        = std::find_if(m_offers.begin(), m_offers.end(), [mimeName](const Mime& mime) {
+              return mime.first == mimeName;
+          });
     if (mimeIt == m_offers.end()) {
         qCDebug(KWIN_XWL) << "Sending X11 clipboard to Wayland failed: unsupported MIME.";
         close(fd);
         return;
     }
 
-    Q_EMIT transferReady((*mimeIt).second, fd);
+    Q_EMIT qobject()->transferReady((*mimeIt).second, fd);
 }
 
-} // namespace Xwl
-} // namespace KWin
+// Templates specializations
+template class WlSource<Wrapland::Server::DataDevice, Wrapland::Server::DataSource>;
+template class X11Source<Wrapland::Client::DataSource>;
+template class WlSource<Wrapland::Server::PrimarySelectionDevice,
+                        Wrapland::Server::PrimarySelectionSource>;
+template class X11Source<Wrapland::Client::PrimarySelectionSource>;
+}
diff --git a/xwl/selection_source.h b/xwl/selection_source.h
index 5bfb0b7e657476aece5b6e34a9bbcbf897fe4cd5..546897209d9e087c72f8c23f9a4df07b71ef4117 100644
--- a/xwl/selection_source.h
+++ b/xwl/selection_source.h
@@ -30,105 +30,98 @@ class QSocketNotifier;
 struct xcb_selection_request_event_t;
 struct xcb_xfixes_selection_notify_event_t;
 
-namespace Wrapland
+namespace KWin::Xwl
 {
-namespace Client
-{
-class DataSource;
-}
-namespace Server
-{
-class DataDevice;
-class DataSource;
-}
-}
 
-namespace KWin
-{
-namespace Xwl
+/*
+ * QObject attribute of a WlSource.
+ * This is a hack around having a template QObject.
+ */
+class qWlSource : public QObject
 {
-class Selection;
+    Q_OBJECT
+
+public:
+    using QObject::QObject;
+
+Q_SIGNALS:
+    void transferReady(xcb_selection_request_event_t* event, qint32 fd);
+};
 
 /**
- * Base class representing a data source.
+ * Representing a Wayland native data source.
  */
-class SelectionSource : public QObject
+template<typename DeviceInterface, typename SourceInterface>
+class WlSource
 {
-    Q_OBJECT
-
 public:
-    SelectionSource(Selection *selection);
+    WlSource(DeviceInterface* di);
+    ~WlSource();
+
+    void setSourceIface(SourceInterface* si);
+
+    bool handleSelectionRequest(xcb_selection_request_event_t* event);
+    void sendTargets(xcb_selection_request_event_t* event);
+    void sendTimestamp(xcb_selection_request_event_t* event);
 
-    xcb_timestamp_t timestamp() const {
+    void receiveOffer(const std::string& mime);
+
+    xcb_timestamp_t timestamp() const
+    {
         return m_timestamp;
     }
-    void setTimestamp(xcb_timestamp_t time) {
+    void setTimestamp(xcb_timestamp_t time)
+    {
         m_timestamp = time;
     }
 
-protected:
-    Selection *selection() const {
-        return m_selection;
-    }
-    void setWindow(xcb_window_t window) {
-        m_window = window;
-    }
-    xcb_window_t window() const {
-        return m_window;
+    qWlSource* qobject() const
+    {
+        return m_qobject;
     }
 
 private:
+    bool checkStartTransfer(xcb_selection_request_event_t* event);
+
+    DeviceInterface* m_di = nullptr;
+    SourceInterface* m_si = nullptr;
+
+    QVector<QString> m_offers;
+    QMetaObject::Connection m_offerConnection;
+
     xcb_timestamp_t m_timestamp = XCB_CURRENT_TIME;
-    Selection *m_selection;
-    xcb_window_t m_window;
+    qWlSource* m_qobject;
 
-    Q_DISABLE_COPY(SelectionSource)
+    Q_DISABLE_COPY(WlSource)
 };
 
-/**
- * Representing a Wayland native data source.
+using Mimes = QVector<QPair<QString, xcb_atom_t>>;
+
+/*
+ * QObject attribute of a X11Source.
+ * This is a hack around having a template QObject.
  */
-class WlSource : public SelectionSource
+class qX11Source : public QObject
 {
     Q_OBJECT
 
 public:
-    WlSource(Selection *selection, Wrapland::Server::DataDevice *ddi);
-    void setDataSourceIface(Wrapland::Server::DataSource *dsi);
-
-    bool handleSelectionRequest(xcb_selection_request_event_t *event);
-    void sendTargets(xcb_selection_request_event_t *event);
-    void sendTimestamp(xcb_selection_request_event_t *event);
-
-    void receiveOffer(const std::string &mime);
-    void sendSelectionNotify(xcb_selection_request_event_t *event, bool success);
+    using QObject::QObject;
 
 Q_SIGNALS:
-    void transferReady(xcb_selection_request_event_t *event, qint32 fd);
-
-private:
-    bool checkStartTransfer(xcb_selection_request_event_t *event);
-
-    Wrapland::Server::DataDevice *m_ddi = nullptr;
-    Wrapland::Server::DataSource *m_dsi = nullptr;
-
-    QVector<QString> m_offers;
-    QMetaObject::Connection m_offerConnection;
-
-    Q_DISABLE_COPY(WlSource)
+    void offersChanged(const QStringList& added, const QStringList& removed);
+    void transferReady(xcb_atom_t target, qint32 fd);
 };
 
-using Mimes = QVector<QPair<QString, xcb_atom_t> >;
-
 /**
  * Representing an X data source.
  */
-class X11Source : public SelectionSource
+template<typename Source>
+class X11Source
 {
-    Q_OBJECT
-
 public:
-    X11Source(Selection *selection, xcb_xfixes_selection_notify_event_t *event);
+    X11Source(xcb_xfixes_selection_notify_event_t* event);
+    ~X11Source();
 
     /**
      * @param ds must exist.
@@ -136,40 +129,50 @@ public:
      * X11Source does not take ownership of it in general, but if the function
      * is called again, it will delete the previous data source.
      */
-    void setDataSource(Wrapland::Client::DataSource *dataSource);
-    Wrapland::Client::DataSource *dataSource() const {
-        return m_dataSource;
+    void setSource(Source* src);
+    Source* source() const
+    {
+        return m_source;
     }
-    void getTargets();
+    void getTargets(xcb_window_t const window, xcb_atom_t const atom) const;
 
-    Mimes offers() const {
+    Mimes offers() const
+    {
         return m_offers;
     }
-    void setOffers(const Mimes &offers);
+    void setOffers(const Mimes& offers);
 
-    bool handleSelectionNotify(xcb_selection_notify_event_t *event);
+    bool handleSelectionNotify(xcb_selection_notify_event_t* event);
 
-    void setRequestor(xcb_window_t window) {
-        setWindow(window);
+    xcb_timestamp_t timestamp() const
+    {
+        return m_timestamp;
+    }
+    void setTimestamp(xcb_timestamp_t time)
+    {
+        m_timestamp = time;
     }
 
-Q_SIGNALS:
-    void offersChanged(const QStringList &added, const QStringList &removed);
-    void transferReady(xcb_atom_t target, qint32 fd);
+    qX11Source* qobject() const
+    {
+        return m_qobject;
+    }
 
 private:
-    void handleTargets();
-    void startTransfer(const QString &mimeName, qint32 fd);
+    void handleTargets(xcb_window_t const requestor);
+    void startTransfer(const QString& mimeName, qint32 fd);
 
     xcb_window_t m_owner;
-    Wrapland::Client::DataSource *m_dataSource = nullptr;
+    Source* m_source = nullptr;
 
     Mimes m_offers;
 
+    xcb_timestamp_t m_timestamp = XCB_CURRENT_TIME;
+    qX11Source* m_qobject;
+
     Q_DISABLE_COPY(X11Source)
 };
 
-} // namespace Xwl
-} // namespace KWin
+}
 
 #endif
diff --git a/xwl/transfer.cpp b/xwl/transfer.cpp
index 16b34f66e0c2a46788a7f816a6a49a09a7d8c339..c3d0607b3642281d121e8260abe535a4d610f603 100644
--- a/xwl/transfer.cpp
+++ b/xwl/transfer.cpp
@@ -19,7 +19,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *********************************************************************/
 #include "transfer.h"
 
-#include "databridge.h"
 #include "xwayland.h"
 
 #include "atoms.h"
@@ -27,8 +26,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include "workspace.h"
 
 #include <Wrapland/Client/connection_thread.h>
-#include <Wrapland/Client/datadevicemanager.h>
 #include <Wrapland/Client/datadevice.h>
+#include <Wrapland/Client/datadevicemanager.h>
 #include <Wrapland/Client/datasource.h>
 
 #include <Wrapland/Server/data_device.h>
@@ -51,7 +50,7 @@ namespace Xwl
 // in Bytes: equals 64KB
 static const uint32_t s_incrChunkSize = 63 * 1024;
 
-Transfer::Transfer(xcb_atom_t selection, qint32 fd, xcb_timestamp_t timestamp, QObject *parent)
+Transfer::Transfer(xcb_atom_t selection, qint32 fd, xcb_timestamp_t timestamp, QObject* parent)
     : QObject(parent)
     , m_atom(selection)
     , m_fd(fd)
@@ -95,8 +94,10 @@ void Transfer::closeFd()
     m_fd = -1;
 }
 
-TransferWltoX::TransferWltoX(xcb_atom_t selection, xcb_selection_request_event_t *request,
-                             qint32 fd, QObject *parent)
+TransferWltoX::TransferWltoX(xcb_atom_t selection,
+                             xcb_selection_request_event_t* request,
+                             qint32 fd,
+                             QObject* parent)
     : Transfer(selection, fd, 0, parent)
     , m_request(request)
 {
@@ -111,17 +112,15 @@ TransferWltoX::~TransferWltoX()
 void TransferWltoX::startTransferFromSource()
 {
     createSocketNotifier(QSocketNotifier::Read);
-    connect(socketNotifier(), &QSocketNotifier::activated, this,
-            [this](int socket) {
-                Q_UNUSED(socket);
-                readWlSource();
-            }
-    );
+    connect(socketNotifier(), &QSocketNotifier::activated, this, [this](int socket) {
+        Q_UNUSED(socket);
+        readWlSource();
+    });
 }
 
 int TransferWltoX::flushSourceData()
 {
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
 
     xcb_change_property(xcbConn,
                         XCB_PROP_MODE_REPLACE,
@@ -144,12 +143,10 @@ void TransferWltoX::startIncr()
 {
     Q_ASSERT(m_chunks.size() == 1);
 
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
 
-    uint32_t mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
-    xcb_change_window_attributes (xcbConn,
-                                  m_request->requestor,
-                                  XCB_CW_EVENT_MASK, mask);
+    uint32_t mask[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
+    xcb_change_window_attributes(xcbConn, m_request->requestor, XCB_CW_EVENT_MASK, mask);
 
     // spec says to make the available space larger
     const uint32_t chunkSpace = 1024 + s_incrChunkSize;
@@ -158,7 +155,9 @@ void TransferWltoX::startIncr()
                         m_request->requestor,
                         m_request->property,
                         atoms->incr,
-                        32, 1, &chunkSpace);
+                        32,
+                        1,
+                        &chunkSpace);
     xcb_flush(xcbConn);
 
     setIncr(true);
@@ -171,8 +170,7 @@ void TransferWltoX::startIncr()
 
 void TransferWltoX::readWlSource()
 {
-    if (m_chunks.size() == 0 ||
-            m_chunks.last().second == s_incrChunkSize) {
+    if (m_chunks.size() == 0 || m_chunks.last().second == s_incrChunkSize) {
         // append new chunk
         auto next = QPair<QByteArray, int>();
         next.first.resize(s_incrChunkSize);
@@ -229,11 +227,10 @@ void TransferWltoX::readWlSource()
     resetTimeout();
 }
 
-bool TransferWltoX::handlePropertyNotify(xcb_property_notify_event_t *event)
+bool TransferWltoX::handlePropertyNotify(xcb_property_notify_event_t* event)
 {
     if (event->window == m_request->requestor) {
-        if (event->state == XCB_PROPERTY_DELETE &&
-                event->atom == m_request->property) {
+        if (event->state == XCB_PROPERTY_DELETE && event->atom == m_request->property) {
             handlePropertyDelete();
         }
         return true;
@@ -252,19 +249,19 @@ void TransferWltoX::handlePropertyDelete()
     if (m_flushPropertyOnDelete) {
         if (!socketNotifier() && m_chunks.isEmpty()) {
             // transfer complete
-            xcb_connection_t *xcbConn = kwinApp()->x11Connection();
+            xcb_connection_t* xcbConn = kwinApp()->x11Connection();
 
             uint32_t mask[] = {0};
-            xcb_change_window_attributes (xcbConn,
-                                          m_request->requestor,
-                                          XCB_CW_EVENT_MASK, mask);
+            xcb_change_window_attributes(xcbConn, m_request->requestor, XCB_CW_EVENT_MASK, mask);
 
             xcb_change_property(xcbConn,
                                 XCB_PROP_MODE_REPLACE,
                                 m_request->requestor,
                                 m_request->property,
                                 m_request->target,
-                                8, 0, nullptr);
+                                8,
+                                0,
+                                nullptr);
             xcb_flush(xcbConn);
             m_flushPropertyOnDelete = false;
             endTransfer();
@@ -274,40 +271,39 @@ void TransferWltoX::handlePropertyDelete()
     }
 }
 
-TransferXtoWl::TransferXtoWl(xcb_atom_t selection, xcb_atom_t target, qint32 fd,
-                             xcb_timestamp_t timestamp, xcb_window_t parentWindow,
-                             QObject *parent)
+TransferXtoWl::TransferXtoWl(xcb_atom_t selection,
+                             xcb_atom_t target,
+                             qint32 fd,
+                             xcb_timestamp_t timestamp,
+                             xcb_window_t parentWindow,
+                             QObject* parent)
     : Transfer(selection, fd, timestamp, parent)
 {
     // create transfer window
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
     m_window = xcb_generate_id(xcbConn);
-    const uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
-                                   XCB_EVENT_MASK_PROPERTY_CHANGE };
+    const uint32_t values[] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE};
     xcb_create_window(xcbConn,
                       XCB_COPY_FROM_PARENT,
                       m_window,
                       parentWindow,
-                      0, 0,
-                      10, 10,
+                      0,
+                      0,
+                      10,
+                      10,
                       0,
                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
                       Xwayland::self()->xcbScreen()->root_visual,
                       XCB_CW_EVENT_MASK,
                       values);
     // convert selection
-    xcb_convert_selection(xcbConn,
-                          m_window,
-                          selection,
-                          target,
-                          atoms->wl_selection,
-                          timestamp);
+    xcb_convert_selection(xcbConn, m_window, selection, target, atoms->wl_selection, timestamp);
     xcb_flush(xcbConn);
 }
 
 TransferXtoWl::~TransferXtoWl()
 {
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
     xcb_destroy_window(xcbConn, m_window);
     xcb_flush(xcbConn);
 
@@ -315,11 +311,10 @@ TransferXtoWl::~TransferXtoWl()
     m_receiver = nullptr;
 }
 
-bool TransferXtoWl::handlePropertyNotify(xcb_property_notify_event_t *event)
+bool TransferXtoWl::handlePropertyNotify(xcb_property_notify_event_t* event)
 {
     if (event->window == m_window) {
-        if (event->state == XCB_PROPERTY_NEW_VALUE &&
-                event->atom == atoms->wl_selection) {
+        if (event->state == XCB_PROPERTY_NEW_VALUE && event->atom == atoms->wl_selection) {
             getIncrChunk();
         }
         return true;
@@ -327,7 +322,7 @@ bool TransferXtoWl::handlePropertyNotify(xcb_property_notify_event_t *event)
     return false;
 }
 
-bool TransferXtoWl::handleSelectionNotify(xcb_selection_notify_event_t *event)
+bool TransferXtoWl::handleSelectionNotify(xcb_selection_notify_event_t* event)
 {
     if (event->requestor != m_window) {
         return false;
@@ -364,17 +359,11 @@ bool TransferXtoWl::handleSelectionNotify(xcb_selection_notify_event_t *event)
 
 void TransferXtoWl::startTransfer()
 {
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-    auto cookie = xcb_get_property(xcbConn,
-                                   1,
-                                   m_window,
-                                   atoms->wl_selection,
-                                   XCB_GET_PROPERTY_TYPE_ANY,
-                                   0,
-                                   0x1fffffff
-                                   );
-
-    auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
+    auto cookie = xcb_get_property(
+        xcbConn, 1, m_window, atoms->wl_selection, XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff);
+
+    auto* reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
     if (reply == nullptr) {
         qCWarning(KWIN_XWL) << "Can't get selection property.";
         endTransfer();
@@ -402,17 +391,12 @@ void TransferXtoWl::getIncrChunk()
         // receive mechanism has not yet been setup
         return;
     }
-    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
+    xcb_connection_t* xcbConn = kwinApp()->x11Connection();
 
-    auto cookie = xcb_get_property(xcbConn,
-                                   0,
-                                   m_window,
-                                   atoms->wl_selection,
-                                   XCB_GET_PROPERTY_TYPE_ANY,
-                                   0,
-                                   0x1fffffff);
+    auto cookie = xcb_get_property(
+        xcbConn, 0, m_window, atoms->wl_selection, XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff);
 
-    auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
+    auto* reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
     if (!reply) {
         qCWarning(KWIN_XWL) << "Can't get selection property.";
         endTransfer();
@@ -438,16 +422,16 @@ DataReceiver::~DataReceiver()
     }
 }
 
-void DataReceiver::transferFromProperty(xcb_get_property_reply_t *reply)
+void DataReceiver::transferFromProperty(xcb_get_property_reply_t* reply)
 {
     m_propertyStart = 0;
     m_propertyReply = reply;
 
-    setData(static_cast<char *>(xcb_get_property_value(reply)),
+    setData(static_cast<char*>(xcb_get_property_value(reply)),
             xcb_get_property_value_length(reply));
 }
 
-void DataReceiver::setData(const char *value, int length)
+void DataReceiver::setData(const char* value, int length)
 {
     // simply set data without copy
     m_data = QByteArray::fromRawData(value, length);
@@ -469,7 +453,7 @@ void DataReceiver::partRead(int length)
     }
 }
 
-void NetscapeUrlReceiver::setData(const char *value, int length)
+void NetscapeUrlReceiver::setData(const char* value, int length)
 {
     auto origData = QByteArray::fromRawData(value, length);
 
@@ -507,14 +491,15 @@ void NetscapeUrlReceiver::setData(const char *value, int length)
     setDataInternal(data);
 }
 
-void MozUrlReceiver::setData(const char *value, int length)
+void MozUrlReceiver::setData(const char* value, int length)
 {
     // represent as QByteArray (guaranteed '\0'-terminated)
     const auto origData = QByteArray::fromRawData(value, length);
 
     // text/x-moz-url data is sent in utf-16 - copies the content
     // and converts it into 8 byte representation
-    const auto byteData = QString::fromUtf16(reinterpret_cast<const char16_t *>(origData.data())).toLatin1();
+    const auto byteData
+        = QString::fromUtf16(reinterpret_cast<const char16_t*>(origData.data())).toLatin1();
 
     if (byteData.indexOf('\n') == -1) {
         // there are no line breaks, not in text/x-moz-url format or empty,
@@ -566,10 +551,8 @@ void TransferXtoWl::dataSourceWrite()
         // property completely transferred
         if (incr()) {
             clearSocketNotifier();
-            xcb_connection_t *xcbConn = kwinApp()->x11Connection();
-            xcb_delete_property(xcbConn,
-                                m_window,
-                                atoms->wl_selection);
+            xcb_connection_t* xcbConn = kwinApp()->x11Connection();
+            xcb_delete_property(xcbConn, m_window, atoms->wl_selection);
             xcb_flush(xcbConn);
         } else {
             // transfer complete
@@ -578,12 +561,10 @@ void TransferXtoWl::dataSourceWrite()
     } else {
         if (!socketNotifier()) {
             createSocketNotifier(QSocketNotifier::Write);
-            connect(socketNotifier(), &QSocketNotifier::activated, this,
-                [this](int socket) {
-                    Q_UNUSED(socket);
-                    dataSourceWrite();
-                }
-            );
+            connect(socketNotifier(), &QSocketNotifier::activated, this, [this](int socket) {
+                Q_UNUSED(socket);
+                dataSourceWrite();
+            });
         }
     }
     resetTimeout();
diff --git a/xwl/transfer.h b/xwl/transfer.h
index 8e645c318ca8ac8c7d8b1c4be2870e6bbda5d78d..f11974e4acdee4a2d29e6acaba2d713e4957a900 100644
--- a/xwl/transfer.h
+++ b/xwl/transfer.h
@@ -57,14 +57,12 @@ class Transfer : public QObject
     Q_OBJECT
 
 public:
-    Transfer(xcb_atom_t selection,
-             qint32 fd,
-             xcb_timestamp_t timestamp,
-             QObject *parent = nullptr);
+    Transfer(xcb_atom_t selection, qint32 fd, xcb_timestamp_t timestamp, QObject* parent = nullptr);
 
-    virtual bool handlePropertyNotify(xcb_property_notify_event_t *event) = 0;
+    virtual bool handlePropertyNotify(xcb_property_notify_event_t* event) = 0;
     void timeout();
-    xcb_timestamp_t timestamp() const {
+    xcb_timestamp_t timestamp() const
+    {
         return m_timestamp;
     }
 
@@ -74,27 +72,34 @@ Q_SIGNALS:
 protected:
     void endTransfer();
 
-    xcb_atom_t atom() const {
+    xcb_atom_t atom() const
+    {
         return m_atom;
     }
-    qint32 fd() const {
+    qint32 fd() const
+    {
         return m_fd;
     }
 
-    void setIncr(bool set) {
+    void setIncr(bool set)
+    {
         m_incr = set;
     }
-    bool incr() const {
+    bool incr() const
+    {
         return m_incr;
     }
-    void resetTimeout() {
+    void resetTimeout()
+    {
         m_timeout = false;
     }
     void createSocketNotifier(QSocketNotifier::Type type);
     void clearSocketNotifier();
-    QSocketNotifier *socketNotifier() const {
+    QSocketNotifier* socketNotifier() const
+    {
         return m_notifier;
     }
+
 private:
     void closeFd();
 
@@ -102,7 +107,7 @@ private:
     qint32 m_fd;
     xcb_timestamp_t m_timestamp = XCB_CURRENT_TIME;
 
-    QSocketNotifier *m_notifier = nullptr;
+    QSocketNotifier* m_notifier = nullptr;
     bool m_incr = false;
     bool m_timeout = false;
 
@@ -118,16 +123,16 @@ class TransferWltoX : public Transfer
 
 public:
     TransferWltoX(xcb_atom_t selection,
-                  xcb_selection_request_event_t *request,
+                  xcb_selection_request_event_t* request,
                   qint32 fd,
-                  QObject *parent = nullptr);
+                  QObject* parent = nullptr);
     ~TransferWltoX() override;
 
     void startTransferFromSource();
-    bool handlePropertyNotify(xcb_property_notify_event_t *event) override;
+    bool handlePropertyNotify(xcb_property_notify_event_t* event) override;
 
 Q_SIGNALS:
-    void selectionNotify(xcb_selection_request_event_t *event, bool success);
+    void selectionNotify(xcb_selection_request_event_t* event, bool success);
 
 private:
     void startIncr();
@@ -135,12 +140,12 @@ private:
     int flushSourceData();
     void handlePropertyDelete();
 
-    xcb_selection_request_event_t *m_request = nullptr;
+    xcb_selection_request_event_t* m_request = nullptr;
 
     /* contains all received data portioned in chunks
      * TODO: explain second QPair component
      */
-    QVector<QPair<QByteArray, int> > m_chunks;
+    QVector<QPair<QByteArray, int>> m_chunks;
 
     bool m_propertyIsSet = false;
     bool m_flushPropertyOnDelete = false;
@@ -156,21 +161,21 @@ class DataReceiver
 public:
     virtual ~DataReceiver();
 
-    void transferFromProperty(xcb_get_property_reply_t *reply);
-
+    void transferFromProperty(xcb_get_property_reply_t* reply);
 
-    virtual void setData(const char *value, int length);
+    virtual void setData(const char* value, int length);
     QByteArray data() const;
 
     void partRead(int length);
 
 protected:
-    void setDataInternal(QByteArray data) {
+    void setDataInternal(QByteArray data)
+    {
         m_data = data;
     }
 
 private:
-    xcb_get_property_reply_t *m_propertyReply = nullptr;
+    xcb_get_property_reply_t* m_propertyReply = nullptr;
     int m_propertyStart = 0;
     QByteArray m_data;
 };
@@ -182,7 +187,7 @@ private:
 class NetscapeUrlReceiver : public DataReceiver
 {
 public:
-    void setData(const char *value, int length) override;
+    void setData(const char* value, int length) override;
 };
 
 /**
@@ -192,7 +197,7 @@ public:
 class MozUrlReceiver : public DataReceiver
 {
 public:
-    void setData(const char *value, int length) override;
+    void setData(const char* value, int length) override;
 };
 
 /**
@@ -206,12 +211,13 @@ public:
     TransferXtoWl(xcb_atom_t selection,
                   xcb_atom_t target,
                   qint32 fd,
-                  xcb_timestamp_t timestamp, xcb_window_t parentWindow,
-                  QObject *parent = nullptr);
+                  xcb_timestamp_t timestamp,
+                  xcb_window_t parentWindow,
+                  QObject* parent = nullptr);
     ~TransferXtoWl() override;
 
-    bool handleSelectionNotify(xcb_selection_notify_event_t *event);
-    bool handlePropertyNotify(xcb_property_notify_event_t *event) override;
+    bool handleSelectionNotify(xcb_selection_notify_event_t* event);
+    bool handlePropertyNotify(xcb_property_notify_event_t* event) override;
 
 private:
     void dataSourceWrite();
@@ -219,7 +225,7 @@ private:
     void getIncrChunk();
 
     xcb_window_t m_window;
-    DataReceiver *m_receiver = nullptr;
+    DataReceiver* m_receiver = nullptr;
 
     Q_DISABLE_COPY(TransferXtoWl)
 };
diff --git a/xwl/xwayland.cpp b/xwl/xwayland.cpp
index 00c79afcae20b83a06cdebe751fd1ef9eab28d13..41a7f363b5e7648d2425427123e371c22326c986 100644
--- a/xwl/xwayland.cpp
+++ b/xwl/xwayland.cpp
@@ -45,8 +45,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include <unistd.h>
 #endif
 
-#include <sys/socket.h>
 #include <iostream>
+#include <sys/socket.h>
 
 static void readDisplay(int pipe)
 {
@@ -58,7 +58,7 @@ static void readDisplay(int pipe)
     QByteArray displayNumber = readPipe.readLine();
 
     displayNumber.prepend(QByteArray(":"));
-    displayNumber.remove(displayNumber.size() -1, 1);
+    displayNumber.remove(displayNumber.size() - 1, 1);
     std::cout << "X-Server started on display " << displayNumber.constData() << std::endl;
 
     setenv("DISPLAY", displayNumber.constData(), true);
@@ -72,14 +72,14 @@ namespace KWin
 namespace Xwl
 {
 
-Xwayland *s_self = nullptr;
+Xwayland* s_self = nullptr;
 
-Xwayland *Xwayland::self()
+Xwayland* Xwayland::self()
 {
     return s_self;
 }
 
-Xwayland::Xwayland(ApplicationWaylandAbstract *app, QObject *parent)
+Xwayland::Xwayland(ApplicationWaylandAbstract* app, QObject* parent)
     : XwaylandInterface(parent)
     , m_app(app)
 {
@@ -156,29 +156,34 @@ void Xwayland::init()
     }
     m_xwaylandProcess->setProcessEnvironment(env);
     m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"),
-                           QString::number(pipeFds[1]),
-                           QStringLiteral("-rootless"),
-                           QStringLiteral("-wm"),
-                           QString::number(fd)});
-    m_xwaylandFailConnection = connect(m_xwaylandProcess, &QProcess::errorOccurred, this,
-        [this] (QProcess::ProcessError error) {
+                                     QString::number(pipeFds[1]),
+                                     QStringLiteral("-rootless"),
+                                     QStringLiteral("-wm"),
+                                     QString::number(fd)});
+    m_xwaylandFailConnection = connect(
+        m_xwaylandProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
             if (error == QProcess::FailedToStart) {
                 std::cerr << "FATAL ERROR: failed to start Xwayland" << std::endl;
             } else {
                 std::cerr << "FATAL ERROR: Xwayland failed, going to exit now" << std::endl;
             }
             Q_EMIT criticalError(1);
-        }
-    );
+        });
     const int xDisplayPipe = pipeFds[0];
-    connect(m_xwaylandProcess, &QProcess::started, this,
-        [this, xDisplayPipe] {
-            QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);
-            QObject::connect(watcher, &QFutureWatcher<void>::finished, this, &Xwayland::continueStartupWithX, Qt::QueuedConnection);
-            QObject::connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater, Qt::QueuedConnection);
-            watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe));
-        }
-    );
+    connect(m_xwaylandProcess, &QProcess::started, this, [this, xDisplayPipe] {
+        QFutureWatcher<void>* watcher = new QFutureWatcher<void>(this);
+        QObject::connect(watcher,
+                         &QFutureWatcher<void>::finished,
+                         this,
+                         &Xwayland::continueStartupWithX,
+                         Qt::QueuedConnection);
+        QObject::connect(watcher,
+                         &QFutureWatcher<void>::finished,
+                         watcher,
+                         &QFutureWatcher<void>::deleteLater,
+                         Qt::QueuedConnection);
+        watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe));
+    });
     m_xwaylandProcess->start();
     close(pipeFds[1]);
 }
@@ -192,7 +197,7 @@ void Xwayland::prepareDestroy()
 void Xwayland::createX11Connection()
 {
     int screenNumber = 0;
-    xcb_connection_t *c = nullptr;
+    xcb_connection_t* c = nullptr;
     if (m_xcbConnectionFd == -1) {
         c = xcb_connect(nullptr, &screenNumber);
     } else {
@@ -217,13 +222,14 @@ void Xwayland::createX11Connection()
 void Xwayland::continueStartupWithX()
 {
     createX11Connection();
-    xcb_connection_t *xcbConn = m_app->x11Connection();
+    xcb_connection_t* xcbConn = m_app->x11Connection();
     if (!xcbConn) {
         // about to quit
         Q_EMIT criticalError(1);
         return;
     }
-    QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(xcbConn), QSocketNotifier::Read, this);
+    QSocketNotifier* notifier
+        = new QSocketNotifier(xcb_get_file_descriptor(xcbConn), QSocketNotifier::Read, this);
     auto processXcbEvents = [this, xcbConn] {
         while (auto event = xcb_poll_for_event(xcbConn)) {
             if (m_dataBridge->filterEvent(event)) {
@@ -231,14 +237,21 @@ void Xwayland::continueStartupWithX()
                 continue;
             }
             long result = 0;
-            QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result);
+            QThread::currentThread()->eventDispatcher()->filterNativeEvent(
+                QByteArrayLiteral("xcb_generic_event_t"), event, &result);
             free(event);
         }
         xcb_flush(xcbConn);
     };
     connect(notifier, &QSocketNotifier::activated, this, processXcbEvents);
-    connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents);
-    connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents);
+    connect(QThread::currentThread()->eventDispatcher(),
+            &QAbstractEventDispatcher::aboutToBlock,
+            this,
+            processXcbEvents);
+    connect(QThread::currentThread()->eventDispatcher(),
+            &QAbstractEventDispatcher::awake,
+            this,
+            processXcbEvents);
 
     xcb_prefetch_extension_data(xcbConn, &xcb_xfixes_id);
     m_xfixes = xcb_get_extension_data(xcbConn, &xcb_xfixes_id);
@@ -254,13 +267,15 @@ void Xwayland::continueStartupWithX()
 
     // Check  whether another windowmanager is running
     const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT};
-    ScopedCPointer<xcb_generic_error_t> redirectCheck(xcb_request_check(connection(),
-                                                                        xcb_change_window_attributes_checked(connection(),
-                                                                                                             rootWindow(),
-                                                                                                             XCB_CW_EVENT_MASK,
-                                                                                                             maskValues)));
+    ScopedCPointer<xcb_generic_error_t> redirectCheck(
+        xcb_request_check(connection(),
+                          xcb_change_window_attributes_checked(
+                              connection(), rootWindow(), XCB_CW_EVENT_MASK, maskValues)));
     if (!redirectCheck.isNull()) {
-        fputs(i18n("kwin_wayland: an X11 window manager is running on the X11 Display.\n").toLocal8Bit().constData(), stderr);
+        fputs(i18n("kwin_wayland: an X11 window manager is running on the X11 Display.\n")
+                  .toLocal8Bit()
+                  .constData(),
+              stderr);
         Q_EMIT criticalError(1);
         return;
     }
@@ -274,7 +289,7 @@ void Xwayland::continueStartupWithX()
     Xcb::sync(); // Trigger possible errors, there's still a chance to abort
 }
 
-DragEventReply Xwayland::dragMoveFilter(Toplevel *target, const QPoint &pos)
+DragEventReply Xwayland::dragMoveFilter(Toplevel* target, const QPoint& pos)
 {
     if (!m_dataBridge) {
         return DragEventReply::Wayland;
diff --git a/xwl/xwayland.h b/xwl/xwayland.h
index b7ad6e63013c587d68f1c641094f9385eae81482..0ca11854e7930c852af69bff668e07d2ceda4af9 100644
--- a/xwl/xwayland.h
+++ b/xwl/xwayland.h
@@ -41,18 +41,20 @@ class KWIN_EXPORT Xwayland : public XwaylandInterface
     Q_OBJECT
 
 public:
-    static Xwayland *self();
+    static Xwayland* self();
 
-    Xwayland(ApplicationWaylandAbstract *app, QObject *parent = nullptr);
+    Xwayland(ApplicationWaylandAbstract* app, QObject* parent = nullptr);
     ~Xwayland() override;
 
     void init();
     void prepareDestroy();
 
-    xcb_screen_t *xcbScreen() const {
+    xcb_screen_t* xcbScreen() const
+    {
         return m_xcbScreen;
     }
-    const xcb_query_extension_reply_t *xfixes() const {
+    const xcb_query_extension_reply_t* xfixes() const
+    {
         return m_xfixes;
     }
 
@@ -64,17 +66,17 @@ private:
     void createX11Connection();
     void continueStartupWithX();
 
-    DragEventReply dragMoveFilter(Toplevel *target, const QPoint &pos) override;
+    DragEventReply dragMoveFilter(Toplevel* target, const QPoint& pos) override;
 
     int m_xcbConnectionFd = -1;
-    QProcess *m_xwaylandProcess = nullptr;
+    QProcess* m_xwaylandProcess = nullptr;
     QMetaObject::Connection m_xwaylandFailConnection;
 
-    xcb_screen_t *m_xcbScreen = nullptr;
-    const xcb_query_extension_reply_t *m_xfixes = nullptr;
-    DataBridge *m_dataBridge = nullptr;
+    xcb_screen_t* m_xcbScreen = nullptr;
+    const xcb_query_extension_reply_t* m_xfixes = nullptr;
+    DataBridge* m_dataBridge = nullptr;
 
-    ApplicationWaylandAbstract *m_app;
+    ApplicationWaylandAbstract* m_app;
 
     Q_DISABLE_COPY(Xwayland)
 };
diff --git a/xwl/xwayland_interface.cpp b/xwl/xwayland_interface.cpp
index 454b4ce939fb7fcf9a6a5713ccc511e8d8742228..c51198643a9a2d67c04c6c9b612790907f625cb5 100644
--- a/xwl/xwayland_interface.cpp
+++ b/xwl/xwayland_interface.cpp
@@ -22,14 +22,14 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 namespace KWin
 {
 
-XwaylandInterface *s_self = nullptr;
+XwaylandInterface* s_self = nullptr;
 
-XwaylandInterface *XwaylandInterface::self()
+XwaylandInterface* XwaylandInterface::self()
 {
     return s_self;
 }
 
-XwaylandInterface::XwaylandInterface(QObject *parent)
+XwaylandInterface::XwaylandInterface(QObject* parent)
     : QObject(parent)
 {
     s_self = this;
diff --git a/xwl/xwayland_interface.h b/xwl/xwayland_interface.h
index a7ef7cb98776cd4ba02405e131a992cc55313296..200508e80a295acdf3866b19a63b27f6ca1aa76d 100644
--- a/xwl/xwayland_interface.h
+++ b/xwl/xwayland_interface.h
@@ -46,19 +46,19 @@ class KWIN_EXPORT XwaylandInterface : public QObject
     Q_OBJECT
 
 public:
-    static XwaylandInterface *self();
+    static XwaylandInterface* self();
 
-    virtual Xwl::DragEventReply dragMoveFilter(Toplevel *target, const QPoint &pos) = 0;
+    virtual Xwl::DragEventReply dragMoveFilter(Toplevel* target, const QPoint& pos) = 0;
 
 protected:
-    explicit XwaylandInterface(QObject *parent = nullptr);
+    explicit XwaylandInterface(QObject* parent = nullptr);
     ~XwaylandInterface() override;
 
 private:
     Q_DISABLE_COPY(XwaylandInterface)
 };
 
-inline XwaylandInterface *xwayland()
+inline XwaylandInterface* xwayland()
 {
     return XwaylandInterface::self();
 }