This commit is contained in:
Kovid Goyal 2025-04-05 02:33:01 +05:30
commit 738d692563
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
11 changed files with 125 additions and 20 deletions

View file

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

View file

@ -217,6 +217,30 @@ show_mouse_cursor(GLFWwindow *w) {
glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}
void
cursor_active_callback(GLFWwindow *w, monotonic_t now) {
if (OPT(mouse_hide.unhide_wait) == 0) {
show_mouse_cursor(w);
} 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_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--;
}
} else {
if (
now < global_state.callback_os_window->mouse_activate_deadline + s_double_to_monotonic_t(0.5) &&
global_state.callback_os_window->mouse_show_threshold == 0
) {
show_mouse_cursor(w);
}
global_state.callback_os_window->mouse_activate_deadline = -1;
}
}
}
void
blank_os_window(OSWindow *osw) {
color_type color = OPT(background);
@ -451,8 +475,8 @@ cursor_enter_callback(GLFWwindow *w, int entered) {
double x, y;
glfwGetCursorPos(w, &x, &y);
debug_input("Mouse cursor entered window: %llu at %fx%f\n", global_state.callback_os_window->id, x, y);
show_mouse_cursor(w);
monotonic_t now = monotonic();
cursor_active_callback(w, now);
global_state.callback_os_window->last_mouse_activity_at = now;
global_state.callback_os_window->mouse_x = x * global_state.callback_os_window->viewport_x_ratio;
global_state.callback_os_window->mouse_y = y * global_state.callback_os_window->viewport_y_ratio;
@ -465,9 +489,9 @@ cursor_enter_callback(GLFWwindow *w, int entered) {
static void
mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
if (!set_callback_window(w)) return;
show_mouse_cursor(w);
mods_at_last_key_or_button_event = mods;
monotonic_t now = monotonic();
cursor_active_callback(w, now);
mods_at_last_key_or_button_event = mods;
OSWindow *window = global_state.callback_os_window;
window->last_mouse_activity_at = now;
if (button >= 0 && (unsigned int)button < arraysz(global_state.callback_os_window->mouse_button_pressed)) {
@ -489,8 +513,8 @@ mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
static void
cursor_pos_callback(GLFWwindow *w, double x, double y) {
if (!set_callback_window(w)) return;
show_mouse_cursor(w);
monotonic_t now = monotonic();
cursor_active_callback(w, now);
global_state.callback_os_window->last_mouse_activity_at = now;
global_state.callback_os_window->cursor_blink_zero_time = now;
global_state.callback_os_window->mouse_x = x * global_state.callback_os_window->viewport_x_ratio;
@ -504,8 +528,10 @@ cursor_pos_callback(GLFWwindow *w, double x, double y) {
static void
scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags, int mods) {
if (!set_callback_window(w)) return;
show_mouse_cursor(w);
monotonic_t now = monotonic();
if (OPT(mouse_hide.scroll_unhide)) {
cursor_active_callback(w, now);
}
global_state.callback_os_window->last_mouse_activity_at = now;
if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags, mods);
request_tick_callback();
@ -519,13 +545,13 @@ window_focus_callback(GLFWwindow *w, int focused) {
if (!set_callback_window(w)) return;
debug_input("\x1b[35mon_focus_change\x1b[m: window id: 0x%llu focused: %d\n", global_state.callback_os_window->id, focused);
global_state.callback_os_window->is_focused = focused ? true : false;
monotonic_t now = monotonic();
if (focused) {
show_mouse_cursor(w);
cursor_active_callback(w, now);
focus_in_event();
global_state.callback_os_window->last_focused_counter = ++focus_counter;
global_state.check_for_active_animated_images = true;
}
monotonic_t now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now;
global_state.callback_os_window->cursor_blink_zero_time = now;
if (is_window_ready_for_callbacks()) {
@ -1371,6 +1397,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
w->is_focused = true;
w->cursor_blink_zero_time = now;
w->last_mouse_activity_at = now;
w->mouse_activate_deadline = -1;
w->mouse_show_threshold = 0;
w->is_semi_transparent = is_semi_transparent;
if (want_semi_transparent && !w->is_semi_transparent) {
static bool warned = false;

View file

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

View file

@ -511,13 +511,51 @@
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.
By default, once the cursor is hidden, it is immediately unhidden on any
further mouse events.
Two formats are supported:
- "<hide-wait>"
- "<hide-wait> <unhide-wait> <unhide-threshold> <scroll-unhide>"
To change the unhide behavior, the optional parameters <unhide-wait>,
<unhide-threshold>, and <scroll-unhide> may be set.
<unhide-wait>:
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.
<unhide-threshold>:
Sets the threshold of mouse activity required to unhide the mouse cursor, when
the <unhide-wait> option is non-zero. When <unhide-wait> is zero, this has no
effect.
For example, if <unhide-threshold> is 40 and <unhide-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.
<scroll-unhide>:
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
'''
)

View file

@ -14,7 +14,7 @@
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,7 +1137,7 @@ def modify_font(self, val: str, ans: dict[str, typing.Any]) -> None:
ans["modify_font"][k] = v
def mouse_hide_wait(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['mouse_hide_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):

View file

@ -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

View file

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

View file

@ -11,7 +11,7 @@
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
@ -568,7 +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_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'})

View file

@ -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.)

View file

@ -39,7 +39,12 @@ struct MenuItem {
};
typedef struct {
monotonic_t visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval;
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;
@ -286,7 +291,8 @@ typedef struct {
} tab_bar_edge_color;
bool tab_bar_data_updated;
bool is_focused;
monotonic_t cursor_blink_zero_time, last_mouse_activity_at;
monotonic_t cursor_blink_zero_time, last_mouse_activity_at, mouse_activate_deadline;
int mouse_show_threshold;
bool has_received_cursor_pos_event;
double mouse_x, mouse_y;
bool mouse_button_pressed[32];

View file

@ -56,7 +56,10 @@ def keys_for_func(opts, name):
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_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')