tools: revamp the touchpad-pressure measuring tool
authorPeter Hutterer <peter.hutterer@who-t.net>
Sun, 29 Mar 2020 02:08:30 +0000 (12:08 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Mon, 30 Mar 2020 04:44:47 +0000 (14:44 +1000)
Let's hope this one is more obvious to use for users.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
doc/user/touchpad-pressure-debugging.rst
tools/libinput-measure-touchpad-pressure.py

index 1ca3fe0e80d8433a7127b226a237256ddba26c68..58837d3a648b05fc324da2a5a32418e4ef3af4c0 100644 (file)
@@ -38,27 +38,43 @@ statistics, including whether a touch is/was considered logically down.
 
 Example output of the tool is below: ::
 
-     $ 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
+    $ sudo libinput measure touchpad-pressure
+    Using Synaptics TM2668-002: /dev/input/event21
+
+    This is an interactive tool
+
+    Place a single finger on the touchpad to measure pressure values.
+    Check that:
+    - touches subjectively perceived as down are tagged as down
+    - touches with a thumb are tagged as thumb
+    - touches with a palm are tagged as palm
+
+    If the touch states do not match the interaction, re-run
+    with --touch-thresholds=down:up using observed pressure values.
+    See --help for more options.
+
+    Press Ctrl+C to exit
+
+    +-------------------------------------------------------------------------------+
+    | Thresh |   70   |  60  |  130   |  100   |                                    |
+    +-------------------------------------------------------------------------------+
+    | Touch  |  down  |  up  |  palm  | thumb  | min  | max  | p  | avg  |  median  |
+    +-------------------------------------------------------------------------------+
+    |  178   |   x    |  x   |        |        |  75  |  75  | 0  |  75  |    75    |
+    |  179   |   x    |  x   |        |        |  35  |  88  | 0  |  77  |    81    |
+    |  180   |   x    |  x   |        |   x    |  65  | 113  | 0  |  98  |    98    |
+    |  181   |   x    |  x   |        |   x    |  50  | 101  | 0  |  86  |    90    |
+    |  182   |   x    |  x   |        |        |  40  |  80  | 0  |  66  |    70    |
+    |  183   |   x    |      |        |        |  43  |  78  | 78 |                 |
+    ...
 
 
 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 **libinput-measure-touchpad-pressure(1)** man
-page for more details.
+well as some statistics. The ``down`` column show that each sequence was
+considered logically down at some point, two of the sequences were considered
+thumbs. This is an interactive tool and its output may change frequently. Refer
+to the **libinput-measure-touchpad-pressure(1)** man page for more details.
 
 By default, this tool uses the :ref:`device-quirks` for the pressure range. To
 narrow down on the best values for your device, specify the 'logically down'
index 812074b272ed6f69e3c4f03f6e039d1640d2a7d9..a6cabe0228e17c471bf69ec3a1dc3506e9ad9d90 100755 (executable)
@@ -37,6 +37,51 @@ except ModuleNotFoundError as e:
     sys.exit(1)
 
 
+class TableFormatter(object):
+    ALIGNMENT = 3
+
+    def __init__(self):
+        self.colwidths = []
+
+    @property
+    def width(self):
+        return sum(self.colwidths) + 1
+
+    def headers(self, args):
+        s = '|'
+        align = self.ALIGNMENT - 1  # account for |
+
+        for arg in args:
+            # +2 because we want space left/right of text
+            w = ((len(arg) + 2 + align) // align) * align
+            self.colwidths.append(w + 1)
+            s += ' {:^{width}s} |'.format(arg, width=w - 2)
+
+        return s
+
+    def values(self, args):
+        s = '|'
+        for w, arg in zip(self.colwidths, args):
+            w -= 1  # width includes | separator
+            if type(arg) == str:
+                # We want space margins for strings
+                s += ' {:{width}s} |'.format(arg, width=w - 2)
+            elif type(arg) == bool:
+                s += '{:^{width}s}|'.format('x' if arg else ' ', width=w)
+            else:
+                s += '{:^{width}d}|'.format(arg, width=w)
+
+        if len(args) < len(self.colwidths):
+            s += '|'.rjust(self.width - len(s), ' ')
+        return s
+
+    def separator(self):
+        return '+' + '-' * (self.width - 2) + '+'
+
+
+fmt = TableFormatter()
+
+
 class Range(object):
     """Class to keep a min/max of a value around"""
     def __init__(self):
@@ -112,36 +157,19 @@ class TouchSequence(object):
 
     def _str_summary(self):
         if not self.points:
-            return "{:78s}".format("Sequence: no pressure values recorded")
-
-        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"
-        if self.was_thumb:
-            s += " thumb"
+            return fmt.values([self.tracking_id, False, False, False, False,
+                              'No pressure values recorded'])
+
+        s = fmt.values([self.tracking_id, self.was_down, True, self.was_palm,
+                        self.was_thumb, self.prange.min, self.prange.max, 0,
+                        self.avg(), self.median()])
 
         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 "    ",
-                "thumb" if self.is_thumb else "     "
-            )
+        s = fmt.values([self.tracking_id, self.is_down, not self.is_down,
+                        self.is_palm, self.is_thumb, self.prange.min,
+                        self.prange.max, self.points[-1].pressure])
         return s
 
 
@@ -227,8 +255,8 @@ def handle_key(device, event):
         libevdev.EV_KEY.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)
+        print('\r\033[2KThis tool cannot handle multiple fingers, '
+              'output will be invalid')
 
 
 def handle_abs(device, event):
@@ -239,7 +267,7 @@ def handle_abs(device, event):
             try:
                 s = device.current_sequence()
                 s.finalize()
-                print("\r{}".format(s))
+                print("\r\033[2K{}".format(s))
             except IndexError:
                 # If the finger was down at startup
                 pass
@@ -248,7 +276,7 @@ def handle_abs(device, event):
         try:
             s = device.current_sequence()
             s.append(Touch(pressure=event.value))
-            print("\r{}".format(s), end="")
+            print("\r\033[2K{}".format(s), end="")
         except IndexError:
             # If the finger was down at startup
             pass
@@ -262,12 +290,27 @@ def handle_event(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("Thumb pressure range used: {}".format(device.thumb))
-    print("Place a single finger on the touchpad to measure pressure values.\n"
-          "Ctrl+C to exit\n")
+    print('This is an interactive tool')
+    print()
+    print("Place a single finger on the touchpad to measure pressure values.")
+    print('Check that:')
+    print('- touches subjectively perceived as down are tagged as down')
+    print('- touches with a thumb are tagged as thumb')
+    print('- touches with a palm are tagged as palm')
+    print()
+    print('If the touch states do not match the interaction, re-run')
+    print('with --touch-thresholds=down:up using observed pressure values.')
+    print('See --help for more options.')
+    print()
+    print("Press Ctrl+C to exit")
+    print()
+
+    headers = fmt.headers(['Touch', 'down', 'up', 'palm', 'thumb', 'min', 'max', 'p', 'avg', 'median'])
+    print(fmt.separator())
+    print(fmt.values(['Thresh', device.down, device.up, device.palm, device.thumb]))
+    print(fmt.separator())
+    print(headers)
+    print(fmt.separator())
 
     while True:
         for event in device.events():
@@ -316,7 +359,9 @@ def main(args):
 
         loop(device)
     except KeyboardInterrupt:
-        pass
+        print('\r\033[2K{}'.format(fmt.separator()))
+        print()
+
     except (PermissionError, OSError):
         print("Error: failed to open device")
     except InvalidDeviceError as e: