#!/usr/bin/env python3 """Drive Xenia Canary's `--hid=file` pad — the container-safe controller. Replaces the old `vgamepad` path, which created its device through `/dev/uinput`. Input devices are not namespaced, so that device registered with the HOST's input stack and every scripted press leaked to the user's desktop. This one writes a text file the emulator polls; nothing leaves the container. pad.py set "press=A" set the pad state and leave it held pad.py clear release everything pad.py tap A [secs] press, hold `secs` (default 0.10), release pad.py dpad down [secs] one menu step (default 0.06 — longer auto-repeats and overshoots) pad.py hold "lt=255" secs hold an arbitrary state for `secs` Buttons: UP DOWN LEFT RIGHT START BACK LS RS LB RB A B X Y. Path from $XENIA_PAD_FILE, default /tmp/xenia_pad.txt (matches --pad_file). Menu conventions in this game: A = OK, B = Back, Y = Gallery/extra, X = Delete. """ import os import sys import time PAD = os.environ.get("XENIA_PAD_FILE", "/tmp/xenia_pad.txt") def write(state: str): # The driver re-parses on any (mtime-ns, size) change, so a plain rewrite is # enough — but write through a temp + rename so a poll can never observe a # half-written file. tmp = PAD + ".tmp" with open(tmp, "w") as f: f.write(state) os.replace(tmp, PAD) def main(): a = sys.argv[1:] if not a: print(__doc__) return 1 cmd = a[0] if cmd == "set": write(a[1]) elif cmd == "clear": write("") elif cmd == "tap": secs = float(a[2]) if len(a) > 2 else 0.10 write(f"press={a[1]}") time.sleep(secs) write("") elif cmd == "dpad": secs = float(a[2]) if len(a) > 2 else 0.06 write(f"press={a[1].upper()}") time.sleep(secs) write("") elif cmd == "hold": write(a[1]) time.sleep(float(a[2])) write("") else: print(f"unknown command {cmd!r}") return 1 return 0 if __name__ == "__main__": raise SystemExit(main())