mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-05-07 10:48:07 +02:00
Merge branch 'makoConstruct-delta-tilt-axis-lock' into 'main'
Fixed biaxial button-scrolling/improved scroll axis lock behaviour, replacing delta length threshold method with distance threshold + delta tilting method See merge request libinput/libinput!1474
This commit is contained in:
commit
79f6888652
6 changed files with 390 additions and 114 deletions
|
|
@ -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;
|
||||
|
|
|
|||
324
src/evdev.c
324
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
12
src/evdev.h
12
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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue