mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-01-02 06:20:10 +01:00
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 <peter.hutterer@who-t.net> Reviewed-by: Hans de Goede <hdegoede@redhat.com>
This commit is contained in:
parent
b2a6ead992
commit
df83144457
4 changed files with 233 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
106
doc/svg/clickfinger-distance.svg
Normal file
106
doc/svg/clickfinger-distance.svg
Normal file
|
|
@ -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>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue