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.
This commit is contained in:
Levi Neely 2026-05-14 18:53:58 +02:00
parent 0801fb7d86
commit 81f60abca7
1 changed files with 54 additions and 9 deletions

View File

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