mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-20 06:50:05 +01:00
tools: add a libinput analyze command with the per-slot-delta subcommand
I've been using this script ever since libinput record was available, might as well ship it with libinput so I don't have to remember where it lives. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
parent
927a7c0745
commit
e11bad41f5
5 changed files with 450 additions and 0 deletions
15
meson.build
15
meson.build
|
|
@ -577,7 +577,22 @@ configure_file(input : 'tools/libinput-measure.man',
|
|||
install_dir : dir_man1,
|
||||
)
|
||||
|
||||
libinput_analyze_sources = [ 'tools/libinput-analyze.c' ]
|
||||
executable('libinput-analyze',
|
||||
libinput_analyze_sources,
|
||||
dependencies : deps_tools,
|
||||
include_directories : [includes_src, includes_include],
|
||||
install_dir : libinput_tool_path,
|
||||
install : true,
|
||||
)
|
||||
configure_file(input : 'tools/libinput-analyze.man',
|
||||
output : 'libinput-analyze.1',
|
||||
configuration : man_config,
|
||||
install_dir : dir_man1,
|
||||
)
|
||||
|
||||
src_python_tools = files(
|
||||
'tools/libinput-analyze-per-slot-delta.py',
|
||||
'tools/libinput-measure-fuzz.py',
|
||||
'tools/libinput-measure-touchpad-tap.py',
|
||||
'tools/libinput-measure-touchpad-pressure.py',
|
||||
|
|
|
|||
69
tools/libinput-analyze-per-slot-delta.man
Normal file
69
tools/libinput-analyze-per-slot-delta.man
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
.TH libinput-analyze-per-slot-delta "1"
|
||||
.SH NAME
|
||||
libinput\-analyze\-per\-slot\-delta \- analyze the per-event delta movement for touch slots
|
||||
.SH SYNOPSIS
|
||||
.B libinput analyze per-slot-delta [\-\-help] [options] \fIrecording.yml\fI
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The
|
||||
.B "libinput analyze per\-slot\-delta"
|
||||
tool analyzes a recording made with
|
||||
.B "libinput record"
|
||||
and prints the delta movement per touch slot.
|
||||
.PP
|
||||
This is a debugging tool only, its output may change at any time. Do not
|
||||
rely on the output.
|
||||
.SH OPTIONS
|
||||
.TP 8
|
||||
.B \-\-help
|
||||
Print help
|
||||
.TP 8
|
||||
.B \-\-use-mm
|
||||
Print data in mm instead of device units
|
||||
.TP 8
|
||||
.B \-\-use-st
|
||||
Use the single-touch ABS_X/ABS_Y instead of the multitouch axes
|
||||
.TP 8
|
||||
.B \-\-use-absolute
|
||||
Print absolute coordinates, not deltas
|
||||
.SH OUTPUT
|
||||
An example output for a single finger touch on a touchpad supporting two
|
||||
slots is below. This output is with the use of the
|
||||
.B --use-mm
|
||||
flag.
|
||||
.PP
|
||||
.nf
|
||||
.sf
|
||||
0.000000 +0ms: ++++++ | ************* |
|
||||
0.021900 +21ms: →↘ +1.10/+0.14 | ************* |
|
||||
0.033468 +11ms: →↘ +1.15/+0.19 | ************* |
|
||||
0.043856 +10ms: →↘ +1.76/+0.22 | ************* |
|
||||
0.053237 +9ms: →↘ +2.20/+0.19 | ************* |
|
||||
.fi
|
||||
.in
|
||||
.PP
|
||||
The entry
|
||||
.B ++++++
|
||||
indicates a finger has been put down,
|
||||
.B ------
|
||||
indicates the finger has lifted.
|
||||
The left-most column is the absolute timestamp in seconds.microseconds
|
||||
followed by the relative time of the event to the previous event. The arrows
|
||||
indicate the approximate direction on a 16-point compass.
|
||||
.PP
|
||||
Each multitouch slot supported by the hardware has one column, where the
|
||||
column shows asterisk
|
||||
.B ********
|
||||
no finger is down for that slot. Where the column shows spaces only, a
|
||||
finger is down but no data is currently available.
|
||||
.PP
|
||||
In the above example, the third events occurs ~33ms into the recording, is
|
||||
11ms after the previous event and has an east south-east direction. The
|
||||
movement for this event was x: 1.15 and y: 0.19 mm. A second finger is not
|
||||
currently down.
|
||||
.SH LIBINPUT
|
||||
Part of the
|
||||
.B libinput(1)
|
||||
suite
|
||||
|
||||
|
||||
264
tools/libinput-analyze-per-slot-delta.py
Executable file
264
tools/libinput-analyze-per-slot-delta.py
Executable file
|
|
@ -0,0 +1,264 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
# vim: set expandtab shiftwidth=4:
|
||||
# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
|
||||
#
|
||||
# Copyright © 2018 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.
|
||||
#
|
||||
#
|
||||
# Measures the relative motion between touch events (based on slots)
|
||||
#
|
||||
# Input is a libinput record yaml file
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import sys
|
||||
import yaml
|
||||
import libevdev
|
||||
|
||||
|
||||
COLOR_RESET = '\x1b[0m'
|
||||
COLOR_RED = '\x1b[6;31m'
|
||||
|
||||
|
||||
class SlotState:
|
||||
NONE = 0
|
||||
BEGIN = 1
|
||||
UPDATE = 2
|
||||
END = 3
|
||||
|
||||
|
||||
class Slot:
|
||||
index = 0
|
||||
state = SlotState.NONE
|
||||
x = 0
|
||||
y = 0
|
||||
dx = 0
|
||||
dy = 0
|
||||
dirty = False
|
||||
|
||||
|
||||
class InputEvent:
|
||||
def __init__(self, data):
|
||||
self.sec = data[0]
|
||||
self.usec = data[1]
|
||||
self.evtype = data[2]
|
||||
self.evcode = data[3]
|
||||
self.value = data[4]
|
||||
|
||||
|
||||
def main(argv):
|
||||
global COLOR_RESET
|
||||
global COLOR_RED
|
||||
|
||||
slots = []
|
||||
xres, yres = 1, 1
|
||||
|
||||
parser = argparse.ArgumentParser(description="Measure delta between event frames for each slot")
|
||||
parser.add_argument("--use-mm", action='store_true', help="Use mm instead of device deltas")
|
||||
parser.add_argument("--use-st", action='store_true', help="Use ABS_X/ABS_Y instead of ABS_MT_POSITION_X/Y")
|
||||
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")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not sys.stdout.isatty():
|
||||
COLOR_RESET = ''
|
||||
COLOR_RED = ''
|
||||
|
||||
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
|
||||
|
||||
if args.use_st:
|
||||
nslots = 1
|
||||
|
||||
slots = [Slot() for _ 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:
|
||||
xres = 1.0 * absinfo[libevdev.EV_ABS.ABS_X.value][4]
|
||||
yres = 1.0 * absinfo[libevdev.EV_ABS.ABS_Y.value][4]
|
||||
if not xres or not yres:
|
||||
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")
|
||||
|
||||
slot = 0
|
||||
last_time = None
|
||||
|
||||
for event in device['events']:
|
||||
for evdev in event['evdev']:
|
||||
s = slots[slot]
|
||||
e = InputEvent(evdev)
|
||||
evbit = libevdev.evbit(e.evtype, e.evcode)
|
||||
|
||||
if args.use_st:
|
||||
# Note: this relies on the EV_KEY events to come in before the
|
||||
# x/y events, otherwise the last/first event in each slot will
|
||||
# be wrong.
|
||||
if (evbit == libevdev.EV_KEY.BTN_TOOL_FINGER or
|
||||
evbit == libevdev.EV_KEY.BTN_TOOL_PEN):
|
||||
slot = 0
|
||||
s = slots[slot]
|
||||
s.dirty = True
|
||||
if e.value:
|
||||
s.state = SlotState.BEGIN
|
||||
else:
|
||||
s.state = SlotState.END
|
||||
elif evbit == libevdev.EV_KEY.BTN_TOOL_DOUBLETAP:
|
||||
if len(slots) > 1:
|
||||
slot = 1
|
||||
s = slots[slot]
|
||||
s.dirty = True
|
||||
if e.value:
|
||||
s.state = SlotState.BEGIN
|
||||
else:
|
||||
s.state = SlotState.END
|
||||
elif evbit == libevdev.EV_ABS.ABS_X:
|
||||
if s.state == SlotState.UPDATE:
|
||||
s.dx = e.value - s.x
|
||||
s.x = e.value
|
||||
s.dirty = True
|
||||
elif evbit == libevdev.EV_ABS.ABS_Y:
|
||||
if s.state == SlotState.UPDATE:
|
||||
s.dy = e.value - s.y
|
||||
s.y = e.value
|
||||
s.dirty = True
|
||||
else:
|
||||
if evbit == libevdev.EV_ABS.ABS_MT_SLOT:
|
||||
slot = e.value
|
||||
s = slots[slot]
|
||||
s.dirty = True
|
||||
elif evbit == libevdev.EV_ABS.ABS_MT_TRACKING_ID:
|
||||
if e.value == -1:
|
||||
s.state = SlotState.END
|
||||
else:
|
||||
s.state = SlotState.BEGIN
|
||||
s.dx = 0
|
||||
s.dy = 0
|
||||
s.dirty = True
|
||||
elif evbit == libevdev.EV_ABS.ABS_MT_POSITION_X:
|
||||
if s.state == SlotState.UPDATE:
|
||||
s.dx = e.value - s.x
|
||||
s.x = e.value
|
||||
s.dirty = True
|
||||
elif evbit == libevdev.EV_ABS.ABS_MT_POSITION_Y:
|
||||
if s.state == SlotState.UPDATE:
|
||||
s.dy = e.value - s.y
|
||||
s.y = e.value
|
||||
s.dirty = True
|
||||
|
||||
if (evbit == libevdev.EV_KEY.BTN_TOUCH or
|
||||
(evbit == libevdev.EV_KEY.BTN_TOOL_DOUBLETAP and nslots < 2) or
|
||||
(evbit == libevdev.EV_KEY.BTN_TOOL_TRIPLETAP and nslots < 3) or
|
||||
(evbit == libevdev.EV_KEY.BTN_TOOL_QUADTAP and nslots < 4) or
|
||||
(evbit == libevdev.EV_KEY.BTN_TOOL_QUINTTAP and nslots < 5)):
|
||||
print(' {} {} {} {}'.format(marker_button,
|
||||
evbit.name,
|
||||
e.value,
|
||||
marker_button))
|
||||
|
||||
if evbit == libevdev.EV_SYN.SYN_REPORT:
|
||||
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) # ms
|
||||
last_time = t
|
||||
|
||||
print("{:2d}.{:06d} {:+4d}ms: ".format(e.sec, e.usec, tdelta), end='')
|
||||
for sl in slots:
|
||||
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 sl.dx != 0 and sl.dy != 0:
|
||||
t = math.atan2(sl.dx, sl.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 sl.dy == 0:
|
||||
if sl.dx < 0:
|
||||
direction = '←←'
|
||||
else:
|
||||
direction = '→→'
|
||||
else:
|
||||
if sl.dy < 0:
|
||||
direction = '↑↑'
|
||||
else:
|
||||
direction = '↓↓'
|
||||
|
||||
color = COLOR_RESET
|
||||
|
||||
if args.use_mm:
|
||||
sl.dx /= xres
|
||||
sl.dy /= yres
|
||||
if math.hypot(sl.dx, sl.dy) > 7:
|
||||
color = COLOR_RED
|
||||
print("{} {}{:+3.2f}/{:+03.2f}{} | ".format(direction, color, sl.dx, sl.dy, COLOR_RESET), end='')
|
||||
elif args.use_absolute:
|
||||
print("{} {}{:4d}/{:4d}{} | ".format(direction, color, sl.x, sl.y, COLOR_RESET), end='')
|
||||
else:
|
||||
print("{} {}{:4d}/{:4d}{} | ".format(direction, color, sl.dx, sl.dy, COLOR_RESET), end='')
|
||||
s.dx = 0
|
||||
s.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 __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
72
tools/libinput-analyze.c
Normal file
72
tools/libinput-analyze.c
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "shared.h"
|
||||
|
||||
static inline void
|
||||
usage(void)
|
||||
{
|
||||
printf("Usage: libinput analyze [--help] <feature>\n");
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int option_index = 0;
|
||||
|
||||
while (1) {
|
||||
int c;
|
||||
static struct option opts[] = {
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ 0, 0, 0, 0}
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "+h", opts, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch(c) {
|
||||
case 'h':
|
||||
usage();
|
||||
return EXIT_SUCCESS;
|
||||
default:
|
||||
usage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind >= argc) {
|
||||
usage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
argc--;
|
||||
argv++;
|
||||
|
||||
return tools_exec_command("libinput-analyze", argc, argv);
|
||||
}
|
||||
30
tools/libinput-analyze.man
Normal file
30
tools/libinput-analyze.man
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
.TH libinput-analyze "1" "" "libinput @LIBINPUT_VERSION@" "libinput Manual"
|
||||
.SH NAME
|
||||
libinput\-analyze \- analyze device data
|
||||
.SH SYNOPSIS
|
||||
.B libinput analyze [\-\-help] \fI<feature> [<args>]\fR
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The
|
||||
.B "libinput analyze"
|
||||
tool analyzes device data. Depending on what is to
|
||||
be analyzed, this tool may not create a libinput context.
|
||||
.PP
|
||||
This is a debugging tool only, its output may change at any time. Do not
|
||||
rely on the output.
|
||||
.PP
|
||||
This tool may need to be run as root to have access to the
|
||||
/dev/input/eventX nodes.
|
||||
.SH OPTIONS
|
||||
.TP 8
|
||||
.B \-\-help
|
||||
Print help
|
||||
.SH FEATURES
|
||||
Features that can be analyzed include
|
||||
.TP 8
|
||||
.B libinput\-analyze\-per-slot-delta(1)
|
||||
analyze the delta per event per slot
|
||||
.SH LIBINPUT
|
||||
Part of the
|
||||
.B libinput(1)
|
||||
suite
|
||||
Loading…
Add table
Reference in a new issue