wayland: Phase 20 — real input forwarding + root background

In nested mode the wlr_wl_backend creates virtual pointer/keyboard devices
that mirror the host compositor's seat.  Wire handle_backend_new_input on
backend->events.new_input (before wlr_backend_start) to capture these real
devices and add separate wl_listener structs for their key/modifier/button/
motion_absolute/frame events.  At the end of wl_display_open, switch the
seat to the real keyboard so focus notifications carry the correct keymap
and modifier state from the host.

handle_keyboard_key / handle_keyboard_modifiers updated to use
seat->keyboard_state.keyboard instead of wl_state.keyboard_dev so the
correct modifier state is used regardless of which keyboard fired the event.

Add a grey wlr_scene_rect root background in handle_backend_new_output so
the compositor output shows a proper desktop instead of solid black.  The
rect is inserted first in the scene tree (always below client surfaces).

Pointer bounds clamping in handle_pointer_motion, handle_pointer_motion_-
absolute, wl_pointer_warp and wl_pointer_warp_to updated to use actual
output dimensions instead of hardcoded 1920x1079.

All 6 new wl_listener nodes removed in wl_display_close.  Smoke test
22/22 PASS, exit 0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-30 12:23:04 +02:00
parent 177e13f995
commit aa294ed13c
1 changed files with 147 additions and 15 deletions

View File

