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