VulpesOS/tundra/vm/session.py
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

95 lines
4.5 KiB
Python

#!/usr/bin/python3
"""A non-root Wayland session using the shared, unmodified Vulpes host."""
import importlib.util
import json
import os
from pathlib import Path
import socket
import shutil
import subprocess
import sys
import time
root = Path('/opt/vulpes')
sys.path.insert(0,str(root/'tools'))
spec = importlib.util.spec_from_file_location('host',root/'tools/run-host.py')
host = importlib.util.module_from_spec(spec)
spec.loader.exec_module(host)
lock = json.loads((root/'engine-lock.json').read_text())
test='tundra.test=1' in Path('/proc/cmdline').read_text()
shutil.copy2('/opt/tundra/probe.sys.mjs',root/'tests/TundraProbe.sys.mjs')
runtime = host.prepare(lock, test=True)
with (runtime/'vulpes.cfg').open('a') as cfg:
cfg.write('''
Services.obs.addObserver({observe(win) {
win.addEventListener("load", () => {
if(win.document.documentElement.getAttribute("windowtype")==="vulpes:host")
ChromeUtils.importESModule("resource://vulpes/tests/TundraProbe.sys.mjs").run(win).catch(e=>dump("TUNDRA_PROBE_ERROR "+e+"\\n"));
},{once:true});
}}, "domwindowopened");
''')
logs = Path('/var/log/tundra')
processes = []
def start(args, log, **kwargs):
p = subprocess.Popen(args,stdout=(logs/log).open('w'),stderr=subprocess.STDOUT,**kwargs)
processes.append(p)
return p
try:
weston = start(['weston','--backend=drm','--renderer=pixman','--shell=kiosk-shell.so',
'--config=/opt/tundra/weston.ini','--idle-time=0','--socket=wayland-0',
],'weston.log')
for _ in range(150):
if weston.poll() is not None: raise RuntimeError('Weston exited')
if Path(os.environ['XDG_RUNTIME_DIR'],'wayland-0').exists(): break
time.sleep(.1)
else: raise RuntimeError('Wayland socket missing')
server = start(['python3','-u',str(root/'tools/serve-gaia.py')],'server.log')
for _ in range(100):
if server.poll() is not None: raise RuntimeError('Gaia server exited')
try:
with socket.create_connection(('127.0.0.1',8765),timeout=.2): break
except OSError: time.sleep(.1)
else: raise RuntimeError('Gaia server did not start')
profile = root/'profiles/tundra-vm'
profile.mkdir(parents=True,exist_ok=True)
prefs = {'browser.shell.checkDefaultBrowser':False,'browser.dom.window.dump.enabled':True,
'browser.startup.homepage_override.mstone':'ignore','browser.aboutwelcome.enabled':False,
'font.default.x-western':'sans-serif','font.name.sans-serif.x-western':'Fira Sans',
'marionette.enabled':False,
'datareporting.policy.dataSubmissionEnabled':False,'toolkit.telemetry.enabled':False,
'app.update.auto':False,
# Pixman/virtio has no reliable Wayland frame clock while idle.
'widget.wayland.vsync.enabled':False}
(profile/'user.js').write_text('\n'.join(f'user_pref({json.dumps(k)}, {json.dumps(v)});' for k,v in prefs.items())+'\n')
env = dict(os.environ,VULPES_FULLSCREEN='1',TUNDRA_TEST='1' if test else '0',VULPES_HOST_ROOT=str(runtime/'vulpes/host'),VULPES_WORKSPACE_ROOT=str(root))
firefox = start([str(runtime/'firefox'),'-no-remote','-profile',str(profile),
'-chrome','chrome://vulpes/content/shell.xhtml'],'gecko.log',env=env)
forwarded=False
for _ in range(1800):
if firefox.poll() is not None: raise RuntimeError('Gecko exited before checks completed')
if not forwarded and 'TUNDRA_POINTER_HOME' in (logs/'gecko.log').read_text(errors='replace'):
print('TUNDRA_POINTER_HOME',flush=True);forwarded=True
if (logs/'test.json').exists(): break
time.sleep(.1)
else: raise RuntimeError('Guest test timeout')
report=json.loads((logs/'test.json').read_text())
report['uid']=os.getuid()
report['checks']['nonRoot']=os.getuid()!=0
report['passed']=report['passed'] and report['checks']['nonRoot']
(logs/'test.json').write_text(json.dumps(report,indent=2)+'\n')
print('TUNDRA_GUI_TEST '+json.dumps(report),flush=True)
if not report['passed']: raise RuntimeError('Guest checks failed')
print('TUNDRA_GUI_READY',flush=True)
if 'tundra.test=1' in Path('/proc/cmdline').read_text():
time.sleep(20) # Host captures the actual virtio display before shutdown.
else:
while firefox.poll() is None and weston.poll() is None: time.sleep(1)
finally:
for p in reversed(processes):
if p.poll() is None:
p.terminate()
try: p.wait(timeout=5)
except subprocess.TimeoutExpired:
p.kill(); p.wait()