mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-01-04 03:50:14 +01:00
Merge branch 'wip/touchpad-palm-pressure'
This commit is contained in:
commit
ca9d6a88d7
15 changed files with 700 additions and 73 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -47,62 +47,44 @@ locally. Note that the hwdb entry is **not public API** and **may change at
|
|||
any time**. Users are advised to @ref reporting_bugs "report a bug" with the
|
||||
updated pressure ranges when testing has completed.
|
||||
|
||||
First, install the "evemu" package providing the ```evemu-record``` tool.
|
||||
Run ```evemu-record``` as root (without arguments) to see a list of devices
|
||||
and select the touchpad device. Pipe the actual output of the tool into a
|
||||
file for later analysis. For example:
|
||||
Use the ```libinput measure touchpad-pressure``` tool provided by libinput.
|
||||
This tool will search for your touchpad device and print some pressure
|
||||
statistics, including whether a touch is/was considered logically down.
|
||||
Example output of the tool is below:
|
||||
|
||||
<pre>
|
||||
$ sudo evemu-record > touchpad-pressure.txt
|
||||
Available devices:
|
||||
/dev/input/event0: Lid Switch
|
||||
/dev/input/event1: Sleep Button
|
||||
/dev/input/event2: Power Button
|
||||
/dev/input/event3: AT Translated Set 2 keyboard
|
||||
/dev/input/event4: SynPS/2 Synaptics TouchPad
|
||||
/dev/input/event5: ELAN Touchscreen
|
||||
[...]
|
||||
Select the device event number [0-19]: 4
|
||||
# Ctrl+C to quit, the output will be in touchpad-pressure.txt
|
||||
$ sudo libinput measure touchpad-pressure
|
||||
Ready for recording data.
|
||||
Pressure range used: 8:10
|
||||
Palm pressure range used: 65535
|
||||
Place a single finger on the touchpad to measure pressure values.
|
||||
Ctrl+C to exit
|
||||
|
||||
Sequence 1190 pressure: min: 39 max: 48 avg: 43 median: 44 tags: down
|
||||
Sequence 1191 pressure: min: 49 max: 65 avg: 62 median: 64 tags: down
|
||||
Sequence 1192 pressure: min: 40 max: 78 avg: 64 median: 66 tags: down
|
||||
Sequence 1193 pressure: min: 36 max: 83 avg: 70 median: 73 tags: down
|
||||
Sequence 1194 pressure: min: 43 max: 76 avg: 72 median: 74 tags: down
|
||||
Touchpad pressure: 47 min: 47 max: 86 tags: down
|
||||
</pre>
|
||||
|
||||
Now move a finger at **normal pressure** several times around the touchpad,
|
||||
as if moving the cursor normally around the screen. Avoid any accidental
|
||||
palm touches or any excessive or light pressure.
|
||||
The example output shows five completed touch sequences and one ongoing one.
|
||||
For each, the respective minimum and maximum pressure values are printed as
|
||||
well as some statistics. The ```tags``` show that sequence was considered
|
||||
logically down at some point. This is an interactive tool and its output may
|
||||
change frequently. Refer to the <i>libinput-measure-touchpad-pressure(1)</i> man
|
||||
page for more details.
|
||||
|
||||
The event recording is then filtered for pressure information, which is
|
||||
sorted and exported to a new file:
|
||||
By default, this tool uses the udev hwdb entries for the pressure range. To
|
||||
narrow down on the best values for your device, specify the 'logically down'
|
||||
and 'logically up' pressure thresholds with the ```--touch-thresholds``
|
||||
argument:
|
||||
<pre>
|
||||
$ grep --only-matching "ABS_MT_PRESSURE[ ]*[0-9]*" touchpad-pressure.txt | \
|
||||
sed -e "s/ABS_MT_PRESSURE[ ]*//" | \
|
||||
sort -n | uniq -c > touchpad-pressure-statistics.txt
|
||||
$ sudo libinput measure touchpad-pressure --touch-thresholds=10:8
|
||||
</pre>
|
||||
|
||||
The file contains a list of (count, pressure-value) tuples which can be
|
||||
visualized with gnuplot. Copy the following into a file named
|
||||
```touchpad-pressure-statistics.gnuplot```:
|
||||
|
||||
<pre>
|
||||
set style data lines
|
||||
plot 'touchpad-pressure-statistics.txt' using 2:1
|
||||
pause -1
|
||||
</pre>
|
||||
|
||||
Now, you can visualize the touchpad pressure curve with the following
|
||||
command:
|
||||
<pre>
|
||||
$ gnuplot touchpad-pressure-statistics.gnuplot
|
||||
</pre>
|
||||
|
||||
The visualization will show a curve with the various pressure ranges, see
|
||||
[this bugzilla attachment](https://bugs.freedesktop.org/attachment.cgi?id=130659).
|
||||
In most cases, the thresholds can be guessed based on this curve. libinput
|
||||
employes a [Schmitt trigger](https://en.wikipedia.org/wiki/Schmitt_trigger)
|
||||
with an upper threshold and a lower threshold. A touch is detected when the
|
||||
pressure goes above the high threshold, a release is detected when the
|
||||
pressure fallse below the low threshold. Thus, an ideal threshold
|
||||
combination is with a high threshold slightly above the minimum threshold, a
|
||||
low threshold on the minimum threshold.
|
||||
Interact with the touchpad and check if the output of this tool matches your
|
||||
expectations.
|
||||
|
||||
Once the thresholds are decided on (e.g. 10 and 8), they can be enabled with
|
||||
the following hwdb file:
|
||||
|
|
|
|||
|
|
@ -421,6 +421,15 @@ configure_file(input : 'tools/libinput-measure-touchpad-tap.man',
|
|||
install_dir : join_paths(get_option('mandir'), 'man1')
|
||||
)
|
||||
|
||||
install_data('tools/libinput-measure-touchpad-pressure',
|
||||
install_dir : libinput_tool_path)
|
||||
configure_file(input : 'tools/libinput-measure-touchpad-pressure.man',
|
||||
output : 'libinput-measure-touchpad-pressure.1',
|
||||
configuration : man_config,
|
||||
install : true,
|
||||
install_dir : join_paths(get_option('mandir'), 'man1')
|
||||
)
|
||||
|
||||
if get_option('debug-gui')
|
||||
dep_gtk = dependency('gtk+-3.0')
|
||||
dep_cairo = dependency('cairo')
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -2278,16 +2312,52 @@ tp_init_palmdetect_edge(struct tp_dispatch *tp,
|
|||
if (width < 70.0)
|
||||
return;
|
||||
|
||||
/* palm edges are 5% of the width on each side */
|
||||
mm.x = width * 0.05;
|
||||
/* palm edges are 8% of the width on each side */
|
||||
mm.x = width * 0.08;
|
||||
edges = evdev_device_mm_to_units(device, &mm);
|
||||
tp->palm.left_edge = edges.x;
|
||||
|
||||
mm.x = width * 0.95;
|
||||
mm.x = width * 0.92;
|
||||
edges = evdev_device_mm_to_units(device, &mm);
|
||||
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);
|
||||
|
|
|
|||
259
tools/libinput-measure-touchpad-pressure
Executable file
259
tools/libinput-measure-touchpad-pressure
Executable file
|
|
@ -0,0 +1,259 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright © 2017 Red Hat, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the "Software"),
|
||||
# to deal in the Software without restriction, including without limitation
|
||||
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
# and/or sell copies of the Software, and to permit persons to whom the
|
||||
# Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice (including the next
|
||||
# paragraph) shall be included in all copies or substantial portions of the
|
||||
# Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import evdev
|
||||
import evdev.ecodes
|
||||
import pyudev
|
||||
|
||||
class Range(object):
|
||||
"""Class to keep a min/max of a value around"""
|
||||
def __init__(self):
|
||||
self.min = float('inf')
|
||||
self.max = float('-inf')
|
||||
|
||||
def update(self, value):
|
||||
self.min = min(self.min, value)
|
||||
self.max = max(self.max, value)
|
||||
|
||||
class Touch(object):
|
||||
"""A single data point of a sequence (i.e. one event frame)"""
|
||||
|
||||
def __init__(self, pressure=None):
|
||||
self.pressure = pressure
|
||||
|
||||
class TouchSequence(object):
|
||||
"""A touch sequence from beginning to end"""
|
||||
|
||||
def __init__(self, device, tracking_id):
|
||||
self.device = device
|
||||
self.tracking_id = tracking_id
|
||||
self.points = []
|
||||
|
||||
self.is_active = True
|
||||
|
||||
self.is_down = False
|
||||
self.was_down = False
|
||||
self.is_palm = False
|
||||
self.was_palm = False
|
||||
|
||||
self.prange = Range()
|
||||
|
||||
def append(self, touch):
|
||||
"""Add a Touch to the sequence"""
|
||||
self.points.append(touch)
|
||||
self.prange.update(touch.pressure)
|
||||
|
||||
if touch.pressure < self.device.up:
|
||||
self.is_down = False
|
||||
elif touch.pressure > self.device.down:
|
||||
self.is_down = True
|
||||
self.was_down = True
|
||||
|
||||
self.is_palm = touch.pressure > self.device.palm
|
||||
if self.is_palm:
|
||||
self.was_palm = True
|
||||
|
||||
def finalize(self):
|
||||
"""Mark the TouchSequence as complete (finger is up)"""
|
||||
self.is_active = False
|
||||
|
||||
def avg(self):
|
||||
"""Average pressure value of this sequence"""
|
||||
return int(sum([p.pressure for p in self.points])/len(self.points))
|
||||
|
||||
def median(self):
|
||||
"""Median pressure value of this sequence"""
|
||||
ps = sorted([p.pressure for p in self.points])
|
||||
idx = int(len(self.points)/2)
|
||||
return ps[idx]
|
||||
|
||||
def __str__(self):
|
||||
return self._str_state() if self.is_active else self._str_summary()
|
||||
|
||||
def _str_summary(self):
|
||||
s = "Sequence {} pressure: min: {:3d} max: {:3d} avg: {:3d} median: {:3d} tags:".format(
|
||||
self.tracking_id,
|
||||
self.prange.min,
|
||||
self.prange.max,
|
||||
self.avg(),
|
||||
self.median())
|
||||
if self.was_down:
|
||||
s += " down"
|
||||
if self.was_palm:
|
||||
s += " palm"
|
||||
|
||||
return s
|
||||
|
||||
def _str_state(self):
|
||||
s = "Touchpad pressure: {:3d} min: {:3d} max: {:3d} tags: {} {}".format(
|
||||
self.points[-1].pressure,
|
||||
self.prange.min,
|
||||
self.prange.max,
|
||||
"down" if self.is_down else " ",
|
||||
"palm" if self.is_palm else " "
|
||||
)
|
||||
return s
|
||||
|
||||
class InvalidDeviceError(Exception):
|
||||
pass
|
||||
|
||||
class Device(object):
|
||||
def __init__(self, path):
|
||||
if path is None:
|
||||
self.path = self.find_touchpad_device()
|
||||
else:
|
||||
self.path = path
|
||||
|
||||
self.device = evdev.InputDevice(self.path)
|
||||
# capabilities rturns a dict with the EV_* codes as key,
|
||||
# each of which is a list of tuples of (code, AbsInfo)
|
||||
#
|
||||
# Get the abs list first (or empty list if missing),
|
||||
# then extract the pressure absinfo from that
|
||||
caps = self.device.capabilities(absinfo=True).get(evdev.ecodes.EV_ABS, [])
|
||||
p = [cap[1] for cap in caps if cap[0] == evdev.ecodes.ABS_MT_PRESSURE]
|
||||
if not p:
|
||||
raise InvalidDeviceError("device does not have ABS_MT_PRESSURE")
|
||||
|
||||
p = p[0]
|
||||
prange = p.max - p.min
|
||||
|
||||
# libinput defaults
|
||||
self.up = int(p.min + 0.12 * prange)
|
||||
self.down = int(p.min + 0.10 * prange)
|
||||
self.palm = 130 # the libinput default
|
||||
|
||||
self._init_thresholds_from_udev()
|
||||
self.sequences = []
|
||||
|
||||
def find_touchpad_device(self):
|
||||
context = pyudev.Context()
|
||||
for device in context.list_devices(subsystem='input'):
|
||||
if not device.get('ID_INPUT_TOUCHPAD', 0):
|
||||
continue
|
||||
|
||||
if not device.device_node or not device.device_node.startswith('/dev/input/event'):
|
||||
continue
|
||||
|
||||
return device.device_node
|
||||
print("Unable to find a touchpad device.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def _init_thresholds_from_udev(self):
|
||||
context = pyudev.Context()
|
||||
ud = pyudev.Devices.from_device_file(context, self.path)
|
||||
v = ud.get('LIBINPUT_ATTR_PRESSURE_RANGE')
|
||||
if v:
|
||||
self.up, self.down = colon_tuple(v)
|
||||
|
||||
v = ud.get('LIBINPUT_ATTR_PALM_PRESSURE_THRESHOLD')
|
||||
if v:
|
||||
self.palm = int(v)
|
||||
|
||||
def start_new_sequence(self, tracking_id):
|
||||
self.sequences.append(TouchSequence(self, tracking_id))
|
||||
|
||||
def current_sequence(self):
|
||||
return self.sequences[-1]
|
||||
|
||||
def handle_key(device, event):
|
||||
tapcodes = [evdev.ecodes.BTN_TOOL_DOUBLETAP,
|
||||
evdev.ecodes.BTN_TOOL_TRIPLETAP,
|
||||
evdev.ecodes.BTN_TOOL_QUADTAP,
|
||||
evdev.ecodes.BTN_TOOL_QUINTTAP]
|
||||
if event.code in tapcodes and event.value > 0:
|
||||
print("\rThis tool cannot handle multiple fingers, output will be invalid", file=sys.stderr)
|
||||
|
||||
def handle_abs(device, event):
|
||||
if event.code == evdev.ecodes.ABS_MT_TRACKING_ID:
|
||||
if event.value > -1:
|
||||
device.start_new_sequence(event.value)
|
||||
else:
|
||||
s = device.current_sequence()
|
||||
s.finalize()
|
||||
print("\r{}".format(s))
|
||||
elif event.code == evdev.ecodes.ABS_MT_PRESSURE:
|
||||
s = device.current_sequence()
|
||||
s.append(Touch(pressure=event.value))
|
||||
print("\r{}".format(s), end="")
|
||||
|
||||
def handle_event(device, event):
|
||||
if event.type == evdev.ecodes.EV_ABS:
|
||||
handle_abs(device, event)
|
||||
elif event.type == evdev.ecodes.EV_KEY:
|
||||
handle_key(device, event)
|
||||
|
||||
def loop(device):
|
||||
print("Ready for recording data.")
|
||||
print("Pressure range used: {}:{}".format(device.down, device.up))
|
||||
print("Palm pressure range used: {}".format(device.palm))
|
||||
print("Place a single finger on the touchpad to measure pressure values.\n"
|
||||
"Ctrl+C to exit\n")
|
||||
|
||||
for event in device.device.read_loop():
|
||||
handle_event(device, event)
|
||||
|
||||
def colon_tuple(string):
|
||||
try:
|
||||
ts = string.split(':')
|
||||
t = tuple([int(x) for x in ts])
|
||||
if len(t) == 2 and t[0] >= t[1]:
|
||||
return t
|
||||
except:
|
||||
pass
|
||||
|
||||
msg = "{} is not in format N:M (N >= M)".format(string)
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
|
||||
def main(args):
|
||||
parser = argparse.ArgumentParser(description="Measure touchpad pressure values")
|
||||
parser.add_argument('path', metavar='/dev/input/event0',
|
||||
nargs='?', type=str, help='Path to device (optional)' )
|
||||
parser.add_argument('--touch-thresholds', metavar='down:up',
|
||||
type=colon_tuple, help='Thresholds when a touch is logically down or up')
|
||||
parser.add_argument('--palm-threshold', metavar='t',
|
||||
type=int, help='Threshold when a touch is a palm')
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
device = Device(args.path)
|
||||
|
||||
if args.touch_thresholds is not None:
|
||||
device.down, device.up = args.touch_thresholds
|
||||
|
||||
if args.palm_threshold is not None:
|
||||
device.palm = args.palm_threshold
|
||||
|
||||
loop(device)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except (PermissionError, OSError):
|
||||
print("Error: failed to open device")
|
||||
except InvalidDeviceError as e:
|
||||
print("Error: {}".format(e))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
63
tools/libinput-measure-touchpad-pressure.man
Normal file
63
tools/libinput-measure-touchpad-pressure.man
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
.TH libinput-measure-touchpad-pressure "1"
|
||||
.SH NAME
|
||||
libinput\-measure\-touchpad\-pressure \- measure pressure properties of devices
|
||||
.SH SYNOPSIS
|
||||
.B libinput measure touchpad\-pressure [\-\-help] [options]
|
||||
[\fI/dev/input/event0\fI]
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The
|
||||
.B "libinput measure touchpad\-pressure"
|
||||
tool measures the pressure of touches on a touchpad. This is
|
||||
an interactive tool. When executed, the tool will prompt the user to
|
||||
interact with the touchpad. On termination, the tool prints a summary of the
|
||||
pressure values seen. This data should be attached to any
|
||||
pressure\-related bug report.
|
||||
.PP
|
||||
For a full description on how libinput's pressure-to-click behavior works, see
|
||||
the online documentation here:
|
||||
.I https://wayland.freedesktop.org/libinput/doc/latest/touchpad_pressure.html
|
||||
and
|
||||
.I https://wayland.freedesktop.org/libinput/doc/latest/palm_detection.html
|
||||
.PP
|
||||
This is a debugging tool only, its output may change at any time. Do not
|
||||
rely on the output.
|
||||
.PP
|
||||
This tool usually needs to be run as root to have access to the
|
||||
/dev/input/eventX nodes.
|
||||
.SH OPTIONS
|
||||
If a device node is given, this tool opens that device node. Otherwise, this
|
||||
tool searches for the first node that looks like a touchpad and uses that
|
||||
node.
|
||||
.TP 8
|
||||
.B \-\-help
|
||||
Print help
|
||||
.TP 8
|
||||
.B \-\-touch\-thresholds=\fI"down:up"\fR
|
||||
Set the logical touch pressure thresholds to
|
||||
.I down
|
||||
and
|
||||
.I up,
|
||||
respectively. When a touch exceeds the pressure in
|
||||
.I down
|
||||
it is considered logically down. If a touch is logically down and goes below
|
||||
the pressure in
|
||||
.I up,
|
||||
it is considered logically up. The thresholds have to be in
|
||||
device-specific pressure values and it is required that
|
||||
.I down
|
||||
>=
|
||||
.I up.
|
||||
.TP 8
|
||||
.B \-\-palm\-threshold=\fIN\fR
|
||||
Assume a palm threshold of
|
||||
.I N.
|
||||
The threshold has to be in device-specific pressure values.
|
||||
.PP
|
||||
If the touch-thresholds or the palm-threshold are not provided,
|
||||
this tool uses the thresholds provided by the udev hwdb (if any) or the
|
||||
built-in defaults.
|
||||
.SH LIBINPUT
|
||||
Part of the
|
||||
.B libinput(1)
|
||||
suite
|
||||
|
|
@ -24,6 +24,9 @@ Features that can be measured include
|
|||
.TP 8
|
||||
.B libinput\-measure\-touchpad\-tap\-time(1)
|
||||
Measure tap-to-click time
|
||||
.TP 8
|
||||
.B libinput\-measure\-touchpad\-pressure(1)
|
||||
Measure touch pressure
|
||||
.SH LIBINPUT
|
||||
Part of the
|
||||
.B libinput(1)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ Measure various properties of devices
|
|||
.TP 8
|
||||
.B libinput\-measure\-touchpad\-tap(1)
|
||||
Measure tap-to-click time
|
||||
.TP 8
|
||||
.B libinput\-measure\-touchpad\-pressure(1)
|
||||
Measure touch pressure.
|
||||
.SH LIBINPUT
|
||||
Part of the
|
||||
.B libinput(1)
|
||||
|
|
|
|||
|
|
@ -186,6 +186,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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue