[PATCH libinput 2/3] touchpad: add pressure-base palm detection

Peter Hutterer peter.hutterer at who-t.net
Fri Jun 30 01:00:57 UTC 2017


If a touch goes past the fixed pressure threshold it is labelled as a palm and
stays a palm. Default value is one that works well here on a T440 and is
virtually impossible to trigger by a normal finger or thumb. A udev property
is exposed so we can handle this in the udev hwdb and the new tool introduce a
few commits ago can help finding the palm detection threshold.

Unlike the other palm detection features, once a palm goes past the threshold
it remains a palm until the touch is released. This means palm overrides any
other palm detection features. For code simplicity, we don't combine the
states but merely check for pressure before and after the other palm detection
functions. If the pressure triggers, it will trigger before anything else. And
if something else is already active (e.g. edge where the pressure doesn't work
well) it will trigger as soon as the palm is released.

The palm threshold should thus be chosen with some room to spare between the
highest finger pressure.

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

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 doc/palm-detection.dox             |  12 +++
 src/evdev-mt-touchpad.c            |  71 +++++++++++++
 src/evdev-mt-touchpad.h            |   4 +
 src/libinput-util.c                |  27 +++++
 src/libinput-util.h                |   1 +
 test/test-misc.c                   |  29 ++++++
 test/test-touchpad.c               | 201 +++++++++++++++++++++++++++++++++----
 udev/90-libinput-model-quirks.hwdb |   1 +
 udev/parse_hwdb.py                 |   6 +-
 9 files changed, 330 insertions(+), 22 deletions(-)

diff --git a/doc/palm-detection.dox b/doc/palm-detection.dox
index 73c81b2c..c8ceb4eb 100644
--- a/doc/palm-detection.dox
+++ b/doc/palm-detection.dox
@@ -13,6 +13,18 @@ Lenovo T440 happened in the left-most and right-most 5% of the touchpad. The
 T440 series has one of the largest touchpads, other touchpads are less
 affected by palm touches.
 
+ at section palm_pressure Palm detection based on pressure
+
+The simplest form of palm detection labels a touch as palm when the pressure
+value goes above a certain threshold. This threshold is usually high enough
+that it cannot be triggered by a finger movement. One a touch is labelled as
+palm based on pressure, it will remain so even if the pressure drops below
+the threshold again. This ensures that a palm remains a palm even when the
+pressure changes as the user is typing.
+
+For some information on how to detect pressure on a touch and debug the
+pressure ranges, see @ref touchpad_pressure.
+
 @section palm_exclusion_zones Palm exclusion zones
 
 libinput enables palm detection on the edge of the touchpad. Two exclusion
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index a3455583..4d0cff97 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -746,12 +746,33 @@ tp_palm_detect_edge(struct tp_dispatch *tp,
 	return true;
 }
 
