mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-05-07 07:18:08 +02:00
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>
This commit is contained in:
parent
b6a944bb80
commit
d21f1ab7ab
3 changed files with 88 additions and 1 deletions
|
|
@ -66,6 +66,14 @@ enum wheel_state {
|
||||||
WHEEL_STATE_SCROLLING,
|
WHEEL_STATE_SCROLLING,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum wheel_direction {
|
||||||
|
WHEEL_DIR_UNKNOW,
|
||||||
|
WHEEL_DIR_VPOS,
|
||||||
|
WHEEL_DIR_VNEG,
|
||||||
|
WHEEL_DIR_HPOS,
|
||||||
|
WHEEL_DIR_HNEG,
|
||||||
|
};
|
||||||
|
|
||||||
struct mt_slot {
|
struct mt_slot {
|
||||||
bool dirty;
|
bool dirty;
|
||||||
enum mt_slot_state state;
|
enum mt_slot_state state;
|
||||||
|
|
@ -111,6 +119,7 @@ struct fallback_dispatch {
|
||||||
bool emulate_hi_res_wheel;
|
bool emulate_hi_res_wheel;
|
||||||
bool hi_res_event_received;
|
bool hi_res_event_received;
|
||||||
struct libinput_timer scroll_timer;
|
struct libinput_timer scroll_timer;
|
||||||
|
enum wheel_direction dir;
|
||||||
} wheel;
|
} wheel;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ enum wheel_event {
|
||||||
WHEEL_EVENT_SCROLL_ACCUMULATED,
|
WHEEL_EVENT_SCROLL_ACCUMULATED,
|
||||||
WHEEL_EVENT_SCROLL,
|
WHEEL_EVENT_SCROLL,
|
||||||
WHEEL_EVENT_SCROLL_TIMEOUT,
|
WHEEL_EVENT_SCROLL_TIMEOUT,
|
||||||
|
WHEEL_EVENT_SCROLL_DIR_CHANGED,
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline const char *
|
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_ACCUMULATED);
|
||||||
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL);
|
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL);
|
||||||
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_TIMEOUT);
|
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_TIMEOUT);
|
||||||
|
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_DIR_CHANGED);
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +102,8 @@ wheel_handle_event_on_state_none(struct fallback_dispatch *dispatch,
|
||||||
case WHEEL_EVENT_SCROLL:
|
case WHEEL_EVENT_SCROLL:
|
||||||
dispatch->wheel.state = WHEEL_STATE_ACCUMULATING_SCROLL;
|
dispatch->wheel.state = WHEEL_STATE_ACCUMULATING_SCROLL;
|
||||||
break;
|
break;
|
||||||
|
case WHEEL_EVENT_SCROLL_DIR_CHANGED:
|
||||||
|
break;
|
||||||
case WHEEL_EVENT_RELEASE:
|
case WHEEL_EVENT_RELEASE:
|
||||||
case WHEEL_EVENT_SCROLL_ACCUMULATED:
|
case WHEEL_EVENT_SCROLL_ACCUMULATED:
|
||||||
case WHEEL_EVENT_SCROLL_TIMEOUT:
|
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;
|
dispatch->wheel.state = WHEEL_STATE_NONE;
|
||||||
break;
|
break;
|
||||||
case WHEEL_EVENT_SCROLL:
|
case WHEEL_EVENT_SCROLL:
|
||||||
|
case WHEEL_EVENT_SCROLL_DIR_CHANGED:
|
||||||
/* Ignore scroll while the wheel is pressed */
|
/* Ignore scroll while the wheel is pressed */
|
||||||
break;
|
break;
|
||||||
case WHEEL_EVENT_PRESS:
|
case WHEEL_EVENT_PRESS:
|
||||||
|
|
@ -144,6 +149,9 @@ wheel_handle_event_on_state_accumulating_scroll(struct fallback_dispatch *dispat
|
||||||
case WHEEL_EVENT_SCROLL:
|
case WHEEL_EVENT_SCROLL:
|
||||||
/* Ignore scroll while accumulating deltas */
|
/* Ignore scroll while accumulating deltas */
|
||||||
break;
|
break;
|
||||||
|
case WHEEL_EVENT_SCROLL_DIR_CHANGED:
|
||||||
|
dispatch->wheel.state = WHEEL_STATE_NONE;
|
||||||
|
break;
|
||||||
case WHEEL_EVENT_RELEASE:
|
case WHEEL_EVENT_RELEASE:
|
||||||
case WHEEL_EVENT_SCROLL_TIMEOUT:
|
case WHEEL_EVENT_SCROLL_TIMEOUT:
|
||||||
log_wheel_bug(dispatch, event);
|
log_wheel_bug(dispatch, event);
|
||||||
|
|
@ -168,6 +176,10 @@ wheel_handle_event_on_state_scrolling(struct fallback_dispatch *dispatch,
|
||||||
case WHEEL_EVENT_SCROLL_TIMEOUT:
|
case WHEEL_EVENT_SCROLL_TIMEOUT:
|
||||||
dispatch->wheel.state = WHEEL_STATE_NONE;
|
dispatch->wheel.state = WHEEL_STATE_NONE;
|
||||||
break;
|
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_RELEASE:
|
||||||
case WHEEL_EVENT_SCROLL_ACCUMULATED:
|
case WHEEL_EVENT_SCROLL_ACCUMULATED:
|
||||||
log_wheel_bug(dispatch, event);
|
log_wheel_bug(dispatch, event);
|
||||||
|
|
@ -330,6 +342,30 @@ wheel_handle_state_scrolling(struct fallback_dispatch *dispatch,
|
||||||
wheel_flush_scroll(dispatch, device, time);
|
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
|
void
|
||||||
fallback_wheel_process_relative(struct fallback_dispatch *dispatch,
|
fallback_wheel_process_relative(struct fallback_dispatch *dispatch,
|
||||||
struct evdev_device *device,
|
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.y += e->value;
|
||||||
dispatch->wheel.hi_res_event_received = true;
|
dispatch->wheel.hi_res_event_received = true;
|
||||||
dispatch->pending_event |= EVDEV_WHEEL;
|
dispatch->pending_event |= EVDEV_WHEEL;
|
||||||
|
wheel_handle_direction_change(dispatch, e, time);
|
||||||
wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time);
|
wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time);
|
||||||
break;
|
break;
|
||||||
case REL_HWHEEL_HI_RES:
|
case REL_HWHEEL_HI_RES:
|
||||||
dispatch->wheel.hi_res.x += e->value;
|
dispatch->wheel.hi_res.x += e->value;
|
||||||
dispatch->wheel.hi_res_event_received = true;
|
dispatch->wheel.hi_res_event_received = true;
|
||||||
dispatch->pending_event |= EVDEV_WHEEL;
|
dispatch->pending_event |= EVDEV_WHEEL;
|
||||||
|
wheel_handle_direction_change(dispatch, e, time);
|
||||||
wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time);
|
wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -442,6 +480,7 @@ fallback_init_wheel(struct fallback_dispatch *dispatch,
|
||||||
char timer_name[64];
|
char timer_name[64];
|
||||||
|
|
||||||
dispatch->wheel.state = WHEEL_STATE_NONE;
|
dispatch->wheel.state = WHEEL_STATE_NONE;
|
||||||
|
dispatch->wheel.dir = WHEEL_DIR_UNKNOW;
|
||||||
|
|
||||||
/* On kernel < 5.0 we need to emulate high-resolution
|
/* On kernel < 5.0 we need to emulate high-resolution
|
||||||
wheel scroll events */
|
wheel scroll events */
|
||||||
|
|
|
||||||
|
|
@ -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, 6 * 120);
|
||||||
|
|
||||||
test_hi_res_wheel_event(dev, axis, 30);
|
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, -60);
|
||||||
|
test_hi_res_wheel_event(dev, axis, -40);
|
||||||
test_hi_res_wheel_event(dev, axis, 180);
|
test_hi_res_wheel_event(dev, axis, 180);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -914,6 +914,44 @@ START_TEST(pointer_scroll_wheel_inhibit_small_deltas)
|
||||||
}
|
}
|
||||||
END_TEST
|
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)
|
START_TEST(pointer_scroll_natural_defaults)
|
||||||
{
|
{
|
||||||
struct litest_device *dev = litest_current_device();
|
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_vertical, LITEST_WHEEL, LITEST_TABLET);
|
||||||
litest_add(pointer_scroll_wheel_hires_send_only_lores_horizontal, 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_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, 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);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue