From fdea693da56f1ee91cd01487d562aa22aac02a87 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Tue, 4 Oct 2022 14:56:45 +0300 Subject: [PATCH] wayland: Implement xwayland-shell-v1 As explained in [1], WL_SURFACE_ID is racy because wayland aggressively reuses object ids. The xwayland-shell-v1 protocol intends to fix that by two things: * associating a serial number with each X11 window. This is to avoid potential XID reuse * referring to the wayland surface by the wl_surface rather than specifying an object id Unfortunately, we will have to maintain both legacy WL_SURFACE_ID and WL_SURFACE_SERIAL for quiet some time until most instances of Xwayland support the xwayland-shell-v1 protocol [2]. [1] https://gitlab.freedesktop.org/xorg/xserver/-/issues/1157 [2] https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/163 --- src/atoms.cpp | 1 + src/atoms.h | 1 + src/events.cpp | 10 +- src/wayland/CMakeLists.txt | 6 +- src/wayland/xwaylandshell_v1_interface.cpp | 165 +++++++++++++++++++++ src/wayland/xwaylandshell_v1_interface.h | 59 ++++++++ src/wayland_server.cpp | 26 +++- src/wayland_server.h | 6 + src/window.h | 7 + 9 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 src/wayland/xwaylandshell_v1_interface.cpp create mode 100644 src/wayland/xwaylandshell_v1_interface.h diff --git a/src/atoms.cpp b/src/atoms.cpp index 726eeb68ab..040614269d 100644 --- a/src/atoms.cpp +++ b/src/atoms.cpp @@ -58,6 +58,7 @@ Atoms::Atoms() , netscape_url(QByteArrayLiteral("_NETSCAPE_URL")) , moz_url(QByteArrayLiteral("text/x-moz-url")) , wl_surface_id(QByteArrayLiteral("WL_SURFACE_ID")) + , wl_surface_serial(QByteArrayLiteral("WL_SURFACE_SERIAL")) , kde_net_wm_appmenu_service_name(QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME")) , kde_net_wm_appmenu_object_path(QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH")) , clipboard(QByteArrayLiteral("CLIPBOARD")) diff --git a/src/atoms.h b/src/atoms.h index aa333e8626..43cf0c5edd 100644 --- a/src/atoms.h +++ b/src/atoms.h @@ -67,6 +67,7 @@ public: Xcb::Atom netscape_url; Xcb::Atom moz_url; Xcb::Atom wl_surface_id; + Xcb::Atom wl_surface_serial; Xcb::Atom kde_net_wm_appmenu_service_name; Xcb::Atom kde_net_wm_appmenu_object_path; Xcb::Atom clipboard; diff --git a/src/events.cpp b/src/events.cpp index d413a56d93..600a5763a0 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -31,6 +31,7 @@ #include "useractions.h" #include "utils/xcbutils.h" #include "wayland/surface_interface.h" +#include "wayland/xwaylandshell_v1_interface.h" #include "wayland_server.h" #include @@ -1333,7 +1334,14 @@ void Window::propertyNotifyEvent(xcb_property_notify_event_t *e) void Window::clientMessageEvent(xcb_client_message_event_t *e) { - if (e->type == atoms->wl_surface_id) { + if (e->type == atoms->wl_surface_serial) { + m_surfaceSerial = (uint64_t(e->data.data32[1]) << 32) | e->data.data32[0]; + if (auto w = waylandServer()) { + if (KWaylandServer::XwaylandSurfaceV1Interface *xwaylandSurface = w->xwaylandShell()->findSurface(m_surfaceSerial)) { + setSurface(xwaylandSurface->surface()); + } + } + } else if (e->type == atoms->wl_surface_id) { m_pendingSurfaceId = e->data.data32[0]; if (auto w = waylandServer()) { if (auto s = KWaylandServer::SurfaceInterface::get(m_pendingSurfaceId, w->xWaylandConnection())) { diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index a84c2a0190..e0781bc2e6 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -188,7 +188,6 @@ ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml PROTOCOL ${WaylandProtocols_DATADIR}/staging/tearing-control/tearing-control-v1.xml BASENAME tearing-control-v1 ) - ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml PROTOCOL ${WaylandProtocols_DATADIR}/unstable/xwayland-keyboard-grab/xwayland-keyboard-grab-unstable-v1.xml BASENAME xwayland-keyboard-grab-unstable-v1 @@ -197,6 +196,10 @@ ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml PROTOCOL ${WaylandProtocols_DATADIR}/staging/content-type/content-type-v1.xml BASENAME content-type-v1 ) +ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml + PROTOCOL ${WaylandProtocols_DATADIR}/staging/xwayland-shell/xwayland-shell-v1.xml + BASENAME xwayland-shell-v1 +) target_sources(kwin PRIVATE abstract_data_source.cpp @@ -272,6 +275,7 @@ target_sources(kwin PRIVATE xdgoutput_v1_interface.cpp xdgshell_interface.cpp xwaylandkeyboardgrab_v1_interface.cpp + xwaylandshell_v1_interface.cpp ) if(CMAKE_SYSTEM_NAME MATCHES "Linux") diff --git a/src/wayland/xwaylandshell_v1_interface.cpp b/src/wayland/xwaylandshell_v1_interface.cpp new file mode 100644 index 0000000000..8b76e0696b --- /dev/null +++ b/src/wayland/xwaylandshell_v1_interface.cpp @@ -0,0 +1,165 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "xwaylandshell_v1_interface.h" + +#include "display.h" +#include "surfacerole_p.h" +#include "surface_interface.h" + +#include "qwayland-server-xwayland-shell-v1.h" + +namespace KWaylandServer +{ + +static const quint32 s_version = 1; + +class XwaylandShellV1InterfacePrivate : public QtWaylandServer::xwayland_shell_v1 +{ +public: + XwaylandShellV1InterfacePrivate(Display *display, XwaylandShellV1Interface *q); + + XwaylandShellV1Interface *q; + QList m_surfaces; + +protected: + void xwayland_shell_v1_destroy(Resource *resource) override; + void xwayland_shell_v1_get_xwayland_surface(Resource *resource, uint32_t id, struct ::wl_resource *surface) override; +}; + +struct XwaylandSurfaceV1State +{ + std::optional serial; +}; + +class XwaylandSurfaceV1InterfacePrivate : public SurfaceRole, public QtWaylandServer::xwayland_surface_v1 +{ +public: + XwaylandSurfaceV1InterfacePrivate(XwaylandShellV1Interface *shell, SurfaceInterface *surface, wl_client *client, uint32_t id, int version, XwaylandSurfaceV1Interface *q); + + void commit() override; + + XwaylandSurfaceV1Interface *q; + XwaylandShellV1Interface *shell; + + XwaylandSurfaceV1State current; + XwaylandSurfaceV1State pending; + +protected: + void xwayland_surface_v1_destroy_resource(Resource *resource) override; + void xwayland_surface_v1_set_serial(Resource *resource, uint32_t serial_lo, uint32_t serial_hi) override; + void xwayland_surface_v1_destroy(Resource *resource) override; +}; + +XwaylandShellV1InterfacePrivate::XwaylandShellV1InterfacePrivate(Display *display, XwaylandShellV1Interface *q) + : QtWaylandServer::xwayland_shell_v1(*display, s_version) + , q(q) +{ +} + +void XwaylandShellV1InterfacePrivate::xwayland_shell_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void XwaylandShellV1InterfacePrivate::xwayland_shell_v1_get_xwayland_surface(Resource *resource, uint32_t id, ::wl_resource *surface_resource) +{ + SurfaceInterface *surface = SurfaceInterface::get(surface_resource); + if (auto role = SurfaceRole::get(surface)) { + wl_resource_post_error(resource->handle, error_role, "the surface already has a role assigned %s", role->name().constData()); + return; + } + + auto xwaylandSurface = new XwaylandSurfaceV1Interface(q, surface, resource->client(), id, resource->version()); + m_surfaces.append(xwaylandSurface); + QObject::connect(xwaylandSurface, &QObject::destroyed, q, [this, xwaylandSurface]() { + m_surfaces.removeOne(xwaylandSurface); + }); +} + +XwaylandSurfaceV1InterfacePrivate::XwaylandSurfaceV1InterfacePrivate(XwaylandShellV1Interface *shell, SurfaceInterface *surface, wl_client *client, uint32_t id, int version, XwaylandSurfaceV1Interface *q) + : SurfaceRole(surface, QByteArrayLiteral("xwayland_surface_v1")) + , QtWaylandServer::xwayland_surface_v1(client, id, version) + , q(q) + , shell(shell) +{ +} + +void XwaylandSurfaceV1InterfacePrivate::commit() +{ + if (pending.serial.has_value()) { + current.serial = std::exchange(pending.serial, std::nullopt); + Q_EMIT shell->surfaceAssociated(q); + } +} + +void XwaylandSurfaceV1InterfacePrivate::xwayland_surface_v1_destroy_resource(Resource *resource) +{ + delete q; +} + +void XwaylandSurfaceV1InterfacePrivate::xwayland_surface_v1_set_serial(Resource *resource, uint32_t serial_lo, uint32_t serial_hi) +{ + const uint64_t serial = (uint64_t(serial_hi) << 32) | serial_lo; + if (!serial) { + wl_resource_post_error(resource->handle, error_invalid_serial, "given serial is 0"); + return; + } + + if (current.serial.has_value()) { + wl_resource_post_error(resource->handle, error_already_associated, + "xwayland_surface_v1 already has a serial assigned to it: %" PRIu64, current.serial.value()); + return; + } + + pending.serial = serial; +} + +void XwaylandSurfaceV1InterfacePrivate::xwayland_surface_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +XwaylandShellV1Interface::XwaylandShellV1Interface(Display *display, QObject *parent) + : QObject(parent) + , d(new XwaylandShellV1InterfacePrivate(display, this)) +{ +} + +XwaylandShellV1Interface::~XwaylandShellV1Interface() +{ +} + +XwaylandSurfaceV1Interface *XwaylandShellV1Interface::findSurface(uint64_t serial) const +{ + for (XwaylandSurfaceV1Interface *surface : std::as_const(d->m_surfaces)) { + if (surface->serial() == serial) { + return surface; + } + } + return nullptr; +} + +XwaylandSurfaceV1Interface::XwaylandSurfaceV1Interface(XwaylandShellV1Interface *shell, SurfaceInterface *surface, wl_client *client, uint32_t id, int version) + : d(new XwaylandSurfaceV1InterfacePrivate(shell, surface, client, id, version, this)) +{ +} + +XwaylandSurfaceV1Interface::~XwaylandSurfaceV1Interface() +{ +} + +SurfaceInterface *XwaylandSurfaceV1Interface::surface() const +{ + return d->surface(); +} + +std::optional XwaylandSurfaceV1Interface::serial() const +{ + return d->current.serial; +} + +} // namespace KWaylandServer diff --git a/src/wayland/xwaylandshell_v1_interface.h b/src/wayland/xwaylandshell_v1_interface.h new file mode 100644 index 0000000000..431a74169d --- /dev/null +++ b/src/wayland/xwaylandshell_v1_interface.h @@ -0,0 +1,59 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include + +#include + +#include +#include + +struct wl_client; + +namespace KWaylandServer +{ + +class Display; +class SurfaceInterface; +class XwaylandShellV1Interface; +class XwaylandShellV1InterfacePrivate; +class XwaylandSurfaceV1InterfacePrivate; + +class KWIN_EXPORT XwaylandSurfaceV1Interface : public QObject +{ + Q_OBJECT + +public: + XwaylandSurfaceV1Interface(XwaylandShellV1Interface *shell, SurfaceInterface *surface, wl_client *client, uint32_t id, int version); + ~XwaylandSurfaceV1Interface() override; + + SurfaceInterface *surface() const; + std::optional serial() const; + +private: + std::unique_ptr d; +}; + +class KWIN_EXPORT XwaylandShellV1Interface : public QObject +{ + Q_OBJECT + +public: + explicit XwaylandShellV1Interface(Display *display, QObject *parent = nullptr); + ~XwaylandShellV1Interface() override; + + XwaylandSurfaceV1Interface *findSurface(uint64_t serial) const; + +Q_SIGNALS: + void surfaceAssociated(XwaylandSurfaceV1Interface *surface); + +private: + std::unique_ptr d; +}; + +} // namespace KWaylandServer diff --git a/src/wayland_server.cpp b/src/wayland_server.cpp index c86ac12925..d03f2e86d6 100644 --- a/src/wayland_server.cpp +++ b/src/wayland_server.cpp @@ -67,6 +67,7 @@ #include "wayland/xdgoutput_v1_interface.h" #include "wayland/xdgshell_interface.h" #include "wayland/xwaylandkeyboardgrab_v1_interface.h" +#include "wayland/xwaylandshell_v1_interface.h" #include "workspace.h" #include "x11window.h" #include "xdgactivationv1.h" @@ -146,6 +147,10 @@ public: }; const QSet inputmethodInterfaces = {"zwp_input_panel_v1", "zwp_input_method_v1"}; + const QSet xwaylandInterfaces = { + QByteArrayLiteral("zwp_xwayland_keyboard_grab_manager_v1"), + QByteArrayLiteral("xwayland_shell_v1"), + }; QSet m_reported; @@ -159,7 +164,7 @@ public: return false; } - if (client != waylandServer()->xWaylandConnection() && interfaceName == "zwp_xwayland_keyboard_grab_manager_v1") { + if (client != waylandServer()->xWaylandConnection() && xwaylandInterfaces.contains(interfaceName)) { return false; } @@ -361,6 +366,25 @@ bool WaylandServer::init(InitializationFlags flags) // The surface will be bound later when a WL_SURFACE_ID message is received. }); + m_xwaylandShell = new KWaylandServer::XwaylandShellV1Interface(m_display, m_display); + connect(m_xwaylandShell, &KWaylandServer::XwaylandShellV1Interface::surfaceAssociated, this, [](KWaylandServer::XwaylandSurfaceV1Interface *surface) { + X11Window *window = workspace()->findClient([&surface](const X11Window *window) { + return window->surfaceSerial() == surface->serial(); + }); + if (window) { + window->setSurface(surface->surface()); + return; + } + + Unmanaged *unmanaged = workspace()->findUnmanaged([&surface](const Unmanaged *window) { + return window->surfaceSerial() == surface->serial(); + }); + if (unmanaged) { + unmanaged->setSurface(surface->surface()); + return; + } + }); + m_tabletManagerV2 = new TabletManagerV2Interface(m_display, m_display); m_keyboardShortcutsInhibitManager = new KeyboardShortcutsInhibitManagerV1Interface(m_display, m_display); diff --git a/src/wayland_server.h b/src/wayland_server.h index 6afd1e5394..d874c3d781 100644 --- a/src/wayland_server.h +++ b/src/wayland_server.h @@ -52,6 +52,7 @@ class XWaylandKeyboardGrabManagerV1Interface; class ContentTypeManagerV1Interface; class DrmLeaseManagerV1; class TearingControlManagerV1Interface; +class XwaylandShellV1Interface; } namespace KWin @@ -128,6 +129,10 @@ public: { return m_keyboardShortcutsInhibitManager; } + KWaylandServer::XwaylandShellV1Interface *xwaylandShell() const + { + return m_xwaylandShell; + } bool isKeyboardShortcutsInhibited() const; @@ -292,6 +297,7 @@ private: KWaylandServer::XWaylandKeyboardGrabManagerV1Interface *m_xWaylandKeyboardGrabManager = nullptr; KWaylandServer::ContentTypeManagerV1Interface *m_contentTypeManager = nullptr; KWaylandServer::TearingControlManagerV1Interface *m_tearingControlInterface = nullptr; + KWaylandServer::XwaylandShellV1Interface *m_xwaylandShell = nullptr; QList m_windows; InitializationFlags m_initFlags; QHash m_waylandOutputs; diff --git a/src/window.h b/src/window.h index b4c41c145e..1ba9649575 100644 --- a/src/window.h +++ b/src/window.h @@ -770,6 +770,7 @@ public: bool skipsCloseAnimation() const; void setSkipCloseAnimation(bool set); + quint64 surfaceSerial() const; quint32 pendingSurfaceId() const; KWaylandServer::SurfaceInterface *surface() const; void setSurface(KWaylandServer::SurfaceInterface *surface); @@ -1930,6 +1931,7 @@ private: mutable bool m_shapeRegionIsValid = false; bool m_skipCloseAnimation; quint32 m_pendingSurfaceId = 0; + quint64 m_surfaceSerial = 0; QPointer m_surface; // when adding new data members, check also copyToDeleted() qreal m_opacity = 1.0; @@ -2297,6 +2299,11 @@ inline const ClientMachine *Window::clientMachine() const return m_clientMachine; } +inline quint64 Window::surfaceSerial() const +{ + return m_surfaceSerial; +} + inline quint32 Window::pendingSurfaceId() const { return m_pendingSurfaceId;