diff --git a/doc/clickpad-softbuttons.dox b/doc/clickpad-softbuttons.dox index e7c4e543..a4f2e444 100644 --- a/doc/clickpad-softbuttons.dox +++ b/doc/clickpad-softbuttons.dox @@ -64,9 +64,16 @@ software-defined button areas. @image html clickfinger.svg "One, two and three-finger click with Clickfinger behavior" -The Xorg synaptics driver uses 30% of the touchpad dimensions as threshold, -libinput does not have this restriction. If two fingers are on the pad -while clicking, that is a two-finger click. +On some touchpads, libinput imposes a limit on how the fingers may be placed +on the touchpad. In the most common use-case this allows for a user to +trigger a click with the thumb while leaving the pointer-moving finger on +the touchpad. + +@image html clickfinger-distance.svg "Illustration of the distance detection algorithm" + +In the illustration above the red area marks the proximity area around the +first finger. Since the thumb is outside of that area libinput considers the +click a single-finger click rather than a two-finger click. Clickfinger configuration can be enabled through the libinput_device_config_click_set_method() call. If clickfingers are diff --git a/doc/svg/clickfinger-distance.svg b/doc/svg/clickfinger-distance.svg new file mode 100644 index 00000000..ac659cfe --- /dev/null +++ b/doc/svg/clickfinger-distance.svg @@ -0,0 +1,106 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c index f0c39b62..469f0fa5 100644 --- a/src/evdev-mt-touchpad-buttons.c +++ b/src/evdev-mt-touchpad-buttons.c @@ -784,12 +784,81 @@ tp_post_physical_buttons(struct tp_dispatch *tp, uint64_t time) return 0; } +static inline int +tp_check_clickfinger_distance(struct tp_dispatch *tp, + struct tp_touch *t1, + struct tp_touch *t2) +{ + int res_x, res_y; + double x, y; + + if (!t1 || !t2) + return 0; + + /* no resolution, so let's assume they're close enough together */ + if (tp->device->abs.fake_resolution) + return 1; + + res_x = tp->device->abs.absinfo_x->resolution; + res_y = tp->device->abs.absinfo_y->resolution; + + x = abs(t1->point.x - t2->point.x)/res_x; + y = abs(t1->point.y - t2->point.y)/res_y; + + /* maximum spread is 40mm horiz, 20mm vert. Anything wider than that + * is probably a gesture. The y spread is small so we ignore clicks + * with thumbs at the bottom of the touchpad while the pointer + * moving finger is still on the pad */ + return (x < 40 && y < 20) ? 1 : 0; +} + static uint32_t tp_clickfinger_set_button(struct tp_dispatch *tp) { uint32_t button; + unsigned int nfingers = tp->nfingers_down; + struct tp_touch *t; + struct tp_touch *first = NULL, + *second = NULL, + *third = NULL; + uint32_t close_touches = 0; - switch (tp->nfingers_down) { + if (nfingers < 2 || nfingers > 3) + goto out; + + /* two or three fingers down on the touchpad. Check for distance + * between the fingers. */ + tp_for_each_touch(tp, t) { + if (t->state != TOUCH_BEGIN && t->state != TOUCH_UPDATE) + continue; + + if (!first) + first = t; + else if (!second) + second = t; + else if (!third) { + third = t; + break; + } + } + + if (!first || !second) { + nfingers = 1; + goto out; + } + + close_touches |= tp_check_clickfinger_distance(tp, first, second) << 0; + close_touches |= tp_check_clickfinger_distance(tp, second, third) << 1; + close_touches |= tp_check_clickfinger_distance(tp, first, third) << 2; + + switch(__builtin_popcount(close_touches)) { + case 0: nfingers = 1; break; + case 1: nfingers = 2; break; + default: nfingers = 3; break; + } + +out: + switch (nfingers) { case 0: case 1: button = BTN_LEFT; break; case 2: button = BTN_RIGHT; break; diff --git a/test/touchpad.c b/test/touchpad.c index a7479100..d9daf434 100644 --- a/test/touchpad.c +++ b/test/touchpad.c @@ -1808,6 +1808,51 @@ START_TEST(touchpad_2fg_clickfinger) } END_TEST +START_TEST(touchpad_2fg_clickfinger_distance) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + libinput_device_config_click_set_method(dev->libinput_device, + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER); + litest_drain_events(li); + + litest_touch_down(dev, 0, 90, 50); + litest_touch_down(dev, 1, 10, 50); + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_LEFT, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); + + litest_touch_down(dev, 0, 50, 5); + litest_touch_down(dev, 1, 50, 95); + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_LEFT, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, + BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); +} +END_TEST + START_TEST(touchpad_clickfinger_to_area_method) { struct litest_device *dev = litest_current_device(); @@ -2636,7 +2681,7 @@ START_TEST(clickpad_topsoftbuttons_clickfinger) litest_assert_empty_queue(li); litest_touch_down(dev, 0, 90, 5); - litest_touch_down(dev, 1, 10, 5); + litest_touch_down(dev, 1, 80, 5); litest_event(dev, EV_KEY, BTN_LEFT, 1); litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_KEY, BTN_LEFT, 0); @@ -5084,6 +5129,7 @@ litest_setup_tests(void) litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger_no_touch, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY); + litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger_distance, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD); litest_add("touchpad:clickfinger", touchpad_clickfinger_to_area_method, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:clickfinger", touchpad_clickfinger_to_area_method_while_down, LITEST_CLICKPAD, LITEST_ANY);