From 8d5f4decb4086e2b7982c3cd1e24afd9c11f551f Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 19 Apr 2017 13:45:34 +1000 Subject: [PATCH 1/3] touchpad: move the pressure range to a hwdb entry Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 44 ++++++++++++++++++++++-------- src/libinput-util.c | 36 ++++++++++++++++++++++++ src/libinput-util.h | 1 + test/test-misc.c | 43 +++++++++++++++++++++++++++++ udev/90-libinput-model-quirks.hwdb | 2 ++ udev/parse_hwdb.py | 8 +++++- 6 files changed, 121 insertions(+), 13 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index eb950a2c..99963e60 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -2372,8 +2372,9 @@ tp_init_pressure(struct tp_dispatch *tp, struct evdev_device *device) { const struct input_absinfo *abs; - unsigned int range; unsigned int code = ABS_PRESSURE; + const char *prop; + int hi, lo; if (tp->has_mt) code = ABS_MT_PRESSURE; @@ -2383,25 +2384,44 @@ tp_init_pressure(struct tp_dispatch *tp, return; } - tp->pressure.use_pressure = true; - abs = libevdev_get_abs_info(device->evdev, code); assert(abs); - range = abs->maximum - abs->minimum; + prop = udev_device_get_property_value(device->udev_device, + "LIBINPUT_ATTR_PRESSURE_RANGE"); + if (prop) { + if (!parse_pressure_range_property(prop, &hi, &lo)) { + evdev_log_bug_client(device, + "discarding invalid pressure range '%s'\n", + prop); + return; + } - if (device->model_flags & EVDEV_MODEL_ELANTECH_TOUCHPAD) { - tp->pressure.high = 24; - tp->pressure.low = 10; - } else if (device->model_flags & EVDEV_MODEL_CYAPA) { - tp->pressure.high = 10; - tp->pressure.low = 8; + if (hi == 0 && lo == 0) { + evdev_log_info(device, + "pressure-based touch detection disabled\n"); + return; + } } else { + unsigned int range = abs->maximum - abs->minimum; + /* Approximately the synaptics defaults */ - tp->pressure.high = abs->minimum + 0.12 * range; - tp->pressure.low = abs->minimum + 0.10 * range; + hi = abs->minimum + 0.12 * range; + lo = abs->minimum + 0.10 * range; } + if (hi > abs->maximum || hi < abs->minimum || + lo > abs->maximum || lo < abs->minimum) { + evdev_log_bug_libinput(device, + "discarding out-of-bounds pressure range %d:%d\n", + hi, lo); + return; + } + + tp->pressure.use_pressure = true; + tp->pressure.high = hi; + tp->pressure.low = lo; + evdev_log_debug(device, "using pressure-based touch detection\n"); } diff --git a/src/libinput-util.c b/src/libinput-util.c index 351bbe4a..38594fa4 100644 --- a/src/libinput-util.c +++ b/src/libinput-util.c @@ -359,6 +359,42 @@ parse_tpkbcombo_layout_poperty(const char *prop, return false; } +/** + * Parses a string of the format "a:b" where both a and b must be integer + * numbers and a > b. Also allowed is the special string vaule "none" which + * amounts to unsetting the property. + * + * @param prop The value of the property + * @param hi Set to the first digit or 0 in case of 'none' + * @param lo Set to the second digit or 0 in case of 'none' + * @return true on success, false otherwise + */ +bool +parse_pressure_range_property(const char *prop, int *hi, int *lo) +{ + int first, second; + + if (!prop) + return false; + + if (streq(prop, "none")) { + *hi = 0; + *lo = 0; + return true; + } + + if (sscanf(prop, "%d:%d", &first, &second) != 2) + return false; + + if (second >= first) + return false; + + *hi = first; + *lo = second; + + return true; +} + /** * 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 3fe0a029..4e97e011 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -393,6 +393,7 @@ int parse_mouse_wheel_click_count_property(const char *prop); 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); enum tpkbcombo_layout { TPKBCOMBO_LAYOUT_UNKNOWN, diff --git a/test/test-misc.c b/test/test-misc.c index 3f4b2290..5101dcff 100644 --- a/test/test-misc.c +++ b/test/test-misc.c @@ -1001,6 +1001,48 @@ START_TEST(calibration_prop_parser) } END_TEST +struct parser_test_pressure_range { + char *tag; + bool success; + int hi, lo; +}; + +START_TEST(pressure_range_prop_parser) +{ + struct parser_test_pressure_range tests[] = { + { "10:8", true, 10, 8 }, + { "100:-1", true, 100, -1 }, + { "-203813:-502023", true, -203813, -502023 }, + { "238492:28210", true, 238492, 28210 }, + { "none", true, 0, 0 }, + { "0:0", false, 0, 0 }, + { "", false, 0, 0 }, + { "abcd", false, 0, 0 }, + { "10:30:10", false, 0, 0 }, + { NULL, false, 0, 0 } + }; + int i; + int hi, lo; + bool success; + + for (i = 0; tests[i].tag != NULL; i++) { + hi = lo = 0xad; + success = parse_pressure_range_property(tests[i].tag, &hi, &lo); + ck_assert(success == tests[i].success); + if (success) { + ck_assert_int_eq(hi, tests[i].hi); + ck_assert_int_eq(lo, tests[i].lo); + } else { + ck_assert_int_eq(hi, 0xad); + ck_assert_int_eq(lo, 0xad); + } + } + + success = parse_pressure_range_property(NULL, NULL, NULL); + ck_assert(success == false); +} +END_TEST + START_TEST(time_conversion) { ck_assert_int_eq(us(10), 10); @@ -1275,6 +1317,7 @@ litest_setup_tests_misc(void) litest_add_no_device("misc:parser", dimension_prop_parser); 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", safe_atoi_test); litest_add_no_device("misc:parser", safe_atod_test); litest_add_no_device("misc:parser", strsplit_test); diff --git a/udev/90-libinput-model-quirks.hwdb b/udev/90-libinput-model-quirks.hwdb index 5370e36f..3ae7a497 100644 --- a/udev/90-libinput-model-quirks.hwdb +++ b/udev/90-libinput-model-quirks.hwdb @@ -88,6 +88,7 @@ libinput:name:* Touchpad:dmi:*svnDellInc.:* ########################################## libinput:name:*ETPS/2 Elantech Touchpad*:dmi:* LIBINPUT_ATTR_RESOLUTION_HINT=31x31 + LIBINPUT_ATTR_PRESSURE_RANGE=24:10 LIBINPUT_MODEL_ELANTECH_TOUCHPAD=1 ########################################## @@ -114,6 +115,7 @@ libinput:name:Atmel maXTouch Touchpad:dmi:*svn*GOOGLE*:pn*Samus* libinput:name:Cypress APA Trackpad ?cyapa?:dmi:* LIBINPUT_MODEL_CYAPA=1 + LIBINPUT_ATTR_PRESSURE_RANGE=10:8 ########################################## # HP diff --git a/udev/parse_hwdb.py b/udev/parse_hwdb.py index 2a342bf1..97f04f4f 100755 --- a/udev/parse_hwdb.py +++ b/udev/parse_hwdb.py @@ -112,7 +112,13 @@ def property_grammar(): Suppress('=') - tpkbcombo_tags('VALUE')] - grammar = Or(model_props + size_props + reliability + tpkbcombo) + pressure_range = INTEGER('X') + Suppress(':') + INTEGER('Y') + pressure_prop = [ Literal('LIBINPUT_ATTR_PRESSURE_RANGE')('NAME') - + Suppress('=') - + Group(pressure_range('SETTINGS*')) ] + + grammar = Or(model_props + size_props + reliability + tpkbcombo + + pressure_prop) return grammar From bed7e62a0d92ccd3e985e73736b0990ac7219483 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 19 Apr 2017 14:11:45 +1000 Subject: [PATCH 2/3] doc: add documentation for touchpad pressure detection Signed-off-by: Peter Hutterer --- doc/Makefile.am | 1 + doc/faqs.dox | 5 ++ doc/page-hierarchy.dox | 1 + doc/touchpad-pressure.dox | 138 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 doc/touchpad-pressure.dox diff --git a/doc/Makefile.am b/doc/Makefile.am index 8af0f336..5391f225 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -29,6 +29,7 @@ header_files = \ $(srcdir)/test-suite.dox \ $(srcdir)/tools.dox \ $(srcdir)/touchpad-jumping-cursors.dox \ + $(srcdir)/touchpad-pressure.dox \ $(srcdir)/touchpads.dox \ $(srcdir)/what-is-libinput.dox diff --git a/doc/faqs.dox b/doc/faqs.dox index 0b8db833..7820ad4e 100644 --- a/doc/faqs.dox +++ b/doc/faqs.dox @@ -15,6 +15,11 @@ motion_normalization for a detailed explanation. See @ref tapping_default +@section faq_touchpad_pressure Why does my touchpad lose track of touches + +The most common cause for this is an incorrect pressure threshold range. +See @ref touchpad_pressure for more info. + @section faq_kinetic_scrolling Kinetic scrolling does not work The X.Org synaptics driver implemented kinetic scrolling in the driver. It diff --git a/doc/page-hierarchy.dox b/doc/page-hierarchy.dox index ebfb65cb..65749feb 100644 --- a/doc/page-hierarchy.dox +++ b/doc/page-hierarchy.dox @@ -5,6 +5,7 @@ - @subpage clickpad_softbuttons - @subpage tapping - @subpage gestures +- @subpage touchpad_pressure - @subpage palm_detection - @subpage t440_support - @subpage touchpad_jumping_cursor diff --git a/doc/touchpad-pressure.dox b/doc/touchpad-pressure.dox new file mode 100644 index 00000000..7c55c9f5 --- /dev/null +++ b/doc/touchpad-pressure.dox @@ -0,0 +1,138 @@ +/** +@page touchpad_pressure Touchpad pressure-based touch detection + +libinput uses the touchpad pressure values to detect wether a finger has +been placed on the touchpad. This is @ref kernel_pressure_information and +combines with a libinput-specific hardware database to adjust the thresholds +on a per-device basis. libinput uses pressure thresholds primarily to filter +out accidental light touches but pressure information is also used for +some @ref palm_detection. + +Pressure thresholds are **not** directly configurable by the user, rather it +is planned that libinput provides custom pressure thresholds for each device +where necessary. See @ref touchpad_pressure_hwdb for instructions for your +local device. + +@section kernel_pressure_information Information provided by the kernel + +The kernel sends multiple values to inform userspace about a finger touching +the touchpad. The most basic is the ```EV_KEY/BTN_TOUCH``` boolean event +that simply announces physical contact with the touchpad. The decision when +this event is sent is usually made by the kernel driver and may depend on +device-specific thresholds. These thresholds are transparent to userspace +and cannot be modified. + +Many contemporary touchpad devices provide an absolute pressure axis in +addition to ```BTN_TOUCH```. This pressure generally increases as the pressure +increases, however few touchpads are capable of detection pressure. The +pressure value is usually related to the covered area - as the pressure +increases a finger flattens and thus covers a larger area. The range +provided by the kernel is not mapped to a specific physical range and +often requires adjustment. Pressure is sent by the ```ABS_PRESSURE``` axis +for single-touch touchpads or ```ABS_MT_PRESSURE``` on multi-touch capable +touchpads. + +Some devices provide additional touch size information through +the ```ABS_MT_TOUCH_MAJOR/ABS_MT_TOUCH_MINOR``` axes and/or +the ```ABS_MT_WIDTH_MAJOR/ABS_MT_WIDTH_MINOR``` axes. While the kernel +documentation specifies how these axes are supposed to be mapped, few +devices forward +reliable information. + +@section touchpad_pressure_hwdb Debugging touchpad pressure ranges + +This section describes how to determine the touchpad pressure ranges +required for a touchpad device and how to add the required hwdb entry +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: + +
+$ 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
+
+ +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 event recording is then filtered for pressure information, which is +sorted and exported to a new file: +
+$ 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
+
+ +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```: + +
+set style data lines
+plot 'touchpad-pressure-statistics.txt' using 2:1
+pause -1
+
+ +Now, you can visualize the touchpad pressure curve with the following +command: +
+$ gnuplot  touchpad-pressure-statistics.gnuplot
+
+ +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. + +Once the thresholds are decided on (e.g. 10 and 8), they can be enabled with +the following hwdb file: + +
+$> cat /etc/udev/hwdb.d/99-touchpad-pressure.hwdb
+libinput:name:*SynPS/2 Synaptics TouchPad:dmi:*svnHewlett-Packard:*pnHPCompaq6910p*
+ LIBINPUT_ATTR_PRESSURE_RANGE=10:8
+
+ +The first line is the match line and should be adjusted for the device name +(see evemu-record's output) and for the local system, based on the +information in ```/sys/class/dmi/id/modalias```. The modalias should be +shortened to the specific system's information, usually system vendor (svn) +and product name (pn). + +Once in place, you need to run the following to commands, adjusted for your +device's event node (see @ref faq_hwdb_changes): +
+    sudo udevadm hwdb --update
+    sudo udevadm test /sys/class/input/eventX
+
+ +If the pressure range property shows up correctly, restart X or the +Wayland compositor and libinput should now use the correct pressure +thresholds. The @ref developer_tools can be used to verify the correct +functionality first without the need for a restart. + +Once the pressure ranges are deemed correct, +@ref reporting_bugs "report a bug" to get the pressure ranges into the +repository. + +*/ From fe4e2aef0459241234788b7fe4251b17b9a3f4ef Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 24 Apr 2017 15:58:22 +1000 Subject: [PATCH 3/3] touchpad: drop the unused touchpad_model enum A leftover from synaptics where we do this detection in the driver. libinput pushes this to the hwdb and sets the model flags accordingly. Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 2d531b5e..9dc88795 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -44,15 +44,6 @@ enum touchpad_event { TOUCHPAD_EVENT_OTHERAXIS = (1 << 3), }; -enum touchpad_model { - MODEL_UNKNOWN = 0, - MODEL_SYNAPTICS, - MODEL_ALPS, - MODEL_APPLETOUCH, - MODEL_ELANTECH, - MODEL_UNIBODY_MACBOOK -}; - enum touch_state { TOUCH_NONE = 0, TOUCH_HOVERING,