Validate hotkeys and support Super alias
This commit is contained in:
parent
2cbc1a98b9
commit
3bc473262d
6 changed files with 138 additions and 34 deletions
|
|
@ -21,6 +21,7 @@ except ValueError:
|
|||
from gi.repository import GLib, Gdk, Gtk # type: ignore[import-not-found]
|
||||
|
||||
from constants import ASSETS_DIR, TRAY_UPDATE_MS
|
||||
from hotkey import split_hotkey
|
||||
|
||||
|
||||
MOD_MAP = {
|
||||
|
|
@ -65,11 +66,14 @@ class X11Adapter:
|
|||
self.menu.popup(None, None, None, None, 0, _time)
|
||||
|
||||
def start_hotkey_listener(self, hotkey: str, callback: Callable[[], None]) -> None:
|
||||
thread = threading.Thread(target=self._listen, args=(hotkey, callback), daemon=True)
|
||||
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 start_cancel_listener(self, callback: Callable[[], None]) -> None:
|
||||
thread = threading.Thread(target=self._listen, args=("Escape", callback), daemon=True)
|
||||
mods, keysym = self._parse_hotkey("Escape")
|
||||
thread = threading.Thread(target=self._listen, args=(mods, keysym, callback), daemon=True)
|
||||
thread.start()
|
||||
|
||||
def inject_text(
|
||||
|
|
@ -123,14 +127,13 @@ class X11Adapter:
|
|||
finally:
|
||||
self.request_quit()
|
||||
|
||||
def _listen(self, hotkey: str, callback: Callable[[], None]) -> None:
|
||||
def _listen(self, mods: int, keysym: int, callback: Callable[[], None]) -> None:
|
||||
disp = None
|
||||
root = None
|
||||
keycode = None
|
||||
try:
|
||||
disp = display.Display()
|
||||
root = disp.screen().root
|
||||
mods, keysym = self._parse_hotkey(hotkey)
|
||||
keycode = self._grab_hotkey(disp, root, mods, keysym)
|
||||
while True:
|
||||
ev = disp.next_event()
|
||||
|
|
@ -149,17 +152,10 @@ class X11Adapter:
|
|||
pass
|
||||
|
||||
def _parse_hotkey(self, hotkey: str):
|
||||
parts = [p.strip() for p in hotkey.split("+") if p.strip()]
|
||||
mods = 0
|
||||
key_part = None
|
||||
for p in parts:
|
||||
low = p.lower()
|
||||
if low in MOD_MAP:
|
||||
mods |= MOD_MAP[low]
|
||||
else:
|
||||
key_part = p
|
||||
if not key_part:
|
||||
raise ValueError("hotkey missing key")
|
||||
modifier_parts, key_part = split_hotkey(hotkey)
|
||||
for modifier in modifier_parts:
|
||||
mods |= MOD_MAP[modifier]
|
||||
|
||||
keysym = XK.string_to_keysym(key_part)
|
||||
if keysym == 0 and len(key_part) == 1:
|
||||
|
|
@ -168,8 +164,31 @@ class X11Adapter:
|
|||
raise ValueError(f"unsupported key: {key_part}")
|
||||
return mods, keysym
|
||||
|
||||
def _validate_hotkey_registration(self, mods: int, keysym: int) -> None:
|
||||
disp = None
|
||||
root = None
|
||||
keycode = None
|
||||
try:
|
||||
disp = display.Display()
|
||||
root = disp.screen().root
|
||||
keycode = self._grab_hotkey(disp, root, mods, keysym)
|
||||
finally:
|
||||
if root is not None and keycode is not None and disp is not None:
|
||||
try:
|
||||
root.ungrab_key(keycode, X.AnyModifier)
|
||||
disp.sync()
|
||||
except Exception:
|
||||
pass
|
||||
if disp is not None:
|
||||
try:
|
||||
disp.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _grab_hotkey(self, disp, root, mods, keysym):
|
||||
keycode = disp.keysym_to_keycode(keysym)
|
||||
if keycode == 0:
|
||||
raise ValueError("hotkey is not available on this keyboard layout")
|
||||
root.grab_key(keycode, mods, True, X.GrabModeAsync, X.GrabModeAsync)
|
||||
root.grab_key(keycode, mods | X.LockMask, True, X.GrabModeAsync, X.GrabModeAsync)
|
||||
root.grab_key(keycode, mods | X.Mod2Mask, True, X.GrabModeAsync, X.GrabModeAsync)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue