95 lines
3.8 KiB
Python
Executable File
95 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Turns `iris::input` debug lines -- from a phone's diagnostics report, or
|
|
from a report the layer-1 harness produced with tracing on
|
|
(`iris::diagnostics::set_trace(true)`) -- back into a `TouchScript` file
|
|
`iris::harness::Harness::replay` can play back at layer 1.
|
|
|
|
Why this exists: `docs/RUST.md`'s "Three test layers" box says the cheapest
|
|
layer that can answer a question wins, and a gesture that misbehaves on
|
|
Iris's phone is otherwise only describable in words. `iris::sense::
|
|
log_input_event`'s one line per platform event (Android's on_touch_event
|
|
once per `MotionEvent`, with historical samples inline; winit's once per
|
|
pointer `WindowEvent`; the harness's `touch`, once per script line) already
|
|
carries everything a `.touch` file's `t_ms action x y` needs -- this just
|
|
reads it back out and reconstructs the samples in order, expanding each
|
|
event's inline historical samples into their own `move` lines first (they
|
|
are always intermediate positions of a move, and Android documents them as
|
|
oldest first, which is also the order they appear in the line).
|
|
|
|
Usage:
|
|
report_to_touch.py < report.txt > replay.touch
|
|
report_to_touch.py report.txt > replay.touch
|
|
|
|
Only lines containing "iris input: action=..." are read; everything else in
|
|
the report (insets, frame timings, drag-release summaries) is ignored, so
|
|
this can be pointed at Copy report's whole clipboard text directly.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
|
|
# The message half of `sense::log_input_event`'s format string, prefix-
|
|
# agnostic: a real report line also carries the ring's own
|
|
# `HH:MM:SS.mmm LEVEL target:` header (`LogLine::format`) or, forwarded
|
|
# through `ai_server::client_log`, a `[<source> <clock> #<seq>]` tag ahead
|
|
# of that -- neither of which this needs to understand, since `search`
|
|
# (not `match`) finds the marker wherever it starts.
|
|
LINE_RE = re.compile(
|
|
r"iris input: action=(?P<action>\w+) x=(?P<x>-?[0-9.]+) y=(?P<y>-?[0-9.]+) "
|
|
r"t=(?P<t>[0-9]+)ms history=(?P<hist>[0-9]+)(?P<rest>.*)$"
|
|
)
|
|
HIST_RE = re.compile(r"(?P<t>[0-9]+):(?P<x>-?[0-9.]+),(?P<y>-?[0-9.]+)")
|
|
|
|
|
|
def _fmt(value: float) -> str:
|
|
"""The number as `TouchScript::parse`'s own `f32::parse` would round-trip
|
|
it -- an integer without a trailing `.0` where the source was one
|
|
(every coordinate here is a physical pixel), `{:g}` otherwise so a
|
|
fractional value from a real device is not silently truncated."""
|
|
if value == int(value):
|
|
return str(int(value))
|
|
return f"{value:g}"
|
|
|
|
|
|
def convert(lines):
|
|
"""Every `iris::input` line, oldest first, expanded to one `(t_ms,
|
|
action, x, y)` tuple per touch sample -- a historical sample is always
|
|
an intermediate `move`, and the event's own sample keeps its real
|
|
action (`down`/`move`/`up`/`cancel`)."""
|
|
rows = []
|
|
for line in lines:
|
|
m = LINE_RE.search(line)
|
|
if not m:
|
|
continue
|
|
hist_count = int(m.group("hist"))
|
|
hist_matches = list(HIST_RE.finditer(m.group("rest")))
|
|
if len(hist_matches) != hist_count:
|
|
print(
|
|
f"report_to_touch: {line.strip()!r} says history={hist_count} but "
|
|
f"holds {len(hist_matches)} samples -- skipped",
|
|
file=sys.stderr,
|
|
)
|
|
continue
|
|
for hm in hist_matches:
|
|
rows.append(
|
|
(int(hm.group("t")), "move", float(hm.group("x")), float(hm.group("y")))
|
|
)
|
|
rows.append(
|
|
(int(m.group("t")), m.group("action"), float(m.group("x")), float(m.group("y")))
|
|
)
|
|
return rows
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) > 2:
|
|
print("usage: report_to_touch.py [report.txt] < report.txt", file=sys.stderr)
|
|
return 2
|
|
text = open(sys.argv[1]) if len(sys.argv) == 2 else sys.stdin
|
|
for t_ms, action, x, y in convert(text):
|
|
print(f"{t_ms} {action} {_fmt(x)} {_fmt(y)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|