#!/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_OVERVIEW_STAGE','/var/tmp/wayfire-cube-overview-20261009')) root=Path(tempfile.mkdtemp(prefix='wcm-cube-overview-')) 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()','','BTN_LEFT'} assert float(value()['cube']['dim'])==.25 assert value()['grid']['toggle_scope']==' KEY_ENTER' print('PASS WCM saves and applies enable/disable changes while preserving plugin path and other preferences',flush=True) rest=wait(lambda:overview_check('Render other faces at rest')) tab=find('General',Atspi.Role.PAGE_TAB) if tab is not None: assert tab.get_parent().get_selection_iface().select_child(tab.get_index_in_parent()) wait(lambda:showing(rest)) assert not rest.get_state_set().contains(Atspi.StateType.CHECKED) for enabled in (True,False,True): assert rest.get_action_iface().do_action(0) wait(lambda:value().getboolean('cube','render_at_rest',fallback=not enabled)==enabled) wait(lambda:ipc('wayfire/get-config-option',{'option':'cube/render_at_rest'})['value']==str(enabled).lower()) assert value()['cube'].getboolean('mouse_overview') assert value()['core']['plugins'].split()==['ipc','stipc','ipc-rules',str(stage/'plugins/libcube.so'),'place','grid'] print('PASS WCM applies resting-face rendering without changing mouse overview or plugin paths',flush=True) print('ALL CUBE 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)