mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-25 07:01:42 +01:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d79efb4730 | ||
|
|
95643b7ea0 | ||
|
|
9dab523af9 | ||
|
|
a29fac2594 | ||
|
|
6d5cb20847 | ||
|
|
987c36c5f6 | ||
|
|
649185237f | ||
|
|
5b43170fda | ||
|
|
958743c79d | ||
|
|
05308932e8 | ||
|
|
4e4853e82b | ||
|
|
3438c248d9 |
No files matched your search
@@ -2,6 +2,11 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Import a base APK with supported native-library or asset splits from a local
|
||||
Android backup or XAPK/APKS/APKM bundle. Check package and version before
|
||||
install; refuse resource, code and feature splits and device-variant sets.
|
||||
This route has local fixture coverage, not a real split-game headset test.
|
||||
|
||||
- Save Steam screenshots under a separate folder for each headset, Steam
|
||||
account and game. Equal screenshot filenames no longer overwrite one another;
|
||||
existing files in the old flat folder are left in place.
|
||||
|
||||
@@ -148,7 +148,7 @@ Get-FileHash .\Frame-Control-Setup-x64.exe -Algorithm SHA256
|
||||
|
||||
The [source](https://github.com/saphid/frame-control) and [release build workflow](.github/workflows/release.yml) are public. The Windows installer is **not code-signed**, so SmartScreen may warn. The macOS app has only an **ad hoc signature**, not an Apple Developer ID signature; the DMG is **unsigned and not notarized**, so Gatekeeper may warn or block it. Only follow the opening steps below if you trust the download.
|
||||
|
||||
日本語: v0.4.26 以降は公式リリースの SHA256SUMS とファイルの SHA-256 値を照合してください。Windows は未署名、macOS は開発者 ID による署名・公証がありません。
|
||||
日本語: [5 ステップの接続ガイド](docs/connection-faq.md#日本語-5-ステップで接続)を参照してください。v0.4.26 以降は公式リリースの SHA256SUMS とファイルの SHA-256 値を照合してください。Windows は未署名、macOS は開発者 ID による署名・公証がありません。
|
||||
|
||||
**Which build?** On a Mac, check Apple menu → About This Mac: the download is
|
||||
for Apple Silicon (M-series); no Intel Mac build is published. On Windows,
|
||||
@@ -245,6 +245,14 @@ network. Guest Wi-Fi/client isolation can prevent discovery and connections.
|
||||
check Developer Mode, the address and network isolation, then run setup again.
|
||||
For connections away from home, see [Tailscale](docs/tailscale.md).
|
||||
|
||||
**Does Frame Control lower streaming lag?** No. Frame Control manages the Frame
|
||||
over SSH. SteamVR or Steam Link carries the game stream. Frame Control does not
|
||||
carry that stream, lower ping, or need the Wireless Adapter. It can connect over
|
||||
Wi-Fi, the Wireless Adapter, or USB when your computer has an SSH route to the
|
||||
Frame. The wired doctor checks only that SSH route. A data-capable USB-A-to-C
|
||||
cable has not been tested for the Frame's USB network. See the
|
||||
[connection FAQ and Japanese quick start](docs/connection-faq.md).
|
||||
|
||||
**What it changes:** only what you click. Installs go to your user account on
|
||||
the Frame (`--user` Flatpaks, Lepton instances, Steam downloads, sideloaded
|
||||
games in `~/devkit-game`), and nothing
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "frame-control",
|
||||
"version": "0.4.26",
|
||||
"version": "0.4.30",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frame-control",
|
||||
"version": "0.4.26",
|
||||
"version": "0.4.30",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^44.4.5",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frame-control",
|
||||
"productName": "Frame Control",
|
||||
"version": "0.4.26",
|
||||
"version": "0.4.30",
|
||||
"description": "Desktop app for managing a Valve Steam Frame over SSH",
|
||||
"private": true,
|
||||
"main": "main.js",
|
||||
|
||||
+10
-4
@@ -4,7 +4,7 @@ A SideQuest backup can contain a small APK and much larger `main.…obb` and
|
||||
`patch.…obb` files. All of them belong to the same game. Installing only the APK
|
||||
can leave the game unable to start because its assets are missing.
|
||||
|
||||
In **Add / Install → Import Android game** (or **Apps → Browse → Import Android game**), choose the APK and its OBB files together.
|
||||
In **Add / Install → Import Android game** (or **Apps → Browse → Import Android game**), choose the base APK, any supported splits, and its OBB files together.
|
||||
You can also drop them anywhere in the window. In the desktop app, dropping the
|
||||
APK alone finds matching OBB files beside it; dropping a folder containing the
|
||||
APK and OBBs also works. Browsers can read only the files you explicitly choose.
|
||||
@@ -37,9 +37,13 @@ are discarded after use or cancellation; abandoned imports expire after an hour
|
||||
when the importer is next used, and dead-server staging folders are swept at
|
||||
startup.
|
||||
|
||||
This flow handles one ordinary APK and standard OBB files for one game at a time.
|
||||
Split APKs, XAPK/APKS archives, nested Android/data folders and private save
|
||||
archives need their own import handling. PC ZIP/folder/EXE imports keep their
|
||||
This flow handles one base APK, supported native-library or asset splits, and
|
||||
standard OBB files for one game at a time. It checks the package and version in
|
||||
each split manifest before touching the headset. The existing Quest merger
|
||||
rejects resource, code and feature splits and sets with multiple device
|
||||
variants. A merged APK is re-signed; that can affect a game's licence or login.
|
||||
XAPK/APKS/APKM archives use the same checks. Nested Android/data folders and
|
||||
private save archives remain unsupported. PC ZIP/folder/EXE imports keep their
|
||||
existing route. Quest USB and Meta library imports keep their existing workflow.
|
||||
|
||||
Validation covers actual APK parsing, the three-file backup layout, package
|
||||
@@ -55,3 +59,5 @@ released, and the battery remained at 99%. See [device evidence](evidence/androi
|
||||
The headset was unworn; this checks transfer and persistence, not rendering,
|
||||
Resident Evil 4 gameplay, or a game that exits before data can be copied. This
|
||||
flow does not change APK compatibility or remove licence checks.
|
||||
The supported-split backup route was checked with generated local fixtures and
|
||||
existing merger tests, not a real split game on the Frame.
|
||||
+11
-9
@@ -436,13 +436,13 @@ permissions remain unverified. They do not change APK install or launch behavior
|
||||
## Local APK/OBB backups and Android bundles
|
||||
|
||||
In **Android apps → Import Android game → Choose game files…** (or **Send to Frame**), choose one
|
||||
standalone APK and its matching `main.<version>.<package>.obb` and/or
|
||||
base APK, any supported splits, and its matching `main.<version>.<package>.obb` and/or
|
||||
`patch.<version>.<package>.obb` files together. The desktop app also discovers
|
||||
matching OBB files beside a lone selected APK. Browser uploads use only the
|
||||
files you selected. Installed apps can receive OBB-only imports.
|
||||
|
||||
You can instead select **one `.xapk`, `.apks`, or `.apkm` archive** containing
|
||||
one standalone APK and optional matching OBBs. These extensions describe
|
||||
one base APK, optional supported splits and matching OBBs. These extensions describe
|
||||
containers, not a guarantee that their contents are supported. OBBs may be
|
||||
at the archive root, under `<package>/`, or under `Android/obb/<package>/`;
|
||||
one enclosing backup folder is allowed. Known manifest/icon/signature
|
||||
@@ -450,7 +450,7 @@ sidecars are ignored; the APK manifest determines the package. Unknown files,
|
||||
unsafe paths, links, encrypted entries, duplicate paths (including case
|
||||
collisions), more than 4,096 entries, or more than 16 GiB unpacked are refused.
|
||||
Staging also keeps 64 MiB of free disk space. Payloads still have the regular
|
||||
one-APK/99-OBB limit. Ordinary `.zip` files keep the PC-title import route;
|
||||
100-file/99-OBB limit. Ordinary `.zip` files keep the PC-title import route;
|
||||
extract an Android ZIP yourself and select its APK/OBBs.
|
||||
|
||||
The importer previews the app and files before **Import game & data**.
|
||||
@@ -463,12 +463,14 @@ data setup. Uploaded and extracted staging is removed on cancellation,
|
||||
inspection failure, or job completion/failure; abandoned imports expire
|
||||
through the existing one-hour staging lifecycle. Original local files stay intact.
|
||||
|
||||
**Limits:** this backup importer refuses multiple APKs, split-only APKs and
|
||||
bases marked as requiring splits. It does not select device variants or restore
|
||||
`Android/data`, private app data, saves, or entitlements. For the separate
|
||||
restricted native-library/asset split workflow see
|
||||
[Quest split APKs](quest-import.md#split-apks) and the CLI above. Resource,
|
||||
code and feature splits are not supported by that merger.
|
||||
**Limits:** this backup importer requires exactly one base APK. For split sets,
|
||||
each split must identify the same package and version, and pass the existing
|
||||
[Quest split merger](quest-import.md#split-apks). It refuses resource, code and
|
||||
feature splits and does not choose among device variants. Merging re-signs the
|
||||
APK, which can affect licences. It does not restore `Android/data`, private app
|
||||
data, saves or entitlements. These split imports have local fixture and merger
|
||||
tests, but no real split game has been installed with this backup route on the
|
||||
Frame.
|
||||
|
||||
For issue #102, the RE4 report only establishes that an APK attempt failed.
|
||||
Missing OBBs are a hypothesis; the edition/build, exact files, failing step,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Connection and streaming lag FAQ
|
||||
|
||||
## Does Frame Control lower my streaming lag?
|
||||
|
||||
No. Frame Control uses SSH to manage the Frame. SteamVR or Steam Link sends the
|
||||
game stream. Frame Control does not carry the game stream. It cannot lower your
|
||||
game-stream ping or fix a stream that stops on the PC. A 40–75 ms ping in a VR
|
||||
game is not a Frame Control measurement.
|
||||
|
||||
You do **not** need the Steam Wireless Adapter to use Frame Control. The app can
|
||||
connect over Wi-Fi, the Wireless Adapter, or USB if the computer has an SSH
|
||||
route to the Frame. Use the route that lets the app connect. To improve game
|
||||
streaming, check the SteamVR or Steam Link connection on the PC and the Frame.
|
||||
The [wired connection doctor](wired-connection.md) checks only Frame Control's
|
||||
SSH route. A successful doctor check does not prove that the game stream uses
|
||||
the cable or has low lag.
|
||||
|
||||
## Can I use a USB-A-to-C cable?
|
||||
|
||||
A Mac reached the Frame over a USB-C connection on the
|
||||
`10.86.200.232/29` USB network. That test used a USB-C host port. We have
|
||||
**not tested a USB-A-to-C cable** with the Frame. A data-capable USB-A-to-C
|
||||
cable might show a USB network interface on your PC, but this is unknown on
|
||||
the tested Frame OS and PC hosts. Check whether your PC shows that interface
|
||||
and whether Frame Control's wired doctor can reach the Frame at
|
||||
`10.86.200.233`. A charge-only cable cannot carry USB data. You can use Wi-Fi
|
||||
for Frame Control if the USB route does not work.
|
||||
|
||||
## 日本語: 5 ステップで接続
|
||||
|
||||
1. [公式リリース](https://github.com/saphid/frame-control/releases/latest)から PC の OS 用ファイルをダウンロードします。Windows は `.exe`、macOS は `.dmg`、Linux は `.AppImage` または `.deb` です。同じリリースの `SHA256SUMS` でファイルを確認してください。
|
||||
2. PC に Frame Control をインストールして起動します。Windows はスタートメニュー、macOS はアプリケーション、Linux はアプリ一覧から開きます。AppImage の場合は実行権限を付けて開きます。
|
||||
3. Frame の Steam 設定 → システムで開発者モードを有効にし、開発者設定でユーザーパスワードを設定します。Frame と PC を同じ信頼できるネットワークに接続し、Frame を起こしておきます。
|
||||
4. PC の Frame Control で **Set Up Connection…** を選びます。Frame が見つからない場合は、Frame の設定で IP アドレスを確認して入力します。macOS では **Devices & connection → + Add a headset…** からアドレスを設定できます。
|
||||
5. Frame に **Pair new host** が出たら、自分が開始した要求だけを承認します。ペアリングに失敗した場合は、PC のターミナルで開発者モードのパスワードを入力します。接続後、Frame Control に戻って状態を確認します。
|
||||
|
||||
Frame Control は管理用の SSH 接続を使います。ゲーム映像は SteamVR / Steam Link が送ります。Frame Control を入れてもゲームの ping は下がりません。無線アダプターは必須ではありません。USB-A-to-C ケーブルで Frame の USB ネットワークが使えるかは、まだ確認できていません。
|
||||
@@ -1,6 +1,6 @@
|
||||
# What we have tested
|
||||
|
||||
Current for Frame Control **v0.4.26**, 25 October 2026. “Real Frame + physical PC” means an actual headset and a non-virtual computer. “VM only” means a Windows virtual machine, sometimes connected to the real Frame. A CI test or a simulated device is **not** a physical hardware test. The date/version column is the last relevant check, not a promise that every new release was retested on that platform. See each link for the exact procedure and limits.
|
||||
Current for Frame Control **v0.4.30**, 25 October 2026. “Real Frame + physical PC” means an actual headset and a non-virtual computer. “VM only” means a Windows virtual machine, sometimes connected to the real Frame. A CI test or a simulated device is **not** a physical hardware test. The date/version column is the last relevant check, not a promise that every new release was retested on that platform. See each link for the exact procedure and limits.
|
||||
|
||||
| Feature | Real Frame + physical PC tested | VM only | Untested or limited | Last check and proof |
|
||||
|---|---|---|---|---|
|
||||
@@ -10,13 +10,18 @@ Current for Frame Control **v0.4.26**, 25 October 2026. “Real Frame + physical
|
||||
| OBS headset source on Linux | Real Frame to OBS 30 on a Linux host; H.264 video and a synthetic 440 Hz output tone reached a saved recording | No | Real game audio, audible monitoring, perceptual A/V sync and awake headset view | 25 Oct, PR #174 head, shipped in v0.4.22: [OBS log](evidence/obs-media-source-real-2026-10-25.md) |
|
||||
| OBS headset source on Windows | No physical Windows PC test | No OBS/Frame VM end-to-end test | App Start/Stop path has automated Windows checks only | 25 Oct, v0.4.23: [Windows PR #179](https://github.com/saphid/frame-control/pull/179), [OBS guide](obs-headset-source.md) |
|
||||
| OBS headset source on macOS | No | No | Helper and OBS Media Source not tested on a Mac with a Frame | 25 Oct, v0.4.22: [OBS PR #174](https://github.com/saphid/frame-control/pull/174), [OBS guide](obs-headset-source.md) |
|
||||
| OBS headset source on a SteamOS / Steam Machine PC | No SteamOS PC or Flatpak OBS runtime test | No SteamOS host VM test | v0.4.27 AppImage launched on Ubuntu x86_64 under Xvfb; the helper sent H.264/AAC from a real Frame to a local UDP receiver. SteamOS read-only root, desktop UI and Flatpak OBS remain unverified | 25 Oct, v0.4.27: [host test](evidence/obs-steamos-host-2026-10-25.md), [OBS guide](obs-headset-source.md#steamos--steam-machine-pc-host) |
|
||||
| Wired connection doctor | No physical PC/Frame cable test of the doctor | No | Synthetic route/SSH tests; a separate Mac USB-C address observation does not test the doctor or game stream | 25 Oct, v0.4.23: [PR #178](https://github.com/saphid/frame-control/pull/178), [wired guide](wired-connection.md) |
|
||||
| Windows Wireless Adapter doctor | No physical Steam Wireless Adapter/dropout test | Windows 11 VM ran read-only checks without the adapter or WLAN | Adapter identification, dropouts and recovery on hardware | 24–25 Oct, v0.4.20–0.4.25: [VM log](evidence/windows-wireless-2026-10-24.md), [doctor guide](windows-wireless-doctor.md), [comparison PR #184](https://github.com/saphid/frame-control/pull/184) |
|
||||
| Linux Wireless Adapter doctor | No Steam Wireless Adapter hardware test | No | Read-only Linux host checks and mocked adapter states; no adapter-specific hardware observation | 24 Oct, v0.4.20: [PR #165](https://github.com/saphid/frame-control/pull/165), [doctor guide](linux-wireless-adapter-doctor.md) |
|
||||
| Switch PC windows | No physical Windows PC test | No real VM-to-Frame Alt+Tab check recorded | Explicit chord is covered by tests; actual window focus, keyboard layouts and controller interaction are unverified | 25 Oct, v0.4.24: [PR #181](https://github.com/saphid/frame-control/pull/181), [PC guide](pc-in-headset.md) |
|
||||
| Keyboard and trackpad from Frame Control | A Linux PC sent a click and ASCII keys to a temporary focused window on the real Frame | No | Worn-headset interaction, Unicode delivery and pixel-accurate pointing | 24 Oct, v0.4.19: [received-event log](evidence/pad-gamescope-2026-10-24.md), [PR #66](https://github.com/saphid/frame-control/pull/66) |
|
||||
| Backup and restore after reset | Same-headset backup, no-op save restore and Return to Game Mode checks | No | Restore **after a factory reset**, replacement restore and Android data restore have not been tested on a headset. No headset reset was done | 24 Oct, v0.4.17: [backup guide](backup-and-recovery.md), [Return to Game Mode log](evidence/return-game-mode-packaged-2026-10-24.json) |
|
||||
| Android game backup import | F-Droid Basic APK with synthetic main and patch OBBs installed, transferred and read back on a real Frame | No | v0.4.30 restricted split-APK backup route has generated-fixture and existing merger tests only; no real split game or RE4 gameplay test | 20 Oct OBB headset check; 25 Oct, v0.4.30 split fixtures: [OBB device evidence](evidence/android-import/device.json), [split PR #206](https://github.com/saphid/frame-control/pull/206), [guide](android-backups.md) |
|
||||
| Passthrough in recordings | No active-passthrough mirror check | No | Standby frame was uniform; whether visible room pixels enter `/dev/video99` is **unknown**. Raw cameras are never captured by Frame Control | 25 Oct, v0.4.21: [headset log](evidence/round3-headset-2026-10-25.md), [recording guide](recording.md#does-record-include-passthrough) |
|
||||
| Open at login | No Frame needed; Linux autostart file toggled on a physical Linux host with an isolated config directory | No | A full login session and physical Windows/macOS host login have not been checked; OS API calls were unit tested | 25 Oct, v0.4.26: [PR #189](https://github.com/saphid/frame-control/pull/189) |
|
||||
| Local Frame Control extensions | Example panel displayed live Frame status in a browser preview; the real Frame served a read-only example in the experimental headset panel | No | No wearer-visible extension view; third-party extensions and physical Windows/macOS extension folders untested | 25 Oct, v0.4.29: [PR #194](https://github.com/saphid/frame-control/pull/194), [PR #201](https://github.com/saphid/frame-control/pull/201), [extension guide](extensions.md) |
|
||||
| Experimental Frame Control SteamVR panel | Real Frame OS 0.5.6 installed its user service, registered an overlay, served authenticated live status and a read-only extension, saved a screenshot and short MP4, and removed owned resources | No | Headset was unworn in standby: overlay reported `not_visible`, stereo capture was dark; wearer-visible pixels and controller input are unverified | 25 Oct, v0.4.29: [PR #198](https://github.com/saphid/frame-control/pull/198), [PR #201](https://github.com/saphid/frame-control/pull/201), [panel guide](headset-app.md) |
|
||||
| Experimental Decky Loader manager | Real Frame OS 0.5.6 ARM64 user-service install, status, update and remove completed; ProtonDB Badges loaded in Decky backend and Steam shared UI registry | No | Decky and plugin UI in the VR shell, wearer-visible badges, controller input and other plugins remain unverified | 25 Oct, v0.4.29: [PR #195](https://github.com/saphid/frame-control/pull/195), [PR #202](https://github.com/saphid/frame-control/pull/202), [headset findings](decky-frame.md) |
|
||||
|
||||
Release notes must preserve these limits until a newer recorded check changes them. When a feature is retested, add its date, exact app/Frame version, host type and proof here. The older [v0.3.1 matrix](support-matrix.html) is a historical snapshot and does not describe current coverage.
|
||||
+36
-4
@@ -4,7 +4,8 @@
|
||||
0.5.6 and injects into Steam's shared CEF context. Its Decky tab registers in
|
||||
JavaScript. A usable plugin menu in the Frame's VR shell has **not** been
|
||||
verified. Frame Control therefore labels its user-service manager experimental.
|
||||
No individual Decky plugin has been tested on the headset.
|
||||
ProtonDB Badges loaded in the backend and Gamepad UI plugin registry, but its
|
||||
VR user interface has not been seen or used.
|
||||
|
||||
## What was checked on the real headset
|
||||
|
||||
@@ -35,6 +36,33 @@ remained in Steam's running web helper until that helper was restarted. After a
|
||||
web helper restart, both `deckyHasLoaded` and the tab hook were absent. All
|
||||
temporary trial files and the test's empty managed data folder were removed.
|
||||
|
||||
## ProtonDB Badges compatibility test
|
||||
|
||||
The [ProtonDB Badges v1.1.0 release](https://github.com/OMGDuke/protondb-decky/releases/tag/v1.1.0)
|
||||
was tested on the same Frame OS build. It is a popular, passive Decky plugin
|
||||
(133 GitHub stars at test time) that adds badges to game pages. The published
|
||||
archive SHA-256 was
|
||||
`b4c72ff0ffe60b1a14abafe75a62f644cbb6689f398a753a1905c281151d9173`.
|
||||
Its six archive entries had no links or path traversal, and its manifest declared
|
||||
`flags: []` and `api_version: 2`. The release contains a frontend bundle and
|
||||
no backend `main.py`; its source backend only manages its own settings.
|
||||
|
||||
The test copied the archive into Frame Control's managed Decky plugin folder,
|
||||
then installed the official Decky 3.3.0 ARM64 binary as a user service. Decky's
|
||||
log said `Plugin ProtonDB Badges is passive` and `Loaded ProtonDB Badges`.
|
||||
`DeckyPluginLoader.plugins` in Steam's `SharedJSContext` contained
|
||||
`ProtonDB Badges`; `deckyHasLoaded` was true there. The separate `SteamVR`
|
||||
CEF target had no Decky globals or plugin text, and SteamVR listed no Decky
|
||||
overlay. The headset was in standby, so no in-headset plugin page or badge
|
||||
interaction was verified. The backend's unauthenticated root URL returned
|
||||
HTTP 403, as expected.
|
||||
|
||||
The test removed the managed Decky service and binary, the test plugin folder,
|
||||
and its shared-device lock. The service was no longer running, the plugin list
|
||||
was empty, the binary and unit were gone, and port 1337 was closed. Decky's
|
||||
already injected frontend may remain in the running Steam web helper until
|
||||
Steam normally restarts; the test did not restart Steam or SteamVR.
|
||||
|
||||
## Why the official Decky installer is not used
|
||||
|
||||
The current [Decky installer](https://github.com/SteamDeckHomebrew/decky-installer/blob/e42a3c828de90673959b4de26cf5c5d2c7830554/cli/install_release.sh)
|
||||
@@ -94,6 +122,10 @@ Control refuses to install alongside an upstream system service.
|
||||
In the headset, open Steam's VR dashboard and look for Decky's plugin tab or
|
||||
Quick Access Menu after an opt-in install. If none appears, inspect whether
|
||||
the VR shell exposes a different menu component and whether Decky's frontend
|
||||
can target it without patching Steam globally. Then test one harmless plugin's
|
||||
install, visible UI, restart persistence and removal. Until those checks pass,
|
||||
Frame Control should not describe Decky plugins as supported on the Frame.
|
||||
can target it without patching Steam globally. Confirm the plugin's visible
|
||||
UI, controller interaction, and persistence across a normal Steam restart.
|
||||
Until those checks pass,
|
||||
Frame Control should not describe Decky plugins as usable in VR yet. A worn
|
||||
headset check is needed for both the Decky menu and the Frame Control SteamVR
|
||||
panel; only then can their controller interaction and plugin presentation be
|
||||
confirmed.
|
||||
@@ -0,0 +1,32 @@
|
||||
# SteamOS PC OBS host feasibility check, 2026-10-25 UTC
|
||||
|
||||
The available x86_64 host, lxso2, runs Ubuntu 24.04.3. It has no `flatpak`
|
||||
command, OBS installation, SteamOS 3.9.5 VM or physical Steam Machine. This
|
||||
check therefore does **not** verify that Frame Control or Flatpak OBS runs on
|
||||
a SteamOS desktop or a read-only-root PC host.
|
||||
|
||||
The downloaded v0.4.27 Linux x86_64 AppImage matched GitHub's SHA-256 digest
|
||||
`42d774653c69b193c3d577743c32b8fe8dfeee7ea1f8cdf7369902962d647b83`.
|
||||
With `APPIMAGE_EXTRACT_AND_RUN=1`, it launched under Xvfb using an isolated
|
||||
profile and its bundled server returned HTTP 200. FUSE mounting, physical
|
||||
display rendering and SteamOS-specific dependencies were not tested.
|
||||
|
||||
The v0.4.27 tagged OBS helper then ran against the real Frame under the shared
|
||||
`/tmp/frame-test.lock`. Frame OS was 0.5.6 and SteamVR activity level was 3
|
||||
(standby). A receiver bound to `127.0.0.1:15099` on lxso2 accepted 497
|
||||
MPEG-TS packets, 93,436 bytes. Every datagram had complete 188-byte packets
|
||||
with sync bytes. `ffprobe` found H.264 video and AAC audio. Closing the
|
||||
helper's control pipe made it exit 0; no owned remote stream process remained.
|
||||
Battery read 100% Full before and after, and the owned lock was released. The
|
||||
temporary `.ts` file was removed. This proves the local UDP sender path on an
|
||||
Ubuntu host; it does not prove Flatpak OBS could receive it on SteamOS.
|
||||
|
||||
The public
|
||||
[OBS Flatpak manifest](https://github.com/flathub/com.obsproject.Studio/blob/master/com.obsproject.Studio.json)
|
||||
lists `--share=network`, which permits use of the host network. We read that
|
||||
manifest but did not install or run the Flatpak. A future check needs a real
|
||||
SteamOS 3.9.5 desktop host or VM with Flatpak OBS: launch the AppImage without
|
||||
root changes, start the source, record from the Media Source at
|
||||
`udp://127.0.0.1:15099`, verify video and audible game audio, then stop and
|
||||
confirm cleanup. The standby test cannot establish real game audio, A/V sync
|
||||
or active passthrough in the mirror.
|
||||
@@ -56,6 +56,42 @@ The [follow-up Linux/Frame transport check](evidence/obs-followup-2026-10-25.md)
|
||||
measured packet continuity and a short MP4 recording on the current SteamOS
|
||||
build. The headset was still in standby.
|
||||
|
||||
## SteamOS / Steam Machine PC host
|
||||
|
||||
Use **Desktop Mode** on the SteamOS PC. Download the Linux x86_64 AppImage to
|
||||
your home folder. Make it executable and open it there; SteamOS has a
|
||||
read-only system root, so these steps do not use `apt` or change `/usr`:
|
||||
|
||||
```sh
|
||||
chmod +x ~/Downloads/Frame-Control-linux-x86_64.AppImage
|
||||
~/Downloads/Frame-Control-linux-x86_64.AppImage
|
||||
```
|
||||
|
||||
If AppImage mounting reports a FUSE error, try
|
||||
`APPIMAGE_EXTRACT_AND_RUN=1 ~/Downloads/Frame-Control-linux-x86_64.AppImage`.
|
||||
This extracts to writable temporary space and needs enough free disk space.
|
||||
Set up the Frame's SSH connection in Frame Control, then use **Start OBS
|
||||
source** before you add or activate OBS's Media Source. Install OBS as the
|
||||
`com.obsproject.Studio` Flatpak through Discover or your normal user Flatpak
|
||||
setup. In that OBS Media Source, clear **Local File**, set **Input** to
|
||||
`udp://127.0.0.1:15099`, and set **Input Format** to `mpegts`. Make a short
|
||||
recording and check both picture and sound before a stream. Use **Stop OBS
|
||||
source** in Frame Control when done.
|
||||
|
||||
The OBS Flatpak's published manifest includes `--share=network`, which is
|
||||
needed for its local UDP receiver. Check your installed permissions with
|
||||
`flatpak info --show-permissions com.obsproject.Studio` if OBS receives no
|
||||
packets. Frame Control's helper sends to loopback on this PC; it does not
|
||||
require an open LAN port or an OBS plugin. The PC does not need a system
|
||||
`ffmpeg` package for the helper; encoding runs on the Frame.
|
||||
|
||||
**Test limit:** The v0.4.27 AppImage launched on an Ubuntu 24.04 x86_64 host
|
||||
under Xvfb. Its helper sent H.264 and AAC from a real Frame to a local UDP
|
||||
receiver on that host. No SteamOS 3.9.5 PC, Steam Machine, Flatpak OBS runtime,
|
||||
read-only-root host, or physical display was available for this check. The
|
||||
SteamOS steps and Flatpak receiver therefore remain **untested on SteamOS**.
|
||||
See the [test record](evidence/obs-steamos-host-2026-10-25.md).
|
||||
|
||||
## Known issue: game audio in OBS
|
||||
|
||||
A Frame owner [reported](https://x.com/AzizahStellazio/status/2114147449468293171)
|
||||
|
||||
+30
-9
@@ -1,10 +1,29 @@
|
||||
# Open questions and on-device checks
|
||||
|
||||
Research as of 2026-09-25, eight days after the Frame's retail release
|
||||
(2026-09-18). Most first-party detail comes from Valve's Steamworks developer
|
||||
pages. Searches of Reddit and the Steam forums turned up **almost no
|
||||
end-user reports** about SSH, desktop streaming, or macOS. Treat that as
|
||||
"not documented yet", not "doesn't work".
|
||||
The September sections below record early checks after the Frame's retail
|
||||
release (2026-09-18). They are a historical test log, not a current setup
|
||||
guide. For current connection steps, see [SteamOS October 2026
|
||||
compatibility](steamos-2026-10-compat.md). For the charge-limit result, see
|
||||
[charge-limit.md](charge-limit.md).
|
||||
|
||||
## Current Frame OS 0.5.6 status (2026-10-25)
|
||||
|
||||
- `frame.local` no longer resolves on the checked 0.5.6 beta build
|
||||
(`20261022.6153909`). Avahi was active but did not publish addresses. The
|
||||
configured Tailscale SSH alias worked. Frame Control can use a saved IP on
|
||||
the same network after verifying the pinned SSH key, or a manual IP/SSH
|
||||
alias. Same-LAN fallback scanning was **not** tested on this Tailscale route.
|
||||
See the [live compatibility check](evidence/steamos-compat-live-2026-10-25.md).
|
||||
- Read-only checks on that build found no SteamOS Manager
|
||||
`BatteryChargeLimit1` interface or `charge_control_*` attribute. The vendor
|
||||
charging switches are root-owned and managed by Valve's charger service.
|
||||
No charging control was written. An 80% limit is **not supported** by Frame
|
||||
Control; see [charge-limit.md](charge-limit.md) and the
|
||||
[headset check](evidence/round3-headset-2026-10-25.md).
|
||||
- Active passthrough in the recording mirror remains **unknown**: the only
|
||||
captured mirror frame was uniform while the headset was unworn in standby.
|
||||
A worn, active passthrough check is still needed; see the
|
||||
[headset check](evidence/round3-headset-2026-10-25.md).
|
||||
|
||||
## Verified on device (2026-09-25)
|
||||
|
||||
@@ -14,7 +33,9 @@ build 20260922.6101926, kernel 6.18, aarch64):
|
||||
- **1–2.** Developer Mode + Set User Password gave working SSH with no terminal
|
||||
steps. `sshd` is enabled and active. The user is `steamos` (in `wheel`) and
|
||||
the hostname is `frame`.
|
||||
- **3.** `frame.local` resolves from the Mac; `avahi-daemon` is active.
|
||||
- **3.** `frame.local` resolved from the Mac on the September build;
|
||||
`avahi-daemon` was active. This result does not apply to the checked 0.5.6
|
||||
beta build; see the current status above.
|
||||
- **5.** `/etc/ssh/sshd_config` has `Include /etc/ssh/sshd_config.d/*.conf`.
|
||||
The existing drop-ins are `20-systemd-userdb.conf` and `99-archlinux.conf`, so
|
||||
`01-frame-keys-only.conf` would sort first as intended. (`--harden` itself
|
||||
@@ -148,9 +169,9 @@ check in the headset:
|
||||
Can an 80% charge limit be set over SSH or adb? **Not safely yet.** SteamOS
|
||||
Manager's `BatteryChargeLimit1` (the interface Steam's own setting uses) has
|
||||
no backend in the Frame's device config, and the battery node is a
|
||||
`max1720x` fuel gauge with no `charge_control_end_threshold`. Research and
|
||||
read-only headset checks are in [charge-limit.md](charge-limit.md). Not tested
|
||||
on a headset.
|
||||
`max1720x` fuel gauge with no `charge_control_end_threshold`. Read-only checks
|
||||
on the real Frame confirmed this on 0.5.6 beta; no charge setting was written.
|
||||
See [charge-limit.md](charge-limit.md).
|
||||
|
||||
## Unconfirmed claims made in these docs
|
||||
|
||||
|
||||
@@ -25,3 +25,19 @@ uses the cable. The Wireless Adapter and Steam streaming can follow a different
|
||||
path. No cable is attached to the Linux test host, so this exact UI has only
|
||||
synthetic route/SSH tests. Windows and Linux physical USB-C behavior, and a
|
||||
fresh wired readback on the current Frame OS, remain unverified.
|
||||
|
||||
## If SSH works but the game stream stops
|
||||
|
||||
If the stream fails on USB, Wi-Fi and the Wireless Adapter, but this doctor
|
||||
reports a working SSH route, check SteamVR on the **PC**. Frame Control's
|
||||
`frame.local` reconnect only restores its headset management connection. It
|
||||
does not restart PC SteamVR or repair the game stream. On a Windows or Linux
|
||||
PC, save your work and close the VR game. Exit SteamVR from Steam on that PC,
|
||||
then start it again. This can show whether a PC SteamVR session is stuck; it
|
||||
does not fix the cause of repeated drops. SteamVR on macOS is not a supported
|
||||
PC streaming host.
|
||||
|
||||
Frame Control has no verified cross-platform command that safely stops only
|
||||
the intended PC SteamVR session. A forced restart could close a running game
|
||||
and lose unsaved progress. For that reason the app gives manual steps and does
|
||||
not add a one-click restart button.
|
||||
@@ -16,6 +16,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'ui'))
|
||||
import frame_android_import as backup
|
||||
import server
|
||||
from apk_fixtures import apk, manifest
|
||||
from test_quest import apk_bytes
|
||||
|
||||
PKG = 'com.Armature.VR4'
|
||||
META = {'package': PKG, 'label': PKG, 'version': '', 'version_code': 210,
|
||||
@@ -66,6 +67,41 @@ class BackupTests(unittest.TestCase):
|
||||
with self.assertRaisesRegex(backup.android.FrameError, 'unique names'):
|
||||
backup.inspect([str(self.apk), str(self.obb[0]), str(duplicate)])
|
||||
|
||||
def test_supported_split_set_uses_manifest_base_and_existing_merge_path(self):
|
||||
base = self.root / 'not-named-base.apk'
|
||||
base.write_bytes(apk_bytes(PKG, split_required=True, version_code=210))
|
||||
split = self.root / 'arm64.apk'
|
||||
split.write_bytes(apk_bytes(PKG, split='config.arm64_v8a', version_code=210,
|
||||
files={'lib/arm64-v8a/libgame.so': b'game'}))
|
||||
plan = backup.inspect([str(split), str(base)])
|
||||
self.assertEqual(plan['apk'], base)
|
||||
self.assertEqual(plan['splits'], [split])
|
||||
self.assertEqual(backup.public(plan)['splits'], ['arm64.apk'])
|
||||
with patch.object(backup.quest, '_running', return_value=False), \
|
||||
patch.object(backup.android, 'read_meta', return_value=None), \
|
||||
patch.object(backup.android, 'install', return_value=META) as install:
|
||||
backup.install(plan)
|
||||
self.assertEqual(install.call_args.kwargs['splits'], [str(split)])
|
||||
|
||||
def test_split_mismatch_and_unsupported_resource_fail_before_frame_access(self):
|
||||
base = self.root / 'base.apk'
|
||||
base.write_bytes(apk_bytes(PKG, split_required=True, version_code=210))
|
||||
split = self.root / 'split.apk'
|
||||
with patch.object(backup.android, 'ssh') as ssh:
|
||||
split.write_bytes(apk_bytes(PKG, split='config.arm64_v8a', version_code=211,
|
||||
files={'lib/arm64-v8a/libgame.so': b'game'}))
|
||||
with self.assertRaisesRegex(backup.android.FrameError, 'version cannot be verified'):
|
||||
backup.inspect([str(base), str(split)])
|
||||
split.write_bytes(apk_bytes(PKG, split='config.xxhdpi', version_code=210,
|
||||
files={'res/drawable/icon.png': b'png'}))
|
||||
with self.assertRaisesRegex(backup.android.FrameError, 'screen-density or language resources'):
|
||||
backup.inspect([str(base), str(split)])
|
||||
split.write_bytes(apk_bytes(PKG, split='config.x86_64', version_code=210,
|
||||
files={'lib/x86_64/libgame.so': b'game'}))
|
||||
with self.assertRaisesRegex(backup.android.FrameError, 'another device'):
|
||||
backup.inspect([str(base), str(split)])
|
||||
ssh.assert_not_called()
|
||||
|
||||
def test_obb_only_repairs_existing_game_and_requests_apk_when_not_installed(self):
|
||||
with patch.object(backup.android, 'read_meta', return_value=None):
|
||||
with self.assertRaisesRegex(backup.android.FrameError, 'Add its APK'):
|
||||
@@ -204,6 +240,21 @@ class BundleTests(unittest.TestCase):
|
||||
self.assertTrue(all(not Path(p).exists() for p in folders))
|
||||
self.assertTrue(source.exists())
|
||||
|
||||
def test_bundle_with_supported_split_uses_same_preview_and_cleanup(self):
|
||||
source = self.bundle({
|
||||
'base.apk': apk_bytes(PKG, split_required=True, version_code=210),
|
||||
'config.arm64.apk': apk_bytes(PKG, split='config.arm64_v8a', version_code=210,
|
||||
files={'lib/arm64-v8a/libgame.so': b'game'}),
|
||||
}, suffix='.apks')
|
||||
with patch.object(backup.android, 'ssh') as ssh:
|
||||
read = server.android_backup({'action': 'inspect', 'paths': [str(source)]})
|
||||
ssh.assert_not_called()
|
||||
self.assertEqual(read['plan']['apk'], 'base.apk')
|
||||
self.assertEqual(read['plan']['splits'], ['config.arm64.apk'])
|
||||
folders = list(server._android_backups[read['token']]['dirs'])
|
||||
server.android_backup({'action': 'discard', 'token': read['token']})
|
||||
self.assertTrue(all(not Path(p).exists() for p in folders))
|
||||
|
||||
def test_browser_upload_bundle_is_staged_and_removed_with_extraction(self):
|
||||
source = self.bundle()
|
||||
token = server.android_backup({'action': 'begin'})['token']
|
||||
@@ -221,7 +272,7 @@ class BundleTests(unittest.TestCase):
|
||||
|
||||
def test_reject_layouts_before_any_headset_access_and_cleanup_failed_stage(self):
|
||||
cases = [
|
||||
({'base.apk': self.apk.read_bytes(), 'split.apk': self.apk.read_bytes()}, 'Split APK'),
|
||||
({'base.apk': self.apk.read_bytes(), 'split.apk': self.apk.read_bytes()}, 'exactly one base'),
|
||||
({'base.apk': self.apk.read_bytes(), f'Android/data/{PKG}/save': b'private'}, 'restore'),
|
||||
({'base.apk': self.apk.read_bytes(), 'main.1.org.other.game.obb': b'wrong'}, 'does not belong'),
|
||||
({'base.apk': self.apk.read_bytes(), '../outside.obb': b'bad'}, 'unsafe'),
|
||||
@@ -229,7 +280,7 @@ class BundleTests(unittest.TestCase):
|
||||
({'base.apk': self.apk.read_bytes(), 'CON.apk': b'bad'}, 'unsafe'),
|
||||
({'base.apk': self.apk.read_bytes(), 'other.exe': b'bad'}, 'Unsupported'),
|
||||
({'base.apk': self.apk.read_bytes(), 'BASE.apk': b'bad'}, 'duplicate'),
|
||||
({'manifest.json': b'{}'}, 'standalone APK'),
|
||||
({'manifest.json': b'{}'}, 'base APK'),
|
||||
]
|
||||
for entries, message in cases:
|
||||
with self.subTest(message=message), patch.object(backup.android, 'ssh') as ssh:
|
||||
@@ -277,7 +328,8 @@ class BundleTests(unittest.TestCase):
|
||||
('x' * len('isSplitRequired')).encode('utf-16-le'))
|
||||
self.apk.write_bytes(apk({'AndroidManifest.xml': data}))
|
||||
with self.subTest(attrs=attrs):
|
||||
with self.assertRaisesRegex(backup.android.FrameError, 'needs split APKs'):
|
||||
message = 'exactly one base' if attrs.get('split') else 'needs split APKs'
|
||||
with self.assertRaisesRegex(backup.android.FrameError, message):
|
||||
backup.inspect([str(self.apk)])
|
||||
|
||||
def test_bundle_install_uses_existing_queue_and_cleans_after_failure(self):
|
||||
|
||||
+58
-14
@@ -131,10 +131,30 @@ def unpack_bundle(path, root):
|
||||
raise android.FrameError('Could not read Android bundle: ' + str(error)) from error
|
||||
paths = [str(local) for local, _, _, _ in entries]
|
||||
if not any(Path(p).suffix.lower() == '.apk' for p in paths):
|
||||
raise android.FrameError('Android bundles must contain one standalone APK and optional OBB files.')
|
||||
raise android.FrameError('Android bundles must contain a base APK and optional supported splits or OBB files.')
|
||||
return paths
|
||||
|
||||
|
||||
def _apk_identity(path):
|
||||
"""Use the manifest, not the filename, to identify a base or split APK."""
|
||||
try:
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
elements = frame_apk.manifest_elements(
|
||||
frame_apk._read(archive, 'AndroidManifest.xml', frame_apk.MAX_MANIFEST))
|
||||
attrs = next((attrs for tag, attrs in elements if tag == 'manifest'), {})
|
||||
package = (attrs.get('package') or (None, None, None))[2]
|
||||
split = (attrs.get('split') or (None, None, None))[2]
|
||||
code = (attrs.get('versionCode') or (None, None, None))[1]
|
||||
major = (attrs.get('versionCodeMajor') or (None, 0, None))[1]
|
||||
if not package or not android.PKG_RE.fullmatch(package):
|
||||
raise android.FrameError(f'{path.name}: no valid package in APK manifest')
|
||||
return package, split, (major, code) if code is not None else None
|
||||
except (zipfile.BadZipFile, frame_apk.ApkError, OSError, UnicodeError,
|
||||
ValueError, RuntimeError, EOFError, zlib.error, struct.error,
|
||||
IndexError) as error:
|
||||
raise android.FrameError(f'Could not read APK manifest in {path.name}: {error}') from error
|
||||
|
||||
|
||||
def _check_standalone(path):
|
||||
try:
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
@@ -168,19 +188,41 @@ def inspect(paths, discover=False):
|
||||
if path.suffix.lower() not in ('.apk', '.obb') or not path.is_file() or path.stat().st_size == 0:
|
||||
raise android.FrameError(f'{path.name}: choose a nonempty APK or .obb game-data file.')
|
||||
apks = [p for p in files if p.suffix.lower() == '.apk']
|
||||
if len(apks) > 1:
|
||||
raise android.FrameError('Choose one game at a time. Split APK backups are not supported by this importer.')
|
||||
apk = apks[0] if apks else None
|
||||
apk, splits = None, []
|
||||
if apks:
|
||||
identities = [(p, *_apk_identity(p)) for p in apks]
|
||||
bases = [p for p, _, split, _ in identities if not split]
|
||||
if len(bases) != 1:
|
||||
raise android.FrameError('Choose one game at a time: exactly one base APK is required.')
|
||||
apk = bases[0]
|
||||
package, version = next((pkg, code) for p, pkg, _, code in identities if p == apk)
|
||||
split_names = set()
|
||||
for path, pkg, split, code in identities:
|
||||
if path == apk:
|
||||
continue
|
||||
if pkg != package or split in split_names:
|
||||
raise android.FrameError('Split APKs must have the same package and distinct split names.')
|
||||
if version is None or code is None or code != version:
|
||||
raise android.FrameError('Split APK version cannot be verified against the base APK.')
|
||||
split_names.add(split)
|
||||
splits.append(path)
|
||||
if apk:
|
||||
_check_standalone(apk)
|
||||
if not splits:
|
||||
_check_standalone(apk)
|
||||
info = android.apk_info(str(apk))
|
||||
try:
|
||||
android.check_installable(info)
|
||||
except android.FrameError as error:
|
||||
error.apk = {'package': info['package'], 'version_code': info.get('version_code'),
|
||||
'blocker': str(error)}
|
||||
raise
|
||||
checked = quest.inspect(info['package'], str(apk))
|
||||
if not splits:
|
||||
try:
|
||||
android.check_installable(info)
|
||||
except android.FrameError as error:
|
||||
error.apk = {'package': info['package'], 'version_code': info.get('version_code'),
|
||||
'blocker': str(error)}
|
||||
raise
|
||||
if splits:
|
||||
added = android.split_entries(str(apk), [str(p) for p in splits])
|
||||
if any(name.startswith('lib/') and not name.startswith('lib/arm64-v8a/') for name in added):
|
||||
raise android.FrameError('This split set includes native libraries for another device. '
|
||||
'Choose only the arm64-v8a split for the Frame.')
|
||||
checked = quest.inspect(info['package'], str(apk), [str(p) for p in splits])
|
||||
pkg = checked['package']
|
||||
if discover and len(files) == 1:
|
||||
files.extend(p for p in sorted(apk.parent.iterdir())
|
||||
@@ -213,13 +255,14 @@ def inspect(paths, discover=False):
|
||||
if not path.is_file() or not path.stat().st_size or path.name in names:
|
||||
raise android.FrameError(f'{path.name}: game-data files must be nonempty and have unique names.')
|
||||
names.add(path.name)
|
||||
return {'apk': apk, 'obb': obb, 'checked': checked,
|
||||
return {'apk': apk, 'splits': splits, 'obb': obb, 'checked': checked,
|
||||
'signatures': {str(p): (p.stat().st_size, p.stat().st_mtime_ns) for p in [*apks, *obb]}}
|
||||
|
||||
|
||||
def public(plan):
|
||||
checked = plan['checked']
|
||||
return {**checked, 'apk': plan['apk'].name if plan['apk'] else None,
|
||||
'splits': [p.name for p in plan['splits']],
|
||||
'obb': [{'name': p.name, 'size': p.stat().st_size} for p in plan['obb']],
|
||||
'size': sum(size for size, _ in plan['signatures'].values())}
|
||||
|
||||
@@ -243,7 +286,8 @@ def install(plan, display='auto', progress=None):
|
||||
quest._check_downgrade(android.read_meta(pkg), checked, False)
|
||||
report('Installing the game app', 15)
|
||||
meta = android.install(str(plan['apk']), source=plan['apk'].name,
|
||||
flatscreen=None if display == 'auto' else display == 'flat')
|
||||
flatscreen=None if display == 'auto' else display == 'flat',
|
||||
splits=[str(p) for p in plan['splits']])
|
||||
else:
|
||||
meta = android._meta_or_fail(pkg)
|
||||
if not plan['obb']:
|
||||
|
||||
+4
-3
@@ -1373,7 +1373,7 @@
|
||||
<div class="store-owned" id="storeOwned">
|
||||
<section class="panel" id="androidBackup">
|
||||
<div class="shelf-head"><h2>Import Android game</h2></div>
|
||||
<p class="sub">Have a backup? Choose the game's APK and all its .obb files together, or one XAPK/APKS/APKM bundle containing a standalone APK. Split sets and Android/data restore are not supported here.
|
||||
<p class="sub">Have a backup? Choose the game's base APK, supported native-library or asset splits, and all its .obb files together, or one XAPK/APKS/APKM bundle. Resource, code and feature splits and Android/data restore are not supported here.
|
||||
Frame Control installs the app and puts its data in the right place.</p>
|
||||
<button class="action small" id="androidBackupChoose">Choose game files…</button>
|
||||
<input type="file" id="androidBackupInput" accept=".apk,.obb,.xapk,.apks,.apkm" multiple hidden>
|
||||
@@ -1916,7 +1916,7 @@
|
||||
</main>
|
||||
</div>
|
||||
<div class="dropzone" id="dropzone" hidden><div><b>Drop to send to the Frame</b>
|
||||
<span class="sub">Android backups: drop an APK with its OBB files, or one XAPK/APKS/APKM bundle (standalone APK only).
|
||||
<span class="sub">Android backups: drop a base APK with supported splits and OBB files, or one XAPK/APKS/APKM bundle.
|
||||
Other files go to <code>~/Downloads</code>;
|
||||
a game's <code>.zip</code>, folder or <code>.exe</code> becomes a Steam library title.</span></div></div>
|
||||
|
||||
@@ -3980,7 +3980,8 @@ async function importAndroidFiles(files, dev, probe = false) {
|
||||
if (!dlg.open) return true;
|
||||
const plan = read.plan;
|
||||
$("androidImportName").textContent = `${plan.label}${plan.version ? " · " + plan.version : ""}`;
|
||||
$("androidImportFiles").innerHTML = (plan.apk ? `<div class="sub">Game app</div><div>${esc(plan.apk)}</div>`
|
||||
$("androidImportFiles").innerHTML = (plan.apk ? `<div class="sub">Game app</div><div>${esc(plan.apk)}</div>` +
|
||||
(plan.splits || []).map(f => `<div>${esc(f)} <span class="sub">split APK</span></div>`).join("")
|
||||
: `<div class="sub">Game app already installed</div>`) +
|
||||
`<div class="sub" style="margin-top:12px">Game data · ${plan.obb.length} file${plan.obb.length === 1 ? "" : "s"}</div>` +
|
||||
plan.obb.map(f => `<div>${esc(f.name)} <span class="sub">${gb(f.size)}</span></div>`).join("");
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
<section><div class="row"><label for="alias">Your configured headset SSH alias<input id="alias" value="frame" maxlength="64" autocomplete="off"></label>
|
||||
<button id="check">Check cable connection</button></div><p id="message" role="status" aria-live="polite">No checks have run.</p></section>
|
||||
<div id="results" aria-live="polite"></div>
|
||||
<section><h2>SSH works, but the PC stream still fails?</h2>
|
||||
<p>The doctor checks Frame Control's SSH route only. If the SteamVR stream stops on USB, Wi-Fi and the Wireless Adapter, the problem may be on the PC. On a Windows or Linux PC, first save your work and close the VR game. Exit SteamVR from Steam on the PC, then start SteamVR again. Frame Control reconnecting to <code>frame.local</code> will not restart PC SteamVR. This is a manual test, not a fix for the cause of repeated drops. Frame Control does not restart PC SteamVR automatically because it cannot safely know which game and session it would stop.</p></section>
|
||||
<p class="sub">The observed USB address is 10.86.200.233 on Frame OS tested in 2026. A different OS build may use another subnet. An unknown result does not prove the cable is broken. The doctor never changes IP settings, accepts a new host key, or bypasses SSH warnings.</p>
|
||||
</main><script>
|
||||
'use strict';
|
||||
|
||||
Reference in new issue
Block a user