11 KiB
Usage Guide
This guide explains how to run applications that use /dev/uinput (like Sunshine) inside containers using vuinputd.
You’ll learn how to connect your container to the host’s input proxy, configure permissions, and verify that input devices are visible and functional inside the container.
1. Overview
vuinputd allows unmodified apps that use /dev/uinput to run safely inside containers.
It provides each container with a virtual /dev/uinput, while a host-side daemon mediates all access to the real uinput subsystem.
This guide shows how to:
- Run a container (Docker, systemd-nspawn, or LXC/LXD)
- Connect it to the host’s virtual
/dev/uinput - Verify that device creation and input forwarding work correctly
2. Prerequisites
Before continuing, ensure the following:
-
vuinputdis installed and running on the host → see docs/BUILD.md -
You have root access on the host (required for mounting and device permissions)
-
The host kernel supports:
/dev/uinput- FUSE/
CUSE
-
Optional tools for debugging and validation inside the container:
apt-get install libinput-tools evtest udev tmux
3. Quick Start (Docker Example)
This is the simplest way to verify that vuinputd works.
🖥️ On the Host
-
Install Docker:
sudo apt-get install docker.io -
Start a test container with
vuinputd’s virtual device mapped in:sudo docker run -it \ --name vuinput-test \ --device=/dev/vuinput:/dev/uinput \ --device-cgroup-rule='c 13:* rw' \ --mount type=bind,src=<path-to-vuinputd-build>,dst=/build \ ubuntu:noble(Replace
<path-to-vuinputd-build>with your actual build directory) -
Test the application
Just run those lines in the container.
# Allow access for any application
chmod 666 /dev/uinput
# Prepare udev stubs so applications relying on libudev work
mkdir -p /run/udev/data/
touch /run/udev/control
# Run the demo application
/build/release/mouse-advanced
The vuinputd daemon on the host should provide some logs. The following section "Verifying Operation" describes a more elaborate check and also some screenshots.
-
Optional: To reuse the container later:
sudo docker start -ia vuinput-test -
To clean up:
sudo docker rm vuinput-test
4. Runtime-Specific Setup
🐳 Docker
(As shown above in Quick Start.)
Key flags:
--device=/dev/vuinput:/dev/uinput— mounts the fake uinput device--device-cgroup-rule='c 13:* rw'— allows access to input devices- Optional: bind your build directory to
/buildfor testing binaries
🧱 systemd-nspawn
-
Install mkosi:
sudo apt-get install mkosi -
Create an Ubuntu 24.04 image:
mkosi -d ubuntu -r noble -t directory ubuntu-dir -
Launch a container with
vuinputdbound:/usr/bin/systemd-nspawn \ -M vuinputtest \ -D ubuntu-dir \ --network-veth \ --system-call-filter="@keyring bpf" \ --bind=/proc:/run/proc \ --bind=/sys:/run/sys \ --bind=/dev/vuinput:/dev/uinput \ --bind=/dev/dri \ --property="DeviceAllow=char-drm rw" \ --property="DeviceAllow=char-input rw" \ --property="DeviceAllow=/dev/vuinput rw" \ -b
🪶 LXC / LXD
Add the following to your container configuration:
lxc.cgroup2.devices.allow: c 120:414795 rwm
lxc.mount.entry: /dev/vuinput dev/uinput none bind,optional,create=file
Then restart the container.
(Adjust the major/minor numbers to match /dev/vuinput on your host — check with ls -l /dev/vuinput. In the current release, 120:414795 is hardcoded. This may change in the future.)
5. Inside the Container
Once inside the container shell:
chmod 666 /dev/uinput
apt-get update
# Optional: install test tools
apt-get install libinput-tools udev evtest tmux
# Prepare udev stubs
mkdir -p /run/udev/data/
touch /run/udev/control
6. Verifying Operation
To test everything, use multiple tmux windows for parallel monitoring.
-
Start
libinputevent monitor:libinput debug-events -
In another window, observe udev events:
udevadm monitor -p -
In a third, run:
evtest /dev/input/event* -
Finally, run the demo binary:
/build/release/mouse-advanced
Expected Results
You should see:
libinputreporting device creation and input eventsudevadmannouncing a new/dev/input/event*evtestshowing input data (e.g. mouse movement)journalctlon the host showingvuinputdlogs about device creation and event forwarding
Sample output from libinput debug-events:

Sample output from udevadm monitor -p:

Sample output from mouse-advanced:

Sample output from journalctl showing vuinputd output:

