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 ()
```
This commit is contained in:
hack-heart 2026-03-01 02:18:28 -08:00
parent c57daaf0d1
commit 7ef26f9a92

View file

@ -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);
}