mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 01:00:18 +02:00
Integration review findings: the scripts they run ssh'd to whatever 'frame' means in ~/.ssh/config. They now take FRAME_ALIAS and FRAME_SSH_OPTS from the server's route. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
77 lines
3.6 KiB
Bash
Executable File
77 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env zsh
|
|
# Mac-side: stop the Steam Frame from going to sleep while an agent works on it.
|
|
#
|
|
# The Frame sleeps when Steam's own idle timer runs out ("Sleep after
|
|
# inactivity": 60 min on AC, 15 min on battery by default). SSH activity
|
|
# doesn't count as input, and asleep the Frame is off the network. `on` sets
|
|
# both timers to Never through Steam's UI (DevTools on 127.0.0.1:8080, via
|
|
# ui/frame_steam.py) and holds a logind sleep inhibitor as a user unit.
|
|
# `off` drops the inhibitor and restores the timers `on` saved.
|
|
#
|
|
# Usage:
|
|
# scripts/keep-awake.sh on
|
|
# scripts/keep-awake.sh off
|
|
# scripts/keep-awake.sh status
|
|
set -euo pipefail
|
|
|
|
FRAME_ALIAS=${FRAME_ALIAS:-frame}
|
|
# Frame Control passes the headset it has chosen: its address and pinned identity.
|
|
ssh_opts=(${(Q)${(z)FRAME_SSH_OPTS:-}})
|
|
HERE=${0:A:h}
|
|
cmd=${1:-status}
|
|
case $cmd in on|off|status) ;; *) echo "usage: keep-awake.sh on|off|status" >&2; exit 2 ;; esac
|
|
|
|
ssh "${ssh_opts[@]}" -o ConnectTimeout=8 "$FRAME_ALIAS" \
|
|
'mkdir -p ~/.cache/frame-control && cat > ~/.cache/frame-control/frame_steam.py' < "$HERE/../ui/frame_steam.py"
|
|
|
|
# Runs on the Frame. Verified 2026-09-28 (BUILD_ID 20260925.6191901): the
|
|
# timers are client settings system_idle_suspend_{ac,battery}_sec (0 = Never),
|
|
# written the way Steam's settings page does (steamui module exporting the
|
|
# SetSetting wrapper). logind refuses an inhibitor from an SSH session
|
|
# ("Interactive authentication required") but allows one from a user unit.
|
|
ssh "${ssh_opts[@]}" "$FRAME_ALIAS" python3 - "$cmd" <<'EOF'
|
|
import json, os, subprocess, sys
|
|
sys.path.insert(0, os.path.expanduser("~/.cache/frame-control"))
|
|
from frame_steam import Page
|
|
|
|
cmd = sys.argv[1]
|
|
saved_path = os.path.expanduser("~/.cache/frame-control/keep-awake.json")
|
|
unit = "fc-keep-awake"
|
|
keys = ("system_idle_suspend_ac_sec", "system_idle_suspend_battery_sec")
|
|
|
|
if cmd == "off": # release the lock first, even if Steam's UI is down
|
|
subprocess.run(["systemctl", "--user", "stop", unit], stderr=subprocess.DEVNULL)
|
|
page = Page()
|
|
def read():
|
|
return {k: page.eval(f"settingsStore.clientSettings.{k}") for k in keys}
|
|
def write(values):
|
|
page.eval("""(async () => { let req;
|
|
webpackChunksteamui.push([[Symbol()], {}, r => { req = r }]);
|
|
const mod = Object.keys(req.m).map(id => req.m[id].toString().includes("Settings.SetSetting") ? req(id) : null).find(Boolean);
|
|
const set = Object.values(mod).find(f => typeof f == "function" && f.toString().includes("SetSetting("));
|
|
for (const [k, v] of Object.entries(%s)) await set(k, v);
|
|
await new Promise(r => setTimeout(r, 1000)); })()""" % json.dumps(values))
|
|
def inhibitor():
|
|
return subprocess.run(["systemctl", "--user", "is-active", "-q", unit]).returncode == 0
|
|
|
|
if cmd == "on":
|
|
current = read()
|
|
if not os.path.exists(saved_path):
|
|
with open(saved_path, "w") as f:
|
|
json.dump(current, f)
|
|
write({k: 0 for k in keys})
|
|
if not inhibitor():
|
|
subprocess.run(["systemd-run", "--user", "-q", f"--unit={unit}",
|
|
"--description=Frame Control: keep the Frame awake",
|
|
"systemd-inhibit", "--what=sleep:idle:handle-suspend-key:handle-power-key",
|
|
"--who=Frame Control", "--why=Keep the Frame awake while an agent works on it",
|
|
"--mode=block", "sleep", "infinity"], check=True)
|
|
elif cmd == "off":
|
|
if os.path.exists(saved_path): # no backup: leave the timers as they are
|
|
with open(saved_path) as f:
|
|
write(json.load(f))
|
|
os.remove(saved_path)
|
|
|
|
print(json.dumps({"timers": read(), "inhibitor": inhibitor()}))
|
|
EOF
|