tools: add a measure touchpad-size tool
authorPeter Hutterer <peter.hutterer@who-t.net>
Sat, 28 Mar 2020 03:26:17 +0000 (13:26 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Mon, 30 Mar 2020 05:14:45 +0000 (15:14 +1000)
Replacement for the touchpad-edge-detector tool with a slightly more
expressive design, hopefully cutting down on some of the bug reports.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
doc/user/absolute-coordinate-ranges.rst
meson.build
tools/libinput-measure-touchpad-size.man [new file with mode: 0644]
tools/libinput-measure-touchpad-size.py [new file with mode: 0755]
tools/libinput-measure.man

index db61376f4b0cacb1db87443ea97e17bbf366f78b..f587083e191784b33da46a663470af8f91f8d593 100644 (file)
@@ -33,59 +33,83 @@ Measuring and fixing touchpad ranges
 To fix the touchpad you need to:
 
 #. measure the physical size of your touchpad in mm
-#. run touchpad-edge-detector
-#. trim the udev match rule to something sensible
-#. replace the resolution with the calculated resolution based on physical settings
+#. run the ``libinput measure touchpad-size`` tool
+#. verify the hwdb entry provided by this tool
 #. test locally
-#. send a patch to the systemd project
+#. send a patch to the `systemd project <https://github.com/systemd/systemd>`_.
 
 Detailed explanations are below.
 
-`libevdev <http://freedesktop.org/wiki/Software/libevdev/>`_ provides a tool
-called **touchpad-edge-detector** that allows measuring the touchpad's input
-ranges. Run the tool as root against the device node of your touchpad device
-and repeatedly move a finger around the whole outside area of the
-touchpad. Then control+c the process and note the output.
-An example output is below:
+.. note:: ``libinput measure touchpad-size`` was introduced in libinput
+         1.16. For earlier versions, use `libevdev <http://freedesktop.org/wiki/Software/libevdev/>`_'s
+         ``touchpad-edge-detector`` tool.
 
 
+The ``libinput measure touchpad-size`` tool is an interactive tool. It must
+be called with the physical dimensions of the touchpad in mm. In the example
+below, we use 100mm wide and 55mm high. The tool will find the touchpad device
+automatically.
+
 ::
 
-     $> sudo touchpad-edge-detector /dev/input/event4
-     Touchpad SynPS/2 Synaptics TouchPad on /dev/input/event4
-     Move one finger around the touchpad to detect the actual edges
-     Kernel says:      x [1024..3112], y [2024..4832]
-     Touchpad sends:   x [2445..4252], y [3464..4071]
+     $> sudo libinput measure touchpad-size 100x55
+     Using "Touchpad SynPS/2 Synaptics TouchPad": /dev/input/event4
+
+     Kernel specified touchpad size: 99.7x75.9mm
+     User specified touchpad size:   100.0x55.0mm
+
+     Kernel axis range:   x [1024..5112], y [2024..4832]
+     Detected axis range: x [   0..   0], y [   0..   0]
+
+     Move one finger along all edges of the touchpad
+     until the detected axis range stops changing.
 
-     Touchpad size as listed by the kernel: 49x66mm
-     Calculate resolution as:
-       x axis: 2088/<width in mm>
-       y axis: 2808/<height in mm>
+     ...
 
-     Suggested udev rule:
-     # <Laptop model description goes here>
-     evdev:name:SynPS/2 Synaptics TouchPad:dmi:bvnLENOVO:bvrGJET72WW(2.22):bd02/21/2014:svnLENOVO:pn20ARS25701:pvrThinkPadT440s:rvnLENOVO:rn20ARS25701:rvrSDK0E50512STD:cvnLENOVO:ct10:cvrNotAvailable:*
-      EVDEV_ABS_00=2445:4252:<x resolution>
-      EVDEV_ABS_01=3464:4071:<y resolution>
-      EVDEV_ABS_35=2445:4252:<x resolution>
-      EVDEV_ABS_36=3464:4071:<y resolution>
+Move the finger around until the detected axis range matches the data sent
+by the device. ``Ctrl+C`` terminates the tool and prints a
+suggested hwdb entry. ::
 
+    ...
+    Kernel axis range:   x [1024..5112], y [2024..4832]
+    ^C
+    Detected axis range: x [2072..4880], y [2159..4832]
+    Resolutions calculated based on user-specified size: x 28, y 49 units/mm
 
+    Suggested hwdb entry:
+    Note: the dmi modalias match is a guess based on your machine's modalias:
+      dmi:bvnLENOVO:bvrGJET72WW(2.22):bd02/21/2014:svnLENOVO:pn20ARS25701:pvrThinkPadT440s:rvnLENOVO:rn20ARS25701:rvrSDK0E50512STD:cvnLENOVO:ct10:cvrNotAvailable:
+    Please verify that this is the most sensible match and adjust if necessary.
+    -8<--------------------------
+    # Laptop model description (e.g. Lenovo X1 Carbon 5th)
+    evdev:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*pvrThinkPadT440s*
+     EVDEV_ABS_00=2072:4880:28
+     EVDEV_ABS_01=2159:4832:49
+     EVDEV_ABS_35=2072:4880:28
+     EVDEV_ABS_36=2159:4832:49
+    -8<--------------------------
+    Instructions on what to do with this snippet are in /usr/lib/udev/hwdb.d/60-evdev.hwdb
 
-Note the discrepancy between the coordinate range the kernels advertises vs.
-what the touchpad sends.
-To fix the advertised ranges, the udev rule should be taken and trimmed
-before being sent to the `systemd project <https://github.com/systemd/systemd>`_.
+
+If there are discrepancies between the coordinate range the kernels
+advertises and what what the touchpad sends, the hwdb entry should be added to the
+``60-evdev.hwdb`` file provided by the `systemd project <https://github.com/systemd/systemd>`_.
 An example commit can be found
 `here <https://github.com/systemd/systemd/commit/26f667eac1c5e89b689aa0a1daef6a80f473e045>`_.
 
-In most cases the match can and should be trimmed to the system vendor (svn)
-and the product version (pvr), with everything else replaced by a wildcard
-(*). In this case, a Lenovo T440s, a suitable match string would be:
+The ``libinput measure touchpad-size`` tool attempts to provide the correct
+dmi match but it does require user verification.
+
+In most cases the dmi match can and should be trimmed to the system vendor (``svn``)
+and the product version (``pvr``) or product name (``pn``), with everything else
+replaced by a wildcard (``*``). In the above case, the match string is:
+
 ::
 
      evdev:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*pvrThinkPadT440s*
 
+As a general rule: for Lenovo devices use ``pvr`` and for all others use
+``pn``.
 
 .. note:: hwdb match strings only allow for alphanumeric ascii characters. Use a
        wildcard (* or ?, whichever appropriate) for special characters.
@@ -95,19 +119,19 @@ The actual axis overrides are in the form:
 ::
 
      # axis number=min:max:resolution
-      EVDEV_ABS_00=2445:4252:42
+      EVDEV_ABS_00=2072:4880:28
 
 or, if the range is correct but the resolution is wrong
 
 ::
 
      # axis number=::resolution
-      EVDEV_ABS_00=::42
+      EVDEV_ABS_00=::28
 
 
 Note the leading single space. The axis numbers are in hex and can be found
-in *linux/input-event-codes.h*. For touchpads ABS_X, ABS_Y,
-ABS_MT_POSITION_X and ABS_MT_POSITION_Y are required.
+in ``linux/input-event-codes.h``. For touchpads ``ABS_X``, ``ABS_Y``,
+``ABS_MT_POSITION_X`` and ``ABS_MT_POSITION_Y`` are required.
 
 .. note:: The touchpad's ranges and/or resolution should only be fixed when
        there is a significant discrepancy. A few units do not make a
index 18fb21bcf9e70d4f291e6846bf50c4212bcb2f9f..c13052d584c22718bf13dfef17a866c5de28c99f 100644 (file)
@@ -595,6 +595,7 @@ configure_file(input : 'tools/libinput-analyze.man',
 src_python_tools = files(
              'tools/libinput-analyze-per-slot-delta.py',
              'tools/libinput-measure-fuzz.py',
+             'tools/libinput-measure-touchpad-size.py',
              'tools/libinput-measure-touchpad-tap.py',
              'tools/libinput-measure-touchpad-pressure.py',
              'tools/libinput-measure-touch-size.py',
@@ -617,6 +618,7 @@ endforeach
 
 src_man = files(
              'tools/libinput-measure-fuzz.man',
+             'tools/libinput-measure-touchpad-size.man',
              'tools/libinput-measure-touchpad-tap.man',
              'tools/libinput-measure-touchpad-pressure.man',
              'tools/libinput-measure-touch-size.man',
diff --git a/tools/libinput-measure-touchpad-size.man b/tools/libinput-measure-touchpad-size.man
new file mode 100644 (file)
index 0000000..fdc60c8
--- /dev/null
@@ -0,0 +1,39 @@
+.TH libinput-measure-touchpad-size "1"
+.SH NAME
+libinput\-measure\-touchpad\-size \- measure the size of a touchpad
+.SH SYNOPSIS
+.B libinput measure touchpad\-size [\-\-help] WxH [\fI/dev/input/event0\fI]
+.SH DESCRIPTION
+.PP
+The
+.B "libinput measure touchpad\-size"
+tool measures the size of a touchpad. This is an interactive tool. When
+executed, the tool will prompt the user to interact with the touchpad. The
+tool records the axis ranges and calculates the size and resolution based on
+those. On termination, the tool prints a hwdb entry that can be added to the 
+.B 60-evdev.hwdb
+file provided by system.
+.PP
+For details see the online documentation here:
+.I https://wayland.freedesktop.org/libinput/doc/latest/absolute-coordinate-ranges.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
+This tool must be provided the physical dimensions of the device in mm.
+For example, if your touchpad is 100mm wide and 55mm heigh, run this tool as
+.B libinput measure touchpad-size 100x55
+.PP
+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
+.SH LIBINPUT
+Part of the
+.B libinput(1)
+suite
diff --git a/tools/libinput-measure-touchpad-size.py b/tools/libinput-measure-touchpad-size.py
new file mode 100755 (executable)
index 0000000..27c618c
--- /dev/null
@@ -0,0 +1,340 @@
+#!/usr/bin/env python3
+# vim: set expandtab shiftwidth=4:
+# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
+#
+# Copyright © 2020 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
+try:
+    import libevdev
+    import pyudev
+except ModuleNotFoundError as e:
+    print('Error: {}'.format(str(e)), file=sys.stderr)
+    print('One or more python modules are missing. Please install those '
+          'modules and re-run this tool.')
+    sys.exit(1)
+
+
+class DeviceError(Exception):
+    pass
+
+
+class Point:
+    def __init__(self, x=None, y=None):
+        self.x = x
+        self.y = y
+
+
+class Touchpad(object):
+    def __init__(self, evdev):
+        x = evdev.absinfo[libevdev.EV_ABS.ABS_X]
+        y = evdev.absinfo[libevdev.EV_ABS.ABS_Y]
+        if not x or not y:
+            raise DeviceError('Device does not have an x or axis')
+
+        if not x.resolution or not y.resolution:
+            print('Device does not have resolutions.', file=sys.stderr)
+            x.resolution = 1
+            y.resolution = 1
+
+        self.xrange = (x.maximum - x.minimum)
+        self.yrange = (y.maximum - y.minimum)
+        self.width = self.xrange / x.resolution
+        self.height = self.yrange / y.resolution
+
+        self._x = x
+        self._y = y
+
+        # We try to make the touchpad at least look proportional. The
+        # terminal character space is (guesswork) ca 2.3 times as high as
+        # wide.
+        self.columns = 30
+        self.rows = int(self.columns * (self.yrange // y.resolution) // (self.xrange // x.resolution) / 2.3)
+        self.pos = Point(0, 0)
+        self.min = Point()
+        self.max = Point()
+
+    @property
+    def x(self):
+        return self._x
+
+    @property
+    def y(self):
+        return self._y
+
+    @x.setter
+    def x(self, x):
+        self._x.minimum = min(self.x.minimum, x)
+        self._x.maximum = max(self.x.maximum, x)
+        self.min.x = min(x, self.min.x or 0xffffffff)
+        self.max.x = max(x, self.max.x or -0xffffffff)
+        # we calculate the position based on the original range.
+        # this means on devices with a narrower range than advertised, not
+        # all corners may be reachable in the touchpad drawing.
+        self.pos.x = min(0.99, (x - self._x.minimum) / self.xrange)
+
+    @y.setter
+    def y(self, y):
+        self._y.minimum = min(self.y.minimum, y)
+        self._y.maximum = max(self.y.maximum, y)
+        self.min.y = min(y, self.min.y or 0xffffffff)
+        self.max.y = max(y, self.max.y or -0xffffffff)
+        # we calculate the position based on the original range.
+        # this means on devices with a narrower range than advertised, not
+        # all corners may be reachable in the touchpad drawing.
+        self.pos.y = min(0.99, (y - self._y.minimum) / self.yrange)
+
+    def update_from_data(self):
+        if None in [self.min.x, self.min.y, self.max.x, self.max.y]:
+            raise DeviceError('Insufficient data to continue')
+        self._x.minimum = self.min.x
+        self._x.maximum = self.max.x
+        self._y.minimum = self.min.y
+        self._y.maximum = self.max.y
+
+    def draw(self):
+        print('Detected axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]'.format(
+              self.min.x if self.min.x is not None else 0,
+              self.max.x if self.max.x is not None else 0,
+              self.min.y if self.min.y is not None else 0,
+              self.max.y if self.max.y is not None else 0))
+
+        print()
+        print('Move one finger along all edges of the touchpad'.center(self.columns))
+        print('until the detected axis range stops changing.'.center(self.columns))
+
+        top = int(self.pos.y * self.rows)
+
+        print('+{}+'.format(''.ljust(self.columns, '-')))
+        for row in range(0, top):
+            print('|{}|'.format(''.ljust(self.columns)))
+
+        left = int(self.pos.x * self.columns)
+        right = max(0, self.columns - 1 - left)
+        print('|{}{}{}|'.format(
+            ''.ljust(left),
+            'O',
+            ''.ljust(right)))
+
+        for row in range(top + 1, self.rows):
+            print('|{}|'.format(''.ljust(self.columns)))
+
+        print('+{}+'.format(''.ljust(self.columns, '-')))
+
+        print('Press Ctrl+C to stop'.center(self.columns))
+
+        print('\033[{}A'.format(self.rows + 8), flush=True)
+
+        self.rows_printed = self.rows + 8
+
+    def erase(self):
+        # Erase all previous lines so we're not left with rubbish
+        for row in range(self.rows_printed):
+            print('\033[K')
+        print('\033[{}A'.format(self.rows_printed))
+
+
+def dimension(string):
+    try:
+        ts = string.split('x')
+        t = tuple([int(x) for x in ts])
+        if len(t) == 2:
+            return t
+    except: # noqa
+        pass
+
+    msg = "{} is not in format WxH".format(string)
+    raise argparse.ArgumentTypeError(msg)
+
+
+def between(v1, v2, deviation):
+    return v1 - deviation < v2 < v1 + deviation
+
+
+def dmi_modalias_match(modalias):
+    modalias = modalias.split(':')
+    dmi = {'svn': None, 'pvr': None, 'pn': None}
+    for m in modalias:
+        for key in dmi:
+            if m.startswith(key):
+                dmi[key] = m[len(key):]
+
+    # Based on the current 60-evdev.hwdb, Lenovo uses pvr and everyone else
+    # uses pn to provide a human-identifiable match
+    if dmi['svn'] == 'LENOVO':
+        return 'dmi:*svn{}:*pvr{}*'.format(dmi['svn'], dmi['pvr'])
+    else:
+        return 'dmi:*svn{}:*pn{}*'.format(dmi['svn'], dmi['pn'])
+
+
+def main(args):
+    parser = argparse.ArgumentParser(
+        description="Measure the touchpad size"
+    )
+    parser.add_argument(
+        'size', metavar='WxH', type=dimension,
+        help='Touchpad size (width by height) in mm',
+    )
+    parser.add_argument(
+        'path', metavar='/dev/input/event0', nargs='?', type=str,
+        help='Path to device (optional)'
+    )
+    context = pyudev.Context()
+
+    args = parser.parse_args()
+    if not args.path:
+        for device in context.list_devices(subsystem='input'):
+            if (device.get('ID_INPUT_TOUCHPAD', 0) and
+                    (device.device_node or '').startswith('/dev/input/event')):
+                args.path = device.device_node
+                name = 'unknown'
+                parent = device
+                while parent is not None:
+                    n = parent.get('NAME', None)
+                    if n:
+                        name = n
+                        break
+                    parent = parent.parent
+
+                print('Using {}: {}'.format(name, device.device_node))
+                break
+        else:
+            print('Unable to find a touchpad device.', file=sys.stderr)
+            return 1
+
+    dev = pyudev.Devices.from_device_file(context, args.path)
+    overrides = [p for p in dev.properties if p.startswith('EVDEV_ABS')]
+    if overrides:
+        print()
+        print('********************************************************************')
+        print('WARNING: axis overrides already in place for this device:')
+        for prop in overrides:
+            print('  {}={}'.format(prop, dev.properties[prop]))
+        print('The systemd hwdb already overrides the axis ranges and/or resolution.')
+        print('This tool is not needed unless you want to verify the axis overrides.')
+        print('********************************************************************')
+        print()
+
+    try:
+        fd = open(args.path, 'rb')
+        evdev = libevdev.Device(fd)
+        touchpad = Touchpad(evdev)
+        print('Kernel specified touchpad size: {:.1f}x{:.1f}mm'.format(touchpad.width, touchpad.height))
+        print('User specified touchpad size:   {:.1f}x{:.1f}mm'.format(*args.size))
+
+        print()
+        print('Kernel axis range:   x [{:4d}..{:4d}], y [{:4d}..{:4d}]'.format(
+              touchpad.x.minimum, touchpad.x.maximum,
+              touchpad.y.minimum, touchpad.y.maximum))
+
+        print('Put your finger on the touchpad to start\033[1A')
+
+        try:
+            touchpad.draw()
+            while True:
+                for event in evdev.events():
+                    if event.matches(libevdev.EV_ABS.ABS_X):
+                        touchpad.x = event.value
+                    elif event.matches(libevdev.EV_ABS.ABS_Y):
+                        touchpad.y = event.value
+                    elif event.matches(libevdev.EV_SYN.SYN_REPORT):
+                        touchpad.draw()
+        except KeyboardInterrupt:
+            touchpad.erase()
+            touchpad.update_from_data()
+
+        print('Detected axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]'.format(
+              touchpad.x.minimum, touchpad.x.maximum,
+              touchpad.y.minimum, touchpad.y.maximum))
+
+        touchpad.x.resolution = round((touchpad.x.maximum - touchpad.x.minimum) / args.size[0])
+        touchpad.y.resolution = round((touchpad.y.maximum - touchpad.y.minimum) / args.size[1])
+
+        print('Resolutions calculated based on user-specified size: x {}, y {} units/mm'.format(
+              touchpad.x.resolution, touchpad.y.resolution))
+
+        # If both x/y are within some acceptable deviation, we skip the axis
+        # overrides and only override the resolution
+        xorig = evdev.absinfo[libevdev.EV_ABS.ABS_X]
+        yorig = evdev.absinfo[libevdev.EV_ABS.ABS_Y]
+        deviation = 1.5 * touchpad.x.resolution  # 1.5 mm rounding on each side
+        skip = between(xorig.minimum, touchpad.x.minimum, deviation)
+        skip = skip and between(xorig.maximum, touchpad.x.maximum, deviation)
+        deviation = 1.5 * touchpad.y.resolution  # 1.5 mm rounding on each side
+        skip = skip and between(yorig.minimum, touchpad.y.minimum, deviation)
+        skip = skip and between(yorig.maximum, touchpad.y.maximum, deviation)
+
+        if skip:
+            print()
+            print('Note: Axis ranges within acceptable deviation, skipping min/max override')
+            print()
+
+        print()
+        print('Suggested hwdb entry:')
+
+        use_dmi = evdev.id['bustype'] not in [0x03, 0x05]  # USB, Bluetooth
+        if use_dmi:
+            modalias = open('/sys/class/dmi/id/modalias').read().strip()
+            print('Note: the dmi modalias match is a guess based on your machine\'s modalias:')
+            print(' ', modalias)
+            print('Please verify that this is the most sensible match and adjust if necessary.')
+
+        print('-8<--------------------------')
+        print('# Laptop model description (e.g. Lenovo X1 Carbon 5th)')
+        if use_dmi:
+            print('evdev:name:{}:{}*'.format(evdev.name, dmi_modalias_match(modalias)))
+        else:
+            print('evdev:input:b{:04X}v{:04X}p{:04X}*'.format(
+                  evdev.id['bustype'], evdev.id['vendor'], evdev.id['product']))
+        print(' EVDEV_ABS_00={}:{}:{}'.format(
+              touchpad.x.minimum if not skip else '',
+              touchpad.x.maximum if not skip else '',
+              touchpad.x.resolution))
+        print(' EVDEV_ABS_01={}:{}:{}'.format(
+              touchpad.y.minimum if not skip else '',
+              touchpad.y.maximum if not skip else '',
+              touchpad.y.resolution))
+        if evdev.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X]:
+            print(' EVDEV_ABS_35={}:{}:{}'.format(
+                  touchpad.x.minimum if not skip else '',
+                  touchpad.x.maximum if not skip else '',
+                  touchpad.x.resolution))
+            print(' EVDEV_ABS_36={}:{}:{}'.format(
+                  touchpad.y.minimum if not skip else '',
+                  touchpad.y.maximum if not skip else '',
+                  touchpad.y.resolution))
+        print('-8<--------------------------')
+        print('Instructions on what to do with this snippet are in /usr/lib/udev/hwdb.d/60-evdev.hwdb')
+    except DeviceError as e:
+        print('Error: {}'.format(e), file=sys.stderr)
+        return 1
+    except PermissionError:
+        print('Unable to open device. Please run me as root', file=sys.stderr)
+        return 1
+
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
index d8664f4ea4091343cb3d366cbbcdbbf9e19844bd..44ec68d3adcb32bd6a0e02e151fc73279d6bf38d 100644 (file)
@@ -28,6 +28,9 @@ Measure touch fuzz to avoid pointer jitter
 .B libinput\-measure\-touch\-size(1)
 Measure touch size and orientation
 .TP 8
+.B libinput\-measure\-touchpad\-size(1)
+Measure the size of a touchpad
+.TP 8
 .B libinput\-measure\-touchpad\-tap(1)
 Measure tap-to-click time
 .TP 8