vdi-cloud/tools/verify-cursor-poc-box.py

121 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""Controlled existing-stream POC; always restores the remote-video cursor toggle.
Only run with cursor-poc-host.ps1's disposable foreground window on Windows.
Shape selections here are scripted replay, NOT automatic host synchronization.
"""
import importlib.util
import json
import os
from pathlib import Path
import signal
import subprocess as S
import time
ROOT = Path(__file__).resolve().parent
os.environ.update(DISPLAY=":0", XAUTHORITY="/home/armbian/.Xauthority")
spec = importlib.util.spec_from_file_location("cursor_poc", ROOT / "cursor-poc-x11.py")
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
OUT = ROOT / "cursor-poc-evidence-v3"
OUT.mkdir(exist_ok=False) # Never overwrite earlier evidence.
def xd(*args):
return S.check_output(["xdotool", *map(str,args)], text=True).strip()
pid = int(xd("getwindowfocus", "getwindowpid"))
window = int(xd("getwindowfocus"))
cmd = Path(f"/proc/{pid}/cmdline").read_bytes().replace(b"\0", b" ")
if b"/moonlight " not in cmd or b" stream 192.168.1.70 Desktop " not in cmd or b" --absolute-mouse " not in cmd:
raise RuntimeError("Unexpected foreground process")
assets = m.validate_assets(m.read_json(ROOT / "windows-shapes.json", 4*1024*1024))
cursor = m.LocalCursor(window)
if any(cursor.snapshot()["argb"]):
cursor.close(); raise RuntimeError("Original local cursor is not hidden")
hidden_remote = False
paused = False
records = []
def interrupted(signum, frame):
raise SystemExit(128+signum)
signal.signal(signal.SIGTERM, interrupted)
signal.signal(signal.SIGINT, interrupted)
def focus():
if int(xd("getwindowfocus", "getwindowpid")) != pid:
raise RuntimeError("Moonlight lost foreground; stop the POC")
def capture(name):
with (OUT / (name+".xwd")).open("wb") as f:
S.run(["xwd", "-root", "-silent"], stdout=f, check=True)
try:
cursor.cursors = {k: cursor.create(v) for k,v in assets.items()}
cursor.cursors["__hidden"] = cursor.create(dict(width=1,height=1,hotX=0,hotY=0,argb=[0]))
focus(); xd("mousemove", 320, 500); time.sleep(.8)
capture("01-remote-cursor")
focus(); xd("key", "ctrl+alt+shift+n"); hidden_remote=True
xd("mousemove",321,500); time.sleep(1.0)
capture("02-video-cursor-disabled")
for name,x in (("arrow",320),("ibeam",960),("sizewe",1600),("wait",2240),("__hidden",320)):
focus(); y=1250 if name=="__hidden" else 500
xd("mousemove", x,y); time.sleep(.25)
cursor.apply(cursor.cursors[name]); time.sleep(.05)
snap=cursor.snapshot()
(OUT/(name+".json")).write_text(json.dumps(snap))
if name=="__hidden":
assert not any(snap["argb"]), "Hidden cursor still has visible pixels"
else:
a=assets[name]
assert (snap["width"],snap["height"],snap["hotX"],snap["hotY"]) == (a["width"],a["height"],a["hotX"],a["hotY"])
assert any(snap["argb"]), "Visible cursor has no pixels"
assert (snap["x"],snap["y"])==(x,y)
records.append(dict(shape=name,x=x,y=y,visible=any(snap["argb"])))
time.sleep(.3) # Allow the host probe to observe the real shape/visibility.
focus(); xd("mousemove",320,500); cursor.apply(cursor.cursors["arrow"]); time.sleep(.4)
# Freeze only this client for < 0.5s; X11 cursor must move while its video/input
# event processing is stopped. SIGCONT is guaranteed before cursor restoration.
os.kill(pid,signal.SIGSTOP); paused=True
try:
first=cursor.snapshot()
xd("mousemove",500,650); time.sleep(.12)
second=cursor.snapshot()
assert (second["x"],second["y"])==(500,650)
assert first["argb"]==second["argb"]
records.append(dict(test="client-paused-local-motion",before=[first["x"],first["y"]],after=[second["x"],second["y"]],passed=True))
finally:
os.kill(pid,signal.SIGCONT); paused=False
time.sleep(.5)
# Click only within the disposable Windows form. Scripted shape replay is
# deliberately distinct from the not-yet-implemented live cursor protocol.
for i in range(32):
focus(); n=i%4; x=320+640*n; y=600+(i//4)*20
cursor.apply(cursor.cursors[("arrow","ibeam","sizewe","wait")[n]])
xd("mousemove",x,y); time.sleep(.06); xd("click",1); time.sleep(.06)
capture("03-local-mode-video-only")
# Verify the actual helper's lease failure path while video cursor is hidden.
cursor.apply(cursor.original)
lease=OUT/"lease.json"
lease.write_text(json.dumps(dict(version=1,shape="arrow",visible=True,expires=time.time()+.8)))
result=S.run(["python3",str(ROOT/"cursor-poc-x11.py"),"--window",str(window),"--pid",str(pid),
"--assets",str(ROOT/"windows-shapes.json"),"--state",str(lease),"--seconds","3"],
capture_output=True,text=True,timeout=5)
(OUT/"lease-expiry.log").write_text(result.stdout+result.stderr)
assert result.returncode==0 and "lease-ended" in result.stdout and "sdl-hidden-cursor-restored" in result.stdout
# Xorg can briefly have no current cursor image after another connection's
# final cursor resource is released. Trigger pointer recomputation before
# checking the *post-exit* image; do not mistake this for an alpha image.
xd("mousemove",2240,741); time.sleep(.1)
assert not any(cursor.snapshot()["argb"])
records.append(dict(test="helper-lease-expiry",passed=True))
finally:
if paused: os.kill(pid,signal.SIGCONT)
try:
cursor.close(sdl_pid=pid)
finally:
# Remote restoration must still run when local cleanup raises an error.
if hidden_remote:
xd("windowactivate","--sync",window); focus()
xd("key","ctrl+alt+shift+n"); time.sleep(.8)
capture("04-remote-cursor-restored")
(OUT/"results.json").write_text(json.dumps(records,indent=2))
print(json.dumps(dict(restored_remote_cursor=hidden_remote,records=records)),flush=True)