feat: Linux-Hotkey-Record via evdev
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
"""Hotkey-Aufzeichnung via evdev (Linux)."""
|
||||
|
||||
import selectors
|
||||
import threading
|
||||
from typing import Callable
|
||||
|
||||
import evdev
|
||||
from evdev import InputDevice, ecodes
|
||||
|
||||
|
||||
def find_all_keyboards() -> list[InputDevice]:
|
||||
"""Gibt alle Input-Devices zurück, die EV_KEY-Events liefern können."""
|
||||
keyboards: list[InputDevice] = []
|
||||
for path in evdev.list_devices():
|
||||
try:
|
||||
device = InputDevice(path)
|
||||
except (PermissionError, OSError):
|
||||
continue
|
||||
capabilities = device.capabilities()
|
||||
if ecodes.EV_KEY in capabilities:
|
||||
keyboards.append(device)
|
||||
else:
|
||||
device.close()
|
||||
return keyboards
|
||||
|
||||
|
||||
def _keycode_to_name(code: int) -> str:
|
||||
"""Übersetzt evdev-Keycode zu Key-Namen. Gibt '' bei unbekanntem Code."""
|
||||
name = ecodes.KEY.get(code)
|
||||
if isinstance(name, list):
|
||||
return name[0]
|
||||
if isinstance(name, str):
|
||||
return name
|
||||
return ""
|
||||
|
||||
|
||||
def record_hotkey(
|
||||
on_result: Callable[[str, bool], None],
|
||||
cancel_event: threading.Event,
|
||||
) -> None:
|
||||
"""Blockiert bis zum ersten Keydown oder bis cancel_event gesetzt wird.
|
||||
|
||||
Ruft on_result(evdev_key_name, has_conflict) auf. has_conflict ist auf
|
||||
Linux immer False — es gibt kein Äquivalent zum Win32-RegisterHotKey-Check.
|
||||
"""
|
||||
devices = find_all_keyboards()
|
||||
if not devices:
|
||||
return
|
||||
|
||||
selector = selectors.DefaultSelector()
|
||||
try:
|
||||
for dev in devices:
|
||||
selector.register(dev.fd, selectors.EVENT_READ, dev)
|
||||
|
||||
captured: str | None = None
|
||||
while captured is None and not cancel_event.is_set():
|
||||
for key, _mask in selector.select(timeout=0.1):
|
||||
dev: InputDevice = key.data
|
||||
for event in dev.read():
|
||||
if event.type == ecodes.EV_KEY and event.value == 1:
|
||||
captured = _keycode_to_name(event.code)
|
||||
break
|
||||
if captured:
|
||||
break
|
||||
|
||||
if captured and not cancel_event.is_set():
|
||||
on_result(captured, False)
|
||||
finally:
|
||||
selector.close()
|
||||
for dev in devices:
|
||||
dev.close()
|
||||
Reference in New Issue
Block a user