From 6104dc50cfa4cfa6467029dfdad17c9848dc225c Mon Sep 17 00:00:00 2001 From: David Redondo Date: Tue, 17 Aug 2021 12:25:34 +0200 Subject: [PATCH] xwl: Sync primary selection Syncs the primary selection from wayland to X and from X to wayland. Instead of doing it through the internal connection like the clipboard, this sets/reacts to changes in SeatInterface::prrimarySelection directly. BUG:422426 FIXED-IN:5.23 --- src/atoms.cpp | 1 + src/atoms.h | 1 + src/xwl/CMakeLists.txt | 1 + src/xwl/databridge.cpp | 4 +- src/xwl/databridge.h | 2 + src/xwl/primary.cpp | 209 +++++++++++++++++++++++++++++++++++++ src/xwl/primary.h | 69 ++++++++++++ src/xwl/selection_source.h | 2 +- 8 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 src/xwl/primary.cpp create mode 100644 src/xwl/primary.h diff --git a/src/atoms.cpp b/src/atoms.cpp index bc68143b68..f1bee5b129 100644 --- a/src/atoms.cpp +++ b/src/atoms.cpp @@ -68,6 +68,7 @@ Atoms::Atoms() , delete_atom(QByteArrayLiteral("DELETE")) , incr(QByteArrayLiteral("INCR")) , wl_selection(QByteArrayLiteral("WL_SELECTION")) + , primary(QByteArrayLiteral("PRIMARY")) , m_dtSmWindowInfo(QByteArrayLiteral("_DT_SM_WINDOW_INFO")) , m_motifSupport(QByteArrayLiteral("_MOTIF_WM_INFO")) , m_helpersRetrieved(false) diff --git a/src/atoms.h b/src/atoms.h index 52aaa4acc7..6b5a82dcb0 100644 --- a/src/atoms.h +++ b/src/atoms.h @@ -77,6 +77,7 @@ public: Xcb::Atom delete_atom; Xcb::Atom incr; Xcb::Atom wl_selection; + Xcb::Atom primary; /** * @internal diff --git a/src/xwl/CMakeLists.txt b/src/xwl/CMakeLists.txt index 085c9f484e..5a7e713d5e 100644 --- a/src/xwl/CMakeLists.txt +++ b/src/xwl/CMakeLists.txt @@ -7,6 +7,7 @@ add_library(KWinXwaylandServerModule OBJECT drag.cpp drag_wl.cpp drag_x.cpp + primary.cpp selection.cpp selection_source.cpp transfer.cpp diff --git a/src/xwl/databridge.cpp b/src/xwl/databridge.cpp index 71e6f3723b..9d75a43e04 100644 --- a/src/xwl/databridge.cpp +++ b/src/xwl/databridge.cpp @@ -9,6 +9,7 @@ #include "databridge.h" #include "clipboard.h" #include "dnd.h" +#include "primary.h" #include "selection.h" #include "xwayland.h" @@ -81,6 +82,7 @@ void DataBridge::init() { m_clipboard = new Clipboard(atoms->clipboard, this); m_dnd = new Dnd(atoms->xdnd_selection, this); + m_primary = new Primary(atoms->primary, this); waylandServer()->dispatch(); kwinApp()->installNativeEventFilter(this); } @@ -89,7 +91,7 @@ bool DataBridge::nativeEventFilter(const QByteArray &eventType, void *message, l { if (eventType == "xcb_generic_event_t") { xcb_generic_event_t *event = static_cast(message); - return m_clipboard->filterEvent(event) || m_dnd->filterEvent(event); + return m_clipboard->filterEvent(event) || m_dnd->filterEvent(event) || m_primary->filterEvent(event); } return false; } diff --git a/src/xwl/databridge.h b/src/xwl/databridge.h index 86cf9f2f09..4bb115a2cc 100644 --- a/src/xwl/databridge.h +++ b/src/xwl/databridge.h @@ -36,6 +36,7 @@ namespace Xwl { class Clipboard; class Dnd; +class Primary; enum class DragEventReply; /** @@ -75,6 +76,7 @@ private: Clipboard *m_clipboard = nullptr; Dnd *m_dnd = nullptr; + Primary *m_primary = nullptr; /* Internal data device interface */ KWayland::Client::DataDevice *m_dataDevice = nullptr; diff --git a/src/xwl/primary.cpp b/src/xwl/primary.cpp new file mode 100644 index 0000000000..2c9dfd6e24 --- /dev/null +++ b/src/xwl/primary.cpp @@ -0,0 +1,209 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2019 Roman Gilg + SPDX-FileCopyrightText: 2021 David Redondo + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "primary.h" + +#include "selection_source.h" + +#include "x11client.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include + +#include +#include + +#include + +namespace KWin +{ +namespace Xwl +{ + +class PrimarySelectionSource : public KWaylandServer::AbstractDataSource +{ + Q_OBJECT +public: + void requestData(const QString & mimeType, qint32 fd) override + { + Q_EMIT dataRequested(mimeType, fd); + } + void cancel() override + { + } + QStringList mimeTypes() const override + { + return m_mimeTypes; + } + void setMimeTypes(const QStringList &mimeTypes) + { + m_mimeTypes = mimeTypes; + } +Q_SIGNALS: + void dataRequested(const QString &mimeType, qint32 fd); +private: + QStringList m_mimeTypes; +}; + +Primary::Primary(xcb_atom_t atom, QObject *parent) + : Selection(atom, parent) +{ + 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, + XCB_COPY_FROM_PARENT, + XCB_CW_EVENT_MASK, + clipboardValues); + registerXfixes(); + xcb_flush(xcbConn); + + connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::primarySelectionChanged, + this, &Primary::wlPrimarySelectionChanged); +} + +Primary::~Primary() = default; + +void Primary::wlPrimarySelectionChanged(KWaylandServer::AbstractDataSource *dsi) +{ + if (m_waitingForTargets) { + return; + } + + if (!ownsSelection(dsi)) { + // Wayland native client provides new selection + if (!m_checkConnection) { + m_checkConnection = connect(workspace(), &Workspace::clientActivated, + this, &Primary::checkWlSource); + } + // remove previous source so checkWlSource() can create a new one + setWlSource(nullptr); + } + checkWlSource(); +} + +bool Primary::ownsSelection(KWaylandServer::AbstractDataSource *dsi) const +{ + return dsi && dsi == m_primarySelectionSource.get(); +} + +void Primary::checkWlSource() +{ + if (m_waitingForTargets) { + return; + } + + auto dsi = waylandServer()->seat()->primarySelection(); + 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 (!dsi || ownsSelection(dsi)) { + // Xwayland source or no source + disconnect(m_checkConnection); + m_checkConnection = QMetaObject::Connection(); + removeSource(); + return; + } + if (!qobject_cast(workspace()->activeClient())) { + // 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); + setWlSource(wls); + if (dsi) { + wls->setDataSourceIface(dsi); + } + ownSelection(true); +} + +void Primary::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) +{ + const AbstractClient *client = workspace()->activeClient(); + if (!qobject_cast(client)) { + // clipboard is only allowed to be acquired when Xwayland has focus + // TODO: can we make this stronger (window id comparison)? + createX11Source(nullptr); + return; + } + + createX11Source(event); + + if (X11Source *source = x11Source()) { + source->getTargets(); + m_waitingForTargets = true; + } else { + qCWarning(KWIN_XWL) << "Could not create a source from" << event << Qt::hex << (event ? event->owner : -1); + } +} + +void Primary::x11OffersChanged(const QStringList &added, const QStringList &removed) +{ + m_waitingForTargets = false; + X11Source *source = x11Source(); + if (!source) { + qCWarning(KWIN_XWL) << "offers changed when not having an X11Source!?"; + return; + } + + const Mimes offers = source->offers(); + + if (!offers.isEmpty()) { + QStringList mimeTypes; + mimeTypes.reserve(offers.size()); + std::transform(offers.begin(), offers.end(), std::back_inserter(mimeTypes), [](const Mimes::value_type &pair) { + return pair.first; + }); + auto newSelection = std::make_unique(); + newSelection->setMimeTypes(mimeTypes); + connect(newSelection.get(), &PrimarySelectionSource::dataRequested, source, &X11Source::startTransfer); + // we keep the old selection around because setPrimarySelection needs it to be still alive + std::swap(m_primarySelectionSource, newSelection); + waylandServer()->seat()->setPrimarySelection(m_primarySelectionSource.get()); + } else { + KWaylandServer::AbstractDataSource *currentSelection = waylandServer()->seat()->primarySelection(); + if (!ownsSelection(currentSelection)) { + waylandServer()->seat()->setPrimarySelection(nullptr); + m_primarySelectionSource.reset(); + } + } +} + +} // namespace Xwl +} // namespace KWin + +#include "primary.moc" diff --git a/src/xwl/primary.h b/src/xwl/primary.h new file mode 100644 index 0000000000..e96ea20181 --- /dev/null +++ b/src/xwl/primary.h @@ -0,0 +1,69 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2019 Roman Gilg + SPDX-FileCopyrightText: 2021 David Redondo + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef KWIN_XWL_PRIMARY +#define KWIN_XWL_PRIMARY + +#include "selection.h" + +#include + +namespace KWaylandServer +{ +class AbstractDataSource; +} + +namespace KWin +{ +namespace Xwl +{ + +class PrimarySelectionSource; + +/** + * Represents the X clipboard, which is on Wayland side just called + * @e selection. + */ +class Primary : public Selection +{ + Q_OBJECT + +public: + Primary(xcb_atom_t atom, QObject *parent); + ~Primary() override; +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 wlPrimarySelectionChanged(KWaylandServer::AbstractDataSource *dsi); + /** + * Check the current state of the selection and if a source needs + * to be created or destroyed. + */ + void checkWlSource(); + + /** + * Returns if dsi is managed by our data bridge + */ + bool ownsSelection(KWaylandServer::AbstractDataSource *dsi) const; + + QMetaObject::Connection m_checkConnection; + + Q_DISABLE_COPY(Primary) + bool m_waitingForTargets = false; + std::unique_ptr m_primarySelectionSource; +}; + +} // namespace Xwl +} // namespace KWin + +#endif + diff --git a/src/xwl/selection_source.h b/src/xwl/selection_source.h index 6e19b9bcf5..8514679bd4 100644 --- a/src/xwl/selection_source.h +++ b/src/xwl/selection_source.h @@ -142,13 +142,13 @@ public: setWindow(window); } + void startTransfer(const QString &mimeName, qint32 fd); Q_SIGNALS: void offersChanged(const QStringList &added, const QStringList &removed); void transferReady(xcb_atom_t target, qint32 fd); private: void handleTargets(); - void startTransfer(const QString &mimeName, qint32 fd); xcb_window_t m_owner; KWayland::Client::DataSource *m_dataSource = nullptr;