Commit 886b5a2c authored by Peter Hutterer's avatar Peter Hutterer

touchpad: add a middle button software area

Middle button interaction is most commonly to paste and it is a single-event
interaction (button press). We provided middle button in software button mode
by emulating it with a two-finger press with L+R down at the same time. This
is also what many touchpads are spectacularly bad at, it is very common to
detect the physical button down event before the second finger registers,
resulting in left or right clicks where a middle button should be triggered.

Unless the fingers are resting on the touchpad for at least one scanout, the
success rate for middle button emulation is only at 70% or so.

This patch adds a 25%-width middle button area between the left and the right
software button, everything else stays the same. To avoid immediate breakage,
the middle button emulation remains but may be removed in the future.
The doc is updated to only refer to the middle button area now.

https://bugs.freedesktop.org/show_bug.cgi?id=94755Signed-off-by: Peter Hutterer's avatarPeter Hutterer <peter.hutterer@who-t.net>
Reviewed-by: default avatarHans de Goede <hdegoede@redhat.com>
parent 1ecf6d7a
......@@ -20,16 +20,20 @@ generated by libinput and passed to the caller in response to a click.
@section software_buttons Software button areas
On most clickpads, this is the default behavior. The bottom of the touchpad
is split in the middle to generate left or right button events on click. The
height of the button area depends on the hardware but is usually around
10mm.
is split into three distinct areas generate left, middle or right button
events on click. The height of the button area depends on the hardware but
is usually around 10mm.
Left, right and middle button events can be triggered as follows:
- if a finger is in the main area or the left button area, a click generates
left button events.
- if a finger is in the right area, a click generates right button events.
- if there is a finger in both the left and right button area, a click
generates middle button events.
- if a finger is in the middle area, a click generates middle button events.
The middle button is always centered on the touchpad and smaller in size
than the left or right button. The actual size is device-dependent though as
many touchpads do not have visible markings for the middle button the exact
location of the button is not visibly obvious.
@image html software-buttons.svg "Left, right and middle-button click with software button areas"
......
......@@ -41,8 +41,8 @@
id="namedview4312"
showgrid="false"
inkscape:zoom="0.57798581"
inkscape:cx="1134.9723"
inkscape:cy="-71.183873"
inkscape:cx="842.57758"
inkscape:cy="-74.644166"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
......@@ -106,7 +106,7 @@
id="rect2858-7"
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" />
<g
transform="matrix(0.79657897,0.11742288,-0.14814182,0.631399,665.11943,-345.64117)"
transform="matrix(0.79657897,0.11742288,-0.14814182,0.631399,749.8966,-336.99044)"
id="g3663-9-8">
<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"
......@@ -124,30 +124,6 @@
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>
<g
id="g4656"
transform="translate(-0.31997204,0.28487182)">
<g
id="g4639">
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#000000;stroke-width:0.72135597px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path2820-0-5"
d="m 970.15914,263.93369 32.58666,-97.47985 c 0,0 12.2603,-46.32869 38.5506,-33.925 20.7457,9.78778 17.4301,24.72594 16.4399,28.75425 -10.1846,41.43257 -30.105,105.84033 -30.105,105.84033 l -0.428,-0.37827 z"
sodipodi:nodetypes="ccssccc" />
<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:0.00144271;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
id="path2824-9-3"
d="m 987.16367,214.84092 c 8.97014,-26.84686 16.75933,-50.19953 17.30923,-51.89481 3.8651,-11.91483 10.3862,-22.76212 16.5622,-27.54951 6.8496,-5.30946 13.8243,-5.75615 21.9108,-1.40323 3.7999,2.04543 6.0302,3.61208 8.2265,5.77836 2.5214,2.487 3.6881,4.17002 5.1008,7.35828 1.3655,3.08181 1.9391,7.11725 1.5051,10.58923 -0.597,4.77663 -10.2821,40.41668 -20.9931,77.25236 -7.7256,26.56907 -9.371,31.11182 -9.5644,31.10964 -0.1479,-0.002 -55.70067,-1.83937 -56.08626,-2.18017 -0.16723,-0.1478 6.29681,-19.93218 16.02913,-49.06015 z"
sodipodi:nodetypes="scsssscsccs" />
<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-8-8"
d="m 1008.3595,164.57667 c 3.8651,-11.91483 7.6606,-19.35039 13.8366,-24.13778 6.8495,-5.30946 12.0833,-5.57765 20.1698,-1.22474 9.3061,4.73331 12.9905,9.62271 11.9094,19.03362 -6.3459,19.3209 -6.9054,22.12042 -9.2168,26.32727 -0.1479,-0.002 -33.6651,-14.70742 -35.0296,-15.23839 -1.4035,-0.54616 -1.8884,-3.70289 -1.6694,-4.75998 z" />
</g>
</g>
<g
transform="translate(-386.56163,2.2570367)"
id="g4656-3">
......
......@@ -64,6 +64,7 @@ static inline const char*
button_event_to_str(enum button_event event) {
switch(event) {
CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_R);
CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_M);
CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_L);
CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_R);
CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_M);
......@@ -92,11 +93,21 @@ is_inside_bottom_right_area(const struct tp_dispatch *tp,
t->point.x > tp->buttons.bottom_area.rightbutton_left_edge;
}
static inline bool
is_inside_bottom_middle_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return is_inside_bottom_button_area(tp, t) &&
!is_inside_bottom_right_area(tp, t) &&
t->point.x > tp->buttons.bottom_area.middlebutton_left_edge;
}
static inline bool
is_inside_bottom_left_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return is_inside_bottom_button_area(tp, t) &&
!is_inside_bottom_middle_area(tp, t) &&
!is_inside_bottom_right_area(tp, t);
}
......@@ -192,6 +203,7 @@ tp_button_none_handle_event(struct tp_dispatch *tp,
{
switch (event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM, event);
break;
......@@ -220,6 +232,7 @@ tp_button_area_handle_event(struct tp_dispatch *tp,
{
switch (event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
case BUTTON_EVENT_IN_TOP_R:
case BUTTON_EVENT_IN_TOP_M:
......@@ -243,6 +256,7 @@ tp_button_bottom_handle_event(struct tp_dispatch *tp,
{
switch (event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
if (event != t->button.curr)
tp_button_set_state(tp,
......@@ -273,6 +287,7 @@ tp_button_top_handle_event(struct tp_dispatch *tp,
{
switch (event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
tp_button_set_state(tp, t, BUTTON_STATE_TOP_TO_IGNORE, event);
break;
......@@ -305,6 +320,7 @@ tp_button_top_new_handle_event(struct tp_dispatch *tp,
{
switch(event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
tp_button_set_state(tp, t, BUTTON_STATE_AREA, event);
break;
......@@ -355,6 +371,7 @@ tp_button_top_to_ignore_handle_event(struct tp_dispatch *tp,
event);
break;
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
case BUTTON_EVENT_IN_AREA:
break;
......@@ -377,6 +394,7 @@ tp_button_ignore_handle_event(struct tp_dispatch *tp,
{
switch (event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
case BUTTON_EVENT_IN_TOP_R:
case BUTTON_EVENT_IN_TOP_M:
......@@ -450,6 +468,8 @@ tp_button_handle_state(struct tp_dispatch *tp, uint64_t time)
if (is_inside_bottom_right_area(tp, t))
event = BUTTON_EVENT_IN_BOTTOM_R;
else if (is_inside_bottom_middle_area(tp, t))
event = BUTTON_EVENT_IN_BOTTOM_M;
else if (is_inside_bottom_left_area(tp, t))
event = BUTTON_EVENT_IN_BOTTOM_L;
else if (is_inside_top_right_area(tp, t))
......@@ -543,7 +563,14 @@ tp_init_softbuttons(struct tp_dispatch *tp,
} else {
tp->buttons.bottom_area.top_edge = height * .85 + yoffset;
}
tp->buttons.bottom_area.rightbutton_left_edge = width/2 + xoffset;
/* The middle button is 25% of the touchpad and centered. Many
* touchpads don't have markings for the middle button at all so we
* need to make it big enough to reliably hit it but not too big so
* it takes away all the space.
*/
tp->buttons.bottom_area.middlebutton_left_edge = width * 0.375 + xoffset;
tp->buttons.bottom_area.rightbutton_left_edge = width * 0.625 + xoffset;
}
void
......@@ -989,6 +1016,8 @@ tp_post_clickpadbutton_buttons(struct tp_dispatch *tp, uint64_t time)
break;
case BUTTON_EVENT_IN_TOP_M:
is_top = 1;
/* fallthrough */
case BUTTON_EVENT_IN_BOTTOM_M:
area |= MIDDLE;
break;
case BUTTON_EVENT_IN_TOP_R:
......
......@@ -70,6 +70,7 @@ enum touch_palm_state {
enum button_event {
BUTTON_EVENT_IN_BOTTOM_R = 30,
BUTTON_EVENT_IN_BOTTOM_M,
BUTTON_EVENT_IN_BOTTOM_L,
BUTTON_EVENT_IN_TOP_R,
BUTTON_EVENT_IN_TOP_M,
......@@ -283,6 +284,7 @@ struct tp_dispatch {
struct {
int32_t top_edge; /* in device coordinates */
int32_t rightbutton_left_edge; /* in device coordinates */
int32_t middlebutton_left_edge; /* in device coordinates */
} bottom_area;
struct {
......
......@@ -987,6 +987,35 @@ START_TEST(clickpad_softbutton_left)
}
END_TEST
START_TEST(clickpad_softbutton_middle)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
litest_drain_events(li);
litest_touch_down(dev, 0, 50, 90);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_assert_button_event(li,
BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_touch_up(dev, 0);
litest_assert_button_event(li,
BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED);
libinput_dispatch(li);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(clickpad_softbutton_right)
{
struct litest_device *dev = litest_current_device();
......@@ -1585,6 +1614,7 @@ litest_setup_tests(void)
litest_add("touchpad:click", clickpad_finger_pin, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:softbutton", clickpad_softbutton_left, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
litest_add("touchpad:softbutton", clickpad_softbutton_middle, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
litest_add("touchpad:softbutton", clickpad_softbutton_right, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
litest_add("touchpad:softbutton", clickpad_softbutton_left_tap_n_drag, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
litest_add("touchpad:softbutton", clickpad_softbutton_right_tap_n_drag, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment