From 0523fce4eb7d723a9fdc3e0b31fdc06b1cc2f888 Mon Sep 17 00:00:00 2001 From: mako yass Date: Sun, 3 May 2026 09:32:10 +1200 Subject: [PATCH] evdev: replace delta-length axis-lock with distance + delta tilt Fixes biaxial button-scrolling. Replaces the previous delta-length threshold for breaking out of axis-lock with a perpendicular-distance threshold combined with delta tilting toward the locked axis line. --- src/evdev-mt-touchpad-gestures.c | 17 -- src/evdev.c | 324 ++++++++++++++++++++++++------- src/evdev.h | 12 +- test/test-pointer.c | 133 +++++++++++-- test/test-touchpad.c | 6 +- test/test-trackpoint.c | 12 +- 6 files changed, 390 insertions(+), 114 deletions(-) diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index 03a081c0..da8ef134 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -321,21 +321,6 @@ tp_gesture_init_pinch(struct tp_dispatch *tp) tp->gesture.prev_scale = 1.0; } -static void -tp_gesture_set_scroll_buildup(struct tp_dispatch *tp) -{ - struct device_float_coords d0, d1; - struct device_float_coords average; - struct tp_touch *first = tp->gesture.touches[0], - *second = tp->gesture.touches[1]; - - d0 = device_delta(first->point, first->gesture.initial); - d1 = device_delta(second->point, second->gesture.initial); - - average = device_float_average(d0, d1); - tp->device->scroll.buildup = tp_normalize_delta(tp, average); -} - static void tp_gesture_apply_scroll_constraints(struct tp_dispatch *tp, struct device_float_coords *raw, @@ -637,7 +622,6 @@ tp_gesture_handle_event_on_state_unknown(struct tp_dispatch *tp, break; case GESTURE_EVENT_SCROLL_START: libinput_timer_cancel(&tp->gesture.hold_timer); - tp_gesture_set_scroll_buildup(tp); tp->gesture.state = GESTURE_STATE_SCROLL_START; break; case GESTURE_EVENT_SWIPE_START: @@ -693,7 +677,6 @@ tp_gesture_handle_event_on_state_hold(struct tp_dispatch *tp, tp->gesture.state = GESTURE_STATE_POINTER_MOTION; break; case GESTURE_EVENT_SCROLL_START: - tp_gesture_set_scroll_buildup(tp); tp_gesture_cancel(tp, time); tp->gesture.state = GESTURE_STATE_SCROLL_START; break; diff --git a/src/evdev.c b/src/evdev.c index cac971f4..1ed77027 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -56,6 +56,12 @@ #define DEFAULT_BUTTON_SCROLL_TIMEOUT usec_from_millis(200) #define SCROLL_BUTTON_LOCK_GRACE_TIMEOUT usec_from_millis(500) +/* the angle by which a scroll button-drag delta is tilted towards the axis by the axis + * magnetism effect */ +#define SCROLL_DELTA_TILT_ANGLE (M_PI * 0.24) +/* the distance at which it decides which axis it's on for the antijitter measure. */ +#define SCROLL_ANTIJITTER_AXIS_DECISION_DISTANCE 5 + enum evdev_device_udev_tags { EVDEV_UDEV_TAG_NONE = 0, EVDEV_UDEV_TAG_INPUT = bit(0), @@ -2343,8 +2349,8 @@ evdev_device_create(struct libinput_seat *seat, struct udev_device *udev_device) /* the log_prefix_name is used as part of a printf format string and * must not contain % directives, see evdev_log_msg */ device->log_prefix_name = str_sanitize(device->devname); - device->scroll.threshold = 5.0; /* Default may be overridden */ - device->scroll.direction_lock_threshold = 5.0; /* Default may be overridden */ + device->scroll.threshold = 5.0; /* Default may be overridden */ + device->scroll.direction_lock_threshold = 18.0; /* Default may be overridden */ device->scroll.direction = 0; device->scroll.wheel_click_angle = evdev_read_wheel_click_props(device); device->model_flags = evdev_read_model_flags(device); @@ -2723,95 +2729,263 @@ evdev_device_has_switch(struct evdev_device *device, enum libinput_switch sw) return libevdev_has_event_code(device->evdev, EV_SW, code); } -static inline bool -evdev_is_scrolling(const struct evdev_device *device, enum libinput_pointer_axis axis) +/* Rotation by the normal vector, `rot`, anticlockwise. + * if `rot` isn't a normal, this includes a scale transform, by its magnitude. */ +static inline struct normalized_coords +v2_rotate(struct normalized_coords rot, struct normalized_coords subject) { - assert(axis == LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL || - axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); - - return (device->scroll.direction & bit(axis)) != 0; + return (struct normalized_coords){ + rot.x * subject.x - rot.y * subject.y, + rot.y * subject.x + rot.x * subject.y, + }; } -static inline void -evdev_start_scrolling(struct evdev_device *device, enum libinput_pointer_axis axis) +/* Rotation by the normal vector, `rot`, clockwise. + * if `rot` isn't a normal, this includes a scale transform, by its magnitude. */ +static inline struct normalized_coords +v2_counter_rotate(struct normalized_coords rot, struct normalized_coords subject) { - assert(axis == LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL || - axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); - - device->scroll.direction |= bit(axis); + return (struct normalized_coords){ + rot.x * subject.x + rot.y * subject.y, + -rot.y * subject.x + rot.x * subject.y, + }; } +static inline struct normalized_coords +v2_add(struct normalized_coords a, struct normalized_coords b) +{ + return (struct normalized_coords){ + a.x + b.x, + a.y + b.y, + }; +} + +static inline struct normalized_coords +v2_subtract(struct normalized_coords a, struct normalized_coords b) +{ + return (struct normalized_coords){ + a.x - b.x, + a.y - b.y, + }; +} + +static inline double +v2_magnitude(struct normalized_coords v) +{ + return hypot(v.x, v.y); +} + +/* Rotates the delta from point acc towards the given axis/angle, `dir`. + * The tilt is scaled by cos²(angle between delta and the axis): axis-aligned + * motion gets the full pull, and motion turning perpendicular is left almost + * free, so an intentional outward stroke isn't fought. + * The math runs in dir's frame and the result is rotated back. + * Returns the world-frame scroll delta to emit. + * It actually behaves the same for forwards or backwards pointing dir. */ +static struct normalized_coords +tilt_delta_for_orientation(struct normalized_coords pos, + struct normalized_coords delta, + struct normalized_coords dir, + double max_tilt_angle) +{ + struct normalized_coords pos_rel = v2_counter_rotate(dir, pos); + struct normalized_coords delta_rel = v2_counter_rotate(dir, delta); + /* we modulate the tilt angle so that it's at its peak only when the delta is + * aligned with the axis. Moving towards the axis need not be accelerated, and + * moving directly away tends to be intentional and shouldn't be impeded. */ + double dl = v2_magnitude(delta_rel); + double alignedness = (dl > 0) ? delta_rel.x / dl : 0; + /* there's no firm logic to squaring it here, it just feels more beautiful, and + * seems to accentuate the effect to the point where it makes diagonal starts + * more comfortable and reliable. */ + double tilt_angle = max_tilt_angle * fabs(alignedness) * fabs(alignedness); + struct normalized_coords nextpos_local_tentative_plus_untilted = + v2_add(pos_rel, delta_rel); + struct normalized_coords flipped_tilt = { + cos(tilt_angle), + /* this ensures that it tilts dr in whichever rotational direction + points towards the y=0 */ + sin(tilt_angle) * + (nextpos_local_tentative_plus_untilted.y <= 0 ? -1.0 : 1.0) * + -(delta_rel.x >= 0 ? 1.0 : -1.0), + }; + struct normalized_coords tilted_dr = v2_rotate(flipped_tilt, delta_rel); + struct normalized_coords nextpos_local_tentative_plus_tilted = + v2_add(pos_rel, tilted_dr); + bool crosses_line = nextpos_local_tentative_plus_untilted.y * + nextpos_local_tentative_plus_tilted.y <= + 0; + struct normalized_coords nextpos_local; + if (crosses_line) { + /* then we'll angle it just so that it lands on 0, while preserves the + * delta's length. The following is equivalent to this formula, but we + * do something a bit more careful to make sure we definitely can't sqrt + * a negative. In theory that should never happen, but we should just + * code to make sure it can't */ + /* tl_x = tr.x + dir_sign * sqrt(dl*dl - tr.y*tr.y) */ + double sqrand = dl * dl - pos_rel.y * pos_rel.y; + double tl_x = sqrand >= 0 + ? pos_rel.x + ((delta_rel.x >= 0) ? 1.0 : -1.0) * + sqrt(sqrand) + : 0; + nextpos_local = (struct normalized_coords){ tl_x, 0 }; + } else { + nextpos_local = nextpos_local_tentative_plus_tilted; + } + struct normalized_coords nextpos = v2_rotate(dir, nextpos_local); + return v2_subtract(nextpos, pos); +} + +/* moves the indicated axis towards 0 by antijitter_margin. */ +static inline struct normalized_coords +deadband_axis(bool vertical, struct normalized_coords v, double antijitter_margin) +{ + return vertical ? (struct + normalized_coords){ v.x, + v.y > 0.0 + ? fmax(0.0, + v.y - antijitter_margin) + : fmin(0.0, + v.y + antijitter_margin) } + : (struct normalized_coords){ + v.x > 0.0 ? fmax(0.0, v.x - antijitter_margin) + : fmin(0.0, v.x + antijitter_margin), + v.y + }; +} + +/* Suppresses scroll output until accumulated motion exceeds scroll.threshold (a + * click-jitter filter), then runs a subtle but effective axis-lock where the delta is + * rotated somewhat towards the nearest axis line. If the accumulator moves + * scroll.direction_lock_threshold away from either axis line, it breaks out of axis + * lock and scrolls freely from then on. + */ void evdev_post_scroll(struct evdev_device *device, usec_t time, enum libinput_pointer_axis_source source, const struct normalized_coords *delta) { - const struct normalized_coords *trigger; - struct normalized_coords event; + struct normalized_coords *acc = &device->scroll.accumulator; + struct normalized_coords delta_post = *delta; - if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) - device->scroll.buildup.y += delta->y; - if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) - device->scroll.buildup.x += delta->x; - - trigger = &device->scroll.buildup; - - /* If we're not scrolling yet, use a distance trigger: moving - past a certain distance starts scrolling */ - if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) && - !evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) { - if (fabs(trigger->y) >= device->scroll.threshold) - evdev_start_scrolling(device, - LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); - if (fabs(trigger->x) >= device->scroll.threshold) - evdev_start_scrolling(device, - LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); - /* We're already scrolling in one direction. Require some - trigger speed to start scrolling in the other direction */ - } else if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) { - if (fabs(delta->y) >= device->scroll.direction_lock_threshold) - evdev_start_scrolling(device, - LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); - } else if (!evdev_is_scrolling(device, - LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) { - if (fabs(delta->x) >= device->scroll.direction_lock_threshold) - evdev_start_scrolling(device, - LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); + /* activate antijitter bevel once it's clear which axis the scroll is moving + * into */ + if (device->scroll.antijitter_axes == 0) { + if (fabs(acc->x) > fabs(acc->y)) { + if (fabs(acc->x) > SCROLL_ANTIJITTER_AXIS_DECISION_DISTANCE) { + device->scroll.antijitter_axes |= + bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); + } + } else { + if (fabs(acc->y) > SCROLL_ANTIJITTER_AXIS_DECISION_DISTANCE) { + device->scroll.antijitter_axes |= + bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); + } + } } - event = *delta; + if (!device->scroll.exceeded_threshold) { + *acc = v2_add(*acc, *delta); + double acc_mag = v2_magnitude(*acc); + if (acc_mag <= device->scroll.threshold) + return; + /* take the post-threshold overshoot as the first emitted delta, + * starting axis-lock state from origin so there's no jump on + * engage. */ + delta_post = *acc; + *acc = (struct normalized_coords){ 0.0, 0.0 }; + device->scroll.exceeded_threshold = true; + } - /* We use the trigger to enable, but the delta from this event for - * the actual scroll movement. Otherwise we get a jump once - * scrolling engages */ - if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) - event.y = 0.0; + struct normalized_coords estimate_next_acc = v2_add(*acc, delta_post); - if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) - event.x = 0.0; + bool is_unlocked = device->scroll.antijitter_axes == + (bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) | + bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)); - if (!normalized_is_zero(event)) { - uint32_t axes = device->scroll.direction; - - if (event.y == 0.0) - axes &= ~bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); - if (event.x == 0.0) - axes &= ~bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); - - switch (source) { - case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: - evdev_notify_axis_finger(device, time, axes, &event); - break; - case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: - evdev_notify_axis_continous(device, time, axes, &event); - break; - default: - evdev_log_bug_libinput(device, - "Posting invalid scroll source %d\n", - source); - break; + struct normalized_coords scroll_movement; + if ((estimate_next_acc.x == 0 && estimate_next_acc.y == 0) || is_unlocked) { + scroll_movement = delta_post; + } else if (fabs(estimate_next_acc.x) < fabs(estimate_next_acc.y)) { + if (fabs(acc->x) < device->scroll.direction_lock_threshold) { + scroll_movement = tilt_delta_for_orientation( + *acc, + delta_post, + (struct normalized_coords){ + 0.0, + estimate_next_acc.y < 0 ? -1.0 : 1.0, + }, + SCROLL_DELTA_TILT_ANGLE); + } else { + device->scroll.antijitter_axes |= + bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); + scroll_movement = delta_post; } + } else { + if (fabs(acc->y) < device->scroll.direction_lock_threshold) { + scroll_movement = tilt_delta_for_orientation( + *acc, + delta_post, + (struct normalized_coords){ + estimate_next_acc.x < 0 ? -1.0 : 1.0, + 0.0, + }, + SCROLL_DELTA_TILT_ANGLE); + } else { + device->scroll.antijitter_axes |= + bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); + scroll_movement = delta_post; + } + } + + struct normalized_coords prev_acc = *acc; + *acc = v2_add(*acc, scroll_movement); + + /* because we often get mouse move events in one pixel increments, and because + * one pixel off-axis move events can't be effectively smoothed away (no matter + * how few of them there are) because they're always perfectly perpendicular to + * the axis even when net mouse movement is mostly aligned,we need to sort of + * ignore one-pixel movements. We do it by moving all points one pixel closer to + * the axes so that movement within that gutter isn't perceived. */ + bool antijitter_horiz = device->scroll.antijitter_axes & + bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); + bool antijitter_vert = device->scroll.antijitter_axes & + bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); + struct normalized_coords scroll_event = + (antijitter_vert ^ antijitter_horiz) + ? v2_subtract(deadband_axis( + !antijitter_vert, + *acc, + device->scroll.direction_lock_threshold), + deadband_axis( + !antijitter_vert, + prev_acc, + device->scroll.direction_lock_threshold)) + : scroll_movement; + + if (scroll_event.x == 0.0 && scroll_event.y == 0.0) + return; + + uint32_t axes = 0; + if (scroll_event.x != 0.0) + axes |= bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); + if (scroll_event.y != 0.0) + axes |= bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); + device->scroll.direction |= axes; + + switch (source) { + case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: + evdev_notify_axis_finger(device, time, axes, &scroll_event); + break; + case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: + evdev_notify_axis_continous(device, time, axes, &scroll_event); + break; + default: + evdev_log_bug_libinput(device, + "Posting invalid scroll source %d\n", + source); + break; } } @@ -2845,8 +3019,10 @@ evdev_stop_scroll(struct evdev_device *device, } } - device->scroll.buildup.x = 0; - device->scroll.buildup.y = 0; + device->scroll.accumulator.x = 0; + device->scroll.accumulator.y = 0; + device->scroll.exceeded_threshold = false; + device->scroll.antijitter_axes = 0; device->scroll.direction = 0; } diff --git a/src/evdev.h b/src/evdev.h index 77561577..500d958f 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -228,10 +228,20 @@ struct evdev_device { /* Checks if buttons are down and commits the setting */ void (*change_scroll_method)(struct evdev_device *device); enum evdev_button_scroll_state button_scroll_state; + /* Minimum accumulated motion magnitude before any scroll + * event is emitted. Filters out small jitter on press. */ double threshold; + /* Sets the perpendicular distance from an axis line at + * which the soft axis-lock in evdev_post_scroll() breaks + * and biaxial scrolling is allowed; the actual half-margin + * used is direction_lock_threshold / 2. */ double direction_lock_threshold; uint32_t direction; - struct normalized_coords buildup; + + /* Scroll axis-locking state. See evdev_post_scroll(). */ + struct normalized_coords accumulator; + int antijitter_axes; + bool exceeded_threshold; struct libinput_device_config_natural_scroll config_natural; /* set during device init if we want natural scrolling, diff --git a/test/test-pointer.c b/test/test-pointer.c index 56540551..4b579eb2 100644 --- a/test/test-pointer.c +++ b/test/test-pointer.c @@ -1090,7 +1090,17 @@ START_TEST(pointer_scroll_wheel_lenovo_scrollpoint) /* Lenovo ScrollPoint has a trackstick instead of a wheel, data sent * via REL_WHEEL is close to x/y coordinate space. + * + * The first scroll event after a press absorbs scroll.threshold (5) + * of motion. Send a priming event + * of magnitude 18 first so the actual test events emit their full + * delta. */ + litest_event(dev, EV_REL, REL_WHEEL, 18); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_dispatch(li); + litest_drain_events(li); + litest_event(dev, EV_REL, REL_WHEEL, 30); litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_REL, REL_WHEEL, -60); @@ -1552,22 +1562,25 @@ START_TEST(pointer_scroll_button) litest_drain_events(li); - litest_button_scroll(dev, BTN_LEFT, 1, 6); + /* the scroll-on threshold eats ~5 from the input magnitude and the + * antijitter bevel takes another 1 off the emitted value, so we push + * the scroll by enough to leave the asserted minimum on the wire. */ + litest_button_scroll(dev, BTN_LEFT, 1, 13); litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); - litest_button_scroll(dev, BTN_LEFT, 1, -7); + litest_button_scroll(dev, BTN_LEFT, 1, -14); litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -7); - litest_button_scroll(dev, BTN_LEFT, 8, 1); + litest_button_scroll(dev, BTN_LEFT, 15, 1); litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 8); - litest_button_scroll(dev, BTN_LEFT, -9, 1); + litest_button_scroll(dev, BTN_LEFT, -16, 1); litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, @@ -1591,6 +1604,95 @@ START_TEST(pointer_scroll_button) } END_TEST +START_TEST(pointer_scroll_button_axis_unlock) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + int i; + + libinput_device_config_scroll_set_method(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); + libinput_device_config_scroll_set_button(dev->libinput_device, BTN_LEFT); + litest_drain_events(li); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_timeout_buttonscroll(li); + litest_drain_events(li); + + /* 30 units of vertical motion commits to the vertical axis */ + for (i = 0; i < 30; i++) { + litest_event(dev, EV_REL, REL_Y, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + litest_dispatch(li); + litest_drain_events(li); + + /* Sustained horizontal motion should rotate the direction accumulator + * and eventually break out of the vertical lock */ + for (i = 0; i < 30; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_dispatch(li); + + litest_assert_scroll(li, + LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, + LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, + 1); +} +END_TEST + +START_TEST(pointer_scroll_button_axis_lock_drift) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + int i; + int n_vertical = 0; + + libinput_device_config_scroll_set_method(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); + libinput_device_config_scroll_set_button(dev->libinput_device, BTN_LEFT); + litest_drain_events(li); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_timeout_buttonscroll(li); + litest_drain_events(li); + + /* Sustained vertical motion with small simultaneous horizontal noise, + * as happens when a hand drifts slightly off-axis during scrolling. + * The vertical axis lock must hold — no horizontal scroll emitted. */ + for (i = 0; i < 30; i++) { + litest_event(dev, EV_REL, REL_X, 1); + litest_event(dev, EV_REL, REL_Y, 3); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + litest_dispatch(li); + + struct libinput_event *event; + while ((event = libinput_get_event(li))) { + enum libinput_event_type t = libinput_event_get_type(event); + if (t == LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS || + t == LIBINPUT_EVENT_POINTER_AXIS) { + struct libinput_event_pointer *ptrev = + libinput_event_get_pointer_event(event); + litest_assert(!libinput_event_pointer_has_axis( + ptrev, + LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)); + if (libinput_event_pointer_has_axis( + ptrev, + LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) + n_vertical++; + } + libinput_event_destroy(event); + } + litest_assert_int_gt(n_vertical, 30); + + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_dispatch(li); +} +END_TEST + START_TEST(pointer_scroll_button_noscroll) { struct litest_device *dev = litest_current_device(); @@ -1764,8 +1866,8 @@ START_TEST(pointer_scroll_button_lock) litest_timeout_buttonscroll(li); for (int i = 0; i < 10; i++) { - litest_event(dev, EV_REL, REL_X, 1); - litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_REL, REL_X, 3); + litest_event(dev, EV_REL, REL_Y, 13); litest_event(dev, EV_SYN, SYN_REPORT, 0); } @@ -1873,8 +1975,8 @@ START_TEST(pointer_scroll_button_lock_enable_while_down) litest_timeout_buttonscroll(li); for (int i = 0; i < 10; i++) { - litest_event(dev, EV_REL, REL_X, 1); - litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_REL, REL_X, 3); + litest_event(dev, EV_REL, REL_Y, 13); litest_event(dev, EV_SYN, SYN_REPORT, 0); } @@ -1937,8 +2039,8 @@ START_TEST(pointer_scroll_button_lock_enable_while_down_just_lock) litest_timeout_buttonscroll(li); for (int i = 0; i < 10; i++) { - litest_event(dev, EV_REL, REL_X, 1); - litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_REL, REL_X, 3); + litest_event(dev, EV_REL, REL_Y, 13); litest_event(dev, EV_SYN, SYN_REPORT, 0); } @@ -2056,8 +2158,8 @@ START_TEST(pointer_scroll_button_lock_enable_while_otherbutton_down) litest_assert_empty_queue(li); for (int i = 0; i < 10; i++) { - litest_event(dev, EV_REL, REL_X, 1); - litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_REL, REL_X, 3); + litest_event(dev, EV_REL, REL_Y, 13); litest_event(dev, EV_SYN, SYN_REPORT, 0); } @@ -2151,10 +2253,9 @@ START_TEST(pointer_scroll_button_lock_middlebutton) litest_timeout_middlebutton(li); litest_timeout_buttonscroll(li); - /* motion events are the same for all of them */ for (int i = 0; i < 10; i++) { - litest_event(dev, EV_REL, REL_X, 1); - litest_event(dev, EV_REL, REL_Y, 6); + litest_event(dev, EV_REL, REL_X, 2); + litest_event(dev, EV_REL, REL_Y, 13); litest_event(dev, EV_SYN, SYN_REPORT, 0); } @@ -3933,6 +4034,8 @@ TEST_COLLECTION(pointer) litest_add_for_device(pointer_scroll_wheel_no_inhibit_small_deltas_when_virtual, LITEST_MOUSE_VIRTUAL); litest_add_for_device(pointer_scroll_wheel_lenovo_scrollpoint, LITEST_LENOVO_SCROLLPOINT); litest_add(pointer_scroll_button, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add(pointer_scroll_button_axis_unlock, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add(pointer_scroll_button_axis_lock_drift, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); litest_add(pointer_scroll_button_noscroll, LITEST_ABSOLUTE|LITEST_BUTTON, LITEST_RELATIVE); litest_add(pointer_scroll_button_noscroll, LITEST_ANY, LITEST_RELATIVE|LITEST_BUTTON); litest_add(pointer_scroll_button_no_event_before_timeout, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); diff --git a/test/test-touchpad.c b/test/test-touchpad.c index 9884da4b..3811ecb4 100644 --- a/test/test-touchpad.c +++ b/test/test-touchpad.c @@ -453,9 +453,9 @@ START_TEST(touchpad_2fg_scroll_slow_distance) litest_assert(axisval >= 0.0); /* this is to verify we test the right thing, if the value - is greater than scroll.threshold we triggered the wrong - condition */ - litest_assert(axisval < 5.0); + is greater than direction_lock_threshold we triggered the + wrong condition */ + litest_assert(axisval < 13.0); libinput_event_destroy(event); event = libinput_get_event(li); diff --git a/test/test-trackpoint.c b/test/test-trackpoint.c index f0383633..c0323d19 100644 --- a/test/test-trackpoint.c +++ b/test/test-trackpoint.c @@ -81,22 +81,26 @@ START_TEST(trackpoint_scroll) litest_drain_events(li); - litest_button_scroll(dev, BTN_MIDDLE, 1, 6); + /* Each litest_button_scroll() sends a single motion event after the + * button-scroll timeout. The new scroll logic absorbs scroll.threshold + * (5) of motion magnitude before emitting, so the dominant axis is + * bumped by 5 here to keep the emitted value matching the assertion. */ + litest_button_scroll(dev, BTN_MIDDLE, 1, 11); litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); - litest_button_scroll(dev, BTN_MIDDLE, 1, -7); + litest_button_scroll(dev, BTN_MIDDLE, 1, -12); litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -7); - litest_button_scroll(dev, BTN_MIDDLE, 8, 1); + litest_button_scroll(dev, BTN_MIDDLE, 13, 1); litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 8); - litest_button_scroll(dev, BTN_MIDDLE, -9, 1); + litest_button_scroll(dev, BTN_MIDDLE, -14, 1); litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,