Commit e11bad41 authored by Peter Hutterer's avatar Peter Hutterer

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's avatarPeter Hutterer <peter.hutterer@who-t.net>
parent 927a7c07
Pipeline #119981 passed with stages
in 27 minutes and 26 seconds
......@@ -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',
......
.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
#!/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)
/*
* 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);
}
.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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment