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
This commit is contained in:
Vlad Zahorodnii 2022-10-04 14:56:45 +03:00
parent 909378e6fd
commit fdea693da5
9 changed files with 278 additions and 3 deletions

View file

@ -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"))

View file

@ -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;

View file

@ -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 <KDecoration2/Decoration>
@ -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())) {

View file

@ -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")

View file

@ -0,0 +1,165 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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<XwaylandSurfaceV1Interface *> 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<uint64_t> 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<uint64_t> XwaylandSurfaceV1Interface::serial() const
{
return d->current.serial;
}
} // namespace KWaylandServer

View file

@ -0,0 +1,59 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include <kwin_export.h>
#include <QObject>
#include <memory>
#include <optional>
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<uint64_t> serial() const;
private:
std::unique_ptr<XwaylandSurfaceV1InterfacePrivate> 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<XwaylandShellV1InterfacePrivate> d;
};
} // namespace KWaylandServer

View file

@ -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<QByteArray> inputmethodInterfaces = {"zwp_input_panel_v1", "zwp_input_method_v1"};
const QSet<QByteArray> xwaylandInterfaces = {
QByteArrayLiteral("zwp_xwayland_keyboard_grab_manager_v1"),
QByteArrayLiteral("xwayland_shell_v1"),
};
QSet<QString> 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);

View file

@ -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<Window *> m_windows;
InitializationFlags m_initFlags;
QHash<Output *, KWaylandServer::OutputInterface *> m_waylandOutputs;

View file

@ -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<KWaylandServer::SurfaceInterface> 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;