Files
Ryan Yin c732f3b9bb refactor(vr): route headset audio with a WirePlumber hook
A default-node hook selects wivrn.sink/wivrn.source whenever they exist,
so the polling wivrn-audio.nu unit, its saved state and `just vr-audio`
go away. Streams follow via linking.follow-default-target.

Assisted-by: Claude Code (claude-opus-5-5)
2026-11-01 11:55:14 -06:00

146 lines
7.3 KiB
Markdown

# Quest 3 PC VR on idols-ai
WiVRn streams PC VR games to Quest 3. It supplies the OpenXR runtime; xrizer translates OpenVR
games. This host uses the server and command-line tools without the Dashboard. SteamVR does not need
to run, but compatibility still depends on the game and translation layer.
## First activation and persistence
Run `just niri` on idols-ai. Log out and back in so Steam sees
`PRESSURE_VESSEL_IMPORT_OPENXR_1_RUNTIMES=1`. That variable is what makes pressure-vessel mount
WiVRn's `$XDG_RUNTIME_DIR/wivrn/comp_ipc` socket into a game's container; without it the game's
OpenXR client fails with `ERROR_RUNTIME_UNAVAILABLE` and the headset stays black.
The root filesystem is a 4 GiB tmpfs. These directories are preserved with mode 0700:
- `~/.config/wivrn/`: server settings, paired headset public keys and discovery cookie.
- `~/.android/`: ADB host keys, so USB authorization survives a reboot.
Before the first activation, migrate any existing directories to their matching locations under
`/persistent/home/<user>/`. Stop WiVRn first. If both locations contain data, reconcile them before
activating; do not overwrite existing pairing state or ADB keys. Do not display or upload ADB
private keys, or copy them into this repository.
OpenXR/OpenVR runtime pointers and sockets remain temporary. WiVRn switches runtime pointers while a
headset is connected and restores previous configuration when it disconnects. Do not manage these
pointers as read-only Home Manager files, persist IPC sockets, or delete existing runtime
configurations blindly.
## Wireless connection and pairing
1. Connect the PC by Ethernet and Quest 3 to the trusted home LAN (`192.168.5.0/24`). Prefer 5 GHz
or 6 GHz Wi-Fi near the access point. Guest Wi-Fi or client isolation can block discovery and
streaming.
2. Install the WiVRn 26.9 client on the headset: the Meta Store build is packaged as
`org.meumeu.wivrn`, the GitHub release APK as `org.meumeu.wivrn.github`. Client and server
versions must match; check the installed server with `wivrn-server --version` after updates.
3. The server does not start automatically at login or boot. From this repository on idols-ai, start
it when you want to use VR:
```console
just vr
just vr-status
```
`just vr-stop` stops it again. Starting the service does not enable it for future logins.
4. Allow a new headset to pair for two minutes:
```console
wivrnctl pair --duration 2
```
This prints a PIN. Open WiVRn on the headset, connect to `ai`, and enter the PIN. If discovery
fails, enter `192.168.5.100` manually. Reconnection after pairing does not require running this
command again.
5. Audio follows the headset: WiVRn's `wivrn.sink` and `wivrn.source` exist only while a headset is
connected, and a WirePlumber hook in [`ai/vr.nix`](./ai/vr.nix) makes them the defaults for
exactly that time; playing streams follow. Your own default devices stay saved and return when
the headset disconnects. Check with `pactl get-default-sink` and `pactl get-default-source`.
Manage paired headsets with `wivrnctl list-paired`, `wivrnctl rename`, or `wivrnctl unpair`. Consult
`wivrnctl --help` and the subcommand's `--help` for arguments.
## Service, settings and logs
Manage the server through systemd so starts retain the CAP_SYS_NICE wrapper:
```console
systemctl --user restart wivrn
just vr-stop
just vr-status
journalctl --user -u wivrn -b -n 100 --no-pager
just vr-logs
```
Service logs use the host's existing persistent journal. There is no separate Dashboard log folder
or log exporter. For server settings, edit `~/.config/wivrn/config.json` following the WiVRn 26.9
configuration schema, then restart the service. The NixOS module also supports declarative server
configuration; an enabled declarative config takes precedence over the user file. Headset-side
settings remain available in the headset application.
## Games and the RTX 4090
The RTX 4090 is the only GPU on this host, so the WiVRn server, the compositor and the game land on
the same card by themselves.
That is not how it used to be. While `ai` ran the Z890 board, its Arrow Lake iGPU did the desktop
rendering under PRIME offload, and a game left on the iGPU while the server ran on the dGPU failed
with `vkAllocateMemory: VK_ERROR_OUT_OF_DEVICE_MEMORY` and `xrt_comp_create_swapchain failed` in
`journalctl --user -u wivrn`, followed by an xrizer panic that killed the game. The fix then was the
variables below, written out as Steam launch options because Steam runs inside a bubblewrap sandbox
that does not mount `/run`, so it cannot see `/run/current-system/sw/bin/nvidia-offload`:
```text
__NV_PRIME_RENDER_OFFLOAD=1 __NV_PRIME_RENDER_OFFLOAD_PROVIDER=NVIDIA-G0 __GLX_VENDOR_LIBRARY_NAME=nvidia __VK_LAYER_NV_optimus=NVIDIA_only %command%
```
It mattered for headset-launched games too: WiVRn starts applications in separate systemd units, and
an already running Steam client keeps its own environment. Re-add those variables only if a second
GPU comes back. Select a Proton version according to the game's compatibility reports; GE-Proton or
DW-Proton alone does not guarantee VR compatibility.
## USB connection
Enable Quest developer mode, connect a data-capable USB cable, and accept the headset's USB
debugging authorization prompt. ADB is already installed in the desktop profile. The current systemd
handles USB access for the active local session. Developer mode is needed for USB or sideloading,
not for the Meta Store wireless workflow.
For sideloading, download the matching official APK and use `adb install -r <apk-path>`. The GitHub
release APK is packaged as `org.meumeu.wivrn.github`, the Meta Store build as `org.meumeu.wivrn`;
pass the package you actually installed.
Pair over Wi-Fi first. For an already paired headset with the GitHub APK installed:
```console
adb devices
adb reverse tcp:9757 tcp:9757
adb shell am start -a android.intent.action.VIEW -d "wivrn+tcp://127.0.0.1:9757" org.meumeu.wivrn.github
```
If multiple devices are connected, add `-s <serial>` to each ADB command. Disable headset Wi-Fi
while validating USB so the test cannot silently use wireless streaming. Recreate the reverse
mapping after disconnecting USB.
## Encoding and hardware validation
The package enables Vulkan Video encoding but does not enable NVENC. WiVRn 26.9 probes Vulkan
encoding before falling back to VAAPI or x264 when NVENC is absent. Check the journal's encoder
configuration after connecting: GPU activity alone does not prove hardware encoding. Vulkan encoding
in this release supports H.264/H.265, not AV1. NVENC/AV1 requires a CUDA-enabled WiVRn build.
- Pair, connect, launch a game, and check both compositor and game GPU usage:
`nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv` has to list both
`wivrn-server` and the game.
- Confirm the journal has no `vkAllocateMemory` or `xrt_comp_create_swapchain` failures, and that
`~/.local/state/xrizer/xrizer.txt` has no `ERROR_RUNTIME_UNAVAILABLE`.
- Restart with systemd and verify the server remains owned by `wivrn.service`.
- Reboot the PC and reconnect without pairing again; verify saved settings and USB authorization
remain intact.
- Test audio routing (`just vr` and `just vr-stop`), microphone, controllers, and a supported
OpenXR/OpenVR game.
Configuration and package checks do not replace these headset tests.