2025-08-19 08:06:37 -04:00

200 lines
5.1 KiB
Python

from pathlib import Path
from typing import Any
from talon import Context, Module, actions, speech_system
from .rpc_client.get_communication_dir_path import get_communication_dir_path
# Indicates whether a pre-phrase signal was emitted during the course of the
# current phrase
did_emit_pre_phrase_signal = False
mod = Module()
ctx = Context()
mac_ctx = Context()
ctx.matches = r"""
tag: user.command_client
"""
mac_ctx.matches = r"""
os: mac
tag: user.command_client
"""
class NotSet:
def __repr__(self):
return "<argument not set>"
def run_command(
command_id: str,
*args,
wait_for_finish: bool = False,
return_command_output: bool = False,
):
"""Runs a command, using command server if available
Args:
command_id (str): The ID of the command to run.
args: The arguments to the command.
wait_for_finish (bool, optional): Whether to wait for the command to finish before returning. Defaults to False.
return_command_output (bool, optional): Whether to return the output of the command. Defaults to False.
Raises:
Exception: If there is an issue with the file-based communication, or
application raises an exception
Returns:
Object: The response from the command, if requested.
"""
# NB: This is a hack to work around the fact that talon doesn't support
# variable argument lists
args = [x for x in args if x is not NotSet]
return actions.user.rpc_client_run_command(
actions.user.command_server_directory(),
actions.user.trigger_command_server_command_execution,
command_id,
args,
wait_for_finish,
return_command_output,
)
@mod.action_class
class Actions:
def run_rpc_command(
command_id: str,
arg1: Any = NotSet,
arg2: Any = NotSet,
arg3: Any = NotSet,
arg4: Any = NotSet,
arg5: Any = NotSet,
):
"""Execute command via RPC."""
run_command(
command_id,
arg1,
arg2,
arg3,
arg4,
arg5,
)
def run_rpc_command_and_wait(
command_id: str,
arg1: Any = NotSet,
arg2: Any = NotSet,
arg3: Any = NotSet,
arg4: Any = NotSet,
arg5: Any = NotSet,
):
"""Execute command via application command server and wait for command to finish."""
run_command(
command_id,
arg1,
arg2,
arg3,
arg4,
arg5,
wait_for_finish=True,
)
def run_rpc_command_get(
command_id: str,
arg1: Any = NotSet,
arg2: Any = NotSet,
arg3: Any = NotSet,
arg4: Any = NotSet,
arg5: Any = NotSet,
) -> Any:
"""Execute command via application command server and return command output."""
return run_command(
command_id,
arg1,
arg2,
arg3,
arg4,
arg5,
return_command_output=True,
)
def command_server_directory() -> str:
"""Return the directory of the command server"""
def trigger_command_server_command_execution():
"""Issue keystroke to trigger command server to execute command that
was written to the file. For internal use only"""
actions.key("ctrl-shift-f17")
def emit_pre_phrase_signal() -> bool:
"""
If in an application supporting the command client, returns True
and touches a file to indicate that a phrase is beginning execution.
Otherwise does nothing and returns False.
"""
return False
def did_emit_pre_phrase_signal() -> bool:
"""Indicates whether the pre-phrase signal was emitted at the start of this phrase"""
# NB: This action is used by cursorless; please don't delete it :)
return did_emit_pre_phrase_signal
@mac_ctx.action_class("user")
class MacUserActions:
def trigger_command_server_command_execution():
actions.key("cmd-shift-f17")
@ctx.action_class("user")
class UserActions:
def emit_pre_phrase_signal():
get_signal_path("prePhrase").touch()
return True
class MissingCommunicationDir(Exception):
pass
def get_signal_path(name: str) -> Path:
"""
Get the path to a signal in the signal subdirectory.
Args:
name (str): The name of the signal
Returns:
Path: The signal path
"""
dir_name = actions.user.command_server_directory()
communication_dir_path = get_communication_dir_path(dir_name)
if not communication_dir_path.exists():
raise MissingCommunicationDir()
signal_dir = communication_dir_path / "signals"
signal_dir.mkdir(parents=True, exist_ok=True)
return signal_dir / name
def pre_phrase(_: Any):
try:
global did_emit_pre_phrase_signal
did_emit_pre_phrase_signal = actions.user.emit_pre_phrase_signal()
except MissingCommunicationDir:
pass
def post_phrase(_: Any):
global did_emit_pre_phrase_signal
did_emit_pre_phrase_signal = False
speech_system.register("pre:phrase", pre_phrase)
speech_system.register("post:phrase", post_phrase)