104 lines
6.6 KiB
Markdown
104 lines
6.6 KiB
Markdown
# Steam Frame
|
||
|
||
How to build, deploy and run dashboards on the Steam Frame, and what was learned on the
|
||
device. Facts below were verified on a Frame with SteamOS `holo` 0.4.3 (system Qt 6.8.0,
|
||
Adreno 750 GPU with Mesa Turnip).
|
||
|
||
## Building
|
||
|
||
Frame builds run in a Debian container (Docker or Podman), either cross-compiled on an x86_64
|
||
PC (no emulation) or natively on the Frame:
|
||
|
||
```sh
|
||
scripts/container-build.sh cross # on the PC → build/cross-arm64/stage
|
||
scripts/container-build.sh native # on the Frame itself (podman/distrobox)
|
||
scripts/container-build.sh cross-bundle # fallback variant, Qt bundled
|
||
scripts/container-build.sh cross shell # a shell in the build container
|
||
```
|
||
|
||
**System Qt (default).** Builds use SteamOS's own Qt; packages are about 3 MB.
|
||
- The container builds against Debian's Qt 6.8.2; at runtime the Frame's Qt 6.8.0 is used.
|
||
Qt keeps binary compatibility within 6.8, as long as the program uses no Qt-internal API.
|
||
- **QML is compiled to bytecode only** (`VRDASH_QML_BYTECODE_ONLY`), because Qt's QML-to-C++
|
||
compiler output is tied to the exact Qt version.
|
||
- The binary then needs only the stable Qt API, `qt_version_tag` (Qt ≥ 6.8), and Qt's Vulkan
|
||
wrapper functions. Verified with an Arch container frozen at Qt 6.8.0 and on the Frame.
|
||
- After a SteamOS update with a different Qt, rebuild, or use the bundled variant.
|
||
|
||
**Bundled Qt (fallback, `-bundle` modes, about 46 MB).**
|
||
- Qt, its plugins and QML modules, and their non-system dependencies are copied next to the
|
||
program (`cmake/BundleQt.cmake`).
|
||
- The GPU and display stack (GL/EGL, Vulkan loader, Wayland, X11, libffi, glibc) always comes
|
||
from SteamOS; bundling it would break GPU access.
|
||
- The Debian copyright files of all bundled libraries are included, as their licenses require.
|
||
|
||
## Packaging and deploying
|
||
|
||
```sh
|
||
scripts/package-release.sh # → dist/<exe>-<version>-linux-arm64/ + .tar.gz
|
||
scripts/package-release.sh --bundle-qt # → …-linux-arm64-bundled
|
||
scripts/deploy-to-frame.sh steamos@<frame> # rsync the newest package to ~/<exe>, run install.sh
|
||
```
|
||
|
||
A release folder contains the program, `install.sh` (install or update: stops a running
|
||
instance, registers with SteamVR, starts it), `uninstall.sh`, `README.txt` and the licenses.
|
||
The app logs to `logs/frame-assistant.log` in that folder (passed by the manifest), and
|
||
needs a settings file first (`~/.config/frameassistant/config.json`, from
|
||
`share/frameassistant/config.example.json`; see README.md, "Settings").
|
||
SteamVR stores the folder's absolute path, so after moving the folder, run `install.sh`
|
||
again.
|
||
|
||
Without the deploy script: copy the `.tar.gz`, unpack it on the Frame, run `./install.sh`
|
||
(SteamVR must be running). Over SSH this works too: SteamVR starts the dashboard itself.
|
||
|
||
## What the Frame requires (all handled by the overlay platform)
|
||
|
||
| Finding | Consequence |
|
||
|---------|-------------|
|
||
| SteamVR is installed system-wide under `/opt/steamvr`; its log is `~/.local/share/Steam/logs/vrserver.txt` | Look there when a manifest or start fails |
|
||
| The manifest needs **`binary_path_linux_arm`**; entries with only `binary_path_linux` are silently skipped ("must specify binary_path") | The manifest template picks the key from the target architecture |
|
||
| SteamVR refuses **`VRApplication_Utility`** (`VRInitError_Init_NotAvailableToUtilityApps`) | Manifest commands connect as `VRApplication_Background` (needs a running SteamVR, never starts one) |
|
||
| An executable named **`vrdashboard`** is taken for SteamVR's own dashboard (its watchdog then "restarts" it) | Executable `frame-assistant`; CMake rejects SteamVR process names |
|
||
| **OpenGL runs through zink** on Vulkan (no native GL driver for the Adreno) | Vulkan is the default: 1.01 ms vs 1.89 ms per frame (`--benchmark`, 1600×1000) |
|
||
| OpenVR must shut down **before** the graphics device is destroyed | Teardown order in `DashboardApplication::runOverlay()` (else a crash at exit with Vulkan) |
|
||
| Qt needs a display connection even for off-screen rendering; over SSH none is set | SteamVR-launched instances inherit it; over SSH, set `DISPLAY=:0` |
|
||
| Process names are cut to 15 characters | Scripts find running instances by command line, not by name |
|
||
| Floated/pinned dashboard tabs stay visible and operable in games | Rendering follows the overlay's visibility, not the dashboard's |
|
||
|
||
## Libraries on the Frame
|
||
|
||
Checked 2026-10-05; details per requirement in [REQUIREMENTS.md](REQUIREMENTS.md), Appendix C.
|
||
|
||
- **System Qt 6.8.0** under `/usr/lib` includes, besides the base modules: DBus, Network,
|
||
Multimedia (FFmpeg plugin), WebSockets, WebEngine (Core, Quick), WebView, TextToSpeech,
|
||
Positioning, Sensors, Svg, VirtualKeyboard. TLS plugins: `libqopensslbackend.so`,
|
||
`libqcertonlybackend.so`.
|
||
- It apparently comes with the KDE Plasma desktop mode (plugins for plasma, kwin, discover).
|
||
**SteamVR does not use Qt 6:** nothing under `/opt/steamvr` links or ships `libQt6*`. So the
|
||
system Qt is not a SteamVR dependency, which is one more reason to keep the bundled-Qt
|
||
fallback.
|
||
- **Qt WebEngine on the Frame can't be loaded** (checked 2026-10-05): `qt6-webengine` and
|
||
`qt6-webchannel` are 6.7.0 while the rest of Qt is 6.8.0, and WebEngine was built against
|
||
FFmpeg 6 (`libavcodec.so.60`, `libavformat.so.60`, `libavutil.so.58`); the Frame has FFmpeg
|
||
7.0 (`.61`/`.59`) and an old FFmpeg 4 (`.58`/`.56`). Only `qt6-webview` and `signon-ui`
|
||
depend on it. A program linking WebEngine fails to start ("libavcodec.so.60: cannot open
|
||
shared object file"). Other Qt modules are 6.8.0 (`pacman -Q | grep qt6-`). Not yet
|
||
checked: whether Qt Multimedia's FFmpeg plugin has the same problem.
|
||
- **Qt add-on modules must come from the system Qt.** Shipping one fails: they use Qt's private
|
||
API, and QtCore aborts on any version mismatch ("Cannot mix incompatible Qt library").
|
||
- **Not present:** any MQTT library (no `libmosquitto`, no Qt MQTT). FrameAssistant ships
|
||
libmosquitto.
|
||
- **Present:** OpenSSL 3.2.1 (`libssl.so.3`, `libcrypto.so.3`), glibc 2.39, `libcjson.so.1`
|
||
(1.7.17), `libpulse.so.0`, `libpipewire-0.3.so.0`, and the tools `wpctl`, `pactl`, `nmcli`.
|
||
- **Libraries we ship must not need newer symbol versions** than the Frame has (e.g.
|
||
`OPENSSL_3.x`, `GLIBC_2.x`): check with `readelf -V`. Debian's libmosquitto needs at most
|
||
`OPENSSL_3.0.0` and `GLIBC_2.34`.
|
||
|
||
## Notes
|
||
|
||
- **The Frame's package repositories** in `/etc/pacman.conf` are private (URLs marked
|
||
"DO_NOT_SHARE"). Don't put them into scripts or this repository.
|
||
- **Valve's public Holo Core preview container** (`registry.gitlab.steamos.cloud/holo/
|
||
holo-core-aarch64-preview/base-devel`) ships Qt 6.10, newer than the Frame's 6.8. It is not
|
||
usable for system-Qt builds.
|