A hosted child is ticked at its owner's frames, one child frame per frame shown; hosted apps launch with `cargo run` from a checkout, open at the size they ask for, open more windows of their own, resize live and die with the WM. The WM is the desktop's portal backend (file chooser, light/dark), with a makepad-drawn file and folder picker; the Wayland clipboard and primary selection meet the makepad clipboard. The AI hub is the WM's own child and its panel shows running models. A covered window gets no frames; tiling and the warp shelves exclude each other; phone WM Settings fixes; dock order places a foreign app next to ours. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
99 lines
4.1 KiB
Python
99 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""The WM's portal backend on the session bus (hidden WM, node 165): Settings for dark mode.
|
|
|
|
portalcheck.py <checkout> [--out DIR]
|
|
|
|
Needs a release build of makepad-app-wm and makepad-remote, busctl and dbus-monitor, and a
|
|
user session bus where nothing else owns org.freedesktop.impl.portal.desktop.makepad.
|
|
Checks: Read and ReadAll answer color-scheme; the Properties version; Introspect; flipping
|
|
the desktop's Dark mode (shell menu) sends SettingChanged and Read follows.
|
|
Exit 1 on a failure.
|
|
"""
|
|
import argparse, json, os, re, subprocess as sp, sys, time
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, os.path.join(HERE, "..", "frametrace"))
|
|
from ftrig import Rig # noqa: E402
|
|
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("root")
|
|
ap.add_argument("--out")
|
|
a = ap.parse_args()
|
|
NAME, PATH = "org.freedesktop.impl.portal.desktop.makepad", "/org/freedesktop/portal/desktop"
|
|
SETTINGS = "org.freedesktop.impl.portal.Settings"
|
|
fails = []
|
|
|
|
|
|
def check(name, ok, detail=""):
|
|
print(("PASS " if ok else "FAIL ") + name + (f" ({detail})" if detail else ""), flush=True)
|
|
if not ok:
|
|
fails.append(name)
|
|
|
|
|
|
def bus(*args):
|
|
p = sp.run(["busctl", "--user", *args], capture_output=True, text=True, timeout=10)
|
|
return (p.stdout + p.stderr).strip()
|
|
|
|
|
|
def scheme():
|
|
out = bus("call", NAME, PATH, SETTINGS, "Read", "ss", "org.freedesktop.appearance", "color-scheme")
|
|
m = re.match(r"v u (\d+)", out)
|
|
return int(m.group(1)) if m else out
|
|
|
|
|
|
out = a.out or os.path.join(os.path.expanduser("~"), "lanes", "wm-review-runs", "wmportal")
|
|
rig = Rig(a.root, "wmportal", apps="", virtual=False, out=out).up()
|
|
mon = None
|
|
try:
|
|
up = None
|
|
for _ in range(120):
|
|
if "wm portal: serving" in open(rig.log).read():
|
|
up = True
|
|
break
|
|
time.sleep(0.5)
|
|
check("the WM serves the portal name", bool(up), [l for l in open(rig.log).read().splitlines() if "portal" in l][-1:])
|
|
s0 = scheme()
|
|
check("Read color-scheme answers 1 (dark) or 2 (light)", s0 in (1, 2), f"{s0}")
|
|
all_ = bus("call", NAME, PATH, SETTINGS, "ReadAll", "as", "1", "org.freedesktop.*")
|
|
check("ReadAll org.freedesktop.* has the appearance", "org.freedesktop.appearance" in all_ and "color-scheme" in all_, all_)
|
|
none = bus("call", NAME, PATH, SETTINGS, "ReadAll", "as", "1", "org.gnome.*")
|
|
check("ReadAll of another namespace is empty", none.startswith("a{sa{sv}} 0"), none)
|
|
nf = bus("call", NAME, PATH, SETTINGS, "Read", "ss", "org.freedesktop.appearance", "nope")
|
|
check("an unknown key is NotFound", "NotFound" in nf or "no such setting" in nf, nf)
|
|
ver = bus("get-property", NAME, PATH, SETTINGS, "version")
|
|
check("the version property", ver == "u 1", ver)
|
|
intro = bus("introspect", NAME, PATH)
|
|
check("Introspect lists Settings", SETTINGS in intro and "SettingChanged" in intro, intro[:120])
|
|
# live flip: open the shell menu's Dark mode row and press it
|
|
mon_log = os.path.join(out, "signals.txt")
|
|
mon = sp.Popen(["dbus-monitor", "--session", f"type='signal',interface='{SETTINGS}'"], stdout=open(mon_log, "w"), stderr=sp.STDOUT)
|
|
time.sleep(0.5)
|
|
# the shell menu opens from the bar's leftmost (palette) button
|
|
time.sleep(2)
|
|
rig.mouse("click", 98, 12)
|
|
time.sleep(1.0)
|
|
rig.grab(os.path.join(out, "menu.png"))
|
|
snap = ""
|
|
print("snap:", snap[:300], flush=True)
|
|
flipped = False
|
|
# the menu is drawn, not widgets: its "Dark mode" row sits at (182, 282) on the rig's
|
|
# 1280x800 desk (the 8th row under the bar)
|
|
m = True
|
|
if m:
|
|
rig.mouse("move", 182, 282)
|
|
time.sleep(0.2)
|
|
rig.mouse("click", 182, 282)
|
|
time.sleep(1.5)
|
|
s1 = scheme()
|
|
flipped = s1 in (1, 2) and s1 != s0
|
|
check("Dark mode flips Read", flipped, f"{s0} -> {s1}")
|
|
sig = open(mon_log).read()
|
|
check("SettingChanged went out with the new scheme", "SettingChanged" in sig and f"uint32 {s1}" in sig, sig[-300:])
|
|
else:
|
|
check("the Dark mode row is on screen (for the live flip)", False, snap[:200])
|
|
finally:
|
|
if mon:
|
|
mon.terminate()
|
|
rig.down()
|
|
print("RESULT", "FAIL " + ", ".join(fails) if fails else "PASS")
|
|
sys.exit(1 if fails else 0)
|