[PATCH libinput 3/4] touchpad: impose maximum distance limits on clickfingers

Peter Hutterer peter.hutterer at who-t.net
Tue Jun 2 22:51:57 PDT 2015


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 <peter.hutterer at who-t.net>
---
 doc/clickpad-softbuttons.dox     |  13 +++--
 doc/svg/clickfinger-distance.svg | 106 +++++++++++++++++++++++++++++++++++++++
 src/evdev-mt-touchpad-buttons.c  |  71 +++++++++++++++++++++++++-
 test/touchpad.c                  |  48 +++++++++++++++++-
 4 files changed, 233 insertions(+), 5 deletions(-)
 create mode 100644 doc/svg/clickfinger-distance.svg

diff --git a/doc/clickpad-softbuttons.dox b/doc/clickpad-softbuttons.dox
index e7c4e54..a4f2e44 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.
+
+ at 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 0000000..ac659cf
--- /dev/null
+++ b/doc/svg/clickfinger-distance.svg
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="89.829216mm"
+   height="59.06765mm"
+   viewBox="0 0 318.2925 209.29482"
+   id="svg4184"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="clickfinger-distance.svg">
+  <defs
+     id="defs4186" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.4"
+     inkscape:cx="235.68795"
+     inkscape:cy="163.39995"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <metadata
+     id="metadata4189">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-257.99662,-299.41313)">
+    <rect
+       width="313.09872"
+       height="167.89594"
+       x="260.59351"
+       y="302.01001"
+       id="rect2858-0"
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.19376326;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" />
+    <rect
+       style="opacity:0.92000002;fill:#7b0000;fill-opacity:0.2983426;stroke:#000000;stroke-width:1.00100005;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4788"
+       width="188.57143"
+       height="79.285713"
+       x="355"
+       y="309.50507" />
+    <g
+       transform="matrix(0.79657897,0.11742288,-0.14814182,0.631399,276.6631,-158.96703)"
+       id="g3663-9-5">
+      <path
+         d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z"
+         id="path2820-6-6"
+         style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
+         id="path2824-1-1"
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
+         id="path2824-7-1-4"
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+         inkscape:connector-curvature="0" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00100005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+       id="path2824-1-1-3"
+       d="m 353.70196,495.15765 c -24.01774,-7.29937 -29.0012,-10.10221 -30.51977,-10.54973 -10.67294,-3.14527 -18.27051,-5.54063 -23.77758,-13.4704 -5.50707,-7.92977 -5.34967,-20.78347 8.87612,-26.31604 14.2258,-5.53257 39.34351,8.79597 60.13061,16.16341 20.7871,7.36744 33.04563,11.44545 39.33422,13.87551 -8.10022,18.05041 -7.22129,21.15857 -10.11054,33.34117 -0.0481,0.20261 -17.87459,-5.12433 -43.93306,-13.04392 z"
+       sodipodi:nodetypes="sszzzcss" />
+    <path
+       inkscape:connector-curvature="0"
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+       id="path2824-7-1-4-3"
+       d="m 324.44991,483.39364 c -10.67294,-1.94747 -17.88441,-5.64478 -21.62691,-8.75386 -8.11652,-9.03765 -6.31775,-15.03428 -3.3272,-13.99784 8.90495,-0.9097 30.20384,9.01528 33.86042,10.17935 -5.80268,11.37909 -1.08919,13.70271 -8.90631,12.57235 z"
+       sodipodi:nodetypes="ccccc" />
+  </g>
+</svg>
diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c
index f0c39b6..469f0fa 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 a747910..d9daf43 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);
-- 
2.4.1



More information about the wayland-devel mailing list