Make Wayland momentum scrolling configurable

This commit is contained in:
Kovid Goyal 2026-01-09 13:46:49 +05:30
parent 1f43a75a07
commit 2ab0fcb373
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
11 changed files with 44 additions and 8 deletions

View file

@ -155,7 +155,7 @@ Detailed list of changes
- Pixel scrolling for the kitty scrollback buffer controlled via :opt:`pixel_scroll` (:pull:`9330`)
- Wayland: Add momentum scrolling in the kitty scrollback buffer for touchpads and touchscreens
- Wayland: momentum scrolling in the kitty scrollback buffer for touchpads and touchscreens, see :opt:`momentum_scroll`
- choose-files kitten: Fix JXL image preview not working (:iss:`9323`)

View file

@ -327,6 +327,7 @@ def generate_wrappers(glfw_header: str) -> None:
bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle)
bool glfwWaylandBeep(GLFWwindow *handle)
pid_t glfwWaylandCompositorPID(void)
void glfwConfigureMomentumScroller(double friction, double min_velocity, double max_velocity, unsigned timer_interval)
unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data)
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)

View file

@ -42,16 +42,15 @@ static const MomentumScroller defaults = {
.friction = 0.04,
.min_velocity = 0.5,
.max_velocity = 100,
.velocity_scale = 0.9,
.timer_interval = 10,
};
static MomentumScroller s = defaults;
GLFWAPI void
glfwConfigureMomentumScroller(double friction, double min_velocity, double max_velocity, double velocity_scale, unsigned timer_interval_ms) {
s.timer_interval = ms_to_monotonic_t(timer_interval_ms);
glfwConfigureMomentumScroller(double friction, double min_velocity, double max_velocity, unsigned timer_interval_ms) {
s.timer_interval = timer_interval_ms ? ms_to_monotonic_t(timer_interval_ms) : defaults.timer_interval;
#define S(w) s.w = w >= 0 ? w : defaults.w
S(friction); S(min_velocity); S(max_velocity); S(velocity_scale);
S(friction); S(min_velocity); S(max_velocity);
#undef S
}
@ -94,7 +93,6 @@ trim_old_samples(monotonic_t now) {
static void
add_velocity(double x, double y) {
x *= s.velocity_scale; y *= s.velocity_scale;
if (x == 0 || x * s.velocity.x >= 0) s.velocity.x += x;
else s.velocity.x = x;
if (y == 0 || y * s.velocity.y >= 0) s.velocity.y += y;
@ -185,7 +183,7 @@ glfw_handle_scroll_event_for_momentum(
_GLFWwindow *w, const GLFWScrollEvent *ev, bool stopped, bool is_finger_based
) {
if (!w) { cancel_existing_scroll(true); return; }
if (!is_finger_based || ev->offset_type != GLFW_SCROLL_OFFEST_HIGHRES || s.friction <= 0) {
if (!is_finger_based || ev->offset_type != GLFW_SCROLL_OFFEST_HIGHRES || s.friction < 0 || s.friction >= 1) {
_glfwInputScroll(w, ev);
return;
}
@ -210,7 +208,7 @@ glfw_handle_scroll_event_for_momentum(
if (ldx * ev->x_offset < 0 || ldy * ev->y_offset < 0) cancel_existing_scroll(true);
s.window_id = w->id;
s.keyboard_modifiers = ev->keyboard_modifiers;
if (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES && s.friction > 0) {
if (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES) {
add_sample(ev->unscaled.x, ev->unscaled.y, now);
if (stopped) s.state = is_suitable_for_momentum() ? MOMENTUM_IN_PROGRESS : NONE;
else s.state = PHYSICAL_EVENT_IN_PROGRESS;

3
kitty/glfw-wrapper.c generated
View file

@ -512,6 +512,9 @@ load_glfw(const char* path) {
*(void **) (&glfwWaylandCompositorPID_impl) = dlsym(handle, "glfwWaylandCompositorPID");
if (glfwWaylandCompositorPID_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwConfigureMomentumScroller_impl) = dlsym(handle, "glfwConfigureMomentumScroller");
if (glfwConfigureMomentumScroller_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwDBusUserNotify_impl) = dlsym(handle, "glfwDBusUserNotify");
if (glfwDBusUserNotify_impl == NULL) dlerror(); // clear error indicator

4
kitty/glfw-wrapper.h generated
View file

@ -2406,6 +2406,10 @@ typedef pid_t (*glfwWaylandCompositorPID_func)(void);
GFW_EXTERN glfwWaylandCompositorPID_func glfwWaylandCompositorPID_impl;
#define glfwWaylandCompositorPID glfwWaylandCompositorPID_impl
typedef void (*glfwConfigureMomentumScroller_func)(double, double, double, unsigned);
GFW_EXTERN glfwConfigureMomentumScroller_func glfwConfigureMomentumScroller_impl;
#define glfwConfigureMomentumScroller glfwConfigureMomentumScroller_impl
typedef unsigned long long (*glfwDBusUserNotify_func)(const GLFWDBUSNotificationData*, GLFWDBusnotificationcreatedfun, void*);
GFW_EXTERN glfwDBusUserNotify_func glfwDBusUserNotify_impl;
#define glfwDBusUserNotify glfwDBusUserNotify_impl

View file

@ -1354,6 +1354,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
static bool is_first_window = true;
if (is_first_window) {
if (global_state.is_wayland) glfwConfigureMomentumScroller(OPT(momentum_scroll), -1, -1, 0);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MAJOR);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_REQUIRED_VERSION_MINOR);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true);

View file

@ -597,6 +597,14 @@ running inside the terminal (for example full-screen TUIs) that handle scrolling
themselves.
''')
opt('momentum_scroll', '0.04', option_type='unit_float', ctype='float', long_text='''
The amount of friction to apply to slow down momentum (inertial) scrolling. A number
from 0 to 1, with 1 meaning no momentum scrolling. Note that this setting only
applies on platforms such as Wayland, that do not provide native momentum scrolling.
On macOS, the native OS based momentum scrolling is used. Also, momentum scrolling
only applies to "finger" based devices such as touchpads and touchscreens. Changes
to this setting only take effect after a kitty restart.
''')
egr() # }}}

View file

@ -1143,6 +1143,9 @@ class Parser:
for k, v in modify_font(val):
ans["modify_font"][k] = v
def momentum_scroll(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['momentum_scroll'] = unit_float(val)
def mouse_hide_wait(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['mouse_hide_wait'] = mouse_hide_wait(val)

View file

@ -512,6 +512,19 @@ convert_from_opts_pixel_scroll(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_momentum_scroll(PyObject *val, Options *opts) {
opts->momentum_scroll = PyFloat_AsFloat(val);
}
static void
convert_from_opts_momentum_scroll(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "momentum_scroll");
if (ret == NULL) return;
convert_from_python_momentum_scroll(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_mouse_hide_wait(PyObject *val, Options *opts) {
mouse_hide_wait(val, opts);
@ -1437,6 +1450,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
if (PyErr_Occurred()) return false;
convert_from_opts_pixel_scroll(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_momentum_scroll(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_mouse_hide_wait(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_url_color(py_opts, opts);

View file

@ -397,6 +397,7 @@ option_names = (
'mark3_foreground',
'menu_map',
'modify_font',
'momentum_scroll',
'mouse_hide_wait',
'mouse_map',
'narrow_symbols',
@ -589,6 +590,7 @@ class Options:
mark2_foreground: Color = Color(0, 0, 0)
mark3_background: Color = Color(242, 116, 188)
mark3_foreground: Color = Color(0, 0, 0)
momentum_scroll: float = 0.04
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']

View file

@ -139,6 +139,7 @@ typedef struct Options {
unsigned undercurl_style;
struct { float thickness; int unit; } underline_exclusion;
float box_drawing_scale[4];
double momentum_scroll;
} Options;
typedef struct WindowLogoRenderData {