+static bool
+tp_palm_detect_pressure_triggered(struct tp_dispatch *tp,
+				  struct tp_touch *t,
+				  uint64_t time)
+{
+	if (!tp->palm.use_pressure)
+		return false;
+
+	if (t->palm.state != PALM_NONE &&
+	    t->palm.state != PALM_PRESSURE)
+		return false;
+
+	if (t->pressure > tp->palm.pressure_threshold)
+		t->palm.state = PALM_PRESSURE;
+
+	return t->palm.state == PALM_PRESSURE;
+}
+
 static void
 tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
 {
 	const char *palm_state;
 	enum touch_palm_state oldstate = t->palm.state;
 
+	if (tp_palm_detect_pressure_triggered(tp, t, time))
+		goto out;
+
 	if (tp_palm_detect_dwt_triggered(tp, t, time))
 		goto out;
 
@@ -764,8 +785,18 @@ tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
 	if (tp_palm_detect_edge(tp, t, time))
 		goto out;
 
+	/* Pressure is highest priority because it cannot be released and
+	 * overrides all other checks. So we check once before anything else
+	 * in case pressure triggers on a non-palm touch. And again after
+	 * everything in case one of the others released but we have a
+	 * pressure trigger now.
+	 */
+	if (tp_palm_detect_pressure_triggered(tp, t, time))
+		goto out;
+
 	return;
 out:
+
 	if (oldstate == t->palm.state)
 		return;
 
@@ -782,6 +813,9 @@ out:
 	case PALM_TOOL_PALM:
 		palm_state = "tool-palm";
 		break;
+	case PALM_PRESSURE:
+		palm_state = "pressure";
+		break;
 	case PALM_NONE:
 	default:
 		abort();
@@ -2288,6 +2322,42 @@ tp_init_palmdetect_edge(struct tp_dispatch *tp,
 	tp->palm.right_edge = edges.x;
 }
 
+static int
+tp_read_palm_pressure_prop(struct tp_dispatch *tp,
+			   const struct evdev_device *device)
+{
+	struct udev_device *udev_device = device->udev_device;
+	const char *prop;
+	int threshold;
+	const int default_palm_threshold = 130;
+
+	prop = udev_device_get_property_value(udev_device,
+			      "LIBINPUT_ATTR_PALM_PRESSURE_THRESHOLD");
+	if (!prop)
+		return default_palm_threshold;
+
+	threshold = parse_palm_pressure_property(prop);
+
+	return threshold > 0 ? threshold : default_palm_threshold;
+}
+
+static inline void
+tp_init_palmdetect_pressure(struct tp_dispatch *tp,
+			    struct evdev_device *device)
+{
+	if (!libevdev_has_event_code(device->evdev, EV_ABS, ABS_MT_PRESSURE)) {
+		tp->palm.use_pressure = false;
+		return;
+	}
+
+	tp->palm.pressure_threshold = tp_read_palm_pressure_prop(tp, device);
+	tp->palm.use_pressure = true;
+
+	evdev_log_debug(device,
+			"palm: pressure threshold is %d\n",
+			tp->palm.pressure_threshold);
+}
+
 static void
 tp_init_palmdetect(struct tp_dispatch *tp,
 		   struct evdev_device *device)
@@ -2308,6 +2378,7 @@ tp_init_palmdetect(struct tp_dispatch *tp,
 		tp->palm.use_mt_tool = true;
 
 	tp_init_palmdetect_edge(tp, device);
+	tp_init_palmdetect_pressure(tp, device);
 }
 
 static void
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index 2873c014..6d014607 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -58,6 +58,7 @@ enum touch_palm_state {
 	PALM_TYPING,
 	PALM_TRACKPOINT,
 	PALM_TOOL_PALM,
+	PALM_PRESSURE,
 };
 
 enum button_event {
@@ -343,6 +344,9 @@ struct tp_dispatch {
 		bool monitor_trackpoint;
 
 		bool use_mt_tool;
+
+		bool use_pressure;
+		int pressure_threshold;
 	} palm;
 
 	struct {
diff --git a/src/libinput-util.c b/src/libinput-util.c
index eeeeca07..9104e9d9 100644
--- a/src/libinput-util.c
+++ b/src/libinput-util.c
@@ -405,6 +405,33 @@ parse_pressure_range_property(const char *prop, int *hi, int *lo)
 }
 
 /**
+ * Helper function to parse the LIBINPUT_ATTR_PALM_PRESSURE_THRESHOLD
+ * property from udev. Property is of the form:
+ * LIBINPUT_ATTR_PALM_PRESSURE_THRESHOLD=<integer>
+ * Where the number indicates the minimum threshold to consider a touch to
+ * be a palm.
+ *
+ * @param prop The value of the udev property (without the *
+ * LIBINPUT_ATTR_PALM_PRESSURE_THRESHOLD=)
+ * @return The pressure threshold or 0 on error
+ */
+int
+parse_palm_pressure_property(const char *prop)
+{
+	int threshold = 0;
+
+	if (!prop)
+		return 0;
+
+	if (!safe_atoi(prop, &threshold) ||
+	    threshold < 0 ||
+	    threshold > 255) /* No touchpad device has pressure > 255 */
+		return 0;
+
+        return threshold;
+}
+
+/**
  * Return the next word in a string pointed to by state before the first
  * separator character. Call repeatedly to tokenize a whole string.
  *
diff --git a/src/libinput-util.h b/src/libinput-util.h
index 8d8e3d56..123dbf03 100644
--- a/src/libinput-util.h
+++ b/src/libinput-util.h
@@ -392,6 +392,7 @@ double parse_trackpoint_accel_property(const char *prop);
 bool parse_dimension_property(const char *prop, size_t *width, size_t *height);
 bool parse_calibration_property(const char *prop, float calibration[6]);
 bool parse_pressure_range_property(const char *prop, int *hi, int *lo);
+int parse_palm_pressure_property(const char *prop);
 
 enum tpkbcombo_layout {
 	TPKBCOMBO_LAYOUT_UNKNOWN,
diff --git a/test/test-misc.c b/test/test-misc.c
index e55daed4..2778ed91 100644
--- a/test/test-misc.c
+++ b/test/test-misc.c
@@ -1044,6 +1044,34 @@ START_TEST(pressure_range_prop_parser)
 }
 END_TEST
 
+START_TEST(palm_pressure_parser)
+{
+	struct parser_test tests[] = {
+		{ "1", 1 },
+		{ "10", 10 },
+		{ "255", 255 },
+
+		{ "-12", 0 },
+		{ "360", 0 },
+		{ "0", 0 },
+		{ "-0", 0 },
+		{ "a", 0 },
+		{ "10a", 0 },
+		{ "10-", 0 },
+		{ "sadfasfd", 0 },
+		{ "361", 0 },
+		{ NULL, 0 }
+	};
+
+	int i, angle;
+
+	for (i = 0; tests[i].tag != NULL; i++) {
+		angle = parse_palm_pressure_property(tests[i].tag);
+		ck_assert_int_eq(angle, tests[i].expected_value);
+	}
+}
+END_TEST
+
 START_TEST(time_conversion)
 {
 	ck_assert_int_eq(us(10), 10);
@@ -1308,6 +1336,7 @@ litest_setup_tests_misc(void)
 	litest_add_no_device("misc:parser", reliability_prop_parser);
 	litest_add_no_device("misc:parser", calibration_prop_parser);
 	litest_add_no_device("misc:parser", pressure_range_prop_parser);
+	litest_add_no_device("misc:parser", palm_pressure_parser);
 	litest_add_no_device("misc:parser", safe_atoi_test);
 	litest_add_no_device("misc:parser", safe_atod_test);
 	litest_add_no_device("misc:parser", strsplit_test);
diff --git a/test/test-touchpad.c b/test/test-touchpad.c
index a380bcfd..57f0d60a 100644
--- a/test/test-touchpad.c
+++ b/test/test-touchpad.c
@@ -32,6 +32,27 @@
 #include "libinput-util.h"
 #include "litest.h"
 
+static inline bool
+has_disable_while_typing(struct litest_device *device)
+{
+	return libinput_device_config_dwt_is_available(device->libinput_device);
+}
+
+static inline struct litest_device *
+dwt_init_paired_keyboard(struct libinput *li,
+			 struct litest_device *touchpad)
+{
+	enum litest_device_type which = LITEST_KEYBOARD;
+
+	if (libevdev_get_id_vendor(touchpad->evdev) == VENDOR_ID_APPLE)
+		which = LITEST_APPLE_KEYBOARD;
+
+	if (libevdev_get_id_vendor(touchpad->evdev) == VENDOR_ID_CHICONY)
+		which = LITEST_ACER_HAWAII_KEYBOARD;
+
+	return litest_add_device(li, which);
+}
+
 START_TEST(touchpad_1fg_motion)
 {
 	struct litest_device *dev = litest_current_device();
@@ -1373,6 +1394,159 @@ START_TEST(touchpad_palm_detect_tool_palm_tap)
 }
 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_palm_detect_pressure)
+{
+	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_disable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	litest_touch_down_extended(dev, 0, 50, 99, axes);
+	litest_touch_move_to(dev, 0, 50, 50, 80, 99, 10, 0);
+	litest_touch_up(dev, 0);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_palm_detect_pressure_late)
+{
+	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_disable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	litest_touch_down(dev, 0, 50, 50);
+	litest_touch_move_to(dev, 0, 50, 70, 80, 90, 10, 0);
+	litest_drain_events(li);
+	libinput_dispatch(li);
+	litest_touch_move_to_extended(dev, 0, 80, 90, 50, 20,
+				      axes, 10, 0);
+	litest_touch_up(dev, 0);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_palm_detect_pressure_keep_palm)
+{
+	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_disable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	litest_touch_down(dev, 0, 80, 90);
+	litest_touch_move_to_extended(dev, 0, 80, 90, 50, 20,
+				      axes, 10, 0);
+	litest_touch_move_to(dev, 0, 50, 20, 80, 90, 10, 0);
+	litest_touch_up(dev, 0);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_palm_detect_pressure_after_edge)
+{
+	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) ||
+	    !touchpad_has_palm_detect_size(dev) ||
+	    !litest_has_2fg_scroll(dev))
+		return;
+
+	litest_enable_2fg_scroll(dev);
+	litest_disable_tap(dev->libinput_device);
+	litest_drain_events(li);
+
+	litest_touch_down(dev, 0, 99, 50);
+	litest_touch_move_to_extended(dev, 0, 99, 50, 20, 50, axes, 20, 0);
+	litest_touch_up(dev, 0);
+	libinput_dispatch(li);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_palm_detect_pressure_after_dwt)
+{
+	struct litest_device *touchpad = litest_current_device();
+	struct litest_device *keyboard;
+	struct libinput *li = touchpad->libinput;
+	struct axis_replacement axes[] = {
+		{ ABS_MT_PRESSURE, 75 },
+		{ -1, 0 }
+	};
+
+	if (!touchpad_has_palm_pressure(touchpad))
+		return;
+
+	keyboard = dwt_init_paired_keyboard(li, touchpad);
+	litest_disable_tap(touchpad->libinput_device);
+	litest_drain_events(li);
+
+	litest_keyboard_key(keyboard, KEY_A, true);
+	litest_keyboard_key(keyboard, KEY_A, false);
+	litest_drain_events(li);
+
+	/* within dwt timeout, dwt blocks events */
+	litest_touch_down(touchpad, 0, 50, 50);
+	litest_touch_move_to_extended(touchpad, 0, 50, 50, 20, 50, axes, 20, 0);
+	litest_assert_empty_queue(li);
+
+	litest_timeout_dwt_short();
+	libinput_dispatch(li);
+	litest_assert_empty_queue(li);
+
+	/* after dwt timeout, pressure blocks events */
+	litest_touch_move_to_extended(touchpad, 0, 20, 50, 50, 50, axes, 20, 0);
+	litest_touch_up(touchpad, 0);
+
+	litest_assert_empty_queue(li);
+
+	litest_delete_device(keyboard);
+}
+END_TEST
+
 START_TEST(touchpad_left_handed)
 {
 	struct litest_device *dev = litest_current_device();
@@ -2619,27 +2793,6 @@ START_TEST(touchpad_initial_state)
 }
 END_TEST
 
-static inline bool
-has_disable_while_typing(struct litest_device *device)
-{
-	return libinput_device_config_dwt_is_available(device->libinput_device);
-}
-
-static inline struct litest_device *
-dwt_init_paired_keyboard(struct libinput *li,
-			 struct litest_device *touchpad)
-{
-	enum litest_device_type which = LITEST_KEYBOARD;
-
-	if (libevdev_get_id_vendor(touchpad->evdev) == VENDOR_ID_APPLE)
-		which = LITEST_APPLE_KEYBOARD;
-
-	if (libevdev_get_id_vendor(touchpad->evdev) == VENDOR_ID_CHICONY)
-		which = LITEST_ACER_HAWAII_KEYBOARD;
-
-	return litest_add_device(li, which);
-}
-
 START_TEST(touchpad_dwt)
 {
 	struct litest_device *touchpad = litest_current_device();
@@ -5005,6 +5158,12 @@ litest_setup_tests_touchpad(void)
 	litest_add("touchpad:palm", touchpad_palm_detect_tool_palm_on_off, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
 	litest_add("touchpad:palm", touchpad_palm_detect_tool_palm_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
 
+	litest_add("touchpad:palm", touchpad_palm_detect_pressure, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+	litest_add("touchpad:palm", touchpad_palm_detect_pressure_late, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+	litest_add("touchpad:palm", touchpad_palm_detect_pressure_keep_palm, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+	litest_add("touchpad:palm", touchpad_palm_detect_pressure_after_edge, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+	litest_add("touchpad:palm", touchpad_palm_detect_pressure_after_dwt, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+
 	litest_add("touchpad:left-handed", touchpad_left_handed, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_CLICKPAD);
 	litest_add_for_device("touchpad:left-handed", touchpad_left_handed_appletouch, LITEST_APPLETOUCH);
 	litest_add("touchpad:left-handed", touchpad_left_handed_clickpad, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
diff --git a/udev/90-libinput-model-quirks.hwdb b/udev/90-libinput-model-quirks.hwdb
index f9e7093a..89e95723 100644
--- a/udev/90-libinput-model-quirks.hwdb
+++ b/udev/90-libinput-model-quirks.hwdb
@@ -183,6 +183,7 @@ libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*:pvrThinkPad??50*:
 libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*:pvrThinkPad??60*:
 libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*:pvrThinkPadX1Carbon3rd:*
  LIBINPUT_MODEL_LENOVO_T450_TOUCHPAD=1
+ LIBINPUT_ATTR_PALM_PRESSURE_THRESHOLD=150
 
 ##########################################
 # Logitech
diff --git a/udev/parse_hwdb.py b/udev/parse_hwdb.py
index 8ac64010..5b7b4538 100755
--- a/udev/parse_hwdb.py
+++ b/udev/parse_hwdb.py
@@ -122,8 +122,12 @@ def property_grammar():
                          Suppress('=') -
                          kbintegration_tags('VALUE')]
 
+    palm_prop = [Literal('LIBINPUT_ATTR_PALM_PRESSURE_THRESHOLD')('NAME') -
+                        Suppress('=') -
+                        INTEGER('X')]
+
     grammar = Or(model_props + size_props + reliability + tpkbcombo +
-                 pressure_prop + kbintegration)
+                 pressure_prop + kbintegration + palm_prop)
 
     return grammar
 
-- 
2.13.0



More information about the wayland-devel mailing list