mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
120 lines
3.9 KiB
Python
120 lines
3.9 KiB
Python
"""Clipboard and key injection for Wayland and X11."""
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import time
|
|
|
|
from i18n import t
|
|
|
|
# Linux input event codes (linux/input-event-codes.h)
|
|
KEYCODES = {
|
|
"ctrl": 29, "control": 29, "shift": 42, "alt": 56, "super": 125, "meta": 125,
|
|
"v": 47, "insert": 110, "enter": 28, "return": 28,
|
|
}
|
|
|
|
|
|
class PasteError(Exception):
|
|
pass
|
|
|
|
|
|
def read_clipboard():
|
|
command = (["xclip", "-selection", "clipboard", "-out"] if _x11()
|
|
else ["wl-paste", "--no-newline"])
|
|
if not shutil.which(command[0]):
|
|
return None
|
|
try:
|
|
res = subprocess.run(command, capture_output=True, timeout=5)
|
|
except (subprocess.SubprocessError, OSError):
|
|
return None
|
|
return res.stdout if res.returncode == 0 else None
|
|
|
|
|
|
def _run_copy(payload):
|
|
"""The clipboard owner may fork; do not leave inherited pipes open."""
|
|
command = (["xclip", "-selection", "clipboard", "-in"] if _x11()
|
|
else ["wl-copy"])
|
|
return subprocess.run(
|
|
command,
|
|
input=payload,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
timeout=10,
|
|
)
|
|
|
|
|
|
def copy(text):
|
|
tool = "xclip" if _x11() else "wl-copy"
|
|
if not shutil.which(tool):
|
|
raise PasteError(t("{tool} not found; clipboard copy is unavailable.", tool=tool))
|
|
try:
|
|
res = _run_copy(text.encode("utf-8"))
|
|
except (subprocess.SubprocessError, OSError) as exc:
|
|
raise PasteError(t("Could not copy to clipboard: {error}", error=exc)) from exc
|
|
if res.returncode != 0:
|
|
raise PasteError(t("{tool} exited with code {code}.",
|
|
tool=tool, code=res.returncode))
|
|
|
|
|
|
def copy_bytes(data):
|
|
tool = "xclip" if _x11() else "wl-copy"
|
|
if data is None or not shutil.which(tool):
|
|
return
|
|
try:
|
|
_run_copy(data)
|
|
except (subprocess.SubprocessError, OSError):
|
|
pass
|
|
|
|
|
|
def ydotool_ready():
|
|
tool = "xdotool" if _x11() else "ydotool"
|
|
return shutil.which(tool) is not None
|
|
|
|
|
|
def _x11():
|
|
return (os.environ.get("XDG_SESSION_TYPE") == "x11"
|
|
or bool(os.environ.get("DISPLAY") and not os.environ.get("WAYLAND_DISPLAY")))
|
|
|
|
|
|
def press(shortcut="ctrl+v", delay=0.12):
|
|
"""Press a key combination through xdotool or ydotool."""
|
|
if not ydotool_ready():
|
|
tool = "xdotool" if _x11() else "ydotool"
|
|
raise PasteError(t("{tool} not found, cannot paste automatically.", tool=tool))
|
|
|
|
if _x11():
|
|
key = shortcut.lower().replace("control", "ctrl")
|
|
time.sleep(delay)
|
|
try:
|
|
res = subprocess.run(
|
|
["xdotool", "key", "--clearmodifiers", key],
|
|
capture_output=True, text=True, timeout=10,
|
|
)
|
|
except (subprocess.SubprocessError, OSError) as exc:
|
|
raise PasteError(t("Could not run xdotool: {error}", error=exc)) from exc
|
|
if res.returncode != 0:
|
|
raise PasteError(t("xdotool failed: {error}",
|
|
error=res.stderr.strip() or "unknown error"))
|
|
return
|
|
|
|
codes = []
|
|
for key in (k.strip().lower() for k in shortcut.split("+") if k.strip()):
|
|
code = KEYCODES.get(key)
|
|
if code is None:
|
|
raise PasteError(t("Unknown key: {key}", key=key))
|
|
codes.append(code)
|
|
|
|
seq = [f"{c}:1" for c in codes] + [f"{c}:0" for c in reversed(codes)]
|
|
time.sleep(delay) # let the selection settle and focus come back
|
|
try:
|
|
res = subprocess.run(["ydotool", "key", *seq], capture_output=True,
|
|
text=True, timeout=10)
|
|
except (subprocess.SubprocessError, OSError) as exc:
|
|
raise PasteError(t("Could not run ydotool: {error}", error=exc)) from exc
|
|
if res.returncode != 0:
|
|
raise PasteError(t(
|
|
"ydotool failed: {error}\nIs ydotoold running? "
|
|
"(systemctl --user status ydotool)",
|
|
error=res.stderr.strip() or "unknown error",
|
|
))
|