voca.plugins.basic — voca version = release = version = release = 0.1.4 documentation

# Source code for voca.plugins.basic

    import functools
    import sys
    import time
    import signal
    import os

    from typing import List
    from typing import Callable
    from typing import Coroutine
    from typing import Awaitable

    import trio
    import eliot

    from voca import utils
    from voca import platforms
    from voca import log

    try:
        import pyautogui
    except KeyError as e:
        # Display failed
        pyautogui = utils.ModuleLazyRaise("pyautogui", e)

    try:
        import pynput
        import pynput.keyboard
    except Exception as e:
        pynput = utils.ModuleLazyRaise("pynput", e)

    registry = utils.Registry()

    v2p = utils.value_to_pronunciation()
    registry.define(
        {
            "?any_text": r"/\w.+/",
            "key": utils.regex("|".join(utils.pronunciation_to_value().keys())),
            "chord": 'key ("+" chord)*',
            "?sigstr": utils.regex(
                "|".join(["SIGUSR1", "SIGUSR2"] + list(v2p[str(x)] for x in range(20)))
            ),
            "?pid": r"/\d+/",
        }
    )

    @log.log_call
    def type_chord(chord: str):
        """Press a key chord. To avoid blocking, call this in a thread."""
        keyboard = pynput.keyboard.Controller()
        modifiers = [getattr(pynput.keyboard.Key, mod.name) for mod in chord.modifiers]

        try:
            key = pynput.keyboard.Key[chord.name]
        except KeyError:
            key = chord.name

        if modifiers:
            with keyboard.pressed(*modifiers):
                # XXX This seems like it wouldn't work, but it is working in tests.
                keyboard.press(key)
                keyboard.release(key)
                pyautogui.press(chord.name)

        else:
            # I'm not sure why pynput wasn't working on simple keys, but this seems to work.
            pyautogui.press(chord.name)

    @log.log_async_call
    async def press(chord: str):
        """Press a key chord."""
        if isinstance(chord, str):
            await trio.run_sync_in_worker_thread(
                functools.partial(pyautogui.typewrite, [chord])
            )
            return

        await trio.run_sync_in_worker_thread(type_chord, chord)

    @log.log_async_call
    async def write(message: str):
        """Type the ``message``."""
        await trio.run_sync_in_worker_thread(
            functools.partial(pyautogui.typewrite, message)
        )

    @registry.register('"alert" any_text')
    @log.log_async_call
    async def _alert(text: str):
        """Show a gui alert with ``text``."""
        await trio.run_sync_in_worker_thread(functools.partial(pyautogui.alert, text))

    @log.log_async_call
    async def speak(message: str):
        """Speak ``message`` aloud."""
        await utils.run_subprocess(["say", message])

    @registry.register("chord")
    @registry.register('"say" chord')
    @log.log_async_call
    async def _say(message: List[str]):
        """Press a key."""
        [chord_string] = message
        chord_value = utils.pronunciation_to_value()[chord_string]
        await press(chord_value)

    @registry.register('"switch" chord')
    @log.log_async_call
    async def _switch(message: List[str]):
        """Press super + key."""
        [chord_string] = message
        chord_value = utils.pronunciation_to_value()[chord_string]
        await press(f"super+{chord_value}")

    @registry.register('"signal" pid sigstr')
    async def send_signal(message):
        """Send a signal to a process."""

        pid, sigstr = message
        try:
            sig = int(sigstr)
        except ValueError:
            sig = getattr(signal, sigstr)

        with eliot.start_action(action_type="kill_signal", signum=sig, pid=pid):
            os.kill(int(pid), sig)

    press_key: Callable = utils.async_runner(press)

    registry.pattern_to_function['"monitor"'] = press_key("M")
    registry.pattern_to_function['"mouse"'] = press_key("O")

    @registry.register('"div0"')
    async def _div0(*args):
        1 / 0

    wrapper = utils.Wrapper(registry)