[PATCH libinput 1/4] tools: add a command to analyze trackpoints

Peter Hutterer peter.hutterer at who-t.net
Wed Jul 12 06:06:15 UTC 2017


Trackpoints can send very different ranges between the various pressures.
Collect the data and print it out to get an idea of what ranges are realistic.

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 meson.build                                 |   9 ++
 tools/libinput-measure-trackpoint-range     | 192 ++++++++++++++++++++++++++++
 tools/libinput-measure-trackpoint-range.man |  31 +++++
 tools/libinput-measure.man                  |   3 +
 tools/libinput.man                          |   3 +
 5 files changed, 238 insertions(+)
 create mode 100755 tools/libinput-measure-trackpoint-range
 create mode 100644 tools/libinput-measure-trackpoint-range.man

diff --git a/meson.build b/meson.build
index abc8d765..ac1ceb17 100644
--- a/meson.build
+++ b/meson.build
@@ -432,6 +432,15 @@ configure_file(input : 'tools/libinput-measure-touchpad-pressure.man',
 	       install_dir : join_paths(get_option('mandir'), 'man1')
 	       )
 
+install_data('tools/libinput-measure-trackpoint-range',
+            install_dir : libinput_tool_path)
+configure_file(input : 'tools/libinput-measure-trackpoint-range.man',
+	       output : 'libinput-measure-trackpoint-range.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')
diff --git a/tools/libinput-measure-trackpoint-range b/tools/libinput-measure-trackpoint-range
new file mode 100755
index 00000000..53dc67ec
--- /dev/null
+++ b/tools/libinput-measure-trackpoint-range
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+# vim: set expandtab shiftwidth=4:
+# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
+#
+# 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
+
+MINIMUM_EVENT_COUNT = 1000
+
+
+class InvalidDeviceError(Exception):
+    pass
+
+
+class Delta(object):
+    def __init__(self, x=0, y=0):
+        self.x = x
+        self.y = y
+
+    def __bool_(self):
+        return self.x != 0 or self.y != 0
+
+
+class Device(object):
+    def __init__(self, path):
+        if path is None:
+            path = self._find_trackpoint_device()
+        self.path = path
+
+        self.device = evdev.InputDevice(self.path)
+
+        self.deltas = []
+        self.nxdeltas = 0
+        self.nydeltas = 0
+
+        self.current_delta = Delta()
+        self.max_delta = Delta(0, 0)
+
+    def _find_trackpoint_device(self):
+        context = pyudev.Context()
+        for device in context.list_devices(subsystem='input'):
+            if not device.get('ID_INPUT_POINTINGSTICK', 0):
+                continue
+
+            if not device.device_node or \
+               not device.device_node.startswith('/dev/input/event'):
+                continue
+
+            return device.device_node
+
+        raise InvalidDeviceError("Unable to find a trackpoint device")
+
+    def handle_rel(self, event):
+        if event.code == evdev.ecodes.REL_X:
+            self.current_delta.x = event.value
+            if self.max_delta.x < abs(event.value):
+                self.max_delta.x = abs(event.value)
+        elif event.code == evdev.ecodes.REL_Y:
+            self.current_delta.y = event.value
+            if self.max_delta.y < abs(event.value):
+                self.max_delta.y = abs(event.value)
+
+    def handle_syn(self, event):
+        self.deltas.append(self.current_delta)
+        if self.current_delta.x != 0:
+            self.nxdeltas += 1
+        if self.current_delta.y != 0:
+            self.nydeltas += 1
+
+        self.current_delta = Delta()
+
+        print("\rTrackpoint sends: max x:{:3d}, max y:{:3} samples [{}, {}]"
+              .format(
+                self.max_delta.x, self.max_delta.y,
+                self.nxdeltas, self.nydeltas,
+              ), end="")
+
+    def read_events(self):
+        for event in self.device.read_loop():
+            if event.type == evdev.ecodes.EV_REL:
+                self.handle_rel(event)
+            elif event.type == evdev.ecodes.EV_SYN:
+                self.handle_syn(event)
+
+    def print_summary(self):
+        print("\n")  # undo the \r from the status line
+        if not self.deltas:
+            return
+
+        if len(self.deltas) < MINIMUM_EVENT_COUNT:
+            print("WARNING: *******************************************\n"
+                  "WARNING: Insufficient samples, data is not reliable\n"
+                  "WARNING: *******************************************\n")
+
+        print("Histogram for x axis deltas, in counts of 5")
+        xs = [d.x for d in self.deltas]
+        minx = min(xs)
+        maxx = max(xs)
+        for i in range(minx, maxx + 1):
+            xc = len([x for x in xs if x == i])
+            xc = int(xc/5)  # counts of 5 is enough
+            print("{:4}: {}".format(i, "+" * xc, end=""))
+
+        print("Histogram for y axis deltas, in counts of 5")
+        ys = [d.y for d in self.deltas]
+        miny = min(ys)
+        maxy = max(ys)
+        for i in range(miny, maxy + 1):
+            yc = len([y for y in ys if y == i])
+            yc = int(yc/5)  # counts of 5 is enough
+            print("{:4}: {}".format(i, "+" * yc, end=""))
+
+        axs = sorted([abs(x) for x in xs])
+        ays = sorted([abs(y) for y in ys])
+
+        avgx = int(sum(axs)/len(axs))
+        avgy = int(sum(ays)/len(ays))
+
+        medx = axs[int(len(axs)/2)]
+        medy = ays[int(len(ays)/2)]
+
+        pc95x = axs[int(len(axs) * 0.95)]
+        pc95y = ays[int(len(ays) * 0.95)]
+
+        print("Average for abs deltas: x: {:3} y: {:3}".format(avgx, avgy))
+        print("Median for abs deltas: x: {:3} y: {:3}".format(medx, medy))
+        print("95% percentile for abs deltas: x: {:3} y: {:3}"
+              .format(pc95x, pc95y)
+              )
+
+
+def main(args):
+    parser = argparse.ArgumentParser(
+                description="Measure the trackpoint delta coordinate range"
+             )
+    parser.add_argument('path', metavar='/dev/input/event0',
+                        nargs='?', type=str, help='Path to device (optional)')
+
+    args = parser.parse_args()
+
+    try:
+        device = Device(args.path)
+
+        print(
+           "This tool measures the commonly used pressure range of the\n"
+           "trackpoint. Push the trackpoint:\n"
+           "- Four times around the screen edges\n"
+           "- From the top left to the bottom right and back, twice\n"
+           "- From the top right to the bottom left and back, twice\n"
+           "A minimum of {} events for each axis is required\n"
+           "\n"
+           "Movements should emulate fast pointer movement on the screen\n"
+           "but not use excessive pressure that would not be used\n"
+           "during day-to-day movement. For best results, run this tool \n"
+           "several times to get an idea of the common range.\n"
+           "\n".format(MINIMUM_EVENT_COUNT))
+        device.read_events()
+    except KeyboardInterrupt:
+        device.print_summary()
+    except (PermissionError, OSError):
+        print("Error: failed to open device")
+    except InvalidDeviceError as e:
+        print("Error: {}".format(e))
+
+
+if __name__ == "__main__":
+    main(sys.argv)
diff --git a/tools/libinput-measure-trackpoint-range.man b/tools/libinput-measure-trackpoint-range.man
new file mode 100644
index 00000000..94f20404
--- /dev/null
+++ b/tools/libinput-measure-trackpoint-range.man
@@ -0,0 +1,31 @@
+.TH LIBINPUT-MEASURE-TRACKPOINT-RANGE "1" "" "libinput @LIBINPUT_VERSION@" "libinput Manual"
+.SH NAME
+libinput\-measure\-trackpoint\-range \- measure the delta range of a trackpoint
+.SH SYNOPSIS
+.B libinput measure trackpoint\-range [\-\-help] [/dev/input/event0]
+.SH DESCRIPTION
+.PP
+The
+.B "libinput measure trackpoint\-range"
+tool measures the delta range of a trackpoint. This is
+an interactive tool. When executed, the tool will prompt the user to
+interact with the trackpoint. On termination, the tool prints a summary of
+the trackpoint deltas seen. This data should be attached to any bug report
+relating to the trackpoint's speed.
+.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 trackpoint 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.man b/tools/libinput-measure.man
index d91afdd0..3bcfa158 100644
--- a/tools/libinput-measure.man
+++ b/tools/libinput-measure.man
@@ -27,6 +27,9 @@ Measure tap-to-click time
 .TP 8
 .B libinput\-measure\-touchpad\-pressure(1)
 Measure touch pressure
+.TP 8
+.B libinput\-measure\-trackpoint\-range(1)
+Measure the delta range of a trackpoint.
 .SH LIBINPUT
 Part of the
 .B libinput(1)
diff --git a/tools/libinput.man b/tools/libinput.man
index fef7cdca..ca8a71fd 100644
--- a/tools/libinput.man
+++ b/tools/libinput.man
@@ -50,6 +50,9 @@ Measure tap-to-click time
 .TP 8
 .B libinput\-measure\-touchpad\-pressure(1)
 Measure touch pressure
+.TP 8
+.B libinput-measure-trackpoint-range(1)
+Measure the delta range of a trackpoint
 .SH LIBINPUT
 Part of the
 .B libinput(1)
-- 
2.13.0



More information about the wayland-devel mailing list