fix(evdev): strip SW_RFKILL_ALL from uinput clone capabilities (#947)

The evdev uinput clone copied EV_SW capabilities verbatim. "ThinkPad Extra Buttons" (thinkpad_acpi) declares SW_RFKILL_ALL, and a fresh uinput device reports every switch as 0. The kernel rfkill-input handler reads a 0 killswitch as radios-off, so every clone creation soft-blocked all radios at startup and again after every resume.

Strip SW_RFKILL_ALL from the clone capability list, dropping EV_SW entirely when it was the only switch. Other switches stay declared so real switch events still forward; a forwarded SW_RFKILL_ALL write is dropped by the kernel for the undeclared code.

Fixes #946
This commit is contained in:
devin-ai-integration[bot]
2026-10-12 18:07:50 -07:00
committed by GitHub
parent b0fefd6ec1
commit cd388b2e93
2 changed files with 78 additions and 0 deletions
@@ -1317,6 +1317,14 @@ class EvdevDeviceHub:
EV_REP is filtered out so the clone never autorepeats: the physical
device's own repeat events are forwarded verbatim, preserving its
repeat rate without doubling repeats.
SW_RFKILL_ALL is stripped from EV_SW: a fresh uinput device reports
every switch as 0, and the kernel's rfkill-input handler reads a 0
killswitch as radios-off — cloning that bit soft-blocks all radios
the moment the clone appears, on ThinkPads and any other device
declaring it (#946). The clone keeps every other switch so real
switch events still forward; a forwarded SW_RFKILL_ALL write is
dropped by the kernel for the undeclared code.
"""
try:
capabilities: dict[int, Sequence[int]] = {
@@ -1324,6 +1332,13 @@ class EvdevDeviceHub:
}
for event_type in (ecodes.EV_SYN, ecodes.EV_FF, ecodes.EV_REP):
capabilities.pop(event_type, None)
switches = [
code for code in capabilities.get(ecodes.EV_SW, ()) if code != ecodes.SW_RFKILL_ALL
]
if switches:
capabilities[ecodes.EV_SW] = switches
else:
capabilities.pop(ecodes.EV_SW, None)
name = _clone_device_name(device)
try:
return UInput(