From c03e582f0cf3a497ed8475a8cab675b2b6031013 Mon Sep 17 00:00:00 2001
From: Olivier Fourdan <ofourdan@redhat.com>
Date: Thu, 28 Apr 2022 12:26:49 +0200
Subject: [PATCH] xwayland: add (fake) device grab support

Add a new command line option "-host-grab" to disable the keyboard
shortcuts and confine the pointer on the host so that Xwayland can
receive all keyboard events.

This is useful when running a complete desktop environment within
Xwayland rootful.

Use [CTRL]+[SHIFT] to release the keyboard and pointer.

This option is not compatible with rootless mode.

Signed-off-by: Olivier Fourdan <ofourdan@redhat.com>
Reviewed-by: Adam Jackson <ajax@redhat.com>
---
 hw/xwayland/man/Xwayland.man  |  13 +++++
 hw/xwayland/meson.build       |   3 +
 hw/xwayland/xwayland-input.c  | 103 ++++++++++++++++++++++++++++++++++
 hw/xwayland/xwayland-screen.c |  15 +++++
 hw/xwayland/xwayland-screen.h |   4 ++
 hw/xwayland/xwayland.c        |   4 ++
 hw/xwayland/xwayland.pc.in    |   1 +
 7 files changed, 143 insertions(+)

diff --git a/hw/xwayland/man/Xwayland.man b/hw/xwayland/man/Xwayland.man
index 97e3c6957b..4e7877730f 100644
--- a/hw/xwayland/man/Xwayland.man
+++ b/hw/xwayland/man/Xwayland.man
@@ -61,6 +61,19 @@ This option is not compatible with rootless mode (\fI-rootless\fP).
 .B \-geometry \fIWxH\fP
 Sets the geometry of the \fIXwayland\fP window to \fIWxH\fP when running rootful.
 
+This option is not compatible with rootless mode (\fI-rootless\fP).
+.TP 8
+.B \-host-grab
+Disable host keyboard shorcuts and confine the pointer when running rootful.
+
+This feature relies on the protocol for inhibiting the compositor keyboard
+shortcuts and on the protocol for pointer locking and confinement and may
+have no effect if the Wayland compositor in use does not support these
+protocols.
+
+Use the keys [CTRL]+[SHIFT] simultaneously to release the keyboard and
+pointer devices.
+
 This option is not compatible with rootless mode (\fI-rootless\fP).
 .TP 8
 .B \-initfd \fIfd\fP
