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