Commit 4ea24cb0 by PLN (Algolia)

fix(launcher): converge the rig units — the tray and lens died and nothing brought them back

The GUI launcher started SuperDirt, Ardour and Pulsar as raw processes and
never touched the systemd --user layer, so a tray that exits 0 when the shell
takes its host away (perf-tray, Sep 14) or a user-closed MIDI lens (exit 78)
stayed dead: the only convergers were tools/gig-up.sh --converge, rig-doctor
and the Bridge watcher, and the Bridge was dead too. Three days of launches
"worked" with no tray and no lens.

ensure() now refuses to start a unit whose proof process already runs outside
it — which is what makes it safe for the launcher to call --ensure --apply
after scsynth is up instead of spawning a second SuperDirt through
parvagues-sc.service. --status agrees, so a launcher-started SuperDirt is no
longer a false problem row. gig-up.sh converges right after the ready-wait,
and honours the midiviz close-latch (the tray's MIDI Monitor button is the
way back within a session).
parent 6e49f925
......@@ -260,6 +260,26 @@ while :; do
sleep 1
done
# 2a) THE REST OF THE RIG — systemd --user units, via the ONE authored
# inventory. The tray (perf-tray), the MIDI lens (midiviz), the session log
# and the reconcilers are units, and this script used to launch around them:
# the tray exits 0 when the shell takes its host away, midiviz exits 78 when
# closed by hand, and then nothing alive ever converged them again — the
# Bridge watcher that does it was dead too (2026-09-17: tray and lens dark
# for 3 days while every launch "worked" and spawned exactly the three
# processes below). `--ensure` reads tools/rig_units.py and does BOTH verbs:
# enable per boot policy AND start. Safe to call with SuperDirt already up:
# ensure() refuses to start a unit whose proof process (scsynth) already runs
# outside it, so the raw sclang this script started is never duplicated by
# parvagues-sc.service. And a hand-closed midiviz stays closed (the latch) —
# the tray's MIDI Monitor button is the way back within a session.
if out=$(python3 "$DIR/tools/rig_units.py" --ensure --apply 2>&1); then
printf '%s\n' "$out" | grep -q 'nothing to do' || printf '%s\n' "$out" | sed 's/^/ /'
else
warn "rig units: converge failed — tray/lens may be missing. Run: tools/rig_units.py --ensure --apply"
printf '%s\n' "$out" | tail -2 | sed 's/^/ /'
fi
# 2b) LIGHT THE SURFACE. The rig owns MIDI now, so paint the LCXL immediately —
# before Ardour and Pulsar even start. Previously the only painter lived in the
# Pulsar HUD, so the board stayed DARK until the editor was up and its frame
......
......@@ -120,6 +120,21 @@ def _sc(*args: str) -> subprocess.CompletedProcess:
)
def _proof_alive(proof: str) -> bool:
"""Is the unit's proof process already running — outside the unit?
2026-09-17: the GUI launcher starts SuperDirt as a raw sclang in a
terminal, NOT as parvagues-sc.service — so to that launcher, ensure()
looked like a duplicate-SuperDirt generator and it never converged the
units at all (tray + MIDI lens dark for 3 days while every launch
"worked"). The PROOF column already named the process that proves a unit
really works; asking it one more question — "is that process already
alive by some other road?" — is what makes ensure() safe to call from the
launcher after scsynth is up.
"""
return subprocess.run(["pgrep", "-x", proof], capture_output=True).returncode == 0
def states() -> dict[str, dict[str, str]]:
"""{unit: {"active":…, "enabled":…}} in TWO forks, not two per unit.
......@@ -224,6 +239,14 @@ def ensure(apply: bool) -> int:
print(f"rig: {u:<24} inactive because it was CLOSED by hand "
f"— leaving it alone (latch: {latch_path(u)})")
continue
# ...and a unit whose proof process already runs outside it must
# not be started on top: two SuperDirts would fight over the same
# MIDI card. Say so — a silent skip reads as a green lie.
proof = PROOF[u]
if proof and _proof_alive(proof):
print(f"rig: {u:<24} unit inactive, but {proof} already runs "
f"outside it — not starting a duplicate")
continue
todo.append(("start", u, s["active"]))
if not todo:
print(f"rig: all {len(UNITS)} units enabled-per-policy and active — nothing to do")
......@@ -254,6 +277,12 @@ def status() -> int:
if s["active"] != "active":
if BOOT[u] == "manual":
note.append("manual — start by hand for its hardware")
elif PROOF[u] and _proof_alive(PROOF[u]):
# Same judgement ensure() makes: the process is alive, only the
# unit shell is off (launcher start). Presence of the process is
# not proof of function — but a dead unit over a live scsynth is
# not the fault this table exists to catch.
note.append(f"unit off, but {PROOF[u]} runs outside it (launcher start)")
else:
note.append("NOT running")
bad += 1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment