[PATCH libinput] touchpad: work palm detection into the tap state machine

Peter Hutterer peter.hutterer at who-t.net
Mon Nov 6 05:10:33 UTC 2017


Unlike the already-existing thumb detection, a touch may be labelled palm at
any time, not just during the initial touch down. This requires full
integration into the tap state machine to unwind properly. For most states, a
palm detection simply ignores the finger and reverts to the most recent state.

One exception is the case of two fingers down, one finger up followed by the
remaining finger detected as a palm finger. This triggers a single-finger tap
but with timestamps that may be from the wrong finger. Since we're within a
short tap timeout anyway this should not matter too much.

https://bugs.freedesktop.org/show_bug.cgi?id=103210

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
The SVG changes are in a separate commit here:
https://github.com/whot/libinput/tree/wip/touchpad-tap-palm-detect
They wouldn't get past the message size limit otherwise.

 src/evdev-mt-touchpad-tap.c | 126 ++++++-
 src/evdev-mt-touchpad.c     |   1 +
 src/evdev-mt-touchpad.h     |   2 +
 test/test-touchpad-tap.c    | 786 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 912 insertions(+), 3 deletions(-)

diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c
index bfcef973..d2b5189d 100644
--- a/src/evdev-mt-touchpad-tap.c
+++ b/src/evdev-mt-touchpad-tap.c
@@ -44,6 +44,7 @@ enum tap_event {
 	TAP_EVENT_BUTTON,
 	TAP_EVENT_TIMEOUT,
 	TAP_EVENT_THUMB,
+	TAP_EVENT_PALM,
 };
 
 /*****************************************
@@ -77,6 +78,7 @@ tap_state_to_str(enum tp_tap_state state)
 	CASE_RETURN_STRING(TAP_STATE_DRAGGING_2);
 	CASE_RETURN_STRING(TAP_STATE_MULTITAP);
 	CASE_RETURN_STRING(TAP_STATE_MULTITAP_DOWN);
+	CASE_RETURN_STRING(TAP_STATE_MULTITAP_PALM);
 	CASE_RETURN_STRING(TAP_STATE_DEAD);
 	}
 	return NULL;
@@ -92,6 +94,7 @@ tap_event_to_str(enum tap_event event)
 	CASE_RETURN_STRING(TAP_EVENT_TIMEOUT);
 	CASE_RETURN_STRING(TAP_EVENT_BUTTON);
 	CASE_RETURN_STRING(TAP_EVENT_THUMB);
+	CASE_RETURN_STRING(TAP_EVENT_PALM);
 	}
 	return NULL;
 }
@@ -178,6 +181,10 @@ tp_tap_idle_handle_event(struct tp_dispatch *tp,
 	case TAP_EVENT_THUMB:
 		log_tap_bug(tp, event);
 		break;
+	case TAP_EVENT_PALM:
+		tp->tap.state = TAP_STATE_IDLE;
+		t->tap.state = TAP_TOUCH_STATE_DEAD;
+		break;
 	}
 }
 
@@ -224,6 +231,11 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp,
 		t->tap.state = TAP_TOUCH_STATE_DEAD;
 		tp_tap_clear_timer(tp);
 		break;
+	case TAP_EVENT_PALM:
+		tp->tap.state = TAP_STATE_IDLE;
+		t->tap.state = TAP_TOUCH_STATE_DEAD;
+		tp_tap_clear_timer(tp);
+		break;
 	}
 }
 
@@ -253,6 +265,9 @@ tp_tap_hold_handle_event(struct tp_dispatch *tp,
 		t->tap.is_thumb = true;
 		t->tap.state = TAP_TOUCH_STATE_DEAD;
 		break;
+	case TAP_EVENT_PALM:
+		tp->tap.state = TAP_STATE_IDLE;
+		break;
 	}
 }
 
@@ -286,6 +301,8 @@ tp_tap_tapped_handle_event(struct tp_dispatch *tp,
 			      LIBINPUT_BUTTON_STATE_RELEASED);
 		break;
 	case TAP_EVENT_THUMB:
+	case TAP_EVENT_PALM:
+		log_tap_bug(tp, event);
 		break;
 	}
 }
@@ -318,6 +335,10 @@ tp_tap_touch2_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		tp->tap.state = TAP_STATE_TOUCH;
+		tp_tap_set_timer(tp, time); /* overwrite timer */
+		break;
 	}
 }
 
@@ -345,6 +366,9 @@ tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		tp->tap.state = TAP_STATE_HOLD;
+		break;
 	}
 }
 
@@ -380,6 +404,29 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		/* There's only one saved press time and it's overwritten by
+		 * the last touch down. So in the case of finger down, palm
+		 * down, finger up, palm detected, we use the
+		 * palm touch's press time here instead of the finger's press
+		 * time. Let's wait and see if that's an issue.
+		 */
+		tp_tap_notify(tp,
+			      tp->tap.saved_press_time,
+			      1,
+			      LIBINPUT_BUTTON_STATE_PRESSED);
+		if (tp->tap.drag_enabled) {
+			tp->tap.state = TAP_STATE_TAPPED;
+			tp->tap.saved_release_time = time;
+			tp_tap_set_timer(tp, time);
+		} else {
+			tp_tap_notify(tp,
+				      time,
+				      1,
+				      LIBINPUT_BUTTON_STATE_RELEASED);
+			tp->tap.state = TAP_STATE_IDLE;
+		}
+		break;
 	}
 }
 
@@ -414,6 +461,9 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		tp->tap.state = TAP_STATE_TOUCH_2;
+		break;
 	}
 }
 
@@ -439,6 +489,9 @@ tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
+		break;
 	}
 }
 
@@ -472,6 +525,9 @@ tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		tp->tap.state = TAP_STATE_TAPPED;
+		break;
 	}
 }
 
@@ -507,6 +563,13 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		tp_tap_notify(tp,
+			      tp->tap.saved_release_time,
+			      1,
+			      LIBINPUT_BUTTON_STATE_RELEASED);
+		tp->tap.state = TAP_STATE_IDLE;
+		break;
 	}
 }
 
@@ -533,6 +596,7 @@ tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp,
 		tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
 		break;
 	case TAP_EVENT_THUMB:
+	case TAP_EVENT_PALM:
 		break;
 	}
 }
@@ -562,6 +626,13 @@ tp_tap_dragging_tap_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		tp_tap_notify(tp,
+			      tp->tap.saved_release_time,
+			      1,
+			      LIBINPUT_BUTTON_STATE_RELEASED);
+		tp->tap.state = TAP_STATE_IDLE;
+		break;
 	}
 }
 
@@ -589,6 +660,9 @@ tp_tap_dragging2_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP;
+		break;
 	}
 }
 
@@ -629,6 +703,7 @@ tp_tap_multitap_handle_event(struct tp_dispatch *tp,
 		tp_tap_clear_timer(tp);
 		break;
 	case TAP_EVENT_THUMB:
+	case TAP_EVENT_PALM:
 		break;
 	}
 }
@@ -667,6 +742,39 @@ tp_tap_multitap_down_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		tp->tap.state = TAP_STATE_MULTITAP_PALM;
+		break;
+	}
+}
+
+static void
+tp_tap_multitap_palm_handle_event(struct tp_dispatch *tp,
+				  struct tp_touch *t,
+				  enum tap_event event,
+				  uint64_t time)
+{
+	switch (event) {
+	case TAP_EVENT_RELEASE:
+		/* This is the palm finger */
+		break;
+	case TAP_EVENT_TOUCH:
+		tp->tap.state = TAP_STATE_MULTITAP_DOWN;
+		break;
+	case TAP_EVENT_MOTION:
+		break;
+	case TAP_EVENT_TIMEOUT:
+	case TAP_EVENT_BUTTON:
+		tp->tap.state = TAP_STATE_IDLE;
+		tp_tap_clear_timer(tp);
+		tp_tap_notify(tp,
+			      tp->tap.saved_release_time,
+			      1,
+			      LIBINPUT_BUTTON_STATE_RELEASED);
+		break;
+	case TAP_EVENT_THUMB:
+	case TAP_EVENT_PALM:
+		break;
 	}
 }
 
@@ -689,6 +797,10 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp,
 		break;
 	case TAP_EVENT_THUMB:
 		break;
