libinput-analyze-touch-down-state.py 6.4 KB

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