From faed62e993544e4ef98bc83e92b60b79d4d61085 Mon Sep 17 00:00:00 2001 From: David Thiel Date: Tue, 7 Apr 2026 14:18:54 +0100 Subject: [PATCH] Make focus_follows_mouse only switch on window-boundary crossings The previous implementation reverted any keyboard-driven window switch on the next mouse motion event because it compared the hovered window against the active window rather than against the previously hovered window. Move the focus-follows-mouse trigger into set_currently_hovered_window so it fires only when the hover ID actually transitions, which also covers OS-window-entry. --- docs/changelog.rst | 2 ++ kitty/mouse.c | 15 +++++++++------ kitty/options/definition.py | 5 ++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8c5264c1d..5f7401f30 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -185,6 +185,8 @@ Detailed list of changes - Allow optionally dragging URLs with the mouse, see :sc:`start_simple_selection` (:pull:`9804`) +- Change :opt:`focus_follows_mouse` to switch the active window only when the mouse crosses into a different window, instead of on every mouse motion event. Prevents accidental mouse bumps from undoing a keyboard-driven window switch. + - Fix thickness of diagonal lines in box drawing characters not the same as horizontal/vertical lines (:iss:`9719`) - Graphics protocol: Fix crash when handling invalid PNG image with direct transmission diff --git a/kitty/mouse.c b/kitty/mouse.c index c473f6a3e..01489df22 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -196,6 +196,15 @@ set_currently_hovered_window(id_type window_id, int modifiers) { debug("Sent mouse leave event to window: %llu\n", left_window->id); } } + if (window_id && OPT(focus_follows_mouse) && global_state.callback_os_window && global_state.callback_os_window->num_tabs) { + Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; + for (unsigned i = 0; i < t->num_windows; i++) { + if (t->windows[i].id == window_id) { + if (i != t->active_window) call_boss(switch_focus_to_in_active_tab, "K", window_id); + break; + } + } + } } } @@ -654,12 +663,6 @@ HANDLER(handle_move_event) { if (handle_scrollbar_mouse(w, -1, MOVE, modifiers)) return; - if (OPT(focus_follows_mouse)) { - Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; - if (window_idx != t->active_window) { - call_boss(switch_focus_to_in_active_tab, "K", t->windows[window_idx].id); - } - } bool mouse_cell_changed = false; bool cell_half_changed = false; if (!set_mouse_position(w, &mouse_cell_changed, &cell_half_changed)) { diff --git a/kitty/options/definition.py b/kitty/options/definition.py index e45612808..b33b928f8 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -857,7 +857,10 @@ fallback to 0.5. opt('focus_follows_mouse', 'no', option_type='to_bool', ctype='bool', long_text=''' -Set the active window to the window under the mouse when moving the mouse around. +Set the active window to the window under the mouse when the mouse crosses +into a different window. The active window does not change while the mouse +moves around within a single window, so an accidental mouse bump will not +undo a keyboard-driven window switch. On macOS, this will also cause the OS Window under the mouse to be focused automatically when the mouse enters it, as long as the kitty application is the active application. '''