From: Peter Hutterer Date: Wed, 3 Jun 2015 02:15:51 +0000 (+1000) Subject: touchpad: impose maximum distance limits on clickfingers X-Git-Tag: 0.17.0~2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=df83144457176e454953d2a580a8f28e73f4a677;p=platform%2Fupstream%2Flibinput.git touchpad: impose maximum distance limits on clickfingers A common use-case for clickfinger is to use the index finger for moving the pointer, then triggering the click with a thumb. If the index finger isn't lifted before the click this counted as two-finger click. To avoid this, check the distance between touches on the touchpad (on touchpads reporting resolution values anyway). If the touches are too far apart, don't count them together (or specifically only count those close enough together as multi-finger). The touch area is uneven, it's wider than high. Spreading fingers horizontally is more common and this also makes it easier to rule out thumbs which tend to be well below the fingers. http://bugs.freedesktop.org/show_bug.cgi?id=90526 Signed-off-by: Peter Hutterer Reviewed-by: Hans de Goede --- 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; + + 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; + } - switch (tp->nfingers_down) { +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);