95 lines
3.9 KiB
Python
95 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Check a fresh, physical arrows-first Steam Big Picture run of Cthulhu."""
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
def check_log(path: Path) -> None:
|
|
records = [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
|
|
starts = [index for index, record in enumerate(records)
|
|
if record.get("message") == "starting Cthulhu"]
|
|
if not starts:
|
|
raise ValueError("no Cthulhu startup in log")
|
|
records = records[starts[-1]:]
|
|
|
|
steam_pid = None
|
|
steam_document = False
|
|
first_link = None
|
|
first_caret = None
|
|
tab_focus = False
|
|
home = power = confirm = cancel = False
|
|
links_before_arrow = set()
|
|
for record in records:
|
|
message = record.get("message")
|
|
if message == "X11 foreground ownership changed":
|
|
pid = record.get("pid") or 0
|
|
if first_link is not None and pid and pid != steam_pid and not cancel:
|
|
raise ValueError("foreground left Steam before the dialog check finished")
|
|
if first_link is None and pid:
|
|
steam_pid = pid
|
|
continue
|
|
if message == "suspended for XTerm focus handoff" and first_link is not None and not cancel:
|
|
raise ValueError("focus switched away from Steam before the dialog check finished")
|
|
if (message == "delivering accessible presentation" and
|
|
record.get("role") == "document web" and
|
|
record.get("utterance", "").startswith("Steam Big Picture Mode")):
|
|
steam_document = True
|
|
if message == "document caret navigation completed" and record.get("moved") is True:
|
|
if first_link is not None and first_caret is None:
|
|
first_caret = record.get("time")
|
|
continue
|
|
if message != "delivering accessible presentation" or record.get("cause") != "object:state-changed:focused":
|
|
continue
|
|
role = record.get("role")
|
|
utterance = record.get("utterance", "")
|
|
if role == "link" and utterance and steam_document:
|
|
if first_link is None:
|
|
first_link = record.get("time")
|
|
links_before_arrow.add(utterance)
|
|
elif first_caret is None:
|
|
links_before_arrow.add(utterance)
|
|
else:
|
|
tab_focus = True
|
|
if first_caret is None:
|
|
continue
|
|
if role == "menu item" and utterance == "Home, menu item":
|
|
home = True
|
|
elif role == "menu item" and utterance == "Power, menu item":
|
|
power = home
|
|
elif role == "button" and utterance == "Confirm, button":
|
|
confirm = power
|
|
elif role == "button" and utterance == "Cancel, button":
|
|
cancel = confirm
|
|
if cancel:
|
|
break
|
|
|
|
if not steam_document or first_link is None:
|
|
raise ValueError("Steam never exposed its document and an initial focused link")
|
|
if len(links_before_arrow) != 1:
|
|
raise ValueError("focus moved between links before the first browse arrow; test arrows first")
|
|
if first_caret is None:
|
|
raise ValueError("no successful browse arrow after Steam became ready")
|
|
if not tab_focus:
|
|
raise ValueError("no focused link after the first arrow and Tab")
|
|
if not home or not power:
|
|
raise ValueError("Escape did not expose Home and Power")
|
|
if not confirm or not cancel:
|
|
raise ValueError("Power did not expose both Confirm and Cancel buttons")
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("log", type=Path, help="fresh Cthulhu debug log")
|
|
arguments = parser.parse_args()
|
|
try:
|
|
check_log(arguments.log)
|
|
except (OSError, ValueError, json.JSONDecodeError) as error:
|
|
parser.exit(1, f"Steam Big Picture check failed: {error}\n")
|
|
print("Steam Big Picture arrows-first focus, menu, and dialog check passed")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|