summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Expósito <jose.exposito89@gmail.com>2021-09-30 17:50:26 +0200
committerJosé Expósito <jose.exposito89@gmail.com>2021-11-08 18:00:46 +0100
commitd21f1ab7abb792a5bd467ea120e0742fe03f6bb5 (patch)
tree5c91ba7251c18c2b7fdf4f5e60f4d2926730a835
parentb6a944bb80a9bb886bf38508e8a2b85be22dda57 (diff)
downloadlibinput-d21f1ab7abb792a5bd467ea120e0742fe03f6bb5.tar.gz
wheel: accumulate scroll when direction changes
Most mice with high-resolution support have a mechanism in place to adjust the wheel to a detent. When scrolling, it is possible to stop between two detents and this mechanism could generate a small amount of scroll in the oposite direction. Track the scroll direction in the wheel state machine and reset it when the direction changes to avoid this issue. Signed-off-by: José Expósito <jose.exposito89@gmail.com>
-rw-r--r--src/evdev-fallback.h9
-rw-r--r--src/evdev-wheel.c39
-rw-r--r--test/test-pointer.c41
3 files changed, 88 insertions, 1 deletions
diff --git a/src/evdev-fallback.h b/src/evdev-fallback.h
index ad41cd3b..0c2f65cb 100644
--- a/src/evdev-fallback.h
+++ b/src/evdev-fallback.h
@@ -66,6 +66,14 @@ enum wheel_state {
WHEEL_STATE_SCROLLING,
};
+enum wheel_direction {
+ WHEEL_DIR_UNKNOW,
+ WHEEL_DIR_VPOS,
+ WHEEL_DIR_VNEG,
+ WHEEL_DIR_HPOS,
+ WHEEL_DIR_HNEG,
+};
+
struct mt_slot {
bool dirty;
enum mt_slot_state state;
@@ -111,6 +119,7 @@ struct fallback_dispatch {
bool emulate_hi_res_wheel;
bool hi_res_event_received;
struct libinput_timer scroll_timer;
+ enum wheel_direction dir;
} wheel;
struct {
diff --git a/src/evdev-wheel.c b/src/evdev-wheel.c
index 215cd2db..5ba0c638 100644
--- a/src/evdev-wheel.c
+++ b/src/evdev-wheel.c
@@ -39,6 +39,7 @@ enum wheel_event {
WHEEL_EVENT_SCROLL_ACCUMULATED,
WHEEL_EVENT_SCROLL,
WHEEL_EVENT_SCROLL_TIMEOUT,
+ WHEEL_EVENT_SCROLL_DIR_CHANGED,
};
static inline const char *
@@ -62,6 +63,7 @@ wheel_event_to_str(enum wheel_event event)
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_ACCUMULATED);
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL);
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_TIMEOUT);
+ CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_DIR_CHANGED);
}
return NULL;
}
@@ -100,6 +102,8 @@ wheel_handle_event_on_state_none(struct fallback_dispatch *dispatch,
case WHEEL_EVENT_SCROLL:
dispatch->wheel.state = WHEEL_STATE_ACCUMULATING_SCROLL;
break;
+ case WHEEL_EVENT_SCROLL_DIR_CHANGED:
+ break;
case WHEEL_EVENT_RELEASE:
case WHEEL_EVENT_SCROLL_ACCUMULATED:
case WHEEL_EVENT_SCROLL_TIMEOUT:
@@ -118,6 +122,7 @@ wheel_handle_event_on_state_pressed(struct fallback_dispatch *dispatch,
dispatch->wheel.state = WHEEL_STATE_NONE;
break;
case WHEEL_EVENT_SCROLL:
+ case WHEEL_EVENT_SCROLL_DIR_CHANGED:
/* Ignore scroll while the wheel is pressed */
break;
case WHEEL_EVENT_PRESS:
@@ -144,6 +149,9 @@ wheel_handle_event_on_state_accumulating_scroll(struct fallback_dispatch *dispat
case WHEEL_EVENT_SCROLL:
/* Ignore scroll while accumulating deltas */
break;
+ case WHEEL_EVENT_SCROLL_DIR_CHANGED:
+ dispatch->wheel.state = WHEEL_STATE_NONE;
+ break;
case WHEEL_EVENT_RELEASE:
case WHEEL_EVENT_SCROLL_TIMEOUT:
log_wheel_bug(dispatch, event);
@@ -168,6 +176,10 @@ wheel_handle_event_on_state_scrolling(struct fallback_dispatch *dispatch,
case WHEEL_EVENT_SCROLL_TIMEOUT:
dispatch->wheel.state = WHEEL_STATE_NONE;
break;
+ case WHEEL_EVENT_SCROLL_DIR_CHANGED:
+ wheel_cancel_scroll_timer(dispatch);
+ dispatch->wheel.state = WHEEL_STATE_NONE;
+ break;
case WHEEL_EVENT_RELEASE:
case WHEEL_EVENT_SCROLL_ACCUMULATED:
log_wheel_bug(dispatch, event);
@@ -330,6 +342,30 @@ wheel_handle_state_scrolling(struct fallback_dispatch *dispatch,
wheel_flush_scroll(dispatch, device, time);
}
+static void
+wheel_handle_direction_change(struct fallback_dispatch *dispatch,
+ struct input_event *e,
+ uint64_t time)
+{
+ enum wheel_direction new_dir = WHEEL_DIR_UNKNOW;
+
+ switch (e->code) {
+ case REL_WHEEL_HI_RES:
+ new_dir = (e->value > 0) ? WHEEL_DIR_VPOS : WHEEL_DIR_VNEG;
+ break;
+ case REL_HWHEEL_HI_RES:
+ new_dir = (e->value > 0) ? WHEEL_DIR_HPOS : WHEEL_DIR_HNEG;
+ break;
+ }
+
+ if (new_dir != WHEEL_DIR_UNKNOW && new_dir != dispatch->wheel.dir) {
+ dispatch->wheel.dir = new_dir;
+ wheel_handle_event(dispatch,
+ WHEEL_EVENT_SCROLL_DIR_CHANGED,
+ time);
+ }
+}
+
void
fallback_wheel_process_relative(struct fallback_dispatch *dispatch,
struct evdev_device *device,
@@ -354,12 +390,14 @@ fallback_wheel_process_relative(struct fallback_dispatch *dispatch,
dispatch->wheel.hi_res.y += e->value;
dispatch->wheel.hi_res_event_received = true;
dispatch->pending_event |= EVDEV_WHEEL;
+ wheel_handle_direction_change(dispatch, e, time);
wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time);
break;
case REL_HWHEEL_HI_RES:
dispatch->wheel.hi_res.x += e->value;
dispatch->wheel.hi_res_event_received = true;
dispatch->pending_event |= EVDEV_WHEEL;
+ wheel_handle_direction_change(dispatch, e, time);
wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time);
break;
}
@@ -442,6 +480,7 @@ fallback_init_wheel(struct fallback_dispatch *dispatch,
char timer_name[64];
dispatch->wheel.state = WHEEL_STATE_NONE;
+ dispatch->wheel.dir = WHEEL_DIR_UNKNOW;
/* On kernel < 5.0 we need to emulate high-resolution
wheel scroll events */
diff --git a/test/test-pointer.c b/test/test-pointer.c
index 77a63963..4e383858 100644
--- a/test/test-pointer.c
+++ b/test/test-pointer.c
@@ -796,8 +796,8 @@ START_TEST(pointer_scroll_wheel_hires)
test_hi_res_wheel_event(dev, axis, 6 * 120);
test_hi_res_wheel_event(dev, axis, 30);
- test_hi_res_wheel_event(dev, axis, -40);
test_hi_res_wheel_event(dev, axis, -60);
+ test_hi_res_wheel_event(dev, axis, -40);
test_hi_res_wheel_event(dev, axis, 180);
}
}
@@ -914,6 +914,44 @@ START_TEST(pointer_scroll_wheel_inhibit_small_deltas)
}
END_TEST
+START_TEST(pointer_scroll_wheel_inhibit_dir_change)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+
+ if (!libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL_HI_RES) &&
+ !libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL_HI_RES))
+ return;
+
+ litest_drain_events(dev->libinput);
+
+ /* Scroll one detent and a bit */
+ litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 120);
+ litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 30);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+ test_high_and_low_wheel_events_value(dev, REL_WHEEL_HI_RES, -150);
+
+ /* Scroll below the threshold in the oposite direction should be ignored */
+ litest_event(dev, EV_REL, REL_WHEEL_HI_RES, -30);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+
+ /* But should be triggered if the scroll continues in the same direction */
+ litest_event(dev, EV_REL, REL_WHEEL_HI_RES, -120);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+ test_high_and_low_wheel_events_value(dev, REL_WHEEL_HI_RES, 150);
+
+ /* Scroll above the threshold in the same dir should be triggered */
+ litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 80);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ libinput_dispatch(li);
+ test_high_and_low_wheel_events_value(dev, REL_WHEEL_HI_RES, -80);
+}
+END_TEST
+
START_TEST(pointer_scroll_natural_defaults)
{
struct litest_device *dev = litest_current_device();
@@ -3543,6 +3581,7 @@ TEST_COLLECTION(pointer)
litest_add(pointer_scroll_wheel_hires_send_only_lores_vertical, LITEST_WHEEL, LITEST_TABLET);
litest_add(pointer_scroll_wheel_hires_send_only_lores_horizontal, LITEST_WHEEL, LITEST_TABLET);
litest_add(pointer_scroll_wheel_inhibit_small_deltas, LITEST_WHEEL, LITEST_TABLET);
+ litest_add(pointer_scroll_wheel_inhibit_dir_change, LITEST_WHEEL, LITEST_TABLET);
litest_add(pointer_scroll_button, 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);