diff --git a/hw/xwayland/meson.build b/hw/xwayland/meson.build
index 37c39497c3..cd5bf7a530 100644
--- a/hw/xwayland/meson.build
+++ b/hw/xwayland/meson.build
@@ -46,6 +46,7 @@ dmabuf_xml = join_paths(protodir, 'unstable', 'linux-dmabuf', 'linux-dmabuf-unst
 viewporter_xml = join_paths(protodir, 'stable', 'viewporter', 'viewporter.xml')
 xdg_shell_xml = join_paths(protodir, 'stable', 'xdg-shell', 'xdg-shell.xml')
 drm_lease_xml = join_paths(protodir, 'staging', 'drm-lease', 'drm-lease-v1.xml')
+shortcuts_inhibit_xml = join_paths(protodir, 'unstable', 'keyboard-shortcuts-inhibit', 'keyboard-shortcuts-inhibit-unstable-v1.xml')
 
 client_header = generator(scanner,
     output : '@BASENAME@-client-protocol.h',
@@ -72,6 +73,7 @@ srcs += client_header.process(dmabuf_xml)
 srcs += client_header.process(viewporter_xml)
 srcs += client_header.process(xdg_shell_xml)
 srcs += client_header.process(drm_lease_xml)
+srcs += client_header.process(shortcuts_inhibit_xml)
 srcs += code.process(relative_xml)
 srcs += code.process(pointer_xml)
 srcs += code.process(gestures_xml)
@@ -82,6 +84,7 @@ srcs += code.process(dmabuf_xml)
 srcs += code.process(viewporter_xml)
 srcs += code.process(xdg_shell_xml)
 srcs += code.process(drm_lease_xml)
+srcs += code.process(shortcuts_inhibit_xml)
 
 xwayland_glamor = []
 eglstream_srcs = []
diff --git a/hw/xwayland/xwayland-input.c b/hw/xwayland/xwayland-input.c
index 177c573033..0cf6fb65fb 100644
--- a/hw/xwayland/xwayland-input.c
+++ b/hw/xwayland/xwayland-input.c
@@ -49,6 +49,7 @@
 #include "tablet-unstable-v2-client-protocol.h"
 #include "pointer-gestures-unstable-v1-client-protocol.h"
 #include "xwayland-keyboard-grab-unstable-v1-client-protocol.h"
+#include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
 
 struct axis_discrete_pending {
     struct xorg_list l;
@@ -128,6 +129,57 @@ init_pointer_buttons(DeviceIntPtr device)
     return TRUE;
 }
 
+static void
+maybe_fake_grab_devices(struct xwl_seat *xwl_seat)
+{
+    struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
+    struct xwl_window *xwl_window;
+
+    if (xwl_screen->rootless)
+        return;
+
+    if (!xwl_screen->host_grab)
+        return;
+
+    if (!xwl_screen->has_grab)
+        return;
+
+    if (!xwl_screen->screen->root)
+        return;
+
+    xwl_window = xwl_window_get(xwl_screen->screen->root);
+    if (!xwl_window)
+        return;
+
+    xwl_seat_confine_pointer(xwl_seat, xwl_window);
+
+    if (!xwl_screen->shortcuts_inhibit_manager)
+        return;
+
+    if (xwl_screen->shortcuts_inhibit)
+        return;
+
+    xwl_screen->shortcuts_inhibit =
+        zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts (
+            xwl_screen->shortcuts_inhibit_manager,
+            xwl_window->surface,
+            xwl_seat->seat);
+}
+
+static void
+maybe_fake_ungrab_devices(struct xwl_seat *xwl_seat)
+{
+    struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
+
+    xwl_seat_unconfine_pointer(xwl_seat);
+
+    if (!xwl_screen->shortcuts_inhibit)
+        return;
+
+    zwp_keyboard_shortcuts_inhibitor_v1_destroy (xwl_screen->shortcuts_inhibit);
+    xwl_screen->shortcuts_inhibit = NULL;
+}
+
 static int
 xwl_pointer_proc(DeviceIntPtr device, int what)
 {
@@ -520,6 +572,8 @@ pointer_handle_enter(void *data, struct wl_pointer *pointer,
     else {
         xwl_seat_maybe_lock_on_hidden_cursor(xwl_seat);
     }
+
+    maybe_fake_grab_devices(xwl_seat);
 }
 
 static void
@@ -539,6 +593,8 @@ pointer_handle_leave(void *data, struct wl_pointer *pointer,
         xwl_seat->focus_window = NULL;
         CheckMotion(NULL, GetMaster(dev, POINTER_OR_FLOAT));
     }
+
+    maybe_fake_ungrab_devices(xwl_seat);
 }
 
 static void
@@ -927,6 +983,34 @@ static const struct zwp_pointer_gesture_pinch_v1_listener pointer_gesture_pinch_
     pointer_gesture_pinch_handle_end
 };
 
+static void
+maybe_toggle_fake_grab(struct xwl_seat *xwl_seat, uint32_t key)
+{
+    struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
+    XkbStateRec state_rec;
+    uint32_t xkb_state;
+
+    if (xwl_screen->rootless)
+        return;
+
+    if (!xwl_screen->host_grab)
+        return;
+
+    state_rec = xwl_seat->keyboard->key->xkbInfo->state;
+    xkb_state = (XkbStateFieldFromRec(&state_rec) & 0xff);
+
+    if (((key == KEY_LEFTSHIFT || key == KEY_RIGHTSHIFT) && (xkb_state & ControlMask)) ||
+        ((key == KEY_LEFTCTRL || key == KEY_RIGHTCTRL) && (xkb_state & ShiftMask))) {
+
+        xwl_screen->has_grab = !xwl_screen->has_grab;
+
+        if (xwl_screen->has_grab)
+            maybe_fake_grab_devices(xwl_seat);
+        else
+            maybe_fake_ungrab_devices(xwl_seat);
+    }
+}
+
 static void
 keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial,
                     uint32_t time, uint32_t key, uint32_t state)
@@ -949,6 +1033,9 @@ keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial,
 
     QueueKeyboardEvents(xwl_seat->keyboard,
                         state ? KeyPress : KeyRelease, key + 8);
+
+    if (!state)
+        maybe_toggle_fake_grab(xwl_seat, key);
 }
 
 static void
@@ -1009,6 +1096,8 @@ keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
     wl_array_copy(&xwl_seat->keys, keys);
     wl_array_for_each(k, &xwl_seat->keys)
         QueueKeyboardEvents(xwl_seat->keyboard, EnterNotify, *k + 8);
+
+    maybe_fake_grab_devices(xwl_seat);
 }
 
 static void
@@ -1024,6 +1113,8 @@ keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
         QueueKeyboardEvents(xwl_seat->keyboard, LeaveNotify, *k + 8);
 
     xwl_seat->keyboard_focus = NULL;
+
+    maybe_fake_ungrab_devices(xwl_seat);
 }
 
 static void
@@ -2824,6 +2915,16 @@ init_keyboard_grab(struct xwl_screen *xwl_screen,
     }
 }
 
