diff --git a/PLAN-drm-kms.md b/PLAN-drm-kms.md new file mode 100644 index 0000000..3d0edaa --- /dev/null +++ b/PLAN-drm-kms.md @@ -0,0 +1,159 @@ +# 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.