#!/usr/bin/env python3 """Toggle Cube's overview through WCM on a private session and config.""" import configparser import json import os from pathlib import Path import socket import struct import subprocess import tempfile import time import gi gi.require_version('Atspi', '2.0') from gi.repository import Atspi assert os.geteuid()!=0 stage=Path(os.environ.get('CUBE_HYPERSPACE_STAGE','/var/tmp/wayfire-cube-hyperspace-prototype-20261010')) root=Path(tempfile.mkdtemp(prefix='wcm-cube-backdrop-')) env=os.environ.copy() for key in ('WAYLAND_DISPLAY','DISPLAY','WAYFIRE_SOCKET','_WAYFIRE_SOCKET','WAYFIRE_CONFIG_FILE', 'WAYFIRE_DEFAULT_CONFIG_BACKEND','NO_AT_BRIDGE'): env.pop(key,None) env.update(XDG_RUNTIME_DIR=str(root),XDG_CONFIG_HOME=str(root/'config'),XDG_DATA_HOME=str(root/'data'), XDG_CACHE_HOME=str(root/'cache'),XDG_STATE_HOME=str(root/'state'),WLR_BACKENDS='headless', WLR_HEADLESS_OUTPUTS='3',WLR_RENDERER='gles2',WLR_RENDER_DRM_DEVICE='/dev/dri/renderD128', WAYFIRE_PLUGIN_PATH=str(stage/'plugins'),WAYFIRE_PLUGIN_XML_PATH=str(stage/'metadata'), GTK_USE_PORTAL='0',GIO_USE_VFS='local',GTK_MODULES='atk-bridge', XDG_DATA_DIRS='/usr/local/share:/usr/share') config=root/'wayfire.ini' config.write_text(f'''# Keep unrelated preferences. [core] plugins = ipc stipc ipc-rules {stage}/plugins/libcube.so place grid xwayland = false vwidth = 4 vheight = 3 stitch_enabled = true stitch_outputs = HEADLESS-1 HEADLESS-2 HEADLESS-3 [cube] activate = BTN_LEFT dim = 0.25 [grid] toggle_scope = KEY_ENTER [output:HEADLESS-1] mode = 1400x1000@60 position = 0,0 [output:HEADLESS-2] mode = 1400x1000@60 position = 1400,0 [output:HEADLESS-3] mode = 1400x1000@60 position = 2800,0 ''') original=config.read_text() shell=root/'shell.ini';shell.write_text('') compositor=subprocess.Popen([str(stage/'wayfire'),'-c',str(config)],env=env, stdout=(root/'wayfire.log').open('w'),stderr=subprocess.STDOUT) ui=None def wait(fn,timeout=15): until=time.monotonic()+timeout while time.monotonic() KEY_ENTER' print('PASS WCM Warp mode, ten controls, live speed and quality, preserving unrelated preferences',flush=True) tab('General') choose('Background mode','Hyperspace') wait(lambda:ipc('wayfire/get-config-option',{'option':'cube/background_mode'})['value']=='hyperspace') assert value()['cube']['background_mode']=='hyperspace' tab('Hyperspace') labels=['Animate hyperspace at rest','Hyperspace speed','Cloud density','Cloud turbulence', 'Light ribbons','Destination glow','Hyperspace brightness','Jump surge','Passing stars', 'Coloured stars','Distant stars','Distant star drift','Space dust','Cloud variation','Pulsar strength','Journey seed','Hyperspace colour','Hyperspace quality','Hyperspace frame rate'] for label in labels:assert find(label,Atspi.Role.LABEL),label idle=wait(lambda:overview_check('Animate hyperspace at rest')) assert idle.get_state_set().contains(Atspi.StateType.CHECKED) for enabled in (False,True): assert idle.get_action_iface().do_action(0) wait(lambda:ipc('wayfire/get-config-option',{'option':'cube/hyperspace_animate_at_rest'})['value']==str(enabled).lower()) assert value().getboolean('cube','hyperspace_animate_at_rest')==enabled for label,key,settings in [('Hyperspace speed','hyperspace_speed',(1.25,0,.65)), ('Cloud density','hyperspace_cloud_density',(1.4,.85)), ('Distant star drift','hyperspace_distant_drift',(0,.1)), ('Space dust','hyperspace_dust_density',(0,1.7,.9)), ('Cloud variation','hyperspace_cloud_variation',(0,1.6,1.15)), ('Pulsar strength','hyperspace_pulsars',(0,1.3,.65)), ('Journey seed','hyperspace_seed',(31415,0))]: tab('Hyperspace') spin=wait(lambda:control(label,Atspi.Role.SPIN_BUTTON)) spin.get_component_iface().scroll_to(Atspi.ScrollType.ANYWHERE) if not showing(spin):spin.get_component_iface().grab_focus() for setting in settings: assert spin.get_value_iface().set_current_value(setting) wait(lambda:float(ipc('wayfire/get-config-option',{'option':'cube/'+key})['value'])==setting) assert float(value()['cube'][key])==setting tab('Hyperspace') quality=wait(lambda:control('Hyperspace quality',Atspi.Role.COMBO_BOX)) quality.get_component_iface().scroll_to(Atspi.ScrollType.ANYWHERE) if not showing(quality):quality.get_component_iface().grab_focus() if not showing(quality): for bar in nodes(): if bar.get_role()==Atspi.Role.SCROLL_BAR and showing(bar) and bar.get_state_set().contains(Atspi.StateType.VERTICAL): val=bar.get_value_iface() val.set_current_value(val.get_maximum_value()) time.sleep(.15) choose('Hyperspace quality','Detailed') wait(lambda:ipc('wayfire/get-config-option',{'option':'cube/hyperspace_quality'})['value']=='2') choose('Hyperspace quality','Balanced') wait(lambda:ipc('wayfire/get-config-option',{'option':'cube/hyperspace_quality'})['value']=='1') assert ipc('wayfire/get-config-option',{'option':'cube/hyperspace_coloured_stars'})['default']=='0.120000' assert ipc('wayfire/get-config-option',{'option':'cube/warp_coloured_stars'})['default']=='0.120000' subprocess.run(['grim','-o','VIRTUAL-1',str(root/'hyperspace-controls.png')],env=env,check=True) assert float(value()['cube']['warp_speed'])==.8 assert value()['grid']['toggle_scope']==' KEY_ENTER' print('PASS WCM Hyperspace mode, all nineteen controls, live speed/density/drift/quality, idle toggle and saved settings',flush=True) print('ALL HYPERSPACE WCM CHECKS PASSED',flush=True) finally: try:(root/'accessibility.txt').write_text('\n'.join(str(n.get_role())+' '+n.get_name() for n in nodes())) except Exception:pass if ui is not None and ui.poll() is None:ui.terminate();ui.wait(timeout=5) compositor.terminate();compositor.wait(timeout=10) print('Artifacts:',root,flush=True)