#!/usr/bin/env python3 """One immutable-static headless process, two genuinely grabbed XDG chains. WHY: outside dismissal must not preclude standard-return measurements. CONTRACT: every mutation/injection/capture consumes a persistent reservation. INVARIANT: only owned groups/sockets; actual codes survive terminal IPC failure. """ import argparse import fcntl import json import os from pathlib import Path import re import shutil import signal import subprocess import tempfile import threading import time from PIL import Image from support import OWNER,BINARY,BINARY_SHA,WLROOTS_SHA,LIMITS,sha,save,reserve,environment,verify from ipc_predicates import nodes,workspace,validate_outputs,validate_reference,measure_owner,scene_from_log,validate_camera from predicates import Defect,COLORS,paint,point,equal,reference,protocol,geometry,mapped,pair,dismissal,root_clip,popup_pixels,terminal p=argparse.ArgumentParser();p.add_argument('lab',type=Path);lab=p.parse_args().lab.resolve() assert not __import__('sys').flags.optimize,'assertions required' assert OWNER.read_text().strip()==str(lab) and (lab/'campaign-owner').read_text().strip()==str(OWNER) lock=(lab/'run.lock').open('a+');fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB) freeze=verify(lab/'results/fixture-freeze-sha256.txt');gate=json.loads((lab/'results/offline-gate.json').read_text()) assert gate['marker']=='STATIC_NESTED_GRAB_FIXTURE_PREFLIGHT=PASS' and gate['fixture_sha256']==freeze['sha256'] assert json.loads((lab/'results/binary-identity.json').read_text())['status']=='PASS' assert sha(BINARY)==sha(lab/'swayfx-mosaic')==BINARY_SHA assert not (lab/'run').exists(),'run path already used' reserve(lab,'run') run=lab/'run';run.mkdir();runtime=Path(tempfile.mkdtemp(prefix='msng-',dir='/tmp'));runtime.chmod(0o700) assert len(os.fsencode(str(runtime/'sway-ipc.4294967295.2147483647.sock')))<108 (run/'runtime-path').write_text(str(runtime)+'\n') config='output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0\noutput HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\n' (run/'config').write_text(config) env=environment();env.update(XDG_RUNTIME_DIR=str(runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2',WLR_RENDERER='pixman') processes=[];files=[];comp=None;pointer=None;failure=None;stage='startup';measures=False;rescue=False started=time.monotonic();finished=threading.Event();expired=threading.Event();chains={};clips={};captures=[];clicks=[];owners={};camera_rows=[] def event(kind,**data): with (run/'events.jsonl').open('a') as out:out.write(json.dumps(dict(ns=time.monotonic_ns(),kind=kind,**data))+'\n') def spawn(name,argv,debug=False): assert not expired.is_set() out=(run/(name+'.log')).open('w');files.append(out) proc=subprocess.Popen(list(map(str,argv)),stdin=subprocess.PIPE,stdout=out,stderr=subprocess.STDOUT, text=True,start_new_session=True,env={**env,**({'WAYLAND_DEBUG':'1'} if debug else {})}) processes.append((name,proc));event('spawn',name=name,pid=proc.pid,argv=list(map(str,argv)));return proc def stop_owned(sig=signal.SIGTERM): for name,proc in list(processes): try:os.killpg(proc.pid,sig) except ProcessLookupError:pass def watchdog(): while not finished.wait(.1): if time.monotonic()-started>150 or any(p.stat().st_size>32*1024*1024 for p in run.glob('*.log')): expired.set();stop_owned();return thread=threading.Thread(target=watchdog,daemon=True);thread.start() def log(name):return (run/(name+'.log')).read_text(errors='replace') def wait(fn,seconds=6): end=min(time.monotonic()+seconds,started+143) while time.monotonic()before);time.sleep(.18) event('injection',action=action,global_point=xy,sent=[x+800,y],extent=[1600,600],grab=grab) def buttons(name): return [s for s in log(name).splitlines() if s.startswith(('POINTER_BUTTON ','BUTTON '))] def click(tag,name,xy,grab=False,action='right'): before={n:len(buttons(n)) for n in [name,'reference','filler']} t0=time.monotonic_ns();inject(action,xy,grab) row=dict(tag=tag,global_point=xy,action=action,start_ns=t0,end_ns=time.monotonic_ns(),events={n:buttons(n)[before[n]:] for n in before}) clicks.append(row);save(lab,'clicks',clicks);return row def delivery(row,name,target): assert not row['events']['reference'] and not row['events']['filler'],'other client received visible popup button' return pair(row['events'][name],target) def correlate_pointer(name,xy,rect,target): # WHY: an injection request alone does not identify applied popup origin. rows=re.findall(rf'POINTER_(?:ENTER|MOTION) target={target} .*?x=([\d.-]+) y=([\d.-]+)',log(name)) assert rows,'missing pointer local coordinates' local=list(map(float,rows[-1]));expected=[xy[0]-rect[0],xy[1]-rect[1]] assert local==expected,('pointer/global geometry mismatch',target,local,expected) return dict(global_point=xy,local=local,rectangle=rect) def close_client(proc,name,value): proc.stdin.write(value+'\n');proc.stdin.flush() code=proc.wait(timeout=3);assert code==0,(name,code) assert 'TOPLEVEL DESTROY' in log(name) wait(lambda:find('org.mosaic.NestedGrabProbe',proc.pid) is None) # CONTRACT: applied owner, paint and XDG surface size are distinct measurements. def owner_state(name,proc,tag): snap=snapshot(tag);ws=workspace(snap['tree'],'1');owner=next(n for n in nodes(ws) if n.get('pid')==proc.pid) assert owner['app_id']=='org.mosaic.NestedGrabProbe' and owner['shell']=='xdg_shell' and owner['visible'] assert ws['output']=='HEADLESS-1' and owner['focused'] and snap['focused']==[owner['id']] measured=measure_owner(log('compositor'),owner);scene=scene_from_log(log('compositor'),'HEADLESS-1') assert scene['workspace']==ws['id'] return snap,ws,owner,measured,scene def open_chain(name): global stage reserve(lab,'chain');cmd('workspace 1');proc=spawn(name,[lab/'nested-client','static_cross'],debug=True) wait(lambda:find('org.mosaic.NestedGrabProbe',proc.pid) and 'TOPLEVEL MAP ' in log(name));time.sleep(.6) snap,ws,owner,before,sc=owner_state(name,proc,name+'-before-camera') assert ws['layout']=='mosaic' current=ws['mosaic']['rows'][0]['scroll_x'];x,w=before['horizontal_content'];desired=-w//2;delta=int(x-desired) if delta: reserve(lab,'camera');cmd(f'[con_id={owner["id"]}] layout mosaic scroll {delta}');time.sleep(.6) snap,ws,owner,after,scene=owner_state(name,proc,name+'-applied') validate_camera(current,ws['mosaic']['rows'][0]['scroll_x'],delta,scene) assert scene['clip_valid'] and scene['global_clip']==[-800,0,800,600] assert not scene_from_log(log('compositor'),'HEADLESS-2')['clip_valid'] refstate=validate_reference(snap,refid,ref.pid) camera_rows.append(dict(chain=name,before=current,delta=delta,after=scene['scroll'],before_owner=before,after_owner=after,scene=scene,reference=refstate)) save(lab,'camera-calibration',camera_rows) if name=='E1': stage='reference';inject('move',[-790,590]);time.sleep(.5) first=capture('H2-reference-1','HEADLESS-2');time.sleep(.6) second=capture('H2-reference-2','HEADLESS-2');cmp=reference(first,second) save(lab,'reference-stability',dict(status='PASS',comparison=cmp,reference=refstate,interval='>=600ms',focus=snap['focused'])) global baseline baseline=first stage=name+'-open-A';topim=capture(name+'-top','HEADLESS-1') topbox=paint(topim,'TOPLEVEL')['bbox'];assert topbox # Tiled scene+window offset is checked against actual painted blue origin. origin=[after['horizontal_content'][0],topbox[1]] assert topbox[0]-800==origin[0] and topbox[3]-topbox[1]==320 assert origin[1]==after['scene'][1]+owner['window_rect']['y'] owners[name]=dict(id=owner['id'],pid=proc.pid,workspace=ws['id'],logical_rect=owner['rect'],applied=after,drawn_size=[420,320],paint=paint(topim,'TOPLEVEL'),content_origin=origin) save(lab,'applied-owner-geometry',owners) cp=click(name+'-open-A',name,point(topim,'TOPLEVEL',-800),True,'left') wait(lambda:'POPUP_A MAP ' in log(name));time.sleep(.25) aim=capture(name+'-A','HEADLESS-1');ap=point(aim,'POPUP_A',-800) stage=name+'-open-B';click(name+'-open-B',name,ap,True,'left') wait(lambda:'POPUP_B MAP ' in log(name));time.sleep(.3) prot=protocol(log(name));g=geometry(log(name),origin) assert g['configured_A']==[100,80,350,170] and g['configured_B']==[100,60,300,130] correlate_pointer(name,ap,g['A'],'POPUP_A') left=capture(name+'-mosaic-H1','HEADLESS-1');right=capture(name+'-mosaic-H2','HEADLESS-2') rgb=popup_pixels(left,right,g,True,baseline) clip=root_clip(log('compositor'),origin,True) chains[name]=dict(protocol=prot,geometry=g,pixels=rgb,owner=owners[name]) clips[name]=dict(tile=scene,root=clip,child=clip['child']) save(lab,'grab-serials',{n:v['protocol']['grabs'] for n,v in chains.items()}) save(lab,'xdg-protocol-order',{n:v['protocol'] for n,v in chains.items()}) save(lab,'popup-root-geometry',{n:v['geometry']['A'] for n,v in chains.items()}) save(lab,'popup-nested-geometry',{n:v['geometry']['B'] for n,v in chains.items()}) save(lab,'applied-clips',clips);save(lab,'mosaic-pixels',{n:v['pixels'] for n,v in chains.items()}) return proc,owner,g,left try: comp=spawn('compositor',[lab/'swayfx-mosaic','-d','-c',run/'config']) sock=wait(lambda:next(iter(runtime.glob('sway-ipc.*.sock')),None)) display=wait(lambda:next((x for x in runtime.glob('wayland-*') if x.is_socket()),None)) env.update(WAYLAND_DISPLAY=display.name,SWAYSOCK=str(sock)) assert Path(f'/proc/{comp.pid}/exe').resolve()==lab/'swayfx-mosaic' maps=Path(f'/proc/{comp.pid}/maps').read_text();(run/'proc-maps.txt').write_text(maps) paths=sorted({s.split()[-1] for s in maps.splitlines() if len(s.split())>=6 and s.split()[-1].startswith('/')}) wl=Path('/usr/lib/libwlroots-0.20.so').resolve() assert [Path(x).resolve() for x in paths if 'libwlroots' in x]==[wl] and sha(wl)==WLROOTS_SHA assert not any('libscenefx' in x for x in paths) save(lab,'dependency-mappings',dict(status='PASS',paths=paths,wlroots=str(wl),wlroots_sha256=sha(wl),SceneFX_shared=False)) outputs=ipc('-t','get_outputs');validate_outputs(outputs);save(lab,'output-geometry',outputs) pointer=spawn('pointer',[lab/'pointer-daemon']);wait(lambda:'POINTER_READY' in log('pointer')) cmd('workspace 2');ref=spawn('reference',[lab/'static-reference'],debug=True) rn=wait(lambda:find('org.mosaic.StaticReference',ref.pid));refid=rn['id'];cmd(f'[con_id={refid}] border none') cmd('workspace 1');filler=spawn('filler',[lab/'static-reference'],debug=True) wait(lambda:find('org.mosaic.StaticReference',filler.pid));cmd('layout mosaic') E1,owner1,g1,im1=open_chain('E1') stage='E1-visible-B';xy=point(im1,'POPUP_B',-800);row=click(stage,'E1',xy) result=delivery(row,'E1','POPUP_B');coords=correlate_pointer('E1',xy,g1['B'],'POPUP_B');mapped(log('E1')) save(lab,'mosaic-input',dict(status='PASS',pair=result,coordinates=coords,click=row)) stage='E1-forbidden-B';B=g1['B'];xy=[int((max(0,B[0])+min(800,B[0]+B[2]))//2),B[1]+30] assert B[0]<=xy[0]