Prevent mouse cursor from showing on short, unintentional flicks

This commit is contained in:
Your Name 2025-03-28 13:35:38 +00:00
parent f56c3edd72
commit 464446e388
7 changed files with 118 additions and 9 deletions

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_show_wait) == 0) {
show_mouse_cursor(w);
} else if (OPT(mouse_show_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));
} 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_scroll_show)) {
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

@ -518,6 +518,36 @@ 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='''
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='''
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.
'''
)

View file

@ -1139,6 +1139,15 @@ class Parser:
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)
def narrow_symbols(self, val: str, ans: dict[str, typing.Any]) -> None:
for k, v in narrow_symbols(val):
ans["narrow_symbols"][k] = v

View file

@ -330,6 +330,32 @@ 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);
@ -1227,6 +1253,10 @@ 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);

View file

@ -396,6 +396,9 @@ 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',
@ -569,6 +572,9 @@ class Options:
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
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

@ -39,7 +39,9 @@ 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, mouse_hide_wait, mouse_show_wait, click_interval;
int mouse_show_threshold;
bool mouse_scroll_show;
double wheel_scroll_multiplier, touch_scroll_multiplier;
int wheel_scroll_min_lines;
bool enable_audio_bell;
@ -286,7 +288,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

@ -57,6 +57,9 @@ 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.color23, Color(255, 0, 0))
self.assertFalse(opts.keyboard_modes[''].keymap)
opts = p('clear_all_shortcuts y', 'map f1 next_window')