whisper-dictation/whisper_app/typer.py

85 lines
2.5 KiB
Python

import os
import shutil
import subprocess
import time
from whisper_app import app, config
def _pynput_type(text):
from pynput.keyboard import Controller as KeyboardController
KeyboardController().type(text)
def _wl_paste():
"""Read current clipboard contents, returns None on failure."""
try:
result = subprocess.run(
["wl-paste", "--no-newline"],
capture_output=True, timeout=2,
)
if result.returncode == 0:
return result.stdout
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
return None
def _wl_copy_bytes(data):
"""Restore clipboard from raw bytes."""
try:
subprocess.run(
["wl-copy"],
input=data, check=False, timeout=2,
)
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
def _paste_via_ydotool():
"""Simulate Ctrl+V using ydotool (kernel-level, works on all compositors)."""
subprocess.run(["ydotool", "key", "29:1", "47:1", "47:0", "29:0"], check=False)
def _paste_via_wtype():
"""Simulate Ctrl+V using wtype (native Wayland, wlroots only)."""
subprocess.run(["wtype", "-M", "ctrl", "-P", "v", "-p", "v", "-m", "ctrl"],
check=False)
def _paste_via_xdotool():
"""Simulate Ctrl+V using xdotool (XWayland fallback)."""
subprocess.run(["xdotool", "key", "ctrl+v"], check=False)
def _pick_paste_cmd():
"""Select best available paste tool for the current Wayland session."""
if shutil.which("ydotool"):
return _paste_via_ydotool
if shutil.which("wtype"):
return _paste_via_wtype
return _paste_via_xdotool
def type_text(text):
"""Type text into the active window, cross-platform."""
if os.name == "nt":
_pynput_type(text)
return
session = os.environ.get("XDG_SESSION_TYPE", "")
if session == "wayland" and shutil.which("wl-copy"):
delay = config.config.get("paste_delay_ms", 300) / 1000.0
old_clipboard = _wl_paste()
subprocess.run(["wl-copy", "--", text], check=False)
time.sleep(0.05)
paste_fn = _pick_paste_cmd()
app.log(f"typer: using {paste_fn.__name__}, session={session}")
paste_fn()
time.sleep(delay)
if old_clipboard is not None:
_wl_copy_bytes(old_clipboard)
elif shutil.which("xdotool"):
subprocess.run(["xdotool", "type", "--clearmodifiers", "--", text], check=False)
else:
_pynput_type(text)