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)