Add pipeline engine and remove legacy compatibility paths
This commit is contained in:
parent
3bc473262d
commit
e221d49020
18 changed files with 1523 additions and 399 deletions
|
|
@ -4,7 +4,7 @@ import logging
|
|||
import threading
|
||||
import time
|
||||
import warnings
|
||||
from typing import Callable, Iterable
|
||||
from typing import Any, Callable, Iterable
|
||||
|
||||
import gi
|
||||
from Xlib import X, XK, display
|
||||
|
|
@ -43,6 +43,8 @@ class X11Adapter:
|
|||
self.indicator = None
|
||||
self.status_icon = None
|
||||
self.menu = None
|
||||
self._hotkey_listener_lock = threading.Lock()
|
||||
self._hotkey_listeners: dict[str, dict[str, Any]] = {}
|
||||
if AppIndicator3 is not None:
|
||||
self.indicator = AppIndicator3.Indicator.new(
|
||||
"aman",
|
||||
|
|
@ -65,15 +67,29 @@ class X11Adapter:
|
|||
if self.menu:
|
||||
self.menu.popup(None, None, None, None, 0, _time)
|
||||
|
||||
def start_hotkey_listener(self, hotkey: str, callback: Callable[[], None]) -> None:
|
||||
mods, keysym = self._parse_hotkey(hotkey)
|
||||
self._validate_hotkey_registration(mods, keysym)
|
||||
thread = threading.Thread(target=self._listen, args=(mods, keysym, callback), daemon=True)
|
||||
thread.start()
|
||||
def set_hotkeys(self, bindings: dict[str, Callable[[], None]]) -> None:
|
||||
if not isinstance(bindings, dict):
|
||||
raise ValueError("bindings must be a dictionary")
|
||||
next_listeners: dict[str, dict[str, Any]] = {}
|
||||
try:
|
||||
for hotkey, callback in bindings.items():
|
||||
if not callable(callback):
|
||||
raise ValueError(f"callback for hotkey {hotkey} must be callable")
|
||||
next_listeners[hotkey] = self._spawn_hotkey_listener(hotkey, callback)
|
||||
except Exception:
|
||||
for listener in next_listeners.values():
|
||||
self._stop_hotkey_listener(listener)
|
||||
raise
|
||||
|
||||
with self._hotkey_listener_lock:
|
||||
previous = self._hotkey_listeners
|
||||
self._hotkey_listeners = next_listeners
|
||||
for listener in previous.values():
|
||||
self._stop_hotkey_listener(listener)
|
||||
|
||||
def start_cancel_listener(self, callback: Callable[[], None]) -> None:
|
||||
mods, keysym = self._parse_hotkey("Escape")
|
||||
thread = threading.Thread(target=self._listen, args=(mods, keysym, callback), daemon=True)
|
||||
thread = threading.Thread(target=self._listen, args=(mods, keysym, callback, threading.Event()), daemon=True)
|
||||
thread.start()
|
||||
|
||||
def inject_text(
|
||||
|
|
@ -127,7 +143,14 @@ class X11Adapter:
|
|||
finally:
|
||||
self.request_quit()
|
||||
|
||||
def _listen(self, mods: int, keysym: int, callback: Callable[[], None]) -> None:
|
||||
def _listen(
|
||||
self,
|
||||
mods: int,
|
||||
keysym: int,
|
||||
callback: Callable[[], None],
|
||||
stop_event: threading.Event,
|
||||
listener_meta: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
disp = None
|
||||
root = None
|
||||
keycode = None
|
||||
|
|
@ -135,14 +158,26 @@ class X11Adapter:
|
|||
disp = display.Display()
|
||||
root = disp.screen().root
|
||||
keycode = self._grab_hotkey(disp, root, mods, keysym)
|
||||
while True:
|
||||
if listener_meta is not None:
|
||||
listener_meta["display"] = disp
|
||||
listener_meta["root"] = root
|
||||
listener_meta["keycode"] = keycode
|
||||
listener_meta["ready"].set()
|
||||
while not stop_event.is_set():
|
||||
if disp.pending_events() == 0:
|
||||
time.sleep(0.05)
|
||||
continue
|
||||
ev = disp.next_event()
|
||||
if ev.type == X.KeyPress and ev.detail == keycode:
|
||||
state = ev.state & ~(X.LockMask | X.Mod2Mask)
|
||||
if state == mods:
|
||||
callback()
|
||||
except Exception as exc:
|
||||
logging.error("hotkey listener stopped: %s", exc)
|
||||
if listener_meta is not None:
|
||||
listener_meta["error"] = exc
|
||||
listener_meta["ready"].set()
|
||||
if not stop_event.is_set():
|
||||
logging.error("hotkey listener stopped: %s", exc)
|
||||
finally:
|
||||
if root is not None and keycode is not None and disp is not None:
|
||||
try:
|
||||
|
|
@ -150,6 +185,13 @@ class X11Adapter:
|
|||
disp.sync()
|
||||
except Exception:
|
||||
pass
|
||||
if disp is not None:
|
||||
try:
|
||||
disp.close()
|
||||
except Exception:
|
||||
pass
|
||||
if listener_meta is not None:
|
||||
listener_meta["ready"].set()
|
||||
|
||||
def _parse_hotkey(self, hotkey: str):
|
||||
mods = 0
|
||||
|
|
@ -185,6 +227,51 @@ class X11Adapter:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
def _spawn_hotkey_listener(self, hotkey: str, callback: Callable[[], None]) -> dict[str, Any]:
|
||||
mods, keysym = self._parse_hotkey(hotkey)
|
||||
self._validate_hotkey_registration(mods, keysym)
|
||||
stop_event = threading.Event()
|
||||
listener_meta: dict[str, Any] = {
|
||||
"hotkey": hotkey,
|
||||
"mods": mods,
|
||||
"keysym": keysym,
|
||||
"stop_event": stop_event,
|
||||
"display": None,
|
||||
"root": None,
|
||||
"keycode": None,
|
||||
"error": None,
|
||||
"ready": threading.Event(),
|
||||
}
|
||||
thread = threading.Thread(
|
||||
target=self._listen,
|
||||
args=(mods, keysym, callback, stop_event, listener_meta),
|
||||
daemon=True,
|
||||
)
|
||||
listener_meta["thread"] = thread
|
||||
thread.start()
|
||||
if not listener_meta["ready"].wait(timeout=2.0):
|
||||
stop_event.set()
|
||||
raise RuntimeError("hotkey listener setup timed out")
|
||||
if listener_meta["error"] is not None:
|
||||
stop_event.set()
|
||||
raise listener_meta["error"]
|
||||
if listener_meta["keycode"] is None:
|
||||
stop_event.set()
|
||||
raise RuntimeError("hotkey listener failed to initialize")
|
||||
return listener_meta
|
||||
|
||||
def _stop_hotkey_listener(self, listener: dict[str, Any]) -> None:
|
||||
listener["stop_event"].set()
|
||||
disp = listener.get("display")
|
||||
if disp is not None:
|
||||
try:
|
||||
disp.close()
|
||||
except Exception:
|
||||
pass
|
||||
thread = listener.get("thread")
|
||||
if thread is not None:
|
||||
thread.join(timeout=1.0)
|
||||
|
||||
def _grab_hotkey(self, disp, root, mods, keysym):
|
||||
keycode = disp.keysym_to_keycode(keysym)
|
||||
if keycode == 0:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue