Companion keeper: after a restart the panel opens the Companion that ran last if it is not running
- only in the watchdog's first 10 minutes, never another version, never during an upgrade - SC verified end to end; laptop SSH to the SC Macs; configs synced Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3355,6 +3355,54 @@ def start_artnet_listener(snap, stop, port=6454):
|
||||
return t
|
||||
|
||||
|
||||
def companion_app_running():
|
||||
"""The .app of the Companion running on this Mac (from ps), or None."""
|
||||
try:
|
||||
out = subprocess.run(["/bin/ps", "-axo", "command"], capture_output=True, text=True, timeout=10).stdout
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return None
|
||||
for line in out.splitlines():
|
||||
m = re.match(r"^(/Applications/Companion[^/]*\.app)/Contents/MacOS/Companion$", line.strip())
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
class CompanionKeeper:
|
||||
"""After this Mac restarts, Companion is open: the deck is Companion, so a Companion that is not a login
|
||||
item would leave the whole panel dark (Southcoast, 2026-10-21: its login items cannot be read without a
|
||||
prompt on its screen). Remembers which copy runs (state "companion_app"; Woonsocket keeps four versions,
|
||||
so never "Companion.app" by guess) and, only in the watchdog's first 10 minutes after it starts (that is,
|
||||
after a login), opens that copy if no Companion is running. Never while an upgrade is going on."""
|
||||
WINDOW = 600
|
||||
|
||||
def __init__(self, engine, data_dir, started=None, running=companion_app_running, opener=None):
|
||||
self.engine, self.data_dir = engine, data_dir
|
||||
self.started = started if started is not None else time.time()
|
||||
self.running, self.opener = running, opener or (lambda app: subprocess.run(["/usr/bin/open", "-g", app], timeout=30))
|
||||
self.opened_at = 0.0
|
||||
|
||||
def check(self, now=None):
|
||||
now = now or time.time()
|
||||
app = self.running()
|
||||
known = self.engine.state.get("companion_app")
|
||||
if app:
|
||||
if app != known:
|
||||
if known:
|
||||
log.info("COMPANION now runs from %s (was %s)", app, known)
|
||||
self.engine.state.set("companion_app", app)
|
||||
return None
|
||||
if not known or now - self.started > self.WINDOW or now - self.opened_at < 120 or not os.path.isdir(known):
|
||||
return None
|
||||
flag = os.path.join(self.data_dir or "", "companion-upgrading")
|
||||
if self.data_dir and os.path.exists(flag) and now - os.path.getmtime(flag) < 1800:
|
||||
return None
|
||||
self.opened_at = now
|
||||
log.info("COMPANION was not running after this Mac started: opening %s", known)
|
||||
self.opener(known)
|
||||
return known
|
||||
|
||||
|
||||
class Periodic(threading.Thread):
|
||||
def __init__(self, name, interval, fn, stop):
|
||||
super().__init__(name=name, daemon=True)
|
||||
@@ -3703,6 +3751,11 @@ def run(cfg, data_dir, dry_run=False, stop=None, config_sha=None):
|
||||
t = Periodic("dante-newcomers", 60.0, lambda: watch_dante_newcomers(snap, cfg, c5, engine), stop)
|
||||
t.start()
|
||||
threads.append(t)
|
||||
if (cfg.get("companion") or {}).get("host", "127.0.0.1") in ("127.0.0.1", "localhost") and sys.platform == "darwin":
|
||||
keeper = CompanionKeeper(engine, data_dir)
|
||||
t = Periodic("companion-keeper", 30.0, keeper.check, stop)
|
||||
t.start()
|
||||
threads.append(t)
|
||||
if cfg.get("ready_page") and not (cfg.get("ready") or {}).get("disabled"):
|
||||
import readiness
|
||||
poller = readiness.Poller(cfg, snap, stop, data_dir, is_live=lambda: engine.live_now(time.time())[0])
|
||||
|
||||
Reference in New Issue
Block a user