Last active
January 30, 2023 20:15
-
-
Save jigpu/65535216a6c010b1ad0d3e99d20b9005 to your computer and use it in GitHub Desktop.
Convert binary Linux kernel input events (e.g. from /dev/input/eventN) into a human-readable list of events.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# Convert binary Linux kernel input events (e.g. from /dev/input/eventN) | |
# into a human-readable list of events. Takes input from stdin. | |
# | |
# Only supports a limited number of event types/codes, but can be extended | |
# without much effort. | |
# | |
# Usage: `sudo cat /dev/input/event<N> | ./event-bin-to-text.py` | |
# | |
# ************ | |
# | |
# The following is an example of how to dump events from a Wacom stylus to | |
# a temporary file and then replay them through this script. First, we need | |
# to know what device node to capture from. The first two commands below | |
# find the X11 device number (20) for our stylus, and then find the kernel | |
# device node associated with it (/dev/input/event26). After that, we use | |
# `cat` to dump the events from the device to a temporary file for later | |
# replay. We move the pen around a little and then press CTRL+C to quit | |
# the dump. Finally, we `cat` the saved dump into this script for review. | |
# | |
# ~~~ | |
# $ xinput list | grep stylus | |
# ⎜ ↳ Wacom Intuos Pro M Pen stylus id=20 [slave pointer (2)] | |
# | |
# $ xinput list-props 20 | grep "Device Node" | |
# Device Node (263): "/dev/input/event26" | |
# | |
# $ sudo cat /dev/input/event26 > /tmp/dump.bin | |
# ^C | |
# | |
# $ cat /tmp/dump.bin | ./event-bin-to-text.py | |
# 1675109136.477377 3 0 14832 --- EV_ABS ABS_X 14832 | |
# 1675109136.477377 3 1 58394 --- EV_ABS ABS_Y 58394 | |
# 1675109136.477377 3 26 -28 --- EV_ABS TILT_X -28 | |
# 1675109136.477377 3 27 27 --- EV_ABS TILT_Y 27 | |
# 1675109136.477377 3 25 40 --- EV_ABS ??? 40 | |
# 1675109136.477377 1 320 1 --- EV_KEY BTN_TOOL_PEN 1 | |
# 1675109136.477377 3 40 2114 --- EV_ABS ABS_MISC 2114 | |
# 1675109136.477377 0 0 0 --- EV_SYN SYN_REPORT 0 ### dt = ??? ms | |
# 1675109136.483354 3 0 14875 --- EV_ABS ABS_X 14875 | |
# 1675109136.483354 3 1 58415 --- EV_ABS ABS_Y 58415 | |
# 1675109136.483354 3 27 26 --- EV_ABS TILT_Y 26 | |
# 1675109136.483354 3 25 39 --- EV_ABS ??? 39 | |
# 1675109136.483354 0 0 0 --- EV_SYN SYN_REPORT 0 ### dt = 5.977 ms | |
# 1675109136.487371 3 0 14990 --- EV_ABS ABS_X 14990 | |
# 1675109136.487371 3 1 58487 --- EV_ABS ABS_Y 58487 | |
# 1675109136.487371 3 24 1971 --- EV_ABS ??? 1971 | |
# 1675109136.487371 1 330 1 --- EV_KEY ??? 1 | |
# 1675109136.487371 4 0 1736442911 --- ??? ??? 1736442911 | |
# 1675109136.487371 0 0 0 --- EV_SYN SYN_REPORT 0 ### dt = 4.017 ms | |
# ~~~ | |
# | |
import struct | |
import sys | |
event_fmt = "<QQHHi" | |
event_len = struct.calcsize(event_fmt) | |
event_unpack = struct.Struct(event_fmt).unpack_from | |
last_time_micros = None | |
def calculate_dt_millis(current_s, current_us): | |
global last_time_micros | |
now_micros = current_s * 1000000 + current_us | |
if last_time_micros is None: | |
last_time_micros = now_micros | |
return None | |
dt_micros = now_micros - last_time_micros | |
last_time_micros = now_micros | |
return dt_micros / 1000 | |
def decode_name(typeval, codeval): | |
return lookup_dict.get((typeval, codeval), "???") | |
def parse_and_dump_one(event): | |
s = event_unpack(event) | |
type_name = decode_name(s[2], None) | |
code_name = decode_name(s[2], s[3]) | |
is_syn_report = s[2] == 0 and s[3] == 0 | |
if is_syn_report: | |
dt_millis = calculate_dt_millis(s[0], s[1]) | |
if dt_millis is None: dt_millis = "???" | |
else: | |
dt_millis = None | |
fmt_str = "{}.{:06}\t{}\t{}\t{:>10}\t---\t{:>8}\t{:>12}\t{:>10}" | |
print_str = fmt_str.format(*s, type_name, code_name, s[4]) | |
if dt_millis is not None: | |
print_str += "\t### dt = {} ms".format(dt_millis) | |
print(print_str) | |
def read_one(stream): | |
event = b'' | |
while len(event) != event_len: | |
nbytes = event_len - len(event) | |
if nbytes > 3: nbytes = 3 | |
data = stream.read(nbytes) | |
if not data: return None | |
event += data | |
return event | |
def main(): | |
while True: | |
event = read_one(sys.stdin.buffer) | |
if event is None: break | |
parse_and_dump_one(event) | |
# https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/input-event-codes.h | |
lookup_dict = { | |
(0, None): "EV_SYN", | |
(0, 0x00): "SYN_REPORT", | |
(1, None): "EV_KEY", | |
(1, 0x140): "BTN_TOOL_PEN", | |
(3, None): "EV_ABS", | |
(3, 0x00): "ABS_X", | |
(3, 0x01): "ABS_Y", | |
(3, 0x1A): "TILT_X", | |
(3, 0x1B): "TILT_Y", | |
(3, 0x28): "ABS_MISC", | |
(3, 0x29): "ABS_MISC+1", | |
(3, 0x2A): "ABS_MISC+2", | |
(3, 0x2B): "ABS_MISC+3", | |
(3, 0x2C): "ABS_MISC+4", | |
} | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment