From 009ef54de74d8c8acdd9fda1ccd4d172b4b20ce2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 24 Aug 2018 11:00:58 +0530 Subject: [PATCH] Implement high precision scrolling with the trackpad on platforms such as macOS and Wayland that implement it. Fixes #819. Note that I have no idea whether the code works well on retina screens, might have to multiple the pixel count by the scale factor on those screens? --- docs/changelog.rst | 3 +++ glfw/cocoa_window.m | 9 ++------- glfw/glfw3.h | 4 +++- glfw/input.c | 4 ++-- glfw/internal.h | 2 +- glfw/wl_init.c | 11 +++-------- glfw/x11_window.c | 8 ++++---- kitty/config_data.py | 5 +++-- kitty/data-types.h | 2 +- kitty/glfw-wrapper.h | 2 +- kitty/glfw.c | 4 ++-- kitty/mouse.c | 47 ++++++++++++++++++++++++++++---------------- kitty/state.h | 1 + 13 files changed, 56 insertions(+), 46 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 88b381110..087ce40d7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -20,6 +20,9 @@ Changelog - Add an option :opt:`env` to set environment variables in child processes from kitty.conf +- Implement high precision scrolling with the trackpad on platforms such as + macOS and Wayland that implement it. (:pull:`819`) + - macOS: Allow scrolling window contents using mouse wheel/trackpad even when the window is not the active window (:iss:`729`) diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 7b35d4f68..27ef3347a 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -884,15 +884,10 @@ is_ascii_control_char(char x) { deltaX = [event scrollingDeltaX]; deltaY = [event scrollingDeltaY]; - - if ([event hasPreciseScrollingDeltas]) - { - deltaX *= 0.1; - deltaY *= 0.1; - } + int flags = [event hasPreciseScrollingDeltas] ? 1 : 0; if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0) - _glfwInputScroll(window, deltaX, deltaY); + _glfwInputScroll(window, deltaX, deltaY, flags); } - (NSDragOperation)draggingEntered:(id )sender diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 714909818..936d44760 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1372,15 +1372,17 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int); * @param[in] window The window that received the event. * @param[in] xoffset The scroll offset along the x-axis. * @param[in] yoffset The scroll offset along the y-axis. + * @param[in] flags A bit-mask providing extra data about the event. flags & 1 will be true if and only if the offset values are "high-precision". Typically pixel values. Otherwise the offset values are number of lines. * * @sa @ref scrolling * @sa @ref glfwSetScrollCallback * * @since Added in version 3.0. Replaces `GLFWmousewheelfun`. + * @since Changed in version 4.0. Added `flags` parameter. * * @ingroup input */ -typedef void (* GLFWscrollfun)(GLFWwindow*,double,double); +typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int); /*! @brief The function signature for key callbacks. * diff --git a/glfw/input.c b/glfw/input.c index 7f6089086..46d06abc4 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -286,10 +286,10 @@ void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, // Notifies shared code of a scroll event // -void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset) +void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags) { if (window->callbacks.scroll) - window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset); + window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags); } // Notifies shared code of a mouse button click event diff --git a/glfw/internal.h b/glfw/internal.h index dcc1b00a3..62171e728 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -717,7 +717,7 @@ void _glfwInputWindowCloseRequest(_GLFWwindow* window); void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor); void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state); -void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset); +void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered); diff --git a/glfw/wl_init.c b/glfw/wl_init.c index 2f204a10f..a4981e1da 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -319,11 +319,6 @@ static void pointerHandleAxis(void* data, { _GLFWwindow* window = _glfw.wl.pointerFocus; double x = 0.0, y = 0.0; - // Wayland scroll events are in pointer motion coordinate space (think two - // finger scroll). The factor 10 is commonly used to convert to "scroll - // step means 1.0. - const double scrollFactor = 1.0 / 10.0; - if (!window) return; @@ -331,11 +326,11 @@ static void pointerHandleAxis(void* data, axis == WL_POINTER_AXIS_VERTICAL_SCROLL); if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) - x = wl_fixed_to_double(value) * scrollFactor; + x = wl_fixed_to_double(value) * -1; else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) - y = wl_fixed_to_double(value) * scrollFactor; + y = wl_fixed_to_double(value) * -1; - _glfwInputScroll(window, x, y); + _glfwInputScroll(window, x, y, 1); } static const struct wl_pointer_listener pointerListener = { diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 213ead16d..a3cb4568b 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -1309,13 +1309,13 @@ static void processEvent(XEvent *event) // Modern X provides scroll events as mouse button presses else if (event->xbutton.button == Button4) - _glfwInputScroll(window, 0.0, 1.0); + _glfwInputScroll(window, 0.0, 1.0, 0); else if (event->xbutton.button == Button5) - _glfwInputScroll(window, 0.0, -1.0); + _glfwInputScroll(window, 0.0, -1.0, 0); else if (event->xbutton.button == Button6) - _glfwInputScroll(window, 1.0, 0.0); + _glfwInputScroll(window, 1.0, 0.0, 0); else if (event->xbutton.button == Button7) - _glfwInputScroll(window, -1.0, 0.0); + _glfwInputScroll(window, -1.0, 0.0, 0); else { diff --git a/kitty/config_data.py b/kitty/config_data.py index 8842061d8..aace1abd0 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -302,8 +302,9 @@ INPUT_LINE_NUMBER in the command line above will be replaced by an integer representing which line should be at the top of the screen.''')) o('wheel_scroll_multiplier', 5.0, long_text=_(''' -Modify the amount scrolled by the mouse wheel or touchpad. Use -negative numbers to change scroll direction.''')) +Modify the amount scrolled by the mouse wheel. Note this is only used for low +precision scrolling devices, not for high precision scrolling on platforms such +as macOS and Wayland. Use negative numbers to change scroll direction.''')) # }}} g('mouse') # {{{ diff --git a/kitty/data-types.h b/kitty/data-types.h index 09b793823..eb6ca402d 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -284,7 +284,7 @@ void enter_event(); void mouse_event(int, int); void focus_in_event(); void wakeup_io_loop(bool); -void scroll_event(double, double); +void scroll_event(double, double, int); void fake_scroll(int, bool); void set_special_key_combo(int glfw_key, int mods, bool is_native); void on_key_input(int key, int scancode, int action, int mods, const char*, int); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index d80919723..d9712a600 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1141,7 +1141,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int); * * @ingroup input */ -typedef void (* GLFWscrollfun)(GLFWwindow*,double,double); +typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int); /*! @brief The function signature for key callbacks. * diff --git a/kitty/glfw.c b/kitty/glfw.c index 7185a454c..8fc336621 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -178,12 +178,12 @@ cursor_pos_callback(GLFWwindow *w, double x, double y) { } static void -scroll_callback(GLFWwindow *w, double xoffset, double yoffset) { +scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags) { if (!set_callback_window(w)) return; show_mouse_cursor(w); double now = monotonic(); global_state.callback_os_window->last_mouse_activity_at = now; - if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset); + if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags); global_state.callback_os_window = NULL; } diff --git a/kitty/mouse.c b/kitty/mouse.c index 0c53116f2..e8fee5f2b 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -527,13 +527,7 @@ mouse_event(int button, int modifiers) { } void -scroll_event(double UNUSED xoffset, double yoffset) { - // glfw inverts the y-axis when reporting scroll events under wayland - // Until this is fixed in upstream, invert y ourselves. - if (global_state.is_wayland) yoffset *= -1; - int s = (int) round(yoffset * OPT(wheel_scroll_multiplier)); - if (s == 0) return; - bool upwards = s > 0; +scroll_event(double UNUSED xoffset, double yoffset, int flags) { bool in_tab_bar; unsigned int window_idx = 0; Window *w = window_for_event(&window_idx, &in_tab_bar); @@ -544,17 +538,36 @@ scroll_event(double UNUSED xoffset, double yoffset) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; if (t) w = t->windows + t->active_window; } - if (w) { - Screen *screen = w->render_data.screen; - if (screen->linebuf == screen->main_linebuf) { - screen_history_scroll(screen, abs(s), upwards); + if (!w) return; + + int s; + if (flags & 1) { + if (yoffset * global_state.callback_os_window->pending_scroll_pixels < 0) { + global_state.callback_os_window->pending_scroll_pixels = 0; // change of direction + } + double pixels = global_state.callback_os_window->pending_scroll_pixels + yoffset; + if (fabs(pixels) < global_state.callback_os_window->fonts_data->cell_height) { + global_state.callback_os_window->pending_scroll_pixels = pixels; + return; + } + s = abs(((int)round(pixels))) / global_state.callback_os_window->fonts_data->cell_height; + if (pixels < 0) s *= -1; + global_state.callback_os_window->pending_scroll_pixels = pixels - s; + } else { + s = (int) round(yoffset * OPT(wheel_scroll_multiplier)); + global_state.callback_os_window->pending_scroll_pixels = 0; + } + if (s == 0) return; + bool upwards = s > 0; + Screen *screen = w->render_data.screen; + if (screen->linebuf == screen->main_linebuf) { + screen_history_scroll(screen, abs(s), upwards); + } else { + if (screen->modes.mouse_tracking_mode) { + int sz = encode_mouse_event(w, upwards ? GLFW_MOUSE_BUTTON_4 : GLFW_MOUSE_BUTTON_5, PRESS, 0); + if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); } } else { - if (screen->modes.mouse_tracking_mode) { - int sz = encode_mouse_event(w, upwards ? GLFW_MOUSE_BUTTON_4 : GLFW_MOUSE_BUTTON_5, PRESS, 0); - if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); } - } else { - fake_scroll(abs(s), upwards); - } + fake_scroll(abs(s), upwards); } } } diff --git a/kitty/state.h b/kitty/state.h index 08f3b4e50..5c978a0d7 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -124,6 +124,7 @@ typedef struct { float background_opacity; FONTS_DATA_HANDLE fonts_data; id_type temp_font_group_id; + double pending_scroll_pixels; } OSWindow;