Commit 25d54b90 authored by Peter Hutterer's avatar Peter Hutterer

touchpad: add pressure-base palm detection

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=94236Signed-off-by: Peter Hutterer's avatarPeter Hutterer <peter.hutterer@who-t.net>
parent 381cce8d
......@@ -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
......
......@@ -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
......
......@@ -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 {
......
......@@ -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=<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.
......
......@@ -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,
......
......@@ -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);
......
......@@ -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);
......
......@@ -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
......
......@@ -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
......
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