-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinject.py
More file actions
159 lines (136 loc) · 4.63 KB
/
inject.py
File metadata and controls
159 lines (136 loc) · 4.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
inject.py — cross-platform keystroke injection
Auto-detects backend at import time:
Linux X11/XWayland → xdotool (injects into active window, skipping own terminal)
Linux Wayland → ydotool
Windows / macOS → pyautogui
Fallback → keyboard library (needs sudo on Linux)
"""
import os
import platform
import shutil
import subprocess
import sys
CTRL_MAP = {
"\b": ("BackSpace", "backspace", "14"),
"\n": ("Return", "enter", "28"),
"\t": ("Tab", "tab", "15"),
" ": ("space", "space", "57"),
}
# Window ID of this process's own terminal — we never inject into it
_OWN_WID: str | None = None
def detect() -> str:
system = platform.system()
if system in ("Windows", "Darwin"):
return "pyautogui"
session = os.environ.get("XDG_SESSION_TYPE", "").lower()
if session == "wayland" and not os.environ.get("DISPLAY"):
if shutil.which("ydotool"):
return "ydotool"
print("[inject] ydotool not found. Install: sudo pacman -S ydotool")
if shutil.which("xdotool"):
return "xdotool"
print("[inject] xdotool not found. Install: sudo pacman -S xdotool")
try:
import pyautogui
return "pyautogui"
except ImportError:
pass
return "keyboard"
BACKEND = detect()
# ── Capture own terminal window ID so we can skip it ──────────────────────────
if BACKEND == "xdotool":
try:
# getactivewindow at import time = the terminal that launched us
result = subprocess.run(
["xdotool", "getactivewindow"],
capture_output=True, text=True, check=False
)
_OWN_WID = result.stdout.strip() or None
if _OWN_WID:
print(f"[inject] Own terminal WID: {_OWN_WID} (will not inject here)")
except Exception:
pass
# Load pyautogui if needed
_pyautogui = None
if BACKEND == "pyautogui":
try:
import pyautogui as _pyautogui
_pyautogui.PAUSE = 0
_pyautogui.FAILSAFE = False
except ImportError:
print("[inject] pyautogui not installed: pip install pyautogui")
BACKEND = "keyboard"
print(f"[inject] Backend: {BACKEND}")
def _get_target_wid() -> str | None:
"""
Returns the window ID to inject into.
Gets the currently active window; if it's our own terminal, returns None
(caller should skip injection or wait).
"""
try:
result = subprocess.run(
["xdotool", "getactivewindow"],
capture_output=True, text=True, check=False
)
wid = result.stdout.strip()
if wid and wid != _OWN_WID:
return wid
except Exception:
pass
return None
def key(value: str):
"""Inject a single character or control key into the active window."""
if BACKEND == "xdotool":
_xdotool(value)
elif BACKEND == "ydotool":
_ydotool(value)
elif BACKEND == "pyautogui":
_pyautogui_key(value)
else:
_keyboard_key(value)
def _xdotool(value: str):
wid = _get_target_wid()
if wid is None:
# Active window is our own terminal — focus the last non-terminal window
# by searching for any window that isn't ours
try:
result = subprocess.run(
["xdotool", "search", "--onlyvisible", "--name", ""],
capture_output=True, text=True, check=False
)
wids = [w for w in result.stdout.strip().splitlines() if w != _OWN_WID]
if wids:
wid = wids[-1]
subprocess.run(["xdotool", "windowfocus", "--sync", wid], check=False)
else:
return # nowhere to inject
except Exception:
return
if value in CTRL_MAP:
subprocess.run(
["xdotool", "key", "--window", wid, "--clearmodifiers", CTRL_MAP[value][0]],
check=False
)
else:
subprocess.run(
["xdotool", "type", "--window", wid, "--clearmodifiers", "--delay", "0", "--", value],
check=False
)
def _ydotool(value: str):
if value in CTRL_MAP:
code = CTRL_MAP[value][2]
subprocess.run(["ydotool", "key", f"{code}:1", f"{code}:0"], check=False)
else:
subprocess.run(["ydotool", "type", "--", value], check=False)
def _pyautogui_key(value: str):
if value in CTRL_MAP:
_pyautogui.press(CTRL_MAP[value][1])
else:
_pyautogui.typewrite(value, interval=0)
def _keyboard_key(value: str):
import keyboard as kb
if value in CTRL_MAP:
kb.press_and_release(CTRL_MAP[value][1])
else:
kb.write(value)