vuinputd/README.md
2025-11-14 22:15:26 +00:00

6 KiB
Raw Blame History

vuinputd

Run Sunshine and other uinput-based apps inside containers — with full input isolation and zero kernel patches.

A minimal CUSE-based proxy for /dev/uinput that lets unmodified applications (like Sunshine) run inside containers while creating virtual input devices safely on the host.


Overview

Containerizing input-producing software (e.g. Sunshine, Moonlight host replacements, remote desktop servers) improves separation and simplifies deployment.
However, exposing the hosts /dev/uinput directly into a container breaks isolation:

  • Containers can create devices visible system-wide or to other containers.
  • Keyboards and mice may attach to host seats or inject input into active host sessions.

vuinputd exposes a virtual /dev/uinput device inside containers (via CUSE). Input devices created by containerized apps are forwarded to the host kernels uinput subsystem, where they appear as normal /dev/input/event* devices visible to all host applications. Those devices are then injected into the containers with udev announcements.


Architecture

vuinputd solves this by introducing a mediated input stack:

  • A fake /dev/uinput inside each container.
  • A host proxy daemon that safely creates the actual devices via /dev/uinput.
  • The proxy forwards add/remove udev events into the container so that wayland compositors that use libinput and other applications see devices natively.
  • udev rules tag and isolate devices per container, preventing the host from consuming them.

Applications use the /dev/uinput interface unmodified, and the mediation adds negligible overhead.

In principle, this design works with any container runtime — systemd-nspawn, Docker, LXC, Podman, and others.


sequenceDiagram
    box transparent Host
        participant Kernel as uinput (kernel)
        participant Daemon as vuinputd
    end

    box transparent Container
        participant App as Container App
        participant VirtUinput as /dev/uinput (virt)
        Participant Game as Game
    end

    Daemon->>VirtUinput: 1. provides virtual /dev/uinput via CUSE
    App->>VirtUinput: 2. create virtual input device
    VirtUinput-->Daemon: 3. data from virtual /dev/uinput via CUSE
    Daemon->>Kernel: 4. create virtual input device
    Kernel->>Daemon: 5. notify applications on host about new eventX device
    Daemon->>App: 6. notify application in container about new eventX device
    App->>VirtUinput: 7. send input data
    VirtUinput-->Daemon: 8. data from virtual /dev/uinput via CUSE
    Daemon->>Kernel: 9. send input data
    Kernel->>Game: 10. send input data via eventX device

Benefits

  • 🎮 SDL2 & Wayland compatibility: vuinputd ensures compositors and games recognize input devices correctly.
  • 🔒 Strong isolation: Containers see only their own devices; the host sees them but ignores them completely.
  • ♻️ Safe lifecycle: Devices are removed cleanly when the containerized app stops.
  • 🛠️ Simple integration: No kernel patches required — only userspace tools and udev rules.

Documentation

See docs/BUILD.md for a short build and installation guide.
See docs/DESIGN.md for a detailed overview of the architecture, design trade-offs, and security considerations. See docs/USAGE.md for a short usage guide.


🧩 Production Readiness

Current Status: 🚧 Prototype / Alpha — functional, not yet production-grade.

vuinputd is currently in a functional prototype stage. It reliably demonstrates the core concept — exposing /dev/uinput devices inside containers via CUSE — but several aspects require hardening before production use.

Goals for Production Readiness


License

MIT