Initial commit: Korin Timer
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from evdev import InputDevice, ecodes, list_devices
|
||||
|
||||
|
||||
def is_mouse(dev: InputDevice) -> bool:
|
||||
keys = dev.capabilities().get(ecodes.EV_KEY, [])
|
||||
rels = dev.capabilities().get(ecodes.EV_REL, [])
|
||||
return ecodes.BTN_LEFT in keys and ecodes.REL_X in rels and ecodes.REL_Y in rels
|
||||
|
||||
|
||||
def is_keyboard(dev: InputDevice) -> bool:
|
||||
caps = dev.capabilities().get(ecodes.EV_KEY, [])
|
||||
# Gaming mice (e.g. Logitech G502) commonly also advertise KEY_A/
|
||||
# KEY_SPACE (programmable buttons / media-key emulation) and would
|
||||
# otherwise get opened redundantly as both "keyboard" and "mouse" by
|
||||
# separate listeners, which was observed to make one of the duplicate
|
||||
# opens randomly drop ("Input device disconnected").
|
||||
return ecodes.KEY_A in caps and ecodes.KEY_SPACE in caps and not is_mouse(dev)
|
||||
|
||||
|
||||
def inaccessible_keyboard_names() -> list[str]:
|
||||
"""Best-effort list of device names the kernel flags as keyboards (a
|
||||
"kbd" handler in /proc/bus/input/devices) but that this process can't
|
||||
actually open.
|
||||
|
||||
evdev.list_devices() silently drops any /dev/input/eventN this process
|
||||
fails an os.access(R_OK | W_OK) check on - no exception, no gap in the
|
||||
returned list, so a real keyboard blocked by permissions just vanishes
|
||||
without a trace and whatever else happens to qualify as "keyboard-like"
|
||||
(e.g. a gaming mouse's macro-key HID sub-interface) silently becomes
|
||||
the only thing listened on. Observed on a Bazzite/GNOME machine: the
|
||||
physical keyboard's device node wasn't covered by the desktop
|
||||
session's usual per-session uaccess ACL (likely stripped by a
|
||||
Steam-Input udev rule for recognized gaming-keyboard hardware IDs),
|
||||
leaving it gated by the static 'input' group alone - which only takes
|
||||
effect for the process's *next* login session, not a new terminal in
|
||||
an already-running one. /proc/bus/input/devices is a plain readable
|
||||
text file regardless of any of that, so it's used here purely to
|
||||
produce an actionable diagnostic, never to open anything.
|
||||
"""
|
||||
try:
|
||||
text = Path("/proc/bus/input/devices").read_text()
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
accessible = {Path(p).name for p in list_devices()}
|
||||
missing: list[str] = []
|
||||
name: str | None = None
|
||||
for line in text.splitlines():
|
||||
if line.startswith("N: Name="):
|
||||
name = line.split("=", 1)[1].strip('"')
|
||||
elif line.startswith("H: Handlers="):
|
||||
handlers = line.split("=", 1)[1].split()
|
||||
if "kbd" not in handlers:
|
||||
continue
|
||||
event_handlers = [h for h in handlers if h.startswith("event")]
|
||||
if event_handlers and event_handlers[0] not in accessible:
|
||||
missing.append(name or event_handlers[0])
|
||||
return missing
|
||||
Reference in New Issue
Block a user