110 lines
2.7 KiB
Python
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)
|