Devices: several headsets, several addresses each, and live connection status

Frame Control can now manage more than one Steam Frame, and reach each at any
of several addresses (LAN IPs per network, its .local name, Tailscale). A
connector in the server tries them all at once, picks the best one that
answers, follows ssh -v through each stage (network, finding, SSH, identity,
login) and streams that to the page. The header shows it live; a new Devices
tab (key 5) manages headsets, addresses and network names.

- ui/frame_devices.py: registry in devices.json, imported from the managed
  ~/.ssh/config blocks; per-headset host key pinning; config block updates.
- ui/frame_network.py: gateway IP+MAC fingerprint, Wi-Fi name, Tailscale.
- ui/frame_link.py: the connector, Test now, Tailscale/mDNS discovery, API.
- server.py: ensure_master delegates to the connector; /api/connection,
  /api/connection/events (SSE), /api/devices.
- Electron: headset switcher and Devices item in the Frame menu.
- frame_connect.py --alias; FRAME_CONTROL_DATA_DIR / FRAME_CONTROL_SSH_DIR
  keep tests off real data.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-28 21:13:58 +10:00
1 parent dcf9689f64
commit a954fc83c9
24 files changed
+3597 -61

No files matched your search

+5 -2
View File
@@ -33,8 +33,11 @@ class HostError(RuntimeError):
def data_dir(*parts):
"""Per-user app data: ~/Library/Application Support, %APPDATA% or $XDG_DATA_HOME."""
if MAC:
"""Per-user app data: ~/Library/Application Support, %APPDATA% or $XDG_DATA_HOME
(or $FRAME_CONTROL_DATA_DIR, which the tests point at a throwaway directory)."""
if os.environ.get("FRAME_CONTROL_DATA_DIR"):
base = Path(os.environ["FRAME_CONTROL_DATA_DIR"])
elif MAC:
base = Path.home() / "Library" / "Application Support" / "Frame Control"
elif WINDOWS:
base = Path(os.environ.get("APPDATA") or Path.home() / "AppData" / "Roaming") / "Frame Control"