Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
57 lines
3.6 KiB
Python
57 lines
3.6 KiB
Python
"""Bounded read-only UI capture of one exact private Workbench run; no input API."""
|
|
from pathlib import Path
|
|
import hashlib,importlib.util,json,os,re,sys,time
|
|
sys.path.insert(0,'/home/jakub/devel/escapistsrereintroduced/tools')
|
|
from headless_display_probe import process,environment,ancestor,identity,owned_sockets,validate_display,executable_argument,X11
|
|
profile=Path('/home/jakub/devel/escapistsrereintroduced/.build/objective-map-profile-0832-final')
|
|
out=Path('/home/jakub/devel/escapistsrereintroduced/.build/objective-map-final-ui-0832');out.mkdir(exist_ok=False)
|
|
prefix='/tmp/erri-headless-compat/pfx'
|
|
executable=Path('/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger Tools/Workbench/ArmaReforgerWorkbenchSteamDiag.exe')
|
|
spec=importlib.util.spec_from_file_location('capture','/tmp/erri-private-window-capture.py')
|
|
capture=importlib.util.module_from_spec(spec);spec.loader.exec_module(capture)
|
|
pending={'standard-long-warning','standard-rough','standard-exact','standard-reopened','shared-root','shared-long-warning','shared-reused-root'}
|
|
records=[];deadline=time.monotonic()+240
|
|
while pending and time.monotonic()<deadline:
|
|
logs=list((profile/'logs').glob('logs_*/script.log'))
|
|
text=logs[0].read_text(errors='replace') if len(logs)==1 else ''
|
|
checkpoints=re.findall(r'ERR OBJECTIVE CAPTURE READY: ([a-z-]+)',text)
|
|
ready=[checkpoints[-1]] if checkpoints and checkpoints[-1] in pending else []
|
|
if ready:
|
|
for entry in Path('/proc').iterdir():
|
|
if not entry.name.isdigit():continue
|
|
try:
|
|
p=process(int(entry.name))
|
|
if p['uid']!=os.getuid() or not executable_argument(p['args'],executable):continue
|
|
if p['args'].count('-profile')!=1:continue
|
|
if p['args'][p['args'].index('-profile')+1:p['args'].index('-profile')+2]!=['Z:'+str(profile)]:continue
|
|
env=environment(p['pid'])
|
|
if env.get('WINEPREFIX','').rstrip('/')!=prefix:continue
|
|
display=env.get('DISPLAY','');number=validate_display(display,os.environ.get('DISPLAY',''))
|
|
scope=ancestor(p['pid']);sockets=owned_sockets(number,scope['pid'])
|
|
if environment(scope['pid']).get('WINEPREFIX','').rstrip('/')!=prefix:continue
|
|
query=X11(display)
|
|
try:
|
|
windows=[]
|
|
for window in query.windows():
|
|
if window['pid']!=p['pid'] or window['server_pid']!=p['pid'] or not window['title'].startswith('World Editor'):continue
|
|
attrs=query.attributes(window['window'])
|
|
if attrs['map_state']==2 and attrs['window_class']==1:windows.append((attrs['width']*attrs['height'],window))
|
|
if not windows:continue
|
|
chosen=max(windows,key=lambda item:item[0])[1]
|
|
assert chosen['window']>0
|
|
assert identity(process(p['pid']))==identity(p) and identity(process(scope['pid']))==identity(scope)
|
|
assert owned_sockets(number,scope['pid'])==sockets
|
|
for name in ready:
|
|
path=out/(name+'.png');capture.capture(display,str(path),chosen['window'])
|
|
latest=re.findall(r'ERR OBJECTIVE CAPTURE READY: ([a-z-]+)',logs[0].read_text(errors='replace'))
|
|
assert latest and latest[-1]==name, 'Capture crossed native stage boundary'
|
|
record={'stage':name,'sha256':hashlib.sha256(path.read_bytes()).hexdigest(),'workbench':identity(p),'gamescope':identity(scope),'display':display,'window':chosen,'sockets':sockets,'profile':str(profile),'read_only':True}
|
|
records.append(record);pending.remove(name);print(name,flush=True)
|
|
(out/'captures.json').write_text(json.dumps(records,indent=2)+'\n')
|
|
finally:query.close()
|
|
break
|
|
except (FileNotFoundError,ProcessLookupError,PermissionError,ValueError):continue
|
|
if 'ERR SMOKE FAIL:' in text or 'ERR SMOKE COMPLETE:' in text:break
|
|
time.sleep(.1)
|
|
if pending:raise RuntimeError('Missing bounded UI captures: '+str(sorted(pending)))
|