From 3f7b457f873411a9d2924d7d46a06e03806cd5e7 Mon Sep 17 00:00:00 2001 From: Yiin Date: Wed, 11 Feb 2026 07:00:56 +0200 Subject: [PATCH] Fix segfault when pointer/keyboard events arrive during init Move cursor surface creation and cursor theme loading before the second wl_display_roundtrip(), which is the first point where the compositor can dispatch pointer_enter or keyboard events to slurp via the newly-created layer surfaces. Previously, cursor_surface was created after the roundtrip, so pointer_handle_enter could call wl_surface_set_buffer_scale() or wp_cursor_shape_manager_v1_get_pointer() with a NULL surface. Similarly, keyboard_handle_key and keyboard_handle_modifiers could dereference NULL xkb_state if a keyboard event arrived before the keymap event. This race is especially pronounced on setups with many outputs (e.g. headless virtual monitors), where the roundtrip takes longer due to output enumeration, widening the window for events to arrive before initialization completes. Also add defensive NULL checks in the affected handlers as a safety net. Fixes: https://github.com/emersion/slurp/issues/181 --- main.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/main.c b/main.c index 84a2e63..e5ec27d 100644 --- a/main.c +++ b/main.c @@ -149,7 +149,7 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, wp_cursor_shape_device_v1_set_shape(device, serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR); wp_cursor_shape_device_v1_destroy(device); - } else { + } else if (seat->cursor_surface) { wl_surface_set_buffer_scale(seat->cursor_surface, output->scale); wl_surface_attach(seat->cursor_surface, wl_cursor_image_get_buffer(output->cursor_image), 0, 0); @@ -311,6 +311,9 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, const uint32_t serial, const uint32_t time, const uint32_t key, const uint32_t key_state) { struct slurp_seat *seat = data; + if (seat->xkb_state == NULL) { + return; + } struct slurp_state *state = seat->state; const xkb_keysym_t keysym = xkb_state_key_get_one_sym(seat->xkb_state, key + 8); @@ -359,6 +362,9 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboar const uint32_t mods_latched, const uint32_t mods_locked, const uint32_t group) { struct slurp_seat *seat = data; + if (seat->xkb_state == NULL) { + return; + } xkb_state_update_mask(seat->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); } @@ -1070,13 +1076,23 @@ int main(int argc, char *argv[]) { zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1); wl_surface_commit(output->surface); } - // second roundtrip for xdg-output - wl_display_roundtrip(state.display); - + // Create cursor surfaces and load cursor themes before the second + // roundtrip, which may dispatch pointer/keyboard events via the + // newly-created layer surfaces. Without this, handlers can + // dereference NULL cursor_surface or xkb_state pointers. if (!state.cursor_shape_manager && !create_cursors(&state)) { return EXIT_FAILURE; } + struct slurp_seat *seat; + wl_list_for_each(seat, &state.seats, link) { + seat->cursor_surface = + wl_compositor_create_surface(state.compositor); + } + + // second roundtrip for xdg-output + wl_display_roundtrip(state.display); + if (output_boxes) { struct slurp_output *box_output; wl_list_for_each(box_output, &state.outputs, link) { @@ -1084,12 +1100,6 @@ int main(int argc, char *argv[]) { } } - struct slurp_seat *seat; - wl_list_for_each(seat, &state.seats, link) { - seat->cursor_surface = - wl_compositor_create_surface(state.compositor); - } - state.running = true; while (state.running && wl_display_dispatch(state.display) != -1) { // This space intentionally left blank