fix(evdev): handle SYN_DROPPED to prevent stale modifier state (#339)

When the kernel's evdev buffer overflows it sends SYN_DROPPED to signal
lost events. Without handling this, a missed key-release can leave
key_pressed_devices in a stale state, causing stuck push-to-talk.

Discard all events between SYN_DROPPED and the next SYN_REPORT, then
reset modifier key state for the affected device.

Refs: https://www.kernel.org/doc/html/latest/input/event-codes.html
This commit is contained in:
Sbenazar
2026-03-25 21:45:53 -07:00
committed by GitHub
parent 9e0a98c0a3
commit 350cfcf17f
@@ -160,6 +160,7 @@ class EvdevKeyboardBackend(KeyboardBackend):
self.key_pressed_devices: set[int] = set()
self._devices_lock = threading.Lock()
self._dropped_devices: set[int] = set() # fds with SYN_DROPPED pending
if not EVDEV_AVAILABLE:
logger.error("python-evdev not available")
@@ -247,6 +248,7 @@ class EvdevKeyboardBackend(KeyboardBackend):
self.devices = []
self.device_fds = []
self.key_pressed_devices = set()
self._dropped_devices = set()
for device_path in device_paths:
try:
@@ -321,6 +323,22 @@ class EvdevKeyboardBackend(KeyboardBackend):
# Read events from this device
for event in device.read():
if event.type == ecodes.EV_SYN:
if event.code == ecodes.SYN_DROPPED:
# Kernel buffer overflowed — discard until SYN_REPORT
self._dropped_devices.add(fd)
logger.warning(
f"SYN_DROPPED on {device.name} (fd={fd}), "
"resetting key state"
)
elif event.code == ecodes.SYN_REPORT:
if fd in self._dropped_devices:
# End of dropped sequence — clear stale state
self._dropped_devices.discard(fd)
self.key_pressed_devices.discard(id(device))
continue
if fd in self._dropped_devices:
continue
if event.type == ecodes.EV_KEY:
self._handle_key_event(event, device)
@@ -343,6 +361,7 @@ class EvdevKeyboardBackend(KeyboardBackend):
if fd in self.device_fds:
with self._devices_lock:
self.device_fds.remove(fd)
self._dropped_devices.discard(fd)
continue
except (OSError, ValueError) as e: