• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8
3# vim: set expandtab shiftwidth=4:
4# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
5#
6# Copyright © 2020 Red Hat, Inc.
7#
8# Permission is hereby granted, free of charge, to any person obtaining a
9# copy of this software and associated documentation files (the 'Software'),
10# to deal in the Software without restriction, including without limitation
11# the rights to use, copy, modify, merge, publish, distribute, sublicense,
12# and/or sell copies of the Software, and to permit persons to whom the
13# Software is furnished to do so, subject to the following conditions:
14#
15# The above copyright notice and this permission notice (including the next
16# paragraph) shall be included in all copies or substantial portions of the
17# Software.
18#
19# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
22# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25# DEALINGS IN THE SOFTWARE.
26#
27#
28# Prints the down/up state of each touch slot
29#
30# Input is a libinput record yaml file
31
32import argparse
33import enum
34import sys
35import yaml
36import libevdev
37
38
39class Slot:
40    class State(enum.Enum):
41        NONE = "NONE"
42        BEGIN = "BEGIN"
43        UPDATE = "UPDATE"
44        END = "END"
45
46    def __init__(self, index):
47        self._state = Slot.State.NONE
48        self.index = index
49        self.used = False
50
51    def begin(self):
52        assert self.state == Slot.State.NONE
53        self.state = Slot.State.BEGIN
54
55    def end(self):
56        assert self.state in (Slot.State.BEGIN, Slot.State.UPDATE)
57        self.state = Slot.State.END
58
59    def sync(self):
60        if self.state == Slot.State.BEGIN:
61            self.state = Slot.State.UPDATE
62        elif self.state == Slot.State.END:
63            self.state = Slot.State.NONE
64
65    @property
66    def state(self):
67        return self._state
68
69    @state.setter
70    def state(self, newstate):
71        assert newstate in Slot.State
72
73        if newstate != Slot.State.NONE:
74            self.used = True
75        self._state = newstate
76
77    @property
78    def is_active(self):
79        return self.state in (Slot.State.BEGIN, Slot.State.UPDATE)
80
81    def __str__(self):
82        return "+" if self.state in (Slot.State.BEGIN, Slot.State.UPDATE) else " "
83
84
85def main(argv):
86    parser = argparse.ArgumentParser(description="Print the state of touches over time")
87    parser.add_argument(
88        "--use-st", action="store_true", help="Ignore slots, use the BTN_TOOL bits"
89    )
90    parser.add_argument(
91        "path", metavar="recording", nargs=1, help="Path to libinput-record YAML file"
92    )
93    args = parser.parse_args()
94
95    yml = yaml.safe_load(open(args.path[0]))
96    device = yml["devices"][0]
97    absinfo = device["evdev"]["absinfo"]
98    try:
99        nslots = absinfo[libevdev.EV_ABS.ABS_MT_SLOT.value][1] + 1
100    except KeyError:
101        args.use_st = True
102
103    tool_slot_map = {
104        libevdev.EV_KEY.BTN_TOOL_FINGER: 0,
105        libevdev.EV_KEY.BTN_TOOL_PEN: 0,
106        libevdev.EV_KEY.BTN_TOOL_DOUBLETAP: 1,
107        libevdev.EV_KEY.BTN_TOOL_TRIPLETAP: 2,
108        libevdev.EV_KEY.BTN_TOOL_QUADTAP: 3,
109        libevdev.EV_KEY.BTN_TOOL_QUINTTAP: 4,
110    }
111    if args.use_st:
112        for bit in tool_slot_map:
113            if bit.value in device["evdev"]["codes"][libevdev.EV_KEY.value]:
114                nslots = max(nslots, tool_slot_map[bit])
115
116    slots = [Slot(i) for i in range(0, nslots)]
117    # We claim the first slots are used just to make the formatting
118    # more consistent
119    for i in range(min(5, len(slots))):
120        slots[i].used = True
121
122    slot = 0
123    last_time = None
124    last_slot_state = None
125    header = "Timestamp | Rel time |     Slots     |"
126    print(header)
127    print("-" * len(header))
128
129    def events():
130        for event in device["events"]:
131            for evdev in event["evdev"]:
132                yield evdev
133
134    for evdev in events():
135        e = libevdev.InputEvent(
136            code=libevdev.evbit(evdev[2], evdev[3]),
137            value=evdev[4],
138            sec=evdev[0],
139            usec=evdev[1],
140        )
141
142        # single-touch formatting is simpler than multitouch, it'll just
143        # show the highest finger down rather than the correct output.
144        if args.use_st:
145            if e.code in tool_slot_map:
146                slot = tool_slot_map[e.code]
147                s = slots[slot]
148                if e.value:
149                    s.begin()
150                else:
151                    s.end()
152        else:
153            if e.code == libevdev.EV_ABS.ABS_MT_SLOT:
154                slot = e.value
155                s = slots[slot]
156                # bcm5974 cycles through slot numbers, so let's say all below
157                # our current slot number was used
158                for sl in slots[: slot + 1]:
159                    sl.used = True
160            else:
161                s = slots[slot]
162                if e.code == libevdev.EV_ABS.ABS_MT_TRACKING_ID:
163                    if e.value == -1:
164                        s.end()
165                    else:
166                        s.begin()
167                elif e.code in (
168                    libevdev.EV_ABS.ABS_MT_POSITION_X,
169                    libevdev.EV_ABS.ABS_MT_POSITION_Y,
170                    libevdev.EV_ABS.ABS_MT_PRESSURE,
171                    libevdev.EV_ABS.ABS_MT_TOUCH_MAJOR,
172                    libevdev.EV_ABS.ABS_MT_TOUCH_MINOR,
173                ):
174                    # If recording started after touch down
175                    if s.state == Slot.State.NONE:
176                        s.begin()
177
178        if e.code == libevdev.EV_SYN.SYN_REPORT:
179            current_slot_state = tuple(s.is_active for s in slots)
180
181            if current_slot_state != last_slot_state:
182                if last_time is None:
183                    last_time = e.sec * 1000000 + e.usec
184                    tdelta = 0
185                else:
186                    t = e.sec * 1000000 + e.usec
187                    tdelta = int((t - last_time) / 1000) / 1000
188                    last_time = t
189
190                fmt = " | ".join([str(s) for s in slots if s.used])
191                print(
192                    "{:2d}.{:06d} | {:+7.3f}s | {}".format(e.sec, e.usec, tdelta, fmt)
193                )
194
195                last_slot_state = current_slot_state
196
197            for s in slots:
198                s.sync()
199
200
201if __name__ == "__main__":
202    main(sys.argv)
203