aman/src/inject.py

110 lines
2.7 KiB
Python

from __future__ import annotations
from typing import Iterable
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Gdk, Gtk
from Xlib import X, XK, display
from Xlib.ext import xtest
def write_clipboard(text: str) -> None:
Gtk.init([])
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard.set_text(text, -1)
clipboard.store()
while Gtk.events_pending():
Gtk.main_iteration()
def paste_clipboard() -> None:
dpy = display.Display()
_send_combo(dpy, ["Control_L", "Shift_L", "v"])
def type_text(text: str) -> None:
if not text:
return
dpy = display.Display()
for ch in text:
if ch == "\n":
_send_combo(dpy, ["Return"])
continue
keysym, needs_shift = _keysym_for_char(ch)
if keysym is None:
continue
if needs_shift:
_send_combo(dpy, ["Shift_L", keysym], already_keysym=True)
else:
_send_combo(dpy, [keysym], already_keysym=True)
def inject(text: str, backend: str) -> None:
backend = (backend or "").strip().lower()
if backend in ("", "clipboard"):
write_clipboard(text)
paste_clipboard()
return
if backend == "injection":
type_text(text)
return
raise ValueError(f"unknown injection backend: {backend}")
def _send_combo(dpy: display.Display, keys: Iterable[str], already_keysym: bool = False) -> None:
keycodes: list[int] = []
for key in keys:
keysym = key if already_keysym else XK.string_to_keysym(key)
if keysym == 0:
continue
keycode = dpy.keysym_to_keycode(keysym)
if keycode == 0:
continue
keycodes.append(keycode)
for code in keycodes:
xtest.fake_input(dpy, X.KeyPress, code)
for code in reversed(keycodes):
xtest.fake_input(dpy, X.KeyRelease, code)
dpy.flush()
_SHIFTED = {
"!": "1",
"@": "2",
"#": "3",
"$": "4",
"%": "5",
"^": "6",
"&": "7",
"*": "8",
"(": "9",
")": "0",
"_": "-",
"+": "=",
"{": "[",
"}": "]",
"|": "\\",
":": ";",
"\"": "'",
"<": ",",
">": ".",
"?": "/",
}
def _keysym_for_char(ch: str) -> tuple[int | None, bool]:
if ch.isupper():
base = ch.lower()
keysym = XK.string_to_keysym(base)
return (keysym if keysym != 0 else None, True)
if ch in _SHIFTED:
keysym = XK.string_to_keysym(_SHIFTED[ch])
return (keysym if keysym != 0 else None, True)
if ch == " ":
return (XK.string_to_keysym("space"), False)
keysym = XK.string_to_keysym(ch)
return (keysym if keysym != 0 else None, False)