# 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/--linux-arm64/ + .tar.gz scripts/package-release.sh --bundle-qt # → …-linux-arm64-bundled scripts/deploy-to-frame.sh steamos@ # rsync the newest package to ~/, 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. 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 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.