From df1f6ba40fa746eaae7831bf7ebd1460bc5f769f Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 4 Oct 2018 08:34:33 +1000 Subject: [PATCH] touchpad: avoid motion events when moving one finger into AREA If a 2fg scroll motion starts with both fingers in the bottom button area and one finger moves into the main area before the other, we used to send motion events for that finger. Once the second finger moved into the main area the scroll was detected correctly but by then the cursor may have moved out of the intended focus area. We have two transitions where we may start sending motion events: when we move out of the bottom area and when the finger moves by more than 5mm within the button area. In both cases, check for any touches that are in the bottom area and started at the 'same' time as our moving touch. Mark those as 'moved' to release them for gestures so we get the right finger count and axis/gesture events instead of just motion events. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad-buttons.c | 45 ++++++++++++++++++++++++++++++++- src/evdev-mt-touchpad.h | 1 + test/litest.h | 9 +++++++ test/test-touchpad.c | 38 ++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c index ae58c154..84c54413 100644 --- a/src/evdev-mt-touchpad-buttons.c +++ b/src/evdev-mt-touchpad-buttons.c @@ -251,6 +251,36 @@ tp_button_area_handle_event(struct tp_dispatch *tp, } } +/** + * Release any button in the bottom area, provided it started within a + * threshold around start_time (i.e. simultaneously with the other touch + * that triggered this call). + */ +static inline void +tp_button_release_other_bottom_touches(struct tp_dispatch *tp, + uint64_t other_start_time) +{ + struct tp_touch *t; + + tp_for_each_touch(tp, t) { + uint64_t tdelta; + + if (t->button.state != BUTTON_STATE_BOTTOM || + t->button.has_moved) + continue; + + if (other_start_time > t->button.initial_time) + tdelta = other_start_time - t->button.initial_time; + else + tdelta = t->button.initial_time - other_start_time; + + if (tdelta > ms2us(80)) + continue; + + t->button.has_moved = true; + } +} + static void tp_button_bottom_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -271,6 +301,14 @@ tp_button_bottom_handle_event(struct tp_dispatch *tp, case BUTTON_EVENT_IN_TOP_L: case BUTTON_EVENT_IN_AREA: tp_button_set_state(tp, t, BUTTON_STATE_AREA, event); + + /* We just transitioned one finger from BOTTOM to AREA, + * if there are other fingers in BOTTOM that started + * simultaneously with this finger, release those fingers + * because they're part of a gesture. + */ + tp_button_release_other_bottom_touches(tp, + t->button.initial_time); break; case BUTTON_EVENT_UP: tp_button_set_state(tp, t, BUTTON_STATE_NONE, event); @@ -485,8 +523,12 @@ tp_button_check_for_movement(struct tp_dispatch *tp, struct tp_touch *t) mm = evdev_device_unit_delta_to_mm(tp->device, &delta); vector_length = hypot(mm.x, mm.y); - if (vector_length > 5.0 /* mm */) + if (vector_length > 5.0 /* mm */) { t->button.has_moved = true; + + tp_button_release_other_bottom_touches(tp, + t->button.initial_time); + } } void @@ -500,6 +542,7 @@ tp_button_handle_state(struct tp_dispatch *tp, uint64_t time) if (t->state == TOUCH_BEGIN) { t->button.initial = t->point; + t->button.initial_time = time; t->button.has_moved = false; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index c514c0f1..93857ae4 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -209,6 +209,7 @@ struct tp_touch { struct libinput_timer timer; struct device_coords initial; bool has_moved; /* has moved more than threshold */ + uint64_t initial_time; } button; struct { diff --git a/test/litest.h b/test/litest.h index 8e6f4447..e204e75a 100644 --- a/test/litest.h +++ b/test/litest.h @@ -961,6 +961,15 @@ litest_has_clickfinger(struct litest_device *dev) return methods & LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; } +static inline bool +litest_has_btnareas(struct litest_device *dev) +{ + struct libinput_device *device = dev->libinput_device; + uint32_t methods = libinput_device_config_click_get_methods(device); + + return methods & LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; +} + static inline void litest_enable_clickfinger(struct litest_device *dev) { diff --git a/test/test-touchpad.c b/test/test-touchpad.c index 8ad695ce..4f119844 100644 --- a/test/test-touchpad.c +++ b/test/test-touchpad.c @@ -511,6 +511,43 @@ START_TEST(touchpad_2fg_scroll_return_to_motion) } END_TEST +START_TEST(touchpad_2fg_scroll_from_btnareas) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!litest_has_2fg_scroll(dev) || + !litest_has_btnareas(dev)) + return; + + litest_enable_2fg_scroll(dev); + litest_enable_buttonareas(dev); + litest_drain_events(li); + + litest_touch_down(dev, 0, 30, 95); + litest_touch_down(dev, 1, 50, 95); + libinput_dispatch(li); + + /* First finger moves out of the area first but it's a scroll + * motion, should not trigger POINTER_MOTION */ + for (int i = 0; i < 5; i++) { + litest_touch_move(dev, 0, 30, 95 - i); + } + libinput_dispatch(li); + + for (int i = 0; i < 20; i++) { + litest_touch_move(dev, 0, 30, 90 - i); + litest_touch_move(dev, 1, 30, 95 - i); + } + libinput_dispatch(li); + + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); +} +END_TEST + START_TEST(touchpad_scroll_natural_defaults) { struct litest_device *dev = litest_current_device(); @@ -6713,6 +6750,7 @@ TEST_COLLECTION(touchpad) litest_add("touchpad:scroll", touchpad_2fg_scroll_return_to_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:scroll", touchpad_2fg_scroll_source, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:scroll", touchpad_2fg_scroll_semi_mt, LITEST_SEMI_MT, LITEST_SINGLE_TOUCH); + litest_add("touchpad:scroll", touchpad_2fg_scroll_from_btnareas, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:scroll", touchpad_scroll_natural_defaults, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:scroll", touchpad_scroll_natural_enable_config, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:scroll", touchpad_scroll_natural_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);