Type only after physical key presses

This commit is contained in:
Edern Deneuville
2026-09-12 12:18:20 +02:00
parent 56c498eddc
commit 1c65fb5868
+9 -39
View File
@@ -2,63 +2,47 @@ from __future__ import annotations
from dataclasses import dataclass, field
from threading import Lock
from typing import Optional, Sequence
from typing import Optional
import keyboard
@dataclass(frozen=True)
class TypeAction:
kind: str
value: str
@dataclass
class KeyStepper:
text: str | Sequence[TypeAction]
text: str
delay: float = 0.0
_index: int = 0
_hook: Optional[object] = None
_lock: Lock = field(default_factory=Lock)
_injecting: bool = False
_actions: list[TypeAction] = field(init=False)
def __post_init__(self) -> None:
if isinstance(self.text, str):
self._actions = [TypeAction("text", char) for char in self.text]
else:
self._actions = list(self.text)
def start(self) -> None:
if not self._actions:
if not self.text:
return
if self._hook is not None:
return
self.type_next_character()
if self._index >= len(self._actions):
return
# suppress=True blocks the physical key while the callback types the next AI character/action.
# suppress=True blocks the physical key while the callback types the next AI character.
self._hook = keyboard.hook(self._on_event, suppress=True)
@property
def remaining_characters(self) -> int:
return max(len(self._actions) - self._index, 0)
return max(len(self.text) - self._index, 0)
def type_next_character(self) -> None:
with self._lock:
if self._index >= len(self._actions):
if self._index >= len(self.text):
self.stop()
return
action = self._actions[self._index]
char = self.text[self._index]
self._index += 1
self._injecting = True
try:
self._type_action(action)
self._type_char(char)
finally:
self._injecting = False
if self._index >= len(self._actions):
if self._index >= len(self.text):
self.stop()
def stop(self) -> None:
@@ -72,20 +56,6 @@ class KeyStepper:
return
self.type_next_character()
def _type_action(self, action: TypeAction) -> None:
if action.kind == "equation":
self._type_word_equation(action.value)
return
for char in action.value:
self._type_char(char)
def _type_word_equation(self, expression: str) -> None:
keyboard.send("alt+=")
keyboard.write(expression, delay=self.delay, exact=True)
# Word converts much of UnicodeMath after Space; Right exits the equation object.
keyboard.send("space")
keyboard.send("right")
def _type_char(self, char: str) -> None:
if char == "\n":
keyboard.send("enter")