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. +@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 @@ -404,6 +404,33 @@ parse_pressure_range_property(const char *prop, int *hi, int *lo) return true; } +/** + * Helper function to parse the LIBINPUT_ATTR_PALM_PRESSURE_THRESHOLD + * property from udev. Property is of the form: + * LIBINPUT_ATTR_PALM_PRESSURE_THRESHOLD= + * 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