From 81f60abca78f2d137e8d40854f224f575d3f433b Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Thu, 14 May 2026 18:53:58 +0200 Subject: [PATCH] wl_backend: safely re-wire input listeners on VT switch When the session resumes after a VT switch, wlroots may destroy and recreate input devices. Remove old listeners before adding new ones to avoid assertion failure in wlr_keyboard_finish. Initialize all listener links at startup so the first remove is safe. --- src/backend/wayland/wl_backend.c | 63 +++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 8509029..da1e83b 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -380,6 +380,7 @@ struct wl_compositor_state { 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_keyboard_destroy_listener; struct wl_listener real_pointer_motion_abs_listener; struct wl_listener real_pointer_button_listener; struct wl_listener real_pointer_frame_listener; @@ -475,6 +476,7 @@ struct wl_compositor_state { /* Phase 29 axis listeners (headless pointer + real nested pointer). */ struct wl_listener pointer_axis_listener; struct wl_listener real_pointer_axis_listener; + struct wl_listener real_pointer_destroy_listener; /* Phase 30: WM dock panel (native Wayland rendering via wm_shm_surface). */ struct wm_shm_surface *dock_surface; /* NULL until screen_create_internals */ int dock_width; /* pixels reserved on right edge */ @@ -1749,6 +1751,40 @@ handle_keyboard_modifiers(struct wl_listener *listener, void *data) * events from the host compositor's seat and must be wired so key presses * and mouse motion reach focused Wayland clients. */ +static void +handle_keyboard_destroy(struct wl_listener *listener, void *data) +{ + (void)listener; (void)data; + wl_list_remove(&wl_state.real_keyboard_key_listener.link); + wl_list_remove(&wl_state.real_keyboard_modifiers_listener.link); + wl_list_remove(&wl_state.real_keyboard_destroy_listener.link); + wl_list_init(&wl_state.real_keyboard_key_listener.link); + wl_list_init(&wl_state.real_keyboard_modifiers_listener.link); + wl_list_init(&wl_state.real_keyboard_destroy_listener.link); + wl_state.real_keyboard_dev = NULL; + wl_state.keyboard_dev = NULL; +} + +static void +handle_pointer_destroy(struct wl_listener *listener, void *data) +{ + (void)listener; (void)data; + wl_list_remove(&wl_state.pointer_motion_listener.link); + wl_list_remove(&wl_state.real_pointer_motion_abs_listener.link); + wl_list_remove(&wl_state.real_pointer_button_listener.link); + wl_list_remove(&wl_state.real_pointer_frame_listener.link); + wl_list_remove(&wl_state.real_pointer_axis_listener.link); + wl_list_remove(&wl_state.real_pointer_destroy_listener.link); + wl_list_init(&wl_state.pointer_motion_listener.link); + wl_list_init(&wl_state.real_pointer_motion_abs_listener.link); + wl_list_init(&wl_state.real_pointer_button_listener.link); + wl_list_init(&wl_state.real_pointer_frame_listener.link); + wl_list_init(&wl_state.real_pointer_axis_listener.link); + wl_list_init(&wl_state.real_pointer_destroy_listener.link); + wl_state.real_pointer_dev = NULL; + wl_state.pointer_dev = NULL; +} + static void handle_backend_new_input(struct wl_listener *listener, void *data) { @@ -1761,13 +1797,10 @@ handle_backend_new_input(struct wl_listener *listener, void *data) switch (dev->type) { case WLR_INPUT_DEVICE_KEYBOARD: { struct wlr_keyboard *kb = wlr_keyboard_from_input_device(dev); - /* Apply default keymap only if the host hasn't provided one yet */ if (wl_state.xkb_keymap && !kb->xkb_state) wlr_keyboard_set_keymap(kb, wl_state.xkb_keymap); wl_state.real_keyboard_dev = dev; wl_state.keyboard_dev = dev; - /* Seat may not exist yet if new_input fires during wlr_backend_start() - * before wlr_seat_create() is called later in wl_display_open(). */ if (wl_state.seat) wlr_seat_set_keyboard(wl_state.seat, kb); wl_state.real_keyboard_key_listener.notify = handle_keyboard_key; @@ -1776,18 +1809,19 @@ handle_backend_new_input(struct wl_listener *listener, void *data) wl_state.real_keyboard_modifiers_listener.notify = handle_keyboard_modifiers; wl_signal_add(&kb->events.modifiers, &wl_state.real_keyboard_modifiers_listener); - wwarning("wl_backend: real keyboard wired from nested backend"); + wl_state.real_keyboard_destroy_listener.notify = handle_keyboard_destroy; + wl_signal_add(&dev->events.destroy, + &wl_state.real_keyboard_destroy_listener); + wwarning("wl_backend: real keyboard wired"); break; } case WLR_INPUT_DEVICE_POINTER: { struct wlr_pointer *ptr = wlr_pointer_from_input_device(dev); wl_state.real_pointer_dev = dev; wl_state.pointer_dev = dev; - /* Relative motion (libinput / standalone DRM) */ wl_state.pointer_motion_listener.notify = handle_pointer_motion; wl_signal_add(&ptr->events.motion, &wl_state.pointer_motion_listener); - /* Absolute motion (nested Wayland backend) */ wl_state.real_pointer_motion_abs_listener.notify = handle_pointer_motion_absolute; wl_signal_add(&ptr->events.motion_absolute, @@ -1801,6 +1835,9 @@ handle_backend_new_input(struct wl_listener *listener, void *data) wl_state.real_pointer_axis_listener.notify = handle_pointer_axis; wl_signal_add(&ptr->events.axis, &wl_state.real_pointer_axis_listener); + wl_state.real_pointer_destroy_listener.notify = handle_pointer_destroy; + wl_signal_add(&dev->events.destroy, + &wl_state.real_pointer_destroy_listener); wwarning("wl_backend: real pointer wired"); break; } @@ -2029,9 +2066,17 @@ wl_display_open(const char *name) (void)name; /* Wayland compositor picks its own socket name */ - /* Make Xlib thread-safe before any X11 call. The startup pump thread - * runs concurrently with the main thread during wScreenInit(); without - * XInitThreads() any concurrent Xlib access is undefined behaviour. */ + /* Initialize listener links so wl_list_remove is safe before first add */ + wl_list_init(&wl_state.real_keyboard_key_listener.link); + wl_list_init(&wl_state.real_keyboard_modifiers_listener.link); + wl_list_init(&wl_state.real_keyboard_destroy_listener.link); + wl_list_init(&wl_state.pointer_motion_listener.link); + wl_list_init(&wl_state.real_pointer_motion_abs_listener.link); + wl_list_init(&wl_state.real_pointer_button_listener.link); + wl_list_init(&wl_state.real_pointer_frame_listener.link); + wl_list_init(&wl_state.real_pointer_axis_listener.link); + wl_list_init(&wl_state.real_pointer_destroy_listener.link); + XInitThreads(); /* Install X error handler immediately: Mesa/EGL may open a connection to