Colour the page nav keys on every page to follow the ALL button.

nav_templates.json holds Companion's own UP / page number / DOWN keys after
'convert to regular button'; make_page.py colours them from the ALL tile and
nav_keys.py copies them onto all 96 other pages (288 keys, recorded in
nav_locations.json; --revert restores the built-in keys). Also rewrites the
README's top half, which an earlier edit had duplicated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 09:40:45 -04:00
co-authored by Claude Opus 5.5
parent 161596cefb
commit 3118cdc281
7 changed files with 1593 additions and 62 deletions
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""Make the UP / page number / DOWN keys on every page follow the status page's ALL button.
The status page (page 19) carries coloured copies of Companion's own nav keys
(see make_page.py and nav_templates.json). This copies them over the built-in
nav keys on every other page, wherever those sit, using the same "copy button"
call as the Companion UI. Each key keeps its normal press action.
python3 nav_keys.py --dry-run list what would change
python3 nav_keys.py apply (also refreshes keys already converted)
python3 nav_keys.py --revert put Companion's built-in nav keys back everywhere
Only nav keys are touched. Every location changed is recorded in nav_locations.json.
"""
import argparse
import gzip
import json
import os
import urllib.error
import urllib.request
from make_page import NAV_LOCATIONS, NAV_MARKER
from push_page import Trpc, WebSocket
HERE = os.path.dirname(os.path.abspath(__file__))
RECORD = os.path.join(HERE, "nav_locations.json")
NAV_TYPES = ("pageup", "pagenum", "pagedown")
def page_export(host, port, page):
url = "http://%s:%d/int/export/page/%d?includeSecrets=false" % (host, port, page)
try:
with urllib.request.urlopen(url, timeout=10) as r:
raw = r.read()
except urllib.error.HTTPError:
return None
try:
raw = gzip.decompress(raw)
except OSError:
pass
return json.loads(raw)
def nav_kind(control):
if control.get("type") in NAV_TYPES:
return control["type"]
notes = control.get("options", {}).get("notes", "") or ""
if control.get("type") == "button-layered" and notes.startswith(NAV_MARKER):
for kind in NAV_TYPES:
if "(%s)" % kind in notes:
return kind
return None
def find_nav_keys(host, port, status_page):
"""[(page, row, column, kind)] for every nav key outside the status page."""
found, page = [], 1
while True:
export = page_export(host, port, page)
if export is None:
break
if page != status_page:
for r, cols in export["page"]["controls"].items():
for c, control in cols.items():
kind = nav_kind(control or {})
if kind:
found.append((page, int(r), int(c), kind))
page += 1
return found
def main():
cfg = json.load(open(os.path.join(HERE, "config.json")))
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("--host", default="na-ingest.local")
ap.add_argument("--port", type=int, default=8000)
ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--revert", action="store_true")
args = ap.parse_args()
status_page = cfg["page"]
record = json.load(open(RECORD)) if os.path.exists(RECORD) else {}
if args.revert:
targets = [(int(k.split("/")[0]), int(k.split("/")[1]), int(k.split("/")[2]), v) for k, v in record.items()]
else:
targets = find_nav_keys(args.host, args.port, status_page)
print("%d nav keys on %d pages" % (len(targets), len({t[0] for t in targets})))
if args.dry_run:
for t in targets:
print(" page %d row %d col %d: %s" % t)
return
ws = WebSocket(args.host, args.port, "/trpc")
done = 0
try:
api = Trpc(ws)
for page, row, col, kind in targets:
location = {"pageNumber": page, "row": row, "column": col}
if args.revert:
api.mutation("controls.resetControl", {"location": location, "newType": kind})
record.pop("%d/%d/%d" % (page, row, col), None)
else:
r, c = NAV_LOCATIONS[kind]
source = {"pageNumber": status_page, "row": int(r), "column": int(c)}
if not api.mutation("controls.copyControl", {"fromLocation": source, "toLocation": location}):
raise RuntimeError("copy to page %d row %d col %d failed" % (page, row, col))
record["%d/%d/%d" % (page, row, col)] = kind
done += 1
finally:
ws.close()
json.dump(record, open(RECORD, "w"), indent=1, sort_keys=True)
print("%s %d nav keys" % ("restored" if args.revert else "coloured", done))
if __name__ == "__main__":
main()