Add clipboard cleanup option for clipboard backend

This commit is contained in:
Thales Maciel 2026-02-25 10:56:32 -03:00
parent 1423e44008
commit ccf968a410
9 changed files with 114 additions and 11 deletions

View file

@ -38,6 +38,7 @@ class SttConfig:
@dataclass
class InjectionConfig:
backend: str = DEFAULT_INJECTION_BACKEND
remove_transcription_from_clipboard: bool = False
@dataclass
@ -115,6 +116,8 @@ def validate(cfg: Config) -> None:
allowed = ", ".join(sorted(ALLOWED_INJECTION_BACKENDS))
raise ValueError(f"injection.backend must be one of: {allowed}")
cfg.injection.backend = backend
if not isinstance(cfg.injection.remove_transcription_from_clipboard, bool):
raise ValueError("injection.remove_transcription_from_clipboard must be boolean")
if not isinstance(cfg.ai.enabled, bool):
raise ValueError("ai.enabled must be boolean")
@ -180,6 +183,11 @@ def _from_dict(data: dict[str, Any], cfg: Config) -> Config:
cfg.stt.device = _as_nonempty_str(stt["device"], "stt.device")
if "backend" in injection:
cfg.injection.backend = _as_nonempty_str(injection["backend"], "injection.backend")
if "remove_transcription_from_clipboard" in injection:
cfg.injection.remove_transcription_from_clipboard = _as_bool(
injection["remove_transcription_from_clipboard"],
"injection.remove_transcription_from_clipboard",
)
if "enabled" in ai:
cfg.ai.enabled = _as_bool(ai["enabled"], "ai.enabled")
if "replacements" in vocabulary:

View file

@ -11,7 +11,13 @@ class DesktopAdapter(Protocol):
def start_cancel_listener(self, callback: Callable[[], None]) -> None:
raise NotImplementedError
def inject_text(self, text: str, backend: str) -> None:
def inject_text(
self,
text: str,
backend: str,
*,
remove_transcription_from_clipboard: bool = False,
) -> None:
raise NotImplementedError
def run_tray(self, state_getter: Callable[[], str], on_quit: Callable[[], None]) -> None:

View file

@ -10,7 +10,14 @@ class WaylandAdapter:
def start_cancel_listener(self, _callback: Callable[[], None]) -> None:
raise SystemExit("Wayland hotkeys are not supported yet.")
def inject_text(self, _text: str, _backend: str) -> None:
def inject_text(
self,
_text: str,
_backend: str,
*,
remove_transcription_from_clipboard: bool = False,
) -> None:
_ = remove_transcription_from_clipboard
raise SystemExit("Wayland text injection is not supported yet.")
def run_tray(self, _state_getter: Callable[[], str], _on_quit: Callable[[], None]) -> None:

View file

@ -2,6 +2,7 @@ from __future__ import annotations
import logging
import threading
import time
import warnings
from typing import Callable, Iterable
@ -33,6 +34,7 @@ MOD_MAP = {
"cmd": X.Mod4Mask,
"command": X.Mod4Mask,
}
CLIPBOARD_RESTORE_DELAY_SEC = 0.15
class X11Adapter:
@ -70,17 +72,35 @@ class X11Adapter:
thread = threading.Thread(target=self._listen, args=("Escape", callback), daemon=True)
thread.start()
def inject_text(self, text: str, backend: str) -> None:
def inject_text(
self,
text: str,
backend: str,
*,
remove_transcription_from_clipboard: bool = False,
) -> None:
backend = (backend or "").strip().lower()
if backend in ("", "clipboard"):
previous_clipboard = None
if remove_transcription_from_clipboard:
previous_clipboard = self._read_clipboard_text()
self._write_clipboard(text)
self._paste_clipboard()
if remove_transcription_from_clipboard:
time.sleep(CLIPBOARD_RESTORE_DELAY_SEC)
self._restore_clipboard_text(previous_clipboard)
return
if backend == "injection":
self._type_text(text)
return
raise ValueError(f"unknown injection backend: {backend}")
def _read_clipboard_text(self) -> str | None:
Gtk.init([])
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
text = clipboard.wait_for_text()
return str(text) if text is not None else None
def run_tray(self, state_getter: Callable[[], str], on_quit: Callable[[], None]) -> None:
self.menu = Gtk.Menu()
quit_item = Gtk.MenuItem(label="Quit")
@ -165,6 +185,14 @@ class X11Adapter:
while Gtk.events_pending():
Gtk.main_iteration()
def _restore_clipboard_text(self, text: str | None) -> None:
Gtk.init([])
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard.set_text(text or "", -1)
clipboard.store()
while Gtk.events_pending():
Gtk.main_iteration()
def _paste_clipboard(self) -> None:
dpy = display.Display()
self._send_combo(dpy, ["Control_L", "Shift_L", "v"])

View file

@ -226,7 +226,13 @@ class Daemon:
self.set_state(State.OUTPUTTING)
logging.info("outputting started")
backend = self.cfg.injection.backend
self.desktop.inject_text(text, backend)
self.desktop.inject_text(
text,
backend,
remove_transcription_from_clipboard=(
self.cfg.injection.remove_transcription_from_clipboard
),
)
except Exception as exc:
logging.error("output failed: %s", exc)
finally: