Merge branch 'wip/touchpad-palm-pressure'

This commit is contained in:
Peter Hutterer 2017-07-04 13:57:32 +10:00
commit ca9d6a88d7
15 changed files with 700 additions and 73 deletions

View file

@ -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

View file

@ -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
&nbsp;
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:

View 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')

View file

@ -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

View file

@ -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 {

View file

@ -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.

View file

@ -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,

View file

@ -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);

View file

@ -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);

View 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)

View 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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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