diff --git a/tools/libinput-analyze-per-slot-delta.man b/tools/libinput-analyze-per-slot-delta.man index 11c66861a..b4c3e379f 100644 --- a/tools/libinput-analyze-per-slot-delta.man +++ b/tools/libinput-analyze-per-slot-delta.man @@ -18,6 +18,14 @@ rely on the output. .B \-\-help Print help .TP 8 +.B \-\-ignore-below= +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= +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 diff --git a/tools/libinput-analyze-per-slot-delta.py b/tools/libinput-analyze-per-slot-delta.py index 3363ea748..e40a7df41 100755 --- a/tools/libinput-analyze-per-slot-delta.py +++ b/tools/libinput-analyze-per-slot-delta.py @@ -40,37 +40,81 @@ 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__':