tools: per-slot-delta: add arguments to set a threshold and ignore-below
authorPeter Hutterer <peter.hutterer@who-t.net>
Sat, 11 Apr 2020 01:58:15 +0000 (11:58 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Sat, 11 Apr 2020 04:35:27 +0000 (14:35 +1000)
The threshold colors events above a certain value in red, ignore-below skips
any line below that threshold.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
tools/libinput-analyze-per-slot-delta.man
tools/libinput-analyze-per-slot-delta.py

index 11c66861a96cc6a049e8e14694c5fc26d118f489..b4c3e379f72b5c586620f293c613bd4a94f217fd 100644 (file)
@@ -18,6 +18,14 @@ rely on the output.
 .B \-\-help
 Print help
 .TP 8
+.B \-\-ignore-below=<units or mm>
+Ignore any movement below the given threshold. The threshold is in
+mm if \fB\-\-use-mm\fR is selected or in device units otherwise.
+.TP 8
+.B \-\-threshold=<units or mm>
+Color any movement above this threshold in red. The threshold is in
+mm if \fB\-\-use-mm\fR is selected or in device units otherwise.
+.TP 8
 .B \-\-use-mm
 Print data in mm instead of device units
 .TP 8
index 3363ea7483b9c8f0c5347f4577be1d33c652e9bb..e40a7df413d419dc02308f61193f24a83f3d4f35 100755 (executable)
@@ -40,37 +40,81 @@ COLOR_RESET = '\x1b[0m'
 COLOR_RED = '\x1b[6;31m'
 
 
-def print_data(dx, dy, is_absolute=False, color=None):
-    if dx != 0 and dy != 0:
-        t = math.atan2(dx, dy)
-        t += math.pi  # in [0, 2pi] range now
-
-        if t == 0:
-            t = 0.01
-        else:
-            t = t * 180.0 / math.pi
-
-        directions = ['↖↑', '↖←', '↙←', '↙↓', '↓↘', '→↘', '→↗', '↑↗']
-        direction = "{:3.0f}".format(t)
-        direction = directions[int(t / 45)]
-    elif dy == 0:
-        if dx < 0:
-            direction = '←←'
-        else:
-            direction = '→→'
-    else:
-        if dy < 0:
-            direction = '↑↑'
+class SlotFormatter():
+    width = 16
+
+    def __init__(self, is_absolute=False, resolution=None,
+                 threshold=None, ignore_below=None):
+        self.threshold = threshold
+        self.ignore_below = ignore_below
+        self.resolution = resolution
+        self.is_absolute = is_absolute
+        self.slots = []
+        self.have_data = False
+        self.filtered = False
+
+    def __str__(self):
+        return ' | '.join(self.slots)
+
+    def format_slot(self, slot):
+        if slot.state == SlotState.BEGIN:
+            self.slots.append('+++++++'.center(self.width))
+            self.have_data = True
+        elif slot.state == SlotState.END:
+            self.slots.append('-------'.center(self.width))
+            self.have_data = True
+        elif slot.state == SlotState.NONE:
+            self.slots.append(('*' * (self.width - 2)).center(self.width))
+        elif not slot.dirty:
+            self.slots.append(' '.center(self.width))
         else:
-            direction = '↓↓'
+            if self.resolution is not None:
+                dx, dy = slot.dx / self.resolution[0], slot.dy / self.resolution[1]
+            else:
+                dx, dy = slot.dx, slot.dy
+            if dx != 0 and dy != 0:
+                t = math.atan2(dx, dy)
+                t += math.pi  # in [0, 2pi] range now
 
-    if not is_absolute:
-        if isinstance(dx, int) and isinstance(dy, int):
-            print("{} {}{:+4d}/{:+4d}{} | ".format(direction, color, dx, dy, COLOR_RESET), end='')
-        else:
-            print("{} {}{:+3.2f}/{:+03.2f}{} | ".format(direction, color, dx, dy, COLOR_RESET), end='')
-    else:
-        print("{} {}{:4d}/{:4d}{} | ".format(direction, color, dx, dy, COLOR_RESET), end='')
+                if t == 0:
+                    t = 0.01
+                else:
+                    t = t * 180.0 / math.pi
+
+                directions = ['↖↑', '↖←', '↙←', '↙↓', '↓↘', '→↘', '→↗', '↑↗']
+                direction = directions[int(t / 45)]
+            elif dy == 0:
+                if dx < 0:
+                    direction = '←←'
+                else:
+                    direction = '→→'
+            else:
+                if dy < 0:
+                    direction = '↑↑'
+                else:
+                    direction = '↓↓'
+
+            color = ''
+            reset = ''
+            if not self.is_absolute:
+                if self.ignore_below is not None or self.threshold is not None:
+                    dist = math.hypot(dx, dy)
+                    if self.ignore_below is not None and dist < self.ignore_below:
+                        self.slots.append(' '.center(self.width))
+                        self.filtered = True
+                        return
+                    if self.threshold is not None and dist >= self.threshold:
+                        color = COLOR_RED
+                        reset = COLOR_RESET
+                if isinstance(dx, int) and isinstance(dy, int):
+                    string = "{} {}{:+4d}/{:+4d}{}".format(direction, color, dx, dy, reset)
+                else:
+                    string = "{} {}{:+3.2f}/{:+03.2f}{}".format(direction, color, dx, dy, reset)
+            else:
+                x, y = slot.x, slot.y
+                string = "{} {}{:4d}/{:4d}{}".format(direction, color, x, y, reset)
+            self.have_data = True
+            self.slots.append(string.ljust(self.width + len(color) + len(reset)))
 
 
 class SlotState:
@@ -115,6 +159,8 @@ def main(argv):
     parser.add_argument("--use-absolute", action='store_true', help="Use absolute coordinates, not deltas")
     parser.add_argument("path", metavar="recording",
                         nargs=1, help="Path to libinput-record YAML file")
+    parser.add_argument("--threshold", type=float, default=None, help="Mark any delta above this treshold")
+    parser.add_argument("--ignore-below", type=float, default=None, help="Ignore any delta below this theshold")
     args = parser.parse_args()
 
     if not sys.stdout.isatty():
@@ -134,10 +180,6 @@ def main(argv):
 
     slots = [Slot(i) for i in range(0, nslots)]
 
-    marker_begin_slot = "   ++++++    | "  # noqa
-    marker_end_slot   = "   ------    | "  # noqa
-    marker_empty_slot = " *********** | "  # noqa
-    marker_no_data    = "             | "  # noqa
     marker_button     = "..............."  # noqa
 
     if args.use_mm:
@@ -147,11 +189,6 @@ def main(argv):
             print("Error: device doesn't have a resolution, cannot use mm")
             sys.exit(1)
 
-        marker_empty_slot = " ************* | "  # noqa
-        marker_no_data =    "               | "  # noqa
-        marker_begin_slot = "    ++++++     | "  # noqa
-        marker_end_slot =   "    ------     | "  # noqa
-
     if args.use_st:
         print("Warning: slot coordinates on FINGER/DOUBLETAP change may be incorrect")
         slots[0].used = True
@@ -166,6 +203,8 @@ def main(argv):
         libevdev.EV_KEY.BTN_TOOL_QUINTTAP: 0,
     }
 
