@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
--- /dev/null
+<?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>
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;
}
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();
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);
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);