Files
FrameAssistant/docs/STEAM-FRAME.md
T
matzmanandClaude Opus 5.5 c5c176644a Remove the HA Dashboard tab and the Qt WebEngine dependency (D40)
DSH-1 ended with a negative result: the Frame's system WebEngine can't be
loaded, and bundling our own failed (no sandbox, ICU resources, Vulkan
abort, empty page). Reverts the tab (60deb43), the container packages
(0676998) and the bundler's WebEngine support (b823ff3, e2cfa8e), and
updates the docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:04:24 +02:00

7.2 KiB
Raw Blame History

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:

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

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, Appendix C.

  • System Qt 6.8.0 under /usr/lib includes, besides the base modules: DBus, Network, Multimedia (FFmpeg plugin), WebSockets, WebEngine (Core, Quick; not loadable, see below), 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-). Qt Multimedia's FFmpeg plugin resolves all its libraries (ldd, no "not found").
  • Bundling our own Qt WebEngine failed (experiment, 2026-10-05; Debian's Qt 6.8.2 incl. WebEngine with its own FFmpeg, 141 MB package): the helper process (QtWebEngineProcess, needs the executable bit) only starts without Chromium's sandbox (--no-sandbox); without QTWEBENGINE_RESOURCES_PATH its child processes find no ICU data ("Invalid file descriptor to ICU data"); with Vulkan the app aborts ("free(): invalid pointer"); with OpenGL the dashboard tab stays empty; in a desktop window the page stays white and Chromium's remote-debugging port never opens. FrameAssistant uses no web engine (D40).
  • 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.