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>
- Only stored and deflated entries are read: Python 3.9's bzip2 and lzma
readers inflate without bound before trimming.
- loadTitles drops a response that a newer request has overtaken, so the
install dialog's replace warning uses the fresh list.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- APK reader: read members with a bounded read, since ZipFile.read inflates
a member fully before trimming it to a forged declared size; the reference
walk counts every entry it examines, dead ends and cycles included.
- Titles: a path that exists under the root wins over stripping the archive
prefix; the prefix is taken before a linked folder is staged elsewhere
(another drive on Windows); the install dialog loads a fresh title list
first and says so if it couldn't check.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- APK reader: cap AndroidManifest.xml, resources.arsc and icon sizes before
inflating them (APKs can come from install links), and follow resource
references without cycles and with a result budget.
- Sideloading: reserve devkit-steam (SteamOS's sideloaded-client trampoline);
the install dialog warns when a name replaces an installed title; a
manifest's exe may name the program as it is in the archive, above the
folder the installer steps into.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- frame_apk: android: attributes win over same-named attributes in
other namespaces; string attributes that keep only a typed value (no
raw string) still resolve; a failed icon read leaves the icon out
instead of failing the install.
- The clipboard IPC origin check can't throw on odd frame URLs.
- fetch-deps.js: 60 s download timeout, at most 5 redirects, a SystemRoot
fallback for tar.exe, and prunes pydoc_data, venv and the static
libpython.
- Docs keep the clipboard-tool note for running the UI in a browser.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- app/build/fetch-deps.js downloads a standalone Python 3.12
(python-build-standalone) for every build and adb from Google's
platform-tools, pinned by SHA-256, and prunes what the server never
uses. It replaces the Windows-only embeddable Python.
- The app runs the bundled Python with -I -u -B -X utf8, so a PYTHONHOME
or PYTHONPATH meant for another Python can't break it and nothing is
written inside the signed macOS bundle.
- An adb you already have still goes first, so two adb versions don't
keep restarting each other's server. arm64 Linux has no official
platform-tools and keeps using the system adb.
- ui/frame_apk.py reads APK badging (package, label, version, min SDK,
ABIs, icon) from the binary manifest and resources.arsc, replacing
aapt2. It matches aapt2 on nine F-Droid APKs and finds launcher icons
the old path missed with adaptive icons.
- The app reads the computer's clipboard through Electron, so Linux no
longer needs wl-clipboard or xclip.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>