3.2 KiB
Kernel 7.0 uinput regression: UI_DEV_SETUP required
Summary
Kernel 7.0.0-22-generic broke the legacy uinput creation sequence. Sunshine's Inputtino and libevdev both use the old method and fail silently.
Pre-diagnostic: is uinput built-in or a module?
Before running the C or Python diagnostic below, check if uinput is a loadable module or built into the kernel:
modinfo uinput 2>&1 | grep filename
# "filename: (builtin)" → uinput is compiled into the kernel
# "filename: /lib/modules/..." → uinput is a loadable module
grep CONFIG_INPUT_UINPUT /boot/config-$(uname -r)
# "CONFIG_INPUT_UINPUT=y" → built-in (no modprobe needed)
# "CONFIG_INPUT_UINPUT=m" → module (modprobe uinput to load)
If built-in: modprobe uinput will succeed silently but lsmod | grep uinput will show nothing — this is expected. The device node at /dev/uinput exists regardless. Permissions (user in input group) are what matter. Skip any module-loading troubleshooting steps.
If a module: sudo modprobe uinput and verify with lsmod | grep uinput. To persist across reboots: echo "uinput" | sudo tee /etc/modules-load.d/uinput.conf.
Root cause
The uinput subsystem on kernel 7.0+ REQUIRES UI_DEV_SETUP ioctl before
UI_DEV_CREATE. The old method (writing struct uinput_user_dev via write()
or calling UI_DEV_CREATE without prior setup) is rejected with EINVAL (-22).
Sunshine v2026.516.143833 links against libevdev 1.13.6, which uses the old method. Inputtino (Sunshine's actual input backend, not libevdev) also uses the old method.
Diagnostic (C test)
#include <linux/uinput.h>
int fd = open("/dev/uinput", O_WRONLY);
ioctl(fd, UI_DEV_CREATE); // FAILS: EINVAL on kernel 7.0
// Correct kernel 7.0 sequence:
struct uinput_setup usetup;
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
strcpy(usetup.name, "test");
ioctl(fd, UI_DEV_SETUP, &usetup); // NEW: Required on 7.0+
ioctl(fd, UI_DEV_CREATE); // SUCCESS
Diagnostic (Python test)
import ctypes
libevdev = ctypes.CDLL('libevdev.so.2')
dev = libevdev.libevdev_new()
libevdev.libevdev_set_name(dev, b'test')
libevdev.libevdev_enable_event_type(dev, 1)
uifd = ctypes.c_int(-1)
ret = libevdev.libevdev_uinput_create_from_device(dev, 3, ctypes.byref(uifd))
# Returns -25 (ENOTTY) on kernel 7.0 — uinput broken
# Returns 0 on kernel 6.x — uinput works
Why the LD_PRELOAD shim may not work
The shim (scripts/uinput-shim.c) intercepts libevdev_uinput_create_from_device.
However, Sunshine's PRIMARY input backend is Inputtino (subproject inside Sunshine
binary), not libevdev. Inputtino creates keyboard/mouse devices through a separate
code path that may not call through libevdev's public API. If the shim is deployed
and input still doesn't work, Inputtino is bypassing it.
Proven workarounds
- xdotool bridge (
scripts/sunshine-input-bridge.py) — tails Sunshine log, injects via xdotool XTEST. Works on any kernel. - Steam Remote Play (
templates/remote_play.vdf) — different protocol, no uinput involved. - Boot kernel 6.x if available.
Kernel version
Tested broken: 7.0.0-22-generic (Ubuntu 26.04) Tested working: Linux 6.x series