From aa294ed13c680da791bd9108f825f23841c0e2d7 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Mon, 30 Mar 2026 12:23:04 +0200 Subject: [PATCH] =?UTF-8?q?wayland:=20Phase=2020=20=E2=80=94=20real=20inpu?= =?UTF-8?q?t=20forwarding=20+=20root=20background?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/backend/wayland/wl_backend.c | 162 ++++++++++++++++++++++++++++--- 1 file changed, 147 insertions(+), 15 deletions(-) diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 81bfce7..fc460f2 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -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 */