Skip to content

input: fix touchpad_scroll_speed and apply it to all scroll methods - #3184

Open
italopaesano wants to merge 2 commits into
WayfireWM:masterfrom
italopaesano:fix/3148-touchpad-scroll-speed
Open

italopaesano wants to merge 2 commits into
WayfireWM:masterfrom
italopaesano:fix/3148-touchpad-scroll-speed

Conversation

@italopaesano

Copy link
Copy Markdown
Contributor

input/touchpad_scroll_speed currently has no effect at all, and button
scrolling on a touchpad has never used it.

The option never applies

get_scroll_speed() required a WLR_INPUT_DEVICE_TABLET_PAD to return
touchpad_scroll_speed:

if ((touchpad && (dev->type != WLR_INPUT_DEVICE_TABLET_PAD)) ||
    (!touchpad && (dev->type != WLR_INPUT_DEVICE_POINTER)))
{
    return 1.0;
}

Its only caller is pointer_t::handle_pointer_axis(), which passes
&ev->pointer->base. wlroots guarantees that device to be of type
WLR_INPUT_DEVICE_POINTER — wlr_pointer_from_input_device() asserts it. The
first branch is therefore always true for finger scrolling and the function
returns 1.0. Tablet pads, the only devices which would have passed the
check, do not emit pointer axis events; for them base.data would not point to
a pointing_device_t either.

This is a regression from 1a93066: before that commit handle_pointer_axis()
applied config.touchpad_scroll_speed directly to
WL_POINTER_AXIS_SOURCE_FINGER events.

The axis source alone is not enough

libinput reports button scrolling as WL_POINTER_AXIS_SOURCE_CONTINUOUS, and
on-button-down is one of the touchpad scroll methods Wayfire offers, so a
touchpad configured that way kept using mouse_scroll_speed. That case never
worked, not even before the regression, because the old code used the same
source-based test.

The source is still needed, though: pointers which do not come from libinput —
for example when a parent compositor forwards finger scrolling to a nested
session — cannot be recognised as touchpads.

The change

Use the touchpad speed when either the device is a touchpad or the event is
finger scrolling:

double wf::pointing_device_t::get_scroll_speed(wl_pointer_axis_source source)
{
    const bool touchpad = is_touchpad || (source == WL_POINTER_AXIS_SOURCE_FINGER);
    return touchpad ? touchpad_scroll_speed : mouse_scroll_speed;
}

is_touchpad comes from libinput_device_config_tap_get_finger_count(dev) > 0,
the check update_options() already uses for every other touchpad option. It
is computed once in the constructor, since the capability does not change at
runtime, and reused in both places, so the compositor has a single definition
of what a touchpad is. Every other touchpad option — natural scroll, tap to
click, acceleration profile, cursor speed — has always been device-based; the
scroll speed was the exception.

The device is no longer passed in, as it was only used by a condition which
could never be true.

Tests

The second commit adds a headless test which announces two dummy pointer
devices and checks the delta an axis event comes out with: finger scrolling,
wheel scrolling, and button scrolling on a touchpad and on another device. One
device is marked as a touchpad the way libinput would report a real one,
because a dummy device is not a libinput device. pointer_axis() is added to
the test harness alongside the existing pointer_button().

The test includes wlr/interfaces/wlr_pointer.h in an extern "C" block of
its own, the way plugins/ipc/stipc.cpp does, rather than adding it to the
installed wlroots-full.hpp: the interfaces header declares itself unstable,
and no plugin needs it.

Verification:

  • ninja -C build test: 35/35, and 33/33 with -Dxwayland=disabled, which is
    what CI builds;
  • with the old condition restored, the finger case fails with
    CHECK( 10 == Approx( 1 ) );
  • with only the is_touchpad term removed, exactly the button scrolling case
    on a touchpad fails with CHECK( 5 == Approx( 1 ) ), so both terms of the
    condition are covered by the test;
  • uncrustify reports no changes.

What the tests do not cover: that a physical touchpad produces these axis
sources, which follows from the libinput backend mapping in wlroots
(backend/libinput/pointer.c) and from the report in the issue, and the
libinput side of is_touchpad, since a dummy device is not a libinput device.

One user-visible consequence of the second part: users who compensated for this
by setting mouse_scroll_speed for a touchpad in on-button-down mode will
need to move the value to touchpad_scroll_speed.

The first part of the fix is the one suggested in #3148 by the reporter.

Fixes #3148

italopaesano and others added 2 commits October 27, 2026 06:42
touchpad_scroll_speed had no effect at all. get_scroll_speed() required a
WLR_INPUT_DEVICE_TABLET_PAD to apply it, but its only caller passes
&ev->pointer->base, which wlroots guarantees to be of type
WLR_INPUT_DEVICE_POINTER -- it asserts so in wlr_pointer_from_input_device().
The condition was therefore always true for finger scrolling and the function
returned 1.0. Tablet pads, the only devices that would have passed the check,
do not emit pointer axis events; for them the cast of base.data to
pointing_device_t would not have been valid either.

Deciding this from the axis source alone is not enough: libinput reports
button scrolling as WL_POINTER_AXIS_SOURCE_CONTINUOUS, and on-button-down is
one of the touchpad scroll methods Wayfire offers, so a touchpad configured
that way kept using mouse_scroll_speed. The source is still needed, though,
for pointers which do not come from libinput, such as events forwarded by a
parent compositor in a nested session.

Use the touchpad speed when either the device is a touchpad or the event is
finger scrolling, and take the device type from the same libinput check which
update_options() already uses for every other touchpad option, so that the
compositor has a single definition of what a touchpad is. The check moves to
the constructor since the capability does not change at runtime.

The device type is no longer passed in, as it was only used by a condition
which could never be true.

Users who compensated for this by setting mouse_scroll_speed for a touchpad in
on-button-down mode will need to move the value to touchpad_scroll_speed.

Fixes WayfireWM#3148

Co-Authored-By: Claude <noreply@anthropic.com>
Add a headless test which announces two dummy pointer devices, sends axis
events through the pointer input path and checks the resulting delta against
the configured multiplier: finger and wheel scrolling, and button scrolling on
a touchpad and on another device.

Neither backend available to CI can produce touchpad input -- the X11 backend
only emits WL_POINTER_AXIS_SOURCE_WHEEL and the headless backend has no input
devices -- so the test drives pointer_t::handle_pointer_axis() through the
existing harness, in the same way pointer_button() already does, and marks the
dummy device as a touchpad the way libinput would report a real one.

The test includes wlr/interfaces/wlr_pointer.h in an extern "C" block of its
own, the way plugins/ipc/stipc.cpp does, since that header has no guard of its
own and carries no stability guarantees.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

touchpad_scroll_speed is ignored for touchpads (get_scroll_speed checks for TABLET_PAD)

1 participant