mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-05-07 11:58:04 +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;
|
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
|
static void
|
||||||
tp_gesture_apply_scroll_constraints(struct tp_dispatch *tp,
|
tp_gesture_apply_scroll_constraints(struct tp_dispatch *tp,
|
||||||
struct device_float_coords *raw,
|
struct device_float_coords *raw,
|
||||||
|
|
@ -637,7 +622,6 @@ tp_gesture_handle_event_on_state_unknown(struct tp_dispatch *tp,
|
||||||
break;
|
break;
|
||||||
case GESTURE_EVENT_SCROLL_START:
|
case GESTURE_EVENT_SCROLL_START:
|
||||||
libinput_timer_cancel(&tp->gesture.hold_timer);
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
||||||
tp_gesture_set_scroll_buildup(tp);
|
|
||||||
tp->gesture.state = GESTURE_STATE_SCROLL_START;
|
tp->gesture.state = GESTURE_STATE_SCROLL_START;
|
||||||
break;
|
break;
|
||||||
case GESTURE_EVENT_SWIPE_START:
|
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;
|
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
|
||||||
break;
|
break;
|
||||||
case GESTURE_EVENT_SCROLL_START:
|
case GESTURE_EVENT_SCROLL_START:
|
||||||
tp_gesture_set_scroll_buildup(tp);
|
|
||||||
tp_gesture_cancel(tp, time);
|
tp_gesture_cancel(tp, time);
|
||||||
tp->gesture.state = GESTURE_STATE_SCROLL_START;
|
tp->gesture.state = GESTURE_STATE_SCROLL_START;
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
324
src/evdev.c
324
src/evdev.c
|
|
@ -56,6 +56,12 @@
|
||||||
#define DEFAULT_BUTTON_SCROLL_TIMEOUT usec_from_millis(200)
|
#define DEFAULT_BUTTON_SCROLL_TIMEOUT usec_from_millis(200)
|
||||||
#define SCROLL_BUTTON_LOCK_GRACE_TIMEOUT usec_from_millis(500)
|
#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 {
|
enum evdev_device_udev_tags {
|
||||||
EVDEV_UDEV_TAG_NONE = 0,
|
EVDEV_UDEV_TAG_NONE = 0,
|
||||||
EVDEV_UDEV_TAG_INPUT = bit(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
|
/* the log_prefix_name is used as part of a printf format string and
|
||||||
* must not contain % directives, see evdev_log_msg */
|
* must not contain % directives, see evdev_log_msg */
|
||||||
device->log_prefix_name = str_sanitize(device->devname);
|
device->log_prefix_name = str_sanitize(device->devname);
|
||||||
device->scroll.threshold = 5.0; /* Default may be overridden */
|
device->scroll.threshold = 5.0; /* Default may be overridden */
|
||||||
device->scroll.direction_lock_threshold = 5.0; /* Default may be overridden */
|
device->scroll.direction_lock_threshold = 18.0; /* Default may be overridden */
|
||||||
device->scroll.direction = 0;
|
device->scroll.direction = 0;
|
||||||
device->scroll.wheel_click_angle = evdev_read_wheel_click_props(device);
|
device->scroll.wheel_click_angle = evdev_read_wheel_click_props(device);
|
||||||
device->model_flags = evdev_read_model_flags(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);
|
return libevdev_has_event_code(device->evdev, EV_SW, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
/* Rotation by the normal vector, `rot`, anticlockwise.
|
||||||
evdev_is_scrolling(const struct evdev_device *device, enum libinput_pointer_axis axis)
|
* 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 ||
|
return (struct normalized_coords){
|
||||||
axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
|
rot.x * subject.x - rot.y * subject.y,
|
||||||
|
rot.y * subject.x + rot.x * subject.y,
|
||||||
return (device->scroll.direction & bit(axis)) != 0;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
/* Rotation by the normal vector, `rot`, clockwise.
|
||||||
evdev_start_scrolling(struct evdev_device *device, enum libinput_pointer_axis axis)
|
* 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 ||
|
return (struct normalized_coords){
|
||||||
axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
|
rot.x * subject.x + rot.y * subject.y,
|
||||||
|
-rot.y * subject.x + rot.x * subject.y,
|
||||||
device->scroll.direction |= bit(axis);
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
void
|
||||||
evdev_post_scroll(struct evdev_device *device,
|
evdev_post_scroll(struct evdev_device *device,
|
||||||
usec_t time,
|
usec_t time,
|
||||||
enum libinput_pointer_axis_source source,
|
enum libinput_pointer_axis_source source,
|
||||||
const struct normalized_coords *delta)
|
const struct normalized_coords *delta)
|
||||||
{
|
{
|
||||||
const struct normalized_coords *trigger;
|
struct normalized_coords *acc = &device->scroll.accumulator;
|
||||||
struct normalized_coords event;
|
struct normalized_coords delta_post = *delta;
|
||||||
|
|
||||||
if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL))
|
/* activate antijitter bevel once it's clear which axis the scroll is moving
|
||||||
device->scroll.buildup.y += delta->y;
|
* into */
|
||||||
if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL))
|
if (device->scroll.antijitter_axes == 0) {
|
||||||
device->scroll.buildup.x += delta->x;
|
if (fabs(acc->x) > fabs(acc->y)) {
|
||||||
|
if (fabs(acc->x) > SCROLL_ANTIJITTER_AXIS_DECISION_DISTANCE) {
|
||||||
trigger = &device->scroll.buildup;
|
device->scroll.antijitter_axes |=
|
||||||
|
bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
|
||||||
/* If we're not scrolling yet, use a distance trigger: moving
|
}
|
||||||
past a certain distance starts scrolling */
|
} else {
|
||||||
if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) &&
|
if (fabs(acc->y) > SCROLL_ANTIJITTER_AXIS_DECISION_DISTANCE) {
|
||||||
!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) {
|
device->scroll.antijitter_axes |=
|
||||||
if (fabs(trigger->y) >= device->scroll.threshold)
|
bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
struct normalized_coords estimate_next_acc = v2_add(*acc, delta_post);
|
||||||
* 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;
|
|
||||||
|
|
||||||
if (!evdev_is_scrolling(device, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL))
|
bool is_unlocked = device->scroll.antijitter_axes ==
|
||||||
event.x = 0.0;
|
(bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) |
|
||||||
|
bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL));
|
||||||
|
|
||||||
if (!normalized_is_zero(event)) {
|
struct normalized_coords scroll_movement;
|
||||||
uint32_t axes = device->scroll.direction;
|
if ((estimate_next_acc.x == 0 && estimate_next_acc.y == 0) || is_unlocked) {
|
||||||
|
scroll_movement = delta_post;
|
||||||
if (event.y == 0.0)
|
} else if (fabs(estimate_next_acc.x) < fabs(estimate_next_acc.y)) {
|
||||||
axes &= ~bit(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
|
if (fabs(acc->x) < device->scroll.direction_lock_threshold) {
|
||||||
if (event.x == 0.0)
|
scroll_movement = tilt_delta_for_orientation(
|
||||||
axes &= ~bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
|
*acc,
|
||||||
|
delta_post,
|
||||||
switch (source) {
|
(struct normalized_coords){
|
||||||
case LIBINPUT_POINTER_AXIS_SOURCE_FINGER:
|
0.0,
|
||||||
evdev_notify_axis_finger(device, time, axes, &event);
|
estimate_next_acc.y < 0 ? -1.0 : 1.0,
|
||||||
break;
|
},
|
||||||
case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS:
|
SCROLL_DELTA_TILT_ANGLE);
|
||||||
evdev_notify_axis_continous(device, time, axes, &event);
|
} else {
|
||||||
break;
|
device->scroll.antijitter_axes |=
|
||||||
default:
|
bit(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
|
||||||
evdev_log_bug_libinput(device,
|
scroll_movement = delta_post;
|
||||||
"Posting invalid scroll source %d\n",
|
|
||||||
source);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
} 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.accumulator.x = 0;
|
||||||
device->scroll.buildup.y = 0;
|
device->scroll.accumulator.y = 0;
|
||||||
|
device->scroll.exceeded_threshold = false;
|
||||||
|
device->scroll.antijitter_axes = 0;
|
||||||
device->scroll.direction = 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 */
|
/* Checks if buttons are down and commits the setting */
|
||||||
void (*change_scroll_method)(struct evdev_device *device);
|
void (*change_scroll_method)(struct evdev_device *device);
|
||||||
enum evdev_button_scroll_state button_scroll_state;
|
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;
|
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;
|
double direction_lock_threshold;
|
||||||
uint32_t direction;
|
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;
|
struct libinput_device_config_natural_scroll config_natural;
|
||||||
/* set during device init if we want natural scrolling,
|
/* 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
|
/* Lenovo ScrollPoint has a trackstick instead of a wheel, data sent
|
||||||
* via REL_WHEEL is close to x/y coordinate space.
|
* 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_REL, REL_WHEEL, 30);
|
||||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
||||||
litest_event(dev, EV_REL, REL_WHEEL, -60);
|
litest_event(dev, EV_REL, REL_WHEEL, -60);
|
||||||
|
|
@ -1552,22 +1562,25 @@ START_TEST(pointer_scroll_button)
|
||||||
|
|
||||||
litest_drain_events(li);
|
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,
|
litest_assert_scroll(li,
|
||||||
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
||||||
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
|
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
|
||||||
6);
|
6);
|
||||||
litest_button_scroll(dev, BTN_LEFT, 1, -7);
|
litest_button_scroll(dev, BTN_LEFT, 1, -14);
|
||||||
litest_assert_scroll(li,
|
litest_assert_scroll(li,
|
||||||
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
||||||
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
|
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
|
||||||
-7);
|
-7);
|
||||||
litest_button_scroll(dev, BTN_LEFT, 8, 1);
|
litest_button_scroll(dev, BTN_LEFT, 15, 1);
|
||||||
litest_assert_scroll(li,
|
litest_assert_scroll(li,
|
||||||
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
||||||
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
|
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
|
||||||
8);
|
8);
|
||||||
litest_button_scroll(dev, BTN_LEFT, -9, 1);
|
litest_button_scroll(dev, BTN_LEFT, -16, 1);
|
||||||
litest_assert_scroll(li,
|
litest_assert_scroll(li,
|
||||||
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
||||||
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
|
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
|
||||||
|
|
@ -1591,6 +1604,95 @@ START_TEST(pointer_scroll_button)
|
||||||
}
|
}
|
||||||
END_TEST
|
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)
|
START_TEST(pointer_scroll_button_noscroll)
|
||||||
{
|
{
|
||||||
struct litest_device *dev = litest_current_device();
|
struct litest_device *dev = litest_current_device();
|
||||||
|
|
@ -1764,8 +1866,8 @@ START_TEST(pointer_scroll_button_lock)
|
||||||
litest_timeout_buttonscroll(li);
|
litest_timeout_buttonscroll(li);
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
litest_event(dev, EV_REL, REL_X, 1);
|
litest_event(dev, EV_REL, REL_X, 3);
|
||||||
litest_event(dev, EV_REL, REL_Y, 6);
|
litest_event(dev, EV_REL, REL_Y, 13);
|
||||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
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);
|
litest_timeout_buttonscroll(li);
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
litest_event(dev, EV_REL, REL_X, 1);
|
litest_event(dev, EV_REL, REL_X, 3);
|
||||||
litest_event(dev, EV_REL, REL_Y, 6);
|
litest_event(dev, EV_REL, REL_Y, 13);
|
||||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
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);
|
litest_timeout_buttonscroll(li);
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
litest_event(dev, EV_REL, REL_X, 1);
|
litest_event(dev, EV_REL, REL_X, 3);
|
||||||
litest_event(dev, EV_REL, REL_Y, 6);
|
litest_event(dev, EV_REL, REL_Y, 13);
|
||||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
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);
|
litest_assert_empty_queue(li);
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
litest_event(dev, EV_REL, REL_X, 1);
|
litest_event(dev, EV_REL, REL_X, 3);
|
||||||
litest_event(dev, EV_REL, REL_Y, 6);
|
litest_event(dev, EV_REL, REL_Y, 13);
|
||||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
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_middlebutton(li);
|
||||||
litest_timeout_buttonscroll(li);
|
litest_timeout_buttonscroll(li);
|
||||||
|
|
||||||
/* motion events are the same for all of them */
|
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
litest_event(dev, EV_REL, REL_X, 1);
|
litest_event(dev, EV_REL, REL_X, 2);
|
||||||
litest_event(dev, EV_REL, REL_Y, 6);
|
litest_event(dev, EV_REL, REL_Y, 13);
|
||||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
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_no_inhibit_small_deltas_when_virtual, LITEST_MOUSE_VIRTUAL);
|
||||||
litest_add_for_device(pointer_scroll_wheel_lenovo_scrollpoint, LITEST_LENOVO_SCROLLPOINT);
|
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, 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_ABSOLUTE|LITEST_BUTTON, LITEST_RELATIVE);
|
||||||
litest_add(pointer_scroll_button_noscroll, LITEST_ANY, LITEST_RELATIVE|LITEST_BUTTON);
|
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);
|
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);
|
litest_assert(axisval >= 0.0);
|
||||||
|
|
||||||
/* this is to verify we test the right thing, if the value
|
/* this is to verify we test the right thing, if the value
|
||||||
is greater than scroll.threshold we triggered the wrong
|
is greater than direction_lock_threshold we triggered the
|
||||||
condition */
|
wrong condition */
|
||||||
litest_assert(axisval < 5.0);
|
litest_assert(axisval < 13.0);
|
||||||
|
|
||||||
libinput_event_destroy(event);
|
libinput_event_destroy(event);
|
||||||
event = libinput_get_event(li);
|
event = libinput_get_event(li);
|
||||||
|
|
|
||||||
|
|
@ -81,22 +81,26 @@ START_TEST(trackpoint_scroll)
|
||||||
|
|
||||||
litest_drain_events(li);
|
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,
|
litest_assert_scroll(li,
|
||||||
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
||||||
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
|
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
|
||||||
6);
|
6);
|
||||||
litest_button_scroll(dev, BTN_MIDDLE, 1, -7);
|
litest_button_scroll(dev, BTN_MIDDLE, 1, -12);
|
||||||
litest_assert_scroll(li,
|
litest_assert_scroll(li,
|
||||||
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
||||||
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
|
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
|
||||||
-7);
|
-7);
|
||||||
litest_button_scroll(dev, BTN_MIDDLE, 8, 1);
|
litest_button_scroll(dev, BTN_MIDDLE, 13, 1);
|
||||||
litest_assert_scroll(li,
|
litest_assert_scroll(li,
|
||||||
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
||||||
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
|
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
|
||||||
8);
|
8);
|
||||||
litest_button_scroll(dev, BTN_MIDDLE, -9, 1);
|
litest_button_scroll(dev, BTN_MIDDLE, -14, 1);
|
||||||
litest_assert_scroll(li,
|
litest_assert_scroll(li,
|
||||||
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
|
||||||
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
|
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue