diff --git a/src/ai_typewriter/key_stepper.py b/src/ai_typewriter/key_stepper.py new file mode 100644 index 0000000..f305a80 --- /dev/null +++ b/src/ai_typewriter/key_stepper.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from threading import Lock +from typing import Optional + +import keyboard + + +@dataclass +class KeyStepper: + text: str + delay: float = 0.0 + _index: int = 0 + _hook: Optional[object] = None + _lock: Lock = field(default_factory=Lock) + _injecting: bool = False + + def start(self) -> None: + if not self.text: + return + if self._hook is not None: + return + # suppress=True blocks the physical key while the callback types the next AI character. + self._hook = keyboard.hook(self._on_event, suppress=True) + + def stop(self) -> None: + hook = self._hook + self._hook = None + if hook is not None: + keyboard.unhook(hook) + + def _on_event(self, event: keyboard.KeyboardEvent) -> None: + if self._injecting or event.event_type != keyboard.KEY_DOWN: + return + + with self._lock: + if self._index >= len(self.text): + self.stop() + return + char = self.text[self._index] + self._index += 1 + + self._injecting = True + try: + self._type_char(char) + finally: + self._injecting = False + + if self._index >= len(self.text): + self.stop() + + def _type_char(self, char: str) -> None: + if char == "\n": + keyboard.send("enter") + elif char == "\t": + keyboard.send("tab") + else: + keyboard.write(char, delay=self.delay, exact=True)