66 lines
4.4 KiB
Python
66 lines
4.4 KiB
Python
"""EXPERIMENTAL adapter; inherited bounded process/socket cleanup stays intact."""
|
|
import argparse
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
from campaign import Campaign
|
|
from scenario import SCENARIO
|
|
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument('lab', type=Path)
|
|
p.add_argument('mode', choices=('control', 'private_standard', 'private_mosaic'))
|
|
a = p.parse_args()
|
|
lab = a.lab.resolve()
|
|
repo = Path(__file__).resolve().parents[3]
|
|
# WHY: one lock covers all modes, dynamic reservations and complete child cleanup.
|
|
# CONTRACT: repeated/failed runs cannot silently replenish campaign budgets.
|
|
# INVARIANT: no hide/return camera pair is generated by this adapter.
|
|
campaign = Campaign(lab)
|
|
campaign.begin(a.mode)
|
|
s = (repo / 'tooling/probes/step20c-applied-state-input/run.py').read_text()
|
|
s = s.replace("f'{args.mode}-{args.kind}-asi{args.asi_mode}-{time.time_ns()}'",
|
|
"f'{args.mode}-dynamic-{time.time_ns()}'")
|
|
s = s.replace('output HEADLESS-1 resolution 800x600 position -800 0',
|
|
'output HEADLESS-1 resolution 1000x750 scale 1.25 position -800 0')
|
|
s = s.replace("Path('/usr/bin/sway')", "lab / 'standard-build/sway/sway'")
|
|
s = s.replace('import argparse', 'import argparse\nfrom PIL import Image\nimport sys\nsys.path.insert(0, '+repr(str(Path(__file__).parent.resolve()))+')\nfrom campaign import reserve')
|
|
s = s.replace("run.mkdir(parents=True, mode=0o700)", "run.mkdir(parents=True, mode=0o700)\n(run / 'fixture-source.py').write_text(Path(__file__).read_text())")
|
|
start = s.index(" expected = {'HEADLESS-1':")
|
|
end = s.index(" for output, side in", start)
|
|
s = s[:start]+''' expected = {'HEADLESS-1': (-960, 0, 960, 600) if globals().get('dynamic_stage') == 'D1' else (-800, 0, 800, 600), 'HEADLESS-2': (0, 0, 800, 600)}
|
|
actual = {o['name']: tuple(o['rect'][k] for k in ('x','y','width','height')) for o in outputs if o['active']}
|
|
if actual != expected or any(o['transform'] != 'normal' or o['scale'] != (1.25 if o['name']=='HEADLESS-1' else 1) for o in outputs if o['active']):
|
|
raise RuntimeError(f'output geometry/scale/transform mismatch: {actual}')
|
|
record('state', tag=tag, workspaces=ipc('-t','get_workspaces'), workspace=workspace(), owner=owner(), outputs=outputs)
|
|
'''+s[end:]
|
|
s = s.replace("['grim', '-o', output, str(path)]", "['grim', '-s', str(next(o['scale'] for o in outputs if o['name']==output)), '-o', output, str(path)]")
|
|
s = s.replace(" record('capture', tag=tag", """ out = next(o for o in outputs if o['name']==output)
|
|
dimensions = list(Image.open(path).size)
|
|
if dimensions != [out['current_mode']['width'],out['current_mode']['height']]:
|
|
raise RuntimeError('physical PNG/mode mismatch')
|
|
record('capture_geometry',tag=tag,output=output,dimensions=dimensions,rect=out['rect'],scale=out['scale'],transform=out['transform'],mode=out['current_mode'])
|
|
record('capture', tag=tag""")
|
|
start=s.index('def inject(action, x, y):');end=s.index('\ndef client_command',start)
|
|
s=s[:start]+'''def inject(action, x, y):
|
|
outputs = [o for o in ipc('-t','get_outputs') if o['active']]
|
|
min_x = min(o['rect']['x'] for o in outputs)
|
|
min_y = min(o['rect']['y'] for o in outputs)
|
|
max_x = max(o['rect']['x']+o['rect']['width'] for o in outputs)
|
|
max_y = max(o['rect']['y']+o['rect']['height'] for o in outputs)
|
|
if not any(o['rect']['x']<=x<o['rect']['x']+o['rect']['width'] and o['rect']['y']<=y<o['rect']['y']+o['rect']['height'] for o in outputs):
|
|
raise ValueError('global point is outside applied outputs')
|
|
sent_x,sent_y = int(x-min_x),int(y-min_y)
|
|
width,height = max_x-min_x,max_y-min_y
|
|
marker=f'INJECT_DONE {action} {sent_x},{sent_y}'
|
|
before=(run/'pointer.log').read_text().count(marker)
|
|
record('pointer_request',action=action,global_point=[x,y],sent=[sent_x,sent_y],extent=[width,height],minimum=[min_x,min_y])
|
|
pointer.stdin.write(f'{action} {sent_x} {sent_y} {width} {height}\\n');pointer.stdin.flush()
|
|
wait(lambda: (run/'pointer.log').read_text().count(marker)>before)
|
|
|
|
'''+s[end:]
|
|
start=s.index(" if args.kind == 'cycles':\n");end=s.index('except Exception as exc:',start)
|
|
scenario=SCENARIO
|
|
s=s[:start]+scenario+'\n'+s[end:]
|
|
generated=lab/f'dynamic-{a.mode}.py'
|
|
generated.write_text(s)
|
|
raise SystemExit(subprocess.call([sys.executable,str(generated),str(lab),a.mode,'cycles','--max-seconds','240','--max-captures','100','--max-bytes','60000000'], pass_fds=(campaign.lock.fileno(),)))
|