wheel: ignore initial small scroll deltas
authorJosé Expósito <jose.exposito89@gmail.com>
Wed, 29 Sep 2021 16:32:54 +0000 (18:32 +0200)
committerJosé Expósito <jose.exposito89@gmail.com>
Mon, 8 Nov 2021 17:00:46 +0000 (18:00 +0100)
Mice with high-resolution support can generate deltas when the finger is
put on the wheel or when the user tries to click the wheel.

To avoid sending involuntary scroll events, add an extra state the the
wheel state machine to accumulate scroll deltas.
While the accumulated scroll is lower than a certain threshold, ignore
them until the threshold is reached.

Since no finish event is sent by the mouse, reset the state machine
after a period of scroll inactivity.

Signed-off-by: José Expósito <jose.exposito89@gmail.com>
src/evdev-fallback.c
src/evdev-fallback.h
src/evdev-wheel.c
test/litest.c
test/litest.h
test/test-pointer.c

index 19ae4a0..28d5de9 100644 (file)
@@ -1109,6 +1109,7 @@ fallback_interface_remove(struct evdev_dispatch *evdev_dispatch)
        struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
        struct evdev_paired_keyboard *kbd;
 
+       libinput_timer_cancel(&dispatch->wheel.scroll_timer);
        libinput_timer_cancel(&dispatch->debounce.timer);
        libinput_timer_cancel(&dispatch->debounce.timer_short);
        libinput_timer_cancel(&dispatch->arbitration.arbitration_timer);
@@ -1222,6 +1223,7 @@ fallback_interface_destroy(struct evdev_dispatch *evdev_dispatch)
 {
        struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
 
+       libinput_timer_destroy(&dispatch->wheel.scroll_timer);
        libinput_timer_destroy(&dispatch->arbitration.arbitration_timer);
        libinput_timer_destroy(&dispatch->debounce.timer);
        libinput_timer_destroy(&dispatch->debounce.timer_short);
index 2338f4a..ad41cd3 100644 (file)
@@ -62,6 +62,7 @@ enum palm_state {
 enum wheel_state {
        WHEEL_STATE_NONE,
        WHEEL_STATE_PRESSED,
+       WHEEL_STATE_ACCUMULATING_SCROLL,
        WHEEL_STATE_SCROLLING,
 };
 
@@ -109,6 +110,7 @@ struct fallback_dispatch {
                struct device_coords hi_res;
                bool emulate_hi_res_wheel;
                bool hi_res_event_received;
+               struct libinput_timer scroll_timer;
        } wheel;
 
        struct {
index acdbf86..215cd2d 100644 (file)
 #include "evdev-fallback.h"
 #include "util-input-event.h"
 
+#define ACC_V120_THRESHOLD 60
+#define WHEEL_SCROLL_TIMEOUT ms2us(500)
+
 enum wheel_event {
        WHEEL_EVENT_PRESS,
        WHEEL_EVENT_RELEASE,
+       WHEEL_EVENT_SCROLL_ACCUMULATED,
        WHEEL_EVENT_SCROLL,
+       WHEEL_EVENT_SCROLL_TIMEOUT,
 };
 
 static inline const char *
@@ -42,6 +47,7 @@ wheel_state_to_str(enum wheel_state state)
        switch(state) {
        CASE_RETURN_STRING(WHEEL_STATE_NONE);
        CASE_RETURN_STRING(WHEEL_STATE_PRESSED);
+       CASE_RETURN_STRING(WHEEL_STATE_ACCUMULATING_SCROLL);
        CASE_RETURN_STRING(WHEEL_STATE_SCROLLING);
        }
        return NULL;
@@ -53,7 +59,9 @@ wheel_event_to_str(enum wheel_event event)
        switch(event) {
        CASE_RETURN_STRING(WHEEL_EVENT_PRESS);
        CASE_RETURN_STRING(WHEEL_EVENT_RELEASE);
+       CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_ACCUMULATED);
        CASE_RETURN_STRING(WHEEL_EVENT_SCROLL);
+       CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_TIMEOUT);
        }
        return NULL;
 }
@@ -67,6 +75,19 @@ log_wheel_bug(struct fallback_dispatch *dispatch, enum wheel_event event)
                               wheel_state_to_str(dispatch->wheel.state));
 }
 
+static inline void
+wheel_set_scroll_timer(struct fallback_dispatch *dispatch, uint64_t time)
+{
+       libinput_timer_set(&dispatch->wheel.scroll_timer,
+                          time + WHEEL_SCROLL_TIMEOUT);
+}
+
+static inline void
+wheel_cancel_scroll_timer(struct fallback_dispatch *dispatch)
+{
+       libinput_timer_cancel(&dispatch->wheel.scroll_timer);
+}
+
 static void
 wheel_handle_event_on_state_none(struct fallback_dispatch *dispatch,
                                 enum wheel_event event,
@@ -77,9 +98,11 @@ wheel_handle_event_on_state_none(struct fallback_dispatch *dispatch,
                dispatch->wheel.state = WHEEL_STATE_PRESSED;
                break;
        case WHEEL_EVENT_SCROLL:
-               dispatch->wheel.state = WHEEL_STATE_SCROLLING;
+               dispatch->wheel.state = WHEEL_STATE_ACCUMULATING_SCROLL;
                break;
        case WHEEL_EVENT_RELEASE:
+       case WHEEL_EVENT_SCROLL_ACCUMULATED:
+       case WHEEL_EVENT_SCROLL_TIMEOUT:
                log_wheel_bug(dispatch, event);
                break;
        }
@@ -98,6 +121,31 @@ wheel_handle_event_on_state_pressed(struct fallback_dispatch *dispatch,
                /* Ignore scroll while the wheel is pressed */
                break;
        case WHEEL_EVENT_PRESS:
+       case WHEEL_EVENT_SCROLL_ACCUMULATED:
+       case WHEEL_EVENT_SCROLL_TIMEOUT:
+               log_wheel_bug(dispatch, event);
+               break;
+       }
+}
+
+static void
+wheel_handle_event_on_state_accumulating_scroll(struct fallback_dispatch *dispatch,
+                                               enum wheel_event event,
+                                               uint64_t time)
+{
+       switch (event) {
+       case WHEEL_EVENT_PRESS:
+               dispatch->wheel.state = WHEEL_STATE_PRESSED;
+               break;
+       case WHEEL_EVENT_SCROLL_ACCUMULATED:
+               dispatch->wheel.state = WHEEL_STATE_SCROLLING;
+               wheel_set_scroll_timer(dispatch, time);
+               break;
+       case WHEEL_EVENT_SCROLL:
+               /* Ignore scroll while accumulating deltas */
+               break;
+       case WHEEL_EVENT_RELEASE:
+       case WHEEL_EVENT_SCROLL_TIMEOUT:
                log_wheel_bug(dispatch, event);
                break;
        }
@@ -111,10 +159,17 @@ wheel_handle_event_on_state_scrolling(struct fallback_dispatch *dispatch,
        switch (event) {
        case WHEEL_EVENT_PRESS:
                dispatch->wheel.state = WHEEL_STATE_PRESSED;
+               wheel_cancel_scroll_timer(dispatch);
                break;
        case WHEEL_EVENT_SCROLL:
+               wheel_cancel_scroll_timer(dispatch);
+               wheel_set_scroll_timer(dispatch, time);
+               break;
+       case WHEEL_EVENT_SCROLL_TIMEOUT:
+               dispatch->wheel.state = WHEEL_STATE_NONE;
                break;
        case WHEEL_EVENT_RELEASE:
+       case WHEEL_EVENT_SCROLL_ACCUMULATED:
                log_wheel_bug(dispatch, event);
                break;
        }
@@ -134,6 +189,11 @@ wheel_handle_event(struct fallback_dispatch *dispatch,
        case WHEEL_STATE_PRESSED:
                wheel_handle_event_on_state_pressed(dispatch, event, time);
                break;
+       case WHEEL_STATE_ACCUMULATING_SCROLL:
+               wheel_handle_event_on_state_accumulating_scroll(dispatch,
+                                                               event,
+                                                               time);
+               break;
        case WHEEL_STATE_SCROLLING:
                wheel_handle_event_on_state_scrolling(dispatch, event, time);
                break;
@@ -249,6 +309,20 @@ wheel_handle_state_pressed(struct fallback_dispatch *dispatch,
 }
 
 static void
+wheel_handle_state_accumulating_scroll(struct fallback_dispatch *dispatch,
+                                      struct evdev_device *device,
+                                      uint64_t time)
+{
+       if (abs(dispatch->wheel.hi_res.x) >= ACC_V120_THRESHOLD ||
+           abs(dispatch->wheel.hi_res.y) >= ACC_V120_THRESHOLD) {
+               wheel_handle_event(dispatch,
+                                  WHEEL_EVENT_SCROLL_ACCUMULATED,
+                                  time);
+               wheel_flush_scroll(dispatch, device, time);
+       }
+}
+
+static void
 wheel_handle_state_scrolling(struct fallback_dispatch *dispatch,
                             struct evdev_device *device,
                             uint64_t time)
@@ -342,16 +416,31 @@ fallback_wheel_handle_state(struct fallback_dispatch *dispatch,
        case WHEEL_STATE_PRESSED:
                wheel_handle_state_pressed(dispatch, device, time);
                break;
+       case WHEEL_STATE_ACCUMULATING_SCROLL:
+               wheel_handle_state_accumulating_scroll(dispatch, device, time);
+               break;
        case WHEEL_STATE_SCROLLING:
                wheel_handle_state_scrolling(dispatch, device, time);
                break;
        }
 }
 
+static void
+wheel_init_scroll_timer(uint64_t now, void *data)
+{
+       struct evdev_device *device = data;
+       struct fallback_dispatch *dispatch =
+               fallback_dispatch(device->dispatch);
+
+       wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL_TIMEOUT, now);
+}
+
 void
 fallback_init_wheel(struct fallback_dispatch *dispatch,
                    struct evdev_device *device)
 {
+       char timer_name[64];
+
        dispatch->wheel.state = WHEEL_STATE_NONE;
 
        /* On kernel < 5.0 we need to emulate high-resolution
@@ -369,4 +458,14 @@ fallback_init_wheel(struct fallback_dispatch *dispatch,
                                      EV_REL,
                                      REL_HWHEEL_HI_RES)))
                dispatch->wheel.emulate_hi_res_wheel = true;
+
+       snprintf(timer_name,
+                sizeof(timer_name),
+                "%s wheel scroll",
+                evdev_device_get_sysname(device));
+       libinput_timer_init(&dispatch->wheel.scroll_timer,
+                           evdev_libinput_context(device),
+                           timer_name,
+                           wheel_init_scroll_timer,
+                           device);
 }
index 187dd34..69d5154 100644 (file)
@@ -4300,6 +4300,12 @@ litest_timeout_finger_switch(void)
 }
 
 void
+litest_timeout_wheel_scroll(void)
+{
+       msleep(600);
+}
+
+void
 litest_timeout_edgescroll(void)
 {
        msleep(300);
index b6ffbf4..990161b 100644 (file)
@@ -895,6 +895,9 @@ void
 litest_timeout_buttonscroll(void);
 
 void
+litest_timeout_wheel_scroll(void);
+
+void
 litest_timeout_edgescroll(void);
 
 void
index 218fe07..77a6396 100644 (file)
@@ -867,6 +867,53 @@ START_TEST(pointer_scroll_wheel_hires_send_only_lores_horizontal)
 }
 END_TEST
 
+START_TEST(pointer_scroll_wheel_inhibit_small_deltas)
+{
+       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 deltas bellow the threshold (60) must be ignored */
+       litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 15);
+       litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 15);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+
+       /* The accumulated scroll is 30, add 30 to trigger scroll */
+       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, -60);
+
+       /* Once the threshold is reached, small scroll deltas are reported */
+       litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 5);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       test_high_and_low_wheel_events_value(dev, REL_WHEEL_HI_RES, -5);
+
+       /* When the scroll timeout is triggered, ignore small deltas again */
+       litest_timeout_wheel_scroll();
+
+       litest_event(dev, EV_REL, REL_WHEEL_HI_RES, -15);
+       litest_event(dev, EV_REL, REL_WHEEL_HI_RES, -15);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_REL, REL_HWHEEL_HI_RES, 15);
+       litest_event(dev, EV_REL, REL_HWHEEL_HI_RES, 15);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
 START_TEST(pointer_scroll_natural_defaults)
 {
        struct litest_device *dev = litest_current_device();
@@ -3495,6 +3542,7 @@ TEST_COLLECTION(pointer)
        litest_add(pointer_scroll_wheel_hires, LITEST_WHEEL, LITEST_TABLET);
        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_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);