7. Handling Phantom Input Events Caused by VTs
On Linux systems without an active graphical session (X11 or Wayland), virtual terminals (VTs) remain in text mode (KD_TEXT) and continue to process keyboard input via the kernel VT keyboard handler.
This can lead to phantom input events, where injected or forwarded input (e.g. via vuinputd) unintentionally reaches:
gettylogin prompts- inactive consoles
- kernel VT hotkeys (e.g.
Ctrl+Alt+Fn)
The following approaches can be used to prevent or mitigate this behavior.
Solution 1: Use KMSCON (DRM/KMS-based console)
A robust solution is to replace the kernel VT text console with a DRM/KMS-based console such as kmscon.
How it helps
-
The kernel VT is no longer responsible for input handling
-
Keyboard input is processed via evdev, not the VT layer
-
Seat assignment is respected:
- devices on non-default seats (e.g.
seat_vuinput) are ignored
- devices on non-default seats (e.g.
-
Phantom input events do not reach
getty
Notes
-
Requires DRM/KMS availability
-
On most real GPUs, the DRM device remains available even when no monitor is connected and enters a hotplug-waiting state
-
For headless systems, a virtual KMS device can be used:
modprobe vkms
Trade-offs
- Additional dependencies (DRM, kmscon)
- Not always desired for minimal or embedded systems
Solution 2: VT Guard Mode (--vt-guard)
vuinputd can be started with the --vt-guard flag to explicitly neutralize VT input handling.
How it works
At startup, vuinputd performs a minimal VT operation such as:
- switching the active VT into graphics mode (
KD_GRAPHICS), or - disabling the kernel keyboard processing for that VT
This is done via direct VT ioctls (e.g. KDSETMODE), ensuring that:
- the kernel VT keyboard handler is inactive
gettydoes not receive injected input events
Characteristics
- Very lightweight
- No DRM, compositor, or additional services required
- Effective even on fully headless systems
Caveats
- Relies on low-level VT ioctls
- Considered hacky, but intentionally minimal
- Bypasses higher-level session management
Solution 3: fallbackdm (Work in Progress)
fallbackdm is an experimental, lightweight logind-integrated fallback display manager.
Intended behavior
- Starts only when no graphical session is active
- Registers a proper
greetersession withsystemd-logind - Takes ownership of a VT and switches it to
KD_GRAPHICS - Prevents
gettyand the VT keyboard handler from receiving input - Leaves other VTs untouched for emergency local access
Advantages
- Clean integration with
systemd-logind - No direct VT hacks
- Compatible with standard Linux session semantics
- Designed to coexist with real display managers
Status
- Currently under development
- Intended as the long-term, principled solution
Summary
| Solution | Headless | Lightweight | logind-aware | Recommended for |
|---|---|---|---|---|
| KMSCON | ⚠️ (vkms) | ❌ | ✅ | Full console replacement |
--vt-guard |
✅ | ✅ | ❌ | Minimal setups |
| fallbackdm | ✅ | ⚠️ | ✅ | Long-term, clean integration |
Choose the approach that best fits your system constraints and deployment model.
8. Troubleshooting
| Symptom | Possible Cause | Fix |
|---|---|---|
/dev/uinput not found |
vuinputd not running |
Start vuinputd on host |
| Permission denied | Missing chmod or wrong cgroup rule |
Run chmod 666 /dev/uinput or adjust device rule |
| No events in container | Missing /run/udev/control |
Create dummy udev files (see section 5) |
| Device appears on host seat | udev rules not isolating | Add udev rules from vuinputd/udev-folder |
| Input delayed or missing | CUSE layer error | Check host logs via journalctl -u vuinputd |
Dez 14 21:33:17 wohnzimmer vuinputd[2172719]: Create /dev/input
Dez 14 21:33:17 wohnzimmer vuinputd[2172719]: [2025-12-14T21:33:17Z DEBUG vuinputd::jobs::inject_in_container_job] Error creating input device /dev/input/event12: Read-o>
Dez 14 21:33:17 wohnzimmer vuinputd[2172719]: thread '<unnamed>' panicked at vuinputd/src/jobs/inject_in_container_job.rs:161:41:
Dez 14 21:33:17 wohnzimmer vuinputd[2172719]: called `Result::unwrap()` on an `Err` value: Os { code: 30, kind: ReadOnlyFilesystem, message: "Read-only file system" }
Ensure /dev and /run are writable in the container. If in doubt, use tmpfs.
9. Notes and Advanced Topics
-
You can safely run multiple containers.
-
Devices are automatically cleaned up when the container stops.
-
Works with Wayland and X11 compositors using libinput.
-
For deeper details, see:
