* fix(evdev): release EVIOCGRAB when forwarding cannot run (#1021)
With the internal evdev listener, Vocalinux holds EVIOCGRAB on physical
keyboards and re-emits through uinput clones. Two windows left the grab
held while nothing could forward:
- Suspend/resume: grabs persisted through sleep while the compositor's
input stack was rebuilt; the listener was torn down and rebuilt ~6s
after resume, and keystrokes typed at the GNOME lock screen in that
window went to a stale forwarding path.
- A monitor thread wedged mid-iteration (fd, lock, or a clone write that
never returns): grabs stayed held forever because the self-healing
path only fired on write exceptions, and the unexpected-exit cleanup
only ran when the thread died.
_on_system_suspend now tears the shortcut listeners down before the
machine sleeps, so no grab survives the suspend boundary; the existing
resume flow rebuilds them once /dev/input settles.
A forwarding watchdog now runs alongside the monitor. The monitor stamps
a heartbeat on every loop; if it goes stale while grab->clone pairs
exist, the watchdog runs the existing _release_failed_forwarder path on
each device so input reaches applications directly, and tracks them as
suspended. Once dispatching resumes, suspended devices are removed so
the rescan re-opens them through the standard open->clone->grab path.
Generation checks keep a recycled fd from ever being torn down under a
newer generation.
* fix(evdev): re-arm watchdog-released devices on the monitor thread
The watchdog closing a device fd races the monitor's in-flight select:
a dead descriptor in the select set trips the error handler, which
exits the loop and tears down every keyboard. Recovery now runs where
the close is safe — the monitor retires each suspended device at its
own loop top and the rescan re-opens it through the standard
open->clone->grab path. The watchdog only ungrabs (safe cross-thread:
that fd never sits in the select set) and marks the device suspended.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>