Option to have focus_follows_mouse only on drops

Fixes #9896
This commit is contained in:
Kovid Goyal 2026-04-21 20:05:00 +05:30
parent c655ea8946
commit 827b4b9e02
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
9 changed files with 37 additions and 7 deletions

View file

@ -255,6 +255,8 @@ Detailed list of changes
- diff kitten: Keep the current (topmost) filename visible when scrolling, controlled by a new option :opt:`kitten-diff.sticky_header` (:pull:`9891`)
- Add an option to :opt:`focus_follows_mouse` to only switch focus on drops rather than movement (:pull:`9896`)
0.46.2 [2026-03-21]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -843,6 +843,9 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
ev->from_self ? Py_True : Py_False, Py_True);
break;
case GLFW_DROP_DROP:
if (w && OPT(focus_follows_mouse).on_drop) {
call_boss(set_active_window, "KO", w->id, Py_True);
}
Py_CLEAR(global_state.drop_dest.data);
global_state.drop_dest.drop_has_happened = true;
global_state.drop_dest.client_window_data_request = 0;

View file

@ -196,7 +196,7 @@ 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) {
if (window_id && OPT(focus_follows_mouse).on_cross && 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) {

View file

@ -855,12 +855,13 @@ fallback to 0.5.
)
opt('focus_follows_mouse', 'no',
option_type='to_bool', ctype='bool',
choices=('no', 'n', 'false', 'y', 'yes', 'true', 'drop'), ctype='!focus_follows_mouse',
long_text='''
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.
undo a keyboard-driven window switch. Setting it to :code:`drop` means
focus will only be changed when a drag and drop happens in a window.
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.
'''

View file

@ -1012,7 +1012,12 @@ class Parser:
ans["filter_notification"][k] = v
def focus_follows_mouse(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['focus_follows_mouse'] = to_bool(val)
val = val.lower()
if val not in self.choices_for_focus_follows_mouse:
raise ValueError(f"The value {val} is not a valid choice for focus_follows_mouse")
ans["focus_follows_mouse"] = val
choices_for_focus_follows_mouse = frozenset(('no', 'n', 'false', 'y', 'yes', 'true', 'drop'))
def font_family(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['font_family'] = parse_font_spec(val)

View file

@ -683,7 +683,7 @@ convert_from_opts_click_interval(PyObject *py_opts, Options *opts) {
static void
convert_from_python_focus_follows_mouse(PyObject *val, Options *opts) {
opts->focus_follows_mouse = PyObject_IsTrue(val);
focus_follows_mouse(val, opts);
}
static void

View file

@ -558,6 +558,22 @@ tab_bar_style(PyObject *val, Options *opts) {
opts->tab_bar_hidden = PyUnicode_CompareWithASCIIString(val, "hidden") == 0 ? true: false;
}
static inline void
focus_follows_mouse(PyObject *val, Options *opts) {
zero_at_ptr(&opts->focus_follows_mouse);
const char *q = PyUnicode_AsUTF8(val);
switch(q[0]) {
case 'y': case 't':
opts->focus_follows_mouse.on_cross = true;
opts->focus_follows_mouse.on_drop = true;
break;
case 'd':
opts->focus_follows_mouse.on_drop = true;
break;
}
}
static inline void
tab_bar_margin_height(PyObject *val, Options *opts) {
if (!PyTuple_Check(val) || PyTuple_GET_SIZE(val) != 2) {

View file

@ -22,6 +22,7 @@ choices_for_allow_cloning = typing.Literal['yes', 'y', 'true', 'no', 'n', 'false
choices_for_allow_remote_control = typing.Literal['password', 'socket-only', 'socket', 'no', 'n', 'false', 'yes', 'y', 'true']
choices_for_background_image_layout = typing.Literal['mirror-tiled', 'scaled', 'tiled', 'clamped', 'centered', 'cscaled']
choices_for_default_pointer_shape = typing.Literal['arrow', 'beam', 'text', 'pointer', 'hand', 'help', 'wait', 'progress', 'crosshair', 'cell', 'vertical-text', 'move', 'e-resize', 'ne-resize', 'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out', 'alias', 'copy', 'not-allowed', 'no-drop', 'grab', 'grabbing']
choices_for_focus_follows_mouse = typing.Literal['no', 'n', 'false', 'y', 'yes', 'true', 'drop']
choices_for_linux_display_server = typing.Literal['auto', 'wayland', 'x11']
choices_for_macos_colorspace = typing.Literal['srgb', 'default', 'displayp3']
choices_for_macos_show_window_title_in = typing.Literal['all', 'menubar', 'none', 'window']
@ -578,7 +579,7 @@ class Options:
enable_audio_bell: bool = True
enabled_layouts: list[str] = ['fat', 'grid', 'horizontal', 'splits', 'stack', 'tall', 'vertical']
file_transfer_confirmation_bypass: str = ''
focus_follows_mouse: bool = False
focus_follows_mouse: choices_for_focus_follows_mouse = 'no'
font_family: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='monospace', axes=(), variable_name=None, features=(), created_from_string='monospace')
font_size: float = 11.0
force_ltr: bool = False

View file

@ -74,7 +74,9 @@ typedef struct Options {
color_type url_color, background, foreground, active_border_color, inactive_border_color, bell_border_color, tab_bar_background, tab_bar_margin_color,
window_title_bar_active_foreground, window_title_bar_active_background, window_title_bar_inactive_foreground, window_title_bar_inactive_background;
monotonic_t repaint_delay, input_delay;
bool focus_follows_mouse;
struct {
bool on_cross, on_drop;
} focus_follows_mouse;
unsigned int hide_window_decorations;
bool macos_hide_from_tasks, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen, macos_fullscreen_ignore_safe_area_insets;
unsigned int macos_option_as_alt;