ahk Python Wrapper

repository·main·Indexed 21 days ago

https://github.com/spyoungtech/ahk

A fully typed Python wrapper around AutoHotkey for automating Windows tasks. It provides APIs for mouse and keyboard control, window and control manipulation, hotkey and hotstring management, clipboard operations, and screen/pixel searches. The library supports both synchronous and asynchronous (asyncio) modes, allows for non-blocking execution via FutureResult, and can run arbitrary AutoHotkey scripts.

Tokens
8.9K
Snippets
31
Records
47
Agent score
75%

What's inside ahk

  1. Use non-blocking (asynchronous) mode

    main

    By default, all ahk methods are blocking, meaning the Python script waits for the AHK command to finish. To run commands in the background while your Python script continues, use blocking=False.

    Key Characteristics of Non-blocking calls:

    • They return a FutureResult object.
    • They are isolated in a new AHK process that terminates after completion.
    • They do not inherit previous global state changes (like set_coord_mode).
    • You can wait for completion using future_result.result(timeout=N).

    Example: Moving mouse while tracking position

    import time
    from ahk import AHK
    
    ahk = AHK()
    ahk.mouse_position = (200, 200)
    
    # Start moving mouse in background
    future = ahk.mouse_move(x=100, y=100, speed=30, blocking=False)
    
    # Python continues immediately
    while True:
        pos = ahk.mouse_position
        print(f"Current position: {pos}")
        if pos == (100, 100):
            break
    
    # Ensure the background task is finished
    future.result(timeout=10)
    from ahk import AHK
    ahk = AHK()
    future_result = ahk.mouse_move(100, 100, speed=40, blocking=False)
    # ... do other work ...
    future_result.result(timeout=10)
  2. Handle AsyncIO in extensions

    main

    Extension functions are tied to the API type used:

    • Synchronous functions (def): Available only when using the AHK() sync API.
    • Asynchronous functions (async def): Available only when using the AsyncAHK() async API.

    To support both, you must provide both a synchronous and an asynchronous version of the function, registering both to the same extension object.

    @my_extension.register
    def my_function(ahk: AHK, foo, bar):
        ...
    
    @my_extension.register
    async def my_function(ahk: AsyncAHK, foo, bar):
        ...
  3. How hotkeys and hotstrings work together

    main

    Hotkeys and Hotstrings run in a separate background process. To use them, you must explicitly call ahk.start_hotkeys().

    • Hotkeys: Map key combinations (using AHK syntax like #n for Win+n) to Python callback functions.
    • Hotstrings: Can perform text replacement (e.g., btw $\rightarrow$ by the way) or trigger a Python callback.

    If you add or remove hotkeys/hotstrings while the process is running, the underlying AHK process is automatically restarted. Use ahk.block_forever() in scripts that only serve hotkeys to prevent the Python script from exiting immediately.

    from ahk import AHK
    
    def my_callback():
        print('Hello callback!')
    
    ahk = AHK()
    
    # Register a hotkey (Win + n)
    ahk.add_hotkey('#n', callback=my_callback)
    
    # Register a hotstring (replacement)
    ahk.add_hotstring('btw', 'by the way')
    
    # Register a hotstring (callback)
    ahk.add_hotstring('trigger', my_callback)
    
    ahk.start_hotkeys()
    ahk.block_forever()
  4. Use the Async API for non-blocking AHK automation

    main

    The ahk library provides an asynchronous API that is mostly identical to the synchronous API. It allows you to perform AutoHotkey operations without blocking the main execution thread, making it suitable for integration into asyncio-based Python applications. The async API provides specialized versions of core objects:

    • AsyncAHK: The main entry point for asynchronous operations.
    • AsyncWindow: An asynchronous representation of an AHK window.
    • AsyncControl: An asynchronous representation of an AHK control.
    • AsyncFutureResult: A handle for managing the results of asynchronous operations.
  5. Core mechanisms for writing AHK extensions

    main

    When writing an extension for ahk, you must adhere to the following communication protocol between Python and the AHK engine:

    • Function Invocation: Python calls AHK functions by name and can pass any number of arguments as strings.
    • Argument Handling: Functions written in AHK must accept zero or more string arguments.
    • Return Values: AHK functions must return a string formatted in a specific message format. This message tells Python the type of the return value so it can be parsed into the appropriate Python type.
    • Message Types: Use predefined message types from the ahk.message module, or define your own custom message types for specialized data exchange.
  6. Use the Sync API for synchronous AHK automation

    main
    The ahk._sync module provides a synchronous version of the library's API, which is the default API used in most documentation and examples. This API is automatically generated from the asynchronous API using unasync. Use the sync API when you want to perform AutoHotkey automation tasks sequentially without managing async/await syntax.
  7. Understand differences between AutoHotkey v1 and v2

    main

    While the ahk Python API maintains consistent function signatures across versions, the underlying behavior changes when using AutoHotkey v2 due to changes in the AutoHotkey engine. Notable differences include:

    • Error Handling: Functions that find and return windows may raise a TargetError in v2 instead of returning None when a window or control is not found.
    • ControlSend Behavior: In v1, ahk.control_send (or Window.send/Control.send) sends keys to the topmost controls if no control is specified. In v2, keys are sent directly to the window. You should specify the control parameter explicitly in v2 to ensure consistent behavior.
    • TrayTip Limitations: The secondstowait parameter for ahk.show_traytip is not supported in v2. Specifying it will trigger a warning and the parameter will be ignored.
    • Missing Features: Some sound functions available in v1 are not yet implemented in the v2 version of the library.
    • SendMode: The default SendMode in v2 is Input, whereas v1 defaults to Event. Consequently, mouse speed parameters in mouse_move or mouse_drag will be ignored in v2 unless the send mode is manually changed.
    • TitleMatchMode: The default TitleMatchMode is 2 in v2, compared to 1 in v1. You can control this using the title_match_mode keyword argument in methods like win_get, or by using set_title_match_mode to change the global default (note: non-blocking calls run in separate processes and are not affected by set_title_match_mode).
  8. Install AutoHotkey dependencies

    main

    The ahk library requires an AutoHotkey executable (AutoHotkey.exe) to function. It supports both v1 and v2.

    Install the binary extra to automatically download and place the necessary executables on your PATH:

    pip install "ahk[binary]"

    Option 2: Provide path in code

    If you have a specific executable, pass the path to the AHK constructor:

    from ahk import AHK
    ahk = AHK(executable_path='C:\path\to\AutoHotkey.exe')

    Option 3: Use Environment Variable

    Set the AHK_PATH environment variable to point to your executable:

    set AHK_PATH=C:\Path\To\AutoHotkey.exe
    python myscript.py
  9. Install the ahk package

    main

    Install the ahk Python wrapper using pip. This package requires Python 3.8+ and supports both AutoHotkey v1 and v2. Note that you may need to install AutoHotkey on your system as a non-Python dependency.

    pip install ahk
  10. Configure AutoHotkey v2 usage

    main

    By default, the library searches for AutoHotkey v1 binaries. To use AutoHotkey v2, you must explicitly specify it using one of the following methods:

    1. executable_path: Provide the direct path to the v2 binary via the executable_path keyword argument in the AHK constructor.
    2. AHK_PATH environment variable: Set the AHK_PATH environment variable to the location of your v2 binary.
    3. version keyword argument: Pass version='v2' to the AHK constructor. This enables the library to search for v2 binary names and default installation locations.

    If you provide the version keyword (either 'v1' or 'v2'), the library performs a check to ensure the discovered binary matches the requested version. If omitted, the version is determined automatically from the binary.

    from ahk import AHK
    
    # Method 1: Explicit path
    ahk = AHK(executable_path=r'C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe')
    
    # Method 2: Using the version keyword
    ahk = AHK(version='v2')
  11. Write an AHK extension

    main

    An extension consists of two parts: an AutoHotkey function and a Python function.

    1. AHK Function: Must accept zero or more arguments and return a formatted message using FormatResponse(message_type_name, payload).
    2. Python Function: Must accept an instance of AHK (or AsyncAHK) as its first parameter. Use the @extension.register decorator to bind the Python function to the extension.

    To use the extension automatically, instantiate AHK with extensions='auto'.

    from ahk import AHK
    from ahk.extensions import Extension
    
    # 1. The AHK code
    script_text = r'''
    SimpleMath(lhs, rhs, operator) {
        if (operator = "+") {
            result := (lhs + rhs)
        } else {
            return FormatResponse("ahk.message.ExceptionResponseMessage", Format("Invalid operator: {}", operator))
        }
        return FormatResponse("ahk.message.IntegerResponseMessage", result)
    }
    '''
    
    # 2. The Extension object
    simple_math_extension = Extension(script_text=script_text)
    
    # 3. The Python registration
    @simple_math_extension.register
    def simple_math(ahk: AHK, lhs: int, rhs: int, operator: str) -> int:
        args = [str(lhs), str(rhs), operator]
        return ahk.function_call('SimpleMath', args, blocking=True)
    
    # 4. Usage
    ahk = AHK(extensions='auto')
    print(ahk.simple_math(2, 2, '+')) # 4
  12. Write and run your first AHK script

    main

    To use AHK, import the AHK class, instantiate it, and use methods like run_script to execute AutoHotkey commands, or win_get to interact with specific windows. Once you have a window object, you can use methods like send to simulate keystrokes.

    from ahk import AHK
    
    ahk = AHK()
    ahk.run_script('Run Notepad')
    notepad_window = ahk.win_get(title='Untitled - Notepad')
    notepad_window.send('Hello World')