fix: simplify shutdown teardown, lock/inhibit ordering, small input gaps

Shutdown/teardown now reuses remove_tab instead of an open-coded free
loop, closing listener/thumb/MRU/timer gaps with less code; remove_tab
also destroys foreign-toplevel handles, and shutdown removes the full
standalone pointer listener set including gestures.

Key handling: delete the session-locked early return that swallowed
lock-screen typing (grabs stay swallowed by their chord handlers,
passive keys now forward; overlay gated centrally), and move Ctrl+Alt+Q
quit above the shortcuts inhibitor so VT switch and quit stay live
under inhibition (pure reorder).

Input: swallow pointer/touch during quit-confirm as well as overlay,
reset idle on scroll in both axis paths, and guard close_timer against
double-arm. Net fewer lines; all 176 smoke tests pass.
This commit is contained in:
Andrew Hamilton 2026-09-29 23:15:02 +10:00
parent 28d5df8d52
commit 498ee6db35

103
kick.c
View file

@ -1651,21 +1651,8 @@ static void shutdown_cleanup(void) {
shutting_down = true;
terminate_clients();
struct kick_tab *tab, *tab_tmp;
wl_list_for_each_safe(tab, tab_tmp, &server.tabs, link) {
struct kick_window *w, *w_tmp;
wl_list_for_each_safe(w, w_tmp, &tab->windows, link) {
if (w->icon_wlr) {
wlr_texture_destroy(w->icon_wlr);
w->icon_wlr = NULL;
}
icon_proto_release(w);
free(w->cmd);
free(w->tag);
free(w->tag_desc);
free(w);
}
free(tab);
}
wl_list_for_each_safe(tab, tab_tmp, &server.tabs, link)
remove_tab(tab);
wl_list_init(&server.tabs);
wl_list_init(&server.mru);
server.mru_alt_anchor = NULL;
@ -1691,6 +1678,14 @@ static void shutdown_cleanup(void) {
wl_list_remove(&pd->button.link);
wl_list_remove(&pd->axis.link);
wl_list_remove(&pd->frame.link);
wl_list_remove(&pd->swipe_begin.link);
wl_list_remove(&pd->swipe_update.link);
wl_list_remove(&pd->swipe_end.link);
wl_list_remove(&pd->pinch_begin.link);
wl_list_remove(&pd->pinch_update.link);
wl_list_remove(&pd->pinch_end.link);
wl_list_remove(&pd->hold_begin.link);
wl_list_remove(&pd->hold_end.link);
wl_list_remove(&pd->destroy.link);
free(pd);
}
@ -4372,6 +4367,7 @@ static void remove_tab(struct kick_tab *tab) {
wlr_texture_destroy(w->icon_wlr);
thumb_release(w);
icon_proto_release(w);
foreign_handle_destroy(w);
if (w->toplevel)
window_detach_xdg_listeners(w);
#if HAVE_XWAYLAND
@ -4569,10 +4565,12 @@ static void close_window(struct kick_window *w) {
kill_window(w, SIGTERM);
kick_log(KICK_LOG_INFO, "kick: closing tab %d (pid %d)\n",
died, (int)shown);
w->close_timer = wl_event_loop_add_timer(server.loop, close_window_escalate,
w);
if (w->close_timer)
wl_event_source_timer_update(w->close_timer, KICK_CLOSE_ESCALATE_MS);
if (!w->close_timer) {
w->close_timer = wl_event_loop_add_timer(server.loop, close_window_escalate,
w);
if (w->close_timer)
wl_event_source_timer_update(w->close_timer, KICK_CLOSE_ESCALATE_MS);
}
}
/* Alt+Delete on the active tab closes the active WINDOW, not the whole tab:
@ -10143,6 +10141,7 @@ static double overlay_wheel_acc = 0.0;
static void update_overlay_visible(bool alt) {
if (alt == host.overlay_visible) return;
if (server.session_locked) return;
if (alt) {
/* Prefix went down: snapshot the shown window. Every focus change
from here until release is a preview -- window_enter leaves the
@ -10273,18 +10272,6 @@ static bool process_key_event(uint32_t time, uint32_t key, uint32_t state,
}
return true;
}
/* Session locked: swallow every compositor grab chord so the user can't
cycle tabs, close windows or open terminals behind the lock screen
(passive keys still flow to the focused lock surface). The quit chord
and confirm modal above run first and remain available, as does the VT
switch below (a matching compositor session action). */
if (server.session_locked)
return true;
/* A focused surface with an active shortcuts inhibitor (game/VM) grabs
the compositor's chords: swallow the tab/window grabs so they reach
the client. Quit and VT switch above/below stay live. */
if (kb_inhibit_focused())
return true;
/* Ctrl+Alt+F1..F12 switch virtual terminals in standalone mode. wlroots
has no built-in handler; the compositor must notice the key and ask the
session (libseat/logind) to activate another VT. Both the press and
@ -10300,6 +10287,29 @@ static bool process_key_event(uint32_t time, uint32_t key, uint32_t state,
}
return true;
}
/* Ctrl+Alt+Q quits kick (both the host close and this chord share
quit_requested): with more than one window open a modal asks before
anything is torn down. Above the inhibitor so quit stays live. */
{ bool is_q = server.xkb_state
? (sym == XKB_KEY_q || sym == XKB_KEY_Q)
: key == 16;
if (alt && ctrl && is_q) {
kbd_repeat_cancel();
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
if (!quit_key_held) {
quit_key_held = true;
quit_requested();
}
} else {
quit_key_held = false;
}
return true;
}
}
/* Inhibited (game/VM grabs chords): swallow tab/window grabs below so
they reach the client. VT switch and quit above stay live. */
if (kb_inhibit_focused())
return true;
/* Function keys are not grabbed (prefix+F11 above is the one exception):
they pass through to the focused client, which may have its own
bindings (e.g. a browser's F11 fullscreen request, which
@ -10429,27 +10439,6 @@ static bool process_key_event(uint32_t time, uint32_t key, uint32_t state,
return true;
}
}
/* Ctrl+Alt+Q quits kick (both the host close and this chord share
quit_requested): with more than one window open a modal asks before
anything is torn down. Grabbed so the focused client never sees the
chord; a latch drops host-synthesized repeats so one hold can't fire
the request (or the confirm) more than once. */
{ bool is_q = server.xkb_state
? (sym == XKB_KEY_q || sym == XKB_KEY_Q)
: key == 16;
if (alt && ctrl && is_q) {
kbd_repeat_cancel();
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
if (!quit_key_held) {
quit_key_held = true;
quit_requested();
}
} else {
quit_key_held = false;
}
return true;
}
}
/* prefix+Delete closes the active window (or the whole tab when it is the
only window). Grabbed so the focused client never sees the key; a latch
drops host-synthesized repeats so one hold can't fire it per repeat.
@ -11526,6 +11515,8 @@ static void standalone_ptr_button_event(struct wl_listener *listener, void *data
static void standalone_ptr_axis_event(struct wl_listener *listener, void *data) {
(void)listener;
struct wlr_pointer_axis_event *ev = data;
if (server.idle_notifier && server.seat)
wlr_idle_notifier_v1_notify_activity(server.idle_notifier, server.seat);
if (!server.seat) return;
/* libinput scales discrete steps by WLR_POINTER_AXIS_DISCRETE_STEP
(±120 per notch; the seat divides it back off for clients), while
@ -12009,7 +12000,7 @@ static bool overlay_preview_select(struct kick_window *w) {
}
static bool overlay_eats_pointer(void) {
return host.overlay_visible && !confirm_quit_pending &&
return (host.overlay_visible || confirm_quit_pending) &&
!server.session_locked;
}
@ -12067,12 +12058,12 @@ static void seat_touch_deliver(uint32_t time_msec, int32_t id, int action,
if (!server.seat) return;
if (server.idle_notifier)
wlr_idle_notifier_v1_notify_activity(server.idle_notifier, server.seat);
/* While the Alt overlay is up, touches never reach clients: a tap on
/* While the Alt overlay or quit confirm is up, touches never reach clients: a tap on
a row previews that window (single tap selects, drag motion
re-targets the preview as the finger slides between titles),
anything else is ignored. Checked before constraint clamping --
the overlay sits outside any constrained surface. */
if (host.overlay_visible && !confirm_quit_pending && !server.session_locked) {
if ((host.overlay_visible || confirm_quit_pending) && !server.session_locked) {
if (action == KICK_TOUCH_DOWN) {
/* First-touch-wins: only the finger that claims the idle
driver previews; concurrent fingers are swallowed silently
@ -12244,6 +12235,8 @@ static void host_ptr_axis(void *data, struct wl_pointer *p, uint32_t time,
uint32_t axis, wl_fixed_t value)
{
(void)data; (void)p;
if (server.idle_notifier && server.seat)
wlr_idle_notifier_v1_notify_activity(server.idle_notifier, server.seat);
if (!server.seat) return;
int32_t discrete = axis < 2 ? host.axis_discrete[axis] : 0;
host.axis_discrete[axis] = 0;