The fork takes its own name everywhere a user can see it: binaries (voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus namespace (com.voculinux.Voculinux, no alias for the old name), config/data/cache dirs, single-instance lock, IBus engine identity, tray icons, resource prefixes, VOCULINUX_* environment names, and the update-checker user agent. The internal Python module stays vocalinux and the distribution metadata keeps the upstream URLs as provenance. On first run a previous Vocalinux install's config.json, custom dictionary, and portal restore token are copied once into the new locations (new config always wins, no merge, no write-back); models re-resolve through the pinned-checksum download path. An offline naming-sweep test proves no old-name strings remain on user-visible surfaces outside the allowlist. Refs #18
111 lines
3.5 KiB
Python
111 lines
3.5 KiB
Python
"""
|
|
Action handler for Voculinux voice commands.
|
|
|
|
This module handles special voice commands like "delete that", "undo", etc.
|
|
"""
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING, Callable
|
|
|
|
if TYPE_CHECKING:
|
|
from ..text_injection.text_injector import TextInjector
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ActionHandler:
|
|
"""
|
|
Handles special voice command actions.
|
|
|
|
This class processes action commands from the speech recognition system
|
|
and performs the appropriate actions like deleting text, undoing, etc.
|
|
"""
|
|
|
|
# Keyboard shortcut mappings for simple forwarding actions.
|
|
# Actions not listed here have custom handlers defined as methods.
|
|
_SHORTCUT_ACTIONS: dict[str, str] = {
|
|
"undo": "ctrl+z",
|
|
"redo": "ctrl+y",
|
|
"select_all": "ctrl+a",
|
|
"select_line": "Home+shift+End",
|
|
"select_word": "ctrl+shift+Right",
|
|
"select_paragraph": "ctrl+shift+Down",
|
|
"cut": "ctrl+x",
|
|
"copy": "ctrl+c",
|
|
"paste": "ctrl+v",
|
|
}
|
|
|
|
def __init__(self, text_injector: "TextInjector"):
|
|
"""
|
|
Initialize the action handler.
|
|
|
|
Args:
|
|
text_injector: The text injector instance for performing actions
|
|
"""
|
|
self.text_injector = text_injector
|
|
self.last_injected_text = ""
|
|
|
|
# Build action dispatch table: custom handlers + shortcut-based actions
|
|
self.action_handlers: dict[str, Callable[[], bool]] = {
|
|
"delete_last": self._handle_delete_last,
|
|
}
|
|
for action, shortcut in self._SHORTCUT_ACTIONS.items():
|
|
self.action_handlers[action] = self._make_shortcut_handler(shortcut)
|
|
|
|
def handle_action(self, action: str) -> bool:
|
|
"""
|
|
Handle a voice command action.
|
|
|
|
Args:
|
|
action: The action to perform
|
|
|
|
Returns:
|
|
True if the action was handled successfully, False otherwise
|
|
"""
|
|
logger.debug(f"Handling action: {action}")
|
|
|
|
handler = self.action_handlers.get(action)
|
|
if handler:
|
|
try:
|
|
return handler()
|
|
except Exception as e:
|
|
logger.error(f"Error handling action '{action}': {e}")
|
|
return False
|
|
else:
|
|
logger.warning(f"Unknown action: {action}")
|
|
return False
|
|
|
|
def set_last_injected_text(self, text: str):
|
|
"""
|
|
Set the last injected text for undo/delete operations.
|
|
|
|
Args:
|
|
text: The last text that was injected
|
|
"""
|
|
self.last_injected_text = text
|
|
|
|
def _make_shortcut_handler(self, shortcut: str) -> Callable[[], bool]:
|
|
"""Create a handler that sends a keyboard shortcut via the text injector."""
|
|
|
|
def handler() -> bool:
|
|
return self.text_injector._inject_keyboard_shortcut(shortcut)
|
|
|
|
return handler
|
|
|
|
def _handle_delete_last(self) -> bool:
|
|
"""Handle 'delete that' command by sending backspace keys."""
|
|
if not self.last_injected_text:
|
|
logger.debug("No text to delete")
|
|
return True
|
|
|
|
# Send real BackSpace key events, one per character. Injecting U+0008 as
|
|
# text does nothing: ydotool has no keymap entry for it and IBus commits
|
|
# it as a literal control character.
|
|
success = self.text_injector.press_backspace(len(self.last_injected_text))
|
|
|
|
if success:
|
|
logger.debug(f"Deleted {len(self.last_injected_text)} characters")
|
|
self.last_injected_text = ""
|
|
|
|
return success
|