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.
This commit is contained in:
mako yass 2026-05-03 09:32:10 +12:00
parent e53c2141b3
commit 0523fce4eb
6 changed files with 390 additions and 114 deletions

View file

@ -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;

View file

@ -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;
}

View file

@ -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,

View file

@ -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);

View file

@ -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);

View file

@ -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,