@ -72,6 +72,12 @@
* compositing client surfaces; wlr_scene_output_commit renders each frame.
* A per-view scene node is created for each XDG/XWayland surface and kept
* in sync with the WM-assigned position.
* Phase 20: real input device forwarding for nested mode.
* handle_backend_new_input wires listeners for the host compositor's real
* pointer/keyboard devices so key presses and mouse motion are forwarded to
* focused Wayland clients. A grey wlr_scene_rect root background is added
* so the compositor output shows a proper desktop instead of solid black.
* Pointer bounds clamping updated to use actual output dimensions.
*/
#ifdef HAVE_CONFIG_H
@ -210,6 +216,15 @@ struct wl_compositor_state {
/* Phase 17: XWayland server for X11 client support */
struct wlr_xwayland *xwayland;
struct wl_listener xwayland_new_surface_listener;
/* Phase 20: real input devices from nested backend + their listeners */
struct wlr_input_device *real_keyboard_dev;
struct wlr_input_device *real_pointer_dev;
struct wl_listener backend_new_input_listener;
struct wl_listener real_keyboard_key_listener;
struct wl_listener real_keyboard_modifiers_listener;
struct wl_listener real_pointer_motion_abs_listener;
struct wl_listener real_pointer_button_listener;
struct wl_listener real_pointer_frame_listener;
/* Phase 18: nested Wayland + wlr_scene */
int is_nested; /* 1 if using wlr_wl_backend */
struct wlr_backend *headless_inner; /* headless backend (always) */
@ -282,6 +297,8 @@ static void wl_pointer_warp_to(int x, int y);
static void handle_pointer_motion(struct wl_listener *listener, void *data);
/* Phase 17: XWayland new surface forward declaration. */
static void handle_xwayland_new_surface(struct wl_listener *listener, void *data);
/* Phase 20: nested backend real input device forward declaration. */
static void handle_backend_new_input(struct wl_listener *listener, void *data);
/* Phase 18: output lifecycle and frame rendering forward declarations. */
static void handle_backend_new_output(struct wl_listener *listener, void *data);
static void handle_output_frame(struct wl_listener *listener, void *data);
@ -816,10 +833,17 @@ handle_keyboard_key(struct wl_listener *listener, void *data)
struct wlr_event_keyboard_key *event = data;
(void)listener;
if (!wl_state.seat || !wl_state.keyboard_dev || !wl_state.keyboard_dev->keyboard)
if (!wl_state.seat)
return;
struct wlr_keyboard *kb = wl_state.keyboard_dev->keyboard;
/* Use the seat's active keyboard — in headless mode that is the headless
* keyboard; in nested mode it is the real host keyboard (updated at the
* end of wl_display_open after handle_backend_new_input runs). */
struct wlr_keyboard *kb = wl_state.seat->keyboard_state.keyboard;
if (!kb) {
if (!wl_state.keyboard_dev || !wl_state.keyboard_dev->keyboard) return;
kb = wl_state.keyboard_dev->keyboard;
}
/* Update modifier state */
wlr_seat_keyboard_notify_modifiers(wl_state.seat, &kb->modifiers);
@ -842,12 +866,76 @@ static void
handle_keyboard_modifiers(struct wl_listener *listener, void *data)
{
(void)listener; (void)data;
if (!wl_state.seat || !wl_state.keyboard_dev || !wl_state.keyboard_dev->keyboard)
return;
struct wlr_keyboard *kb = wl_state.keyboard_dev->keyboard;
if (!wl_state.seat) return;
struct wlr_keyboard *kb = wl_state.seat->keyboard_state.keyboard;
if (!kb) {
if (!wl_state.keyboard_dev || !wl_state.keyboard_dev->keyboard) return;
kb = wl_state.keyboard_dev->keyboard;
}
wlr_seat_keyboard_notify_modifiers(wl_state.seat, &kb->modifiers);
}
/* ------------------------------------------------------------------ */
/* Phase 20: real input device forwarding for nested mode */
/* ------------------------------------------------------------------ */
/*
* handle_backend_new_input — fired for every input device added to any
* sub-backend. Headless synthetic devices (which never generate events)
* are skipped; the explicit setup code in wl_display_open handles those.
* Real devices from wlr_wl_backend (the nested compositor path) carry
* events from the host compositor's seat and must be wired so key presses
* and mouse motion reach focused Wayland clients.
*/
static void
handle_backend_new_input(struct wl_listener *listener, void *data)
{
struct wlr_input_device *dev = data;
(void)listener;
/* Skip headless synthetic devices — they never generate events. */
if (wlr_input_device_is_headless(dev))
return;
/* Real device from the nested Wayland backend. */
switch (dev->type) {
case WLR_INPUT_DEVICE_KEYBOARD:
if (!dev->keyboard) break;
/* wlroots' nested backend auto-receives the host keymap via the
* wl_keyboard.keymap event — no need to set XKB manually here. */
wl_state.real_keyboard_dev = dev;
wl_state.real_keyboard_key_listener.notify = handle_keyboard_key;
wl_signal_add(&dev->keyboard->events.key,
&wl_state.real_keyboard_key_listener);
wl_state.real_keyboard_modifiers_listener.notify = handle_keyboard_modifiers;
wl_signal_add(&dev->keyboard->events.modifiers,
&wl_state.real_keyboard_modifiers_listener);
wwarning("wl_backend: real keyboard wired from nested backend");
break;
case WLR_INPUT_DEVICE_POINTER:
if (!dev->pointer) break;
/* Nested backend delivers absolute motion events (host cursor
* position normalised to [0,1]); no relative motion needed. */
wl_state.real_pointer_dev = dev;
wl_state.real_pointer_motion_abs_listener.notify =
handle_pointer_motion_absolute;
wl_signal_add(&dev->pointer->events.motion_absolute,
&wl_state.real_pointer_motion_abs_listener);
wl_state.real_pointer_button_listener.notify = handle_pointer_button;
wl_signal_add(&dev->pointer->events.button,
&wl_state.real_pointer_button_listener);
wl_state.real_pointer_frame_listener.notify = handle_pointer_frame;
wl_signal_add(&dev->pointer->events.frame,
&wl_state.real_pointer_frame_listener);
wwarning("wl_backend: real pointer wired from nested backend");
break;
default:
break;
}
}
/* ------------------------------------------------------------------ */
/* Phase 18: output frame rendering and backend new-output handler */
/* ------------------------------------------------------------------ */
@ -930,6 +1018,18 @@ handle_backend_new_output(struct wl_listener *listener, void *data)
wl_signal_add(&output->events.frame, &wl_state.output_frame_listener);
wwarning("wl_backend: scene output created for %s (%dx%d)",
output->name, output->width, output->height);
/* Phase 20: root background rectangle.
* Inserted first in the scene tree so it is always below client
* surfaces. WindowMaker grey (0.35, 0.35, 0.35) matches the
* classic WindowMaker desktop colour. */
{
float bg[4] = { 0.35f, 0.35f, 0.35f, 1.0f };
wlr_scene_rect_create(&wl_state.scene->node,
(int)output->width, (int)output->height, bg);
wwarning("wl_backend: root background created (%dx%d, grey)",
(int)output->width, (int)output->height);
}
}
wwarning("wl_backend: new output %s (%dx%d)",
@ -1044,6 +1144,14 @@ wl_display_open(const char *name)
else
wwarning("wl_backend: wlr_scene_create failed (no compositing)");
/* Phase 20: wire new_input BEFORE starting the backend so that real
* input devices from the nested Wayland backend (fired during
* wlr_backend_start) are captured before the headless devices are
* added explicitly below. */
wl_state.backend_new_input_listener.notify = handle_backend_new_input;
wl_signal_add(&wl_state.backend->events.new_input,
&wl_state.backend_new_input_listener);
/* Wire new_output BEFORE starting the backend. */
wl_state.backend_new_output_listener.notify = handle_backend_new_output;
wl_signal_add(&wl_state.backend->events.new_output,
@ -1252,6 +1360,15 @@ wl_display_open(const char *name)
wwarning("wl_backend: shutdown pipe creation failed; SIGTERM may crash");
}
/* Phase 20: if a real keyboard was found from the nested backend
* (captured in handle_backend_new_input during wlr_backend_start),
* switch the seat to it now so keyboard focus notifications use the
* correct keymap and modifier state from the host compositor. This
* must happen after the headless keyboard setup above (which may have
* called wlr_seat_set_keyboard with the headless device). */
if (wl_state.real_keyboard_dev)
wlr_seat_set_keyboard(wl_state.seat, wl_state.real_keyboard_dev);
wl_state.started = 1;
return NULL; /* no X11 Display* in the Wayland path */
}
@ -1349,6 +1466,13 @@ wl_display_close(void)
WL_LISTENER_REMOVE(wl_state.pointer_motion_abs_listener);
WL_LISTENER_REMOVE(wl_state.pointer_button_listener);
WL_LISTENER_REMOVE(wl_state.pointer_frame_listener);
/* Phase 20: backend new_input listener and real device listeners */
WL_LISTENER_REMOVE(wl_state.backend_new_input_listener);
WL_LISTENER_REMOVE(wl_state.real_keyboard_key_listener);
WL_LISTENER_REMOVE(wl_state.real_keyboard_modifiers_listener);
WL_LISTENER_REMOVE(wl_state.real_pointer_motion_abs_listener);
WL_LISTENER_REMOVE(wl_state.real_pointer_button_listener);
WL_LISTENER_REMOVE(wl_state.real_pointer_frame_listener);
#undef WL_LISTENER_REMOVE
/* Skip wl_display_destroy() and wl_display_destroy_clients().
@ -1592,12 +1716,14 @@ handle_pointer_motion(struct wl_listener *listener, void *data)
{
(void)listener;
struct wlr_event_pointer_motion *ev = data;
int ow = wl_state.output ? (int)wl_state.output->width : 1920;
int oh = wl_state.output ? (int)wl_state.output->height : 1080;
wl_state.pointer_x += (int)ev->delta_x;
wl_state.pointer_y += (int)ev->delta_y;
if (wl_state.pointer_x < 0) wl_state.pointer_x = 0;
if (wl_state.pointer_y < 0) wl_state.pointer_y = 0;
if (wl_state.pointer_x > 1919) wl_state.pointer_x = 1919;
if (wl_state.pointer_y > 1079) wl_state.pointer_y = 1079;
if (wl_state.pointer_x >= ow) wl_state.pointer_x = ow - 1;
if (wl_state.pointer_y >= oh) wl_state.pointer_y = oh - 1;
wl_update_pointer_focus(ev->time_msec);
}
@ -1606,12 +1732,14 @@ handle_pointer_motion_absolute(struct wl_listener *listener, void *data)
{
(void)listener;
struct wlr_event_pointer_motion_absolute *ev = data;
wl_state.pointer_x = (int)(ev->x * 1920);
wl_state.pointer_y = (int)(ev->y * 1080);
int ow = wl_state.output ? (int)wl_state.output->width : 1920;
int oh = wl_state.output ? (int)wl_state.output->height : 1080;
wl_state.pointer_x = (int)(ev->x * (double)ow);
wl_state.pointer_y = (int)(ev->y * (double)oh);
if (wl_state.pointer_x < 0) wl_state.pointer_x = 0;
if (wl_state.pointer_y < 0) wl_state.pointer_y = 0;
if (wl_state.pointer_x > 1919) wl_state.pointer_x = 1919;
if (wl_state.pointer_y > 1079) wl_state.pointer_y = 1079;
if (wl_state.pointer_x >= ow) wl_state.pointer_x = ow - 1;
if (wl_state.pointer_y >= oh) wl_state.pointer_y = oh - 1;
wl_update_pointer_focus(ev->time_msec);
}
@ -1740,12 +1868,14 @@ static void
wl_pointer_warp(int dx, int dy)
{
/* Phase 54: relative warp — clamp to output bounds. */
int ow = wl_state.output ? (int)wl_state.output->width : 1920;
int oh = wl_state.output ? (int)wl_state.output->height : 1080;
wl_state.pointer_x += dx;
wl_state.pointer_y += dy;
if (wl_state.pointer_x < 0) wl_state.pointer_x = 0;
if (wl_state.pointer_y < 0) wl_state.pointer_y = 0;
if (wl_state.pointer_x > 1919) wl_state.pointer_x = 1919;
if (wl_state.pointer_y > 1079) wl_state.pointer_y = 1079;
if (wl_state.pointer_x >= ow) wl_state.pointer_x = ow - 1;
if (wl_state.pointer_y >= oh) wl_state.pointer_y = oh - 1;
/* Phase 13: update pointer focus after relative warp */
wl_update_pointer_focus(0);
}
@ -1754,10 +1884,12 @@ static void
wl_pointer_warp_to(int x, int y)
{
/* Phase 54: absolute warp — clamp to output bounds. */
int ow = wl_state.output ? (int)wl_state.output->width : 1920;
int oh = wl_state.output ? (int)wl_state.output->height : 1080;
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x > 1919) x = 1919;
if (y > 1079) y = 1079;
if (x >= ow) x = ow - 1;
if (y >= oh) y = oh - 1;
wl_state.pointer_x = x;
wl_state.pointer_y = y;
/* Phase 13: update pointer focus after absolute warp */