+	case TAP_EVENT_PALM:
+		if (tp->nfingers_down == 0)
+			tp->tap.state = TAP_STATE_IDLE;
+		break;
 	}
 }
 
@@ -751,6 +863,9 @@ tp_tap_handle_event(struct tp_dispatch *tp,
 	case TAP_STATE_MULTITAP_DOWN:
 		tp_tap_multitap_down_handle_event(tp, t, event, time);
 		break;
+	case TAP_STATE_MULTITAP_PALM:
+		tp_tap_multitap_palm_handle_event(tp, t, event, time);
+		break;
 	case TAP_STATE_DEAD:
 		tp_tap_dead_handle_event(tp, t, event, time);
 		break;
@@ -817,15 +932,20 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
 		    tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
 			t->tap.state = TAP_TOUCH_STATE_DEAD;
 
-		/* If a touch was considered thumb for tapping once, we
+		/* If a touch was considered thumb or palm for tapping once, we
 		 * ignore it for the rest of lifetime */
-		if (t->tap.is_thumb)
+		if (t->tap.is_thumb || t->tap.is_palm)
 			continue;
 
 		if (t->state == TOUCH_HOVERING)
 			continue;
 
-		if (t->state == TOUCH_BEGIN) {
+		if (t->palm.state != PALM_NONE) {
+			assert(!t->tap.is_palm);
+			tp_tap_handle_event(tp, t, TAP_EVENT_PALM, time);
+			t->tap.is_palm = true;
+			t->tap.state = TAP_TOUCH_STATE_DEAD;
+		} else if (t->state == TOUCH_BEGIN) {
 			/* The simple version: if a touch is a thumb on
 			 * begin we ignore it. All other thumb touches
 			 * follow the normal tap state for now */
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index a7dad940..7d77c03a 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -290,6 +290,7 @@ tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
 	t->thumb.state = THUMB_STATE_MAYBE;
 	t->thumb.first_touch_time = time;
 	t->tap.is_thumb = false;
+	t->tap.is_palm = false;
 	assert(tp->nfingers_down >= 1);
 	tp->hysteresis.last_motion_time = time;
 }
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index 3da6c58e..05ef352e 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -103,6 +103,7 @@ enum tp_tap_state {
 	TAP_STATE_DRAGGING_2,
 	TAP_STATE_MULTITAP,
 	TAP_STATE_MULTITAP_DOWN,
+	TAP_STATE_MULTITAP_PALM,
 	TAP_STATE_DEAD, /**< finger count exceeded */
 };
 
@@ -195,6 +196,7 @@ struct tp_touch {
 		enum tp_tap_touch_state state;
 		struct device_coords initial;
 		bool is_thumb;
+		bool is_palm;
 	} tap;
 
 	struct {
diff --git a/test/test-touchpad-tap.c b/test/test-touchpad-tap.c
index 6fc3c4f6..34efb017 100644
--- a/test/test-touchpad-tap.c
+++ b/test/test-touchpad-tap.c
@@ -2375,12 +2375,779 @@ START_TEST(touchpad_drag_lock_default_unavailable)
 }
 END_TEST
 
+static inline bool
+touchpad_has_palm_pressure(struct litest_device *dev)
+{
+	struct libevdev *evdev = dev->evdev;
+
+	if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_PRESSURE))
+		return true;
+
+	return false;
+}
+
+START_TEST(touchpad_tap_palm_on_idle)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* Finger down is immediately palm */
+
+	litest_touch_down_extended(dev, 0, 50, 50, axes);
+	litest_touch_up(dev, 0);
+
+	libinput_dispatch(li);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_touch)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* Finger down is palm after touch begin */
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+	litest_touch_up(dev, 0);
+
+	libinput_dispatch(li);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_touch_hold_timeout)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* Finger down is palm after tap timeout */
+
+	litest_touch_down(dev, 0, 50, 50);
+	libinput_dispatch(li);
+	litest_timeout_tap();
+	libinput_dispatch(li);
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+	litest_touch_up(dev, 0);
+
+	libinput_dispatch(li);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_touch_hold_move)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* Finger down is palm after tap move threshold */
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_move_to(dev, 0, 50, 50, 60, 60, 10, 1);
+	litest_drain_events(li);
+
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+	litest_touch_up(dev, 0);
+
+	libinput_dispatch(li);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_tapped)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* tap + palm down */
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_up(dev, 0);
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+	litest_touch_up(dev, 0);
+
+	libinput_dispatch(li);
+	litest_timeout_tap();
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_tapped_2fg)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* tap + palm down + tap with second finger */
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_up(dev, 0);
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+
+	libinput_dispatch(li);
+
+	litest_touch_down(dev, 1, 50, 50);
+	litest_touch_up(dev, 1);
+	libinput_dispatch(li);
+
+	litest_timeout_tap();
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+
+	litest_touch_up(dev, 0);
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_drag)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* tap + finger down (->drag), finger turns into palm */
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_up(dev, 0);
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+
+	litest_touch_down(dev, 0, 50, 50);
+	libinput_dispatch(li);
+	litest_timeout_tap();
+	libinput_dispatch(li);
+
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_touch_up(dev, 0);
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_drag_2fg)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+	int which = _i; /* ranged test */
+	int this = which % 2,
+	    other = (which + 1) % 2;
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* tap + finger down, 2nd finger down, finger turns to palm */
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_up(dev, 0);
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+
+	litest_touch_down(dev, this, 50, 50);
+	litest_touch_down(dev, other, 60, 50);
+	libinput_dispatch(li);
+
+	litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
+	libinput_dispatch(li);
+
+	litest_touch_move_to(dev, other, 60, 50, 65, 50, 10, 1);
+	litest_assert_only_typed_events(li,
+					LIBINPUT_EVENT_POINTER_MOTION);
+	litest_touch_up(dev, other);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_touch_up(dev, this);
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_touch_2)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+	int which = _i; /* ranged test */
+	int this = which % 2,
+	    other = (which + 1) % 2;
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* 2fg tap with one finger detected as palm */
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_down(dev, 1, 60, 60);
+	litest_drain_events(li);
+	litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
+
+
+	litest_touch_up(dev, this);
+	litest_touch_up(dev, other);
+
+	libinput_dispatch(li);
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_timeout_tap();
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_touch_2_retouch)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+	int which = _i; /* ranged test */
+	int this = which % 2,
+	    other = (which + 1) % 2;
+
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* 2fg tap with one finger detected as palm, that finger is lifted
+	 * and taps again as not-palm  */
+	litest_touch_down(dev, this, 50, 50);
+	litest_touch_down(dev, other, 60, 60);
+	litest_drain_events(li);
+	litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
+	litest_touch_up(dev, this);
+	libinput_dispatch(li);
+
+	litest_touch_down(dev, this, 70, 70);
+	litest_touch_up(dev, this);
+	litest_touch_up(dev, other);
+
+	libinput_dispatch(li);
+	litest_assert_button_event(li,
+				   BTN_RIGHT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_timeout_tap();
+	litest_assert_button_event(li,
+				   BTN_RIGHT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_touch_3)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+	int which = _i; /* ranged test */
+	int this = which % 3;
+
+	if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) <= 3)
+		return;
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* 3fg tap with one finger detected as palm, that finger is lifted,
+	   other two fingers lifted cause 2fg tap */
+	litest_touch_down(dev, this, 50, 50);
+	litest_touch_down(dev, (this + 1) % 3, 60, 50);
+	litest_touch_down(dev, (this + 2) % 3, 70, 50);
+	litest_drain_events(li);
+	litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
+	litest_touch_up(dev, this);
+	libinput_dispatch(li);
+
+	litest_touch_up(dev, (this + 1) % 3);
+	litest_touch_up(dev, (this + 2) % 3);
+
+	libinput_dispatch(li);
+	litest_assert_button_event(li,
+				   BTN_RIGHT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_timeout_tap();
+	litest_assert_button_event(li,
+				   BTN_RIGHT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_touch_3_retouch)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+	int which = _i; /* ranged test */
+	int this = which % 3;
+
+	if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) <= 3)
+		return;
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* 3fg tap with one finger detected as palm, that finger is lifted,
+	   then put down again as normal finger -> 3fg tap */
+	litest_touch_down(dev, this, 50, 50);
+	litest_touch_down(dev, (this + 1) % 3, 60, 50);
+	litest_touch_down(dev, (this + 2) % 3, 70, 50);
+	litest_drain_events(li);
+	litest_timeout_tap();
+	libinput_dispatch(li);
+
+	litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
+	litest_touch_up(dev, this);
+	libinput_dispatch(li);
+
+	litest_touch_down(dev, this, 50, 50);
+	litest_touch_up(dev, this);
+	litest_touch_up(dev, (this + 1) % 3);
+	litest_touch_up(dev, (this + 2) % 3);
+
+	libinput_dispatch(li);
+	litest_assert_button_event(li,
+				   BTN_MIDDLE,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_timeout_tap();
+	litest_assert_button_event(li,
+				   BTN_MIDDLE,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_on_touch_4)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+	int which = _i; /* ranged test */
+	int this = which % 4;
+
+	if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) <= 4)
+		return;
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	/* 3fg tap with one finger detected as palm, that finger is lifted,
+	   other two fingers lifted cause 2fg tap */
+	litest_touch_down(dev, this, 50, 50);
+	litest_touch_down(dev, (this + 1) % 4, 60, 50);
+	litest_touch_down(dev, (this + 2) % 4, 70, 50);
+	litest_touch_down(dev, (this + 3) % 4, 80, 50);
+	litest_drain_events(li);
+	litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
+	litest_touch_up(dev, this);
+	libinput_dispatch(li);
+
+	litest_touch_up(dev, (this + 1) % 4);
+	litest_touch_up(dev, (this + 2) % 4);
+	litest_touch_up(dev, (this + 3) % 4);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_after_tap)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_up(dev, 0);
+	libinput_dispatch(li);
+
+	libinput_dispatch(li);
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+	litest_touch_up(dev, 0);
+	libinput_dispatch(li);
+
+	litest_timeout_tap();
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_multitap)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+	int range = _i, /* ranged test */
+	    ntaps;
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	for (ntaps = 0; ntaps <= range; ntaps++) {
+		litest_touch_down(dev, 0, 50, 50);
+		litest_touch_up(dev, 0);
+		libinput_dispatch(li);
+		msleep(10);
+	}
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+	litest_touch_up(dev, 0);
+	libinput_dispatch(li);
+	litest_timeout_tap();
+	libinput_dispatch(li);
+
+	for (ntaps = 0; ntaps <= range; ntaps++) {
+		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);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_multitap_timeout)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+	int range = _i, /* ranged test */
+	    ntaps;
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	for (ntaps = 0; ntaps <= range; ntaps++) {
+		litest_touch_down(dev, 0, 50, 50);
+		litest_touch_up(dev, 0);
+		libinput_dispatch(li);
+		msleep(10);
+	}
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+	libinput_dispatch(li);
+	litest_timeout_tap();
+	libinput_dispatch(li);
+
+	for (ntaps = 0; ntaps <= range; ntaps++) {
+		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);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_multitap_down_again)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+	int range = _i, /* ranged test */
+	    ntaps;
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	for (ntaps = 0; ntaps <= range; ntaps++) {
+		litest_touch_down(dev, 0, 50, 50);
+		litest_touch_up(dev, 0);
+		libinput_dispatch(li);
+		msleep(10);
+	}
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+	libinput_dispatch(li);
+
+	/* keep palm finger down */
+	for (ntaps = 0; ntaps <= range; ntaps++) {
+		litest_touch_down(dev, 1, 50, 50);
+		litest_touch_up(dev, 1);
+		libinput_dispatch(li);
+		msleep(10);
+	}
+
+	for (ntaps = 0; ntaps <= 2 * range; ntaps++) {
+		litest_assert_button_event(li,
+					   BTN_LEFT,
+					   LIBINPUT_BUTTON_STATE_PRESSED);
+		litest_assert_button_event(li,
+					   BTN_LEFT,
+					   LIBINPUT_BUTTON_STATE_RELEASED);
+	}
+
+	litest_touch_up(dev, 0);
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_palm_multitap_click)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+	int range = _i, /* ranged test */
+	    ntaps;
+
+	if (!touchpad_has_palm_pressure(dev))
+		return;
+
+	litest_enable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	for (ntaps = 0; ntaps <= range; ntaps++) {
+		litest_touch_down(dev, 0, 50, 50);
+		litest_touch_up(dev, 0);
+		libinput_dispatch(li);
+		msleep(10);
+	}
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
+	libinput_dispatch(li);
+	/* keep palm finger down */
+
+	litest_button_click(dev, BTN_LEFT, true);
+	litest_button_click(dev, BTN_LEFT, false);
+	libinput_dispatch(li);
+
+	for (ntaps = 0; ntaps <= range; ntaps++) {
+		litest_assert_button_event(li,
+					   BTN_LEFT,
+					   LIBINPUT_BUTTON_STATE_PRESSED);
+		litest_assert_button_event(li,
+					   BTN_LEFT,
+					   LIBINPUT_BUTTON_STATE_RELEASED);
+	}
+
+	/* the click */
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+	litest_touch_up(dev, 0);
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
 void
 litest_setup_tests_touchpad_tap(void)
 {
 	struct range multitap_range = {3, 5};
 	struct range tap_map_range = { LIBINPUT_CONFIG_TAP_MAP_LRM,
 				       LIBINPUT_CONFIG_TAP_MAP_LMR + 1 };
+	struct range range_2fg = {0, 2};
+	struct range range_3fg = {0, 3};
+	struct range range_4fg = {0, 4};
 
 	litest_add("tap-1fg:1fg", touchpad_1fg_tap, LITEST_TOUCHPAD, LITEST_ANY);
 	litest_add("tap-1fg:1fg", touchpad_1fg_doubletap, LITEST_TOUCHPAD, LITEST_ANY);
@@ -2455,4 +3222,23 @@ litest_setup_tests_touchpad_tap(void)
 	litest_add("tap:drag", touchpad_drag_disabled, LITEST_TOUCHPAD, LITEST_ANY);
 	litest_add("tap:drag", touchpad_drag_disabled_immediate, LITEST_TOUCHPAD, LITEST_ANY);
 	litest_add_ranged("tap-multitap:drag", touchpad_drag_disabled_multitap_no_drag, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
+
+	litest_add("tap:palm", touchpad_tap_palm_on_idle, LITEST_TOUCHPAD, LITEST_ANY);
+	litest_add("tap:palm", touchpad_tap_palm_on_touch, LITEST_TOUCHPAD, LITEST_ANY);
+	litest_add("tap:palm", touchpad_tap_palm_on_touch_hold_timeout, LITEST_TOUCHPAD, LITEST_ANY);
+	litest_add("tap:palm", touchpad_tap_palm_on_touch_hold_move, LITEST_TOUCHPAD, LITEST_ANY);
+	litest_add("tap:palm", touchpad_tap_palm_on_tapped, LITEST_TOUCHPAD, LITEST_ANY);
+	litest_add("tap:palm", touchpad_tap_palm_on_tapped_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+	litest_add("tap:palm", touchpad_tap_palm_on_drag, LITEST_TOUCHPAD, LITEST_ANY);
+	litest_add_ranged("tap:palm", touchpad_tap_palm_on_drag_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_2fg);
+	litest_add_ranged("tap:palm", touchpad_tap_palm_on_touch_2, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_2fg);
+	litest_add_ranged("tap:palm", touchpad_tap_palm_on_touch_2_retouch, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_2fg);
+	litest_add_ranged("tap:palm", touchpad_tap_palm_on_touch_3, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_3fg);
+	litest_add_ranged("tap:palm", touchpad_tap_palm_on_touch_3_retouch, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_3fg);
+	litest_add_ranged("tap:palm", touchpad_tap_palm_on_touch_4, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_4fg);
+	litest_add("tap:palm", touchpad_tap_palm_after_tap, LITEST_TOUCHPAD, LITEST_ANY);
+	litest_add_ranged("tap:palm", touchpad_tap_palm_multitap, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
+	litest_add_ranged("tap:palm", touchpad_tap_palm_multitap_timeout, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
+	litest_add_ranged("tap:palm", touchpad_tap_palm_multitap_down_again, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &multitap_range);
+	litest_add_ranged("tap:palm", touchpad_tap_palm_multitap_click, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
 }
-- 
2.13.6



More information about the wayland-devel mailing list