diff --git a/docs/changelog.rst b/docs/changelog.rst index 075f4bb2e..3f84528cf 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -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`) diff --git a/glfw/glfw.py b/glfw/glfw.py index 2d1dd3f09..b720d2d0d 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -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) diff --git a/glfw/momentum-scroll.c b/glfw/momentum-scroll.c index 73aef4abb..b3a4a800a 100644 --- a/glfw/momentum-scroll.c +++ b/glfw/momentum-scroll.c @@ -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; diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 8e257fcc0..5f14526e3 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -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 diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index e6e051bb0..18d37de51 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -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 diff --git a/kitty/glfw.c b/kitty/glfw.c index a3df5d953..ea197e051 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -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); diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 19d997ba0..f7de55781 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -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() # }}} diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 516b6e408..e5e339fc0 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -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) diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index 5ca2b9ec3..f23c3a359 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -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); diff --git a/kitty/options/types.py b/kitty/options/types.py index 4c632feb5..e0263901d 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -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'] diff --git a/kitty/state.h b/kitty/state.h index 810f4cd92..8a8216c51 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -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 {