ProPresenter updates are safe: any installed copy counts as running, the newest version opens

- newest_of picks by CFBundleShortVersionString, not file name (ProPresenter.app sorted after ProPresenter 20.1.app)
- mac_start takes newest_of; auto-setup uses it for programs on the Companion Mac
- WS and SC switched over; NA's ProP restart re-rendered (same pick today)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 14:33:39 -04:00
co-authored by Claude Opus 5.5
parent 5db1d93732
commit 42b325e927
7 changed files with 82 additions and 24 deletions
+8 -2
View File
@@ -230,9 +230,15 @@ def items(inv, nets):
continue
fid = slug("%s_ingest" % a["app"])
pretty = a["label"].title() if a["app"] not in ("propresenter", "ableton") else {"propresenter": "ProPresenter", "ableton": "Ableton"}[a["app"]]
start = {"template": "mac_start", "params": {"host": "ingest", "process": a["process"], "app": app_path},
# Any installed copy counts, and the newest opens: an update that installs beside the old one
# ("ProPresenter 20.0.1.app" and "ProPresenter.app" at Woonsocket) never reads as stopped.
newest = a["open"] if "*" in a.get("open", "") else ""
if newest:
exe = b.app_match(newest, a["process"])
opens = {"app": "$APP", "newest_of": newest} if newest else {"app": app_path}
start = {"template": "mac_start", "params": dict({"host": "ingest", "process": a["process"]}, **opens),
"label": "start %s on this Mac if it is not running" % pretty, "short": a["label"]}
again = {"template": "mac_restart", "params": {"host": "ingest", "process": a["process"], "app": app_path, "wait_seconds": 10, "force": True},
again = {"template": "mac_restart", "params": dict({"host": "ingest", "process": a["process"], "wait_seconds": 10, "force": True}, **opens),
"label": "restart %s on this Mac (closes it first)" % pretty, "short": a["label"]}
item = {"ident": "app:ingest|%s" % a["process"], "group": a.get("group") or ("VIDEO" if a["app"] == "arena" else "STREAM" if a["app"] == "obs" else "SHOW"),
"label": a["label"], "checks": [{"type": "process", "match": exe, "name": a["label"], "fix": fid}],
+21 -3
View File
@@ -64,6 +64,22 @@ def mac_quit(process, wait_seconds, force, stop_children=False):
return s
def newest_app(pattern):
"""APP=... the newest installed copy by its own version number (CFBundleShortVersionString), not by
file name: "ProPresenter.app" sorts after "ProPresenter 20.1.app" by name (2026-10-21, before the
Southcoast/Woonsocket ProPresenter update)."""
return ("APP=$(for a in %s; do [ -d \"$a\" ] && printf '%%s|%%s\\n' \"$(defaults read \"$a/Contents/Info\" "
"CFBundleShortVersionString 2>/dev/null)\" \"$a\"; done | sort -t '|' -k1,1V | tail -1 | cut -d '|' -f2-); "
% str(pattern).strip())
def app_match(pattern, process):
"""A pgrep -f pattern for the program from any copy matching an /Applications glob (never its helpers)."""
import re as _re
parts = [_re.escape(x) for x in str(pattern).split("*")]
return "[^/]*".join(parts).replace("\\ ", " ") + "/Contents/MacOS/" + _re.escape(process).replace("\\ ", " ") + "$"
def mac_open(app, background=False, arg=None):
"""open -a Name / open /path/App.app (+ -g to stay in the background)."""
app = str(app)
@@ -109,8 +125,10 @@ TEMPLATES = {
"words": "Start a Mac program if it is not running",
"os": "mac", "start_only": True,
"params": [P_HOST, ("process", "Process name", "As pgrep sees it (Activity Monitor's process name).", "str", None),
("app", "App to open", "Its name, or the full /Applications/... path.", "str", None)],
"render": lambda p, cfg: "pgrep -xq %s || %s" % (q(p["process"]), mac_open(p["app"])),
("app", "App to open", "Its name, the full /Applications/... path, or $APP with 'newest of' below.", "str", None),
("newest_of", "Newest of", "A pattern like /Applications/ProPresenter*.app: the newest installed version opens.", "str", "")],
"render": lambda p, cfg: ("pgrep -xq %s || { %s%s; }" % (q(p["process"]), newest_app(p["newest_of"]), mac_open("$APP"))
if p.get("newest_of") else "pgrep -xq %s || %s" % (q(p["process"]), mac_open(p["app"]))),
"suggest": lambda p, cfg: "start %s if it is not running" % p["app"],
},
"mac_restart": {
@@ -123,7 +141,7 @@ TEMPLATES = {
("background", "Open it in the background", "No window comes to the front.", "bool", False),
("stop_children", "Also stop everything it started", "Its helper processes (a translation server, say).", "bool", False),
("newest_of", "Newest of", "A pattern like /Applications/ProPresenter*.app: the newest installed version opens.", "str", "")],
"render": lambda p, cfg: (("APP=$(ls -d %s | sort -V | tail -1); " % str(p["newest_of"]).strip()) if p.get("newest_of") else "")
"render": lambda p, cfg: (newest_app(p["newest_of"]) if p.get("newest_of") else "")
+ mac_quit(p["process"], p.get("wait_seconds", 10), p.get("force", True), p.get("stop_children", False))
+ mac_open("$APP" if p.get("newest_of") else p["app"], p.get("background", False)),
"suggest": lambda p, cfg: "restart %s" % (p["app"] if not p.get("newest_of") else "%s (newest installed)" % p["process"]),