#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 *
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;
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;
}
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,
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;
}
/* 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;
}
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;
}
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;
}
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)
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
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);
}
}
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();
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);