+static void
+init_keyboard_shortcuts_inhibit(struct xwl_screen *xwl_screen,
+                                uint32_t id, uint32_t version)
+{
+    xwl_screen->shortcuts_inhibit_manager =
+         wl_registry_bind(xwl_screen->registry, id,
+                          &zwp_keyboard_shortcuts_inhibit_manager_v1_interface,
+                          1);
+}
+
 /* The compositor may send us wl_seat and its capabilities before sending e.g.
    relative_pointer_manager or pointer_gesture interfaces. This would result in
    devices being created in capabilities handler, but listeners not, because
@@ -2873,6 +2974,8 @@ input_handler(void *data, struct wl_registry *registry, uint32_t id,
         init_tablet_manager(xwl_screen, id, version);
     } else if (strcmp(interface, "zwp_xwayland_keyboard_grab_manager_v1") == 0) {
         init_keyboard_grab(xwl_screen, id, version);
+    } else if (strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0) {
+        init_keyboard_shortcuts_inhibit(xwl_screen, id, version);
     }
 }
 
diff --git a/hw/xwayland/xwayland-screen.c b/hw/xwayland/xwayland-screen.c
index 5261125143..a69fc4dc70 100644
--- a/hw/xwayland/xwayland-screen.c
+++ b/hw/xwayland/xwayland-screen.c
@@ -306,6 +306,12 @@ xwl_cursor_confined_to(DeviceIntPtr device,
     struct xwl_seat *xwl_seat = device->public.devicePrivate;
     struct xwl_window *xwl_window;
 
+    /* If running rootful with host grab requested, do not tamper with
+     * pointer confinement.
+     */
+    if (!xwl_screen->rootless && xwl_screen->host_grab && xwl_screen->has_grab)
+        return;
+
     if (!xwl_seat)
         xwl_seat = xwl_screen_get_default_seat(xwl_screen);
 
@@ -679,6 +685,10 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
         else if (strcmp(argv[i], "-fullscreen") == 0) {
             xwl_screen->fullscreen = 1;
         }
+        else if (strcmp(argv[i], "-host-grab") == 0) {
+            xwl_screen->host_grab = 1;
+            xwl_screen->has_grab = 1;
+        }
     }
 
     if (use_fixed_size) {
@@ -745,6 +755,11 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
         xwl_screen->fullscreen = FALSE;
     }
 
+    if (xwl_screen->host_grab && xwl_screen->rootless) {
+        ErrorF("error, cannot use host grab when running rootless\n");
+        return FALSE;
+    }
+
     if (!xwl_screen->rootless && !xwl_screen->xdg_wm_base) {
         ErrorF("missing XDG-WM-Base protocol\n");
         return FALSE;
diff --git a/hw/xwayland/xwayland-screen.h b/hw/xwayland/xwayland-screen.h
index 360e2aa855..6ddb6f7519 100644
--- a/hw/xwayland/xwayland-screen.h
+++ b/hw/xwayland/xwayland-screen.h
@@ -59,6 +59,8 @@ struct xwl_screen {
     int present;
     int force_xrandr_emulation;
     int fullscreen;
+    int host_grab;
+    int has_grab;
 
     CreateScreenResourcesProcPtr CreateScreenResources;
     CloseScreenProcPtr CloseScreen;
@@ -88,6 +90,8 @@ struct xwl_screen {
     struct zwp_pointer_constraints_v1 *pointer_constraints;
     struct zwp_pointer_gestures_v1 *pointer_gestures;
     struct zwp_xwayland_keyboard_grab_manager_v1 *wp_grab;
+    struct zwp_keyboard_shortcuts_inhibit_manager_v1 *shortcuts_inhibit_manager;
+    struct zwp_keyboard_shortcuts_inhibitor_v1 *shortcuts_inhibit;
     struct zwp_linux_dmabuf_v1 *dmabuf;
     struct zxdg_output_manager_v1 *xdg_output_manager;
     struct wp_viewporter *viewporter;
diff --git a/hw/xwayland/xwayland.c b/hw/xwayland/xwayland.c
index 8fb929eeb6..e4983ed1e9 100644
--- a/hw/xwayland/xwayland.c
+++ b/hw/xwayland/xwayland.c
@@ -93,6 +93,7 @@ ddxUseMsg(void)
     ErrorF("-rootless              run rootless, requires wm support\n");
     ErrorF("-fullscreen            run fullscreen when rootful\n");
     ErrorF("-geometry WxH          set Xwayland window size when rootful\n");
+    ErrorF("-host-grab             disable host keyboard shortcuts when rootful\n");
     ErrorF("-wm fd                 create X client for wm on given fd\n");
     ErrorF("-initfd fd             add given fd as a listen socket for initialization clients\n");
     ErrorF("-listenfd fd           add given fd as a listen socket\n");
@@ -233,6 +234,9 @@ ddxProcessArgument(int argc, char *argv[], int i)
     else if (strcmp(argv[i], "-fullscreen") == 0) {
         return 1;
     }
+    else if (strcmp(argv[i], "-host-grab") == 0) {
+        return 1;
+    }
 
     return 0;
 }
diff --git a/hw/xwayland/xwayland.pc.in b/hw/xwayland/xwayland.pc.in
index 5976bd6b14..70db37db75 100644
--- a/hw/xwayland/xwayland.pc.in
+++ b/hw/xwayland/xwayland.pc.in
@@ -15,3 +15,4 @@ have_no_touch_pointer_emulation=true
 have_force_xrandr_emulation=true
 have_geometry=true
 have_fullscreen=true
+have_host_grab=true
-- 
GitLab