diff --git a/doc/clickpad-softbuttons.dox b/doc/clickpad-softbuttons.dox
index a4f2e444..cf4c8c9e 100644
--- a/doc/clickpad-softbuttons.dox
+++ b/doc/clickpad-softbuttons.dox
@@ -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"
diff --git a/doc/svg/software-buttons.svg b/doc/svg/software-buttons.svg
index 903535c8..c0bc6104 100644
--- a/doc/svg/software-buttons.svg
+++ b/doc/svg/software-buttons.svg
@@ -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" />
-
-
-
-
-
-
-
diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c
index 076eab00..a9b75216 100644
--- a/src/evdev-mt-touchpad-buttons.c
+++ b/src/evdev-mt-touchpad-buttons.c
@@ -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:
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index 1f05a03e..7277726e 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -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 {
diff --git a/test/touchpad-buttons.c b/test/touchpad-buttons.c
index 064c29ef..080c6702 100644
--- a/test/touchpad-buttons.c
+++ b/test/touchpad-buttons.c
@@ -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);