# DRM/KMS Direct Mode — Deferred to Post-Launch **Status:** Deferred (too much risk before launch) **Created:** 2026-08-17 --- ## Goal Remove Cage (Wayland compositor) from -dev kiosk variants and run RadiantWave directly on DRM/KMS via SDL3's built-in `SDL_VIDEODRIVER=drm` backend. This simplifies the stack, removes compositor overhead, and is better suited for embedded/hardware kiosks. **Decision:** Deferring until after the current launch. Cage works, and the transition touches every variant's boot pipeline. --- ## What Cage Currently Provides | Feature | How Cage Does It | DRM/KMS Replacement | |---------|------------------|---------------------| | Display mode setting | Opens `/dev/dri/card*` via libseat, selects connector/CRTC/mode | SDL3 DRM backend opens DRM devices directly | | Fullscreen surface | Creates Wayland surface, manages lifecycle | SDL3 creates DRM framebuffer surface | | Page flipping / VSync | Handles `drmHandlePageFlip()` | SDL3 DRM backend handles page flip | | Input devices | Opens `/dev/input/*` via libseat, generates Wayland events | SDL3 DRM backend reads evdev directly | | Keyboard mapping | XKB keymap loading, key repeat | SDL3 handles keymap translation | | Seat management | Connects to seatd via libseat | Not needed — group permissions (`video`, `input`) suffice | --- ## What Stays Safe Without Cage | Concern | Why It's Safe | |---------|---------------| | **Audio** | PipeWire/ALSA works independently of the compositor. App uses SDL3_mixer -> ALSA. Audio init service still runs before launch. | | **WiFi** | NetworkManager uses system bus (`sd_bus_open_system()` in `network_manager.cpp`). No session bus needed. `networkmanager` group handles D-Bus access. | | **Keyboard/input** | SDL3's DRM backend reads `/dev/input/*` evdev directly. `input` group grants access. | | **DRM/display** | SDL3's DRM backend opens `/dev/dri/card*` directly. `video` group grants access. | | **Plymouth** | `sudo -n plymouth quit --wait` still works — Plymouth runs as root on DRM/KMS. | | **Auto-login** | getty autologin still works on tty1. | | **Health check** | `pgrep -f '.radiantwave-unwrapped'` still works — no wrapper changes. | | **Backup service** | Runs as kiosk user, rsyncs `~/.local/radiantwave`. No compositor dependency. | --- ## Implementation Plan — 4 Phases All changes are in **kioskconfig only**. The radiantwave app needs no code changes — SDL3 handles DRM/KMS internally when `SDL_VIDEODRIVER=drm` is set. The NixOS system already provides Mesa/EGL/GBM via `hardware.graphics.enable = true`. Only **-dev** variants are modified. Production configs stay on Cage. ### New module: `kiosk-drm.nix` A NixOS module that overrides the Wayland/cage setup: - `environment.systemPackages = mkForce [...]` — same list as `configuration.nix` but **without `pkgs.cage`** - `environment.loginShellInit = mkForce ''...` — same script but: - Sets `SDL_VIDEODRIVER=drm` instead of `wayland` - Removes `WLR_RENDERER`, `WLR_NO_HARDWARE_CURSORS`, `QT_QPA_PLATFORM`, `XDG_SESSION_TYPE`, `GODEBUG` - Launches `exec /run/current-system/sw/bin/radiantwave` directly (no `dbus-run-session cage --`) - Keeps: network wait, audio init, Plymouth quit - `services.seatd.enable = mkForce false` — nothing uses libseat anymore --- ### Phase 1: `radiantwavekiosk-dev` (Intel NUC) **Files:** - Create `kiosk-drm.nix` - Modify `flake.nix`: add `./kiosk-drm.nix` to `extraModules` for `radiantwavekiosk-dev` - `dev-iso.nix` already targets `radiantwavekiosk-dev` — no change needed **Verify:** `nix build .#nixosConfigurations.radiantwavekiosk-dev.config.system.build.toplevel` --- ### Phase 2: `radiantwavekiosk-commercial-dev` (commercial HW) **Files:** - Modify `flake.nix`: add `./kiosk-drm.nix` to `extraModules` for `radiantwavekiosk-commercial-dev` - Create `commercial-dev-iso.nix` (same pattern as `dev-iso.nix`, targeting `radiantwavekiosk-commercial-dev`) - Add `commercial-dev-iso` config to `flake.nix` **Verify:** `nix build .#nixosConfigurations.radiantwavekiosk-commercial-dev.config.system.build.toplevel` --- ### Phase 3: `radiantwavekiosk-android-dev` (rotated display) **Files:** - Create `radiantwavekiosk-android-dev` in `flake.nix` (currently only production `radiantwavekiosk-android` exists) - Create `android-kiosk-drm.nix` — DRM/KMS mode + display rotation **Challenge:** `rotated_display.nix` uses `wlr-randr` (Wayland tool) for 90-degree rotation. Without Cage/Wayland, this won't work. Alternatives: - Option A: Use `modetest` from `libdrm` to set DRM rotation property - Option B: Handle rotation in the app (query display orientation, render rotated) - Option C: Kernel DRM rotation via modprobe params or udev rule This is the hardest phase — needs investigation. **Verify:** `nix build .#nixosConfigurations.radiantwavekiosk-android-dev.config.system.build.toplevel` --- ### Phase 4: `radiantwavekiosk-rpi-dev` (RPi 5) **Files:** - Modify `flake.nix`: add `./kiosk-drm.nix` to `extraModules` for `radiantwavekiosk-rpi-dev` - Modify `build-rpi.sh`: update to build `radiantwavekiosk-rpi-dev` instead of `radiantwavekiosk-rpi` RPi 5 uses VC4/V3D GPU driver — `hardware.graphics.enable = true` + `nixos-raspberrypi.nixosModules.raspberry-pi-5.display-vc4` already handles this. **Verify:** `./build-rpi.sh` (builds SD card image) --- ## Risks and Unknowns 1. **SDL3 DRM backend maturity** — Less commonly used than the Wayland backend. Needs testing on NUC (Intel i915) and RPi 5 (VC4/V3D) hardware. 2. **Android rotation** — `wlr-randr` won't work without Wayland. Needs an alternative rotation strategy. 3. **Input device permissions** — SDL3's DRM backend reads `/dev/input/*` directly. Need to verify `input` group is sufficient and udev rules don't block access. 4. **Multi-GPU/multi-output** — DRM/KMS needs to select the right connector. SDL3 handles this, but edge cases exist (e.g., multiple monitors). 5. **Plymouth -> app handoff** — Plymouth runs on DRM/KMS too. The handoff should work, but timing might differ without a Wayland compositor in between. --- ## radiantwave Repo — No Changes Needed The app doesn't directly use DRM/KMS APIs. SDL3 handles the DRM backend internally when `SDL_VIDEODRIVER=drm` is set. The Nix package doesn't need changes — SDL3 dynamically loads DRM libraries at runtime. The only change is the environment variable in the kiosk startup script (`SDL_VIDEODRIVER=drm` vs `wayland`), which lives in kioskconfig.