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);