+    nskipped_lines = 0
+
     for event in device['events']:
         for evdev in event['evdev']:
             s = slots[slot]
@@ -259,37 +298,29 @@ def main(argv):
                 else:
                     tool_state = '   '
 
-                print("{:2d}.{:06d} {:+5d}ms {}: ".format(e.sec, e.usec, tdelta, tool_state), end='')
+                fmt = SlotFormatter(is_absolute=args.use_absolute,
+                                    resolution=(xres, yres) if args.use_mm else None,
+                                    threshold=args.threshold,
+                                    ignore_below=args.ignore_below)
                 for sl in [s for s in slots if s.used]:
-                    if sl.state == SlotState.NONE:
-                        print(marker_empty_slot, end='')
-                    elif sl.state == SlotState.BEGIN:
-                        print(marker_begin_slot, end='')
-                    elif sl.state == SlotState.END:
-                        print(marker_end_slot, end='')
-                    elif not sl.dirty:
-                        print(marker_no_data, end='')
-                    else:
-                        if args.use_mm:
-                            sl.dx /= xres
-                            sl.dy /= yres
-                            color = COLOR_RESET
-                            if math.hypot(sl.dx, sl.dy) > 7:
-                                color = COLOR_RED
-                            print_data(sl.dx, sl.dy, color=color)
-                        elif args.use_absolute:
-                            print_data(sl.x, sl.y, is_absolute=True)
-                        else:
-                            print_data(sl.dx, sl.dy)
-                        s.dx = 0
-                        s.dy = 0
+                    fmt.format_slot(sl)
+
+                    sl.dirty = False
+                    sl.dx = 0
+                    sl.dy = 0
                     if sl.state == SlotState.BEGIN:
                         sl.state = SlotState.UPDATE
                     elif sl.state == SlotState.END:
                         sl.state = SlotState.NONE
 
-                    sl.dirty = False
-                print("")
+                if fmt.have_data:
+                    if nskipped_lines > 0:
+                        print("")
+                        nskipped_lines = 0
+                    print("{:2d}.{:06d} {:+5d}ms {}: {}".format(e.sec, e.usec, tdelta, tool_state, fmt))
+                elif fmt.filtered:
+                    nskipped_lines += 1
+                    print("\r", " " * 21, "... {} below threshold".format(nskipped_lines), flush=True, end='')
 
 
 if __name__ == '__main__':