mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-20 09:10:04 +01:00
190 lines
6.3 KiB
Python
190 lines
6.3 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
# -*- coding: utf-8
|
||
|
|
# vim: set expandtab shiftwidth=4:
|
||
|
|
# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
|
||
|
|
#
|
||
|
|
# Copyright © 2021 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 data from a libinput recording in a table format to ease
|
||
|
|
# debugging.
|
||
|
|
#
|
||
|
|
# Input is a libinput record yaml file
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import sys
|
||
|
|
import yaml
|
||
|
|
import libevdev
|
||
|
|
|
||
|
|
# minimum width of a field in the table
|
||
|
|
MIN_FIELD_WIDTH = 6
|
||
|
|
|
||
|
|
|
||
|
|
# Default is to just return the value of an axis, but some axes want special
|
||
|
|
# formatting.
|
||
|
|
def format_value(code, value):
|
||
|
|
if code in (libevdev.EV_ABS.ABS_MISC, libevdev.EV_MSC.MSC_SERIAL):
|
||
|
|
return f"{value & 0xFFFFFFFF:#x}"
|
||
|
|
|
||
|
|
# Rel axes we always print the sign
|
||
|
|
if code.type == libevdev.EV_REL:
|
||
|
|
return f"{value:+d}"
|
||
|
|
|
||
|
|
return f"{value}"
|
||
|
|
|
||
|
|
|
||
|
|
# The list of axes we want to track
|
||
|
|
def is_tracked_axis(code):
|
||
|
|
if code.type in (libevdev.EV_KEY, libevdev.EV_SW, libevdev.EV_SYN):
|
||
|
|
return False
|
||
|
|
|
||
|
|
# We don't do slots in this tool
|
||
|
|
if code.type == libevdev.EV_ABS:
|
||
|
|
if libevdev.EV_ABS.ABS_MT_SLOT <= code <= libevdev.EV_ABS.ABS_MAX:
|
||
|
|
return False
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def main(argv):
|
||
|
|
parser = argparse.ArgumentParser(
|
||
|
|
description="Display a recording in a tabular format"
|
||
|
|
)
|
||
|
|
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]))
|
||
|
|
if yml["ndevices"] > 1:
|
||
|
|
print(f"WARNING: Using only first {yml['ndevices']} devices in recording")
|
||
|
|
device = yml["devices"][0]
|
||
|
|
|
||
|
|
if not device["events"]:
|
||
|
|
print(f"No events found in recording")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
def events():
|
||
|
|
"""
|
||
|
|
Yields the next event in the recording
|
||
|
|
"""
|
||
|
|
for event in device["events"]:
|
||
|
|
for evdev in event.get("evdev", []):
|
||
|
|
yield libevdev.InputEvent(
|
||
|
|
code=libevdev.evbit(evdev[2], evdev[3]),
|
||
|
|
value=evdev[4],
|
||
|
|
sec=evdev[0],
|
||
|
|
usec=evdev[1],
|
||
|
|
)
|
||
|
|
|
||
|
|
def interesting_axes(events):
|
||
|
|
"""
|
||
|
|
Yields the libevdev codes with the axes in this recording
|
||
|
|
"""
|
||
|
|
used_axes = []
|
||
|
|
for e in events:
|
||
|
|
if e.code not in used_axes and is_tracked_axis(e.code):
|
||
|
|
yield e.code
|
||
|
|
used_axes.append(e.code)
|
||
|
|
|
||
|
|
# Compile all axes that we want to print first
|
||
|
|
axes = sorted(
|
||
|
|
interesting_axes(events()), key=lambda x: x.type.value * 1000 + x.value
|
||
|
|
)
|
||
|
|
# Strip the REL_/ABS_ prefix for the headers
|
||
|
|
headers = [a.name[4:].rjust(MIN_FIELD_WIDTH) for a in axes]
|
||
|
|
# for easier formatting later, we keep the header field width in a dict
|
||
|
|
axes = {a: len(h) for a, h in zip(axes, headers)}
|
||
|
|
|
||
|
|
# Time is a special case, always the first entry
|
||
|
|
# Format uses ms only, we rarely ever care about µs
|
||
|
|
headers = [f"{'Time':<7s}"] + headers + ["Keys"]
|
||
|
|
header_line = f"{' | '.join(headers)}"
|
||
|
|
print(header_line)
|
||
|
|
print("-" * len(header_line))
|
||
|
|
|
||
|
|
current_frame = {} # {evdev-code: value}
|
||
|
|
axes_in_use = {} # to print axes never sending events
|
||
|
|
last_fields = [] # to skip duplicate lines
|
||
|
|
continuation_count = 0
|
||
|
|
|
||
|
|
keystate = {}
|
||
|
|
keystate_changed = False
|
||
|
|
|
||
|
|
for e in events():
|
||
|
|
axes_in_use[e.code] = True
|
||
|
|
|
||
|
|
if e.code.type == libevdev.EV_KEY:
|
||
|
|
keystate[e.code] = e.value
|
||
|
|
keystate_changed = True
|
||
|
|
elif is_tracked_axis(e.code):
|
||
|
|
current_frame[e.code] = e.value
|
||
|
|
elif e.code == libevdev.EV_SYN.SYN_REPORT:
|
||
|
|
fields = []
|
||
|
|
for a in axes:
|
||
|
|
s = format_value(a, current_frame[a]) if a in current_frame else " "
|
||
|
|
fields.append(s.rjust(max(MIN_FIELD_WIDTH, axes[a])))
|
||
|
|
current_frame = {}
|
||
|
|
|
||
|
|
if last_fields != fields or keystate_changed:
|
||
|
|
last_fields = fields.copy()
|
||
|
|
keystate_changed = False
|
||
|
|
|
||
|
|
if continuation_count:
|
||
|
|
continuation_count = 0
|
||
|
|
print("")
|
||
|
|
|
||
|
|
fields.insert(0, f"{e.sec: 3d}.{e.usec//1000:03d}")
|
||
|
|
keys_down = [k.name for k, v in keystate.items() if v]
|
||
|
|
fields.append(", ".join(keys_down))
|
||
|
|
print(" | ".join(fields))
|
||
|
|
else:
|
||
|
|
continuation_count += 1
|
||
|
|
print(f"\r ... +{continuation_count}", end="", flush=True)
|
||
|
|
|
||
|
|
# Print out any rel/abs axes that not generate events in
|
||
|
|
# this recording
|
||
|
|
unused_axes = []
|
||
|
|
for evtype, evcodes in device["evdev"]["codes"].items():
|
||
|
|
for c in evcodes:
|
||
|
|
code = libevdev.evbit(int(evtype), int(c))
|
||
|
|
if is_tracked_axis(code) and code not in axes_in_use:
|
||
|
|
unused_axes.append(code)
|
||
|
|
|
||
|
|
if unused_axes:
|
||
|
|
print(
|
||
|
|
f"Axes present but without events: {', '.join([a.name for a in unused_axes])}"
|
||
|
|
)
|
||
|
|
|
||
|
|
for e in events():
|
||
|
|
if libevdev.EV_ABS.ABS_MT_SLOT <= code <= libevdev.EV_ABS.ABS_MAX:
|
||
|
|
print(
|
||
|
|
"WARNING: This recording contains multitouch data that is not supported by this tool."
|
||
|
|
)
|
||
|
|
break
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
try:
|
||
|
|
main(sys.argv)
|
||
|
|
except BrokenPipeError:
|
||
|
|
pass
|