Wayland has 3 types of scrolling events:
- axis. Used for e.g., touchpad 2 finger smooth scrolling - axis_discrete. Used for mouse scroll wheels (i.e., notches) - axis_value120. Used for high resolution input devices
Wine currently only supports axis_discrete, meaning that 2 finger scroll events get ignored.
This commit tries to add basic support for axis scrolling events, by translating the smooth motion in scroll increments using some primitive assumptions about line height and number of lines to scroll.
-- v8: wip! winewayland.drv: Try to support smooth scroll events
From: Ray Strode rstrode@redhat.com
Wayland has 3 types of scrolling events:
- axis. Used for e.g., touchpad 2 finger smooth scrolling - axis_discrete. Used for mouse scroll wheels (i.e., notches) - axis_value120. Used for high resolution input devices
Wine currently only supports axis_discrete, meaning that 2 finger scroll events get ignored.
This commit tries to add basic support for axis scrolling events, by translating the smooth motion in scroll increments using some primitive assumptions about line height and number of lines to scroll. --- dlls/winewayland.drv/wayland_pointer.c | 101 +++++++++++++++++++++++++ dlls/winewayland.drv/waylanddrv.h | 4 + 2 files changed, 105 insertions(+)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index fad75c8506c..bf0d303f8cc 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -191,10 +191,104 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { + struct wayland_pointer *pointer = &process_wayland.pointer; + struct wayland_surface *surface; + INPUT input = {0}; + HWND hwnd; + /* Windows wheel events aren't in pixel units but a fractional unit + * proportional to the number of lines to scroll. By default a value of + * WHEEL_DELTA means scroll the application content by 3 lines. + * + * We also don't know exactly how big a "line" is in pixel units, which is + * what gets reported to this event handler. We can approximate it by + * saying a line is 18 pixels, and we're probably in the right ballpark + * most of the time. + * + * We also don't want to scroll the full swipe value in one go, because if + * it's a big swipe, it's likely to be several lines which can look + * discontinuous and jarring. For it to scroll relatively smoothly, we + * should report intermediate events at least every line. + */ + double pixels_per_line = 18.0; + double scale = 1.0; + const double lines_per_wheel_spin = 3.0; + const double wheel_delta_per_line = WHEEL_DELTA / lines_per_wheel_spin; + bool invert = false; + double lines_to_scroll; + int32_t direction; + double wheel_delta_for_value; + + double pixels_scrolled; + double residual_pixels; + + if (!(hwnd = wayland_pointer_get_focused_hwnd())) return; + + if (!(surface = wayland_surface_lock_hwnd(hwnd))) return; + + scale = surface->window.scale; + pthread_mutex_unlock(&surface->mutex); + + pixels_per_line *= scale; + + if (pointer->scroll_frame_number == pointer->next_frame_number) return; + + input.type = INPUT_MOUSE; + + switch (axis) + { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + input.mi.dwFlags = MOUSEEVENTF_WHEEL; + value += pointer->incomplete_vertical_scroll_value; + invert = true; + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + input.mi.dwFlags = MOUSEEVENTF_HWHEEL; + value += pointer->incomplete_horizontal_scroll_value; + break; + default: break; + } + + pixels_scrolled = wl_fixed_to_double(value); + direction = pixels_scrolled >= 0? 1 : -1; + + lines_to_scroll = fabs(pixels_scrolled / pixels_per_line) * lines_per_wheel_spin; + wheel_delta_for_value = lines_to_scroll * wheel_delta_per_line; + + TRACE("hwnd=%p axis=%u value=%lf\n", hwnd, axis, pixels_scrolled); + + for (int32_t i = 0; i < (int32_t) lines_to_scroll; i++) + { + wheel_delta_for_value -= (int32_t) wheel_delta_per_line; + + input.mi.mouseData = (int32_t) wheel_delta_per_line; + input.mi.mouseData *= direction; + + if (invert) + input.mi.mouseData *= -1; + + __wine_send_input(hwnd, &input, NULL); + } + + residual_pixels = (wheel_delta_for_value / wheel_delta_per_line) * pixels_per_line; + residual_pixels *= direction; + + switch (axis) + { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + pointer->incomplete_vertical_scroll_value = wl_fixed_from_double(residual_pixels); + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + pointer->incomplete_horizontal_scroll_value = wl_fixed_from_double(residual_pixels); + break; + default: break; + } }
static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) { + struct wayland_pointer *pointer = &process_wayland.pointer; + + pointer->next_frame_number++; }
static void pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer, @@ -210,6 +304,7 @@ static void pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, static void pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + struct wayland_pointer *pointer = &process_wayland.pointer; INPUT input = {0}; HWND hwnd;
@@ -233,6 +328,7 @@ static void pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_point TRACE("hwnd=%p axis=%u discrete=%d\n", hwnd, axis, discrete);
__wine_send_input(hwnd, &input, NULL); + pointer->scroll_frame_number = pointer->next_frame_number; }
static const struct wl_pointer_listener pointer_listener = @@ -333,6 +429,10 @@ void wayland_pointer_init(struct wl_pointer *wl_pointer) pointer->wl_pointer = wl_pointer; pointer->focused_hwnd = NULL; pointer->enter_serial = 0; + pointer->scroll_frame_number = 0; + pointer->next_frame_number = 0; + pointer->incomplete_vertical_scroll_value = 0; + pointer->incomplete_horizontal_scroll_value = 0; pthread_mutex_unlock(&pointer->mutex); wl_pointer_add_listener(pointer->wl_pointer, &pointer_listener, NULL); } @@ -361,6 +461,7 @@ void wayland_pointer_deinit(void) pointer->wl_pointer = NULL; pointer->focused_hwnd = NULL; pointer->enter_serial = 0; + pointer->next_frame_number = 0; pthread_mutex_unlock(&pointer->mutex); }
diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 0883c43f1ff..d174ddd08ed 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -99,6 +99,10 @@ struct wayland_pointer HWND constraint_hwnd; uint32_t enter_serial; uint32_t button_serial; + uint32_t scroll_frame_number; + uint32_t next_frame_number; + wl_fixed_t incomplete_horizontal_scroll_value; + wl_fixed_t incomplete_vertical_scroll_value; struct wayland_cursor cursor; pthread_mutex_t mutex; };
(so i think this is in an okay state now, but to get the kind of speed warren wanted, I had to go back to scrolling three lines per small swipe. this means it's hard to scroll to a precise line. Above I discussed adding a non-linear curve, and I even considered something kookier like using the drawn vector to set up momentum, but I think those ideas are likely to have compat concerns with apps that use the scroll events in a less than standard way.
What I think I might do at some point (got to put this down for a while) is look for changes in direction mid-swipe. If one is found, the slow down the speed to scroll line by line instead of 3 lines at a time. I think users will naturally try this without even thinking, so it may be a good easy solution.
(but won't know if it feels right in practice until I try it. it shouldn't be more than 3-5 lines of code, but will experiment later when i have time)
Tested e05923e859e5b931dc778a9a4bedff98000febba. I'm a bit confused. In the above "unset DISPLAY; wine explorer.exe" test it now scrolls a lot faster, 20 full swipes to get from top to bottom vs 18 swipes with xwayland. That may seem very similar but in another app it feels a lot slower than Xwayland. Is something else interpreting the input differently?
oh it might be ignoring the scroll amount and just treating each event as a one increment scroll or something like that.
i'll push a little (untested) change to add a workaround for that.
I'm thinking I'm going to rework this though next time I get back to this. I think it would be better to do everything from `pointer_handle_frame` (and just characterize the swipe from the axis events)