diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 0417614ce..64d5f83be 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -717,9 +717,9 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int * } if (send_cell_data_to_gpu(TD.vao_idx, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true; } - if (OPT(mouse_hide_wait) > 0 && !is_mouse_hidden(os_window)) { - if (now - os_window->last_mouse_activity_at >= OPT(mouse_hide_wait)) hide_mouse(os_window); - else set_maximum_wait(OPT(mouse_hide_wait) - now + os_window->last_mouse_activity_at); + if (OPT(mouse_hide.hide_wait) > 0 && !is_mouse_hidden(os_window)) { + if (now - os_window->last_mouse_activity_at >= OPT(mouse_hide.hide_wait)) hide_mouse(os_window); + else set_maximum_wait(OPT(mouse_hide.hide_wait) - now + os_window->last_mouse_activity_at); } Tab *tab = os_window->tabs + os_window->active_tab; *active_window_bg = OPT(background); diff --git a/kitty/glfw.c b/kitty/glfw.c index dadc48178..8b3377056 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -219,12 +219,12 @@ show_mouse_cursor(GLFWwindow *w) { void cursor_active_callback(GLFWwindow *w, monotonic_t now) { - if (OPT(mouse_show_wait) == 0) { + if (OPT(mouse_hide.unhide_wait) == 0) { show_mouse_cursor(w); - } else if (OPT(mouse_show_wait) > 0) { + } else if (OPT(mouse_hide.unhide_wait) > 0) { if (global_state.callback_os_window->mouse_activate_deadline == -1) { - global_state.callback_os_window->mouse_activate_deadline = OPT(mouse_show_wait) + now; - global_state.callback_os_window->mouse_show_threshold = (int) (OPT(mouse_show_wait) * OPT(mouse_show_threshold)); + global_state.callback_os_window->mouse_activate_deadline = OPT(mouse_hide.unhide_wait) + now; + global_state.callback_os_window->mouse_show_threshold = (int) (OPT(mouse_hide.unhide_wait) / 1e9 * OPT(mouse_hide.unhide_threshold)); } else if (now < global_state.callback_os_window->mouse_activate_deadline) { if (global_state.callback_os_window->mouse_show_threshold > 0) { global_state.callback_os_window->mouse_show_threshold--; @@ -529,7 +529,7 @@ static void scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags, int mods) { if (!set_callback_window(w)) return; monotonic_t now = monotonic(); - if (OPT(mouse_scroll_show)) { + if (OPT(mouse_hide.scroll_unhide)) { cursor_active_callback(w, now); } global_state.callback_os_window->last_mouse_activity_at = now; diff --git a/kitty/keys.c b/kitty/keys.c index 2481ba14c..68175df8f 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -235,7 +235,7 @@ on_key_input(const GLFWkeyevent *ev) { } if (!w) { debug("no active window, ignoring\n"); return; } send_pending_click_to_window(w, -1); - if (OPT(mouse_hide_wait) < 0 && !is_no_action_key(key, native_key)) hide_mouse(global_state.callback_os_window); + if (OPT(mouse_hide.hide_wait) < 0 && !is_no_action_key(key, native_key)) hide_mouse(global_state.callback_os_window); Screen *screen = w->render_data.screen; id_type active_window_id = w->id; diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 39210d89d..10380a9e5 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -511,43 +511,51 @@ agr('mouse', 'Mouse') opt('mouse_hide_wait', '3.0', macos_default='0.0', - option_type='float', ctype='time', + option_type='mouse_hide_wait', ctype='!mouse_hide_wait', long_text=''' Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to zero to disable mouse cursor hiding. Set to a negative value to hide the mouse cursor immediately when typing text. Disabled by default on macOS as getting it to work robustly with the ever-changing sea of bugs that is Cocoa is too much effort. -''' - ) -opt('mouse_show_wait', '0.0', - option_type='float', ctype='time', - long_text=''' + +By default, once the cursor is hidden, it is immediately unhidden on any +further mouse events. + +Two formats are supported: + - "" + - " " + +To change the unhide behavior, the optional parameters , +, and may be set. + +: Waits for the specified number of seconds after mouse events before unhiding the mouse cursor. Set to zero to unhide mouse cursor immediately on mouse activity. This is useful to prevent the mouse cursor from unhiding on accidental swipes on the trackpad. -''' - ) -opt('mouse_show_threshold', '40', - option_type='positive_int', - long_text=''' -Sets the threshold of mouse activity required to unhide the mouse cursor, when -the mouse_show_wait option is non-zero. When mouse_show_wait is zero, this has -no effect. -For example, if mouse_show_threshold is 40 and mouse_show_wait is 2.5, when -kitty detects a mouse event, it records the number of mouse events in the next -2.5 seconds, and checks if that exceeds 40 * 2.5 = 100. If it does, then the -mouse cursor is unhidden, otherwise nothing happens. -''' - ) -opt('mouse_scroll_show', 'yes', - option_type='to_bool', ctype='bool', - long_text=''' +: +Sets the threshold of mouse activity required to unhide the mouse cursor, when +the option is non-zero. When is zero, this has no +effect. + +For example, if is 40 and is 2.5, when kitty +detects a mouse event, it records the number of mouse events in the next 2.5 +seconds, and checks if that exceeds 40 * 2.5 = 100. If it does, then the mouse +cursor is unhidden, otherwise nothing happens. + +: Controls what mouse events may unhide the mouse cursor. If enabled, both scroll and movement events may unhide the cursor. If disabled, only mouse movements can unhide the cursor. + + +Examples of valid values: + - 0.0 + - 1.0 + - -1.0 + - 0.1 3.0 40 yes ''' ) diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 19d2d9d15..bc11eddc9 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -14,7 +14,7 @@ from kitty.options.utils import ( cursor_trail_decay, deprecated_adjust_line_height, deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias, deprecated_send_text, disable_ligatures, edge_width, env, filter_notification, font_features, hide_window_decorations, macos_option_as_alt, - macos_titlebar_color, menu_map, modify_font, narrow_symbols, notify_on_cmd_finish, + macos_titlebar_color, menu_map, modify_font, mouse_hide_wait, narrow_symbols, notify_on_cmd_finish, optional_edge_width, parse_font_spec, parse_map, parse_mouse_map, paste_actions, pointer_shape_when_dragging, remote_control_password, resize_debounce_time, scrollback_lines, scrollback_pager_history_size, shell_integration, store_multiple, symbol_map, tab_activity_symbol, @@ -1137,16 +1137,7 @@ class Parser: ans["modify_font"][k] = v def mouse_hide_wait(self, val: str, ans: dict[str, typing.Any]) -> None: - ans['mouse_hide_wait'] = float(val) - - def mouse_scroll_show(self, val: str, ans: dict[str, typing.Any]) -> None: - ans['mouse_scroll_show'] = to_bool(val) - - def mouse_show_threshold(self, val: str, ans: dict[str, typing.Any]) -> None: - ans['mouse_show_threshold'] = positive_int(val) - - def mouse_show_wait(self, val: str, ans: dict[str, typing.Any]) -> None: - ans['mouse_show_wait'] = float(val) + ans['mouse_hide_wait'] = mouse_hide_wait(val) def narrow_symbols(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in narrow_symbols(val): diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index 3031e0299..e2164ae2c 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -319,7 +319,7 @@ convert_from_opts_touch_scroll_multiplier(PyObject *py_opts, Options *opts) { static void convert_from_python_mouse_hide_wait(PyObject *val, Options *opts) { - opts->mouse_hide_wait = parse_s_double_to_monotonic_t(val); + mouse_hide_wait(val, opts); } static void @@ -330,32 +330,6 @@ convert_from_opts_mouse_hide_wait(PyObject *py_opts, Options *opts) { Py_DECREF(ret); } -static void -convert_from_python_mouse_show_wait(PyObject *val, Options *opts) { - opts->mouse_show_wait = parse_s_double_to_monotonic_t(val); -} - -static void -convert_from_opts_mouse_show_wait(PyObject *py_opts, Options *opts) { - PyObject *ret = PyObject_GetAttrString(py_opts, "mouse_show_wait"); - if (ret == NULL) return; - convert_from_python_mouse_show_wait(ret, opts); - Py_DECREF(ret); -} - -static void -convert_from_python_mouse_scroll_show(PyObject *val, Options *opts) { - opts->mouse_scroll_show = PyObject_IsTrue(val); -} - -static void -convert_from_opts_mouse_scroll_show(PyObject *py_opts, Options *opts) { - PyObject *ret = PyObject_GetAttrString(py_opts, "mouse_scroll_show"); - if (ret == NULL) return; - convert_from_python_mouse_scroll_show(ret, opts); - Py_DECREF(ret); -} - static void convert_from_python_url_color(PyObject *val, Options *opts) { opts->url_color = color_as_int(val); @@ -1253,10 +1227,6 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) { if (PyErr_Occurred()) return false; convert_from_opts_mouse_hide_wait(py_opts, opts); if (PyErr_Occurred()) return false; - convert_from_opts_mouse_show_wait(py_opts, opts); - if (PyErr_Occurred()) return false; - convert_from_opts_mouse_scroll_show(py_opts, opts); - if (PyErr_Occurred()) return false; convert_from_opts_url_color(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_url_style(py_opts, opts); diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index bc959f19f..ecfa0c552 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -197,6 +197,18 @@ visual_bell_duration(PyObject *src, Options *opts) { #undef parse_animation +static inline void +mouse_hide_wait(PyObject *val, Options *opts) { + if (!PyTuple_Check(val) || PyTuple_GET_SIZE(val) != 4) { + PyErr_SetString(PyExc_TypeError, "mouse_hide_wait is not a 4-item tuple"); + return; + } + opts->mouse_hide.hide_wait = parse_s_double_to_monotonic_t(PyTuple_GET_ITEM(val, 0)); + opts->mouse_hide.unhide_wait = parse_s_double_to_monotonic_t(PyTuple_GET_ITEM(val, 1)); + opts->mouse_hide.unhide_threshold = PyLong_AsLong(PyTuple_GET_ITEM(val, 2)); + opts->mouse_hide.scroll_unhide = PyObject_IsTrue(PyTuple_GET_ITEM(val, 3)); +} + static inline void cursor_trail_decay(PyObject *src, Options *opts) { opts->cursor_trail_decay_fast = PyFloat_AsFloat(PyTuple_GET_ITEM(src, 0)); diff --git a/kitty/options/types.py b/kitty/options/types.py index bf1f55a78..8e6a75446 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -11,7 +11,7 @@ import kitty.fast_data_types from kitty.fonts import FontSpec import kitty.fonts from kitty.options.utils import ( - AliasMap, KeyDefinition, KeyboardModeMap, MouseMap, MouseMapping, NotifyOnCmdFinish, + AliasMap, KeyDefinition, KeyboardModeMap, MouseHideWait, MouseMap, MouseMapping, NotifyOnCmdFinish, TabBarMarginHeight ) import kitty.options.utils @@ -396,9 +396,6 @@ option_names = ( 'modify_font', 'mouse_hide_wait', 'mouse_map', - 'mouse_scroll_show', - 'mouse_show_threshold', - 'mouse_show_wait', 'narrow_symbols', 'notify_on_cmd_finish', 'open_url_with', @@ -571,10 +568,7 @@ class Options: mark2_foreground: Color = Color(0, 0, 0) mark3_background: Color = Color(242, 116, 188) mark3_foreground: Color = Color(0, 0, 0) - mouse_hide_wait: float = 0.0 if is_macos else 3.0 - mouse_scroll_show: bool = True - mouse_show_threshold: int = 40 - mouse_show_wait: float = 0.0 + mouse_hide_wait: MouseHideWait = MouseHideWait(hide_wait=0.0, show_wait=0.0, show_threshold=40, scroll_show=True) if is_macos else MouseHideWait(hide_wait=3.0, show_wait=0.0, show_threshold=40, scroll_show=True) notify_on_cmd_finish: NotifyOnCmdFinish = NotifyOnCmdFinish(when='never', duration=5.0, action='notify', cmdline=(), clear_on=('focus', 'next')) open_url_with: list[str] = ['default'] paste_actions: frozenset[str] = frozenset({'confirm', 'quote-urls-at-prompt'}) diff --git a/kitty/options/utils.py b/kitty/options/utils.py index 5dab6906b..a8fccd252 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -1590,6 +1590,24 @@ def cursor_blink_interval(spec: str) -> tuple[float, EasingFunction, EasingFunct return parse_animation(spec) +class MouseHideWait(NamedTuple): + hide_wait: float + show_wait: float + show_threshold: int + scroll_show: bool + + +def mouse_hide_wait(x: str) -> MouseHideWait: + parts = x.split(maxsplit=3) + if len(parts) != 1 and len(parts) != 4: + log_error(f'Invalid mouse_hide_wait: {x}, ignoring') + return MouseHideWait(3.0, 0.0, 40, True) + if len(parts) == 1: + return MouseHideWait(float(parts[0]), 0.0, 40, True) + else: + return MouseHideWait(float(parts[0]), float(parts[1]), int(parts[2]), to_bool(parts[3])) + + def visual_bell_duration(spec: str) -> tuple[float, EasingFunction, EasingFunction]: return parse_animation(spec, interval=0.) diff --git a/kitty/state.h b/kitty/state.h index 56ff1c7e3..b192aed2a 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -39,9 +39,12 @@ struct MenuItem { }; typedef struct { - monotonic_t visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, mouse_show_wait, click_interval; - int mouse_show_threshold; - bool mouse_scroll_show; + monotonic_t visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, click_interval; + struct { + monotonic_t hide_wait, unhide_wait; + int unhide_threshold; + bool scroll_unhide; + } mouse_hide; double wheel_scroll_multiplier, touch_scroll_multiplier; int wheel_scroll_min_lines; bool enable_audio_bell; diff --git a/kitty_tests/options.py b/kitty_tests/options.py index e73b65201..b717d9ac0 100644 --- a/kitty_tests/options.py +++ b/kitty_tests/options.py @@ -56,10 +56,10 @@ def conf_parsing(self): opts = p('font_size 11.37', 'clear_all_shortcuts y', 'color23 red') self.ae(opts.font_size, 11.37) - self.ae(opts.mouse_hide_wait, 0 if is_macos else 3) - self.ae(opts.mouse_show_wait, 0) - self.ae(opts.mouse_show_threshold, 40) - self.ae(opts.mouse_scroll_show, True) + self.ae(opts.mouse_hide_wait[0], 0 if is_macos else 3) + self.ae(opts.mouse_hide_wait[1], 0) + self.ae(opts.mouse_hide_wait[2], 40) + self.ae(opts.mouse_hide_wait[3], True) self.ae(opts.color23, Color(255, 0, 0)) self.assertFalse(opts.keyboard_modes[''].keymap) opts = p('clear_all_shortcuts y', 'map f1 next_window')