From 7ef26f9a924923448626a565b3062d5a8ea67667 Mon Sep 17 00:00:00 2001 From: hack-heart <43978612+hack-heart@users.noreply.github.com> Date: Sun, 1 Mar 2026 02:18:28 -0800 Subject: [PATCH 1/2] guard seat_get_focus() NULL dereferences seat_get_focus() can return NULL when the focus stack is empty. a few call sites don't check for this and just dereference the result, which crashes sway. this happens when a client disconnects abruptly, leaving the seat with a stale has_focus flag or empty focus stack. whatever touches focus next dereferences NULL. the two confirmed crash sites were seat_set_workspace_focus and seat_set_focus_surface (coredumps below). seat_unfocus_unless_client has the same pattern so a check was added there too for safety. cannot remember how this one was triggered: ``` seat_send_unfocus () seat_set_workspace_focus () seat_set_focus () wl_signal_emit_mutable () container_begin_destroy () view_unmap () handle_unmap () wl_signal_emit_mutable () wlr_surface_unmap () destroy_xdg_toplevel () destroy_xdg_surface_role_object () destroy_xdg_surface () xdg_client_handle_resource_destroy () wl_client_destroy () ``` mouse click on empty container (clicked wallpaper, likely just after a wallpaper engine crash): ``` seat_send_unfocus () seat_set_workspace_focus () seat_set_focus () handle_button () wl_signal_emit_mutable () handle_pointer_button () handle_libinput_readable () wl_event_loop_dispatch () ``` IPC workspace switch (with noctalia workspace widget): ``` seat_send_unfocus () seat_set_workspace_focus () seat_set_focus () workspace_switch () cmd_workspace () execute_command () ipc_client_handle_command () ipc_client_handle_readable () wl_event_loop_dispatch () ``` layer surface teardown (restarted noctalia shell): ``` seat_send_unfocus () seat_set_focus_surface () seat_set_focus_layer () handle_node_destroy () wl_signal_emit_mutable () sway_scene_node_destroy () layer_surface_destroy () surface_handle_role_resource_destroy () ``` --- sway/input/seat.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sway/input/seat.c b/sway/input/seat.c index ab31b6746..9709f8c57 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -1136,10 +1136,12 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n if (node == NULL) { // Close any popups on the old focus - if (node_is_view(last_focus)) { - view_close_popups(last_focus->sway_container->view); + if (last_focus) { + if (node_is_view(last_focus)) { + view_close_popups(last_focus->sway_container->view); + } + seat_send_unfocus(last_focus, seat); } - seat_send_unfocus(last_focus, seat); sway_input_method_relay_set_focus(&seat->im_relay, NULL); seat->has_focus = false; return; @@ -1291,7 +1293,9 @@ void seat_set_focus_surface(struct sway_seat *seat, struct wlr_surface *surface, bool unfocus) { if (seat->has_focus && unfocus) { struct sway_node *focus = seat_get_focus(seat); - seat_send_unfocus(focus, seat); + if (focus) { + seat_send_unfocus(focus, seat); + } seat->has_focus = false; } @@ -1340,7 +1344,7 @@ void seat_unfocus_unless_client(struct sway_seat *seat, struct wl_client *client } if (seat->has_focus) { struct sway_node *focus = seat_get_focus(seat); - if (node_is_view(focus) && wl_resource_get_client( + if (focus && node_is_view(focus) && wl_resource_get_client( focus->sway_container->view->surface->resource) != client) { seat_set_focus(seat, NULL); } From 8420113af5fb82ac2d42253c7fd9e998d4e3eb16 Mon Sep 17 00:00:00 2001 From: hack-heart <43978612+hack-heart@users.noreply.github.com> Date: Tue, 10 Mar 2026 00:09:37 -0700 Subject: [PATCH 2/2] set has_focus to false when focus is NULL Set has_focus to false in seat_unfocus_unless_client when seat_get_focus() returns NULL. --- sway/input/seat.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sway/input/seat.c b/sway/input/seat.c index 9709f8c57..950d6de9f 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -1344,7 +1344,9 @@ void seat_unfocus_unless_client(struct sway_seat *seat, struct wl_client *client } if (seat->has_focus) { struct sway_node *focus = seat_get_focus(seat); - if (focus && node_is_view(focus) && wl_resource_get_client( + if (!focus) { + seat->has_focus = false; + } else if (node_is_view(focus) && wl_resource_get_client( focus->sway_container->view->surface->resource) != client) { seat_set_focus(seat, NULL); }