Source code for ahkpy.hotkey_context

import dataclasses as dc
import functools
from contextlib import contextmanager
from typing import Callable, Optional, Union

from .hotkey import hotkey as _hotkey
from .hotstring import hotstring as _hotstring
from .remap_key import remap_key as _remap_key
from .flow import ahk_call, global_ahk_lock, _wrap_callback

__all__ = [
    "HotkeyContext",
    "default_context",
    "hotkey",
    "hotstring",
    "remap_key",
]


[docs]@dc.dataclass(frozen=True) class HotkeyContext: """The hotkey, hotstring, and key remappings immutable factory. If the *active_when* argument is a callable, it is executed every time the user triggers the hotkey or hotstring. The callable takes either zero arguments or a *hot_id* argument. The argument is the identifier of the triggered utility. For hotkeys, the identifier will be the *key_name* of the :class:`~ahkpy.Hotkey` that was pressed by the user. For hotstrings, the identifier will be the full AutoHotkey hotstring with packed options. The hotkey/hotstring action is executed only if the callable returns ``True``. The optional positional *args* will be passed to the *active_when* when it is called. If you want the *active_when* to be called with keyword arguments use :func:`functools.partial`. In the following example pressing the :kbd:`F1` key shows the message only when the mouse cursor is over the taskbar:: def is_mouse_over_taskbar(): return ahkpy.get_window_under_mouse().class_name == "Shell_TrayWnd" ctx = ahkpy.HotkeyContext(is_mouse_over_taskbar) ctx.hotkey("F1", ahkpy.message_box, "Pressed F1 over the taskbar.") :command: `Hotkey, If, % FunctionObject <https://www.autohotkey.com/docs/commands/Hotkey.htm#IfFn>`_ """ active_when: Optional[Callable] __slots__ = ("active_when",) # TODO: Consider adding context options: MaxThreadsBuffer, # MaxThreadsPerHotkey, and InputLevel. def __init__(self, active_when: Callable = None, *args): if active_when is None: object.__setattr__(self, "active_when", None) return active_when = _wrap_callback( functools.partial(active_when, *args), ("hotkey",), _bare_predicate, _predicate, ) object.__setattr__(self, "active_when", active_when) # Copy arguments verbatim to make Pylance's suggestions work. Use # functools.wraps so that the API docs are generated. @functools.wraps(_hotkey) def hotkey( self, key_name: str, func: Callable = None, *args, buffer=False, priority=0, max_threads=1, input_level=0, ): return _hotkey( self, key_name, func, *args, buffer=buffer, priority=priority, max_threads=max_threads, input_level=input_level, ) @functools.wraps(_remap_key) def remap_key(self, origin_key, destination_key, *, mode=None, level=None): return _remap_key(self, origin_key, destination_key, mode=mode, level=level) @functools.wraps(_hotstring) def hotstring( self, trigger: str, repl: Union[str, Callable] = None, *args, case_sensitive=False, conform_to_case=True, replace_inside_word=False, wait_for_end_char=True, omit_end_char=False, backspacing=True, priority=0, text=False, mode=None, key_delay=None, reset_recognizer=False, ): return _hotstring( self, trigger, repl, *args, case_sensitive=case_sensitive, conform_to_case=conform_to_case, replace_inside_word=replace_inside_word, wait_for_end_char=wait_for_end_char, omit_end_char=omit_end_char, backspacing=backspacing, priority=priority, text=text, mode=mode, key_delay=key_delay, reset_recognizer=reset_recognizer, ) @contextmanager def _manager(self): # I don't want to make HotkeyContext a Python context manager, because # the end users will be tempted to use it as such, e.g: # # with hotkey_context(lambda: ...): # hotkey(...) # # This approach has a number of issues that can be mitigated, but better # be avoided: # # 1. Current context must be stored in a thread-local storage in order # to be referenced by hotkey(). This can be solved by returning the # context as `with ... as ctx`. # 2. Nested contexts become possible, but implementing them is not # trivial. # # Instead, the following is the chosen way to use the hotkey contexts: # # ctx = hotkey_context(lambda: ...) # ctx.hotkey(...) with global_ahk_lock: self._enter() try: yield finally: self._exit() def _enter(self): if self.active_when is not None: ahk_call("HotkeyContext", self.active_when) def _exit(self): if self.active_when is not None: ahk_call("HotkeyExitContext")
def _bare_predicate(func, *_): return bool(func()) def _predicate(func, hot_id): return bool(func(hot_id=hot_id)) default_context = HotkeyContext() hotkey = default_context.hotkey remap_key = default_context.remap_key hotstring = default_context.hotstring