diff options
author | Peter Hutterer <peter.hutterer@who-t.net> | 2021-01-25 08:17:21 +1000 |
---|---|---|
committer | Peter Hutterer <peter.hutterer@who-t.net> | 2021-01-29 08:06:53 +1000 |
commit | ff598741a972adaf85779cd8485948e8d2d1e666 (patch) | |
tree | 6325363966617284a20309970844d0e4c76905af /tools/libinput-analyze-touch-down-state.py | |
parent | 9925594257dc50ed9ee9b343cf8d329f007f966b (diff) | |
download | libinput-ff598741a972adaf85779cd8485948e8d2d1e666.tar.gz |
tools: add a tool to analyze the finger count from a recording
Given a libinput recording, print the timestamps of any finger changes, i.e.
which slots are currently logically down. For example:
Timestamp | Rel time | Slots |
--------------------------------------
0.000000 | +0.000s | + | | | |
0.454631 | +0.454s | | | | |
5.065401 | +4.610s | + | | | |
6.140281 | +1.074s | + | + | | |
7.410377 | +1.270s | | + | | |
7.420200 | +0.009s | | | | |
11.233108 | +3.812s | + | + | | |
11.850206 | +0.617s | | | | |
13.827740 | +1.977s | + | | | |
14.704027 | +0.876s | + | + | | |
16.050577 | +1.346s | + | | | |
16.905186 | +0.854s | | | | |
This data is available with the per-slot-delta tool but the output here is
more compressed, making it easier to detect stuck fingers. Pressure
thresholds are not currently supported.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Diffstat (limited to 'tools/libinput-analyze-touch-down-state.py')
-rwxr-xr-x | tools/libinput-analyze-touch-down-state.py | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/tools/libinput-analyze-touch-down-state.py b/tools/libinput-analyze-touch-down-state.py new file mode 100755 index 00000000..d4e0d527 --- /dev/null +++ b/tools/libinput-analyze-touch-down-state.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 +# 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. +# +# +# Prints the down/up state of each touch slot +# +# Input is a libinput record yaml file + +import argparse +import enum +import sys +import yaml +import libevdev + + +class Slot: + class State(enum.Enum): + NONE = "NONE" + BEGIN = "BEGIN" + UPDATE = "UPDATE" + END = "END" + + def __init__(self, index): + self._state = Slot.State.NONE + self.index = index + self.used = False + + def begin(self): + assert self.state == Slot.State.NONE + self.state = Slot.State.BEGIN + + def end(self): + assert self.state in (Slot.State.BEGIN, Slot.State.UPDATE) + self.state = Slot.State.END + + def sync(self): + if self.state == Slot.State.BEGIN: + self.state = Slot.State.UPDATE + elif self.state == Slot.State.END: + self.state = Slot.State.NONE + + @property + def state(self): + return self._state + + @state.setter + def state(self, newstate): + assert newstate in Slot.State + + if newstate != Slot.State.NONE: + self.used = True + self._state = newstate + + @property + def is_active(self): + return self.state in (Slot.State.BEGIN, Slot.State.UPDATE) + + def __str__(self): + return "+" if self.state in (Slot.State.BEGIN, Slot.State.UPDATE) else " " + + +def main(argv): + parser = argparse.ArgumentParser(description="Print the state of touches over time") + parser.add_argument( + "--use-st", action="store_true", help="Ignore slots, use the BTN_TOOL bits" + ) + parser.add_argument( + "path", metavar="recording", nargs=1, help="Path to libinput-record YAML file" + ) + args = parser.parse_args() + + yml = yaml.safe_load(open(args.path[0])) + device = yml["devices"][0] + absinfo = device["evdev"]["absinfo"] + try: + nslots = absinfo[libevdev.EV_ABS.ABS_MT_SLOT.value][1] + 1 + except KeyError: + args.use_st = True + + tool_slot_map = { + libevdev.EV_KEY.BTN_TOOL_FINGER: 0, + libevdev.EV_KEY.BTN_TOOL_PEN: 0, + libevdev.EV_KEY.BTN_TOOL_DOUBLETAP: 1, + libevdev.EV_KEY.BTN_TOOL_TRIPLETAP: 2, + libevdev.EV_KEY.BTN_TOOL_QUADTAP: 3, + libevdev.EV_KEY.BTN_TOOL_QUINTTAP: 4, + } + if args.use_st: + for bit in tool_slot_map: + if bit.value in device["evdev"]["codes"][libevdev.EV_KEY.value]: + nslots = max(nslots, tool_slot_map[bit]) + + slots = [Slot(i) for i in range(0, nslots)] + # We claim the first slots are used just to make the formatting + # more consistent + for i in range(min(5, len(slots))): + slots[i].used = True + + slot = 0 + last_time = None + last_slot_state = None + header = "Timestamp | Rel time | Slots |" + print(header) + print("-" * len(header)) + + def events(): + for event in device["events"]: + for evdev in event["evdev"]: + yield evdev + + for evdev in events(): + e = libevdev.InputEvent( + code=libevdev.evbit(evdev[2], evdev[3]), + value=evdev[4], + sec=evdev[0], + usec=evdev[1], + ) + + # single-touch formatting is simpler than multitouch, it'll just + # show the highest finger down rather than the correct output. + if args.use_st: + if e.code in tool_slot_map: + slot = tool_slot_map[e.code] + s = slots[slot] + if e.value: + s.begin() + else: + s.end() + else: + if e.code == libevdev.EV_ABS.ABS_MT_SLOT: + slot = e.value + s = slots[slot] + # bcm5974 cycles through slot numbers, so let's say all below + # our current slot number was used + for sl in slots[: slot + 1]: + sl.used = True + else: + s = slots[slot] + if e.code == libevdev.EV_ABS.ABS_MT_TRACKING_ID: + if e.value == -1: + s.end() + else: + s.begin() + elif e.code in ( + libevdev.EV_ABS.ABS_MT_POSITION_X, + libevdev.EV_ABS.ABS_MT_POSITION_Y, + libevdev.EV_ABS.ABS_MT_PRESSURE, + libevdev.EV_ABS.ABS_MT_TOUCH_MAJOR, + libevdev.EV_ABS.ABS_MT_TOUCH_MINOR, + ): + # If recording started after touch down + if s.state == Slot.State.NONE: + s.begin() + + if e.code == libevdev.EV_SYN.SYN_REPORT: + current_slot_state = tuple(s.is_active for s in slots) + + if current_slot_state != last_slot_state: + if last_time is None: + last_time = e.sec * 1000000 + e.usec + tdelta = 0 + else: + t = e.sec * 1000000 + e.usec + tdelta = int((t - last_time) / 1000) / 1000 + last_time = t + + fmt = " | ".join([str(s) for s in slots if s.used]) + print( + "{:2d}.{:06d} | {:+7.3f}s | {}".format(e.sec, e.usec, tdelta, fmt) + ) + + last_slot_state = current_slot_state + + for s in slots: + s.sync() + + +if __name__ == "__main__": + main(sys.argv) |