diff --git a/.coveragerc b/.coveragerc index 27dc3e3c..b4c3abd9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,2 @@ [run] -branch = True source = ahk diff --git a/.github/ISSUE_TEMPLATE/00_bug.yaml b/.github/ISSUE_TEMPLATE/00_bug.yaml new file mode 100644 index 00000000..0394b234 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/00_bug.yaml @@ -0,0 +1,60 @@ +name: bug report +description: something went wrong +body: + - type: markdown + attributes: + value: | + Please use this issue template to report bug behavior + + - type: textarea + id: what-happened + attributes: + label: describe your issue + description: Please describe the problem, the expected behavior, and the actual behavior + placeholder: | + I was doing ... + I ran ... + I expected ... + I got ... + validations: + required: true + - type: input + id: library-version + attributes: + label: ahk.__version__ + placeholder: 1.x.x + validations: + required: false + - type: input + id: ahk-version + attributes: + label: AutoHotkey version + placeholder: v1 or v2 + validations: + required: false + - type: textarea + id: code + attributes: + label: Code to reproduce the issue + description: Minimal Python code that can be used to reproduce the issue. (no backticks needed) + placeholder: | + from ahk import AHK + ahk = AHK() + ahk.do_something() + render: python + validations: + required: false + - type: textarea + id: error-log + attributes: + label: 'Traceback/Error message' + description: The full traceback/error you receive or other error information, if applicable + placeholder: | + Traceback (most recent call last): + File "C:\path\to\yourscript.py", line 3, in + ahk.failure() + File "C:\path\to\site-packages\ahk\_sync\engine.py", line 220, in __getattr__ + raise AttributeError(f'{self.__class__.__name__!r} object has no attribute {name!r}') + AttributeError: 'AHK' object has no attribute 'failure' + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/01_feature.yaml b/.github/ISSUE_TEMPLATE/01_feature.yaml new file mode 100644 index 00000000..40769698 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01_feature.yaml @@ -0,0 +1,33 @@ +name: feature request +description: something new +body: + - type: markdown + attributes: + value: | + Use this form to create feature requests + + - type: checkboxes + attributes: + label: Checked the documentation + description: | + The documentation contains information about features that are already implemented. Please check this first before making a request. + (requests for features marked as "Not Implemented" in the documentation are OK, but please provide context on how you want to use this feature). + options: + - label: I have checked [the documentation](https://ahk.readthedocs.io/en/latest/api/methods.html) for the feature I am requesting + required: true + + + - type: textarea + id: freeform + attributes: + label: describe your feature request + placeholder: | + I want to do ... + I tried ... + It does not work because ... + + This feature would be useful because ... + + Additional information can be found at https://www.autohotkey.com/docs/ ... + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..933290b3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: +- name: documentation + url: https://ahk.readthedocs.io/en/latest/ + about: See the full documentation diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..b23911c0 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,37 @@ +name: release + +on: + push: + tags: + - 'v*.*.*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: 3.11 + + - name: build + shell: bash + run: | + python -m pip install --upgrade wheel setuptools build + python -m build + - name: Release PyPI + shell: bash + env: + TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + run: | + pip install --upgrade twine + twine upload dist/* + - name: Release GitHub + uses: softprops/action-gh-release@v1 + with: + files: "dist/*" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..187afd9d --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,46 @@ +on: [ push, pull_request ] + +jobs: + build: + strategy: + fail-fast: false + matrix: + python_version: ["3.10", "3.9", "3.8", "3.11"] + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python_version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements-dev.txt + python -m pip install . + - name: Test with coverage/pytest + timeout-minutes: 10 + env: + PYTHONUNBUFFERED: "1" + run: | + tox -e py + - name: Coveralls + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_PARALLEL: "true" + COVERALLS_SERVICE_JOB_ID: ${{ github.run_id }} + run: | + pip install --upgrade coveralls + coveralls --service=github + finish: + runs-on: ubuntu-latest + needs: build + steps: + - name: finish coveralls + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + pip install --upgrade coveralls + coveralls --service=github --finish diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fd8988f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/ + +# CMake +cmake-build-*/ + + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + + +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..f120defc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,64 @@ +repos: + +- repo: local + hooks: + - id: unasync-rewrite + name: unasync-rewrite + entry: python .unasync-rewrite.py + language: python + types: [python] + files: ^(ahk/_async/.*\.py|\.unasync-rewrite\.py|tests/_async/.*\.py) + pass_filenames: false + additional_dependencies: + - git+https://github.com/spyoungtech/unasync.git@unasync-remove + - tokenize_rt + - black + - id: set-constants + name: set-constants + entry: python _set_constants.py + language: python + types: [python] + pass_filenames: false + files: ^(ahk/daemon\.ahk|ahk/_constants\.py) + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: mixed-line-ending + args: ["-f", "lf"] + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: double-quote-string-fixer +- repo: https://github.com/psf/black-pre-commit-mirror + rev: '26.5.1' + hooks: + - id: black + args: + - "-S" + - "-l" + - "120" + exclude: ^(ahk/_sync/.*\.py) +- repo: https://github.com/asottile/reorder-python-imports + rev: v3.17.0 + hooks: + - id: reorder-python-imports + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v2.3.0' + hooks: + - id: mypy + args: + - "--strict" + exclude: ^(tests/.*|setup\.py|\.build\.py|\.unasync-rewrite\.py|_tests_setup\.py|buildunasync\.py) + additional_dependencies: + - jinja2 + +- repo: https://github.com/pycqa/flake8 + rev: '7.3.0' # pick a git hash / tag to point to + hooks: + - id: flake8 + args: + - "--ignore" + - "E501,E704,E301,W503,E701" + files: ahk\/(?!_sync).* diff --git a/.unasync-rewrite.py b/.unasync-rewrite.py new file mode 100644 index 00000000..01c379d8 --- /dev/null +++ b/.unasync-rewrite.py @@ -0,0 +1,57 @@ +import os +import shutil +import subprocess +import sys + +import black + +GIT_EXECUTABLE = shutil.which('git') + +changes = 0 + +if hasattr(black, 'ASTSafetyError'): + exceptions = (AssertionError, black.ASTSafetyError) +else: + exceptions = (AssertionError,) + + +def _copyfunc(src, dst, *, follow_symlinks=True): + global changes + with open(src, encoding='UTF-8') as f: + contents = f.read() + if os.path.exists(dst): + with open(dst, encoding='UTF-8') as dst_f: + dst_contents = dst_f.read() + try: + black.assert_equivalent( + src=contents, + dst=dst_contents, + ) + except exceptions: + changes += 1 + print('MODIFIED', dst) + shutil.copy2(src, dst, follow_symlinks=follow_symlinks) + else: + changes += 1 + print('ADDED', dst) + shutil.copy2(src, dst, follow_symlinks=follow_symlinks) + if GIT_EXECUTABLE is None: + print('WARNING could not find git!', file=sys.stderr) + else: + subprocess.run([GIT_EXECUTABLE, 'add', '--intent-to-add', dst]) + return dst + + +def main() -> int: + if os.path.isdir('build'): + shutil.rmtree('build') + subprocess.run([sys.executable, 'setup.py', 'build_py'], check=True) + subprocess.run([sys.executable, '_tests_setup.py', 'build_py'], check=True) + shutil.copytree('build/lib/ahk/_sync', 'ahk/_sync', dirs_exist_ok=True, copy_function=_copyfunc) + shutil.copytree('build/lib/tests/_sync', 'tests/_sync', dirs_exist_ok=True, copy_function=_copyfunc) + + return changes + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..716d5543 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,282 @@ +# Contribution Guide + +This guide is a work in progress, but aims to help a new contributor make a successful contribution to this project. + +If you have questions about contributing not answered here, always feel free to [open an issue](https://github.com/spyoungtech/ahk/issues) +or [discussion](https://github.com/spyoungtech/ahk/discussions) and I will help you the best that I am able. + + +* [Contribution Guide](#contribution-guide) +* [Before contributing](#before-contributing) +* [Initial development setup](#initial-development-setup) + * [Code formatting, linting, etc.](#code-formatting-linting-etc) +* [Unasync Code Generation](#unasync-code-generation) +* [Pre-commit hooks](#pre-commit-hooks) +* [Running tests](#running-tests) +* [How this project works, briefly](#how-this-project-works-briefly) + * [Hotkeys](#hotkeys) +* [Example: Implementing a new method](#example-implementing-a-new-method) + * [Writing the AutoHotkey code](#writing-the-autohotkey-code) + * [Writing the Python code](#writing-the-python-code) + * [Testing and code generation](#testing-and-code-generation) +* [About your contributions :balance_scale:](#about-your-contributions-balance_scale) + + + +# Before contributing + +Generally, all contributions should be associated with an [open issue](https://github.com/spyoungtech/ahk/issues). +Contributors are strongly encouraged to comment on an existing issue or create a new issue before working on a PR, +especially for feature work. Some contributions don't necessarily require this, such as typo fixes or documentation +improvements. When in doubt, create an issue. + + + +# Initial development setup + +Some prerequisite steps are needed to get ready for development on this project: + +- Activated virtualenv with Python version 3.9 or later (`py -m venv venv` and `venv\Scripts\activate`) +- Installed the dev requirements (`pip install -r requirements-dev.txt`) (this includes a binary redistribution of AutoHotkey) +- Installed pre-commit hooks (`pre-commit install`) + +That's it! + +## Code formatting, linting, etc. + +All matters of code style, linting, etc. are all handled by pre-commit hooks. All the proper parameters for formatting +and correct order of operations are provided there. If you try to run `black` or similar formatters directly on the +project, it will likely produce a lot of unintended changes that will not be accepted. + +For these reasons and more, it is critical that you use the `pre-commit` hooks in order to make a successful contribution. + + +# Unasync Code Generation + +This project leverages a [fork](https://github.com/spyoungtech/unasync/tree/unasync-remove) of [`unasync`](https://github.com/python-trio/unasync) +to automatically generate synchronous code (output to the `ahk/_sync` directory) from async code in the `ahk/_async` directory. + +To be clear: **you will _never_ need to write code directly in the `ahk/_sync` directory**. This is all auto-generated code. + +Code generation runs as part of the pre-commit hooks. + + +# Pre-commit hooks + +Pre-commit hooks are an essential part of development for this project. They will ensure your code is properly formatted +and linted. It is also essential for performing code generation, as discussed in the previous section. + +To run the pre-commit hooks: + +```bash +pre-commit run --all-files +``` + + +# Running tests + +The test suite is managed by [`tox`](https://tox.wiki/en/latest/) (installed as part of `requirements-dev`) + +You can run the test suite with the following command: + +```bash +tox -e py +``` + +Tox runs tests in an isolated environment. + +Although `tox` is the recommended way of testing, with all dev requirements installed, +you can run the tests directly with `pytest` (but be sure to run code generation first!): + +```bash +pytest tests +``` + +Notes: + +- The test suite expects the presence of the (legacy since Windows 11) `notepad.exe` program. This is included by default in Windows 10, but you may have to install this manually in later versions of Windows +- You will pretty much need to leave your computer alone during the test suite run. Moving the mouse, typing on the keyboard, or doing much of anything will make tests fail +- Due to the nature of this library, the test suite takes a long time to run +- Some tests (which only run locally, not in CI) for pixelsearch/imagesearch may fail depending on your monitor settings. This can safely be ignored. +- Some tests are flaky -- the tox configuration adds appropriate reruns to pytest to compensate for this, but reruns are not always 100% effective +- You can also simply rely on the GitHub Actions workflows for running tests + + +# How this project works, briefly + +Understanding how this project works under the hood is an important part to contributing. Here, we'll graze over the +most important implementation details, but contributors are encouraged to dive into the source code to learn more +and always feel free to open an issue or discussion to ask questions. + +This project is a wrapper around AutoHotkey. That is: it does not directly implement the underlying functionality, but +instead relies directly on AutoHotkey itself to function; specifically, AutoHotkey is invoked as a subprocess. + +In typical usage, an AutoHotkey subprocess is created and runs the "daemon" script (found in `ahk/templates/`). The +[Auto-Execute section](https://www.autohotkey.com/docs/v2/Scripts.htm#auto) of which is an infinite loop that awaits +inputs via `stdin` to execute functions and return responses. The request and response formats are specialized. + +A typical function call (like, say, `ahk.mouse_move`) works roughly like this: + +0. If the AutoHotkey subprocess has not been previously started (or if the call is made with `blocking=False`), a new AutoHotkey process is created, running the daemon AHK script. +1. Python takes the keyword arguments of the method (if any) and prepares them into a request message (fundamentally, a list of strings, starting with the function name followed by any arguments) +2. The request is sent via `stdin` to the AutoHotkey subprocess (by implementation detail, arguments are base64 encoded and pipe-delimited and the mesage is newline-terminated) +3. The AutoHotkey subprocess (which is a loop reading `stdin`) reads/decodes the message and calls the corresponding function -- All such function calls to AutoHotkey **ALWAYS** return a response, even when the return value is ultimately `None`. +4. The AutoHotkey functions return a response, which is then written to `stdout` to send back to Python. The message contains information about the return type (such as a string, tuple, Exception, etc.) and the payload itself +5. Python then reads the response from the subprocess's `stdout` handle, translates the response to the return value expected by the caller. Responses can also be exception types, in which case, an exception can be raised as a result of decoding the message + + +Technically, a subprocess is only one possible transport. Although it is the only one implemented directly in this library, +alternate transports can be used, such as in the [ahk-client](https://github.com/spyoungtech/ahk-client) project, which implements +AHK function calls over HTTP (to a server running [ahk-server](https://github.com/spyoungtech/ahk-server)). + + +## Hotkeys + +Hotkeys work slightly different from typical functions. Hotkeys are powered by a separate subprocess, which is started +with the `start_hotkeys` method. This subprocess runs the hotkeys script (e.g. `ahk/templates/hotkeys-v2.ahk`). This works +like a normal AutoHotkey script and when hotkeys are triggered, they write to `stdout`. A Python thread reads +from `stdout` and triggers the registered hotkey function. Unlike normal functions found in `ahk/_async`, the implementation of hotkeys +(found in `ahk/hotkeys.py`) is not implemented async-first -- it is all synchronous/threaded Python. + + +# Example: Implementing a new method + +This section will guide you through the steps of implementing a basic new feature. This is very closely related to the +documented process of [writing an extension](https://ahk.readthedocs.io/en/latest/extending.html), except that you are +including the functionality directly in the project, rather than using the extensions interface. It is highly +recommended that you read the extension docs! + +This involves three basic steps: + +1. Writing the AutoHotkey function(s) -- for both v1 and v2 +2. Writing the (async) Python method(s) +3. Generating the sync code and testing (which implies writing tests at some point!) + + +In this example, we'll add a simple method that simply calls into AHK to do some arithemetic. Normally, +such a method wouldn't be prudent to implement in this library since Python can obviously handle arithmetic without +AutoHotkey, but we'll ignore this just for the sake of the example. + +It is recommended, but not required, that you start by checking out a new branch named after the GitHub issue number +you're working on in the format `gh-` e.g.: + +```bash +git checkout -b gh-12345 +``` + + +## Writing the AutoHotkey code + +For example, in `ahk/templates/daemon-v2.ahk`, you may add a new function as so: + +```AutoHotkey +AHKSimpleMath(lhs, rhs, operator) { + if (operator = "+") { + result := (lhs + rhs) + } else if (operator = "*") { + result := (lhs * rhs) + } else { ; invalid operator argument + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("Invalid operator: {}", operator)) + } + return FormatResponse("ahk.message.IntegerResponseMessage", result) +} +``` + +And you would add the same to `ahk/templates/daemon.ahk` for AHK V1. + +Note that functions must always return a response (e.g. as provided by `FormatResponse`). Refer to the [extension guide](https://ahk.readthedocs.io/en/latest/extending.html) +for more information about available message formats and implementing new message formats. + + +## Writing the Python code + + +For example, in `ahk/_async/engine.py` you might add the following method to the `AsyncAHK` class: + +```python +async def simple_math(self, lhs: int, rhs: int, operator: Literal['+', '*']) -> int: + """ + Exposes arithmetic functions in AutoHotkey for plus and times operators + """ + assert isinstance(lhs, int) + assert isinstance(rhs, int) + + # Normally, you probably want to validate all inputs, but we'll comment this out to demo bubbling up AHK exceptions + # assert operator in ('+', '*') + + args = [str(lhs), str(rhs), operator] # all args must be strings + result = await self._transport.function_call('AHKSimpleMath', args, blocking=True) + return result +``` + + +The most important part of this code is that the last part of the function returns the value of `await self._transport.function_call("FUNCTION NAME", ...)`. + +:warning: For functions that accept the `blocking` keyword argument, it is important that no further manipulation be done on the value returned +(since it can be a _future_ result and not the ultimate value). If additional processing of the return value is needed, it +should be implemented in the message type instead. + + +## Testing and code generation + +In `tests/_async` create a new testcase in a new file like `tests/_async/test_math.py` with some basic test cases +that cover a range of possible inputs and expected exceptional cases: + +```python +import unittest + +import pytest + +from ahk import AsyncAHK + +class MathTestCases(unittest.IsolatedAsyncioTestCase): + async def test_simple_math_plus_operator(self): + ahk = AsyncAHK() + result = await ahk.simple_math(1, 2, '+') + expected = 3 + assert result == expected + + async def test_simple_math_times_operator(self): + ahk = AsyncAHK() + result = await ahk.simple_math(2, 3, '*') + expected = 6 + assert result == expected + + async def test_simple_math_bad_operator(self): + ahk = AsyncAHK() + with pytest.raises(Exception) as exc_info: + await ahk.simple_math(1, 2, '>>>') + assert "Invalid operator:" in str(exc_info.value) +``` + +Finally, run the `pre-commit` hooks to generate the synchronous code (both for your implementation and your tests) + +```bash +pre-commit run --all-files +``` + +You'll notice that the `ahk/_sync` directory and the `tests/_sync` directories now contain the synchronous +versions of your implementation code and your tests, respectively. + +And then run the tests: + +```bash +tox -e py +``` + +When all tests are passing, you are ready to open a pull request to get your contributions reviewed and merged. + +# About your contributions :balance_scale: + +When you submit contributions to this project, you should understand that your contributions will be licensed under +the license terms of the project (found in `LICENSE`). + +Moreover, by submitting a pull request to this project, you are representing that the code you are contributing is your own and is +unencumbered by any other licensing requirements. + +Do not submit unoriginal code that is either unlicensed or licensed under any other terms without stating its source and +ensuring the contribution is fully compliant with any such licensing terms (which usually requires, at a minimum, +including the license itself). Even when contributing work under implied, creative commons, or licenses that do not +require attribution or notices (e.g. [_unlicence_](https://unlicense.org/) or similar), you are expected to explicitly +state the source of any material you submit that is not your own work. This includes, for example, code snippets found +on StackOverflow, the AutoHotkey forums, or any other source other than your own brain. diff --git a/MANIFEST.in b/MANIFEST.in index bd895033..dd7fcd35 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,5 @@ +include ahk/templates/daemon.ahk +include ahk/templates/hotkeys.ahk +include docs/README.md +include buildunasync.py include LICENSE -include README.md -recursive-include ahk/templates * diff --git a/README.md b/README.md deleted file mode 100644 index ca3768f7..00000000 --- a/README.md +++ /dev/null @@ -1,344 +0,0 @@ -# ahk - -A Python wrapper around AHK. - -[![Build](https://ci.appveyor.com/api/projects/status/2c53x6gglw9nxgj1/branch/master?svg=true)](https://ci.appveyor.com/project/spyoungtech/ahk/branch/master) -[![version](https://img.shields.io/pypi/v/ahk.svg?colorB=blue)](https://pypi.org/project/ahk/) -[![pyversion](https://img.shields.io/pypi/pyversions/ahk.svg?)](https://pypi.org/project/ahk/) -[![Coverage](https://coveralls.io/repos/github/spyoungtech/ahk/badge.svg?branch=master)](https://coveralls.io/github/spyoungtech/ahk?branch=master) - - -# Installation - -``` -pip install ahk -``` - -See also [Non-Python dependencies](#deps) - - -# Usage - -```python -from ahk import AHK -ahk = AHK() -ahk.mouse_move(x=100, y=100, speed=10, blocking=True) # blocks until mouse finishes moving (the default) -print(ahk.mouse_position) # (100, 100) -``` - -![ahk](https://raw.githubusercontent.com/spyoungtech/ahk/master/docs/_static/ahk.gif) - - -# Examples - -Non-exhaustive examples of some of the functions available with this package. Full documentation coming soon! - - -## Mouse - - -```python -from ahk import AHK -ahk = AHK() - -ahk.mouse_position # tuple of mouse coordinates (x,y) -ahk.mouse_move(100, 100, speed=10, relative=True) # move mouse offset from current position -ahk.mouse_position = (100, 100) # moves mouse instantly to absolute position -ahk.click() # click primary mouse button -ahk.double_click() -ahk.click(200, 200) # click a particular position -ahk.right_click() -ahk.mouse_drag(100, 100, relative=True) -``` - -## Keyboard - -```python -from ahk import AHK -ahk = AHK() - -ahk.type('hello, world!') # sends keys, as if typed (performs ahk string escapes) -ahk.send_input('Hello`, World{!}') # Like AHK SendInput, must escape strings yourself! -ahk.key_wait('a', timeout=3) # wait up to 3 seconds for the "a" key to be pressed -ahk.key_state('Control') # return True or False based on whether Control key is pressed down -ahk.key_state('CapsLock', mode='T') # check toggle state of a key (like for NumLock, CapsLock, etc) -ahk.key_press('a') # press and release a key -ahk.key_down('Control') # press down (but do not release) Control key -ahk.key_up('Control') # release the key -``` - -## Windows - -You can do stuff with windows, too. - - -Getting windows - -```python -from ahk import AHK -from ahk.window import Window -ahk = AHK() -win = ahk.active_window # get the active window -win = ahk.win_get(title='Untitled - Notepad') # by title -win = list(ahk.windows()) # list of all windows -win = Window(ahk, ahk_id='0xabc123') # by ahk_id -win = Window.from_mouse_position(ahk) # a window under the mouse cursor -win = Window.from_pid('20366') # by process ID -``` - -Working with windows -```python -from ahk import AHK -ahk = AHK() -ahk.run_script('Run Notepad') -win = ahk.find_window(title=b'Untitled - Notepad') -win.send('hello') # send keys directly to a window (does not need focus!) -win.move(x=200, y=300, width=500, height=800) -win.activate() # give the window focus -win.disable() # make the window non-interactable -win.enable() # enable it again -win.to_top() # moves window on top of other windows -win.to_bottom() -win.always_on_top = True # make the windows always on top -win.close() - -for window in ahk.windows(): - print(window.title) - -# some more attributes -print(window.text) -print(window.rect) # (x, y, width, height) -print(window.id) # ahk_id -print(window.pid) -print(window.process) -``` - -## Screen - -```python -from ahk import AHK -ahk = AHK() -ahk.image_search('C:\\path\\to\\image.jpg') # find an image on screen -# find image within a boundary on screen -ahk.image_search('C:\\path\\to\\image.jpg', upper_bound=(100, 100), # upper-left corner of search area - lower_bound=(400, 400)) # lower-right corner of search area -ahk.pixel_get_color(100, 100) # get color of pixel located at coords (100, 100) -ahk.pixel_search('0x9d6346') # get coords of first pixel with specified color -``` - -## Sound - -```python -from ahk import AHK -ahk = AHK() - -ahk.sound_play('C:\\path\\to\\sound.wav') # play an audio file -ahk.sound_beep(frequency=440, duration=1000) # play a beep -ahk.get_volume(device_number=1) # get volume of a device -ahk.set_volume(50, device_number=1) # set volume of a device -ahk.sound_get(device_number=1, component_type='MASTER', control_type='VOLUME') # get sound device property -ahk.sound_set(50, device_number=1, component_type='MASTER', control_type='VOLUME') # set sound device property -``` - -## non-blocking modes - -For some functions, you can also opt for a non-blocking interface, so you can do other stuff while AHK scripts run. - -```python -import time -from ahk import AHK -ahk = AHK() -ahk.mouse_position = (200, 200) # moves the mouse instantly to the position -start = time.time() -ahk.mouse_move(x=100, y=100, speed=30, blocking=False) -while True: # report mouse position while it moves - t = round(time.time() - start, 4) - position = ahk.mouse_position - print(t, position) - if position == (100, 100): - break -``` - -You should see an output something like - -``` -0.032 (187, 187) -0.094 (173, 173) -0.137 (164, 164) -... -0.788 (100, 103) -0.831 (100, 101) -0.873 (100, 100) -``` - - -## Run arbitrary AutoHotkey scripts - -```python -from ahk import AHK -ahk = AHK() -ahk_script = 'Run Notepad' -ahk.run_script(ahk_script, blocking=False) -``` - - -### Communicating data from ahk to Python - -If you're writing your own ahk scripts to use with this library, you can use `FileAppend` with the `*` parameter to get data from your ahk script into Python. - -Suppose you have a script like so - -```autohotkey -#Persistent -data := "Hello Data!" -FileAppend, %data%, * ; send data var to stdout -ExitApp -``` - -```py -result = ahk.run_script(my_script) -print(result) # Hello Data! -``` - -If your autohotkey returns something that can't be decoded, add the keyword argument `decode=False` in which case you'll get back a `CompletedProcess` object where stdout (and stderr) will be bytes and you can handle it however you choose. - -```py -result = ahk.run_script(my_script, decode=False) -print(result.stdout) # b'Hello Data!' -``` - - -## Experimental features - -Experimental features are things that are minimally functional, (even more) likely to have breaking changes, even -for minor releases. - -Github issues are provided for convenience to collect feedback on these features. - - -### Hotkeys - -[GH-9] - -Hotkeys now have a primitive implementation. You give it a hotkey (a string the same as in an ahk script, without the `::`) -and the body of an AHK script to execute as a response to the hotkey. - - - -```python -from ahk import AHK, Hotkey -ahk = AHK() -key_combo = '#n' -script = 'Run Notepad' -hotkey = Hotkey(ahk, key_combo, script) -hotkey.start() # listener process activated -``` -At this point, the hotkey is active. -If you press ![Windows Key][winlogo] + n, the script `Run Notepad` will execute. - -There is no need to add `return` to the provided script, as it is provided by the template. - -To stop the hotkey call the `stop()` method. - -```python -hotkey.stop() -``` - - -### ActionChain - -[GH-25] - -`ActionChain`s let you define a set of actions to be performed in order at a later time. - -They work just like the `AHK` class, except the actions are deferred until the `perform` method is called. - -An additional method `sleep` is provided to allow for waiting between actions. - -```python -from ahk import ActionChain -ac = ActionChain() -ac.mouse_move(100, 100, speed=10) # nothing yet -ac.sleep(1) # still nothing happening -ac.mouse_move(500, 500, speed=10) # not yet -ac.perform() # *now* each of the actions run in order -``` - -Just like anywhere else, scripts running simultaneously may conflict with one another, so using blocking interfaces is -generally recommended. Currently, there is limited support for interacting with windows in actionchains, you may want to use `win_set`) - - -### find_window/find_windows methods - -[GH-26] - -Right now, these are implemented by iterating over all window handles and filtering with Python. -They may be optimized in the future. - -`AHK.find_windows` returns a generator filtering results based on attributes provided as keyword arguments. -`AHK.find_window` is similar, but returns the first matching window instead of all matching windows. - -There are couple convenience functions, but not sure if these will stay around or maybe we'll add more, depending on feedback. - -* find_windows_by_title -* find_window_by_title -* find_windows_by_text -* find_window_by_text - -## Errors and Debugging - -You can enable debug logging, which will output script text before execution, and some other potentially useful -debugging information. - -```python -import logging -logging.basicConfig(level=logging.DEBUG) -``` - -Also note that, for now, errors with running AHK scripts will often pass silently. In the future, better error handling -will be added. - - - -## Non-Python dependencies - -To use this package, you need the [AutoHotkey executable](https://www.autohotkey.com/download/). - -It's expected to be on PATH by default. You can also use the `AHK_PATH` environment variable to specify the executable location. - -Alternatively, you may provide the path in code - -```python -from ahk import AHK -ahk = AHK(executable_path='C:\\path\\to\\AutoHotkey.exe') -``` - - -# Contributing - -All contributions are welcomed and appreciated. - -Please feel free to open a GitHub issue or PR for feedback, ideas, feature requests or questions. - -There's still some work to be done in the way of implementation. The ideal interfaces are still yet to be determined and -*your* help would be invaluable. - - -The vision is to provide access to the most useful features of the AutoHotkey API in a Pythonic way. - - -[winlogo]: http://i.stack.imgur.com/Rfuw7.png -[GH-9]: https://github.com/spyoungtech/ahk/issues/9 -[GH-25]: https://github.com/spyoungtech/ahk/issues/25 -[GH-26]: https://github.com/spyoungtech/ahk/issues/26 - -# Similar projects - -These are some similar projects that are commonly used for automation with Python. - -* [Pyautogui](https://pyautogui.readthedocs.io) - Al Sweigart's creation for cross-platform automation -* [Pywinauto](https://pywinauto.readthedocs.io) - Automation on Windows platforms with Python. -* [keyboard](https://github.com/boppreh/keyboard) - Pure Python cross-platform keyboard hooks/control and hotkeys! -* [mouse](https://github.com/boppreh/mouse) - From the creators of `keyboard`, Pure Python *mouse* control! -* [pynput](https://github.com/moses-palmer/pynput) - Keyboard and mouse control - diff --git a/_set_constants.py b/_set_constants.py new file mode 100644 index 00000000..a44a283b --- /dev/null +++ b/_set_constants.py @@ -0,0 +1,49 @@ +import shutil +import subprocess +import sys + +with open('ahk/templates/daemon.ahk') as f: + daemon_script = f.read() + +with open('ahk/templates/hotkeys.ahk') as hotkeyfile: + hotkey_script = hotkeyfile.read() + +with open('ahk/templates/daemon-v2.ahk') as fv2: + daemon_script_v2 = fv2.read() + +with open('ahk/templates/hotkeys-v2.ahk') as hotkeyfilev2: + hotkey_script_v2 = hotkeyfilev2.read() + +GIT_EXECUTABLE = shutil.which('git') + +if not GIT_EXECUTABLE: + raise RuntimeError('git executable not found') + +new_contents = f'''\ +# THIS FILE IS AUTOGENERATED BY _set_constants.py +# DO NOT EDIT BY HAND + +DAEMON_SCRIPT_TEMPLATE = r"""{daemon_script} +""" + +HOTKEYS_SCRIPT_TEMPLATE = r"""{hotkey_script} +""" + +DAEMON_SCRIPT_V2_TEMPLATE = r"""{daemon_script_v2} +""" + +HOTKEYS_SCRIPT_V2_TEMPLATE = r"""{hotkey_script_v2} +""" +''' + +with open('ahk/_constants.py', encoding='utf-8') as f: + constants_text = f.read() + +if constants_text != new_contents: + with open('ahk/_constants.py', 'w', encoding='utf-8') as f: + f.write(new_contents) + print('MODIFIED _constants.py', file=sys.stderr) + subprocess.run([GIT_EXECUTABLE, 'add', '--intent-to-add', 'ahk/_constants.py']) + raise SystemExit(1) +else: + raise SystemExit(0) diff --git a/_tests_setup.py b/_tests_setup.py new file mode 100644 index 00000000..949b414c --- /dev/null +++ b/_tests_setup.py @@ -0,0 +1,38 @@ +# Not a real setup package. This is just to unasync our tests files +import setuptools +import unasync + +setuptools.setup( + name='ahk', + version='0.0.1', + author='Example Author', + author_email='author@example.com', + description='A package used to test customized unasync', + url='https://github.com/pypa/sampleproject', + packages=['tests', 'tests._async'], + cmdclass={ + 'build_py': unasync.cmdclass_build_py( + rules=[ + unasync.Rule( + fromdir='/tests/_async/', + todir='/tests/_sync/', + additional_replacements={ + 'AsyncAHK': 'AHK', + 'AsyncTransport': 'Transport', + 'AsyncWindow': 'Window', + 'AsyncDaemonProcessTransport': 'DaemonProcessTransport', + '_AIOP': '_SIOP', + 'async_create_process': 'sync_create_process', + 'adrain_stdin': 'drain_stdin', + 'IsolatedAsyncioTestCase': 'TestCase', + 'asyncSetUp': 'setUp', + 'asyncTearDown': 'tearDown', + 'async_sleep': 'sleep', + # "__aenter__": "__aenter__", + }, + ), + ] + ) + }, + # package_dir={"": "src"}, +) diff --git a/ahk/__init__.py b/ahk/__init__.py index a528d9cb..eda73d85 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -1,3 +1,70 @@ -from ahk.autohotkey import AHK, ActionChain -from ahk.keyboard import Hotkey -__all__ = ['AHK'] +from typing import Any +from typing import Optional + +from ._async import AsyncAHK +from ._async import AsyncControl +from ._async import AsyncWindow +from ._async.transport import AsyncFutureResult +from ._sync import AHK +from ._sync import Control +from ._sync import Window +from ._sync.transport import FutureResult +from ._types import Coordinates +from ._types import CoordMode +from ._types import CoordModeRelativeTo +from ._types import CoordModeTargets +from ._types import MatchModes +from ._types import MatchSpeeds +from ._types import MouseButton +from ._types import Position +from ._types import SendMode +from ._types import TitleMatchMode +from ._utils import MsgBoxButtons +from ._utils import MsgBoxDefaultButton +from ._utils import MsgBoxIcon +from ._utils import MsgBoxModality + +__all__ = [ + 'AHK', + 'Window', + 'AsyncWindow', + 'AsyncAHK', + 'Control', + 'AsyncControl', + 'MsgBoxButtons', + 'MsgBoxDefaultButton', + 'MsgBoxIcon', + 'MsgBoxModality', + 'Coordinates', + 'CoordMode', + 'CoordModeRelativeTo', + 'CoordModeTargets', + 'MatchModes', + 'MatchSpeeds', + 'MouseButton', + 'Position', + 'SendMode', + 'TitleMatchMode', + 'MsgBoxButtons', + 'MsgBoxDefaultButton', + 'MsgBoxIcon', + 'MsgBoxModality', + 'AsyncFutureResult', + 'FutureResult', +] + +_global_instance: Optional[AHK[None]] = None + + +def __getattr__(name: str) -> Any: + global _global_instance + if name in dir(AHK): + if _global_instance is None: + try: + _global_instance = AHK() + except EnvironmentError as init_error: + raise EnvironmentError( + 'Tried to create default global AHK instance, but it failed. This is most likely due to AutoHotkey.exe not being available on PATH or other default locations' + ) from init_error + return getattr(_global_instance, name) + raise AttributeError(f'module {__name__!r} has no attribute {name!r}') diff --git a/ahk/_async/__init__.py b/ahk/_async/__init__.py new file mode 100644 index 00000000..139416a1 --- /dev/null +++ b/ahk/_async/__init__.py @@ -0,0 +1,5 @@ +from .engine import AsyncAHK +from .window import AsyncControl +from .window import AsyncWindow + +__all__ = ['AsyncAHK', 'AsyncWindow', 'AsyncControl'] diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py new file mode 100644 index 00000000..8bb9c6e9 --- /dev/null +++ b/ahk/_async/engine.py @@ -0,0 +1,3976 @@ +from __future__ import annotations + +import asyncio +import os +import sys +import tempfile +import time +import warnings +from functools import partial +from typing import Any +from typing import Awaitable +from typing import Callable +from typing import Coroutine +from typing import Generic +from typing import List +from typing import Literal +from typing import NoReturn +from typing import Optional +from typing import overload +from typing import Tuple +from typing import Type +from typing import TypeVar +from typing import Union + +from .transport import AsyncDaemonProcessTransport +from .transport import AsyncFutureResult +from .transport import AsyncTransport +from .window import AsyncControl +from .window import AsyncWindow +from ahk._hotkey import Hotkey +from ahk._hotkey import Hotstring +from ahk._types import _BUTTONS +from ahk._types import Coordinates +from ahk._types import CoordModeRelativeTo +from ahk._types import CoordModeTargets +from ahk._types import MouseButton +from ahk._types import Position +from ahk._types import SendMode +from ahk._types import TitleMatchMode +from ahk._utils import _get_executable_major_version +from ahk._utils import _resolve_executable_path +from ahk._utils import MsgBoxButtons +from ahk._utils import MsgBoxDefaultButton +from ahk._utils import MsgBoxIcon +from ahk._utils import MsgBoxModality +from ahk._utils import MsgBoxOtherOptions +from ahk._utils import type_escape +from ahk.directives import Directive +from ahk.extensions import _extension_registry +from ahk.extensions import _ExtensionMethodRegistry +from ahk.extensions import _resolve_extensions +from ahk.extensions import Extension +from ahk.keys import Key + +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + +async_sleep = asyncio.sleep # unasync: remove +sleep = time.sleep + +AsyncFilterFunc: TypeAlias = Callable[[AsyncWindow], Awaitable[bool]] # unasync: remove +SyncFilterFunc: TypeAlias = Callable[[AsyncWindow], bool] + +AsyncPropertyReturnTupleIntInt: TypeAlias = Coroutine[None, None, Coordinates] # unasync: remove +SyncPropertyReturnTupleIntInt: TypeAlias = Coordinates + +AsyncPropertyReturnOptionalAsyncWindow: TypeAlias = Coroutine[None, None, Optional[AsyncWindow]] # unasync: remove +SyncPropertyReturnOptionalAsyncWindow: TypeAlias = Optional[AsyncWindow] + +_PROPERTY_DEPRECATION_WARNING_MESSAGE = 'Use of the {0} property is not recommended (in the async API only) and may be removed in a future version. Use the get_{0} method instead' + + +def _resolve_button(button: Union[str, int]) -> str: + """ + Resolve a string of a button name to a canonical name used for AHK script + :param button: + :type button: str + :return: + """ + if isinstance(button, str): + button = button.lower() + + if button in _BUTTONS: + resolved_button = _BUTTONS[button] + elif isinstance(button, int) and button > 3: + # for addtional mouse buttons + resolved_button = f'X{button - 3}' + else: + assert isinstance(button, str) + resolved_button = button + return resolved_button + + +T_AHKVersion = TypeVar('T_AHKVersion', bound=Optional[Literal['v1', 'v2']]) + + +class AsyncAHK(Generic[T_AHKVersion]): + # fmt: off + @overload + def __init__(self: AsyncAHK[None], *, TransportClass: Optional[Type[AsyncTransport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', extensions: list[Extension] | None | Literal['auto'] = None): ... + @overload + def __init__(self: AsyncAHK[None], *, TransportClass: Optional[Type[AsyncTransport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', extensions: list[Extension] | None | Literal['auto'] = None, version: None): ... + @overload + def __init__(self: AsyncAHK[Literal['v2']], *, TransportClass: Optional[Type[AsyncTransport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', extensions: list[Extension] | None | Literal['auto'] = None, version: Literal['v2']): ... + @overload + def __init__(self: AsyncAHK[Literal['v1']], *, TransportClass: Optional[Type[AsyncTransport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', extensions: list[Extension] | None | Literal['auto'] = None, version: Literal['v1']): ... + # fmt: on + def __init__( + self: AsyncAHK[Optional[Literal['v1', 'v2']]], + *, + TransportClass: Optional[Type[AsyncTransport]] = None, + directives: Optional[list[Directive | Type[Directive]]] = None, + executable_path: str = '', + extensions: list[Extension] | None | Literal['auto'] = None, + version: Optional[Literal['v1', 'v2']] = None, + ): + if version not in (None, 'v1', 'v2'): + raise ValueError(f'Invalid version ({version!r}). Must be one of None, "v1", or "v2"') + executable_path = _resolve_executable_path(executable_path=executable_path, version=version) + skip_version_check = False + if version is None: + try: + version = _get_executable_major_version(executable_path) + except Exception as e: + warnings.warn( + f'Could not detect AHK version ({e}). This is likely caused by a misconfigured AutoHotkey executable and will likely cause a fatal error later on.\nAssuming v1 for now.' + ) + version = 'v1' + skip_version_check = True + + if not skip_version_check: + detected_version = _get_executable_major_version(executable_path) + if version != detected_version: + raise RuntimeError( + f'AutoHotkey {version} was requested but AutoHotkey {detected_version} was detected for executable {executable_path}' + ) + self._version: Literal['v1', 'v2'] = version + self._extension_registry: _ExtensionMethodRegistry + self._extensions: list[Extension] + if extensions == 'auto': + self._extensions = [ext for ext in _extension_registry if ext._requires in (None, version)] + else: + self._extensions = _resolve_extensions(extensions) if extensions else [] + for ext in self._extensions: + if ext._requires not in (None, version): + raise ValueError( + f'Incompatible extension detected. Extension requires AutoHotkey {ext._requires} but current version is {version}' + ) + self._method_registry = _ExtensionMethodRegistry( + sync_methods={}, async_methods={}, async_window_methods={}, sync_window_methods={} + ) + for ext in self._extensions: + self._method_registry.merge(ext._extension_method_registry) + if TransportClass is None: + TransportClass = AsyncDaemonProcessTransport + assert TransportClass is not None + transport = TransportClass( + executable_path=executable_path, directives=directives, extensions=self._extensions, version=version + ) + self._transport: AsyncTransport = transport + + def __repr__(self) -> str: + return f'<{self.__module__}.{self.__class__.__qualname__} object version={self._version!r}>' + + def __getattr__(self, name: str) -> Callable[..., Any]: + is_async = False + is_async = True # unasync: remove + if is_async: + if name in self._method_registry.async_methods: + method = self._method_registry.async_methods[name] + return partial(method, self) + else: + if name in self._method_registry.sync_methods: + method = self._method_registry.sync_methods[name] + return partial(method, self) + + raise AttributeError(f'{self.__class__.__name__!r} object has no attribute {name!r}') + + def _get_window_extension_method(self, name: str) -> Callable[..., Any] | None: + is_async = False + is_async = True # unasync: remove + if is_async: + if name in self._method_registry.async_window_methods: + method = self._method_registry.async_window_methods[name] + return method + else: + if name in self._method_registry.sync_window_methods: + method = self._method_registry.sync_window_methods[name] + return method + return None + + def add_hotkey( + self, keyname: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None + ) -> None: + """ + Register a function to be called when a hotkey is pressed. + + Key notes: + + - You must call the `start_hotkeys` method for the hotkeys to be active + - All hotkeys run in a single AHK process instance (but Python callbacks also get their own thread and can run simultaneously) + - If you add a hotkey after the hotkey thread/instance is active, it will be restarted automatically + - `async` functions are not directly supported as callbacks, however you may write a synchronous function that calls `asyncio.run`/`loop.create_task` etc. + + :param keyname: the key trigger for the hotkey, such as ``#n`` (win+n) + :param callback: callback function to call when the hotkey is triggered + :param ex_handler: a function which accepts two parameters: the keyname for the hotkey and the exception raised by the callback function. + :return: + """ + hotkey = Hotkey(keyname, callback, ex_handler=ex_handler) + with warnings.catch_warnings(record=True) as caught_warnings: + self._transport.add_hotkey(hotkey=hotkey) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return None + + async def function_call(self, function_name: str, args: list[str] | None = None, blocking: bool = True) -> Any: + """ + Call an AHK function defined in the daemon script. This method is intended for use by extension authors. + """ + if args is None: + args = [] + return await self._transport.function_call(function_name, args, blocking=blocking, engine=self) # type: ignore[call-overload] + + def add_hotstring( + self, + trigger: str, + replacement_or_callback: Union[str, Callable[[], Any]], + ex_handler: Optional[Callable[[str, Exception], Any]] = None, + options: str = '', + ) -> None: + """ + Register a hotstring, e.g., `::btw::by the way` + + Key notes: + + - You must call the `start_hotkeys` method for registered hotstrings to be active + - All hotstrings (and hotkeys) run in a single AHK process instance separate from other AHK processes. + + :param trigger: the trigger phrase for the hotstring, e.g., ``btw`` + :param replacement_or_callback: the replacement phrase (e.g., ``by the way``) or a Python callable to execute in response to the hotstring trigger + :param ex_handler: a function which accepts two parameters: the hotstring and the exception raised by the callback function. + :param options: the hotstring options -- same meanings as in AutoHotkey. + :return: + """ + hotstring = Hotstring(trigger, replacement_or_callback, ex_handler=ex_handler, options=options) + with warnings.catch_warnings(record=True) as caught_warnings: + self._transport.add_hotstring(hotstring=hotstring) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return None + + def remove_hotkey(self, keyname: str) -> None: + def _() -> None: + return None + + h = Hotkey(keyname=keyname, callback=_) # XXX: this can probably be avoided + self._transport.remove_hotkey(hotkey=h) + return None + + def clear_hotkeys(self) -> None: + self._transport.clear_hotkeys() + return None + + def remove_hotstring(self, trigger: str) -> None: + hs = Hotstring(trigger=trigger, replacement_or_callback='') # XXX: this can probably be avoided + self._transport.remove_hotstring(hs) + return None + + def clear_hotstrings(self) -> None: + self._transport.clear_hotstrings() + return None + + async def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> None: + """ + Sets the default title match mode + + Does not affect methods called with ``blocking=True`` (because these run in a separate AHK process) + + Reference: https://www.autohotkey.com/docs/commands/SetTitleMatchMode.htm + + :param title_match_mode: the match mode (and/or match speed) to set as the default title match mode. Can be 1, 2, 3, 'Regex', 'Fast', 'Slow' or a tuple of these. + :return: None + """ + + args = [] + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + await self._transport.function_call('AHKSetTitleMatchMode', args) + return None + + async def get_title_match_mode(self) -> str: + """ + Get the title match mode. + + I.E. the current value of ``A_TitleMatchMode`` + + """ + resp = await self._transport.function_call('AHKGetTitleMatchMode') + return resp + + async def get_title_match_speed(self) -> str: + """ + Get the title match mode speed. + + I.E. the current value of ``A_TitleMatchModeSpeed`` + + """ + resp = await self._transport.function_call('AHKGetTitleMatchSpeed') + return resp + + async def set_coord_mode(self, target: CoordModeTargets, relative_to: CoordModeRelativeTo = 'Screen') -> None: + """ + Analog of `CoordMode `_ + """ + args = [str(target), str(relative_to)] + await self._transport.function_call('AHKSetCoordMode', args) + return None + + async def get_coord_mode(self, target: CoordModeTargets) -> str: + """ + Analog for ``A_CoordMode`` + """ + args = [str(target)] + resp = await self._transport.function_call('AHKGetCoordMode', args) + return resp + + async def set_send_mode(self, mode: SendMode) -> None: + """ + Analog for `SendMode `_ + """ + args = [str(mode)] + await self._transport.function_call('AHKSetSendMode', args) + return None + + async def get_send_mode(self) -> str: + resp = await self._transport.function_call('AHKGetSendMode') + return resp + + # fmt: off + @overload + async def control_click(self, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def control_click(self, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def control_click(self, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def control_click(self, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def control_click( + self, + button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', + click_count: int = 1, + options: str = '', + control: str = '', + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `ControlClick `_ + """ + args = [control, title, text, str(button), str(click_count), options, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = await self._transport.function_call('AHKControlClick', args=args, blocking=blocking) + + return resp + + # fmt: off + @overload + async def control_get_text(self, *, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> str: ... + @overload + async def control_get_text(self, *, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def control_get_text(self, *, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + @overload + async def control_get_text(self, *, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def control_get_text( + self, + *, + control: str = '', + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[str, AsyncFutureResult[str]]: + """ + Analog for `ControlGetText `_ + """ + args = [control, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = await self._transport.function_call('AHKControlGetText', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def control_get_position(self, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Position: ... + @overload + async def control_get_position(self, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Position]: ... + @overload + async def control_get_position(self, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Position: ... + @overload + async def control_get_position(self, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[Position, AsyncFutureResult[Position]]: ... + # fmt: on + async def control_get_position( + self, + control: str = '', + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[Position, AsyncFutureResult[Position]]: + """ + Analog to `ControlGetPos `_ + """ + args = [control, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + + resp = await self._transport.function_call('AHKControlGetPos', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def control_send( + self, + keys: str, + control: str = '', + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `ControlSend `_ + """ + args = [control, keys, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = await self._transport.function_call('AHKControlSend', args, blocking=blocking) + return resp + + # TODO: raw option for control_send + + def start_hotkeys(self) -> None: + """ + Start the Autohotkey process for triggering hotkeys + + """ + return self._transport.start_hotkeys() + + def stop_hotkeys(self) -> None: + """ + Stop the Autohotkey process for triggering hotkeys/hotstrings + + """ + return self._transport.stop_hotkeys() + + async def set_detect_hidden_windows(self, value: bool) -> None: + """ + Analog for `DetectHiddenWindows `_ + + :param value: The setting value. ``True`` to turn on hidden window detection, ``False`` to turn it off. + """ + + if value not in (True, False): + raise TypeError(f'detect hidden windows must be a boolean, got object of type {type(value)}') + args = [] + if value is True: + args.append('1') + else: + args.append('0') + await self._transport.function_call('AHKSetDetectHiddenWindows', args=args) + return None + + @staticmethod + def _format_win_args( + title: str, + text: str, + exclude_title: str, + exclude_text: str, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + ) -> List[str]: + args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + return args + + # fmt: off + @overload + async def list_windows(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> List[AsyncWindow]: ... + @overload + async def list_windows(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[List[AsyncWindow]]: ... + @overload + async def list_windows(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[AsyncWindow]: ... + @overload + async def list_windows(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + # fmt: on + async def list_windows( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: + """ + Enumerate all windows matching the criteria. + + Analog for `WinGet List subcommand _` + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWindowList', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + async def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[True]) -> Coordinates: ... + @overload + async def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[False]) -> AsyncFutureResult[Coordinates]: ... + @overload + async def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None) -> Coordinates: ... + @overload + async def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True) -> Union[Coordinates, AsyncFutureResult[Coordinates]]: ... + # fmt: on + async def get_mouse_position( + self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True + ) -> Union[Coordinates, AsyncFutureResult[Coordinates]]: + """ + Analog for `MouseGetPos `_ + """ + if coord_mode: + args = [str(coord_mode)] + else: + args = [] + resp = await self._transport.function_call('AHKMouseGetPos', args, blocking=blocking) + return resp + + @property + def mouse_position(self) -> AsyncPropertyReturnTupleIntInt: + """ + Convenience property for :py:meth:`get_mouse_position` + + Setter accepts a tuple of x,y coordinates passed to :py:meth:`mouse_mouse` + """ + warnings.warn( # unasync: remove + _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('mouse_position'), category=DeprecationWarning, stacklevel=2 + ) + return self.get_mouse_position() + + @mouse_position.setter + def mouse_position(self, new_position: Tuple[int, int]) -> None: + """ + Convenience setter for ``mouse_move`` + + :param new_position: a tuple of x,y coordinates to move to + """ + raise RuntimeError('Use of the mouse_position setter is not supported in the async API.') # unasync: remove + x, y = new_position + return self.mouse_move(x=x, y=y, speed=0, relative=False) + + # fmt: off + @overload + async def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Union[str, int]] = None, *, speed: Optional[int] = None, relative: bool = False, send_mode: Optional[SendMode] = None, coord_mode: Optional[CoordModeRelativeTo] = None) -> None: ... + @overload + async def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Union[str, int]] = None, *, blocking: Literal[True], speed: Optional[int] = None, relative: bool = False, send_mode: Optional[SendMode] = None, coord_mode: Optional[CoordModeRelativeTo] = None) -> None: ... + @overload + async def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Union[str, int]] = None, *, blocking: Literal[False], speed: Optional[int] = None, relative: bool = False, send_mode: Optional[SendMode] = None, coord_mode: Optional[CoordModeRelativeTo] = None) -> AsyncFutureResult[None]: ... + @overload + async def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Union[str, int]] = None, *, speed: Optional[int] = None, relative: bool = False, blocking: bool = True, send_mode: Optional[SendMode] = None, coord_mode: Optional[CoordModeRelativeTo] = None) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def mouse_move( + self, + x: Optional[Union[str, int]] = None, + y: Optional[Union[str, int]] = None, + *, + speed: Optional[int] = None, + relative: bool = False, + send_mode: Optional[SendMode] = None, + blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `MouseMove `_ + """ + if relative and (x is None or y is None): + x = x or 0 + y = y or 0 + elif not relative and (x is None or y is None): + posx, posy = await self.get_mouse_position() + x = x or posx + y = y or posy + + if speed is None: + speed = 2 + args = [str(x), str(y), str(speed)] + if relative: + args.append('R') + else: + args.append('') + if send_mode: + args.append(send_mode) + else: + args.append('') + + if coord_mode: + args.append(coord_mode) + else: + args.append('') + + resp = await self._transport.function_call('AHKMouseMove', args, blocking=blocking) + return resp + + async def a_run_script(self, *args: Any, **kwargs: Any) -> Union[str, AsyncFutureResult[str]]: + """ + Deprecated. Use :py:meth:`run_script` instead. + """ + warnings.warn('a_run_script is deprecated. Use run_script instead.', DeprecationWarning, stacklevel=2) + return await self.run_script(*args, **kwargs) + + # fmt: off + @overload + async def get_active_window(self) -> Optional[AsyncWindow]: ... + @overload + async def get_active_window(self, blocking: Literal[True]) -> Optional[AsyncWindow]: ... + @overload + async def get_active_window(self, blocking: Literal[False]) -> AsyncFutureResult[Optional[AsyncWindow]]: ... + @overload + async def get_active_window(self, blocking: bool = True) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: ... + # fmt: on + async def get_active_window( + self: AsyncAHK[Any], blocking: bool = True + ) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]], AsyncFutureResult[AsyncWindow]]: + """ + Gets the currently active window. + """ + return await self.win_get( + title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking + ) + + # Ideally, this would be type-hinted for the AHK version. But we cant: https://github.com/python/mypy/issues/9937 + @property + def active_window(self) -> AsyncPropertyReturnOptionalAsyncWindow: + """ + Gets the currently active window. Convenience property for :py:meth:`get_active_window` + """ + warnings.warn( # unasync: remove + _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('active_window'), category=DeprecationWarning, stacklevel=2 + ) + return self.get_active_window() + + async def find_windows( + self, + func: Optional[AsyncFilterFunc] = None, + *, + title_match_mode: Optional[TitleMatchMode] = None, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + exact: Optional[bool] = None, + ) -> List[AsyncWindow]: + if exact is not None and title_match_mode is not None: + raise TypeError('exact and match_mode parameters are mutually exclusive') + if exact is not None: + warnings.warn('exact parameter is deprecated. Use title_match_mode instead', stacklevel=2) + if exact: + title_match_mode = (3, 'Fast') + else: + title_match_mode = (1, 'Fast') + elif title_match_mode is None: + title_match_mode = (1, 'Fast') + + windows = await self.list_windows( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + ) + if func is None: + return windows + else: + ret: List[AsyncWindow] = [] + for win in windows: + match = await func(win) + if match: + ret.append(win) + return ret + + async def find_windows_by_class( + self, class_name: str, *, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> List[AsyncWindow]: + with warnings.catch_warnings(record=True) as caught_warnings: + ret = await self.find_windows( + title=f'ahk_class {class_name}', title_match_mode=title_match_mode, exact=exact + ) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return ret + + async def find_windows_by_text( + self, text: str, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> List[AsyncWindow]: + with warnings.catch_warnings(record=True) as caught_warnings: + ret = await self.find_windows(text=text, exact=exact, title_match_mode=title_match_mode) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return ret + + async def find_windows_by_title( + self, title: str, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> List[AsyncWindow]: + with warnings.catch_warnings(record=True) as caught_warnings: + ret = await self.find_windows(title=title, exact=exact, title_match_mode=title_match_mode) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return ret + + async def find_window( + self, + func: Optional[AsyncFilterFunc] = None, + *, + title_match_mode: Optional[TitleMatchMode] = None, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + exact: Optional[bool] = None, + ) -> Optional[AsyncWindow]: + with warnings.catch_warnings(record=True) as caught_warnings: + windows = await self.find_windows( + func, + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + exact=exact, + title_match_mode=title_match_mode, + ) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return windows[0] if windows else None + + async def find_window_by_class( + self, class_name: str, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> Optional[AsyncWindow]: + with warnings.catch_warnings(record=True) as caught_warnings: + windows = await self.find_windows_by_class( + class_name=class_name, exact=exact, title_match_mode=title_match_mode + ) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return windows[0] if windows else None + + async def find_window_by_text( + self, text: str, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> Optional[AsyncWindow]: + with warnings.catch_warnings(record=True) as caught_warnings: + windows = await self.find_windows_by_text(text=text, exact=exact, title_match_mode=title_match_mode) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return windows[0] if windows else None + + async def find_window_by_title( + self, title: str, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> Optional[AsyncWindow]: + with warnings.catch_warnings(record=True) as caught_warnings: + windows = await self.find_windows_by_title(title=title, exact=exact, title_match_mode=title_match_mode) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return windows[0] if windows else None + + async def get_volume(self, device_number: int = 1) -> float: + """ + Analog for `SoundGetWaveVolume `_ + """ + args = [str(device_number)] + response = await self._transport.function_call('AHKGetVolume', args) + return response + + # fmt: off + @overload + async def key_down(self, key: Union[str, Key]) -> None: ... + @overload + async def key_down(self, key: Union[str, Key], *, blocking: Literal[True]) -> None: ... + @overload + async def key_down(self, key: Union[str, Key], *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def key_down(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def key_down(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + """ + Shortcut for :py:meth:`send_input` but transforms specified key to perform a key "DOWN" only (no release) + """ + if isinstance(key, str): + key = Key(key_name=key) + if blocking: + await self.send_input(key.DOWN, blocking=True) + return None + else: + return await self.send_input(key.DOWN, blocking=False) + + # fmt: off + @overload + async def key_press(self, key: Union[str, Key], *, release: bool = True) -> None: ... + @overload + async def key_press(self, key: Union[str, Key], *, blocking: Literal[True], release: bool = True) -> None: ... + @overload + async def key_press(self, key: Union[str, Key], *, blocking: Literal[False], release: bool = True) -> AsyncFutureResult[None]: ... + @overload + async def key_press(self, key: Union[str, Key], *, release: bool = True, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def key_press( + self, key: Union[str, Key], *, release: bool = True, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + """ + Press (and release) a key. Sends `:py:meth:`key_down` then, if ``release`` is ``True`` (the default), sends + :py:meth:`key_up` subsequently. + """ + if blocking: + await self.key_down(key, blocking=True) + if release: + await self.key_up(key, blocking=True) + return None + else: + d = await self.key_down(key, blocking=False) + if release: + return await self.key_up(key, blocking=False) + return d + + # fmt: off + @overload + async def key_release(self, key: Union[str, Key]) -> None: ... + @overload + async def key_release(self, key: Union[str, Key], *, blocking: Literal[True]) -> None: ... + @overload + async def key_release(self, key: Union[str, Key], *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + """ + Alias for :py:meth:`key_up` + """ + if blocking: + await self.key_up(key=key, blocking=True) + return None + else: + return await self.key_up(key=key, blocking=False) + + # fmt: off + @overload + async def key_state(self, key_name: str, *, mode: Optional[Literal['T', 'P']] = None) -> Union[float, int, str, None]: ... + @overload + async def key_state(self, key_name: str, *, mode: Optional[Literal['T', 'P']] = None, blocking: Literal[True]) -> Union[float, int, str, None]: ... + @overload + async def key_state(self, key_name: str, *, mode: Optional[Literal['T', 'P']] = None, blocking: Literal[False]) -> Union[AsyncFutureResult[str], AsyncFutureResult[int], AsyncFutureResult[float], AsyncFutureResult[None]]: ... + @overload + async def key_state(self, key_name: str, *, mode: Optional[Literal['T', 'P']] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None], Union[str, AsyncFutureResult[str]], Union[int, AsyncFutureResult[int]], Union[float, AsyncFutureResult[float]]]: ... + # fmt: on + async def key_state( + self, key_name: str, *, mode: Optional[Literal['T', 'P']] = None, blocking: bool = True + ) -> Union[ + int, + float, + str, + None, + AsyncFutureResult[str], + AsyncFutureResult[int], + AsyncFutureResult[float], + AsyncFutureResult[None], + ]: + """ + Analog for `GetKeyState `_ + """ + args: List[str] = [key_name] + if mode is not None: + if mode not in ('T', 'P'): + raise ValueError(f'Invalid value for mode parameter. Mode must be `T` or `P`. Got {mode!r}') + args.append(mode) + else: + args.append('') + resp = await self._transport.function_call('AHKKeyState', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def key_up(self, key: Union[str, Key]) -> None: ... + @overload + async def key_up(self, key: Union[str, Key], *, blocking: Literal[True]) -> None: ... + @overload + async def key_up(self, key: Union[str, Key], *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + """ + Shortcut for :py:meth:`send_input` but transforms specified key to perform a key "UP" only. Useful if the key + was previously pressed down but not released. + """ + if isinstance(key, str): + key = Key(key_name=key) + if blocking: + await self.send_input(key.UP, blocking=True) + return None + else: + return await self.send_input(key.UP, blocking=False) + + # fmt: off + @overload + async def key_wait(self, key_name: str, *, timeout: Optional[int | float] = None, logical_state: bool = False, released: bool = False) -> bool: ... + @overload + async def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int | float] = None, logical_state: bool = False, released: bool = False) -> bool: ... + @overload + async def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int | float] = None, logical_state: bool = False, released: bool = False) -> AsyncFutureResult[bool]: ... + @overload + async def key_wait(self, key_name: str, *, timeout: Optional[int | float] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + # fmt: on + async def key_wait( + self, + key_name: str, + *, + timeout: Optional[int | float] = None, + logical_state: bool = False, + released: bool = False, + blocking: bool = True, + ) -> Union[bool, AsyncFutureResult[bool]]: + """ + Analog for `KeyWait `_ + """ + options = '' + if not released: + options += 'D' + if logical_state: + options += 'L' + if timeout is not None: + assert timeout >= 0, 'Timeout value must be non-negative' + options += f'T{timeout}' + args = [key_name, options] + + resp = await self._transport.function_call('AHKKeyWait', args, blocking=blocking) + return resp + + async def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, AsyncFutureResult[str]]: + """ + Run an AutoHotkey script. + Can either be a path to a script (``.ahk``) file or a string containing script contents + """ + return await self._transport.run_script(script_text_or_path, blocking=blocking, timeout=timeout) + + async def set_send_level(self, level: int) -> None: + """ + Analog for `SendLevel `_ + """ + if not isinstance(level, int): + raise TypeError('level must be an integer between 0 and 100') + if not 0 <= level <= 100: + raise ValueError('level value must be between 0 and 100') + args = [str(level)] + await self._transport.function_call('AHKSetSendLevel', args) + + async def get_send_level(self) -> int: + """ + Get the current `SendLevel `_ + (I.E. the value of ``A_SendLevel``) + """ + resp = await self._transport.function_call('AHKGetSendLevel') + return resp + + # fmt: off + @overload + async def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + async def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, send_mode: Optional[SendMode] = None, blocking: Literal[True]) -> None: ... + @overload + async def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, send_mode: Optional[SendMode] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, send_mode: Optional[SendMode] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def send( + self, + s: str, + *, + raw: bool = False, + key_delay: Optional[int] = None, + key_press_duration: Optional[int] = None, + send_mode: Optional[SendMode] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `Send `_ + """ + args = [s] + if key_delay: + args.append(str(key_delay)) + else: + args.append('') + if key_press_duration: + args.append(str(key_press_duration)) + else: + args.append('') + if send_mode: + args.append(send_mode) + else: + args.append('') + + if raw: + raw_resp = await self._transport.function_call('AHKSendRaw', args=args, blocking=blocking) + return raw_resp + else: + resp = await self._transport.function_call('AHKSend', args=args, blocking=blocking) + return resp + + # fmt: off + @overload + async def send_raw(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None) -> None: ... + @overload + async def send_raw(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[True]) -> None: ... + @overload + async def send_raw(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def send_raw(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def send_raw( + self, + s: str, + *, + key_delay: Optional[int] = None, + key_press_duration: Optional[int] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `SendRaw `_ + """ + resp = await self.send( + s, raw=True, key_delay=key_delay, key_press_duration=key_press_duration, blocking=blocking + ) + return resp + + # fmt: off + @overload + async def send_input(self, s: str) -> None: ... + @overload + async def send_input(self, s: str, *, blocking: Literal[True]) -> None: ... + @overload + async def send_input(self, s: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def send_input(self, s: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def send_input(self, s: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `SendInput `_ + """ + args = [s, '', ''] + resp = await self._transport.function_call('AHKSendInput', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def type(self, s: str) -> None: ... + @overload + async def type(self, s: str, *, blocking: Literal[True]) -> None: ... + @overload + async def type(self, s: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def type(self, s: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def type(self, s: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + """ + Like :py:meth:`send_input` but performs necessary escapes for you. + """ + resp = await self.send_input(type_escape(s), blocking=blocking) + return resp + + # fmt: off + @overload + async def send_play(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None) -> None: ... + @overload + async def send_play(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[True]) -> None: ... + @overload + async def send_play(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def send_play(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def send_play( + self, + s: str, + *, + key_delay: Optional[int] = None, + key_press_duration: Optional[int] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `SendPlay `_ + """ + args = [s] + if key_delay: + args.append(str(key_delay)) + else: + args.append('') + if key_press_duration: + args.append(str(key_press_duration)) + else: + args.append('') + + resp = await self._transport.function_call('AHKSendPlay', args=args, blocking=blocking) + return resp + + # fmt: off + @overload + async def set_capslock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + @overload + async def set_capslock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + @overload + async def set_capslock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def set_capslock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def set_capslock_state( + self, + state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, + *, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `SetCapsLockState `_ + """ + args: List[str] = [] + if state is not None: + if str(state).lower() not in ('1', '0', 'on', 'off', 'alwayson', 'alwaysoff'): + raise ValueError( + f'Invalid value for state. Must be one of On, Off, AlwaysOn, AlwaysOff or None. Got {state!r}' + ) + if state is True: + state = 'On' + elif state is False: + state = 'Off' + + args.append(str(state)) + else: + args.append('') + + resp = await self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def set_numlock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + @overload + async def set_numlock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + @overload + async def set_numlock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def set_numlock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def set_numlock_state( + self, + state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, + *, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `SetCapsLockState `_ + """ + args: List[str] = [] + if state is not None: + if str(state).lower() not in ('1', '0', 'on', 'off', 'alwayson', 'alwaysoff'): + raise ValueError( + f'Invalid value for state. Must be one of On, Off, AlwaysOn, AlwaysOff or None. Got {state!r}' + ) + if state is True: + state = 'On' + elif state is False: + state = 'Off' + + args.append(str(state)) + else: + args.append('') + + resp = await self._transport.function_call('AHKSetNumLockState', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def set_scroll_lock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + @overload + async def set_scroll_lock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + @overload + async def set_scroll_lock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def set_scroll_lock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def set_scroll_lock_state( + self, + state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, + *, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `SetCapsLockState `_ + """ + args: List[str] = [] + if state is not None: + if str(state).lower() not in ('1', '0', 'on', 'off', 'alwayson', 'alwaysoff'): + raise ValueError( + f'Invalid value for state. Must be one of On, Off, AlwaysOn, AlwaysOff or None. Got {state!r}' + ) + if state is True: + state = 'On' + elif state is False: + state = 'Off' + + args.append(str(state)) + else: + args.append('') + + resp = await self._transport.function_call('AHKSetScrollLockState', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def set_volume(self, value: int, device_number: int = 1) -> None: ... + @overload + async def set_volume(self, value: int, device_number: int = 1, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def set_volume(self, value: int, device_number: int = 1, *, blocking: Literal[True]) -> None: ... + @overload + async def set_volume(self, value: int, device_number: int = 1, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def set_volume( + self, value: int, device_number: int = 1, *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `SoundSetWaveVolume `_ + """ + args = [str(device_number), str(value)] + return await self._transport.function_call('AHKSetVolume', args, blocking=blocking) + + # fmt: off + + # in v2 the "second" parameter is not supported + @overload + async def show_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, second: None = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, second: None = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, second: None = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, second: None = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + + @overload + async def show_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def show_traytip( + self, + title: str, + text: str, + second: Optional[float] = None, + type_id: int = 1, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `TrayTip `_ + """ + if second is None: + second = 1.0 + else: + if self._version == 'v2': + warnings.warn( + 'supplying seconds is not supported when using AutoHotkey v2. This parameter will be ignored' + ) + + option = type_id + (16 if silent else 0) + (32 if large_icon else 0) + args = [title, text, str(second), str(option)] + return await self._transport.function_call('AHKTrayTip', args, blocking=blocking) + + # fmt: off + @overload + async def show_error_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_error_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_error_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_error_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + + @overload + async def show_error_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_error_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_error_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_error_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + + # fmt: on + async def show_error_traytip( + self: AsyncAHK[Any], + title: str, + text: str, + second: Optional[float] = None, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Convenience method for :py:meth:`show_traytip` for error-style messages + """ + return await self.show_traytip( + title=title, text=text, second=second, type_id=3, silent=silent, large_icon=large_icon, blocking=blocking + ) + + # fmt: off + @overload + async def show_info_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_info_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_info_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_info_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + + @overload + async def show_info_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_info_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_info_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_info_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def show_info_traytip( + self: AsyncAHK[Any], + title: str, + text: str, + second: Optional[float] = None, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Convenience method for :py:meth:`show_traytip` for info-style messages + """ + return await self.show_traytip( + title=title, text=text, second=second, type_id=1, silent=silent, large_icon=large_icon, blocking=blocking + ) + + # fmt: off + @overload + async def show_warning_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_warning_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_warning_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_warning_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + + @overload + async def show_warning_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_warning_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_warning_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_warning_traytip(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def show_warning_traytip( + self: AsyncAHK[Any], + title: str, + text: str, + second: Optional[float] = None, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Convenience method for :py:meth:`show_traytip` for warning-style messages + """ + return await self.show_traytip( + title=title, text=text, second=second, type_id=2, silent=silent, large_icon=large_icon, blocking=blocking + ) + + async def show_tooltip( + self, + text: str = '', + x: Optional[int] = None, + y: Optional[int] = None, + which: int = 1, + ) -> None: + """ + Analog for `ToolTip `_ + """ + if which not in range(1, 21): + raise ValueError('which must be an integer between 1 and 20') + args = [text] + if x is not None: + args.append(str(x)) + else: + args.append('') + if y is not None: + args.append(str(y)) + else: + args.append('') + args.append(str(which)) + await self._transport.function_call('AHKShowToolTip', args) + + async def hide_tooltip(self, which: int = 1) -> None: + await self.show_tooltip(which=which) + + async def menu_tray_tooltip(self, value: str) -> None: + """ + Change the menu tray icon tooltip that appears when hovering the mouse over the tray icon. + Does not affect tray icon for AHK processes started with :py:meth:`run_script` or ``blocking=False`` + + Uses the `Tip subcommand `_ + """ + + args = [value] + await self._transport.function_call('AHKMenuTrayTip', args) + return None + + async def menu_tray_icon(self, filename: str = '*', icon_number: int = 1, freeze: Optional[bool] = None) -> None: + """ + Change the tray icon menu. + Does not affect tray icon for AHK processes started with :py:meth:`run_script` or ``blocking=False`` + + Uses the `Icon subcommand `_ + + If called with no parameters, the tray icon will be reset to the original default. + """ + args = [filename, str(icon_number)] + if freeze is True: + args.append('1') + elif freeze is False: + args.append('0') + await self._transport.function_call('AHKMenuTrayIcon', args) + return None + + async def menu_tray_icon_show(self) -> None: + """ + Show ('unhide') the tray icon previously hidden by :py:class:`~ahk.directives.NoTrayIcon` directive. + Does not affect tray icon for AHK processes started with :py:meth:`run_script` or ``blocking=False`` + """ + await self._transport.function_call('AHKMenuTrayShow') + return None + + async def menu_tray_icon_hide(self) -> None: + """ + hides the tray icon. + Does not affect tray icon for AHK processes started with :py:meth:`run_script` or ``blocking=False`` + """ + await self._transport.function_call('AHKMenuTrayHide') + return None + + # fmt: off + @overload + async def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: ... + @overload + async def sound_beep(self, frequency: int = 523, duration: int = 150, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def sound_beep(self, frequency: int = 523, duration: int = 150, *, blocking: Literal[True]) -> None: ... + @overload + async def sound_beep(self, frequency: int = 523, duration: int = 150, *, blocking: bool = True) -> Optional[AsyncFutureResult[None]]: ... + # fmt: on + async def sound_beep( + self, frequency: int = 523, duration: int = 150, *, blocking: bool = True + ) -> Optional[AsyncFutureResult[None]]: + """ + Analog for `SoundBeep `_ + """ + args = [str(frequency), str(duration)] + await self._transport.function_call('AHKSoundBeep', args, blocking=blocking) + return None + + # fmt: off + @overload + async def sound_get(self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME') -> str: ... + @overload + async def sound_get(self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME', *, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def sound_get(self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME', *, blocking: Literal[True]) -> str: ... + @overload + async def sound_get(self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME', *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def sound_get( + self, + device_number: int = 1, + component_type: str = 'MASTER', + control_type: str = 'VOLUME', + *, + blocking: bool = True, + ) -> Union[str, AsyncFutureResult[str]]: + """ + Analog for `SoundGet `_ + """ + args = [str(device_number), component_type, control_type] + return await self._transport.function_call('AHKSoundGet', args, blocking=blocking) + + # fmt: off + @overload + async def sound_play(self, filename: str) -> None: ... + @overload + async def sound_play(self, filename: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def sound_play(self, filename: str, *, blocking: Literal[True]) -> None: ... + @overload + async def sound_play(self, filename: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def sound_play(self, filename: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `SoundPlay `_ + """ + return await self._transport.function_call('AHKSoundPlay', [filename], blocking=blocking) + + async def sound_set( + self, + value: Union[str, int, float], + device_number: int = 1, + component_type: str = 'MASTER', + control_type: str = 'VOLUME', + *, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `SoundSet `_ + """ + args = [str(device_number), component_type, control_type, str(value)] + return await self._transport.function_call('AHKSoundSet', args, blocking=blocking) + + # fmt: off + @overload + async def win_get(self: AsyncAHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> AsyncWindow: ... + @overload + async def win_get(self: AsyncAHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[AsyncWindow]: ... + @overload + async def win_get(self: AsyncAHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> AsyncWindow: ... + @overload + async def win_get(self: AsyncAHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... + + @overload + async def win_get(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[AsyncWindow, None]: ... + @overload + async def win_get(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... + @overload + async def win_get(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... + @overload + async def win_get(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[AsyncWindow, None, AsyncFutureResult[Union[None, AsyncWindow]], AsyncFutureResult[AsyncWindow]]: ... + # fmt: on + async def win_get( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[None, AsyncWindow]], AsyncFutureResult[AsyncWindow]]: + """ + Analog for `WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetID', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> str: ... + @overload + async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + @overload + async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def win_get_text( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[str, AsyncFutureResult[str]]: + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetText', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> str: ... + @overload + async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + @overload + async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def win_get_title( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[str, AsyncFutureResult[str]]: + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetTitle', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_get_class(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> str: ... + @overload + async def win_get_class(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def win_get_class(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + @overload + async def win_get_class(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def win_get_class( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[str, AsyncFutureResult[str]]: + """ + Analog for `WinGetClass `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetClass', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_get_position(self: AsyncAHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Position: ... + @overload + async def win_get_position(self: AsyncAHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Position]: ... + @overload + async def win_get_position(self: AsyncAHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Position: ... + @overload + async def win_get_position(self: AsyncAHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[Position, AsyncFutureResult[Position]]: ... + + @overload + async def win_get_position(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[Position, None]: ... + @overload + async def win_get_position(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[Position, None]]: ... + @overload + async def win_get_position(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[Position, None]: ... + @overload + async def win_get_position(self: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[Position, None, AsyncFutureResult[Union[Position, None]]]: ... + # fmt: on + async def win_get_position( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[Position, None, AsyncFutureResult[Union[Position, None]], AsyncFutureResult[Position]]: + """ + Analog for `WinGetPos `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetPos', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[AsyncWindow, None]: ... + @overload + async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... + @overload + async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... + @overload + async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[AsyncWindow, None, AsyncFutureResult[Union[AsyncWindow, None]]]: ... + # fmt: on + async def win_get_idlast( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[AsyncWindow, None]]]: + """ + Like the IDLast subcommand for WinGet + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetIDLast', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[int, None]: ... + @overload + async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[int, None]]: ... + @overload + async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... + @overload + async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[int, None, AsyncFutureResult[Union[int, None]]]: ... + # fmt: on + async def win_get_pid( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[int, None, AsyncFutureResult[Union[int, None]]]: + """ + Get a window by process ID. + + Like the pid subcommand for WinGet + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetPID', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[str, None]: ... + @overload + async def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[str, None]]: ... + @overload + async def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... + @overload + async def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, str, AsyncFutureResult[Optional[str]]]: ... + # fmt: on + async def win_get_process_name( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, str, AsyncFutureResult[Optional[str]]]: + """ + Get the process name of a window + + Analog for `ProcessName subcommand for WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetProcessName', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[str, None]: ... + @overload + async def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[str, None]]: ... + @overload + async def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... + @overload + async def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[str, None, Union[None, str, AsyncFutureResult[Optional[str]]]]: ... + # fmt: on + async def win_get_process_path( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[str, None, Union[None, str, AsyncFutureResult[Optional[str]]]]: + """ + Get the process path for a window. + + Analog for the `ProcessPath subcommand for WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetProcessPath', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> int: ... + @overload + async def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[int]: ... + @overload + async def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> int: ... + @overload + async def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[int, AsyncFutureResult[int]]: ... + # fmt: on + async def win_get_count( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[int, AsyncFutureResult[int]]: + """ + Analog for the `Count subcommand for WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetCount', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[int, None]: ... + @overload + async def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[int, None]]: ... + @overload + async def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... + @overload + async def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, int, AsyncFutureResult[Optional[int]]]: ... + # fmt: on + async def win_get_minmax( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, int, AsyncFutureResult[Optional[int]]]: + """ + Analog for the `MinMax subcommand for WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetMinMax', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[List[AsyncControl], None]: ... + @overload + async def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[List[AsyncControl], None]]: ... + @overload + async def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[List[AsyncControl], None]: ... + @overload + async def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[List[AsyncControl], None, AsyncFutureResult[Optional[List[AsyncControl]]]]: ... + # fmt: on + async def win_get_control_list( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[List[AsyncControl], None, AsyncFutureResult[Optional[List[AsyncControl]]]]: + """ + Analog for the `ControlList subcommand for WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinGetControlList', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + async def win_get_from_mouse_position(self) -> Union[AsyncWindow, None]: ... + @overload + async def win_get_from_mouse_position(self, *, blocking: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... + @overload + async def win_get_from_mouse_position(self, *, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... + @overload + async def win_get_from_mouse_position(self, *, blocking: bool = True) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: ... + # fmt: on + async def win_get_from_mouse_position( + self, *, blocking: bool = True + ) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: + resp = await self._transport.function_call('AHKWinFromMouse', blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> bool: ... + @overload + async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + @overload + async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + @overload + async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + # fmt: on + async def win_exists( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[bool, AsyncFutureResult[bool]]: + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinExist', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_activate(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_activate(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_activate(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_activate(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_activate( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinActivate `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinActivate', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_set_title( + self, + new_title: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinSetTitle `_ + """ + args = [new_title, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = await self._transport.function_call('AHKWinSetTitle', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_set_always_on_top( + self, + toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `AlwaysOnTop subcommand of WinSet `_ + """ + args = [str(toggle), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = await self._transport.function_call('AHKWinSetAlwaysOnTop', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_set_bottom( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `Bottom subcommand of WinSet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinSetBottom', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_set_top( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `Top subcommand of WinSet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinSetTop', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_set_disable( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `Disable subcommand of WinSet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinSetDisable', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_set_enable( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `Enable subcommand of WinSet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinSetEnable', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_set_redraw( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `Redraw subcommand of WinSet `_ + """ + + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinSetRedraw', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> bool: ... + @overload + async def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + @overload + async def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + @overload + async def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + # fmt: on + async def win_set_style( + self, + style: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[bool, AsyncFutureResult[bool]]: + """ + Analog for `Style subcommand of WinSet `_ + """ + + args = [style, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = await self._transport.function_call('AHKWinSetStyle', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> bool: ... + @overload + async def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + @overload + async def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + @overload + async def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + # fmt: on + async def win_set_ex_style( + self, + style: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[bool, AsyncFutureResult[bool]]: + """ + Analog for `ExStyle subcommand of WinSet `_ + """ + args = [style, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = await self._transport.function_call('AHKWinSetExStyle', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> bool: ... + @overload + async def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + @overload + async def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + @overload + async def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + # fmt: on + async def win_set_region( + self, + options: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[bool, AsyncFutureResult[bool]]: + """ + Analog for `Region subcommand of WinSet `_ + """ + args = [options, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = await self._transport.function_call('AHKWinSetRegion', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_set_transparent( + self, + transparency: Union[int, Literal['Off']], + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `Transparent subcommand of WinSet `_ + """ + args = [str(transparency), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = await self._transport.function_call('AHKWinSetTransparent', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_set_trans_color( + self, + color: Union[int, str], + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `TransColor subcommand of WinSet `_ + """ + args = [str(color), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = await self._transport.function_call('AHKWinSetTransColor', args, blocking=blocking) + return resp + + # alias for backwards compatibility + windows = list_windows + + # fmt: off + @overload + async def right_click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + async def right_click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: Literal[True], coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + async def right_click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: Literal[False], coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> AsyncFutureResult[None]: ... + @overload + async def right_click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def right_click( + self, + x: Optional[Union[int, Tuple[int, int]]] = None, + y: Optional[int] = None, + click_count: Optional[int] = None, + direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, + *, + relative: Optional[bool] = None, + blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, + send_mode: Optional[SendMode] = None, + ) -> Union[None, AsyncFutureResult[None]]: + button = 'R' + return await self.click( + x, + y, + button=button, + click_count=click_count, + direction=direction, + relative=relative, + blocking=blocking, + coord_mode=coord_mode, + send_mode=send_mode, + ) + + # fmt: off + @overload + async def click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + async def click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: Literal[True], coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + async def click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: Literal[False], coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> AsyncFutureResult[None]: ... + @overload + async def click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def click( + self, + x: Optional[Union[int, Tuple[int, int]]] = None, + y: Optional[int] = None, + button: Optional[Union[MouseButton, str]] = None, + click_count: Optional[int] = None, + direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, + *, + relative: Optional[bool] = None, + blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, + send_mode: Optional[SendMode] = None, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `Click `_ + """ + if x or y: + if y is None and isinstance(x, tuple) and len(x) == 2: + # allow position to be specified by a two-sequence tuple + x, y = x + assert x is not None and y is not None, 'If provided, position must be specified by x AND y' + if button is None: + button = 'L' + button = _resolve_button(button) + + if relative: + r = 'Rel' + else: + r = '' + if coord_mode is None: + coord_mode = '' + if send_mode is None: + send_mode = '' + args = [str(x), str(y), button, str(click_count), direction or '', r, coord_mode, str(send_mode)] + resp = await self._transport.function_call('AHKClick', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def image_search(self, image_path: str, upper_bound: Tuple[Union[int, str], Union[int, str]] = (0, 0), lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, *, color_variation: Optional[int] = None, coord_mode: Optional[CoordModeRelativeTo] = None, scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None) -> Optional[Coordinates]: ... + @overload + async def image_search(self, image_path: str, upper_bound: Tuple[Union[int, str], Union[int, str]] = (0, 0), lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, *, color_variation: Optional[int] = None, coord_mode: Optional[CoordModeRelativeTo] = None, scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[Optional[Coordinates]]: ... + @overload + async def image_search(self, image_path: str, upper_bound: Tuple[Union[int, str], Union[int, str]] = (0, 0), lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, *, color_variation: Optional[int] = None, coord_mode: Optional[CoordModeRelativeTo] = None, scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[True]) -> Optional[Coordinates]: ... + @overload + async def image_search(self, image_path: str, upper_bound: Tuple[Union[int, str], Union[int, str]] = (0, 0), lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, *, color_variation: Optional[int] = None, coord_mode: Optional[CoordModeRelativeTo] = None, scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: bool = True) -> Union[Coordinates, None, AsyncFutureResult[Optional[Coordinates]]]: ... + # fmt: on + async def image_search( + self, + image_path: str, + upper_bound: Tuple[Union[int, str], Union[int, str]] = (0, 0), + lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, + *, + color_variation: Optional[int] = None, + coord_mode: Optional[CoordModeRelativeTo] = None, + scale_height: Optional[int] = None, + scale_width: Optional[int] = None, + transparent: Optional[str] = None, + icon: Optional[int] = None, + blocking: bool = True, + ) -> Union[Coordinates, None, AsyncFutureResult[Optional[Coordinates]]]: + """ + Analog for `ImageSearch `_ + """ + + if scale_height and not scale_width: + scale_width = -1 + elif scale_width and not scale_height: + scale_height = -1 + + options: List[Union[str, int]] = [] + if icon: + options.append(f'Icon{icon}') + if color_variation is not None: + options.append(color_variation) + if transparent is not None: + options.append(f'Trans{transparent}') + if scale_width: + options.append(f'w{scale_width}') + options.append(f'h{scale_height}') + + x1, y1 = upper_bound + if lower_bound: + x2, y2 = lower_bound + else: + x2, y2 = ('A_ScreenWidth', 'A_ScreenHeight') + + args = [str(x1), str(y1), str(x2), str(y2)] + if options: + opts = ' '.join(f'*{opt}' for opt in options) + args.append(opts + f' {image_path}') + else: + args.append(image_path) + + if coord_mode is not None: + args.append(coord_mode) + else: + args.append('') + + resp = await self._transport.function_call('AHKImageSearch', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def mouse_drag(self, x: int, y: int, *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, relative: Optional[bool] = None, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + async def mouse_drag(self, x: int, y: int, *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, relative: Optional[bool] = None, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def mouse_drag(self, x: int, y: int, *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, relative: Optional[bool] = None, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None, blocking: Literal[True]) -> None: ... + @overload + async def mouse_drag(self, x: int, y: int, *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def mouse_drag( + self, + x: int, + y: int, + *, + from_position: Optional[Tuple[int, int]] = None, + speed: Optional[int] = None, + button: Optional[Union[MouseButton, str]] = None, + relative: Optional[bool] = None, + blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, + send_mode: Optional[SendMode] = None, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `MouseClickDrag `_ + """ + if button is None: + button = 'Left' + else: + button = _resolve_button(button) + if from_position: + x1, y1 = from_position + args = [str(button), str(x1), str(y1), str(x), str(y)] + else: + args = [str(button), '', '', str(x), str(y)] + + if speed: + args.append(str(speed)) + else: + args.append('') + + if relative: + args.append('R') + else: + args.append('') + + if coord_mode: + args.append(coord_mode) + else: + args.append('') + + if send_mode: + args.append(send_mode) + else: + args.append('') + + resp = await self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def pixel_get_color(self, x: int, y: int, *, coord_mode: Optional[CoordModeRelativeTo] = None, alt: bool = False, slow: bool = False, rgb: bool = True) -> str: ... + @overload + async def pixel_get_color(self, x: int, y: int, *, coord_mode: Optional[CoordModeRelativeTo] = None, alt: bool = False, slow: bool = False, rgb: bool = True, blocking: Literal[True]) -> str: ... + @overload + async def pixel_get_color(self, x: int, y: int, *, coord_mode: Optional[CoordModeRelativeTo] = None, alt: bool = False, slow: bool = False, rgb: bool = True, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def pixel_get_color(self, x: int, y: int, *, coord_mode: Optional[CoordModeRelativeTo] = None, alt: bool = False, slow: bool = False, rgb: bool = True, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def pixel_get_color( + self, + x: int, + y: int, + *, + coord_mode: Optional[CoordModeRelativeTo] = None, + alt: bool = False, + slow: bool = False, + rgb: bool = True, + blocking: bool = True, + ) -> Union[str, AsyncFutureResult[str]]: + """ + Analog for `PixelGetColor `_ + """ + args = [str(x), str(y), coord_mode or ''] + + options = ' '.join(word for word, val in zip(('Alt', 'Slow', 'RGB'), (alt, slow, rgb)) if val) + args.append(options) + + resp = await self._transport.function_call('AHKPixelGetColor', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def pixel_search(self, search_region_start: Tuple[int, int], search_region_end: Tuple[int, int], color: Union[str, int], variation: int = 0, *, coord_mode: Optional[CoordModeRelativeTo] = None, fast: bool = True, rgb: bool = True) -> Optional[Coordinates]: ... + @overload + async def pixel_search(self, search_region_start: Tuple[int, int], search_region_end: Tuple[int, int], color: Union[str, int], variation: int = 0, *, coord_mode: Optional[CoordModeRelativeTo] = None, fast: bool = True, rgb: bool = True, blocking: Literal[True]) -> Optional[Coordinates]: ... + @overload + async def pixel_search(self, search_region_start: Tuple[int, int], search_region_end: Tuple[int, int], color: Union[str, int], variation: int = 0, *, coord_mode: Optional[CoordModeRelativeTo] = None, fast: bool = True, rgb: bool = True, blocking: Literal[False]) -> AsyncFutureResult[Optional[Coordinates]]: ... + @overload + async def pixel_search(self, search_region_start: Tuple[int, int], search_region_end: Tuple[int, int], color: Union[str, int], variation: int = 0, *, coord_mode: Optional[CoordModeRelativeTo] = None, fast: bool = True, rgb: bool = True, blocking: bool = True) -> Union[Optional[Coordinates], AsyncFutureResult[Optional[Coordinates]]]: ... + # fmt: on + async def pixel_search( + self, + search_region_start: Tuple[int, int], + search_region_end: Tuple[int, int], + color: Union[str, int], + variation: int = 0, + *, + coord_mode: Optional[CoordModeRelativeTo] = None, + fast: bool = True, + rgb: bool = True, + blocking: bool = True, + ) -> Union[Optional[Coordinates], AsyncFutureResult[Optional[Coordinates]]]: + """ + Analog for `PixelSearch `_ + """ + x1, y1 = search_region_start + x2, y2 = search_region_end + args = [str(x1), str(y1), str(x2), str(y2), str(color), str(variation)] + mode = ' '.join(word for word, val in zip(('Fast', 'RGB'), (fast, rgb)) if val) + args.append(mode) + args.append(coord_mode or '') + resp = await self._transport.function_call('AHKPixelSearch', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_close(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_close(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_close(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_close(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_close( + self, + title: str = '', + text: str = '', + seconds_to_wait: Optional[int] = None, + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinClose `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(seconds_to_wait) if seconds_to_wait else '') + + resp = await self._transport.function_call('AHKWinClose', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_kill(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_kill(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def win_kill(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_kill(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_kill( + self, + title: str = '', + text: str = '', + seconds_to_wait: Optional[int] = None, + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinKill `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(seconds_to_wait) if seconds_to_wait else '') + + resp = await self._transport.function_call('AHKWinKill', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_minimize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_minimize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def win_minimize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_minimize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_minimize( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinMinimize `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinMinimize', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_maximize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_maximize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def win_maximize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_maximize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_maximize( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinMaximize `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinMaximize', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_restore(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_restore(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def win_restore(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_restore(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_restore( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinRestore `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinRestore', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_wait(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None) -> AsyncWindow: ... + @overload + async def win_wait(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[AsyncWindow]: ... + @overload + async def win_wait(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[True]) -> AsyncWindow: ... + @overload + async def win_wait(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: bool = True) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... + # fmt: on + async def win_wait( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + timeout: Optional[int] = None, + blocking: bool = True, + ) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: + """ + Analog for `WinWait `_ + """ + if not title and not text and not exclude_title and not exclude_text: + raise ValueError( + 'Expected non-blank value for at least one of the following: title, text, exclude_title, exclude_text' + ) + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(timeout) if timeout else '') + resp = await self._transport.function_call('AHKWinWait', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + async def win_wait_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None) -> AsyncWindow: ... + @overload + async def win_wait_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[AsyncWindow]: ... + @overload + async def win_wait_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[True]) -> AsyncWindow: ... + @overload + async def win_wait_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: bool = True) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... + # fmt: on + async def win_wait_active( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + timeout: Optional[int] = None, + blocking: bool = True, + ) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: + """ + Analog for `WinWaitActive `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(timeout) if timeout else '') + resp = await self._transport.function_call('AHKWinWaitActive', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + async def win_wait_not_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None) -> AsyncWindow: ... + @overload + async def win_wait_not_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[AsyncWindow]: ... + @overload + async def win_wait_not_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[True]) -> AsyncWindow: ... + @overload + async def win_wait_not_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: bool = True) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... + # fmt: on + async def win_wait_not_active( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + timeout: Optional[int] = None, + blocking: bool = True, + ) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: + """ + Analog for `WinWaitNotActive `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(timeout) if timeout else '') + resp = await self._transport.function_call('AHKWinWaitNotActive', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + async def win_wait_close(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None) -> None: ... + @overload + async def win_wait_close(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_wait_close(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_wait_close(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_wait_close( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + timeout: Optional[int] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinWaitClose `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(timeout) if timeout else '') + resp = await self._transport.function_call('AHKWinWaitClose', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + async def win_show(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_show(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_show(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_show(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_show( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinShow `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinShow', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_hide(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_hide(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_hide(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_hide(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_hide( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinHide `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinHide', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_is_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> bool: ... + @overload + async def win_is_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + @overload + async def win_is_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + @overload + async def win_is_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + # fmt: on + async def win_is_active( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[bool, AsyncFutureResult[bool]]: + """ + Check if a window is active. + + Uses `WinActive `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = await self._transport.function_call('AHKWinIsActive', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def win_move(self, x: int, y: int, *, width: Optional[int] = None, height: Optional[int] = None, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + async def win_move(self, x: int, y: int, *, width: Optional[int] = None, height: Optional[int] = None, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def win_move(self, x: int, y: int, *, width: Optional[int] = None, height: Optional[int] = None, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_move(self, x: int, y: int, *, width: Optional[int] = None, height: Optional[int] = None, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def win_move( + self, + x: int, + y: int, + *, + width: Optional[int] = None, + height: Optional[int] = None, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinMove `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(x)) + args.append(str(y)) + args.append(str(width) if width is not None else '') + args.append(str(height) if height is not None else '') + resp = await self._transport.function_call('AHKWinMove', args, blocking=blocking) + return resp + + # fmt: off + @overload + async def get_clipboard(self) -> str: ... + @overload + async def get_clipboard(self, *, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def get_clipboard(self, *, blocking: Literal[True]) -> str: ... + @overload + async def get_clipboard(self, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def get_clipboard(self, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: + """ + Get the string contents of the clipboard + """ + return await self._transport.function_call('AHKGetClipboard', blocking=blocking) + + async def set_clipboard(self, s: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + """ + Set the contents of the clipboard + """ + args = [s] + return await self._transport.function_call('AHKSetClipboard', args, blocking=blocking) + + async def get_clipboard_all(self, *, blocking: bool = True) -> Union[bytes, AsyncFutureResult[bytes]]: + """ + Get the full binary contents of the keyboard. The return value is intended to be used with :py:meth:`set_clipboard_all` + """ + return await self._transport.function_call('AHKGetClipboardAll', blocking=blocking) + + # fmt: off + @overload + async def set_clipboard_all(self, contents: bytes) -> None: ... + @overload + async def set_clipboard_all(self, contents: bytes, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def set_clipboard_all(self, contents: bytes, *, blocking: Literal[True]) -> None: ... + @overload + async def set_clipboard_all(self, contents: bytes, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def set_clipboard_all( + self, contents: bytes, *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + """ + Set the full binary contents of the clipboard. Expects bytes object as returned by :py:meth:`get_clipboard_all` + """ + # TODO: figure out how to do this without a tempfile + if not isinstance(contents, bytes): + raise ValueError('Malformed data. Can only set bytes as returned by get_clipboard_all') + if not contents: + raise ValueError('bytes must be nonempty. If you want to clear the clipboard, use `set_clipboard`') + with tempfile.NamedTemporaryFile(prefix='ahk-python', suffix='.clip', mode='wb', delete=False) as f: + f.write(contents) + + args = [f'*c {f.name}' if self._transport._version != 'v2' else f.name] + try: + resp = await self._transport.function_call('AHKSetClipboardAll', args, blocking=blocking) + return resp + finally: + try: + os.remove(f.name) + except Exception: + pass + + def on_clipboard_change( + self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None + ) -> None: + """ + call a function in response to clipboard change. + Uses `OnClipboardChange() `_ + """ + self._transport.on_clipboard_change(callback, ex_handler) + + # fmt: off + @overload + async def clip_wait(self, timeout: Optional[float] = None, wait_for_any_data: bool = False) -> None: ... + @overload + async def clip_wait(self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def clip_wait(self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: Literal[True]) -> None: ... + @overload + async def clip_wait(self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def clip_wait( + self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + """ + Wait until the clipboard contents change + + Analog for `ClipWait `_ + """ + args = [str(timeout) if timeout else ''] + if wait_for_any_data: + args.append('1') + else: + args.append('0') + return await self._transport.function_call('AHKClipWait', args, blocking=blocking) + + async def block_input( + self, + value: Literal['On', 'Off', 'Default', 'Send', 'Mouse', 'MouseMove', 'MouseMoveOff', 'SendAndMouse'], + /, # flake8: noqa + ) -> None: + """ + Analog for `BlockInput `_ + """ + await self._transport.function_call('AHKBlockInput', args=[value]) + + # fmt: off + @overload + async def reg_delete(self, key_name: str, value_name: Optional[str] = None) -> None: ... + @overload + async def reg_delete(self, key_name: str, value_name: Optional[str] = None, *, blocking: Literal[False]) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def reg_delete(self, key_name: str, value_name: Optional[str] = None, *, blocking: Literal[True]) -> None: ... + @overload + async def reg_delete(self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def reg_delete( + self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `RegDelete `_ + """ + args = [key_name, value_name if value_name is not None else ''] + return await self._transport.function_call('AHKRegDelete', args, blocking=blocking) + + # fmt: off + @overload + async def reg_write(self, value_type: Literal['REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ', 'REG_DWORD', 'REG_BINARY'], key_name: str, value_name: Optional[str] = None, value: Optional[str] = None) -> None: ... + @overload + async def reg_write(self, value_type: Literal['REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ', 'REG_DWORD', 'REG_BINARY'], key_name: str, value_name: Optional[str] = None, value: Optional[str] = None, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def reg_write(self, value_type: Literal['REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ', 'REG_DWORD', 'REG_BINARY'], key_name: str, value_name: Optional[str] = None, value: Optional[str] = None, *, blocking: Literal[True]) -> None: ... + @overload + async def reg_write(self, value_type: Literal['REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ', 'REG_DWORD', 'REG_BINARY'], key_name: str, value_name: Optional[str] = None, value: Optional[str] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def reg_write( + self, + value_type: Literal['REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ', 'REG_DWORD', 'REG_BINARY'], + key_name: str, + value_name: Optional[str] = None, + value: Optional[str] = None, + *, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `RegWrite `_ + """ + args = [value_type, key_name] + if value_name is not None: + args.append(value_name) + else: + args.append('') + if value is not None: + args.append(value) + else: + args.append('') + return await self._transport.function_call('AHKRegWrite', args, blocking=blocking) + + # fmt: off + @overload + async def reg_read(self, key_name: str, value_name: Optional[str] = None) -> str: ... + @overload + async def reg_read(self, key_name: str, value_name: Optional[str] = None, *, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def reg_read(self, key_name: str, value_name: Optional[str] = None, *, blocking: Literal[True]) -> str: ... + @overload + async def reg_read(self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def reg_read( + self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True + ) -> Union[str, AsyncFutureResult[str]]: + """ + Analog for `RegRead `_ + """ + args = [key_name] + if value_name is not None: + args.append(value_name) + else: + args.append('') + return await self._transport.function_call('AHKRegRead', args, blocking=blocking) + + # fmt: off + @overload + async def msg_box(self, text: str = '', title: str = 'Message', buttons: MsgBoxButtons = MsgBoxButtons.OK, icon: Optional[MsgBoxIcon] = None, default_button: Optional[MsgBoxDefaultButton] = None, modality: Optional[MsgBoxModality] = None, help_button: bool = False, text_right_justified: bool = False, right_to_left_reading: bool = False, timeout: Optional[int] = None) -> str: ... + @overload + async def msg_box(self, text: str = '', title: str = 'Message', buttons: MsgBoxButtons = MsgBoxButtons.OK, icon: Optional[MsgBoxIcon] = None, default_button: Optional[MsgBoxDefaultButton] = None, modality: Optional[MsgBoxModality] = None, help_button: bool = False, text_right_justified: bool = False, right_to_left_reading: bool = False, timeout: Optional[int] = None, *, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def msg_box(self, text: str = '', title: str = 'Message', buttons: MsgBoxButtons = MsgBoxButtons.OK, icon: Optional[MsgBoxIcon] = None, default_button: Optional[MsgBoxDefaultButton] = None, modality: Optional[MsgBoxModality] = None, help_button: bool = False, text_right_justified: bool = False, right_to_left_reading: bool = False, timeout: Optional[int] = None, *, blocking: Literal[True]) -> str: ... + @overload + async def msg_box(self, text: str = '', title: str = 'Message', buttons: MsgBoxButtons = MsgBoxButtons.OK, icon: Optional[MsgBoxIcon] = None, default_button: Optional[MsgBoxDefaultButton] = None, modality: Optional[MsgBoxModality] = None, help_button: bool = False, text_right_justified: bool = False, right_to_left_reading: bool = False, timeout: Optional[int] = None, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def msg_box( + self, + text: str = '', + title: str = 'Message', + buttons: MsgBoxButtons = MsgBoxButtons.OK, + icon: Optional[MsgBoxIcon] = None, + default_button: Optional[MsgBoxDefaultButton] = None, + modality: Optional[MsgBoxModality] = None, + help_button: bool = False, + text_right_justified: bool = False, + right_to_left_reading: bool = False, + timeout: Optional[int] = None, + *, + blocking: bool = True, + ) -> Union[str, AsyncFutureResult[str]]: + options: int = int(buttons) + for opt in (icon, default_button, modality): + if opt is not None: + options += opt + if help_button: + options += MsgBoxOtherOptions.HELP_BUTTON + if text_right_justified: + options += MsgBoxOtherOptions.TEXT_RIGHT_JUSTIFIED + if right_to_left_reading: + options += MsgBoxOtherOptions.RIGHT_TO_LEFT_READING_ORDER + + args = [str(options), title, text] + if timeout is not None: + args.append(str(timeout)) + else: + args.append('') + + return await self._transport.function_call('AHKMsgBox', args, blocking=blocking) + + # fmt: off + @overload + async def input_box(self, prompt: str = '', title: str = 'Input', default: str = '', hide: bool = False, width: Optional[int] = None, height: Optional[int] = None, x: Optional[int] = None, y: Optional[int] = None, locale: bool = True, timeout: Optional[int] = None) -> Union[None, str]: ... + @overload + async def input_box(self, prompt: str = '', title: str = 'Input', default: str = '', hide: bool = False, width: Optional[int] = None, height: Optional[int] = None, x: Optional[int] = None, y: Optional[int] = None, locale: bool = True, timeout: Optional[int] = None, *, blocking: Literal[False]) -> Union[AsyncFutureResult[str], AsyncFutureResult[None]]: ... + @overload + async def input_box(self, prompt: str = '', title: str = 'Input', default: str = '', hide: bool = False, width: Optional[int] = None, height: Optional[int] = None, x: Optional[int] = None, y: Optional[int] = None, locale: bool = True, timeout: Optional[int] = None, *, blocking: Literal[True]) -> Union[str, None]: ... + @overload + async def input_box(self, prompt: str = '', title: str = 'Input', default: str = '', hide: bool = False, width: Optional[int] = None, height: Optional[int] = None, x: Optional[int] = None, y: Optional[int] = None, locale: bool = True, timeout: Optional[int] = None, *, blocking: bool = True) -> Union[str, None, AsyncFutureResult[str], AsyncFutureResult[None]]: ... + # fmt: on + async def input_box( + self, + prompt: str = '', + title: str = 'Input', + default: str = '', + hide: bool = False, + width: Optional[int] = None, + height: Optional[int] = None, + x: Optional[int] = None, + y: Optional[int] = None, + locale: bool = True, + timeout: Optional[int] = None, + *, + blocking: bool = True, + ) -> Union[None, str, AsyncFutureResult[str], AsyncFutureResult[None]]: + """ + Like AHK's ``InputBox`` + + If the user presses Cancel or closes the box, ``None`` is returned. + Otherwise, the user's input is returned. + Raises a ``TimeoutError`` if a timeout is specified and expires. + """ + args = [title, prompt] + if hide: + args.append('hide') + else: + args.append('') + for opt in (width, height, x, y): + if opt is not None: + args.append(str(opt)) + else: + args.append('') + if locale: + args.append('Locale') + else: + args.append('') + if timeout is not None: + args.append(str(timeout)) + else: + args.append('') + args.append(default) + return await self._transport.function_call('AHKInputBox', args, blocking=blocking) + + # fmt: off + @overload + async def file_select_box(self, title: str = 'Select File', multi: bool = False, root: str = '', filter: str = '', save_button: bool = False, file_must_exist: bool = False, path_must_exist: bool = False, prompt_create_new_file: bool = False, prompt_override_file: bool = False, follow_shortcuts: bool = True) -> Union[None, str]: ... + @overload + async def file_select_box(self, title: str = 'Select File', multi: bool = False, root: str = '', filter: str = '', save_button: bool = False, file_must_exist: bool = False, path_must_exist: bool = False, prompt_create_new_file: bool = False, prompt_override_file: bool = False, follow_shortcuts: bool = True, *, blocking: Literal[False]) -> Union[AsyncFutureResult[str], AsyncFutureResult[None]]: ... + @overload + async def file_select_box(self, title: str = 'Select File', multi: bool = False, root: str = '', filter: str = '', save_button: bool = False, file_must_exist: bool = False, path_must_exist: bool = False, prompt_create_new_file: bool = False, prompt_override_file: bool = False, follow_shortcuts: bool = True, *, blocking: Literal[True]) -> Union[str, None]: ... + @overload + async def file_select_box(self, title: str = 'Select File', multi: bool = False, root: str = '', filter: str = '', save_button: bool = False, file_must_exist: bool = False, path_must_exist: bool = False, prompt_create_new_file: bool = False, prompt_override_file: bool = False, follow_shortcuts: bool = True, *, blocking: bool = True) -> Union[str, None, AsyncFutureResult[str], AsyncFutureResult[None]]: ... + # fmt: on + async def file_select_box( + self, + title: str = 'Select File', + multi: bool = False, + root: str = '', + filter: str = '', + save_button: bool = False, + file_must_exist: bool = False, + path_must_exist: bool = False, + prompt_create_new_file: bool = False, + prompt_override_file: bool = False, + follow_shortcuts: bool = True, + *, + blocking: bool = True, + ) -> Union[str, None, AsyncFutureResult[str], AsyncFutureResult[None]]: + opts = 0 + if file_must_exist: + opts += 1 + if path_must_exist: + opts += 2 + if prompt_create_new_file: + opts += 8 + if prompt_override_file: + opts += 8 + if not follow_shortcuts: + opts += 32 + options = '' + if multi: + options += 'M' + if save_button: + options += 'S' + if opts: + options += str(opts) + args = [options, root, title, filter] + return await self._transport.function_call('AHKFileSelectFile', args, blocking=blocking) + + # fmt: off + @overload + async def folder_select_box(self, prompt: str = 'Select Folder', root: str = '', chroot: bool = False, enable_new_directories: bool = True, edit_field: bool = False, new_dialog_style: bool = False) -> Union[None, str]: ... + @overload + async def folder_select_box(self, prompt: str = 'Select Folder', root: str = '', chroot: bool = False, enable_new_directories: bool = True, edit_field: bool = False, new_dialog_style: bool = False, *, blocking: Literal[False]) -> Union[AsyncFutureResult[str], AsyncFutureResult[None]]: ... + @overload + async def folder_select_box(self, prompt: str = 'Select Folder', root: str = '', chroot: bool = False, enable_new_directories: bool = True, edit_field: bool = False, new_dialog_style: bool = False, *, blocking: Literal[True]) -> Union[str, None]: ... + @overload + async def folder_select_box(self, prompt: str = 'Select Folder', root: str = '', chroot: bool = False, enable_new_directories: bool = True, edit_field: bool = False, new_dialog_style: bool = False, *, blocking: bool = True) -> Union[str, None, AsyncFutureResult[str], AsyncFutureResult[None]]: ... + # fmt: on + async def folder_select_box( + self, + prompt: str = 'Select Folder', + root: str = '', + chroot: bool = False, + enable_new_directories: bool = True, + edit_field: bool = False, + new_dialog_style: bool = False, + *, + blocking: bool = True, + ) -> Union[str, None, AsyncFutureResult[str], AsyncFutureResult[None]]: + if not chroot: + starting_folder = '*' + else: + starting_folder = '' + starting_folder += root + if enable_new_directories: + opts = 1 + else: + opts = 0 + if edit_field: + opts += 2 + if new_dialog_style: + opts += 4 + args = [starting_folder, str(opts), prompt] + return await self._transport.function_call('AHKFileSelectFolder', args, blocking=blocking) + + async def block_forever(self) -> NoReturn: + """ + Blocks (sleeps) forever. Utility method to prevent script from exiting. + """ + while True: + await async_sleep(1) + + async def get_version(self) -> str: + return await self._transport._get_full_version() + + async def get_major_version(self) -> Literal['v1', 'v2']: + return await self._transport._get_major_version() diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py new file mode 100644 index 00000000..06fdf8cd --- /dev/null +++ b/ahk/_async/transport.py @@ -0,0 +1,871 @@ +from __future__ import annotations + +import asyncio.subprocess +import atexit +import os +import re +import subprocess +import sys +import tempfile +import threading +import warnings +from abc import ABC +from abc import abstractmethod +from concurrent.futures import Future +from concurrent.futures import ThreadPoolExecutor +from io import BytesIO +from typing import Any +from typing import Callable +from typing import Generic +from typing import List +from typing import Literal +from typing import Optional +from typing import overload +from typing import Protocol +from typing import runtime_checkable +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +import jinja2 + +from ahk._constants import DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE +from ahk._constants import DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE +from ahk._hotkey import Hotkey +from ahk._hotkey import Hotstring +from ahk._hotkey import ThreadedHotkeyTransport +from ahk._types import Coordinates +from ahk._types import FunctionName +from ahk._types import Position +from ahk._utils import _version_detection_script +from ahk._utils import try_remove +from ahk.directives import Directive +from ahk.exceptions import AHKProtocolError +from ahk.extensions import _resolve_includes +from ahk.extensions import Extension +from ahk.message import _message_registry +from ahk.message import RequestMessage +from ahk.message import ResponseMessage + +if TYPE_CHECKING: + from ahk import AsyncControl + from ahk import AsyncWindow + +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias, TypeGuard +else: + from typing import TypeAlias, TypeGuard + +if sys.version_info < (3, 11): + from typing_extensions import Self +else: + from typing import Self + +T_AsyncFuture = TypeVar('T_AsyncFuture') # unasync: remove +T_SyncFuture = TypeVar('T_SyncFuture') + + +class AsyncFutureResult(Generic[T_AsyncFuture]): # unasync: remove + def __init__(self, task: asyncio.Task[T_AsyncFuture]): + self._task: asyncio.Task[T_AsyncFuture] = task + + async def result(self) -> T_AsyncFuture: + return await self._task + + +class FutureResult(Generic[T_SyncFuture]): + def __init__(self, future: Future[T_SyncFuture]): + self._fut: Future[T_SyncFuture] = future + + def result(self, timeout: Optional[float] = None) -> T_SyncFuture: + return self._fut.result(timeout=timeout) + + +AsyncIOProcess: TypeAlias = asyncio.subprocess.Process # unasync: remove + +SyncIOProcess: TypeAlias = 'subprocess.Popen[bytes]' + + +@runtime_checkable +class Killable(Protocol): + def kill(self) -> None: ... + + +def kill(proc: Killable) -> None: + try: + proc.kill() + except: # noqa + pass + + +def async_assert_send_nonblocking_type_correct( + obj: Any, +) -> TypeGuard[ + Future[ + Union[None, Coordinates, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] + ] +]: + return True + + +class Communicable(Protocol): + runargs: List[str] + + async def start(self, atexit_cleanup: bool = True) -> None: ... + def astart(self, *args: Any, **kwargs: Any) -> None: ... # unasync: remove + + def communicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: ... + + async def acommunicate( # unasync: remove + self, input_bytes: Optional[bytes], timeout: Optional[int] = None + ) -> Tuple[bytes, bytes]: ... + + @property + def returncode(self) -> Optional[int]: ... + + def kill(self) -> None: ... + + +class AsyncAHKProcess: + def __init__(self, runargs: List[str]): + self.runargs = runargs + self._proc: Optional[AsyncIOProcess] = None + + @property + def returncode(self) -> Optional[int]: + assert self._proc is not None + return self._proc.returncode + + def astart(self, *args: Any, **kwargs: Any) -> None: ... # unasync: remove + + async def start(self, atexit_cleanup: bool = True) -> None: + self._proc = await async_create_process(self.runargs) + if atexit_cleanup: + atexit.register(kill, self._proc) + return None + + async def adrain_stdin(self) -> None: # unasync: remove + assert self._proc is not None + assert self._proc.stdin is not None + await self._proc.stdin.drain() + return None + + def drain_stdin(self) -> None: + assert isinstance(self._proc, subprocess.Popen) + assert self._proc.stdin is not None + self._proc.stdin.flush() + return None + + def write(self, content: bytes) -> None: + assert self._proc is not None + assert self._proc.stdin is not None + self._proc.stdin.write(content) + + async def readline(self) -> bytes: + assert self._proc is not None + assert self._proc.stdout is not None + line = await self._proc.stdout.readline() + assert isinstance(line, bytes) + return line + + async def read(self) -> bytes: + assert self._proc is not None + assert self._proc.stdout is not None + b = await self._proc.stdout.read() + assert isinstance(b, bytes) + return b + + def kill(self) -> None: + assert self._proc is not None, 'no process to kill' + self._proc.kill() + + async def acommunicate( # unasync: remove + self, input_bytes: Optional[bytes] = None, timeout: Optional[int] = None + ) -> Tuple[bytes, bytes]: + assert self._proc is not None + if timeout is not None: # unasync: remove + raise RuntimeError('timeout not supported in async api') + return await self._proc.communicate(input=input_bytes) + + def communicate(self, input_bytes: Optional[bytes] = None, timeout: Optional[int] = None) -> Tuple[bytes, bytes]: + assert self._proc is not None + assert isinstance(self._proc, subprocess.Popen) + return self._proc.communicate(input=input_bytes, timeout=timeout) + + async def __aenter__(self) -> Self: + await self.start(atexit_cleanup=False) + return self + + async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> Literal[False]: + try: + self.kill() + except Exception: + pass + return False + + +async def async_create_process(runargs: List[str]) -> asyncio.subprocess.Process: # unasync: remove + return await asyncio.subprocess.create_subprocess_exec( + *runargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + + +def sync_create_process(runargs: List[str]) -> subprocess.Popen[bytes]: + return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + + +class AsyncTransport(ABC): + _started: bool = False + + def __init__( + self, + /, + directives: Optional[list[Union[Directive, Type[Directive]]]] = None, + version: Optional[Literal['v1', 'v2']] = 'v1', + hotkey_transport: Optional[ThreadedHotkeyTransport] = None, + **kwargs: Any, + ): + self._hotkey_transport = hotkey_transport + self._directives: list[Union[Directive, Type[Directive]]] = directives or [] + self._version: Optional[Literal['v1', 'v2']] = version + + async def _get_full_version(self) -> str: + res = await self.run_script(_version_detection_script) + version = res.strip() + assert re.match(r'^\d+\.', version) + return version + + async def _get_major_version(self) -> Literal['v1', 'v2']: + version = await self._get_full_version() + match = re.match(r'^(\d+)\.', version) + if not match: + raise ValueError(f'Unexpected version {version!r}') + major_version = match.group(1) + if major_version == '1': + return 'v1' + elif major_version == '2': + return 'v2' + else: + raise ValueError(f'Unexpected version {version!r}') + + def on_clipboard_change( + self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None + ) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + self._hotkey_transport.on_clipboard_change(callback, ex_handler) + return None + + def add_hotkey(self, hotkey: Hotkey) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + with warnings.catch_warnings(record=True) as caught_warnings: + self._hotkey_transport.add_hotkey(hotkey=hotkey) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return None + + def add_hotstring(self, hotstring: Hotstring) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + with warnings.catch_warnings(record=True) as caught_warnings: + self._hotkey_transport.add_hotstring(hotstring=hotstring) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return None + + def remove_hotkey(self, hotkey: Hotkey) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + self._hotkey_transport.remove_hotkey(hotkey) + return None + + def clear_hotkeys(self) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + self._hotkey_transport.clear_hotkeys() + return None + + def remove_hotstring(self, hotstring: Hotstring) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + self._hotkey_transport.remove_hotstring(hotstring) + return None + + def clear_hotstrings(self) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + self._hotkey_transport.clear_hotstrings() + return None + + def start_hotkeys(self) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + return self._hotkey_transport.start() + + def stop_hotkeys(self) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + return self._hotkey_transport.stop() + + async def init(self) -> None: + self._started = True + return None + + # fmt: off + @overload + async def run_script(self, script_text_or_path: str, /, *, timeout: Optional[int] = None) -> str: ... + @overload + async def run_script(self, script_text_or_path: str, /, *, blocking: Literal[False], timeout: Optional[int] = None) -> AsyncFutureResult[str]: ... + @overload + async def run_script(self, script_text_or_path: str, /, *, blocking: Literal[True], timeout: Optional[int] = None) -> str: ... + @overload + async def run_script(self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + @abstractmethod + async def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, AsyncFutureResult[str]]: ... + + # fmt: off + @overload + async def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + @overload + async def function_call(self, function_name: Literal['AHKImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Coordinates, None, AsyncFutureResult[Union[Coordinates, None]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKPixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[str, AsyncFutureResult[str]]: ... + @overload + async def function_call(self, function_name: Literal['AHKPixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Optional[Coordinates], AsyncFutureResult[Optional[Coordinates]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Coordinates, AsyncFutureResult[Coordinates]]: ... + @overload + async def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[int, float, str, None, AsyncFutureResult[None], AsyncFutureResult[str], AsyncFutureResult[int], AsyncFutureResult[float]]: ... + @overload + async def function_call(self, function_name: Literal['AHKMouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKMouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKKeyWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + @overload + async def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSendInput'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSendEvent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSetNumLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSetScrollLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[str, AsyncFutureResult[str]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[str, AsyncFutureResult[str]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[str, AsyncFutureResult[str]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinMinimize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinFromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Optional[bool], AsyncFutureResult[Optional[bool]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[Position, None], AsyncFutureResult[Union[None, Position]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[None, AsyncWindow], AsyncFutureResult[Union[None, AsyncWindow]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[None, AsyncWindow], AsyncFutureResult[Union[None, AsyncWindow]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[int, None], AsyncFutureResult[Union[int, None]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[int, AsyncFutureResult[int]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[None, int], AsyncFutureResult[Union[None, int]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[List[AsyncControl], None, AsyncFutureResult[Union[List[AsyncControl], None]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[None, int], AsyncFutureResult[Union[None, int]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetBottom'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetDisable'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetEnable'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetRedraw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetRegion'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSetDetectHiddenWindows'], args: Optional[List[str]] = None) -> None: ... + @overload + async def function_call(self, function_name: Literal['AHKWinSetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSetTitleMatchMode'], args: Optional[List[str]] = None) -> None: ... + @overload + async def function_call(self, function_name: Literal['AHKGetTitleMatchMode']) -> str: ... + @overload + async def function_call(self, function_name: Literal['AHKGetTitleMatchSpeed']) -> str: ... + @overload + async def function_call(self, function_name: Literal['AHKControlGetText'], args: Optional[List[str]] = None, *, engine: Optional[AsyncAHK[Any]] = None, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + @overload + async def function_call(self, function_name: Literal['AHKControlClick'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKControlGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[Position, AsyncFutureResult[Position]]: ... + @overload + async def function_call(self, function_name: Literal['AHKGetCoordMode'], args: List[str]) -> str: ... + @overload + async def function_call(self, function_name: Literal['AHKSetCoordMode'], args: List[str]) -> None: ... + @overload + async def function_call(self, function_name: Literal['AHKGetSendLevel']) -> int: ... + @overload + async def function_call(self, function_name: Literal['AHKSetSendMode'], args: List[str]) -> None: ... + @overload + async def function_call(self, function_name: Literal['AHKGetSendMode']) -> str: ... + @overload + async def function_call(self, function_name: Literal['AHKSetSendLevel'], args: List[str]) -> None: ... + @overload + async def function_call(self, function_name: Literal['AHKWinWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinWaitActive'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinWaitNotActive'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... + + @overload + async def function_call(self, function_name: Literal['AHKWinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinIsActive'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + @overload + async def function_call(self, function_name: Literal['AHKGetVolume'], args: Optional[List[str]] = None) -> float: ... + @overload + async def function_call(self, function_name: Literal['AHKSoundBeep'], args: Optional[List[str]] = None, *, blocking: bool = True) -> None: ... + @overload + async def function_call(self, function_name: Literal['AHKSoundGet'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSoundPlay'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSoundSet'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSetVolume'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKTrayTip'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKGetClipboard'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + @overload + async def function_call(self, function_name: Literal['AHKGetClipboardAll'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[bytes, AsyncFutureResult[bytes]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSetClipboard'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKSetClipboardAll'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKBlockInput'], args: Optional[List[str]], *, blocking: bool = True) -> None: ... + @overload + async def function_call(self, function_name: Literal['AHKShowToolTip'], args: Optional[List[str]]) -> None: ... + @overload + async def function_call(self, function_name: Literal['AHKClipWait'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + + # @overload + # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... + @overload + async def function_call(self, function_name: Literal['AHKWinWaitClose'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKRegRead'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + @overload + async def function_call(self, function_name: Literal['AHKRegWrite'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKRegDelete'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKMenuTrayTip'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKMenuTrayIcon'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKMenuTrayShow'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKMenuTrayHide'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKGuiNew'], args: List[str], *, engine: AsyncAHK[Any]) -> str: ... + @overload + async def function_call(self, function_name: Literal['AHKMsgBox'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + @overload + async def function_call(self, function_name: Literal['AHKInputBox'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, None, AsyncFutureResult[str], AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKFileSelectFile'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, None, AsyncFutureResult[str], AsyncFutureResult[None]]: ... + @overload + async def function_call(self, function_name: Literal['AHKFileSelectFolder'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, None, AsyncFutureResult[str], AsyncFutureResult[None]]: ... + # fmt: on + + async def function_call( + self, + function_name: FunctionName, + args: Optional[List[str]] = None, + blocking: bool = True, + engine: Optional[AsyncAHK[Any]] = None, + ) -> Any: + if not self._started and blocking: + with warnings.catch_warnings(record=True) as caught_warnings: + await self.init() + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=3) + request = RequestMessage(function_name=function_name, args=args) + if blocking: + return await self.send(request, engine=engine) + else: + return await self.a_send_nonblocking(request, engine=engine) + + @abstractmethod + async def send( + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None + ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: ... + + @abstractmethod # unasync: remove + async def a_send_nonblocking( # unasync: remove + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None + ) -> AsyncFutureResult[ + Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] + ]: ... + + @abstractmethod + def send_nonblocking( + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None + ) -> FutureResult[ + Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] + ]: ... + + +class AsyncDaemonProcessTransport(AsyncTransport): + def __init__( + self, + *, + executable_path: str = '', + directives: Optional[list[Directive | Type[Directive]]] = None, + jinja_loader: Optional[jinja2.BaseLoader] = None, + template: Optional[jinja2.Template] = None, + extensions: list[Extension] | None = None, + version: Optional[Literal['v1', 'v2']] = None, + skip_version_check: bool = False, + ): + self._extensions = extensions or [] + self._proc: Optional[AsyncAHKProcess] + self._proc = None + self._temp_script: Optional[str] = None + self.__template: jinja2.Template + self._jinja_env: jinja2.Environment + self._execution_lock = threading.Lock() + self._a_execution_lock = asyncio.Lock() # unasync: remove + self._executable_path = executable_path + + if version is None or version == 'v1': + template_name = 'daemon.ahk' + const_script = _DAEMON_SCRIPT_TEMPLATE + elif version == 'v2': + template_name = 'daemon-v2.ahk' + const_script = _DAEMON_SCRIPT_V2_TEMPLATE + else: + raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') + + if jinja_loader is None: + try: + loader: jinja2.BaseLoader + loader = jinja2.PackageLoader('ahk', 'templates') + except ValueError: + # see: https://github.com/spyoungtech/ahk/issues/201 + warnings.warn( + 'Jinja could not find templates with PackageLoader. Falling back to BaseLoader', + category=UserWarning, + ) + loader = jinja2.BaseLoader() + self._jinja_env = jinja2.Environment(loader=loader, trim_blocks=True, autoescape=False) + else: + self._jinja_env = jinja2.Environment(loader=jinja_loader, trim_blocks=True, autoescape=False) + try: + self.__template = self._jinja_env.get_template(template_name) + except jinja2.TemplateNotFound: + warnings.warn('daemon template missing. Falling back to constant', category=UserWarning) + self.__template = self._jinja_env.from_string(const_script) + if template is None: + template = self.__template + self._template: jinja2.Template = template + directives = directives or [] + if extensions: + includes = _resolve_includes(extensions) + directives = includes + directives + hotkey_transport = ThreadedHotkeyTransport( + executable_path=self._executable_path, directives=directives, version=version + ) + super().__init__(directives=directives, version=version, hotkey_transport=hotkey_transport) + + @property + def template(self) -> jinja2.Template: + return self._template + + async def init(self) -> None: + await self.start() + await super().init() + return None + + async def start(self) -> None: + assert self._proc is None, 'cannot start a process twice' + with warnings.catch_warnings(record=True) as caught_warnings: + async with self.lock: + self._proc = self._create_process() + await self._proc.start() + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + + def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: Any) -> str: + if template is None: + template = self._template + kwargs['daemon'] = self.__template + message_types = {str(tom, 'utf-8'): c.__name__.upper() for tom, c in _message_registry.items()} + return template.render( + directives=self._directives, + message_types=message_types, + message_registry=_message_registry, + extensions=self._extensions, + ahk_version=self._version, + **kwargs, + ) + + @property + def lock(self) -> Any: + return self._a_execution_lock # unasync: remove + return self._execution_lock + + def _create_process(self, template: Optional[jinja2.Template] = None, **template_kwargs: Any) -> AsyncAHKProcess: + if template is None: + if template_kwargs: + raise ValueError('template kwargs were specified, but no template was provided') + if self._temp_script is None or not os.path.exists(self._temp_script): + script_text = self._render_script() + with tempfile.NamedTemporaryFile( + mode='w', prefix='python-ahk-', suffix='.ahk', delete=False + ) as tempscriptfile: + tempscriptfile.write(script_text) # XXX: can we make this async? + self._temp_script = tempscriptfile.name + daemon_script = self._temp_script + atexit.register(try_remove, tempscriptfile.name) + else: + daemon_script = self._temp_script + else: + script_text = self._render_script(template=template, **template_kwargs) + with tempfile.NamedTemporaryFile(mode='w', prefix='python-ahk-', suffix='.ahk', delete=False) as tempscript: + tempscript.write(script_text) + daemon_script = tempscript.name + atexit.register(try_remove, tempscript.name) + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] + proc = AsyncAHKProcess(runargs=runargs) + return proc + + async def _send_nonblocking( + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None + ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: + msg = request.format() + async with self._create_process() as proc: + proc.write(msg) + await proc.adrain_stdin() + tom = await proc.readline() + num_lines = await proc.readline() + content_buffer = BytesIO() + content_buffer.write(tom) + content_buffer.write(num_lines) + try: + lines_to_read = int(num_lines) + 1 + except ValueError as e: + try: + stdout = tom + num_lines + await proc.read() + except Exception: + stdout = b'' + raise AHKProtocolError( + 'Unexpected data received. This is usually the result of an unhandled error in the AHK process' + + (f': {stdout!r}' if stdout else '') + ) from e + for _ in range(lines_to_read): + part = await proc.readline() + content_buffer.write(part) + content = content_buffer.getvalue()[:-1] + response = ResponseMessage.from_bytes(content, engine=engine) + return response.unpack() # type: ignore + + async def a_send_nonblocking( # unasync: remove + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None + ) -> AsyncFutureResult[ + Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] + ]: + loop = asyncio.get_running_loop() + task = loop.create_task(self._send_nonblocking(request=request, engine=engine)) + return AsyncFutureResult(task) + + def send_nonblocking( + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None + ) -> FutureResult[Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]]: + # this is only used by the sync implementation + pool = ThreadPoolExecutor(max_workers=1) + fut = pool.submit(self._send_nonblocking, request=request, engine=engine) + pool.shutdown(wait=False) + assert async_assert_send_nonblocking_type_correct( + fut + ) # workaround to get mypy correctness in sync and async implementation + return FutureResult(fut) + + async def send( + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None + ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: + msg = request.format() + assert self._proc is not None + async with self.lock: + self._proc.write(msg) + await self._proc.adrain_stdin() + tom = await self._proc.readline() + num_lines = await self._proc.readline() + content_buffer = BytesIO() + content_buffer.write(tom) + content_buffer.write(num_lines) + try: + lines_to_read = int(num_lines) + 1 + except ValueError as e: + try: + stdout = tom + num_lines + await self._proc.read() + except Exception: + stdout = b'' + raise AHKProtocolError( + 'Unexpected data received. This is usually the result of an unhandled error in the AHK process' + + (f': {stdout!r}' if stdout else '') + ) from e + for _ in range(lines_to_read): + part = await self._proc.readline() + content_buffer.write(part) + content = content_buffer.getvalue()[:-1] + response = ResponseMessage.from_bytes(content, engine=engine) + return response.unpack() # type: ignore + + async def _async_run_nonblocking( # unasync: remove + self, proc: Communicable, script_bytes: Optional[bytes], timeout: Optional[int] = None + ) -> AsyncFutureResult[str]: + loop = asyncio.get_running_loop() + + async def f() -> str: + try: + await proc.start(atexit_cleanup=False) + stdout, stderr = await proc.acommunicate(script_bytes, timeout) + finally: + try: + proc.kill() + except Exception: + pass + if proc.returncode != 0: + assert proc.returncode is not None + raise subprocess.CalledProcessError(proc.returncode, proc.runargs, stdout, stderr) + return stdout.decode('utf-8') + + task = loop.create_task(f()) + return AsyncFutureResult(task) + + def _sync_run_nonblocking( + self, + proc: Communicable, + script_bytes: Optional[bytes], + timeout: Optional[int] = None, + ) -> FutureResult[str]: + raise RuntimeError('This method can only be called from the sync API') # unasync: remove + + def f() -> str: + try: + proc.astart(atexit_cleanup=False) + stdout, stderr = proc.communicate(script_bytes, timeout) + finally: + try: + proc.kill() + except Exception: + pass + if proc.returncode != 0: + assert proc.returncode is not None + raise subprocess.CalledProcessError(proc.returncode, proc.runargs, stdout, stderr) + return stdout.decode('utf-8') + + pool = ThreadPoolExecutor(max_workers=1) + fut = pool.submit(f) + pool.shutdown(wait=False) + return FutureResult(fut) + + # fmt: off + @overload + async def run_script(self, script_text_or_path: str, /, *, timeout: Optional[int] = None) -> str: ... + @overload + async def run_script(self, script_text_or_path: str, /, *, blocking: Literal[False], timeout: Optional[int] = None) -> AsyncFutureResult[str]: ... + @overload + async def run_script(self, script_text_or_path: str, /, *, blocking: Literal[True], timeout: Optional[int] = None) -> str: ... + @overload + async def run_script(self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, AsyncFutureResult[str]]: + if os.path.exists(script_text_or_path): + script_bytes = None + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', script_text_or_path] + else: + script_bytes = bytes(script_text_or_path, 'utf-8') + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', '*'] + proc = AsyncAHKProcess(runargs) + if blocking: + async with proc: + stdout, stderr = await proc.acommunicate(script_bytes, timeout=timeout) + if proc.returncode != 0: + assert proc.returncode is not None + raise subprocess.CalledProcessError(proc.returncode, proc.runargs, stdout, stderr) + return stdout.decode('utf-8') + else: + return await self._async_run_nonblocking(proc, script_bytes, timeout=timeout) + + +if TYPE_CHECKING: + from .engine import AsyncAHK diff --git a/ahk/_async/window.py b/ahk/_async/window.py new file mode 100644 index 00000000..3c5e5e8c --- /dev/null +++ b/ahk/_async/window.py @@ -0,0 +1,776 @@ +from __future__ import annotations + +import sys +import warnings +from functools import partial +from typing import Any +from typing import Callable +from typing import Coroutine +from typing import Literal +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING +from typing import TypedDict +from typing import TypeVar +from typing import Union + +from ahk._types import Position +from ahk.exceptions import WindowNotFoundException + +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + +if sys.version_info < (3, 11): + from typing_extensions import NotRequired +else: + from typing import NotRequired + +if TYPE_CHECKING: + from .engine import AsyncAHK + from .transport import AsyncFutureResult + + +AsyncPropertyReturnStr: TypeAlias = Coroutine[None, None, str] # unasync: remove +SyncPropertyReturnStr: TypeAlias = str + +AsyncPropertyReturnInt: TypeAlias = Coroutine[None, None, int] # unasync: remove +SyncPropertyReturnInt: TypeAlias = int + +AsyncPropertyReturnTupleIntInt: TypeAlias = Coroutine[None, None, Tuple[int, int]] # unasync: remove +SyncPropertyReturnTupleIntInt: TypeAlias = Tuple[int, int] + +AsyncPropertyReturnBool: TypeAlias = Coroutine[None, None, bool] # unasync: remove +SyncPropertyReturnBool: TypeAlias = bool + +_PROPERTY_DEPRECATION_WARNING_MESSAGE = 'Use of the {0} property is not recommended (in the async API only) and may be removed in a future version. Use the get_{0} method instead.' +_SETTERS_REMOVED_ERROR_MESSAGE = ( + 'Use of the {0} property setter is not supported in the async API. Use the set_{0} instead.' +) + +T_EngineVersion = TypeVar('T_EngineVersion', bound=Optional[Literal['v1', 'v2']]) + + +class AsyncWindow: + def __init__(self, engine: AsyncAHK[T_EngineVersion], ahk_id: str): + self._engine: AsyncAHK[T_EngineVersion] = engine + if not ahk_id: + raise ValueError(f'Invalid ahk_id: {ahk_id!r}') + self._ahk_id: str = ahk_id + + def __repr__(self) -> str: + return f'<{self.__class__.__qualname__} ahk_id={self._ahk_id!r}>' + + def __eq__(self, other: object) -> bool: + if not isinstance(other, AsyncWindow): + return NotImplemented + return self._ahk_id == other._ahk_id + + def __hash__(self) -> int: + return hash(self._ahk_id) + + def __getattr__(self, name: str) -> Callable[..., Any]: + method = self._engine._get_window_extension_method(name) + if method is None: + raise AttributeError(f'{self.__class__.__name__!r} object has no attribute {name!r}') + else: + return partial(method, self) + + async def close(self) -> None: + await self._engine.win_close( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + return None + + async def kill(self) -> None: + await self._engine.win_kill( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + + async def exists(self) -> bool: + return await self._engine.win_exists( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + + @property + def id(self) -> str: + return self._ahk_id + + @property + def exist(self) -> AsyncPropertyReturnBool: + warnings.warn( # unasync: remove + _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('exist'), category=DeprecationWarning, stacklevel=2 + ) + return self.exists() + + async def get_pid(self) -> int: + pid = await self._engine.win_get_pid( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + if pid is None: + raise WindowNotFoundException( + f'Error when trying to get PID of window {self._ahk_id!r}. The window may have been closed before the operation could be completed' + ) + return pid + + @property + def pid(self) -> AsyncPropertyReturnInt: + warnings.warn( # unasync: remove + _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('pid'), category=DeprecationWarning, stacklevel=2 + ) + return self.get_pid() + + async def get_process_name(self) -> str: + name = await self._engine.win_get_process_name( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + if name is None: + raise WindowNotFoundException( + f'Error when trying to get process name of window {self._ahk_id!r}. The window may have been closed before the operation could be completed' + ) + return name + + @property + def process_name(self) -> AsyncPropertyReturnStr: + warnings.warn( # unasync: remove + _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('process_name'), category=DeprecationWarning, stacklevel=2 + ) + return self.get_process_name() + + async def get_process_path(self) -> str: + path = await self._engine.win_get_process_path( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + if path is None: + raise WindowNotFoundException( + f'Error when trying to get process path of window {self._ahk_id!r}. The window may have been closed before the operation could be completed' + ) + return path + + @property + def process_path(self) -> AsyncPropertyReturnStr: + warnings.warn( # unasync: remove + _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('process_path'), category=DeprecationWarning, stacklevel=2 + ) + return self.get_process_path() + + async def get_minmax(self) -> int: + minmax = await self._engine.win_get_minmax( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + if minmax is None: + raise WindowNotFoundException( + f'Error when trying to get minmax state of window {self._ahk_id}. The window may have been closed before the operation could be completed' + ) + return minmax + + async def get_title(self) -> str: + title = await self._engine.win_get_title( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + return title + + @property + def title(self) -> AsyncPropertyReturnStr: + warnings.warn( # unasync: remove + _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('title'), category=DeprecationWarning, stacklevel=2 + ) + return self.get_title() + + @title.setter + def title(self, value: str) -> Any: + raise RuntimeError(_SETTERS_REMOVED_ERROR_MESSAGE) # unasync: remove + self.set_title(value) + + async def set_title(self, new_title: str) -> None: + await self._engine.win_set_title( + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + new_title=new_title, + title_match_mode=(1, 'Fast'), + ) + return None + + async def list_controls(self) -> Sequence['AsyncControl']: + controls = await self._engine.win_get_control_list( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + if controls is None: + raise WindowNotFoundException( + f'Error when trying to enumerate controls for window {self._ahk_id}. The window may have been closed before the operation could be completed' + ) + return controls + + # fmt: off + @overload + async def minimize(self) -> None: ... + @overload + async def minimize(self, blocking: Literal[True]) -> None: ... + @overload + async def minimize(self, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def minimize(self, blocking: bool = True) -> Optional[AsyncFutureResult[None]]: ... + # fmt: on + async def minimize(self, blocking: bool = True) -> Optional[AsyncFutureResult[None]]: + return await self._engine.win_minimize( + title=f'ahk_id {self._ahk_id}', title_match_mode=(1, 'Fast'), detect_hidden_windows=True, blocking=blocking + ) + + # fmt: off + @overload + async def maximize(self) -> None: ... + @overload + async def maximize(self, blocking: Literal[True]) -> None: ... + @overload + async def maximize(self, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def maximize(self, blocking: bool = True) -> Optional[AsyncFutureResult[None]]: ... + # fmt: on + async def maximize(self, blocking: bool = True) -> Optional[AsyncFutureResult[None]]: + return await self._engine.win_maximize( + title=f'ahk_id {self._ahk_id}', title_match_mode=(1, 'Fast'), detect_hidden_windows=True, blocking=blocking + ) + + # fmt: off + @overload + async def restore(self) -> None: ... + @overload + async def restore(self, blocking: Literal[True]) -> None: ... + @overload + async def restore(self, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def restore(self, blocking: bool = True) -> Optional[AsyncFutureResult[None]]: ... + # fmt: on + async def restore(self, blocking: bool = True) -> Optional[AsyncFutureResult[None]]: + return await self._engine.win_restore( + title=f'ahk_id {self._ahk_id}', title_match_mode=(1, 'Fast'), detect_hidden_windows=True, blocking=blocking + ) + + # fmt: off + @overload + async def get_class(self) -> str: ... + @overload + async def get_class(self, blocking: Literal[True]) -> str: ... + @overload + async def get_class(self, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def get_class(self, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def get_class(self, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: + return await self._engine.win_get_class( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast'), blocking=blocking + ) + + # fmt: off + @overload + async def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> None: ... + @overload + async def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: Literal[True]) -> None: ... + @overload + async def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def set_always_on_top( + self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_set_always_on_top( + toggle=toggle, + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + async def is_always_on_top(self) -> bool: ... + @overload + async def is_always_on_top(self, *, blocking: Literal[False]) -> AsyncFutureResult[Optional[bool]]: ... + @overload + async def is_always_on_top(self, *, blocking: Literal[True]) -> bool: ... + @overload + async def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[Optional[bool]]]: ... + # fmt: on + async def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[Optional[bool]]]: + args = [f'ahk_id {self._ahk_id}'] + resp = await self._engine._transport.function_call( + 'AHKWinIsAlwaysOnTop', args, blocking=blocking + ) # XXX: maybe shouldn't access transport directly? + if resp is None: + raise WindowNotFoundException( + f'Error when trying to get always on top style for window {self._ahk_id}. The window may have been closed before the operation could be completed' + ) + return resp + + @property + def always_on_top(self) -> AsyncPropertyReturnBool: + return self.is_always_on_top() + + @always_on_top.setter + def always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> Any: + raise RuntimeError(_SETTERS_REMOVED_ERROR_MESSAGE) # unasync: remove + self.set_always_on_top(toggle) + + # fmt: off + @overload + async def send(self, keys: str, control: str = '') -> None: ... + @overload + async def send(self, keys: str, control: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def send(self, keys: str, control: str = '', *, blocking: Literal[True]) -> None: ... + @overload + async def send(self, keys: str, control: str = '', *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def send( + self, keys: str, control: str = '', *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.control_send( + keys=keys, + control=control, + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + async def click(self, x: int = 0, y: int = 0, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '') -> None: ... + @overload + async def click(self, x: int = 0, y: int = 0, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def click(self, x: int = 0, y: int = 0, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: Literal[True]) -> None: ... + @overload + async def click(self, x: int = 0, y: int = 0, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def click( + self, + x: int = 0, + y: int = 0, + *, + button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', + click_count: int = 1, + options: str = '', + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + pos = f'X{x} Y{y}' + return await self._engine.control_click( + control=pos, + title=f'ahk_id {self._ahk_id}', + button=button, + click_count=click_count, + options=options, + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + async def get_text(self) -> str: ... + @overload + async def get_text(self, *, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def get_text(self, *, blocking: Literal[True]) -> str: ... + @overload + async def get_text(self, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + # fmt: on + async def get_text(self, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: + return await self._engine.win_get_text( + title=f'ahk_id {self._ahk_id}', blocking=blocking, detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + + @property + def text(self) -> AsyncPropertyReturnStr: + return self.get_text() + + # fmt: off + @overload + async def get_position(self) -> Position: ... + @overload + async def get_position(self, *, blocking: Literal[False]) -> AsyncFutureResult[Optional[Position]]: ... + @overload + async def get_position(self, *, blocking: Literal[True]) -> Position: ... + @overload + async def get_position(self, *, blocking: bool = True) -> Union[Position, AsyncFutureResult[Optional[Position]], AsyncFutureResult[Position]]: ... + # fmt: on + async def get_position( + self, *, blocking: bool = True + ) -> Union[Position, AsyncFutureResult[Optional[Position]], AsyncFutureResult[Position]]: + resp = await self._engine.win_get_position( # type: ignore[misc] # this appears to be a mypy bug + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + if resp is None: + raise WindowNotFoundException( + f'Error when trying to get position for window {self._ahk_id}. The window may have been closed before the operation could be completed' + ) + return resp + + # fmt: off + @overload + async def activate(self) -> None: ... + @overload + async def activate(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def activate(self, *, blocking: Literal[True]) -> None: ... + @overload + async def activate(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def activate(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + resp = await self._engine.win_activate( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + return resp + + # fmt: off + @overload + async def to_bottom(self, *, blocking: Literal[True]) -> None: ... + @overload + async def to_bottom(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def to_bottom(self) -> None: ... + # fmt: on + async def to_bottom(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_set_bottom( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + async def to_top(self, *, blocking: Literal[True]) -> None: ... + @overload + async def to_top(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def to_top(self) -> None: ... + # fmt: on + async def to_top(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_set_top( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + async def show(self, *, blocking: Literal[True]) -> None: ... + @overload + async def show(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show(self) -> None: ... + # fmt: on + async def show(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_show( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + async def hide(self, *, blocking: Literal[True]) -> None: ... + @overload + async def hide(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def hide(self) -> None: ... + # fmt: on + async def hide(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_hide( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + async def disable(self, *, blocking: Literal[True]) -> None: ... + @overload + async def disable(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def disable(self) -> None: ... + # fmt: on + async def disable(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_set_disable( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + async def enable(self, *, blocking: Literal[True]) -> None: ... + @overload + async def enable(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def enable(self) -> None: ... + # fmt: on + async def enable(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_set_enable( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + async def redraw(self, *, blocking: Literal[True]) -> None: ... + @overload + async def redraw(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def redraw(self) -> None: ... + @overload + async def redraw(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def redraw(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_set_redraw( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + @overload + async def set_style(self, style: str) -> bool: ... + + @overload + async def set_style(self, style: str, *, blocking: Literal[True]) -> bool: ... + + @overload + async def set_style(self, style: str, *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + + @overload + async def set_style(self, style: str, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + + async def set_style(self, style: str, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: + return await self._engine.win_set_style( + style=style, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + @overload + async def set_ex_style(self, style: str) -> bool: ... + + @overload + async def set_ex_style(self, style: str, *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + + @overload + async def set_ex_style(self, style: str, *, blocking: Literal[True]) -> bool: ... + + @overload + async def set_ex_style(self, style: str, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + + async def set_ex_style(self, style: str, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: + return await self._engine.win_set_ex_style( + style=style, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + @overload + async def set_region(self, options: str) -> bool: ... + + @overload + async def set_region(self, options: str, *, blocking: Literal[True]) -> bool: ... + + @overload + async def set_region(self, options: str, *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + + @overload + async def set_region(self, options: str, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + + async def set_region(self, options: str, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: + return await self._engine.win_set_region( + options=options, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + async def set_transparent( + self, transparency: Union[int, Literal['Off']], *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_set_transparent( + transparency=transparency, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + async def set_trans_color( + self, color: Union[int, str], *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_set_trans_color( + color=color, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + @property + def active(self) -> AsyncPropertyReturnBool: + return self.is_active() + + async def is_active(self) -> bool: + return await self._engine.win_is_active( + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + async def move( + self, x: int, y: int, *, width: Optional[int] = None, height: Optional[int] = None, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.win_move( + x=x, + y=y, + width=width, + height=height, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + # fmt: off + @overload + @classmethod + async def from_pid(cls, engine: AsyncAHK[Literal['v2']], pid: int) -> AsyncWindow: ... + @overload + @classmethod + async def from_pid(cls, engine: Union[AsyncAHK[Literal['v1']], AsyncAHK[None]], pid: int) -> Optional[AsyncWindow]: ... + # fmt: on + @classmethod + async def from_pid(cls, engine: AsyncAHK[Any], pid: int) -> Optional[AsyncWindow]: + return await engine.win_get(title=f'ahk_pid {pid}') + + @classmethod + async def from_mouse_position(cls, engine: AsyncAHK[Any]) -> Optional[AsyncWindow]: + return await engine.win_get_from_mouse_position() + + +_ControlTargetKwargs = TypedDict('_ControlTargetKwargs', {'title': str, 'control': NotRequired[str]}) + + +class AsyncControl: + def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): + self.window: AsyncWindow = window + self.hwnd: str = hwnd + self.control_class: str = control_class + self._engine = window._engine + self.use_hwnd: bool = False + + def _get_target_params(self, use_hwnd: Optional[bool] = None) -> _ControlTargetKwargs: + if use_hwnd is None: + use_hwnd = self.use_hwnd + if use_hwnd: + return {'title': f'ahk_id {self.hwnd}'} + else: + return {'title': f'ahk_id {self.window._ahk_id}', 'control': self.control_class} + + # fmt: off + @overload + async def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', use_hwnd: Optional[bool] = None) -> None: ... + @overload + async def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', use_hwnd: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', use_hwnd: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', use_hwnd: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def click( + self, + *, + button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', + click_count: int = 1, + options: str = '', + use_hwnd: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.control_click( + button=button, + click_count=click_count, + options=options, + title_match_mode=(1, 'Fast'), + detect_hidden_windows=True, + blocking=blocking, + **self._get_target_params(use_hwnd), + ) + + # fmt: off + @overload + async def send(self, keys: str, *, use_hwnd: Optional[bool] = None) -> None: ... + @overload + async def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + async def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def send( + self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + return await self._engine.control_send( + keys=keys, + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), + ) + + async def get_text( + self, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[str, AsyncFutureResult[str]]: + return await self._engine.control_get_text( + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), + ) + + # fmt: off + @overload + async def get_position(self, *, use_hwnd: Optional[bool] = None) -> Position: ... + @overload + async def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Position]: ... + @overload + async def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: Literal[True]) -> Position: ... + @overload + async def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: bool = True) -> Union[Position, AsyncFutureResult[Position]]: ... + # fmt: on + async def get_position( + self, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[Position, AsyncFutureResult[Position]]: + return await self._engine.control_get_position( + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), + ) + + def __repr__(self) -> str: + return f'<{self.__class__.__name__} window={self.window!r}, control_hwnd={self.hwnd!r}, control_class={self.control_class!r}>' diff --git a/ahk/_constants.py b/ahk/_constants.py new file mode 100644 index 00000000..03f3830e --- /dev/null +++ b/ahk/_constants.py @@ -0,0 +1,6197 @@ +# THIS FILE IS AUTOGENERATED BY _set_constants.py +# DO NOT EDIT BY HAND + +DAEMON_SCRIPT_TEMPLATE = r"""{% block daemon_script %} +{% block directives %} +#Requires AutoHotkey v1.1.17+ +#NoEnv +#Persistent +#SingleInstance Off +; BEGIN user-defined directives +{% block user_directives %} +{% for directive in directives %} +{{ directive }} + +{% endfor %} + +; END user-defined directives +{% endblock user_directives %} +{% endblock directives %} + +Critical, 100 + +{% block message_types %} +MESSAGE_TYPES := Object({% for tom, msg_class in message_registry.items() %}"{{ msg_class.fqn() }}", "{{ tom.decode('utf-8') }}"{% if not loop.last %}, {% endif %}{% endfor %}) +{% endblock message_types %} + +NOVALUE_SENTINEL := Chr(57344) + +FormatResponse(ByRef MessageType, ByRef payload) { + global MESSAGE_TYPES + newline_count := CountNewlines(payload) + response := Format("{}`n{}`n{}`n", MESSAGE_TYPES[MessageType], newline_count, payload) + return response +} + +FormatNoValueResponse() { + global NOVALUE_SENTINEL + return FormatResponse("ahk.message.NoValueResponseMessage", NOVALUE_SENTINEL) +} + +FormatBinaryResponse(ByRef bin) { + b64 := b64encode(bin) + return FormatResponse("ahk.message.B64BinaryResponseMessage", b64) +} + +AHKSetDetectHiddenWindows(args*) { + {% block AHKSetDetectHiddenWindows %} + value := args[1] + DetectHiddenWindows, %value% + return FormatNoValueResponse() + {% endblock AHKSetDetectHiddenWindows %} +} + +AHKSetTitleMatchMode(args*) { + {% block AHKSetTitleMatchMode %} + val1 := args[1] + val2 := args[2] + if (val1 != "") { + SetTitleMatchMode, %val1% + } + if (val2 != "") { + SetTitleMatchMode, %val2% + } + return FormatNoValueResponse() + {% endblock AHKSetTitleMatchMode %} +} + +AHKGetTitleMatchMode(args*) { + {% block AHKGetTitleMatchMode %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) + {% endblock AHKGetTitleMatchMode %} +} + +AHKGetTitleMatchSpeed(args*) { + {% block AHKGetTitleMatchSpeed %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) + {% endblock AHKGetTitleMatchSpeed %} +} + +AHKSetSendLevel(args*) { + {% block AHKSetSendLevel %} + level := args[1] + SendLevel, %level% + return FormatNoValueResponse() + {% endblock AHKSetSendLevel %} +} + +AHKGetSendLevel(args*) { + {% block AHKGetSendLevel %} + + return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) + {% endblock AHKGetSendLevel %} +} + +AHKWinExist(args*) { + {% block AHKWinExist %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + if WinExist(title, text, extitle, extext) { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } else { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp + {% endblock AHKWinExist %} +} + +AHKWinClose(args*) { + {% block AHKWinClose %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + secondstowait := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinClose %} +} + +AHKWinKill(args*) { + {% block AHKWinKill %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + secondstowait := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinKill, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinKill %} +} + +AHKWinWait(args*) { + {% block AHKWinWait %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + if (timeout != "") { + WinWait, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWait, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp + {% endblock AHKWinWait %} +} + +AHKWinWaitActive(args*) { + {% block AHKWinWaitActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + if (timeout != "") { + WinWaitActive, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitActive, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp + {% endblock AHKWinWaitActive %} +} + +AHKWinWaitNotActive(args*) { + {% block AHKWinWaitNotActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + if (timeout != "") { + WinWaitNotActive, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitNotActive, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp + {% endblock AHKWinWaitNotActive %} +} + +AHKWinWaitClose(args*) { + {% block AHKWinWaitClose %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + if (timeout != "") { + WinWaitClose, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitClose, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } else { + resp := FormatNoValueResponse() + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp + {% endblock AHKWinWaitClose %} +} + +AHKWinMinimize(args*) { + {% block AHKWinMinimize %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinMinimize, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinMinimize %} +} + +AHKWinMaximize(args*) { + {% block AHKWinMaximize %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinMaximize, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinMaximize %} +} + +AHKWinRestore(args*) { + {% block AHKWinRestore %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinRestore, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinRestore %} +} + +AHKWinIsActive(args*) { + {% block AHKWinIsActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + if WinActive(title, text, extitle, extext) { + response := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } else { + response := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinIsActive %} +} + +AHKWinGetID(args*) { + {% block AHKWinGetID %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, ID, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.WindowResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetID %} +} + +AHKWinGetTitle(args*) { + {% block AHKWinGetTitle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGetTitle, text, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatResponse("ahk.message.StringResponseMessage", text) + {% endblock AHKWinGetTitle %} +} + +AHKWinGetIDLast(args*) { + {% block AHKWinGetIDLast %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, IDLast, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.WindowResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetIDLast %} +} + +AHKWinGetPID(args*) { + {% block AHKWinGetPID %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, PID, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetPID %} +} + +AHKWinGetProcessName(args*) { + {% block AHKWinGetProcessName %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, ProcessName, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetProcessName %} +} + +AHKWinGetProcessPath(args*) { + {% block AHKWinGetProcessPath %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, ProcessPath, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetProcessPath %} +} + +AHKWinGetCount(args*) { + {% block AHKWinGetCount %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, Count, %title%, %text%, %extitle%, %extext% + if (output = 0) { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetCount %} +} + +AHKWinGetMinMax(args*) { + {% block AHKWinGetMinMax %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, MinMax, %title%, %text%, %extitle%, %extext% + if (output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetMinMax %} +} + +AHKWinGetControlList(args*) { + {% block AHKWinGetControlList %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, ahkid, ID, %title%, %text%, %extitle%, %extext% + + if (ahkid = "") { + return FormatNoValueResponse() + } + + WinGet, ctrList, ControlList, %title%, %text%, %extitle%, %extext% + WinGet, ctrListID, ControlListHWND, %title%, %text%, %extitle%, %extext% + + if (ctrListID = "") { + return FormatResponse("ahk.message.WindowControlListResponseMessage", Format("('{}', [])", ahkid)) + } + + ctrListArr := StrSplit(ctrList, "`n") + ctrListIDArr := StrSplit(ctrListID, "`n") + if (ctrListArr.Length() != ctrListIDArr.Length()) { + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatResponse("ahk.message.ExceptionResponseMessage", "Control hwnd/class lists have unexpected lengths") + } + + output := Format("('{}', [", ahkid) + + for index, hwnd in ctrListIDArr { + classname := ctrListArr[index] + output .= Format("('{}', '{}'), ", hwnd, classname) + + } + output .= "])" + response := FormatResponse("ahk.message.WindowControlListResponseMessage", output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetControlList %} +} + +AHKWinGetTransparent(args*) { + {% block AHKWinGetTransparent %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, Transparent, %title%, %text%, %extitle%, %extext% + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetTransparent %} +} +AHKWinGetTransColor(args*) { + {% block AHKWinGetTransColor %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, TransColor, %title%, %text%, %extitle%, %extext% + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetTransColor %} +} +AHKWinGetStyle(args*) { + {% block AHKWinGetStyle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, Style, %title%, %text%, %extitle%, %extext% + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetStyle %} +} +AHKWinGetExStyle(args*) { + {% block AHKWinGetExStyle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, ExStyle, %title%, %text%, %extitle%, %extext% + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetExStyle %} +} + +AHKWinGetText(args*) { + {% block AHKWinGetText %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGetText, output,%title%,%text%,%extitle%,%extext% + + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was an error getting window text") + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetText %} +} + +AHKWinSetTitle(args*) { + {% block AHKWinSetTitle %} + new_title := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinSetTitle, %title%, %text%, %new_title%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetTitle %} +} + +AHKWinSetAlwaysOnTop(args*) { + {% block AHKWinSetAlwaysOnTop %} + toggle := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, AlwaysOntop, %toggle%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetAlwaysOnTop %} +} + +AHKWinSetBottom(args*) { + {% block AHKWinSetBottom %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Bottom,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetBottom %} +} + +AHKWinShow(args*) { + {% block AHKWinShow %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinShow, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinShow %} +} + +AHKWinHide(args*) { + {% block AHKWinHide %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinHide, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinHide %} +} + +AHKWinSetTop(args*) { + {% block AHKWinSetTop %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Top,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetTop %} +} + +AHKWinSetEnable(args*) { + {% block AHKWinSetEnable %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Enable,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetEnable %} +} + +AHKWinSetDisable(args*) { + {% block AHKWinSetDisable %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Disable,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetDisable %} +} + +AHKWinSetRedraw(args*) { + {% block AHKWinSetRedraw %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Redraw,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetRedraw %} +} + +AHKWinSetStyle(args*) { + {% block AHKWinSetStyle %} + + style := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Style, %style%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp + {% endblock AHKWinSetStyle %} +} + +AHKWinSetExStyle(args*) { + {% block AHKWinSetExStyle %} + + style := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, ExStyle, %style%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp + {% endblock AHKWinSetExStyle %} +} + +AHKWinSetRegion(args*) { + {% block AHKWinSetRegion %} + + options := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Region, %options%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp + {% endblock AHKWinSetRegion %} +} + +AHKWinSetTransparent(args*) { + {% block AHKWinSetTransparent %} + + transparency := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Transparent, %transparency%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetTransparent %} +} + +AHKWinSetTransColor(args*) { + {% block AHKWinSetTransColor %} + + color := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinSetTransColor %} +} + +AHKImageSearch(args*) { + {% block AHKImageSearch %} + + imagepath := args[5] + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + coord_mode := args[6] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode, Pixel, %coord_mode% + } + + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } + + ImageSearch, xpos, ypos,% x1,% y1,% x2,% y2, %imagepath% + + if (coord_mode != "") { + CoordMode, Pixel, %current_mode% + } + + if (ErrorLevel = 2) { + s := FormatResponse("ahk.message.ExceptionResponseMessage", "there was a problem that prevented the args from conducting the search (such as failure to open the image file or a badly formatted option)") + } else if (ErrorLevel = 1) { + s := FormatNoValueResponse() + } else { + s := FormatResponse("ahk.message.CoordinateResponseMessage", Format("({}, {})", xpos, ypos)) + } + + return s + {% endblock AHKImageSearch %} +} + +AHKPixelGetColor(args*) { + {% block AHKPixelGetColor %} + + x := args[1] + y := args[2] + coord_mode := args[3] + options := args[4] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode, Pixel, %coord_mode% + } + + PixelGetColor, color, %x%, %y%, %options% + ; TODO: check errorlevel + + if (coord_mode != "") { + CoordMode, Pixel, %current_mode% + } + + return FormatResponse("ahk.message.StringResponseMessage", color) + {% endblock AHKPixelGetColor %} +} + +AHKPixelSearch(args*) { + {% block AHKPixelSearch %} + + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + color := args[5] + variation := args[6] + options := args[7] + coord_mode := args[8] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode, Pixel, %coord_mode% + } + + PixelSearch, resultx, resulty, %x1%, %y1%, %x2%, %y2%, %color%, %variation%, %options% + + if (coord_mode != "") { + CoordMode, Pixel, %current_mode% + } + + if (ErrorLevel = 1) { + return FormatNoValueResponse() + } else if (ErrorLevel = 0) { + payload := Format("({}, {})", resultx, resulty) + return FormatResponse("ahk.message.CoordinateResponseMessage", payload) + } else if (ErrorLevel = 2) { + return FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem conducting the pixel search (ErrorLevel 2)") + } else { + return FormatResponse("ahk.message.ExceptionResponseMessage", "Unexpected error. This is probably a bug. Please report this at https://github.com/spyoungtech/ahk/issues") + } + + {% endblock AHKPixelSearch %} +} + +AHKMouseGetPos(args*) { + {% block AHKMouseGetPos %} + + coord_mode := args[1] + current_coord_mode := Format("{}", A_CoordModeMouse) + if (coord_mode != "") { + CoordMode, Mouse, %coord_mode% + } + MouseGetPos, xpos, ypos + + payload := Format("({}, {})", xpos, ypos) + resp := FormatResponse("ahk.message.CoordinateResponseMessage", payload) + + if (coord_mode != "") { + CoordMode, Mouse, %current_coord_mode% + } + + return resp + {% endblock AHKMouseGetPos %} +} + +AHKKeyState(args*) { + {% block AHKKeyState %} + + keyname := args[1] + mode := args[2] + if (mode != "") { + state := GetKeyState(keyname, mode) + } else{ + state := GetKeyState(keyname) + } + + if (state = "") { + return FormatNoValueResponse() + } + + if state is integer + return FormatResponse("ahk.message.IntegerResponseMessage", state) + + if state is float + return FormatResponse("ahk.message.FloatResponseMessage", state) + + return FormatResponse("ahk.message.StringResponseMessage", state) + + {% endblock AHKKeyState %} +} + +AHKMouseMove(args*) { + {% block AHKMouseMove %} + x := args[1] + y := args[2] + speed := args[3] + relative := args[4] + send_mode := args[5] + coord_mode := args[6] + + current_send_mode := Format("{}", A_SendMode) + current_coord_mode := Format("{}", A_CoordModeMouse) + + if (send_mode != "") { + SendMode, %send_mode% + } + + if (coord_mode != "") { + CoordMode, Mouse, %coord_mode% + } + + if (relative != "") { + MouseMove, %x%, %y%, %speed%, R + } else { + MouseMove, %x%, %y%, %speed% + } + + if (send_mode != "") { + SendMode, %current_send_mode% + } + + if (coord_mode != "") { + CoordMode, Mouse, %current_coord_mode% + } + + resp := FormatNoValueResponse() + return resp + {% endblock AHKMouseMove %} +} + +AHKClick(args*) { + {% block AHKClick %} + x := args[1] + y := args[2] + button := args[3] + click_count := args[4] + direction := args[5] + r := args[6] + relative_to := args[7] + send_mode := args[8] + current_coord_rel := Format("{}", A_CoordModeMouse) + current_send_mode := Format("{}", A_SendMode) + + if (send_mode != "") { + SendMode, %send_mode% + } + + if (relative_to != "") { + CoordMode, Mouse, %relative_to% + } + + + Click, %x%, %y%, %button%, %direction%, %r% + + if (send_mode != "") { + SendMode, %current_send_mode% + } + + if (relative_to != "") { + CoordMode, Mouse, %current_coord_rel% + } + + return FormatNoValueResponse() + + {% endblock AHKClick %} +} + +AHKGetCoordMode(args*) { + {% block AHKGetCoordMode %} + + target := args[1] + + if (target = "ToolTip") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeToolTip) + } + if (target = "Pixel") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModePixel) + } + if (target = "Mouse") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMouse) + } + if (target = "Caret") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeCaret) + } + if (target = "Menu") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMenu) + } + return FormatResponse("ahk.message.ExceptionResponseMessage", "Invalid coord mode") + {% endblock AHKGetCoordMode %} +} + +AHKSetCoordMode(args*) { + {% block AHKSetCoordMode %} + target := args[1] + relative_to := args[2] + CoordMode, %target%, %relative_to% + + return FormatNoValueResponse() + {% endblock AHKSetCoordMode %} +} + +AHKGetSendMode(args*) { + return FormatResponse("ahk.message.StringResponseMessage", A_SendMode) +} + + +AHKSetSendMode(args*) { + mode := args[1] + SendMode, %mode% + return FormatNoValueResponse() +} + + +AHKMouseClickDrag(args*) { + {% block AHKMouseClickDrag %} + button := args[1] + x1 := args[2] + y1 := args[3] + x2 := args[4] + y2 := args[5] + speed := args[6] + relative := args[7] + relative_to := args[8] + send_mode := args[8] + current_send_mode := Format("{}", A_SendMode) + if (send_mode != "") { + SendMode, %send_mode% + } + + current_coord_rel := Format("{}", A_CoordModeMouse) + + if (relative_to != "") { + CoordMode, Mouse, %relative_to% + } + + MouseClickDrag, %button%, %x1%, %y1%, %x2%, %y2%, %speed%, %relative% + + if (relative_to != "") { + CoordMode, Mouse, %current_coord_rel% + } + + if (send_mode != "") { + SendMode, %current_send_mode% + } + + return FormatNoValueResponse() + + {% endblock AHKMouseClickDrag %} +} + +AHKRegRead(args*) { + {% block RegRead %} + + key_name := args[1] + value_name := args[2] + + RegRead, output, %key_name%, %value_name% + + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) + } + else { + resp := FormatResponse("ahk.message.StringResponseMessage", Format("{}", output)) + } + return resp + {% endblock RegRead %} +} + +AHKRegWrite(args*) { + {% block RegWrite %} + + value_type := args[1] + key_name := args[2] + value_name := args[3] + value := args[4] + RegWrite, %value_type%, %key_name%, %value_name%, %value% + if (ErrorLevel = 1) { + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) + } + + return FormatNoValueResponse() + {% endblock RegWrite %} +} + +AHKRegDelete(args*) { + {% block RegDelete %} + + key_name := args[1] + value_name := args[2] + RegDelete, %key_name%, %value_name% + if (ErrorLevel = 1) { + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) + } + return FormatNoValueResponse() + + {% endblock RegDelete %} +} + +AHKKeyWait(args*) { + {% block AHKKeyWait %} + + keyname := args[1] + options := args[2] + + if (options = "") { + KeyWait,% keyname + } else { + KeyWait,% keyname,% options + } + ret := ErrorLevel + + if (ret = 1) { + return FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else if (ret = 0) { + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + } else { + ; Unclear if this is even reachable + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("There was a problem. ErrorLevel: {}", ret)) + } + + {% endblock AHKKeyWait %} +} + +SetKeyDelay(args*) { + {% block SetKeyDelay %} + SetKeyDelay, args[1], args[2] + {% endblock SetKeyDelay %} +} + +AHKSend(args*) { + {% block AHKSend %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + send_mode := args[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + current_send_mode := Format("{}", A_SendMode) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + + if (send_mode != "") { + SendMode, %send_mode% + } + + Send,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + + if (send_mode != "") { + SendMode, %current_send_mode% + } + + return FormatNoValueResponse() + {% endblock AHKSend %} +} + +AHKSendRaw(args*) { + {% block AHKSendRaw %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + send_mode := args[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + current_send_mode := Format("{}", A_SendMode) + + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + + if (send_mode != "") { + SendMode, %send_mode% + } + + SendRaw,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + + if (send_mode != "") { + SendMode, %current_send_mode% + } + + return FormatNoValueResponse() + {% endblock AHKSendRaw %} +} + +AHKSendInput(args*) { + {% block AHKSendInput %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + + SendInput,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + return FormatNoValueResponse() + {% endblock AHKSendInput %} +} + +AHKSendEvent(args*) { + {% block AHKSendEvent %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + + SendEvent,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + return FormatNoValueResponse() + {% endblock AHKSendEvent %} +} + +AHKSendPlay(args*) { + {% block AHKSendPlay %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelayPlay) + current_key_duration := Format("{}", A_KeyDurationPlay) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration%, Play + } + + SendPlay,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + return FormatNoValueResponse() + {% endblock AHKSendPlay %} +} + +AHKSetCapsLockState(args*) { + {% block AHKSetCapsLockState %} + state := args[1] + if (state = "") { + SetCapsLockState % !GetKeyState("CapsLock", "T") + } else { + SetCapsLockState, %state% + } + return FormatNoValueResponse() + {% endblock AHKSetCapsLockState %} +} + + +AHKSetNumLockState(args*) { + {% block AHKSetNumLockState %} + state := args[1] + if (state = "") { + SetNumLockState % !GetKeyState("NumLock", "T") + } else { + SetNumLockState, %state% + } + return FormatNoValueResponse() + {% endblock AHKSetNumLockState %} +} + +AHKSetScrollLockState(args*) { + {% block AHKSetScrollLockState %} + state := args[1] + if (state = "") { + SetScrollLockState % !GetKeyState("ScrollLock", "T") + } else { + SetScrollLockState, %state% + } + return FormatNoValueResponse() + {% endblock AHKSetScrollLockState %} +} + +HideTrayTip(args*) { + {% block HideTrayTip %} + TrayTip ; Attempt to hide it the normal way. + if SubStr(A_OSVersion,1,3) = "10." { + Menu Tray, NoIcon + Sleep 200 ; It may be necessary to adjust this sleep. + Menu Tray, Icon + } + {% endblock HideTrayTip %} +} + +AHKWinGetClass(args*) { + {% block AHKWinGetClass %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGetClass, output,%title%,%text%,%extitle%,%extext% + + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was an error getting window class") + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetClass %} +} + +AHKWinActivate(args*) { + {% block AHKWinActivate %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinActivate, %title%, %text%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinActivate %} +} + +AHKWindowList(args*) { + {% block AHKWindowList %} + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + if (detect_hw) { + DetectHiddenWindows, %detect_hw% + } + + WinGet windows, List, %title%, %text%, %extitle%, %extext% + r := "" + Loop %windows% + { + id := windows%A_Index% + r .= id . "`," + } + resp := FormatResponse("ahk.message.WindowListResponseMessage", r) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp + {% endblock AHKWindowList %} +} + +AHKControlClick(args*) { + {% block AHKControlClick %} + + ctrl := args[1] + title := args[2] + text := args[3] + button := args[4] + click_count := args[5] + options := args[6] + exclude_title := args[7] + exclude_text := args[8] + detect_hw := args[9] + match_mode := args[10] + match_speed := args[11] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + ControlClick, %ctrl%, %title%, %text%, %button%, %click_count%, %options%, %exclude_title%, %exclude_text% + + if (ErrorLevel != 0) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "Failed to click control") + } else { + response := FormatNoValueResponse() + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response + {% endblock AHKControlClick %} +} + +AHKControlGetText(args*) { + {% block AHKControlGetText %} + + ctrl := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + ControlGetText, result, %ctrl%, %title%, %text%, %extitle%, %extext% + + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the text") + } else { + response := FormatResponse("ahk.message.StringResponseMessage", result) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response + {% endblock AHKControlGetText %} +} + +AHKControlGetPos(args*) { + {% block AHKControlGetPos %} + + ctrl := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + ControlGetPos, x, y, w, h, %ctrl%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the text") + } else { + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse("ahk.message.PositionResponseMessage", result) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response + + {% endblock AHKControlGetPos %} +} + +AHKControlSend(args*) { + {% block AHKControlSend %} + ctrl := args[1] + keys := args[2] + title := args[3] + text := args[4] + extitle := args[5] + extext := args[6] + detect_hw := args[7] + match_mode := args[8] + match_speed := args[9] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + ControlSend, %ctrl%, %keys%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKControlSend %} +} + +AHKWinFromMouse(args*) { + {% block AHKWinFromMouse %} + + MouseGetPos,,, MouseWin + + if (MouseWin = "") { + return FormatNoValueResponse() + } + + return FormatResponse("ahk.message.WindowResponseMessage", MouseWin) + {% endblock AHKWinFromMouse %} +} + +AHKWinIsAlwaysOnTop(args*) { + {% block AHKWinIsAlwaysOnTop %} + + title := args[1] + WinGet, ExStyle, ExStyle, %title% + if (ExStyle = "") + return FormatNoValueResponse() + + if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + else + return FormatResponse("ahk.message.BooleanResponseMessage", 0) + {% endblock AHKWinIsAlwaysOnTop %} +} + +AHKWinMove(args*) { + {% block AHKWinMove %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + x := args[8] + y := args[9] + width := args[10] + height := args[11] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinMove, %title%, %text%, %x%, %y%, %width%, %height%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + + {% endblock AHKWinMove %} +} + +AHKWinGetPos(args*) { + {% block AHKWinGetPos %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGetPos, x, y, w, h, %title%, %text%, %extitle%, %extext% + + if (x = "") { + response := FormatNoValueResponse() + } else { + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse("ahk.message.PositionResponseMessage", result) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response + {% endblock AHKWinGetPos %} +} + +AHKGetVolume(args*) { + {% block AHKGetVolume %} + + device_number := args[1] + + try { + SoundGetWaveVolume, retval, %device_number% + } catch e { + response := FormatResponse("ahk.message.ExceptionResponseMessage", Format("There was a problem getting the volume with device of index {} ({})", device_number, e.message)) + return response + } + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", Format("There was a problem getting the volume with device of index {}", device_number)) + } else { + response := FormatResponse("ahk.message.FloatResponseMessage", Format("{}", retval)) + } + return response + {% endblock AHKGetVolume %} +} + +AHKSoundBeep(args*) { + {% block AHKSoundBeep %} + freq := args[1] + duration := args[2] + SoundBeep , %freq%, %duration% + return FormatNoValueResponse() + {% endblock AHKSoundBeep %} +} + +AHKSoundGet(args*) { + {% block AHKSoundGet %} + + device_number := args[1] + component_type := args[2] + control_type := args[3] + + SoundGet, retval, %component_type%, %control_type%, %device_number% + ; TODO interpret return type + return FormatResponse("ahk.message.StringResponseMessage", Format("{}", retval)) + {% endblock AHKSoundGet %} +} + +AHKSoundSet(args*) { + {% block AHKSoundSet %} + device_number := args[1] + component_type := args[2] + control_type := args[3] + value := args[4] + SoundSet, %value%, %component_type%, %control_type%, %device_number% + return FormatNoValueResponse() + {% endblock AHKSoundSet %} +} + +AHKSoundPlay(args*) { + {% block AHKSoundPlay %} + filename := args[1] + SoundPlay, %filename% + return FormatNoValueResponse() + {% endblock AHKSoundPlay %} +} + +AHKSetVolume(args*) { + {% block AHKSetVolume %} + device_number := args[1] + value := args[2] + SoundSetWaveVolume, %value%, %device_number% + return FormatNoValueResponse() + {% endblock AHKSetVolume %} +} + +CountNewlines(ByRef s) { + newline := "`n" + StringReplace, s, s, %newline%, %newline%, UseErrorLevel + count := ErrorLevel + return count +} + +AHKEcho(args*) { + {% block AHKEcho %} + arg := args[1] + return FormatResponse("ahk.message.StringResponseMessage", arg) + {% endblock AHKEcho %} +} + +AHKTraytip(args*) { + {% block AHKTraytip %} + title := args[1] + text := args[2] + second := args[3] + option := args[4] + + TrayTip, %title%, %text%, %second%, %option% + return FormatNoValueResponse() + {% endblock AHKTraytip %} +} + +AHKShowToolTip(args*) { + {% block AHKShowToolTip %} + text := args[1] + x := args[2] + y := args[3] + which := args[4] + ToolTip, %text%, %x%, %y%, %which% + return FormatNoValueResponse() + {% endblock AHKShowToolTip %} +} + +AHKGetClipboard(args*) { + {% block AHKGetClipboard %} + + return FormatResponse("ahk.message.StringResponseMessage", Clipboard) + {% endblock AHKGetClipboard %} +} + +AHKGetClipboardAll(args*) { + {% block AHKGetClipboardAll %} + data := ClipboardAll + return FormatBinaryResponse(data) + {% endblock AHKGetClipboardAll %} +} + +AHKSetClipboard(args*) { + {% block AHKSetClipboard %} + text := args[1] + Clipboard := text + return FormatNoValueResponse() + {% endblock AHKSetClipboard %} +} + +AHKSetClipboardAll(args*) { + {% block AHKSetClipboardAll %} + ; TODO there should be a way for us to accept a base64 string instead + filename := args[1] + FileRead, Clipboard, %filename% + return FormatNoValueResponse() + {% endblock AHKSetClipboardAll %} +} + +AHKClipWait(args*) { + + timeout := args[1] + wait_for_any_data := args[2] + + ClipWait, %timeout%, %wait_for_any_data% + + if (ErrorLevel = 1) { + return FormatResponse("ahk.message.TimeoutResponseMessage", "timed out waiting for clipboard data") + } + return FormatNoValueResponse() +} + +AHKBlockInput(args*) { + value := args[1] + BlockInput, %value% + return FormatNoValueResponse() +} + +AHKMenuTrayTip(args*) { + value := args[1] + Menu, Tray, Tip, %value% + return FormatNoValueResponse() +} + +AHKMenuTrayShow(args*) { + Menu, Tray, Icon + return FormatNoValueResponse() +} + +AHKMenuTrayHide(args*) { + Menu, Tray, NoIcon + return FormatNoValueResponse() +} + +AHKMenuTrayIcon(args*) { + filename := args[1] + icon_number := args[2] + freeze := args[3] + Menu, Tray, Icon, %filename%, %icon_number%,%freeze% + return FormatNoValueResponse() +} + +AHKGuiNew(args*) { + + options := args[1] + title := args[2] + Gui, New, %options%, %title% + return FormatResponse("ahk.message.StringResponseMessage", hwnd) +} + +AHKMsgBox(args*) { + + options := args[1] + title := args[2] + text := args[3] + timeout := args[4] + MsgBox,% options, %title%, %text%, %timeout% + IfMsgBox, Yes + ret := FormatResponse("ahk.message.StringResponseMessage", "Yes") + IfMsgBox, No + ret := FormatResponse("ahk.message.StringResponseMessage", "No") + IfMsgBox, OK + ret := FormatResponse("ahk.message.StringResponseMessage", "OK") + IfMsgBox, Cancel + ret := FormatResponse("ahk.message.StringResponseMessage", "Cancel") + IfMsgBox, Abort + ret := FormatResponse("ahk.message.StringResponseMessage", "Abort") + IfMsgBox, Ignore + ret := FormatResponse("ahk.message.StringResponseMessage", "Ignore") + IfMsgBox, Retry + ret := FormatResponse("ahk.message.StringResponseMessage", "Retry") + IfMsgBox, Continue + ret := FormatResponse("ahk.message.StringResponseMessage", "Continue") + IfMsgBox, TryAgain + ret := FormatResponse("ahk.message.StringResponseMessage", "TryAgain") + IfMsgBox, Timeout + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "MsgBox timed out") + return ret +} + +AHKInputBox(args*) { + + title := args[1] + prompt := args[2] + hide := args[3] + width := args[4] + height := args[5] + x := args[6] + y := args[7] + locale := args[8] + timeout := args[9] + default := args[10] + + InputBox, output, %title%, %prompt%, %hide%, %width%, %height%, %x%, %y%, %locale%, %timeout%, %default% + if (ErrorLevel = 2) { + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "Input box timed out") + } else if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +AHKFileSelectFile(byRef args) { + + options := args[1] + root := args[2] + title := args[3] + filter := args[4] + FileSelectFile, output, %options%, %root%, %title%, %filter% + if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +AHKFileSelectFolder(byRef args) { + + starting_folder := args[1] + options := args[2] + prompt := args[3] + + FileSelectFolder, output, %starting_folder%, %options%, %prompt% + + if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") + + +b64decode(ByRef pszString) { + ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw + ; [in] LPCSTR pszString, A pointer to a string that contains the formatted string to be converted. + ; [in] DWORD cchString, The number of characters of the formatted string to be converted, not including the terminating NULL character. If this parameter is zero, pszString is considered to be a null-terminated string. + ; [in] DWORD dwFlags, Indicates the format of the string to be converted. (see table in link above) + ; [in] BYTE *pbBinary, A pointer to a buffer that receives the returned sequence of bytes. If this parameter is NULL, the function calculates the length of the buffer needed and returns the size, in bytes, of required memory in the DWORD pointed to by pcbBinary. + ; [in, out] DWORD *pcbBinary, A pointer to a DWORD variable that, on entry, contains the size, in bytes, of the pbBinary buffer. After the function returns, this variable contains the number of bytes copied to the buffer. If this value is not large enough to contain all of the data, the function fails and GetLastError returns ERROR_MORE_DATA. + ; [out] DWORD *pdwSkip, A pointer to a DWORD value that receives the number of characters skipped to reach the beginning of the -----BEGIN ...----- header. If no header is present, then the DWORD is set to zero. This parameter is optional and can be NULL if it is not needed. + ; [out] DWORD *pdwFlags A pointer to a DWORD value that receives the flags actually used in the conversion. These are the same flags used for the dwFlags parameter. In many cases, these will be the same flags that were passed in the dwFlags parameter. If dwFlags contains one of the following flags, this value will receive a flag that indicates the actual format of the string. This parameter is optional and can be NULL if it is not needed. + + if (pszString = "") { + return "" + } + + cchString := StrLen(pszString) + + dwFlags := 0x00000001 ; CRYPT_STRING_BASE64: Base64, without headers. + getsize := 0 ; When this is NULL, the function returns the required size in bytes (for our first call, which is needed for our subsequent call) + buff_size := 0 ; The function will write to this variable on our first call + pdwSkip := 0 ; We don't use any headers or preamble, so this is zero + pdwFlags := 0 ; We don't need this, so make it null + + ; The first call calculates the required size. The result is written to pbBinary + success := DllCall("Crypt32\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "UInt", getsize, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags) + if (success = 0) { + return "" + } + + ; We're going to give a pointer to a variable to the next call, but first we want to make the buffer the correct size using VarSetCapacity using the previous return value + VarSetCapacity(ret, buff_size, 0) + + ; Now that we know the buffer size we need and have the variable's capacity set to the proper size, we'll pass a pointer to the variable for the decoded value to be written to + + success := DllCall("Crypt32\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "Ptr", &ret, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags) + if (success=0) { + return "" + } + + return StrGet(&ret, "UTF-8") +} + +b64encode(ByRef data) { + ; REF: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptbinarytostringa + ; [in] const BYTE *pbBinary: A pointer to the array of bytes to be converted into a string. + ; [in] DWORD cbBinary: The number of elements in the pbBinary array. + ; [in] DWORD dwFlags: Specifies the format of the resulting formatted string (see table in REF) + ; [out, optional] LPSTR pszString: A pointer to the string, or null (0) to calculate size + ; [in, out] DWORD *pcchString: A pointer to a DWORD variable that contains the size, in TCHARs, of the pszString buffer + + cbBinary := StrLen(data) * (A_IsUnicode ? 2 : 1) + if (cbBinary = 0) { + return "" + } + dwFlags := 0x00000001 | 0x40000000 ; CRYPT_STRING_BASE64 + CRYPT_STRING_NOCRLF + + ; First step is to get the size so we can set the capacity of our return buffer correctly + success := DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", &data, "UInt", cbBinary, "UInt", dwFlags, "Ptr", 0, "UIntP", buff_size) + if (success = 0) { + msg := Format("Problem converting data to base64 when calling CryptBinaryToString ({})", A_LastError) + throw Exception(msg, -1) + } + + VarSetCapacity(ret, buff_size * (A_IsUnicode ? 2 : 1)) + + ; Now we do the conversion to base64 and rteturn the string + + success := DllCall("Crypt32\CryptBinaryToString", "Ptr", &data, "UInt", cbBinary, "UInt", dwFlags, "Str", ret, "UIntP", buff_size) + if (success = 0) { + msg := Format("Problem converting data to base64 when calling CryptBinaryToString ({})", A_LastError) + throw Exception(msg, -1) + } + return ret +} + +; End of included content + +CommandArrayFromQuery(ByRef text) { + decoded_commands := [] + encoded_array := StrSplit(text, "|") + function_name := encoded_array[1] + encoded_array.RemoveAt(1) + decoded_commands.push(function_name) + for index, encoded_value in encoded_array { + decoded_value := b64decode(encoded_value) + decoded_commands.push(decoded_value) + } + return decoded_commands +} + +; BEGIN extension scripts +{% for ext in extensions %} +{{ ext.script_text }} + +{% endfor %} +; END extension scripts + +{% block before_autoexecute %} +{% endblock before_autoexecute %} + +{% block autoexecute %} +stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] +pyresp := "" + +Loop { + query := RTrim(stdin.ReadLine(), "`n") + if (query = "") { + ; Technically this should only happen if the Python process has died, so sending a message is probably futile + ; But if this somehow triggers in some other case, we'll try to have an informative error raised. + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", "Unexpected empty message; AHK exiting. This is likely a bug. Please report this issue at https://github.com/spyoungtech/ahk/issues") + FileAppend, %pyresp%, *, UTF-8 + + ; Exit to avoid leaving the process hanging around needlessly + ExitApp + } + argsArray := CommandArrayFromQuery(query) + try { + func := argsArray[1] + argsArray.RemoveAt(1) + {% block before_function %} + {% endblock before_function %} + pyresp := %func%(argsArray*) + {% block after_function %} + {% endblock after_function %} + } catch e { + {% block function_error_handle %} + message := Format("Error occurred in {}. The error message was: {}", e.What, e.message) + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) + {% endblock function_error_handle %} + } + {% block send_response %} + if (pyresp) { + FileAppend, %pyresp%, *, UTF-8 + } else { + msg := FormatResponse("ahk.message.ExceptionResponseMessage", Format("Unknown Error when calling {}", func)) + FileAppend, %msg%, *, UTF-8 + } + {% endblock send_response %} +} +{% endblock autoexecute %} +{% endblock daemon_script %} + +""" + +HOTKEYS_SCRIPT_TEMPLATE = r"""#Requires AutoHotkey v1.1.17+ +#Persistent + +{% for directive in directives %} +{% if directive.apply_to_hotkeys_process %} + +{{ directive }} +{% endif %} +{% endfor %} + +{% if on_clipboard %} +OnClipboardChange("ClipChanged") +{% endif %} +KEEPALIVE := Chr(57344) +stdin := FileOpen("*", "r `n", "UTF-8") +SetTimer, keepalive, 2000 + +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") + +b64decode(ByRef pszString) { + ; TODO load DLL globally for performance + ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw + ; [in] LPCSTR pszString, A pointer to a string that contains the formatted string to be converted. + ; [in] DWORD cchString, The number of characters of the formatted string to be converted, not including the terminating NULL character. If this parameter is zero, pszString is considered to be a null-terminated string. + ; [in] DWORD dwFlags, Indicates the format of the string to be converted. (see table in link above) + ; [in] BYTE *pbBinary, A pointer to a buffer that receives the returned sequence of bytes. If this parameter is NULL, the function calculates the length of the buffer needed and returns the size, in bytes, of required memory in the DWORD pointed to by pcbBinary. + ; [in, out] DWORD *pcbBinary, A pointer to a DWORD variable that, on entry, contains the size, in bytes, of the pbBinary buffer. After the function returns, this variable contains the number of bytes copied to the buffer. If this value is not large enough to contain all of the data, the function fails and GetLastError returns ERROR_MORE_DATA. + ; [out] DWORD *pdwSkip, A pointer to a DWORD value that receives the number of characters skipped to reach the beginning of the -----BEGIN ...----- header. If no header is present, then the DWORD is set to zero. This parameter is optional and can be NULL if it is not needed. + ; [out] DWORD *pdwFlags A pointer to a DWORD value that receives the flags actually used in the conversion. These are the same flags used for the dwFlags parameter. In many cases, these will be the same flags that were passed in the dwFlags parameter. If dwFlags contains one of the following flags, this value will receive a flag that indicates the actual format of the string. This parameter is optional and can be NULL if it is not needed. + + if (pszString = "") { + return "" + } + + cchString := StrLen(pszString) + dwFlags := 0x00000001 ; CRYPT_STRING_BASE64: Base64, without headers. + getsize := 0 ; When this is NULL, the function returns the required size in bytes (for our first call, which is needed for our subsequent call) + buff_size := 0 ; The function will write to this variable on our first call + pdwSkip := 0 ; We don't use any headers or preamble, so this is zero + pdwFlags := 0 ; We don't need this, so make it null + + + ; The first call calculates the required size. The result is written to pbBinary + success := DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "UInt", getsize, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) + if (success = 0) { + return "" + } + + ; We're going to give a pointer to a variable to the next call, but first we want to make the buffer the correct size using VarSetCapacity using the previous return value + VarSetCapacity(ret, buff_size, 0) + + ; Now that we know the buffer size we need and have the variable's capacity set to the proper size, we'll pass a pointer to the variable for the decoded value to be written to + + success := DllCall( "Crypt32.dll\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "Ptr", &ret, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) + if (success=0) { + return "" + } + + return StrGet(&ret, "UTF-8") +} + +{% for hotkey in hotkeys %} + +{{ hotkey.keyname }}:: + FileAppend, {{ hotkey._id }}`n, *, UTF-8 + return + +{% endfor %} + +{% for hotstring in hotstrings %} +{% if hotstring.replacement %} +:{{ hotstring.options }}:{{ hotstring.trigger }}:: + hostring_{{ hotstring._id }}_func() { + replacement_b64 := "{{ hotstring._replacement_as_b64 }}" + replacement := b64decode(replacement_b64) + Send, %replacement% + } +{% else %} +:{{ hotstring.options }}:{{ hotstring.trigger }}:: + hostring_{{ hotstring._id }}_func() { + FileAppend, {{ hotstring._id }}`n, *, UTF-8 + } +{% endif %} + + +{% endfor %} + +{% if on_clipboard %} + + +ClipChanged(Type) { + CLIPBOARD_SENTINEL := Chr(57345) + ret := Format("{}{}`n", CLIPBOARD_SENTINEL, Type) + FileAppend, %ret%, *, UTF-8 + return +} +{% endif %} + + +keepalive: + global KEEPALIVE + global stdin + FileAppend, %KEEPALIVE%`n, *, UTF-8 + alive_message := RTrim(stdin.ReadLine(), "`n") + if (alive_message != KEEPALIVE) { + ; The parent Python process has terminated unexpectedly + ; Exit to avoid leaving the hotkey process around + ExitApp + } + return + +""" + +DAEMON_SCRIPT_V2_TEMPLATE = r"""{% block daemon_script %} +{% block directives %} +;#NoEnv +#Requires Autohotkey >= 2.0- +Persistent +;#Warn All, Off +#SingleInstance Off +; BEGIN user-defined directives +{% block user_directives %} +{% for directive in directives %} +{{ directive }} + +{% endfor %} + +; END user-defined directives +{% endblock user_directives %} +{% endblock directives %} + +Critical 100 + + +{% block message_types %} +MESSAGE_TYPES := Map({% for tom, msg_class in message_registry.items() %}"{{ msg_class.fqn() }}", "{{ tom.decode('utf-8') }}"{% if not loop.last %}, {% endif %}{% endfor %}) +{% endblock message_types %} + +NOVALUE_SENTINEL := Chr(57344) + +StrCount(haystack, needle) { + StrReplace(haystack, needle, "",, &count) + return count +} + +FormatResponse(MessageType, payload) { + global MESSAGE_TYPES + newline_count := StrCount(payload, "`n") + response := Format("{}`n{}`n{}`n", MESSAGE_TYPES[MessageType], newline_count, payload) + return response +} + +FormatNoValueResponse() { + global NOVALUE_SENTINEL + return FormatResponse("ahk.message.NoValueResponseMessage", NOVALUE_SENTINEL) +} + +FormatBinaryResponse(bin) { + b64 := b64encode(bin) + return FormatResponse("ahk.message.B64BinaryResponseMessage", b64) +} + +AHKSetDetectHiddenWindows(args*) { + {% block AHKSetDetectHiddenWindows %} + value := args[1] + DetectHiddenWindows(value) + return FormatNoValueResponse() + {% endblock AHKSetDetectHiddenWindows %} +} + +AHKSetTitleMatchMode(args*) { + {% block AHKSetTitleMatchMode %} + val1 := args[1] + val2 := args[2] + if (val1 != "") { + SetTitleMatchMode(val1) + } + if (val2 != "") { + SetTitleMatchMode(val2) + } + return FormatNoValueResponse() + {% endblock AHKSetTitleMatchMode %} +} + +AHKGetTitleMatchMode(args*) { + {% block AHKGetTitleMatchMode %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) + {% endblock AHKGetTitleMatchMode %} +} + +AHKGetTitleMatchSpeed(args*) { + {% block AHKGetTitleMatchSpeed %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) + {% endblock AHKGetTitleMatchSpeed %} +} + +AHKSetSendLevel(args*) { + {% block AHKSetSendLevel %} + level := args[1] + SendLevel(level) + return FormatNoValueResponse() + {% endblock AHKSetSendLevel %} +} + +AHKGetSendLevel(args*) { + {% block AHKGetSendLevel %} + + return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) + {% endblock AHKGetSendLevel %} +} + +AHKWinExist(args*) { + {% block AHKWinExist %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + if WinExist(title, text, extitle, extext) { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } else { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return resp + {% endblock AHKWinExist %} +} + +AHKWinClose(args*) { + {% block AHKWinClose %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + secondstowait := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (secondstowait != "") { + WinClose(title, text, secondstowait, extitle, extext) + } else { + WinClose(title, text,, extitle, extext) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinClose %} +} + +AHKWinKill(args*) { + {% block AHKWinKill %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + secondstowait := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (secondstowait != "") { + WinKill(title, text, secondstowait, extitle, extext) + } else { + WinKill(title, text,, extitle, extext) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinKill %} +} + +AHKWinWait(args*) { + {% block AHKWinWait %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (timeout != "") { + output := WinWait(title, text, timeout, extitle, extext) + if (output = 0) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } else { + resp := resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } else { + output := WinWait(title, text,, extitle, extext) + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return resp + {% endblock AHKWinWait %} +} + +AHKWinWaitActive(args*) { + {% block AHKWinWaitActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (timeout != "") { + output := WinWaitActive(title, text, timeout, extitle, extext) + if (output = 0) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWaitActive timed out waiting for the window") + } else { + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } else { + output := WinWaitActive(title, text,, extitle, extext) + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return resp + {% endblock AHKWinWaitActive %} +} + +AHKWinWaitNotActive(args*) { + {% block AHKWinWaitNotActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (timeout != "") { + if (WinWaitNotActive(title, text, timeout, extitle, extext) = 1) { + output := WinGetID() + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } else { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } + } else { + WinWaitNotActive(title, text,, extitle, extext) + output := WinGetID() + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return resp + {% endblock AHKWinWaitNotActive %} +} + +AHKWinWaitClose(args*) { + {% block AHKWinWaitClose %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (timeout != "") { + if (WinWaitClose(title, text, timeout, extitle, extext) = 1) { + resp := FormatNoValueResponse() + } else { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } + } else { + WinWaitClose(title, text,, extitle, extext) + resp := FormatNoValueResponse() + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return resp + {% endblock AHKWinWaitClose %} +} + +AHKWinMinimize(args*) { + {% block AHKWinMinimize %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinMinimize(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinMinimize %} +} + +AHKWinMaximize(args*) { + {% block AHKWinMaximize %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinMaximize(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinMaximize %} +} + +AHKWinRestore(args*) { + {% block AHKWinRestore %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinRestore(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinRestore %} +} + +AHKWinIsActive(args*) { + {% block AHKWinIsActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if WinActive(title, text, extitle, extext) { + response := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } else { + response := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinIsActive %} +} + +AHKWinGetID(args*) { + {% block AHKWinGetID %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetID(title, text, extitle, extext) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetID %} +} + +AHKWinGetTitle(args*) { + {% block AHKWinGetTitle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + text := WinGetTitle(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatResponse("ahk.message.StringResponseMessage", text) + {% endblock AHKWinGetTitle %} +} + +AHKWinGetIDLast(args*) { + {% block AHKWinGetIDLast %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetIDLast(title, text, extitle, extext) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetIDLast %} +} + +AHKWinGetPID(args*) { + {% block AHKWinGetPID %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetPID(title, text, extitle, extext) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetPID %} +} + +AHKWinGetProcessName(args*) { + {% block AHKWinGetProcessName %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetProcessName(title, text, extitle, extext) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetProcessName %} +} + +AHKWinGetProcessPath(args*) { + {% block AHKWinGetProcessPath %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetProcessPath(title, text, extitle, extext) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetProcessPath %} +} + +AHKWinGetCount(args*) { + {% block AHKWinGetCount %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetCount(title, text, extitle, extext) + if (output = 0) { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetCount %} +} + +AHKWinGetMinMax(args*) { + {% block AHKWinGetMinMax %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetMinMax(title, text, extitle, extext) + if (output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetMinMax %} +} + +AHKWinGetControlList(args*) { + {% block AHKWinGetControlList %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + ahkid := WinGetID(title, text, extitle, extext) + if (ahkid = "") { + return FormatNoValueResponse() + } + ctrList := WinGetControls(title, text, extitle, extext) + ctrListID := WinGetControlsHwnd(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + if (ctrListID = "") { + return FormatResponse("ahk.message.WindowControlListResponseMessage", Format("('{}', [])", ahkid)) + } + + if (ctrList.Length != ctrListID.Length) { + return FormatResponse("ahk.message.ExceptionResponseMessage", "Control hwnd/class lists have unexpected lengths") + } + + output := Format("('{}', [", ahkid) + + for index, hwnd in ctrListID { + classname := ctrList[index] + output .= Format("('{}', '{}'), ", hwnd, classname) + + } + output .= "])" + response := FormatResponse("ahk.message.WindowControlListResponseMessage", output) + return response + {% endblock AHKWinGetControlList %} +} + +AHKWinGetTransparent(args*) { + {% block AHKWinGetTransparent %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetTransparent(title, text, extitle, extext) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetTransparent %} +} +AHKWinGetTransColor(args*) { + {% block AHKWinGetTransColor %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetTransColor(title, text, extitle, extext) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetTransColor %} +} +AHKWinGetStyle(args*) { + {% block AHKWinGetStyle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetStyle(title, text, extitle, extext) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetStyle %} +} + +AHKWinGetExStyle(args*) { + {% block AHKWinGetExStyle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetExStyle(title, text, extitle, extext) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetExStyle %} +} + +AHKWinGetText(args*) { + {% block AHKWinGetText %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetText(title,text,extitle,extext) + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + + return response + {% endblock AHKWinGetText %} +} + +AHKWinSetTitle(args*) { + {% block AHKWinSetTitle %} + new_title := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetTitle(new_title, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTitle %} +} + +AHKWinSetAlwaysOnTop(args*) { + {% block AHKWinSetAlwaysOnTop %} + toggle := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + if (toggle = "On") { + toggle := 1 + } else if (toggle = "Off") { + toggle := 0 + } else if (toggle = "") { + toggle := 1 + } + + try { + WinSetAlwaysOnTop(toggle, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetAlwaysOnTop %} +} + +AHKWinSetBottom(args*) { + {% block AHKWinSetBottom %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinMoveBottom(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetBottom %} +} + +AHKWinShow(args*) { + {% block AHKWinShow %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinShow(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinShow %} +} + +AHKWinHide(args*) { + {% block AHKWinHide %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinHide(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinHide %} +} + +AHKWinSetTop(args*) { + {% block AHKWinSetTop %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinMoveTop(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTop %} +} + +AHKWinSetEnable(args*) { + {% block AHKWinSetEnable %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetEnabled(1, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetEnable %} +} + +AHKWinSetDisable(args*) { + {% block AHKWinSetDisable %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetEnabled(0, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetDisable %} +} + +AHKWinSetRedraw(args*) { + {% block AHKWinSetRedraw %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinRedraw(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetRedraw %} +} + +AHKWinSetStyle(args*) { + {% block AHKWinSetStyle %} + + style := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetStyle(style, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + {% endblock AHKWinSetStyle %} +} + +AHKWinSetExStyle(args*) { + {% block AHKWinSetExStyle %} + + style := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + WinSetExStyle(style, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + {% endblock AHKWinSetExStyle %} +} + +AHKWinSetRegion(args*) { + {% block AHKWinSetRegion %} + + options := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetRegion(options, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + {% endblock AHKWinSetRegion %} +} + +AHKWinSetTransparent(args*) { + {% block AHKWinSetTransparent %} + + transparency := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetTransparent(transparency, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTransparent %} +} + +AHKWinSetTransColor(args*) { + {% block AHKWinSetTransColor %} + + color := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetTransColor(color, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTransColor %} +} + +AHKImageSearch(args*) { + {% block AHKImageSearch %} + + imagepath := args[5] + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + coord_mode := args[6] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode("Pixel", coord_mode) + } + + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } + + try { + if (ImageSearch(&xpos, &ypos, x1, y1, x2, y2, imagepath) = 1) { + s := FormatResponse("ahk.message.CoordinateResponseMessage", Format("({}, {})", xpos, ypos)) + } else { + s := FormatNoValueResponse() + } + } + finally { + if (coord_mode != "") { + CoordMode("Pixel", current_mode) + } + } + + return s + {% endblock AHKImageSearch %} +} + +AHKPixelGetColor(args*) { + {% block AHKPixelGetColor %} + + x := args[1] + y := args[2] + coord_mode := args[3] + options := args[4] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode("Pixel", coord_mode) + } + + try { + color := PixelGetColor(x, y, options) + } + finally { + if (coord_mode != "") { + CoordMode("Pixel", current_mode) + } + } + + return FormatResponse("ahk.message.StringResponseMessage", color) + {% endblock AHKPixelGetColor %} +} + +AHKPixelSearch(args*) { + {% block AHKPixelSearch %} + + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + color := args[5] + variation := args[6] + options := args[7] + coord_mode := args[8] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode("Pixel", coord_mode) + } + try { + if (PixelSearch(&resultx, &resulty, x1, y1, x2, y2, color, variation) = 1) { + payload := Format("({}, {})", resultx, resulty) + ret := FormatResponse("ahk.message.CoordinateResponseMessage", payload) + } else { + ret := FormatNoValueResponse() + } + } + finally { + if (coord_mode != "") { + CoordMode("Pixel", current_mode) + } + } + + return ret + + {% endblock AHKPixelSearch %} +} + +AHKMouseGetPos(args*) { + {% block AHKMouseGetPos %} + + coord_mode := args[1] + current_coord_mode := Format("{}", A_CoordModeMouse) + if (coord_mode != "") { + CoordMode("Mouse", coord_mode) + } + MouseGetPos(&xpos, &ypos) + + payload := Format("({}, {})", xpos, ypos) + resp := FormatResponse("ahk.message.CoordinateResponseMessage", payload) + + if (coord_mode != "") { + CoordMode("Mouse", current_coord_mode) + } + + return resp + {% endblock AHKMouseGetPos %} +} + +AHKKeyState(args*) { + {% block AHKKeyState %} + + keyname := args[1] + mode := args[2] + if (mode != "") { + state := GetKeyState(keyname, mode) + } else{ + state := GetKeyState(keyname) + } + + if (state = "") { + return FormatNoValueResponse() + } + + if IsInteger(state) + return FormatResponse("ahk.message.IntegerResponseMessage", state) + + if IsFloat(state) + return FormatResponse("ahk.message.FloatResponseMessage", state) + + return FormatResponse("ahk.message.StringResponseMessage", state) + + {% endblock AHKKeyState %} +} + +AHKMouseMove(args*) { + {% block AHKMouseMove %} + x := args[1] + y := args[2] + speed := args[3] + relative := args[4] + send_mode := args[5] + coord_mode := args[6] + current_send_mode := Format("{}", A_SendMode) + + current_coord_mode := Format("{}", A_CoordModeMouse) + if (coord_mode != "") { + CoordMode("Mouse", coord_mode) + } + + + + if (send_mode != "") { + SendMode send_mode + } + + if (relative != "") { + MouseMove(x, y, speed, "R") + } else { + MouseMove(x, y, speed) + } + + if (send_mode != "") { + SendMode current_send_mode + } + + if (coord_mode != "") { + CoordMode("Mouse", current_coord_mode) + } + + resp := FormatNoValueResponse() + return resp + {% endblock AHKMouseMove %} +} + +AHKClick(args*) { + {% block AHKClick %} + x := args[1] + y := args[2] + button := args[3] + click_count := args[4] + direction := args[5] + r := args[6] + relative_to := args[7] + send_mode := args[8] + current_coord_rel := Format("{}", A_CoordModeMouse) + current_send_mode := Format("{}", A_SendMode) + + if (send_mode != "") { + SendMode send_mode + } + + if (relative_to != "") { + CoordMode("Mouse", relative_to) + } + + Click(x, y, button, direction, r) + + if (relative_to != "") { + CoordMode("Mouse", current_coord_rel) + } + + if (send_mode != "") { + SendMode current_send_mode + } + return FormatNoValueResponse() + + {% endblock AHKClick %} +} + +AHKGetCoordMode(args*) { + {% block AHKGetCoordMode %} + + target := args[1] + + if (target = "ToolTip") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeToolTip) + } + if (target = "Pixel") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModePixel) + } + if (target = "Mouse") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMouse) + } + if (target = "Caret") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeCaret) + } + if (target = "Menu") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMenu) + } + return FormatResponse("ahk.message.ExceptionResponseMessage", "Invalid coord mode") + {% endblock AHKGetCoordMode %} +} + +AHKSetCoordMode(args*) { + {% block AHKSetCoordMode %} + target := args[1] + relative_to := args[2] + CoordMode(target, relative_to) + + return FormatNoValueResponse() + {% endblock AHKSetCoordMode %} +} + + +AHKGetSendMode(args*) { + return FormatResponse("ahk.message.StringResponseMessage", A_SendMode) +} + + +AHKSetSendMode(args*) { + mode := args[1] + SendMode mode + return FormatNoValueResponse() +} + + +AHKMouseClickDrag(args*) { + {% block AHKMouseClickDrag %} + button := args[1] + x1 := args[2] + y1 := args[3] + x2 := args[4] + y2 := args[5] + speed := args[6] + relative := args[7] + relative_to := args[8] + send_mode := args[9] + current_coord_rel := Format("{}", A_CoordModeMouse) + current_send_mode := Format("{}", A_SendMode) + + if (send_mode != "") { + SendMode send_mode + } + + if (relative_to != "") { + CoordMode("Mouse", relative_to) + } + + if (speed = "") { + speed := A_DefaultMouseSpeed + } + + if (x1 = "" and y1 = "") { + MouseClickDrag(button, , , x2, y2, speed, relative) + } + else { + MouseClickDrag(button, x1, y1, x2, y2, speed, relative) + } + + + if (relative_to != "") { + CoordMode("Mouse", current_coord_rel) + } + + if (send_mode != "") { + SendMode current_send_mode + } + + return FormatNoValueResponse() + + {% endblock AHKMouseClickDrag %} +} + +AHKRegRead(args*) { + {% block RegRead %} + + key_name := args[1] + value_name := args[2] + + output := RegRead(key_name, value_name) + resp := FormatResponse("ahk.message.StringResponseMessage", Format("{}", output)) + return resp + {% endblock RegRead %} +} + +AHKRegWrite(args*) { + {% block RegWrite %} + value_type := args[1] + key_name := args[2] + value_name := args[3] + value := args[4] +; RegWrite(value_type, key_name, value_name, value) + if (value_name != "") { + RegWrite(value, value_type, key_name, value_name) + } else { + RegWrite(value, value_type, key_name) + } + return FormatNoValueResponse() + {% endblock RegWrite %} +} + +AHKRegDelete(args*) { + {% block RegDelete %} + + key_name := args[1] + value_name := args[2] + if (value_name != "") { + RegDelete(key_name, value_name) + } else { + RegDelete(key_name) + } + return FormatNoValueResponse() + + {% endblock RegDelete %} +} + +AHKKeyWait(args*) { + {% block AHKKeyWait %} + + keyname := args[1] + options := args[2] + + if (options = "") { + ret := KeyWait(keyname) + } else { + ret := KeyWait(keyname, options) + } + + if (ret = 0) { + return FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else { + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + } + {% endblock AHKKeyWait %} +} + +;SetKeyDelay(args*) { +; {% block SetKeyDelay %} +; SetKeyDelay(args[1], args[2]) +; {% endblock SetKeyDelay %} +;} + +AHKSend(args*) { + {% block AHKSend %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + send_mode := args[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + current_send_mode := Format("{}", A_SendMode) + + if (send_mode != "") { + SendMode send_mode + } + + if (key_delay != "" or key_press_duration != "") { + if (key_delay != "" and key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } else if (key_delay != "" and key_press_duration = "") { + SetKeyDelay(key_delay) + } else if (key_delay = "" and key_press_duration != "") { + SetKeyDelay(current_delay, key_press_duration) + } + } + + + Send(str) + + if (send_mode != "") { + SendMode current_send_mode + } + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSend %} +} + +AHKSendRaw(args*) { + {% block AHKSendRaw %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + if (key_delay != "" and key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } else if (key_delay != "" and key_press_duration = "") { + SetKeyDelay(key_delay) + } else if (key_delay = "" and key_press_duration != "") { + SetKeyDelay(current_delay, key_press_duration) + } + } + + Send("{Raw}" str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSendRaw %} +} + +AHKSendInput(args*) { + {% block AHKSendInput %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + if (key_delay != "" and key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } else if (key_delay != "" and key_press_duration = "") { + SetKeyDelay(key_delay) + } else if (key_delay = "" and key_press_duration != "") { + SetKeyDelay(current_delay, key_press_duration) + } + } + + SendInput(str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSendInput %} +} + +AHKSendEvent(args*) { + {% block AHKSendEvent %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + if (key_delay != "" and key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } else if (key_delay != "" and key_press_duration = "") { + SetKeyDelay(key_delay) + } else if (key_delay = "" and key_press_duration != "") { + SetKeyDelay(current_delay, key_press_duration) + } + } + + SendEvent(str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSendEvent %} +} + +AHKSendPlay(args*) { + {% block AHKSendPlay %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelayPlay) + current_key_duration := Format("{}", A_KeyDurationPlay) + + if (key_delay != "" or key_press_duration != "") { + if (key_delay != "" and key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } else if (key_delay != "" and key_press_duration = "") { + SetKeyDelay(key_delay) + } else if (key_delay = "" and key_press_duration != "") { + SetKeyDelay(current_delay, key_press_duration) + } + } + + SendPlay(str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSendPlay %} +} + +AHKSetCapsLockState(args*) { + {% block AHKSetCapsLockState %} + state := args[1] + if (state = "") { + SetCapsLockState(!GetKeyState("CapsLock", "T")) + } else { + SetCapsLockState(state) + } + return FormatNoValueResponse() + {% endblock AHKSetCapsLockState %} +} + + +AHKSetNumLockState(args*) { + {% block AHKSetNumLockState %} + state := args[1] + if (state = "") { + SetNumLockState(!GetKeyState("NumLock", "T")) + } else { + SetNumLockState(state) + } + return FormatNoValueResponse() + {% endblock AHKSetNumLockState %} +} + + +AHKSetScrollLockState(args*) { + {% block AHKSetScrollLockState %} + state := args[1] + if (state = "") { + SetScrollLockState(!GetKeyState("ScrollLock", "T")) + } else { + SetScrollLockState(state) + } + return FormatNoValueResponse() + {% endblock AHKSetScrollLockState %} +} + +HideTrayTip(args*) { + {% block HideTrayTip %} + TrayTip ; Attempt to hide it the normal way. + if SubStr(A_OSVersion,1,3) = "10." { + A_IconHidden := true + Sleep 200 ; It may be necessary to adjust this sleep. + A_IconHidden := false + } + {% endblock HideTrayTip %} +} + +AHKWinGetClass(args*) { + {% block AHKWinGetClass %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetClass(title,text,extitle,extext) + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + + return response + {% endblock AHKWinGetClass %} +} + +AHKWinActivate(args*) { + {% block AHKWinActivate %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + WinActivate(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinActivate %} +} + +AHKWindowList(args*) { + {% block AHKWindowList %} + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + if (detect_hw) { + DetectHiddenWindows(detect_hw) + } + try { + windows := WinGetList(title, text, extitle, extext) + r := "" + for id in windows + { + r .= id . "`," + } + resp := FormatResponse("ahk.message.WindowListResponseMessage", r) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return resp + {% endblock AHKWindowList %} +} + +AHKControlClick(args*) { + {% block AHKControlClick %} + + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] + title := args[2] + text := args[3] + button := args[4] + click_count := args[5] + options := args[6] + exclude_title := args[7] + exclude_text := args[8] + detect_hw := args[9] + match_mode := args[10] + match_speed := args[11] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + ControlClick(ctrl || unset, title, text, button, click_count, options, exclude_title, exclude_text) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + response := FormatNoValueResponse() + return response + {% endblock AHKControlClick %} +} + +AHKControlGetText(args*) { + {% block AHKControlGetText %} + + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + result := ControlGetText(ctrl, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + response := FormatResponse("ahk.message.StringResponseMessage", result) + + return response + {% endblock AHKControlGetText %} +} + +AHKControlGetPos(args*) { + {% block AHKControlGetPos %} + + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + ControlGetPos(&x, &y, &w, &h, ctrl, title, text, extitle, extext) + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse("ahk.message.PositionResponseMessage", result) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKControlGetPos %} +} + +AHKControlSend(args*) { + {% block AHKControlSend %} + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] + keys := args[2] + title := args[3] + text := args[4] + extitle := args[5] + extext := args[6] + detect_hw := args[7] + match_mode := args[8] + match_speed := args[9] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + ControlSend(keys, ctrl || unset, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKControlSend %} +} + +AHKWinFromMouse(args*) { + {% block AHKWinFromMouse %} + + MouseGetPos(,, &MouseWin) + + if (MouseWin = "") { + return FormatNoValueResponse() + } + + return FormatResponse("ahk.message.WindowResponseMessage", MouseWin) + {% endblock AHKWinFromMouse %} +} + +AHKWinIsAlwaysOnTop(args*) { + {% block AHKWinIsAlwaysOnTop %} + ; TODO: detect hidden windows / etc? + title := args[1] + ExStyle := WinGetExStyle(title) + if (ExStyle = "") + return FormatNoValueResponse() + + if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + else + return FormatResponse("ahk.message.BooleanResponseMessage", 0) + {% endblock AHKWinIsAlwaysOnTop %} +} + +AHKWinMove(args*) { + {% block AHKWinMove %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + x := args[8] + y := args[9] + width := args[10] + height := args[11] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + if (width = "" or height = "") { + WinGetPos(&_, &__, &w, &h, title, text, extitle, extext) + if (width = "") { + width := w + } + if (height = "") { + height := h + } + } + + try { + WinMove(x, y, width, height, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + + {% endblock AHKWinMove %} +} + +AHKWinGetPos(args*) { + {% block AHKWinGetPos %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + WinGetPos(&x, &y, &w, &h, title, text, extitle, extext) + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse("ahk.message.PositionResponseMessage", result) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + + return response + {% endblock AHKWinGetPos %} +} + +AHKGetVolume(args*) { + {% block AHKGetVolume %} + + device_number := args[1] + + retval := SoundGetVolume(,device_number) + response := FormatResponse("ahk.message.FloatResponseMessage", Format("{}", retval)) + return response + {% endblock AHKGetVolume %} +} + +AHKSoundBeep(args*) { + {% block AHKSoundBeep %} + freq := args[1] + duration := args[2] + SoundBeep(freq, duration) + return FormatNoValueResponse() + {% endblock AHKSoundBeep %} +} + +AHKSoundGet(args*) { + {% block AHKSoundGet %} + return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundGet is not supported in ahk v2") + {% endblock AHKSoundGet %} +} + +AHKSoundSet(args*) { + {% block AHKSoundSet %} + return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundSet is not supported in ahk v2") + {% endblock AHKSoundSet %} +} + +AHKSoundPlay(args*) { + {% block AHKSoundPlay %} + filename := args[1] + SoundPlay(filename) + return FormatNoValueResponse() + {% endblock AHKSoundPlay %} +} + +AHKSetVolume(args*) { + {% block AHKSetVolume %} + device_number := args[1] + value := args[2] + SoundSetVolume(value,,device_number) + return FormatNoValueResponse() + {% endblock AHKSetVolume %} +} + + +AHKEcho(args*) { + {% block AHKEcho %} + arg := args[1] + return FormatResponse("ahk.message.StringResponseMessage", arg) + {% endblock AHKEcho %} +} + +AHKTraytip(args*) { + {% block AHKTraytip %} + title := args[1] + text := args[2] + second := args[3] + option := args[4] + + TrayTip(text, title, option) + return FormatNoValueResponse() + {% endblock AHKTraytip %} +} + +AHKShowToolTip(args*) { + {% block AHKShowToolTip %} + text := args[1] + x := args[2] + y := args[3] + which := args[4] + + ToolTip(text, IsNumber(x) ? Number(x) : unset, IsNumber(y) ? Number(y) : unset, which || unset) + ; In AHK v2, doubling the call to ToolTip seems necessary to ensure synchronous creation of the window + ; This seems to be more reliable than sleeping to wait for the tooltip callback + ; Without this doubled up call (or a sleep) we return the the blocking loop (awaiting next command from Python) + ; before the tooltip window is created, meaning the tooltip will not show until if/when processing the next command + ToolTip(text, IsNumber(x) ? Number(x) : unset, IsNumber(y) ? Number(y) : unset, which || unset) + return FormatNoValueResponse() + {% endblock AHKShowToolTip %} +} + +AHKGetClipboard(args*) { + {% block AHKGetClipboard %} + + return FormatResponse("ahk.message.StringResponseMessage", A_Clipboard) + {% endblock AHKGetClipboard %} +} + +AHKGetClipboardAll(args*) { + {% block AHKGetClipboardAll %} + data := ClipboardAll() + return FormatBinaryResponse(&data) + {% endblock AHKGetClipboardAll %} +} + +AHKSetClipboard(args*) { + {% block AHKSetClipboard %} + text := args[1] + A_Clipboard := text + return FormatNoValueResponse() + {% endblock AHKSetClipboard %} +} + +AHKSetClipboardAll(args*) { + {% block AHKSetClipboardAll %} + ; TODO there should be a way for us to accept a base64 string instead + filename := args[1] + contents := FileRead(filename, "RAW") + A_Clipboard := ClipboardAll(contents) + return FormatNoValueResponse() + {% endblock AHKSetClipboardAll %} +} + +AHKClipWait(args*) { + + timeout := args[1] + wait_for_any_data := args[2] + + if ClipWait(timeout, wait_for_any_data) + return FormatNoValueResponse() + else + return FormatResponse("ahk.message.TimeoutResponseMessage", "timed out waiting for clipboard data") + return FormatNoValueResponse() +} + +AHKBlockInput(args*) { + value := args[1] + BlockInput(value) + return FormatNoValueResponse() +} + +AHKMenuTrayTip(args*) { + value := args[1] + A_IconTip := value + return FormatNoValueResponse() +} + +AHKMenuTrayShow(args*) { + A_IconHidden := 0 + return FormatNoValueResponse() +} + +AHKMenuTrayHide(args*) { + A_IconHidden := 1 + return FormatNoValueResponse() +} + +AHKMenuTrayIcon(args*) { + filename := args[1] + icon_number := args[2] + freeze := args[3] + TraySetIcon(filename, icon_number, freeze) + return FormatNoValueResponse() +} + +;AHKGuiNew(args*) { +; +; options := args[1] +; title := args[2] +; Gui(New, options, title) +; return FormatResponse("ahk.message.StringResponseMessage", hwnd) +;} + +AHKMsgBox(args*) { + + options := args[1] + title := args[2] + text := args[3] + timeout := args[4] + if (timeout != "") { + options := "" options " T" timeout + } + res := MsgBox(text, title, options) + if (res = "Timeout") { + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "MsgBox timed out") + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", res) + } + return ret +} + +AHKInputBox(args*) { + + title := args[1] + prompt := args[2] + hide := args[3] + width := args[4] + height := args[5] + x := args[6] + y := args[7] + locale := args[8] + timeout := args[9] + default := args[10] + + ; TODO: support options correctly + options := "" + if (timeout != "") { + options .= "T" timeout + } + output := InputBox(prompt, title, options, default) + if (output.Result = "Timeout") { + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "Input box timed out") + } else if (output.Result = "Cancel") { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output.Value) + } + return ret +} + +AHKFileSelectFile(args*) { + + options := args[1] + root := args[2] + title := args[3] + filter := args[4] + output := FileSelect(options, root, title, filter) + if (output = "") { + ret := FormatNoValueResponse() + } else { + if IsObject(output) { + if (output.Length = 0) { + ret := FormatNoValueResponse() + } + else { + files := "" + for index, filename in output + if (A_Index != 1) { + files .= "`n" + } + files .= filename + ret := FormatResponse("ahk.message.StringResponseMessage", files) + } + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + } + return ret +} + +AHKFileSelectFolder(args*) { + + starting_folder := args[1] + options := args[2] + prompt := args[3] + + output := DirSelect(starting_folder, options, prompt) + + if (output = "") { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") + +b64decode(&pszString) { + ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw + ; [in] LPCSTR pszString, A pointer to a string that contains the formatted string to be converted. + ; [in] DWORD cchString, The number of characters of the formatted string to be converted, not including the terminating NULL character. If this parameter is zero, pszString is considered to be a null-terminated string. + ; [in] DWORD dwFlags, Indicates the format of the string to be converted. (see table in link above) + ; [in] BYTE *pbBinary, A pointer to a buffer that receives the returned sequence of bytes. If this parameter is NULL, the function calculates the length of the buffer needed and returns the size, in bytes, of required memory in the DWORD pointed to by pcbBinary. + ; [in, out] DWORD *pcbBinary, A pointer to a DWORD variable that, on entry, contains the size, in bytes, of the pbBinary buffer. After the function returns, this variable contains the number of bytes copied to the buffer. If this value is not large enough to contain all of the data, the function fails and GetLastError returns ERROR_MORE_DATA. + ; [out] DWORD *pdwSkip, A pointer to a DWORD value that receives the number of characters skipped to reach the beginning of the -----BEGIN ...----- header. If no header is present, then the DWORD is set to zero. This parameter is optional and can be NULL if it is not needed. + ; [out] DWORD *pdwFlags A pointer to a DWORD value that receives the flags actually used in the conversion. These are the same flags used for the dwFlags parameter. In many cases, these will be the same flags that were passed in the dwFlags parameter. If dwFlags contains one of the following flags, this value will receive a flag that indicates the actual format of the string. This parameter is optional and can be NULL if it is not needed. + + if (pszString = "") { + return "" + } + + cchString := StrLen(pszString) + + dwFlags := 0x00000001 ; CRYPT_STRING_BASE64: Base64, without headers. + getsize := 0 ; When this is NULL, the function returns the required size in bytes (for our first call, which is needed for our subsequent call) +; buff_size := 0 ; The function will write to this variable on our first call + pdwSkip := 0 ; We don't use any headers or preamble, so this is zero + pdwFlags := 0 ; We don't need this, so make it null + + ; The first call calculates the required size. The result is written to pbBinary + success := DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(pszString), "UInt", cchString, "UInt", dwFlags, "UInt", getsize, "UIntP", &buff_size := 0, "Int", pdwSkip, "Int", pdwFlags ) + if (success = 0) { + return "" + } + + ; We're going to give a pointer to a variable to the next call, but first we want to make the buffer the correct size using VarSetCapacity using the previous return value + ret := Buffer(buff_size) + + ; Now that we know the buffer size we need and have the variable's capacity set to the proper size, we'll pass a pointer to the variable for the decoded value to be written to + + success := DllCall( "Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(pszString), "UInt", cchString, "UInt", dwFlags, "Ptr", ret.Ptr, "UIntP", &buff_size, "Int", pdwSkip, "Int", pdwFlags ) + if (success=0) { + return "" + } + return StrGet(ret, "UTF-8") +} + + +b64encode(&data) { + ; REF: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptbinarytostringa + ; [in] const BYTE *pbBinary: A pointer to the array of bytes to be converted into a string. + ; [in] DWORD cbBinary: The number of elements in the pbBinary array. + ; [in] DWORD dwFlags: Specifies the format of the resulting formatted string (see table in REF) + ; [out, optional] LPSTR pszString: A pointer to the string, or null (0) to calculate size + ; [in, out] DWORD *pcchString: A pointer to a DWORD variable that contains the size, in TCHARs, of the pszString buffer + + cbBinary := data.Size + if (cbBinary = 0) { + return "" + } + dwFlags := 0x00000001 | 0x40000000 ; CRYPT_STRING_BASE64 + CRYPT_STRING_NOCRLF + + ; First step is to get the size so we can set the capacity of our return buffer correctly + success := DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", data, "UInt", cbBinary, "UInt", dwFlags, "Ptr", 0, "UIntP", &buff_size := 0) + if (success = 0) { + msg := Format("Problem converting data to base64 when calling CryptBinaryToString ({})", A_LastError) + throw Error(msg, -1) + } + + VarSetStrCapacity(&ret, buff_size * 2) + + ; Now we do the conversion to base64 and rteturn the string + + success := DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", data, "UInt", cbBinary, "UInt", dwFlags, "Str", ret, "UIntP", &buff_size) + if (success = 0) { + msg := Format("Problem converting data to base64 when calling CryptBinaryToString ({})", A_LastError) + throw Error(msg, -1) + } + return ret +} + + +CommandArrayFromQuery(text) { + decoded_commands := [] + encoded_array := StrSplit(text, "|") + function_name := encoded_array[1] + encoded_array.RemoveAt(1) + decoded_commands.push(function_name) + for index, encoded_value in encoded_array { + decoded_value := b64decode(&encoded_value) + decoded_commands.push(decoded_value) + } + return decoded_commands +} + +; BEGIN extension scripts +{% for ext in extensions %} +{{ ext.script_text }} + +{% endfor %} +; END extension scripts +{% block before_autoexecute %} +{% endblock before_autoexecute %} + +{% block autoexecute %} +stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] +stdout := FileOpen("*", "w", "UTF-8") +pyresp := "" + +Loop { + query := RTrim(stdin.ReadLine(), "`n") + if (query = "") { + ; Technically, this should only happen if the Python process has died, so sending a message is probably futile + ; But if this somehow triggers in some other case and the Python process is still listening, we'll try to have an informative error raised. + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", "Unexpected empty message; AHK exiting. This is likely a bug. Please report this issue at https://github.com/spyoungtech/ahk/issues") + stdout.Write(pyresp) + stdout.Read(0) + ; Exit to avoid leaving the process hanging around + ExitApp + } + argsArray := CommandArrayFromQuery(query) + try { + func_name := argsArray[1] + argsArray.RemoveAt(1) + {% block before_function %} + {% endblock before_function %} + pyresp := %func_name%(argsArray*) + {% block after_function %} + {% endblock after_function %} + } catch Any as e { + {% block function_error_handle %} + message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}`nStack:`n{}", e.what, e.line, e.message, e.extra, e.stack) + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) + {% endblock function_error_handle %} + } + {% block send_response %} + if (pyresp) { + stdout.Write(pyresp) + stdout.Read(0) + } else { + msg := FormatResponse("ahk.message.ExceptionResponseMessage", Format("Unknown Error when calling {}", func_name)) + stdout.Write(msg) + stdout.Read(0) + } + {% endblock send_response %} +} + +{% endblock autoexecute %} +{% endblock daemon_script %} + +""" + +HOTKEYS_SCRIPT_V2_TEMPLATE = r"""#Requires AutoHotkey >= 2.0- +{% for directive in directives %} +{% if directive.apply_to_hotkeys_process %} + +{{ directive }} +{% endif %} +{% endfor %} + + +KEEPALIVE := Chr(57344) + +stdout := FileOpen("*", "w", "UTF-8") +stdin := FileOpen("*", "r `n", "UTF-8") + +WriteStdout(s) { + global stdout + Critical "On" + stdout.Write(s) + stdout.Read(0) + Critical "Off" +} + +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") + +b64decode(&pszString) { + ; TODO load DLL globally for performance + ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw + ; [in] LPCSTR pszString, A pointer to a string that contains the formatted string to be converted. + ; [in] DWORD cchString, The number of characters of the formatted string to be converted, not including the terminating NULL character. If this parameter is zero, pszString is considered to be a null-terminated string. + ; [in] DWORD dwFlags, Indicates the format of the string to be converted. (see table in link above) + ; [in] BYTE *pbBinary, A pointer to a buffer that receives the returned sequence of bytes. If this parameter is NULL, the function calculates the length of the buffer needed and returns the size, in bytes, of required memory in the DWORD pointed to by pcbBinary. + ; [in, out] DWORD *pcbBinary, A pointer to a DWORD variable that, on entry, contains the size, in bytes, of the pbBinary buffer. After the function returns, this variable contains the number of bytes copied to the buffer. If this value is not large enough to contain all of the data, the function fails and GetLastError returns ERROR_MORE_DATA. + ; [out] DWORD *pdwSkip, A pointer to a DWORD value that receives the number of characters skipped to reach the beginning of the -----BEGIN ...----- header. If no header is present, then the DWORD is set to zero. This parameter is optional and can be NULL if it is not needed. + ; [out] DWORD *pdwFlags A pointer to a DWORD value that receives the flags actually used in the conversion. These are the same flags used for the dwFlags parameter. In many cases, these will be the same flags that were passed in the dwFlags parameter. If dwFlags contains one of the following flags, this value will receive a flag that indicates the actual format of the string. This parameter is optional and can be NULL if it is not needed. + + if (pszString = "") { + return "" + } + + cchString := StrLen(pszString) + + dwFlags := 0x00000001 ; CRYPT_STRING_BASE64: Base64, without headers. + getsize := 0 ; When this is NULL, the function returns the required size in bytes (for our first call, which is needed for our subsequent call) +; buff_size := 0 ; The function will write to this variable on our first call + pdwSkip := 0 ; We don't use any headers or preamble, so this is zero + pdwFlags := 0 ; We don't need this, so make it null + + ; The first call calculates the required size. The result is written to pbBinary + success := DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(pszString), "UInt", cchString, "UInt", dwFlags, "UInt", getsize, "UIntP", &buff_size := 0, "Int", pdwSkip, "Int", pdwFlags ) + if (success = 0) { + return "" + } + + ; We're going to give a pointer to a variable to the next call, but first we want to make the buffer the correct size using VarSetCapacity using the previous return value + ret := Buffer(buff_size) + + ; Now that we know the buffer size we need and have the variable's capacity set to the proper size, we'll pass a pointer to the variable for the decoded value to be written to + + success := DllCall( "Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(pszString), "UInt", cchString, "UInt", dwFlags, "Ptr", ret.Ptr, "UIntP", &buff_size, "Int", pdwSkip, "Int", pdwFlags ) + if (success=0) { + return "" + } + return StrGet(ret, "UTF-8") +} + + + +{% for hotkey in hotkeys %} + +{{ hotkey.keyname }}:: +{ + WriteStdout("{{ hotkey._id }}`n") + return +} +{% endfor %} + +{% for hotstring in hotstrings %} +{% if hotstring.replacement %} +:{{ hotstring.options }}:{{ hotstring.trigger }}:: + hostring_{{ hotstring._id }}_func(hs) { + replacement_b64 := "{{ hotstring._replacement_as_b64 }}" + replacement := b64decode(&replacement_b64) + Send(replacement) + } +{% else %} +:{{ hotstring.options }}:{{ hotstring.trigger }}:: + hostring_{{ hotstring._id }}_func(hs) { + WriteStdout("{{ hotstring._id }}`n") + } +{% endif %} + + +{% endfor %} + +{% if on_clipboard %} + + +ClipChanged(Type) { + CLIPBOARD_SENTINEL := Chr(57345) + ret := Format("{}{}`n", CLIPBOARD_SENTINEL, Type) + WriteStdout(ret) + return +} + +OnClipboardChange(ClipChanged) + +{% endif %} +SetTimer KeepAliveFunc, 2000 + +KeepAliveFunc() { + global stdin + global KEEPALIVE + WriteStdout(Format("{}`n", KEEPALIVE)) + alive_message := RTrim(stdin.ReadLine(), "`n") + if (alive_message != KEEPALIVE) { + ; The parent Python process has terminated unexpectedly + ; Exit to avoid leaving the hotkey process around + ExitApp + } + return +} + +""" diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py new file mode 100644 index 00000000..1aa52a81 --- /dev/null +++ b/ahk/_hotkey.py @@ -0,0 +1,465 @@ +from __future__ import annotations + +import atexit +import functools +import logging +import re +import subprocess +import sys +import tempfile +import threading +import time +import warnings +from abc import ABC +from abc import abstractmethod +from base64 import b64encode +from queue import Queue +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Literal +from typing import Optional +from typing import Protocol +from typing import runtime_checkable +from typing import Type +from typing import TypeVar +from typing import Union + +import jinja2 + +from ._constants import HOTKEYS_SCRIPT_TEMPLATE as _HOTKEY_SCRIPT +from ._constants import HOTKEYS_SCRIPT_V2_TEMPLATE as _HOTKEY_V2_SCRIPT +from .directives import Directive +from ahk._utils import hotkey_escape +from ahk._utils import try_remove + +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + + +P_HotkeyCallbackParam = ParamSpec('P_HotkeyCallbackParam') +T_HotkeyCallbackReturn = TypeVar('T_HotkeyCallbackReturn') + +_KEEPALIVE_SENTINEL = b'\xee\x80\x80' + +_CLIPBOARD_SENTINEL = '\ue001' + + +def _default_ex_handler(failure: Union[str, int], ex: Exception) -> None: + if isinstance(failure, str): + logging.error(f'Failure in hotkey/hotstring {failure!r}', exc_info=True) + elif isinstance(failure, int): + logging.error(f'Failure in clipboard callback {failure!r}', exc_info=True) + else: + logging.fatal(f'Ex handler called with bad value {failure!r}', exc_info=False) + raise TypeError(f'bad value for ex handler {failure!r}') from ex + + +class HotkeyTransportBase(ABC): + def __init__( + self, + executable_path: str, + default_ex_handler: Optional[Callable[[str, Exception], Any]] = None, + directives: Optional[list[Directive | Type[Directive]]] = None, + version: Optional[Literal['v1', 'v2']] = None, + ): + self._version = version + self._executable_path = executable_path + self._hotkeys: Dict[str, Hotkey] = {} + self._default_ex_handler: Callable[[str, Exception], Any] = default_ex_handler or _default_ex_handler + self._hotstrings: Dict[str, Hotstring] = {} + self._running: bool = False + self._get_callback_registry = functools.lru_cache(maxsize=None)(self._callback_registry_uncached) + self._clipboard_callback: Optional[Callable[[int], Any]] = None + self._clipboard_ex_handler: Optional[Callable[[int, Exception], Any]] = None + if directives is None: + directives = [] + self._directives: list[Directive | Type[Directive]] = [d for d in directives if d.apply_to_hotkeys_process] + + @property + def _callback_registry(self) -> Dict[str, Union[Hotkey, Hotstring]]: + return self._get_callback_registry() + + def _callback_registry_uncached(self) -> Dict[str, Union[Hotkey, Hotstring]]: + registry: Dict[str, Union[Hotkey, Hotstring]] = dict(self._hotkeys) + registry.update(self._hotstrings) + return registry + + @abstractmethod + def restart(self) -> Any: + return NotImplemented + + @abstractmethod + def start(self) -> Any: + return NotImplemented + + def add_hotkey(self, hotkey: Hotkey) -> None: + if hotkey._id in self._callback_registry: + warnings.warn('Hotkey was already registered! This action will remove the original entry.', stacklevel=2) + self._hotkeys[hotkey._id] = hotkey + self._get_callback_registry.cache_clear() + if self._running: + self.restart() + return None + + def add_hotstring(self, hotstring: Hotstring) -> None: + if hotstring._id in self._callback_registry: + warnings.warn('Hotstring was already registered! This action will remove the original entry.', stacklevel=2) + self._hotstrings[hotstring._id] = hotstring + self._get_callback_registry.cache_clear() + if self._running: + self.restart() + # TODO: add support for adding IfWinActive/IfWinExist + return None + + def remove_hotkey(self, hotkey: Hotkey) -> None: + if hotkey._id not in self._callback_registry: + raise ValueError(f'Hotkey {hotkey.keyname!r} is not registered') + del self._hotkeys[hotkey._id] + self._get_callback_registry.cache_clear() + if self._running: + self.restart() + return None + + def clear_hotkeys(self) -> None: + self._hotkeys.clear() + self._get_callback_registry.cache_clear() + if self._running: + self.restart() + return None + + def remove_hotstring(self, hotstring: Hotstring) -> None: + if hotstring._id not in self._callback_registry: + raise ValueError(f'Hostring {hotstring.trigger!r} is not registered') + del self._hotstrings[hotstring._id] + self._get_callback_registry.cache_clear() + if self._running: + self.restart() + return None + + def clear_hotstrings(self) -> None: + self._hotstrings.clear() + self._get_callback_registry.cache_clear() + if self._running: + self.restart() + return None + + def on_clipboard_change( + self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None + ) -> None: + self._clipboard_callback = callback + if ex_handler is not None: + self._clipboard_ex_handler = ex_handler + if self._running: + self.restart() + + +class STOP: ... + + +class ThreadedHotkeyTransport(HotkeyTransportBase): + def __init__( + self, + executable_path: str, + default_ex_handler: Optional[Callable[[str, Exception], Any]] = None, + directives: Optional[list[Directive | Type[Directive]]] = None, + version: Optional[Literal['v1', 'v2']] = None, + ): + super().__init__( + executable_path=executable_path, + default_ex_handler=default_ex_handler, + directives=directives, + version=version, + ) + self._callback_threads: List[threading.Thread] = [] + self._proc: Optional[subprocess.Popen[bytes]] = None + self._callback_queue: Queue[Union[str, Type[STOP]]] = Queue() + self._listener_thread: Optional[threading.Thread] = None + self._dispatcher_thread: Optional[threading.Thread] = None + loader: jinja2.BaseLoader + + if version is None or version == 'v1': + template_name = 'hotkeys.ahk' + const_script = _HOTKEY_SCRIPT + elif version == 'v2': + template_name = 'hotkeys-v2.ahk' + const_script = _HOTKEY_V2_SCRIPT + else: + raise ValueError(f'Invalid version {version!r}') + + try: + loader = jinja2.PackageLoader('ahk', 'templates') + except ValueError: + # see: https://github.com/spyoungtech/ahk/issues/201 + warnings.warn( + 'Jinja could not find templates with PackageLoader. Falling back to BaseLoader', + category=UserWarning, + ) + loader = jinja2.BaseLoader() + self._jinja_env: jinja2.Environment = jinja2.Environment(loader=loader, autoescape=False) + self._template: jinja2.Template + try: + self._template = self._jinja_env.get_template(template_name) + except jinja2.TemplateNotFound: + warnings.warn('hotkey template not found, falling back to constant', category=UserWarning) + self._template = self._jinja_env.from_string(const_script) + + def _do_callback( + self, + hotkey_or_clip_change_type: Union[str, int], + cb: Callable[[], Any], + ex_handler: Optional[Union[Callable[[str, Exception], Any], Callable[[int, Exception], Any]]] = None, + ) -> None: + if ex_handler is None: + ex_handler = self._default_ex_handler + try: + cb() + except Exception as cb_exc: + ex_handler(hotkey_or_clip_change_type, cb_exc) # type: ignore[arg-type] + return None + + def start(self) -> None: + self._callback_queue.empty() + assert self._running is False, 'Already running!' + assert self._listener_thread is None, 'Listener is already active!' + assert self._dispatcher_thread is None, 'Dispatcher is already active!' + self._running = True + listener_thread = threading.Thread(target=self.listener, daemon=True) + self._listener_thread = listener_thread + listener_thread.start() + dispatcher_thread = threading.Thread(target=self.dispatcher, daemon=True) + self._dispatcher_thread = dispatcher_thread + dispatcher_thread.start() + + def stop(self) -> None: + assert self._running is True, 'Not running! Must be started first!' + assert self._dispatcher_thread is not None + for i in range(1, 11): + if self._proc is not None: + break + logging.debug(f'stop called before dispatched has started proc. Waiting for proc ({i}/10)') + time.sleep(0.1) + assert self._proc is not None + self._running = False + + self._callback_queue.empty() + self._callback_queue.put_nowait(STOP) + logging.debug('Waiting for stop...') + if self._dispatcher_thread is not None: + try: + self._dispatcher_thread.join(timeout=3) + except TimeoutError: + logging.debug('DISPATCHER JOIN TIMED OUT!') + self._dispatcher_thread = None + logging.debug('Waiting for callback stop...') + self._callback_queue.join() + if self._listener_thread is not None: + try: + self._listener_thread.join(timeout=3) + except TimeoutError: + logging.debug('LISTENER JOIN TIMED OUT!') + self._listener_thread = None + self._proc.kill() + + def restart(self) -> None: + self.stop() + self.start() + + def dispatcher(self) -> None: + while True: + ex_handler: Union[Callable[[str, Exception], Any], Callable[[int, Exception], Any]] + cb: Union[Callable[[], Any], Callable[[int], Any]] + job = self._callback_queue.get() + if job is STOP: + self._callback_queue.task_done() + break + assert isinstance(job, str) + if job.startswith(_CLIPBOARD_SENTINEL): + assert self._clipboard_callback is not None + clip_change_type = int(job.lstrip(_CLIPBOARD_SENTINEL)) + callback = self._clipboard_callback + + def f() -> None: + callback(clip_change_type) + + cb = f + if self._clipboard_ex_handler is not None: + ex_handler = self._clipboard_ex_handler + else: + ex_handler = _default_ex_handler + elif job not in self._callback_registry: + logging.warning(f'Received request to dispatch unregistered hotkey: {job!r}. Ignoring.') + self._callback_queue.task_done() + continue + else: + hot_thing: Union[Hotstring, Hotkey] = self._callback_registry[job] + assert hot_thing.callback is not None + cb = hot_thing.callback + assert hot_thing.ex_handler is not None + ex_handler = hot_thing.ex_handler + assert cb is not None + assert ex_handler is not None + t = threading.Thread(target=self._do_callback, args=(job, cb, ex_handler), daemon=True) + self._callback_threads.append(t) + t.start() + self._callback_queue.task_done() # maybe _do_callback should handle this? + + def _render_hotkey_template(self) -> str: + if self._clipboard_callback is not None: + on_clipboard = True + else: + on_clipboard = False + ret = self._template.render( + hotkeys=list(self._hotkeys.values()), + hotstrings=self._hotstrings.values(), + on_clipboard=on_clipboard, + directives=self._directives, + ) + return ret + + def listener(self) -> None: + hotkey_script_contents = self._render_hotkey_template() + logging.debug('hotkey script contents:\n%s', hotkey_script_contents) + with tempfile.NamedTemporaryFile( + mode='w', prefix='python-ahk-hotkeys-', suffix='.ahk', delete=False, encoding='utf-8' + ) as f: + f.write(hotkey_script_contents) + atexit.register(try_remove, f.name) + self._proc = subprocess.Popen( + [self._executable_path, '/CP65001', '/ErrorStdOut', f.name], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + atexit.register(kill, self._proc) + assert self._proc.stdout is not None + assert self._proc.stdin is not None + while self._running: + line = self._proc.stdout.readline() + if line.rstrip(b'\n') == _KEEPALIVE_SENTINEL: + logging.debug('keepalive received') + self._proc.stdin.write(b'\xee\x80\x80\n') + self._proc.stdin.flush() + continue + if not line.strip(): + logging.debug('Listener: Process probably died, exiting') + break + logging.debug(f'Received {line!r}') + self._callback_queue.put_nowait(line.decode('UTF-8').strip()) + # although redundant with the atexit handler, this will prevent + # excessive use of disk space in cases where the hotkey process is [re]started many times + try_remove(f.name) + + +class Hotkey: + def __init__( + self, keyname: str, callback: Callable[[], Any], *, ex_handler: Optional[Callable[[str, Exception], Any]] = None + ): + self._keyname: str = keyname + self.callback: Callable[[], Any] = callback + self.ex_handler: Callable[[str, Exception], Any] = ex_handler or _default_ex_handler + self._validate() + + @property + def keyname(self) -> str: + return self._keyname + + def __hash__(self) -> int: + return hash(self.keyname) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Hotkey): + return NotImplemented + return hash(self) == hash(other) + + def _validate(self) -> None: + assert '\n' not in self.keyname, 'Newlines not allowed in hotkey trigger keys' + return None + + @property + def _id(self) -> str: + return str(hash(self)) + + +class Hotstring: + def __init__( + self, + trigger: str, + replacement_or_callback: Union[str, Callable[[], Any]], + *, + ex_handler: Optional[Callable[[str, Exception], Any]] = None, + options: str = '', + ): + self.replacement: Optional[str] + self.callback: Optional[Callable[[], Any]] + self.ex_handler: Optional[Callable[[str, Exception], Any]] + self._trigger: str = hotkey_escape(trigger) + self._options: str = options + if callable(replacement_or_callback): + self.replacement = None + self.callback = replacement_or_callback + self.ex_handler = ex_handler or _default_ex_handler + else: + if not isinstance(replacement_or_callback, str): + raise TypeError('Expected callable or str for hotstring') + if ex_handler is not None: + raise TypeError( + 'ex_handler may only be specified when a callable is used. Must be None when using string replacement.' + ) + assert isinstance(replacement_or_callback, str) + self.replacement = replacement_or_callback + self.callback = None + self.ex_handler = None + self._validate() + + @property + def options(self) -> str: + return self._options + + @property + def trigger(self) -> str: + return self._trigger + + def __hash__(self) -> int: + return hash(self.trigger) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Hotstring): + return NotImplemented + return hash(self) == hash(other) + + @property + def _id(self) -> str: + return str(hash(self)).replace('-', '0') + + @property + def _replacement_as_b64(self) -> str: + assert self.replacement is not None + data = bytes(self.replacement, 'UTF-8') + return str(b64encode(data), 'UTF-8') + + def _validate(self) -> None: + if not isinstance(self.trigger, str): + raise TypeError(f'trigger must be a string. Got {self.trigger!r}') + if self.options: + assert '\n' not in self.options, 'Newlines not allowed in options' + assert 'x' not in self.options.lower(), 'X is not an allowed option. Use a callback instead.' + assert re.fullmatch( + r'(\*|\?|C|C1|K\d+|O|P\d+|S[IPE]|T|Z)+', self.options.upper() + ), f'Invalid options: {self.options!r}' + return None + + +@runtime_checkable +class Killable(Protocol): + def kill(self) -> None: ... + + +def kill(proc: Killable) -> None: + try: + proc.kill() + except: # noqa + pass diff --git a/ahk/_sync/__init__.py b/ahk/_sync/__init__.py new file mode 100644 index 00000000..b2a72267 --- /dev/null +++ b/ahk/_sync/__init__.py @@ -0,0 +1,5 @@ +from .engine import AHK +from .window import Control +from .window import Window + +__all__ = ['AHK', 'Window', 'Control'] diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py new file mode 100644 index 00000000..fef493ef --- /dev/null +++ b/ahk/_sync/engine.py @@ -0,0 +1,3963 @@ +from __future__ import annotations + +import asyncio +import os +import sys +import tempfile +import time +import warnings +from functools import partial +from typing import Any +from typing import Awaitable +from typing import Callable +from typing import Coroutine +from typing import Generic +from typing import List +from typing import Literal +from typing import NoReturn +from typing import Optional +from typing import overload +from typing import Tuple +from typing import Type +from typing import TypeVar +from typing import Union + +from .transport import DaemonProcessTransport +from .transport import FutureResult +from .transport import Transport +from .window import Control +from .window import Window +from ahk._hotkey import Hotkey +from ahk._hotkey import Hotstring +from ahk._types import _BUTTONS +from ahk._types import Coordinates +from ahk._types import CoordModeRelativeTo +from ahk._types import CoordModeTargets +from ahk._types import MouseButton +from ahk._types import Position +from ahk._types import SendMode +from ahk._types import TitleMatchMode +from ahk._utils import _get_executable_major_version +from ahk._utils import _resolve_executable_path +from ahk._utils import MsgBoxButtons +from ahk._utils import MsgBoxDefaultButton +from ahk._utils import MsgBoxIcon +from ahk._utils import MsgBoxModality +from ahk._utils import MsgBoxOtherOptions +from ahk._utils import type_escape +from ahk.directives import Directive +from ahk.extensions import _extension_registry +from ahk.extensions import _ExtensionMethodRegistry +from ahk.extensions import _resolve_extensions +from ahk.extensions import Extension +from ahk.keys import Key + +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + +sleep = time.sleep + +SyncFilterFunc: TypeAlias = Callable[[Window], bool] + +SyncPropertyReturnTupleIntInt: TypeAlias = Coordinates + +SyncPropertyReturnOptionalAsyncWindow: TypeAlias = Optional[Window] + +_PROPERTY_DEPRECATION_WARNING_MESSAGE = 'Use of the {0} property is not recommended (in the async API only) and may be removed in a future version. Use the get_{0} method instead' + + +def _resolve_button(button: Union[str, int]) -> str: + """ + Resolve a string of a button name to a canonical name used for AHK script + :param button: + :type button: str + :return: + """ + if isinstance(button, str): + button = button.lower() + + if button in _BUTTONS: + resolved_button = _BUTTONS[button] + elif isinstance(button, int) and button > 3: + # for addtional mouse buttons + resolved_button = f'X{button - 3}' + else: + assert isinstance(button, str) + resolved_button = button + return resolved_button + + +T_AHKVersion = TypeVar('T_AHKVersion', bound=Optional[Literal['v1', 'v2']]) + + +class AHK(Generic[T_AHKVersion]): + # fmt: off + @overload + def __init__(self: AHK[None], *, TransportClass: Optional[Type[Transport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', extensions: list[Extension] | None | Literal['auto'] = None): ... + @overload + def __init__(self: AHK[None], *, TransportClass: Optional[Type[Transport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', extensions: list[Extension] | None | Literal['auto'] = None, version: None): ... + @overload + def __init__(self: AHK[Literal['v2']], *, TransportClass: Optional[Type[Transport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', extensions: list[Extension] | None | Literal['auto'] = None, version: Literal['v2']): ... + @overload + def __init__(self: AHK[Literal['v1']], *, TransportClass: Optional[Type[Transport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', extensions: list[Extension] | None | Literal['auto'] = None, version: Literal['v1']): ... + # fmt: on + def __init__( + self: AHK[Optional[Literal['v1', 'v2']]], + *, + TransportClass: Optional[Type[Transport]] = None, + directives: Optional[list[Directive | Type[Directive]]] = None, + executable_path: str = '', + extensions: list[Extension] | None | Literal['auto'] = None, + version: Optional[Literal['v1', 'v2']] = None, + ): + if version not in (None, 'v1', 'v2'): + raise ValueError(f'Invalid version ({version!r}). Must be one of None, "v1", or "v2"') + executable_path = _resolve_executable_path(executable_path=executable_path, version=version) + skip_version_check = False + if version is None: + try: + version = _get_executable_major_version(executable_path) + except Exception as e: + warnings.warn( + f'Could not detect AHK version ({e}). This is likely caused by a misconfigured AutoHotkey executable and will likely cause a fatal error later on.\nAssuming v1 for now.' + ) + version = 'v1' + skip_version_check = True + + if not skip_version_check: + detected_version = _get_executable_major_version(executable_path) + if version != detected_version: + raise RuntimeError( + f'AutoHotkey {version} was requested but AutoHotkey {detected_version} was detected for executable {executable_path}' + ) + self._version: Literal['v1', 'v2'] = version + self._extension_registry: _ExtensionMethodRegistry + self._extensions: list[Extension] + if extensions == 'auto': + self._extensions = [ext for ext in _extension_registry if ext._requires in (None, version)] + else: + self._extensions = _resolve_extensions(extensions) if extensions else [] + for ext in self._extensions: + if ext._requires not in (None, version): + raise ValueError( + f'Incompatible extension detected. Extension requires AutoHotkey {ext._requires} but current version is {version}' + ) + self._method_registry = _ExtensionMethodRegistry( + sync_methods={}, async_methods={}, async_window_methods={}, sync_window_methods={} + ) + for ext in self._extensions: + self._method_registry.merge(ext._extension_method_registry) + if TransportClass is None: + TransportClass = DaemonProcessTransport + assert TransportClass is not None + transport = TransportClass( + executable_path=executable_path, directives=directives, extensions=self._extensions, version=version + ) + self._transport: Transport = transport + + def __repr__(self) -> str: + return f'<{self.__module__}.{self.__class__.__qualname__} object version={self._version!r}>' + + def __getattr__(self, name: str) -> Callable[..., Any]: + is_async = False + if is_async: + if name in self._method_registry.async_methods: + method = self._method_registry.async_methods[name] + return partial(method, self) + else: + if name in self._method_registry.sync_methods: + method = self._method_registry.sync_methods[name] + return partial(method, self) + + raise AttributeError(f'{self.__class__.__name__!r} object has no attribute {name!r}') + + def _get_window_extension_method(self, name: str) -> Callable[..., Any] | None: + is_async = False + if is_async: + if name in self._method_registry.async_window_methods: + method = self._method_registry.async_window_methods[name] + return method + else: + if name in self._method_registry.sync_window_methods: + method = self._method_registry.sync_window_methods[name] + return method + return None + + def add_hotkey( + self, keyname: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None + ) -> None: + """ + Register a function to be called when a hotkey is pressed. + + Key notes: + + - You must call the `start_hotkeys` method for the hotkeys to be active + - All hotkeys run in a single AHK process instance (but Python callbacks also get their own thread and can run simultaneously) + - If you add a hotkey after the hotkey thread/instance is active, it will be restarted automatically + - `async` functions are not directly supported as callbacks, however you may write a synchronous function that calls `asyncio.run`/`loop.create_task` etc. + + :param keyname: the key trigger for the hotkey, such as ``#n`` (win+n) + :param callback: callback function to call when the hotkey is triggered + :param ex_handler: a function which accepts two parameters: the keyname for the hotkey and the exception raised by the callback function. + :return: + """ + hotkey = Hotkey(keyname, callback, ex_handler=ex_handler) + with warnings.catch_warnings(record=True) as caught_warnings: + self._transport.add_hotkey(hotkey=hotkey) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return None + + def function_call(self, function_name: str, args: list[str] | None = None, blocking: bool = True) -> Any: + """ + Call an AHK function defined in the daemon script. This method is intended for use by extension authors. + """ + if args is None: + args = [] + return self._transport.function_call(function_name, args, blocking=blocking, engine=self) # type: ignore[call-overload] + + def add_hotstring( + self, + trigger: str, + replacement_or_callback: Union[str, Callable[[], Any]], + ex_handler: Optional[Callable[[str, Exception], Any]] = None, + options: str = '', + ) -> None: + """ + Register a hotstring, e.g., `::btw::by the way` + + Key notes: + + - You must call the `start_hotkeys` method for registered hotstrings to be active + - All hotstrings (and hotkeys) run in a single AHK process instance separate from other AHK processes. + + :param trigger: the trigger phrase for the hotstring, e.g., ``btw`` + :param replacement_or_callback: the replacement phrase (e.g., ``by the way``) or a Python callable to execute in response to the hotstring trigger + :param ex_handler: a function which accepts two parameters: the hotstring and the exception raised by the callback function. + :param options: the hotstring options -- same meanings as in AutoHotkey. + :return: + """ + hotstring = Hotstring(trigger, replacement_or_callback, ex_handler=ex_handler, options=options) + with warnings.catch_warnings(record=True) as caught_warnings: + self._transport.add_hotstring(hotstring=hotstring) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return None + + def remove_hotkey(self, keyname: str) -> None: + def _() -> None: + return None + + h = Hotkey(keyname=keyname, callback=_) # XXX: this can probably be avoided + self._transport.remove_hotkey(hotkey=h) + return None + + def clear_hotkeys(self) -> None: + self._transport.clear_hotkeys() + return None + + def remove_hotstring(self, trigger: str) -> None: + hs = Hotstring(trigger=trigger, replacement_or_callback='') # XXX: this can probably be avoided + self._transport.remove_hotstring(hs) + return None + + def clear_hotstrings(self) -> None: + self._transport.clear_hotstrings() + return None + + def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> None: + """ + Sets the default title match mode + + Does not affect methods called with ``blocking=True`` (because these run in a separate AHK process) + + Reference: https://www.autohotkey.com/docs/commands/SetTitleMatchMode.htm + + :param title_match_mode: the match mode (and/or match speed) to set as the default title match mode. Can be 1, 2, 3, 'Regex', 'Fast', 'Slow' or a tuple of these. + :return: None + """ + + args = [] + if isinstance(title_match_mode, tuple): + (match_mode, match_speed) = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + self._transport.function_call('AHKSetTitleMatchMode', args) + return None + + def get_title_match_mode(self) -> str: + """ + Get the title match mode. + + I.E. the current value of ``A_TitleMatchMode`` + + """ + resp = self._transport.function_call('AHKGetTitleMatchMode') + return resp + + def get_title_match_speed(self) -> str: + """ + Get the title match mode speed. + + I.E. the current value of ``A_TitleMatchModeSpeed`` + + """ + resp = self._transport.function_call('AHKGetTitleMatchSpeed') + return resp + + def set_coord_mode(self, target: CoordModeTargets, relative_to: CoordModeRelativeTo = 'Screen') -> None: + """ + Analog of `CoordMode `_ + """ + args = [str(target), str(relative_to)] + self._transport.function_call('AHKSetCoordMode', args) + return None + + def get_coord_mode(self, target: CoordModeTargets) -> str: + """ + Analog for ``A_CoordMode`` + """ + args = [str(target)] + resp = self._transport.function_call('AHKGetCoordMode', args) + return resp + + def set_send_mode(self, mode: SendMode) -> None: + """ + Analog for `SendMode `_ + """ + args = [str(mode)] + self._transport.function_call('AHKSetSendMode', args) + return None + + def get_send_mode(self) -> str: + resp = self._transport.function_call('AHKGetSendMode') + return resp + + # fmt: off + @overload + def control_click(self, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def control_click(self, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def control_click(self, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def control_click(self, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def control_click( + self, + button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', + click_count: int = 1, + options: str = '', + control: str = '', + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `ControlClick `_ + """ + args = [control, title, text, str(button), str(click_count), options, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = self._transport.function_call('AHKControlClick', args=args, blocking=blocking) + + return resp + + # fmt: off + @overload + def control_get_text(self, *, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> str: ... + @overload + def control_get_text(self, *, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def control_get_text(self, *, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + @overload + def control_get_text(self, *, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def control_get_text( + self, + *, + control: str = '', + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[str, FutureResult[str]]: + """ + Analog for `ControlGetText `_ + """ + args = [control, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = self._transport.function_call('AHKControlGetText', args, blocking=blocking) + return resp + + # fmt: off + @overload + def control_get_position(self, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Position: ... + @overload + def control_get_position(self, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Position]: ... + @overload + def control_get_position(self, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Position: ... + @overload + def control_get_position(self, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[Position, FutureResult[Position]]: ... + # fmt: on + def control_get_position( + self, + control: str = '', + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[Position, FutureResult[Position]]: + """ + Analog to `ControlGetPos `_ + """ + args = [control, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + + resp = self._transport.function_call('AHKControlGetPos', args, blocking=blocking) + return resp + + # fmt: off + @overload + def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def control_send( + self, + keys: str, + control: str = '', + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `ControlSend `_ + """ + args = [control, keys, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = self._transport.function_call('AHKControlSend', args, blocking=blocking) + return resp + + # TODO: raw option for control_send + + def start_hotkeys(self) -> None: + """ + Start the Autohotkey process for triggering hotkeys + + """ + return self._transport.start_hotkeys() + + def stop_hotkeys(self) -> None: + """ + Stop the Autohotkey process for triggering hotkeys/hotstrings + + """ + return self._transport.stop_hotkeys() + + def set_detect_hidden_windows(self, value: bool) -> None: + """ + Analog for `DetectHiddenWindows `_ + + :param value: The setting value. ``True`` to turn on hidden window detection, ``False`` to turn it off. + """ + + if value not in (True, False): + raise TypeError(f'detect hidden windows must be a boolean, got object of type {type(value)}') + args = [] + if value is True: + args.append('1') + else: + args.append('0') + self._transport.function_call('AHKSetDetectHiddenWindows', args=args) + return None + + @staticmethod + def _format_win_args( + title: str, + text: str, + exclude_title: str, + exclude_text: str, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + ) -> List[str]: + args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + return args + + # fmt: off + @overload + def list_windows(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> List[Window]: ... + @overload + def list_windows(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[List[Window]]: ... + @overload + def list_windows(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[Window]: ... + @overload + def list_windows(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[List[Window], FutureResult[List[Window]]]: ... + # fmt: on + def list_windows( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[List[Window], FutureResult[List[Window]]]: + """ + Enumerate all windows matching the criteria. + + Analog for `WinGet List subcommand _` + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWindowList', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[True]) -> Coordinates: ... + @overload + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[False]) -> FutureResult[Coordinates]: ... + @overload + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None) -> Coordinates: ... + @overload + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True) -> Union[Coordinates, FutureResult[Coordinates]]: ... + # fmt: on + def get_mouse_position( + self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True + ) -> Union[Coordinates, FutureResult[Coordinates]]: + """ + Analog for `MouseGetPos `_ + """ + if coord_mode: + args = [str(coord_mode)] + else: + args = [] + resp = self._transport.function_call('AHKMouseGetPos', args, blocking=blocking) + return resp + + @property + def mouse_position(self) -> SyncPropertyReturnTupleIntInt: + """ + Convenience property for :py:meth:`get_mouse_position` + + Setter accepts a tuple of x,y coordinates passed to :py:meth:`mouse_mouse` + """ + return self.get_mouse_position() + + @mouse_position.setter + def mouse_position(self, new_position: Tuple[int, int]) -> None: + """ + Convenience setter for ``mouse_move`` + + :param new_position: a tuple of x,y coordinates to move to + """ + x, y = new_position + return self.mouse_move(x=x, y=y, speed=0, relative=False) + + # fmt: off + @overload + def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Union[str, int]] = None, *, speed: Optional[int] = None, relative: bool = False, send_mode: Optional[SendMode] = None, coord_mode: Optional[CoordModeRelativeTo] = None) -> None: ... + @overload + def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Union[str, int]] = None, *, blocking: Literal[True], speed: Optional[int] = None, relative: bool = False, send_mode: Optional[SendMode] = None, coord_mode: Optional[CoordModeRelativeTo] = None) -> None: ... + @overload + def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Union[str, int]] = None, *, blocking: Literal[False], speed: Optional[int] = None, relative: bool = False, send_mode: Optional[SendMode] = None, coord_mode: Optional[CoordModeRelativeTo] = None) -> FutureResult[None]: ... + @overload + def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Union[str, int]] = None, *, speed: Optional[int] = None, relative: bool = False, blocking: bool = True, send_mode: Optional[SendMode] = None, coord_mode: Optional[CoordModeRelativeTo] = None) -> Union[None, FutureResult[None]]: ... + # fmt: on + def mouse_move( + self, + x: Optional[Union[str, int]] = None, + y: Optional[Union[str, int]] = None, + *, + speed: Optional[int] = None, + relative: bool = False, + send_mode: Optional[SendMode] = None, + blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `MouseMove `_ + """ + if relative and (x is None or y is None): + x = x or 0 + y = y or 0 + elif not relative and (x is None or y is None): + posx, posy = self.get_mouse_position() + x = x or posx + y = y or posy + + if speed is None: + speed = 2 + args = [str(x), str(y), str(speed)] + if relative: + args.append('R') + else: + args.append('') + if send_mode: + args.append(send_mode) + else: + args.append('') + + if coord_mode: + args.append(coord_mode) + else: + args.append('') + + resp = self._transport.function_call('AHKMouseMove', args, blocking=blocking) + return resp + + def a_run_script(self, *args: Any, **kwargs: Any) -> Union[str, FutureResult[str]]: + """ + Deprecated. Use :py:meth:`run_script` instead. + """ + warnings.warn('a_run_script is deprecated. Use run_script instead.', DeprecationWarning, stacklevel=2) + return self.run_script(*args, **kwargs) + + # fmt: off + @overload + def get_active_window(self) -> Optional[Window]: ... + @overload + def get_active_window(self, blocking: Literal[True]) -> Optional[Window]: ... + @overload + def get_active_window(self, blocking: Literal[False]) -> FutureResult[Optional[Window]]: ... + @overload + def get_active_window(self, blocking: bool = True) -> Union[Optional[Window], FutureResult[Optional[Window]]]: ... + # fmt: on + def get_active_window( + self: AHK[Any], blocking: bool = True + ) -> Union[Optional[Window], FutureResult[Optional[Window]], FutureResult[Window]]: + """ + Gets the currently active window. + """ + return self.win_get( + title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking + ) + + # Ideally, this would be type-hinted for the AHK version. But we cant: https://github.com/python/mypy/issues/9937 + @property + def active_window(self) -> SyncPropertyReturnOptionalAsyncWindow: + """ + Gets the currently active window. Convenience property for :py:meth:`get_active_window` + """ + return self.get_active_window() + + def find_windows( + self, + func: Optional[SyncFilterFunc] = None, + *, + title_match_mode: Optional[TitleMatchMode] = None, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + exact: Optional[bool] = None, + ) -> List[Window]: + if exact is not None and title_match_mode is not None: + raise TypeError('exact and match_mode parameters are mutually exclusive') + if exact is not None: + warnings.warn('exact parameter is deprecated. Use title_match_mode instead', stacklevel=2) + if exact: + title_match_mode = (3, 'Fast') + else: + title_match_mode = (1, 'Fast') + elif title_match_mode is None: + title_match_mode = (1, 'Fast') + + windows = self.list_windows( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + ) + if func is None: + return windows + else: + ret: List[Window] = [] + for win in windows: + match = func(win) + if match: + ret.append(win) + return ret + + def find_windows_by_class( + self, class_name: str, *, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> List[Window]: + with warnings.catch_warnings(record=True) as caught_warnings: + ret = self.find_windows( + title=f'ahk_class {class_name}', title_match_mode=title_match_mode, exact=exact + ) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return ret + + def find_windows_by_text( + self, text: str, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> List[Window]: + with warnings.catch_warnings(record=True) as caught_warnings: + ret = self.find_windows(text=text, exact=exact, title_match_mode=title_match_mode) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return ret + + def find_windows_by_title( + self, title: str, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> List[Window]: + with warnings.catch_warnings(record=True) as caught_warnings: + ret = self.find_windows(title=title, exact=exact, title_match_mode=title_match_mode) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return ret + + def find_window( + self, + func: Optional[SyncFilterFunc] = None, + *, + title_match_mode: Optional[TitleMatchMode] = None, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + exact: Optional[bool] = None, + ) -> Optional[Window]: + with warnings.catch_warnings(record=True) as caught_warnings: + windows = self.find_windows( + func, + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + exact=exact, + title_match_mode=title_match_mode, + ) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return windows[0] if windows else None + + def find_window_by_class( + self, class_name: str, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> Optional[Window]: + with warnings.catch_warnings(record=True) as caught_warnings: + windows = self.find_windows_by_class( + class_name=class_name, exact=exact, title_match_mode=title_match_mode + ) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return windows[0] if windows else None + + def find_window_by_text( + self, text: str, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> Optional[Window]: + with warnings.catch_warnings(record=True) as caught_warnings: + windows = self.find_windows_by_text(text=text, exact=exact, title_match_mode=title_match_mode) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return windows[0] if windows else None + + def find_window_by_title( + self, title: str, exact: Optional[bool] = None, title_match_mode: Optional[TitleMatchMode] = None + ) -> Optional[Window]: + with warnings.catch_warnings(record=True) as caught_warnings: + windows = self.find_windows_by_title(title=title, exact=exact, title_match_mode=title_match_mode) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return windows[0] if windows else None + + def get_volume(self, device_number: int = 1) -> float: + """ + Analog for `SoundGetWaveVolume `_ + """ + args = [str(device_number)] + response = self._transport.function_call('AHKGetVolume', args) + return response + + # fmt: off + @overload + def key_down(self, key: Union[str, Key]) -> None: ... + @overload + def key_down(self, key: Union[str, Key], *, blocking: Literal[True]) -> None: ... + @overload + def key_down(self, key: Union[str, Key], *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def key_down(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def key_down(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, FutureResult[None]]: + """ + Shortcut for :py:meth:`send_input` but transforms specified key to perform a key "DOWN" only (no release) + """ + if isinstance(key, str): + key = Key(key_name=key) + if blocking: + self.send_input(key.DOWN, blocking=True) + return None + else: + return self.send_input(key.DOWN, blocking=False) + + # fmt: off + @overload + def key_press(self, key: Union[str, Key], *, release: bool = True) -> None: ... + @overload + def key_press(self, key: Union[str, Key], *, blocking: Literal[True], release: bool = True) -> None: ... + @overload + def key_press(self, key: Union[str, Key], *, blocking: Literal[False], release: bool = True) -> FutureResult[None]: ... + @overload + def key_press(self, key: Union[str, Key], *, release: bool = True, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def key_press( + self, key: Union[str, Key], *, release: bool = True, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + """ + Press (and release) a key. Sends `:py:meth:`key_down` then, if ``release`` is ``True`` (the default), sends + :py:meth:`key_up` subsequently. + """ + if blocking: + self.key_down(key, blocking=True) + if release: + self.key_up(key, blocking=True) + return None + else: + d = self.key_down(key, blocking=False) + if release: + return self.key_up(key, blocking=False) + return d + + # fmt: off + @overload + def key_release(self, key: Union[str, Key]) -> None: ... + @overload + def key_release(self, key: Union[str, Key], *, blocking: Literal[True]) -> None: ... + @overload + def key_release(self, key: Union[str, Key], *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, FutureResult[None]]: + """ + Alias for :py:meth:`key_up` + """ + if blocking: + self.key_up(key=key, blocking=True) + return None + else: + return self.key_up(key=key, blocking=False) + + # fmt: off + @overload + def key_state(self, key_name: str, *, mode: Optional[Literal['T', 'P']] = None) -> Union[float, int, str, None]: ... + @overload + def key_state(self, key_name: str, *, mode: Optional[Literal['T', 'P']] = None, blocking: Literal[True]) -> Union[float, int, str, None]: ... + @overload + def key_state(self, key_name: str, *, mode: Optional[Literal['T', 'P']] = None, blocking: Literal[False]) -> Union[FutureResult[str], FutureResult[int], FutureResult[float], FutureResult[None]]: ... + @overload + def key_state(self, key_name: str, *, mode: Optional[Literal['T', 'P']] = None, blocking: bool = True) -> Union[None, FutureResult[None], Union[str, FutureResult[str]], Union[int, FutureResult[int]], Union[float, FutureResult[float]]]: ... + # fmt: on + def key_state( + self, key_name: str, *, mode: Optional[Literal['T', 'P']] = None, blocking: bool = True + ) -> Union[ + int, + float, + str, + None, + FutureResult[str], + FutureResult[int], + FutureResult[float], + FutureResult[None], + ]: + """ + Analog for `GetKeyState `_ + """ + args: List[str] = [key_name] + if mode is not None: + if mode not in ('T', 'P'): + raise ValueError(f'Invalid value for mode parameter. Mode must be `T` or `P`. Got {mode!r}') + args.append(mode) + else: + args.append('') + resp = self._transport.function_call('AHKKeyState', args, blocking=blocking) + return resp + + # fmt: off + @overload + def key_up(self, key: Union[str, Key]) -> None: ... + @overload + def key_up(self, key: Union[str, Key], *, blocking: Literal[True]) -> None: ... + @overload + def key_up(self, key: Union[str, Key], *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, FutureResult[None]]: + """ + Shortcut for :py:meth:`send_input` but transforms specified key to perform a key "UP" only. Useful if the key + was previously pressed down but not released. + """ + if isinstance(key, str): + key = Key(key_name=key) + if blocking: + self.send_input(key.UP, blocking=True) + return None + else: + return self.send_input(key.UP, blocking=False) + + # fmt: off + @overload + def key_wait(self, key_name: str, *, timeout: Optional[int | float] = None, logical_state: bool = False, released: bool = False) -> bool: ... + @overload + def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int | float] = None, logical_state: bool = False, released: bool = False) -> bool: ... + @overload + def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int | float] = None, logical_state: bool = False, released: bool = False) -> FutureResult[bool]: ... + @overload + def key_wait(self, key_name: str, *, timeout: Optional[int | float] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + # fmt: on + def key_wait( + self, + key_name: str, + *, + timeout: Optional[int | float] = None, + logical_state: bool = False, + released: bool = False, + blocking: bool = True, + ) -> Union[bool, FutureResult[bool]]: + """ + Analog for `KeyWait `_ + """ + options = '' + if not released: + options += 'D' + if logical_state: + options += 'L' + if timeout is not None: + assert timeout >= 0, 'Timeout value must be non-negative' + options += f'T{timeout}' + args = [key_name, options] + + resp = self._transport.function_call('AHKKeyWait', args, blocking=blocking) + return resp + + def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, FutureResult[str]]: + """ + Run an AutoHotkey script. + Can either be a path to a script (``.ahk``) file or a string containing script contents + """ + return self._transport.run_script(script_text_or_path, blocking=blocking, timeout=timeout) + + def set_send_level(self, level: int) -> None: + """ + Analog for `SendLevel `_ + """ + if not isinstance(level, int): + raise TypeError('level must be an integer between 0 and 100') + if not 0 <= level <= 100: + raise ValueError('level value must be between 0 and 100') + args = [str(level)] + self._transport.function_call('AHKSetSendLevel', args) + + def get_send_level(self) -> int: + """ + Get the current `SendLevel `_ + (I.E. the value of ``A_SendLevel``) + """ + resp = self._transport.function_call('AHKGetSendLevel') + return resp + + # fmt: off + @overload + def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, send_mode: Optional[SendMode] = None, blocking: Literal[True]) -> None: ... + @overload + def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, send_mode: Optional[SendMode] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, send_mode: Optional[SendMode] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def send( + self, + s: str, + *, + raw: bool = False, + key_delay: Optional[int] = None, + key_press_duration: Optional[int] = None, + send_mode: Optional[SendMode] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `Send `_ + """ + args = [s] + if key_delay: + args.append(str(key_delay)) + else: + args.append('') + if key_press_duration: + args.append(str(key_press_duration)) + else: + args.append('') + if send_mode: + args.append(send_mode) + else: + args.append('') + + if raw: + raw_resp = self._transport.function_call('AHKSendRaw', args=args, blocking=blocking) + return raw_resp + else: + resp = self._transport.function_call('AHKSend', args=args, blocking=blocking) + return resp + + # fmt: off + @overload + def send_raw(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None) -> None: ... + @overload + def send_raw(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[True]) -> None: ... + @overload + def send_raw(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def send_raw(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def send_raw( + self, + s: str, + *, + key_delay: Optional[int] = None, + key_press_duration: Optional[int] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `SendRaw `_ + """ + resp = self.send( + s, raw=True, key_delay=key_delay, key_press_duration=key_press_duration, blocking=blocking + ) + return resp + + # fmt: off + @overload + def send_input(self, s: str) -> None: ... + @overload + def send_input(self, s: str, *, blocking: Literal[True]) -> None: ... + @overload + def send_input(self, s: str, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def send_input(self, s: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def send_input(self, s: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + """ + Analog for `SendInput `_ + """ + args = [s, '', ''] + resp = self._transport.function_call('AHKSendInput', args, blocking=blocking) + return resp + + # fmt: off + @overload + def type(self, s: str) -> None: ... + @overload + def type(self, s: str, *, blocking: Literal[True]) -> None: ... + @overload + def type(self, s: str, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def type(self, s: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def type(self, s: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + """ + Like :py:meth:`send_input` but performs necessary escapes for you. + """ + resp = self.send_input(type_escape(s), blocking=blocking) + return resp + + # fmt: off + @overload + def send_play(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None) -> None: ... + @overload + def send_play(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[True]) -> None: ... + @overload + def send_play(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def send_play(self, s: str, *, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def send_play( + self, + s: str, + *, + key_delay: Optional[int] = None, + key_press_duration: Optional[int] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `SendPlay `_ + """ + args = [s] + if key_delay: + args.append(str(key_delay)) + else: + args.append('') + if key_press_duration: + args.append(str(key_press_duration)) + else: + args.append('') + + resp = self._transport.function_call('AHKSendPlay', args=args, blocking=blocking) + return resp + + # fmt: off + @overload + def set_capslock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + @overload + def set_capslock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + @overload + def set_capslock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def set_capslock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def set_capslock_state( + self, + state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, + *, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `SetCapsLockState `_ + """ + args: List[str] = [] + if state is not None: + if str(state).lower() not in ('1', '0', 'on', 'off', 'alwayson', 'alwaysoff'): + raise ValueError( + f'Invalid value for state. Must be one of On, Off, AlwaysOn, AlwaysOff or None. Got {state!r}' + ) + if state is True: + state = 'On' + elif state is False: + state = 'Off' + + args.append(str(state)) + else: + args.append('') + + resp = self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) + return resp + + # fmt: off + @overload + def set_numlock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + @overload + def set_numlock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + @overload + def set_numlock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def set_numlock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def set_numlock_state( + self, + state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, + *, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `SetCapsLockState `_ + """ + args: List[str] = [] + if state is not None: + if str(state).lower() not in ('1', '0', 'on', 'off', 'alwayson', 'alwaysoff'): + raise ValueError( + f'Invalid value for state. Must be one of On, Off, AlwaysOn, AlwaysOff or None. Got {state!r}' + ) + if state is True: + state = 'On' + elif state is False: + state = 'Off' + + args.append(str(state)) + else: + args.append('') + + resp = self._transport.function_call('AHKSetNumLockState', args, blocking=blocking) + return resp + + # fmt: off + @overload + def set_scroll_lock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + @overload + def set_scroll_lock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + @overload + def set_scroll_lock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def set_scroll_lock_state(self, state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def set_scroll_lock_state( + self, + state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, + *, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `SetCapsLockState `_ + """ + args: List[str] = [] + if state is not None: + if str(state).lower() not in ('1', '0', 'on', 'off', 'alwayson', 'alwaysoff'): + raise ValueError( + f'Invalid value for state. Must be one of On, Off, AlwaysOn, AlwaysOff or None. Got {state!r}' + ) + if state is True: + state = 'On' + elif state is False: + state = 'Off' + + args.append(str(state)) + else: + args.append('') + + resp = self._transport.function_call('AHKSetScrollLockState', args, blocking=blocking) + return resp + + # fmt: off + @overload + def set_volume(self, value: int, device_number: int = 1) -> None: ... + @overload + def set_volume(self, value: int, device_number: int = 1, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def set_volume(self, value: int, device_number: int = 1, *, blocking: Literal[True]) -> None: ... + @overload + def set_volume(self, value: int, device_number: int = 1, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def set_volume( + self, value: int, device_number: int = 1, *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + """ + Analog for `SoundSetWaveVolume `_ + """ + args = [str(device_number), str(value)] + return self._transport.function_call('AHKSetVolume', args, blocking=blocking) + + # fmt: off + + # in v2 the "second" parameter is not supported + @overload + def show_traytip(self: AHK[Literal['v2']], title: str, text: str, second: None = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_traytip(self: AHK[Literal['v2']], title: str, text: str, second: None = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_traytip(self: AHK[Literal['v2']], title: str, text: str, second: None = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_traytip(self: AHK[Literal['v2']], title: str, text: str, second: None = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + + @overload + def show_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def show_traytip( + self, + title: str, + text: str, + second: Optional[float] = None, + type_id: int = 1, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `TrayTip `_ + """ + if second is None: + second = 1.0 + else: + if self._version == 'v2': + warnings.warn( + 'supplying seconds is not supported when using AutoHotkey v2. This parameter will be ignored' + ) + + option = type_id + (16 if silent else 0) + (32 if large_icon else 0) + args = [title, text, str(second), str(option)] + return self._transport.function_call('AHKTrayTip', args, blocking=blocking) + + # fmt: off + @overload + def show_error_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_error_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_error_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_error_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + + @overload + def show_error_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_error_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_error_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_error_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + + # fmt: on + def show_error_traytip( + self: AHK[Any], + title: str, + text: str, + second: Optional[float] = None, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Convenience method for :py:meth:`show_traytip` for error-style messages + """ + return self.show_traytip( + title=title, text=text, second=second, type_id=3, silent=silent, large_icon=large_icon, blocking=blocking + ) + + # fmt: off + @overload + def show_info_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_info_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_info_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_info_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + + @overload + def show_info_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_info_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_info_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_info_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def show_info_traytip( + self: AHK[Any], + title: str, + text: str, + second: Optional[float] = None, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Convenience method for :py:meth:`show_traytip` for info-style messages + """ + return self.show_traytip( + title=title, text=text, second=second, type_id=1, silent=silent, large_icon=large_icon, blocking=blocking + ) + + # fmt: off + @overload + def show_warning_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_warning_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_warning_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_warning_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + + @overload + def show_warning_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_warning_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_warning_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_warning_traytip(self: Union[AHK[Literal['v1']], AHK[None]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def show_warning_traytip( + self: AHK[Any], + title: str, + text: str, + second: Optional[float] = None, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Convenience method for :py:meth:`show_traytip` for warning-style messages + """ + return self.show_traytip( + title=title, text=text, second=second, type_id=2, silent=silent, large_icon=large_icon, blocking=blocking + ) + + def show_tooltip( + self, + text: str = '', + x: Optional[int] = None, + y: Optional[int] = None, + which: int = 1, + ) -> None: + """ + Analog for `ToolTip `_ + """ + if which not in range(1, 21): + raise ValueError('which must be an integer between 1 and 20') + args = [text] + if x is not None: + args.append(str(x)) + else: + args.append('') + if y is not None: + args.append(str(y)) + else: + args.append('') + args.append(str(which)) + self._transport.function_call('AHKShowToolTip', args) + + def hide_tooltip(self, which: int = 1) -> None: + self.show_tooltip(which=which) + + def menu_tray_tooltip(self, value: str) -> None: + """ + Change the menu tray icon tooltip that appears when hovering the mouse over the tray icon. + Does not affect tray icon for AHK processes started with :py:meth:`run_script` or ``blocking=False`` + + Uses the `Tip subcommand `_ + """ + + args = [value] + self._transport.function_call('AHKMenuTrayTip', args) + return None + + def menu_tray_icon(self, filename: str = '*', icon_number: int = 1, freeze: Optional[bool] = None) -> None: + """ + Change the tray icon menu. + Does not affect tray icon for AHK processes started with :py:meth:`run_script` or ``blocking=False`` + + Uses the `Icon subcommand `_ + + If called with no parameters, the tray icon will be reset to the original default. + """ + args = [filename, str(icon_number)] + if freeze is True: + args.append('1') + elif freeze is False: + args.append('0') + self._transport.function_call('AHKMenuTrayIcon', args) + return None + + def menu_tray_icon_show(self) -> None: + """ + Show ('unhide') the tray icon previously hidden by :py:class:`~ahk.directives.NoTrayIcon` directive. + Does not affect tray icon for AHK processes started with :py:meth:`run_script` or ``blocking=False`` + """ + self._transport.function_call('AHKMenuTrayShow') + return None + + def menu_tray_icon_hide(self) -> None: + """ + hides the tray icon. + Does not affect tray icon for AHK processes started with :py:meth:`run_script` or ``blocking=False`` + """ + self._transport.function_call('AHKMenuTrayHide') + return None + + # fmt: off + @overload + def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: ... + @overload + def sound_beep(self, frequency: int = 523, duration: int = 150, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def sound_beep(self, frequency: int = 523, duration: int = 150, *, blocking: Literal[True]) -> None: ... + @overload + def sound_beep(self, frequency: int = 523, duration: int = 150, *, blocking: bool = True) -> Optional[FutureResult[None]]: ... + # fmt: on + def sound_beep( + self, frequency: int = 523, duration: int = 150, *, blocking: bool = True + ) -> Optional[FutureResult[None]]: + """ + Analog for `SoundBeep `_ + """ + args = [str(frequency), str(duration)] + self._transport.function_call('AHKSoundBeep', args, blocking=blocking) + return None + + # fmt: off + @overload + def sound_get(self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME') -> str: ... + @overload + def sound_get(self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME', *, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def sound_get(self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME', *, blocking: Literal[True]) -> str: ... + @overload + def sound_get(self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME', *, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def sound_get( + self, + device_number: int = 1, + component_type: str = 'MASTER', + control_type: str = 'VOLUME', + *, + blocking: bool = True, + ) -> Union[str, FutureResult[str]]: + """ + Analog for `SoundGet `_ + """ + args = [str(device_number), component_type, control_type] + return self._transport.function_call('AHKSoundGet', args, blocking=blocking) + + # fmt: off + @overload + def sound_play(self, filename: str) -> None: ... + @overload + def sound_play(self, filename: str, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def sound_play(self, filename: str, *, blocking: Literal[True]) -> None: ... + @overload + def sound_play(self, filename: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def sound_play(self, filename: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + """ + Analog for `SoundPlay `_ + """ + return self._transport.function_call('AHKSoundPlay', [filename], blocking=blocking) + + def sound_set( + self, + value: Union[str, int, float], + device_number: int = 1, + component_type: str = 'MASTER', + control_type: str = 'VOLUME', + *, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `SoundSet `_ + """ + args = [str(device_number), component_type, control_type, str(value)] + return self._transport.function_call('AHKSoundSet', args, blocking=blocking) + + # fmt: off + @overload + def win_get(self: AHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Window: ... + @overload + def win_get(self: AHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Window]: ... + @overload + def win_get(self: AHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Window: ... + @overload + def win_get(self: AHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[Window, FutureResult[Window]]: ... + + @overload + def win_get(self: Union[AHK[Literal['v1']], AHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[Window, None]: ... + @overload + def win_get(self: Union[AHK[Literal['v1']], AHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Union[Window, None]]: ... + @overload + def win_get(self: Union[AHK[Literal['v1']], AHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[Window, None]: ... + @overload + def win_get(self: Union[AHK[Literal['v1']], AHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[Window, None, FutureResult[Union[None, Window]], FutureResult[Window]]: ... + # fmt: on + def win_get( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[Window, None, FutureResult[Union[None, Window]], FutureResult[Window]]: + """ + Analog for `WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetID', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> str: ... + @overload + def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + @overload + def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def win_get_text( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[str, FutureResult[str]]: + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetText', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> str: ... + @overload + def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + @overload + def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def win_get_title( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[str, FutureResult[str]]: + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetTitle', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_get_class(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> str: ... + @overload + def win_get_class(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def win_get_class(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + @overload + def win_get_class(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def win_get_class( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[str, FutureResult[str]]: + """ + Analog for `WinGetClass `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetClass', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_get_position(self: AHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Position: ... + @overload + def win_get_position(self: AHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Position]: ... + @overload + def win_get_position(self: AHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Position: ... + @overload + def win_get_position(self: AHK[Literal['v2']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[Position, FutureResult[Position]]: ... + + @overload + def win_get_position(self: Union[AHK[Literal['v1']], AHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[Position, None]: ... + @overload + def win_get_position(self: Union[AHK[Literal['v1']], AHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Union[Position, None]]: ... + @overload + def win_get_position(self: Union[AHK[Literal['v1']], AHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[Position, None]: ... + @overload + def win_get_position(self: Union[AHK[Literal['v1']], AHK[None]], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[Position, None, FutureResult[Union[Position, None]]]: ... + # fmt: on + def win_get_position( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[Position, None, FutureResult[Union[Position, None]], FutureResult[Position]]: + """ + Analog for `WinGetPos `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetPos', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[Window, None]: ... + @overload + def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Union[Window, None]]: ... + @overload + def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[Window, None]: ... + @overload + def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[Window, None, FutureResult[Union[Window, None]]]: ... + # fmt: on + def win_get_idlast( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[Window, None, FutureResult[Union[Window, None]]]: + """ + Like the IDLast subcommand for WinGet + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetIDLast', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[int, None]: ... + @overload + def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Union[int, None]]: ... + @overload + def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... + @overload + def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[int, None, FutureResult[Union[int, None]]]: ... + # fmt: on + def win_get_pid( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[int, None, FutureResult[Union[int, None]]]: + """ + Get a window by process ID. + + Like the pid subcommand for WinGet + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetPID', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[str, None]: ... + @overload + def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Union[str, None]]: ... + @overload + def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... + @overload + def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, str, FutureResult[Optional[str]]]: ... + # fmt: on + def win_get_process_name( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, str, FutureResult[Optional[str]]]: + """ + Get the process name of a window + + Analog for `ProcessName subcommand for WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetProcessName', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[str, None]: ... + @overload + def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Union[str, None]]: ... + @overload + def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... + @overload + def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[str, None, Union[None, str, FutureResult[Optional[str]]]]: ... + # fmt: on + def win_get_process_path( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[str, None, Union[None, str, FutureResult[Optional[str]]]]: + """ + Get the process path for a window. + + Analog for the `ProcessPath subcommand for WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetProcessPath', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> int: ... + @overload + def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[int]: ... + @overload + def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> int: ... + @overload + def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[int, FutureResult[int]]: ... + # fmt: on + def win_get_count( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[int, FutureResult[int]]: + """ + Analog for the `Count subcommand for WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetCount', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[int, None]: ... + @overload + def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Union[int, None]]: ... + @overload + def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... + @overload + def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, int, FutureResult[Optional[int]]]: ... + # fmt: on + def win_get_minmax( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, int, FutureResult[Optional[int]]]: + """ + Analog for the `MinMax subcommand for WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetMinMax', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[List[Control], None]: ... + @overload + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Union[List[Control], None]]: ... + @overload + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[List[Control], None]: ... + @overload + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[List[Control], None, FutureResult[Optional[List[Control]]]]: ... + # fmt: on + def win_get_control_list( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[List[Control], None, FutureResult[Optional[List[Control]]]]: + """ + Analog for the `ControlList subcommand for WinGet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinGetControlList', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + def win_get_from_mouse_position(self) -> Union[Window, None]: ... + @overload + def win_get_from_mouse_position(self, *, blocking: Literal[False]) -> FutureResult[Union[Window, None]]: ... + @overload + def win_get_from_mouse_position(self, *, blocking: Literal[True]) -> Union[Window, None]: ... + @overload + def win_get_from_mouse_position(self, *, blocking: bool = True) -> Union[Optional[Window], FutureResult[Optional[Window]]]: ... + # fmt: on + def win_get_from_mouse_position( + self, *, blocking: bool = True + ) -> Union[Optional[Window], FutureResult[Optional[Window]]]: + resp = self._transport.function_call('AHKWinFromMouse', blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> bool: ... + @overload + def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[bool]: ... + @overload + def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + @overload + def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + # fmt: on + def win_exists( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[bool, FutureResult[bool]]: + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinExist', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_activate(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_activate(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_activate(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_activate(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_activate( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinActivate `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinActivate', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_set_title( + self, + new_title: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinSetTitle `_ + """ + args = [new_title, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = self._transport.function_call('AHKWinSetTitle', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_set_always_on_top( + self, + toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `AlwaysOnTop subcommand of WinSet `_ + """ + args = [str(toggle), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = self._transport.function_call('AHKWinSetAlwaysOnTop', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_set_bottom( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `Bottom subcommand of WinSet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinSetBottom', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_set_top( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `Top subcommand of WinSet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinSetTop', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_set_disable( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `Disable subcommand of WinSet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinSetDisable', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_set_enable( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `Enable subcommand of WinSet `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinSetEnable', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_set_redraw( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `Redraw subcommand of WinSet `_ + """ + + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinSetRedraw', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> bool: ... + @overload + def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[bool]: ... + @overload + def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + @overload + def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + # fmt: on + def win_set_style( + self, + style: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[bool, FutureResult[bool]]: + """ + Analog for `Style subcommand of WinSet `_ + """ + + args = [style, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = self._transport.function_call('AHKWinSetStyle', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> bool: ... + @overload + def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[bool]: ... + @overload + def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + @overload + def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + # fmt: on + def win_set_ex_style( + self, + style: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[bool, FutureResult[bool]]: + """ + Analog for `ExStyle subcommand of WinSet `_ + """ + args = [style, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = self._transport.function_call('AHKWinSetExStyle', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> bool: ... + @overload + def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[bool]: ... + @overload + def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + @overload + def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + # fmt: on + def win_set_region( + self, + options: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[bool, FutureResult[bool]]: + """ + Analog for `Region subcommand of WinSet `_ + """ + args = [options, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = self._transport.function_call('AHKWinSetRegion', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_set_transparent( + self, + transparency: Union[int, Literal['Off']], + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `Transparent subcommand of WinSet `_ + """ + args = [str(transparency), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = self._transport.function_call('AHKWinSetTransparent', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_set_trans_color( + self, + color: Union[int, str], + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `TransColor subcommand of WinSet `_ + """ + args = [str(color), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('1') + elif detect_hidden_windows is False: + args.append('0') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + else: + args.append('') + if title_match_mode is not None: + if isinstance(title_match_mode, tuple): + match_mode, match_speed = title_match_mode + elif title_match_mode in (1, 2, 3, 'RegEx'): + match_mode = title_match_mode + match_speed = '' + elif title_match_mode in ('Fast', 'Slow'): + match_mode = '' + match_speed = title_match_mode + else: + raise ValueError( + f"Invalid value for title_match_mode argument. Expected 1, 2, 3, 'RegEx', 'Fast', 'Slow' or a tuple of these. Got {title_match_mode!r}" + ) + args.append(str(match_mode)) + args.append(str(match_speed)) + else: + args.append('') + args.append('') + resp = self._transport.function_call('AHKWinSetTransColor', args, blocking=blocking) + return resp + + # alias for backwards compatibility + windows = list_windows + + # fmt: off + @overload + def right_click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + def right_click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: Literal[True], coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + def right_click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: Literal[False], coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> FutureResult[None]: ... + @overload + def right_click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> Union[None, FutureResult[None]]: ... + # fmt: on + def right_click( + self, + x: Optional[Union[int, Tuple[int, int]]] = None, + y: Optional[int] = None, + click_count: Optional[int] = None, + direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, + *, + relative: Optional[bool] = None, + blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, + send_mode: Optional[SendMode] = None, + ) -> Union[None, FutureResult[None]]: + button = 'R' + return self.click( + x, + y, + button=button, + click_count=click_count, + direction=direction, + relative=relative, + blocking=blocking, + coord_mode=coord_mode, + send_mode=send_mode, + ) + + # fmt: off + @overload + def click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + def click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: Literal[True], coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + def click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: Literal[False], coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> FutureResult[None]: ... + @overload + def click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, click_count: Optional[int] = None, direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, *, relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> Union[None, FutureResult[None]]: ... + # fmt: on + def click( + self, + x: Optional[Union[int, Tuple[int, int]]] = None, + y: Optional[int] = None, + button: Optional[Union[MouseButton, str]] = None, + click_count: Optional[int] = None, + direction: Optional[Literal['U', 'D', 'Up', 'Down']] = None, + *, + relative: Optional[bool] = None, + blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, + send_mode: Optional[SendMode] = None, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `Click `_ + """ + if x or y: + if y is None and isinstance(x, tuple) and len(x) == 2: + # allow position to be specified by a two-sequence tuple + x, y = x + assert x is not None and y is not None, 'If provided, position must be specified by x AND y' + if button is None: + button = 'L' + button = _resolve_button(button) + + if relative: + r = 'Rel' + else: + r = '' + if coord_mode is None: + coord_mode = '' + if send_mode is None: + send_mode = '' + args = [str(x), str(y), button, str(click_count), direction or '', r, coord_mode, str(send_mode)] + resp = self._transport.function_call('AHKClick', args, blocking=blocking) + return resp + + # fmt: off + @overload + def image_search(self, image_path: str, upper_bound: Tuple[Union[int, str], Union[int, str]] = (0, 0), lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, *, color_variation: Optional[int] = None, coord_mode: Optional[CoordModeRelativeTo] = None, scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None) -> Optional[Coordinates]: ... + @overload + def image_search(self, image_path: str, upper_bound: Tuple[Union[int, str], Union[int, str]] = (0, 0), lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, *, color_variation: Optional[int] = None, coord_mode: Optional[CoordModeRelativeTo] = None, scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[False]) -> FutureResult[Optional[Coordinates]]: ... + @overload + def image_search(self, image_path: str, upper_bound: Tuple[Union[int, str], Union[int, str]] = (0, 0), lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, *, color_variation: Optional[int] = None, coord_mode: Optional[CoordModeRelativeTo] = None, scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[True]) -> Optional[Coordinates]: ... + @overload + def image_search(self, image_path: str, upper_bound: Tuple[Union[int, str], Union[int, str]] = (0, 0), lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, *, color_variation: Optional[int] = None, coord_mode: Optional[CoordModeRelativeTo] = None, scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: bool = True) -> Union[Coordinates, None, FutureResult[Optional[Coordinates]]]: ... + # fmt: on + def image_search( + self, + image_path: str, + upper_bound: Tuple[Union[int, str], Union[int, str]] = (0, 0), + lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, + *, + color_variation: Optional[int] = None, + coord_mode: Optional[CoordModeRelativeTo] = None, + scale_height: Optional[int] = None, + scale_width: Optional[int] = None, + transparent: Optional[str] = None, + icon: Optional[int] = None, + blocking: bool = True, + ) -> Union[Coordinates, None, FutureResult[Optional[Coordinates]]]: + """ + Analog for `ImageSearch `_ + """ + + if scale_height and not scale_width: + scale_width = -1 + elif scale_width and not scale_height: + scale_height = -1 + + options: List[Union[str, int]] = [] + if icon: + options.append(f'Icon{icon}') + if color_variation is not None: + options.append(color_variation) + if transparent is not None: + options.append(f'Trans{transparent}') + if scale_width: + options.append(f'w{scale_width}') + options.append(f'h{scale_height}') + + x1, y1 = upper_bound + if lower_bound: + x2, y2 = lower_bound + else: + x2, y2 = ('A_ScreenWidth', 'A_ScreenHeight') + + args = [str(x1), str(y1), str(x2), str(y2)] + if options: + opts = ' '.join(f'*{opt}' for opt in options) + args.append(opts + f' {image_path}') + else: + args.append(image_path) + + if coord_mode is not None: + args.append(coord_mode) + else: + args.append('') + + resp = self._transport.function_call('AHKImageSearch', args, blocking=blocking) + return resp + + # fmt: off + @overload + def mouse_drag(self, x: int, y: int, *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, relative: Optional[bool] = None, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> None: ... + @overload + def mouse_drag(self, x: int, y: int, *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, relative: Optional[bool] = None, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def mouse_drag(self, x: int, y: int, *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, relative: Optional[bool] = None, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None, blocking: Literal[True]) -> None: ... + @overload + def mouse_drag(self, x: int, y: int, *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, button: Optional[Union[MouseButton, str]] = None, relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, send_mode: Optional[SendMode] = None) -> Union[None, FutureResult[None]]: ... + # fmt: on + def mouse_drag( + self, + x: int, + y: int, + *, + from_position: Optional[Tuple[int, int]] = None, + speed: Optional[int] = None, + button: Optional[Union[MouseButton, str]] = None, + relative: Optional[bool] = None, + blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, + send_mode: Optional[SendMode] = None, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `MouseClickDrag `_ + """ + if button is None: + button = 'Left' + else: + button = _resolve_button(button) + if from_position: + x1, y1 = from_position + args = [str(button), str(x1), str(y1), str(x), str(y)] + else: + args = [str(button), '', '', str(x), str(y)] + + if speed: + args.append(str(speed)) + else: + args.append('') + + if relative: + args.append('R') + else: + args.append('') + + if coord_mode: + args.append(coord_mode) + else: + args.append('') + + if send_mode: + args.append(send_mode) + else: + args.append('') + + resp = self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) + return resp + + # fmt: off + @overload + def pixel_get_color(self, x: int, y: int, *, coord_mode: Optional[CoordModeRelativeTo] = None, alt: bool = False, slow: bool = False, rgb: bool = True) -> str: ... + @overload + def pixel_get_color(self, x: int, y: int, *, coord_mode: Optional[CoordModeRelativeTo] = None, alt: bool = False, slow: bool = False, rgb: bool = True, blocking: Literal[True]) -> str: ... + @overload + def pixel_get_color(self, x: int, y: int, *, coord_mode: Optional[CoordModeRelativeTo] = None, alt: bool = False, slow: bool = False, rgb: bool = True, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def pixel_get_color(self, x: int, y: int, *, coord_mode: Optional[CoordModeRelativeTo] = None, alt: bool = False, slow: bool = False, rgb: bool = True, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def pixel_get_color( + self, + x: int, + y: int, + *, + coord_mode: Optional[CoordModeRelativeTo] = None, + alt: bool = False, + slow: bool = False, + rgb: bool = True, + blocking: bool = True, + ) -> Union[str, FutureResult[str]]: + """ + Analog for `PixelGetColor `_ + """ + args = [str(x), str(y), coord_mode or ''] + + options = ' '.join(word for word, val in zip(('Alt', 'Slow', 'RGB'), (alt, slow, rgb)) if val) + args.append(options) + + resp = self._transport.function_call('AHKPixelGetColor', args, blocking=blocking) + return resp + + # fmt: off + @overload + def pixel_search(self, search_region_start: Tuple[int, int], search_region_end: Tuple[int, int], color: Union[str, int], variation: int = 0, *, coord_mode: Optional[CoordModeRelativeTo] = None, fast: bool = True, rgb: bool = True) -> Optional[Coordinates]: ... + @overload + def pixel_search(self, search_region_start: Tuple[int, int], search_region_end: Tuple[int, int], color: Union[str, int], variation: int = 0, *, coord_mode: Optional[CoordModeRelativeTo] = None, fast: bool = True, rgb: bool = True, blocking: Literal[True]) -> Optional[Coordinates]: ... + @overload + def pixel_search(self, search_region_start: Tuple[int, int], search_region_end: Tuple[int, int], color: Union[str, int], variation: int = 0, *, coord_mode: Optional[CoordModeRelativeTo] = None, fast: bool = True, rgb: bool = True, blocking: Literal[False]) -> FutureResult[Optional[Coordinates]]: ... + @overload + def pixel_search(self, search_region_start: Tuple[int, int], search_region_end: Tuple[int, int], color: Union[str, int], variation: int = 0, *, coord_mode: Optional[CoordModeRelativeTo] = None, fast: bool = True, rgb: bool = True, blocking: bool = True) -> Union[Optional[Coordinates], FutureResult[Optional[Coordinates]]]: ... + # fmt: on + def pixel_search( + self, + search_region_start: Tuple[int, int], + search_region_end: Tuple[int, int], + color: Union[str, int], + variation: int = 0, + *, + coord_mode: Optional[CoordModeRelativeTo] = None, + fast: bool = True, + rgb: bool = True, + blocking: bool = True, + ) -> Union[Optional[Coordinates], FutureResult[Optional[Coordinates]]]: + """ + Analog for `PixelSearch `_ + """ + x1, y1 = search_region_start + x2, y2 = search_region_end + args = [str(x1), str(y1), str(x2), str(y2), str(color), str(variation)] + mode = ' '.join(word for word, val in zip(('Fast', 'RGB'), (fast, rgb)) if val) + args.append(mode) + args.append(coord_mode or '') + resp = self._transport.function_call('AHKPixelSearch', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_close(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_close(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_close(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_close(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_close( + self, + title: str = '', + text: str = '', + seconds_to_wait: Optional[int] = None, + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinClose `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(seconds_to_wait) if seconds_to_wait else '') + + resp = self._transport.function_call('AHKWinClose', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + def win_kill(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_kill(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[None, FutureResult[None]]: ... + @overload + def win_kill(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_kill(self, title: str = '', text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_kill( + self, + title: str = '', + text: str = '', + seconds_to_wait: Optional[int] = None, + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinKill `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(seconds_to_wait) if seconds_to_wait else '') + + resp = self._transport.function_call('AHKWinKill', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + def win_minimize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_minimize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[None, FutureResult[None]]: ... + @overload + def win_minimize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_minimize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_minimize( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinMinimize `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinMinimize', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + def win_maximize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_maximize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[None, FutureResult[None]]: ... + @overload + def win_maximize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_maximize(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_maximize( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinMaximize `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinMaximize', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + def win_restore(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_restore(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[None, FutureResult[None]]: ... + @overload + def win_restore(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_restore(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_restore( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinRestore `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinRestore', args, engine=self, blocking=blocking) + return resp + + # fmt: off + @overload + def win_wait(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None) -> Window: ... + @overload + def win_wait(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[False]) -> FutureResult[Window]: ... + @overload + def win_wait(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[True]) -> Window: ... + @overload + def win_wait(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: bool = True) -> Union[Window, FutureResult[Window]]: ... + # fmt: on + def win_wait( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + timeout: Optional[int] = None, + blocking: bool = True, + ) -> Union[Window, FutureResult[Window]]: + """ + Analog for `WinWait `_ + """ + if not title and not text and not exclude_title and not exclude_text: + raise ValueError( + 'Expected non-blank value for at least one of the following: title, text, exclude_title, exclude_text' + ) + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(timeout) if timeout else '') + resp = self._transport.function_call('AHKWinWait', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + def win_wait_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None) -> Window: ... + @overload + def win_wait_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[False]) -> FutureResult[Window]: ... + @overload + def win_wait_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[True]) -> Window: ... + @overload + def win_wait_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: bool = True) -> Union[Window, FutureResult[Window]]: ... + # fmt: on + def win_wait_active( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + timeout: Optional[int] = None, + blocking: bool = True, + ) -> Union[Window, FutureResult[Window]]: + """ + Analog for `WinWaitActive `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(timeout) if timeout else '') + resp = self._transport.function_call('AHKWinWaitActive', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + def win_wait_not_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None) -> Window: ... + @overload + def win_wait_not_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[False]) -> FutureResult[Window]: ... + @overload + def win_wait_not_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[True]) -> Window: ... + @overload + def win_wait_not_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: bool = True) -> Union[Window, FutureResult[Window]]: ... + # fmt: on + def win_wait_not_active( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + timeout: Optional[int] = None, + blocking: bool = True, + ) -> Union[Window, FutureResult[Window]]: + """ + Analog for `WinWaitNotActive `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(timeout) if timeout else '') + resp = self._transport.function_call('AHKWinWaitNotActive', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + def win_wait_close(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None) -> None: ... + @overload + def win_wait_close(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_wait_close(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: Literal[True]) -> None: ... + @overload + def win_wait_close(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, timeout: Optional[int] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_wait_close( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + timeout: Optional[int] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinWaitClose `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(timeout) if timeout else '') + resp = self._transport.function_call('AHKWinWaitClose', args, blocking=blocking, engine=self) + return resp + + # fmt: off + @overload + def win_show(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_show(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_show(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_show(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_show( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinShow `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinShow', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_hide(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_hide(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_hide(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_hide(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_hide( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinHide `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinHide', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_is_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> bool: ... + @overload + def win_is_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + @overload + def win_is_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[bool]: ... + @overload + def win_is_active(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + # fmt: on + def win_is_active( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[bool, FutureResult[bool]]: + """ + Check if a window is active. + + Uses `WinActive `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + resp = self._transport.function_call('AHKWinIsActive', args, blocking=blocking) + return resp + + # fmt: off + @overload + def win_move(self, x: int, y: int, *, width: Optional[int] = None, height: Optional[int] = None, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + @overload + def win_move(self, x: int, y: int, *, width: Optional[int] = None, height: Optional[int] = None, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def win_move(self, x: int, y: int, *, width: Optional[int] = None, height: Optional[int] = None, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def win_move(self, x: int, y: int, *, width: Optional[int] = None, height: Optional[int] = None, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def win_move( + self, + x: int, + y: int, + *, + width: Optional[int] = None, + height: Optional[int] = None, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinMove `_ + """ + args = self._format_win_args( + title=title, + text=text, + exclude_title=exclude_title, + exclude_text=exclude_text, + title_match_mode=title_match_mode, + detect_hidden_windows=detect_hidden_windows, + ) + args.append(str(x)) + args.append(str(y)) + args.append(str(width) if width is not None else '') + args.append(str(height) if height is not None else '') + resp = self._transport.function_call('AHKWinMove', args, blocking=blocking) + return resp + + # fmt: off + @overload + def get_clipboard(self) -> str: ... + @overload + def get_clipboard(self, *, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def get_clipboard(self, *, blocking: Literal[True]) -> str: ... + @overload + def get_clipboard(self, *, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def get_clipboard(self, *, blocking: bool = True) -> Union[str, FutureResult[str]]: + """ + Get the string contents of the clipboard + """ + return self._transport.function_call('AHKGetClipboard', blocking=blocking) + + def set_clipboard(self, s: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + """ + Set the contents of the clipboard + """ + args = [s] + return self._transport.function_call('AHKSetClipboard', args, blocking=blocking) + + def get_clipboard_all(self, *, blocking: bool = True) -> Union[bytes, FutureResult[bytes]]: + """ + Get the full binary contents of the keyboard. The return value is intended to be used with :py:meth:`set_clipboard_all` + """ + return self._transport.function_call('AHKGetClipboardAll', blocking=blocking) + + # fmt: off + @overload + def set_clipboard_all(self, contents: bytes) -> None: ... + @overload + def set_clipboard_all(self, contents: bytes, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def set_clipboard_all(self, contents: bytes, *, blocking: Literal[True]) -> None: ... + @overload + def set_clipboard_all(self, contents: bytes, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def set_clipboard_all( + self, contents: bytes, *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + """ + Set the full binary contents of the clipboard. Expects bytes object as returned by :py:meth:`get_clipboard_all` + """ + # TODO: figure out how to do this without a tempfile + if not isinstance(contents, bytes): + raise ValueError('Malformed data. Can only set bytes as returned by get_clipboard_all') + if not contents: + raise ValueError('bytes must be nonempty. If you want to clear the clipboard, use `set_clipboard`') + with tempfile.NamedTemporaryFile(prefix='ahk-python', suffix='.clip', mode='wb', delete=False) as f: + f.write(contents) + + args = [f'*c {f.name}' if self._transport._version != 'v2' else f.name] + try: + resp = self._transport.function_call('AHKSetClipboardAll', args, blocking=blocking) + return resp + finally: + try: + os.remove(f.name) + except Exception: + pass + + def on_clipboard_change( + self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None + ) -> None: + """ + call a function in response to clipboard change. + Uses `OnClipboardChange() `_ + """ + self._transport.on_clipboard_change(callback, ex_handler) + + # fmt: off + @overload + def clip_wait(self, timeout: Optional[float] = None, wait_for_any_data: bool = False) -> None: ... + @overload + def clip_wait(self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def clip_wait(self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: Literal[True]) -> None: ... + @overload + def clip_wait(self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def clip_wait( + self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + """ + Wait until the clipboard contents change + + Analog for `ClipWait `_ + """ + args = [str(timeout) if timeout else ''] + if wait_for_any_data: + args.append('1') + else: + args.append('0') + return self._transport.function_call('AHKClipWait', args, blocking=blocking) + + def block_input( + self, + value: Literal['On', 'Off', 'Default', 'Send', 'Mouse', 'MouseMove', 'MouseMoveOff', 'SendAndMouse'], + /, # flake8: noqa + ) -> None: + """ + Analog for `BlockInput `_ + """ + self._transport.function_call('AHKBlockInput', args=[value]) + + # fmt: off + @overload + def reg_delete(self, key_name: str, value_name: Optional[str] = None) -> None: ... + @overload + def reg_delete(self, key_name: str, value_name: Optional[str] = None, *, blocking: Literal[False]) -> Union[None, FutureResult[None]]: ... + @overload + def reg_delete(self, key_name: str, value_name: Optional[str] = None, *, blocking: Literal[True]) -> None: ... + @overload + def reg_delete(self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def reg_delete( + self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + """ + Analog for `RegDelete `_ + """ + args = [key_name, value_name if value_name is not None else ''] + return self._transport.function_call('AHKRegDelete', args, blocking=blocking) + + # fmt: off + @overload + def reg_write(self, value_type: Literal['REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ', 'REG_DWORD', 'REG_BINARY'], key_name: str, value_name: Optional[str] = None, value: Optional[str] = None) -> None: ... + @overload + def reg_write(self, value_type: Literal['REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ', 'REG_DWORD', 'REG_BINARY'], key_name: str, value_name: Optional[str] = None, value: Optional[str] = None, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def reg_write(self, value_type: Literal['REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ', 'REG_DWORD', 'REG_BINARY'], key_name: str, value_name: Optional[str] = None, value: Optional[str] = None, *, blocking: Literal[True]) -> None: ... + @overload + def reg_write(self, value_type: Literal['REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ', 'REG_DWORD', 'REG_BINARY'], key_name: str, value_name: Optional[str] = None, value: Optional[str] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def reg_write( + self, + value_type: Literal['REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ', 'REG_DWORD', 'REG_BINARY'], + key_name: str, + value_name: Optional[str] = None, + value: Optional[str] = None, + *, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for `RegWrite `_ + """ + args = [value_type, key_name] + if value_name is not None: + args.append(value_name) + else: + args.append('') + if value is not None: + args.append(value) + else: + args.append('') + return self._transport.function_call('AHKRegWrite', args, blocking=blocking) + + # fmt: off + @overload + def reg_read(self, key_name: str, value_name: Optional[str] = None) -> str: ... + @overload + def reg_read(self, key_name: str, value_name: Optional[str] = None, *, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def reg_read(self, key_name: str, value_name: Optional[str] = None, *, blocking: Literal[True]) -> str: ... + @overload + def reg_read(self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def reg_read( + self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True + ) -> Union[str, FutureResult[str]]: + """ + Analog for `RegRead `_ + """ + args = [key_name] + if value_name is not None: + args.append(value_name) + else: + args.append('') + return self._transport.function_call('AHKRegRead', args, blocking=blocking) + + # fmt: off + @overload + def msg_box(self, text: str = '', title: str = 'Message', buttons: MsgBoxButtons = MsgBoxButtons.OK, icon: Optional[MsgBoxIcon] = None, default_button: Optional[MsgBoxDefaultButton] = None, modality: Optional[MsgBoxModality] = None, help_button: bool = False, text_right_justified: bool = False, right_to_left_reading: bool = False, timeout: Optional[int] = None) -> str: ... + @overload + def msg_box(self, text: str = '', title: str = 'Message', buttons: MsgBoxButtons = MsgBoxButtons.OK, icon: Optional[MsgBoxIcon] = None, default_button: Optional[MsgBoxDefaultButton] = None, modality: Optional[MsgBoxModality] = None, help_button: bool = False, text_right_justified: bool = False, right_to_left_reading: bool = False, timeout: Optional[int] = None, *, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def msg_box(self, text: str = '', title: str = 'Message', buttons: MsgBoxButtons = MsgBoxButtons.OK, icon: Optional[MsgBoxIcon] = None, default_button: Optional[MsgBoxDefaultButton] = None, modality: Optional[MsgBoxModality] = None, help_button: bool = False, text_right_justified: bool = False, right_to_left_reading: bool = False, timeout: Optional[int] = None, *, blocking: Literal[True]) -> str: ... + @overload + def msg_box(self, text: str = '', title: str = 'Message', buttons: MsgBoxButtons = MsgBoxButtons.OK, icon: Optional[MsgBoxIcon] = None, default_button: Optional[MsgBoxDefaultButton] = None, modality: Optional[MsgBoxModality] = None, help_button: bool = False, text_right_justified: bool = False, right_to_left_reading: bool = False, timeout: Optional[int] = None, *, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def msg_box( + self, + text: str = '', + title: str = 'Message', + buttons: MsgBoxButtons = MsgBoxButtons.OK, + icon: Optional[MsgBoxIcon] = None, + default_button: Optional[MsgBoxDefaultButton] = None, + modality: Optional[MsgBoxModality] = None, + help_button: bool = False, + text_right_justified: bool = False, + right_to_left_reading: bool = False, + timeout: Optional[int] = None, + *, + blocking: bool = True, + ) -> Union[str, FutureResult[str]]: + options: int = int(buttons) + for opt in (icon, default_button, modality): + if opt is not None: + options += opt + if help_button: + options += MsgBoxOtherOptions.HELP_BUTTON + if text_right_justified: + options += MsgBoxOtherOptions.TEXT_RIGHT_JUSTIFIED + if right_to_left_reading: + options += MsgBoxOtherOptions.RIGHT_TO_LEFT_READING_ORDER + + args = [str(options), title, text] + if timeout is not None: + args.append(str(timeout)) + else: + args.append('') + + return self._transport.function_call('AHKMsgBox', args, blocking=blocking) + + # fmt: off + @overload + def input_box(self, prompt: str = '', title: str = 'Input', default: str = '', hide: bool = False, width: Optional[int] = None, height: Optional[int] = None, x: Optional[int] = None, y: Optional[int] = None, locale: bool = True, timeout: Optional[int] = None) -> Union[None, str]: ... + @overload + def input_box(self, prompt: str = '', title: str = 'Input', default: str = '', hide: bool = False, width: Optional[int] = None, height: Optional[int] = None, x: Optional[int] = None, y: Optional[int] = None, locale: bool = True, timeout: Optional[int] = None, *, blocking: Literal[False]) -> Union[FutureResult[str], FutureResult[None]]: ... + @overload + def input_box(self, prompt: str = '', title: str = 'Input', default: str = '', hide: bool = False, width: Optional[int] = None, height: Optional[int] = None, x: Optional[int] = None, y: Optional[int] = None, locale: bool = True, timeout: Optional[int] = None, *, blocking: Literal[True]) -> Union[str, None]: ... + @overload + def input_box(self, prompt: str = '', title: str = 'Input', default: str = '', hide: bool = False, width: Optional[int] = None, height: Optional[int] = None, x: Optional[int] = None, y: Optional[int] = None, locale: bool = True, timeout: Optional[int] = None, *, blocking: bool = True) -> Union[str, None, FutureResult[str], FutureResult[None]]: ... + # fmt: on + def input_box( + self, + prompt: str = '', + title: str = 'Input', + default: str = '', + hide: bool = False, + width: Optional[int] = None, + height: Optional[int] = None, + x: Optional[int] = None, + y: Optional[int] = None, + locale: bool = True, + timeout: Optional[int] = None, + *, + blocking: bool = True, + ) -> Union[None, str, FutureResult[str], FutureResult[None]]: + """ + Like AHK's ``InputBox`` + + If the user presses Cancel or closes the box, ``None`` is returned. + Otherwise, the user's input is returned. + Raises a ``TimeoutError`` if a timeout is specified and expires. + """ + args = [title, prompt] + if hide: + args.append('hide') + else: + args.append('') + for opt in (width, height, x, y): + if opt is not None: + args.append(str(opt)) + else: + args.append('') + if locale: + args.append('Locale') + else: + args.append('') + if timeout is not None: + args.append(str(timeout)) + else: + args.append('') + args.append(default) + return self._transport.function_call('AHKInputBox', args, blocking=blocking) + + # fmt: off + @overload + def file_select_box(self, title: str = 'Select File', multi: bool = False, root: str = '', filter: str = '', save_button: bool = False, file_must_exist: bool = False, path_must_exist: bool = False, prompt_create_new_file: bool = False, prompt_override_file: bool = False, follow_shortcuts: bool = True) -> Union[None, str]: ... + @overload + def file_select_box(self, title: str = 'Select File', multi: bool = False, root: str = '', filter: str = '', save_button: bool = False, file_must_exist: bool = False, path_must_exist: bool = False, prompt_create_new_file: bool = False, prompt_override_file: bool = False, follow_shortcuts: bool = True, *, blocking: Literal[False]) -> Union[FutureResult[str], FutureResult[None]]: ... + @overload + def file_select_box(self, title: str = 'Select File', multi: bool = False, root: str = '', filter: str = '', save_button: bool = False, file_must_exist: bool = False, path_must_exist: bool = False, prompt_create_new_file: bool = False, prompt_override_file: bool = False, follow_shortcuts: bool = True, *, blocking: Literal[True]) -> Union[str, None]: ... + @overload + def file_select_box(self, title: str = 'Select File', multi: bool = False, root: str = '', filter: str = '', save_button: bool = False, file_must_exist: bool = False, path_must_exist: bool = False, prompt_create_new_file: bool = False, prompt_override_file: bool = False, follow_shortcuts: bool = True, *, blocking: bool = True) -> Union[str, None, FutureResult[str], FutureResult[None]]: ... + # fmt: on + def file_select_box( + self, + title: str = 'Select File', + multi: bool = False, + root: str = '', + filter: str = '', + save_button: bool = False, + file_must_exist: bool = False, + path_must_exist: bool = False, + prompt_create_new_file: bool = False, + prompt_override_file: bool = False, + follow_shortcuts: bool = True, + *, + blocking: bool = True, + ) -> Union[str, None, FutureResult[str], FutureResult[None]]: + opts = 0 + if file_must_exist: + opts += 1 + if path_must_exist: + opts += 2 + if prompt_create_new_file: + opts += 8 + if prompt_override_file: + opts += 8 + if not follow_shortcuts: + opts += 32 + options = '' + if multi: + options += 'M' + if save_button: + options += 'S' + if opts: + options += str(opts) + args = [options, root, title, filter] + return self._transport.function_call('AHKFileSelectFile', args, blocking=blocking) + + # fmt: off + @overload + def folder_select_box(self, prompt: str = 'Select Folder', root: str = '', chroot: bool = False, enable_new_directories: bool = True, edit_field: bool = False, new_dialog_style: bool = False) -> Union[None, str]: ... + @overload + def folder_select_box(self, prompt: str = 'Select Folder', root: str = '', chroot: bool = False, enable_new_directories: bool = True, edit_field: bool = False, new_dialog_style: bool = False, *, blocking: Literal[False]) -> Union[FutureResult[str], FutureResult[None]]: ... + @overload + def folder_select_box(self, prompt: str = 'Select Folder', root: str = '', chroot: bool = False, enable_new_directories: bool = True, edit_field: bool = False, new_dialog_style: bool = False, *, blocking: Literal[True]) -> Union[str, None]: ... + @overload + def folder_select_box(self, prompt: str = 'Select Folder', root: str = '', chroot: bool = False, enable_new_directories: bool = True, edit_field: bool = False, new_dialog_style: bool = False, *, blocking: bool = True) -> Union[str, None, FutureResult[str], FutureResult[None]]: ... + # fmt: on + def folder_select_box( + self, + prompt: str = 'Select Folder', + root: str = '', + chroot: bool = False, + enable_new_directories: bool = True, + edit_field: bool = False, + new_dialog_style: bool = False, + *, + blocking: bool = True, + ) -> Union[str, None, FutureResult[str], FutureResult[None]]: + if not chroot: + starting_folder = '*' + else: + starting_folder = '' + starting_folder += root + if enable_new_directories: + opts = 1 + else: + opts = 0 + if edit_field: + opts += 2 + if new_dialog_style: + opts += 4 + args = [starting_folder, str(opts), prompt] + return self._transport.function_call('AHKFileSelectFolder', args, blocking=blocking) + + def block_forever(self) -> NoReturn: + """ + Blocks (sleeps) forever. Utility method to prevent script from exiting. + """ + while True: + sleep(1) + + def get_version(self) -> str: + return self._transport._get_full_version() + + def get_major_version(self) -> Literal['v1', 'v2']: + return self._transport._get_major_version() diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py new file mode 100644 index 00000000..eaa16988 --- /dev/null +++ b/ahk/_sync/transport.py @@ -0,0 +1,806 @@ +from __future__ import annotations + +import asyncio.subprocess +import atexit +import os +import re +import subprocess +import sys +import tempfile +import threading +import warnings +from abc import ABC +from abc import abstractmethod +from concurrent.futures import Future +from concurrent.futures import ThreadPoolExecutor +from io import BytesIO +from typing import Any +from typing import Callable +from typing import Generic +from typing import List +from typing import Literal +from typing import Optional +from typing import overload +from typing import Protocol +from typing import runtime_checkable +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +import jinja2 + +from ahk._constants import DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE +from ahk._constants import DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE +from ahk._hotkey import Hotkey +from ahk._hotkey import Hotstring +from ahk._hotkey import ThreadedHotkeyTransport +from ahk._types import Coordinates +from ahk._types import FunctionName +from ahk._types import Position +from ahk._utils import _version_detection_script +from ahk._utils import try_remove +from ahk.directives import Directive +from ahk.exceptions import AHKProtocolError +from ahk.extensions import _resolve_includes +from ahk.extensions import Extension +from ahk.message import _message_registry +from ahk.message import RequestMessage +from ahk.message import ResponseMessage + + +if TYPE_CHECKING: + from ahk import Control + from ahk import Window + +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias, TypeGuard +else: + from typing import TypeAlias, TypeGuard + +if sys.version_info < (3, 11): + from typing_extensions import Self +else: + from typing import Self + +T_SyncFuture = TypeVar('T_SyncFuture') + + + + +class FutureResult(Generic[T_SyncFuture]): + def __init__(self, future: Future[T_SyncFuture]): + self._fut: Future[T_SyncFuture] = future + + def result(self, timeout: Optional[float] = None) -> T_SyncFuture: + return self._fut.result(timeout=timeout) + + + +SyncIOProcess: TypeAlias = 'subprocess.Popen[bytes]' + + +@runtime_checkable +class Killable(Protocol): + def kill(self) -> None: ... + + +def kill(proc: Killable) -> None: + try: + proc.kill() + except: # noqa + pass + + +def async_assert_send_nonblocking_type_correct( + obj: Any, +) -> TypeGuard[ + Future[ + Union[None, Coordinates, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]] + ] +]: + return True + + +class Communicable(Protocol): + runargs: List[str] + + def start(self, atexit_cleanup: bool = True) -> None: ... + + def communicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: ... + + + @property + def returncode(self) -> Optional[int]: ... + + def kill(self) -> None: ... + + +class SyncAHKProcess: + def __init__(self, runargs: List[str]): + self.runargs = runargs + self._proc: Optional[SyncIOProcess] = None + + @property + def returncode(self) -> Optional[int]: + assert self._proc is not None + return self._proc.returncode + + + def start(self, atexit_cleanup: bool = True) -> None: + self._proc = sync_create_process(self.runargs) + if atexit_cleanup: + atexit.register(kill, self._proc) + return None + + + def drain_stdin(self) -> None: + assert isinstance(self._proc, subprocess.Popen) + assert self._proc.stdin is not None + self._proc.stdin.flush() + return None + + def write(self, content: bytes) -> None: + assert self._proc is not None + assert self._proc.stdin is not None + self._proc.stdin.write(content) + + def readline(self) -> bytes: + assert self._proc is not None + assert self._proc.stdout is not None + line = self._proc.stdout.readline() + assert isinstance(line, bytes) + return line + + def read(self) -> bytes: + assert self._proc is not None + assert self._proc.stdout is not None + b = self._proc.stdout.read() + assert isinstance(b, bytes) + return b + + def kill(self) -> None: + assert self._proc is not None, 'no process to kill' + self._proc.kill() + + + def communicate(self, input_bytes: Optional[bytes] = None, timeout: Optional[int] = None) -> Tuple[bytes, bytes]: + assert self._proc is not None + assert isinstance(self._proc, subprocess.Popen) + return self._proc.communicate(input=input_bytes, timeout=timeout) + + def __enter__(self) -> Self: + self.start(atexit_cleanup=False) + return self + + def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> Literal[False]: + try: + self.kill() + except Exception: + pass + return False + + + + +def sync_create_process(runargs: List[str]) -> subprocess.Popen[bytes]: + return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + + +class Transport(ABC): + _started: bool = False + + def __init__( + self, + /, + directives: Optional[list[Union[Directive, Type[Directive]]]] = None, + version: Optional[Literal['v1', 'v2']] = 'v1', + hotkey_transport: Optional[ThreadedHotkeyTransport] = None, + **kwargs: Any, + ): + self._hotkey_transport = hotkey_transport + self._directives: list[Union[Directive, Type[Directive]]] = directives or [] + self._version: Optional[Literal['v1', 'v2']] = version + + def _get_full_version(self) -> str: + res = self.run_script(_version_detection_script) + version = res.strip() + assert re.match(r'^\d+\.', version) + return version + + def _get_major_version(self) -> Literal['v1', 'v2']: + version = self._get_full_version() + match = re.match(r'^(\d+)\.', version) + if not match: + raise ValueError(f'Unexpected version {version!r}') + major_version = match.group(1) + if major_version == '1': + return 'v1' + elif major_version == '2': + return 'v2' + else: + raise ValueError(f'Unexpected version {version!r}') + + def on_clipboard_change( + self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None + ) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + self._hotkey_transport.on_clipboard_change(callback, ex_handler) + return None + + def add_hotkey(self, hotkey: Hotkey) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + with warnings.catch_warnings(record=True) as caught_warnings: + self._hotkey_transport.add_hotkey(hotkey=hotkey) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return None + + def add_hotstring(self, hotstring: Hotstring) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + with warnings.catch_warnings(record=True) as caught_warnings: + self._hotkey_transport.add_hotstring(hotstring=hotstring) + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + return None + + def remove_hotkey(self, hotkey: Hotkey) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + self._hotkey_transport.remove_hotkey(hotkey) + return None + + def clear_hotkeys(self) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + self._hotkey_transport.clear_hotkeys() + return None + + def remove_hotstring(self, hotstring: Hotstring) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + self._hotkey_transport.remove_hotstring(hotstring) + return None + + def clear_hotstrings(self) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + self._hotkey_transport.clear_hotstrings() + return None + + def start_hotkeys(self) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + return self._hotkey_transport.start() + + def stop_hotkeys(self) -> None: + assert self._hotkey_transport is not None, 'current transport does not support hotkey functionality' + return self._hotkey_transport.stop() + + def init(self) -> None: + self._started = True + return None + + # fmt: off + @overload + def run_script(self, script_text_or_path: str, /, *, timeout: Optional[int] = None) -> str: ... + @overload + def run_script(self, script_text_or_path: str, /, *, blocking: Literal[False], timeout: Optional[int] = None) -> FutureResult[str]: ... + @overload + def run_script(self, script_text_or_path: str, /, *, blocking: Literal[True], timeout: Optional[int] = None) -> str: ... + @overload + def run_script(self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None) -> Union[str, FutureResult[str]]: ... + # fmt: on + @abstractmethod + def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, FutureResult[str]]: + ... + + # fmt: off + @overload + def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[bool, FutureResult[bool]]: ... + @overload + def function_call(self, function_name: Literal['AHKImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Coordinates, None, FutureResult[Union[Coordinates, None]]]: ... + @overload + def function_call(self, function_name: Literal['AHKPixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[str, FutureResult[str]]: ... + @overload + def function_call(self, function_name: Literal['AHKPixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Optional[Coordinates], FutureResult[Optional[Coordinates]]]: ... + @overload + def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Coordinates, FutureResult[Coordinates]]: ... + @overload + def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[int, float, str, None, FutureResult[None], FutureResult[str], FutureResult[int], FutureResult[float]]: ... + @overload + def function_call(self, function_name: Literal['AHKMouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKMouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKKeyWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[bool, FutureResult[bool]]: ... + @overload + def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSendInput'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSendEvent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSetNumLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSetScrollLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[str, FutureResult[str]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[str, FutureResult[str]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[str, FutureResult[str]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinMinimize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[List[Window], FutureResult[List[Window]]]: ... + @overload + def function_call(self, function_name: Literal['AHKControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinFromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Optional[Window], FutureResult[Optional[Window]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Optional[bool], FutureResult[Optional[bool]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[Position, None], FutureResult[Union[None, Position]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[None, Window], FutureResult[Union[None, Window]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[None, Window], FutureResult[Union[None, Window]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[int, None], FutureResult[Union[int, None]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[None, str], FutureResult[Union[None, str]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[None, str], FutureResult[Union[None, str]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[int, FutureResult[int]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[List[Window], FutureResult[List[Window]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[None, int], FutureResult[Union[None, int]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[List[Control], None, FutureResult[Union[List[Control], None]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[None, int], FutureResult[Union[None, int]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[None, str], FutureResult[Union[None, str]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[None, str], FutureResult[Union[None, str]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Union[None, str], FutureResult[Union[None, str]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetBottom'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetDisable'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetEnable'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetRedraw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[bool, FutureResult[bool]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[bool, FutureResult[bool]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetRegion'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[bool, FutureResult[bool]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSetDetectHiddenWindows'], args: Optional[List[str]] = None) -> None: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSetTitleMatchMode'], args: Optional[List[str]] = None) -> None: ... + @overload + def function_call(self, function_name: Literal['AHKGetTitleMatchMode']) -> str: ... + @overload + def function_call(self, function_name: Literal['AHKGetTitleMatchSpeed']) -> str: ... + @overload + def function_call(self, function_name: Literal['AHKControlGetText'], args: Optional[List[str]] = None, *, engine: Optional[AHK[Any]] = None, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + @overload + def function_call(self, function_name: Literal['AHKControlClick'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKControlGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[Position, FutureResult[Position]]: ... + @overload + def function_call(self, function_name: Literal['AHKGetCoordMode'], args: List[str]) -> str: ... + @overload + def function_call(self, function_name: Literal['AHKSetCoordMode'], args: List[str]) -> None: ... + @overload + def function_call(self, function_name: Literal['AHKGetSendLevel']) -> int: ... + @overload + def function_call(self, function_name: Literal['AHKSetSendMode'], args: List[str]) -> None: ... + @overload + def function_call(self, function_name: Literal['AHKGetSendMode']) -> str: ... + @overload + def function_call(self, function_name: Literal['AHKSetSendLevel'], args: List[str]) -> None: ... + @overload + def function_call(self, function_name: Literal['AHKWinWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Window, FutureResult[Window]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinWaitActive'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Window, FutureResult[Window]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinWaitNotActive'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Window, FutureResult[Window]]: ... + + @overload + def function_call(self, function_name: Literal['AHKWinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinIsActive'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[bool, FutureResult[bool]]: ... + @overload + def function_call(self, function_name: Literal['AHKGetVolume'], args: Optional[List[str]] = None) -> float: ... + @overload + def function_call(self, function_name: Literal['AHKSoundBeep'], args: Optional[List[str]] = None, *, blocking: bool = True) -> None: ... + @overload + def function_call(self, function_name: Literal['AHKSoundGet'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + @overload + def function_call(self, function_name: Literal['AHKSoundPlay'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSoundSet'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSetVolume'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKTrayTip'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKGetClipboard'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + @overload + def function_call(self, function_name: Literal['AHKGetClipboardAll'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[bytes, FutureResult[bytes]]: ... + @overload + def function_call(self, function_name: Literal['AHKSetClipboard'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSetClipboardAll'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKBlockInput'], args: Optional[List[str]], *, blocking: bool = True) -> None: ... + @overload + def function_call(self, function_name: Literal['AHKShowToolTip'], args: Optional[List[str]]) -> None: ... + @overload + def function_call(self, function_name: Literal['AHKClipWait'], args: Optional[List[str]], *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + + # @overload + # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... + @overload + def function_call(self, function_name: Literal['AHKWinWaitClose'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKRegRead'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + @overload + def function_call(self, function_name: Literal['AHKRegWrite'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKRegDelete'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKMenuTrayTip'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKMenuTrayIcon'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKMenuTrayShow'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKMenuTrayHide'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKGuiNew'], args: List[str], *, engine: AHK[Any]) -> str: ... + @overload + def function_call(self, function_name: Literal['AHKMsgBox'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + @overload + def function_call(self, function_name: Literal['AHKInputBox'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, None, FutureResult[str], FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKFileSelectFile'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, None, FutureResult[str], FutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKFileSelectFolder'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, None, FutureResult[str], FutureResult[None]]: ... + # fmt: on + + def function_call( + self, + function_name: FunctionName, + args: Optional[List[str]] = None, + blocking: bool = True, + engine: Optional[AHK[Any]] = None, + ) -> Any: + if not self._started and blocking: + with warnings.catch_warnings(record=True) as caught_warnings: + self.init() + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=3) + request = RequestMessage(function_name=function_name, args=args) + if blocking: + return self.send(request, engine=engine) + else: + return self.send_nonblocking(request, engine=engine) + + @abstractmethod + def send( + self, request: RequestMessage, engine: Optional[AHK[Any]] = None + ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: + ... + + + @abstractmethod + def send_nonblocking( + self, request: RequestMessage, engine: Optional[AHK[Any]] = None + ) -> FutureResult[Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]]: + ... + + +class DaemonProcessTransport(Transport): + def __init__( + self, + *, + executable_path: str = '', + directives: Optional[list[Directive | Type[Directive]]] = None, + jinja_loader: Optional[jinja2.BaseLoader] = None, + template: Optional[jinja2.Template] = None, + extensions: list[Extension] | None = None, + version: Optional[Literal['v1', 'v2']] = None, + skip_version_check: bool = False, + ): + self._extensions = extensions or [] + self._proc: Optional[SyncAHKProcess] + self._proc = None + self._temp_script: Optional[str] = None + self.__template: jinja2.Template + self._jinja_env: jinja2.Environment + self._execution_lock = threading.Lock() + self._executable_path = executable_path + + if version is None or version == 'v1': + template_name = 'daemon.ahk' + const_script = _DAEMON_SCRIPT_TEMPLATE + elif version == 'v2': + template_name = 'daemon-v2.ahk' + const_script = _DAEMON_SCRIPT_V2_TEMPLATE + else: + raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') + + if jinja_loader is None: + try: + loader: jinja2.BaseLoader + loader = jinja2.PackageLoader('ahk', 'templates') + except ValueError: + # see: https://github.com/spyoungtech/ahk/issues/201 + warnings.warn( + 'Jinja could not find templates with PackageLoader. Falling back to BaseLoader', + category=UserWarning, + ) + loader = jinja2.BaseLoader() + self._jinja_env = jinja2.Environment(loader=loader, trim_blocks=True, autoescape=False) + else: + self._jinja_env = jinja2.Environment(loader=jinja_loader, trim_blocks=True, autoescape=False) + try: + self.__template = self._jinja_env.get_template(template_name) + except jinja2.TemplateNotFound: + warnings.warn('daemon template missing. Falling back to constant', category=UserWarning) + self.__template = self._jinja_env.from_string(const_script) + if template is None: + template = self.__template + self._template: jinja2.Template = template + directives = directives or [] + if extensions: + includes = _resolve_includes(extensions) + directives = includes + directives + hotkey_transport = ThreadedHotkeyTransport( + executable_path=self._executable_path, directives=directives, version=version + ) + super().__init__(directives=directives, version=version, hotkey_transport=hotkey_transport) + + @property + def template(self) -> jinja2.Template: + return self._template + + def init(self) -> None: + self.start() + super().init() + return None + + def start(self) -> None: + assert self._proc is None, 'cannot start a process twice' + with warnings.catch_warnings(record=True) as caught_warnings: + with self.lock: + self._proc = self._create_process() + self._proc.start() + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) + + def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: Any) -> str: + if template is None: + template = self._template + kwargs['daemon'] = self.__template + message_types = {str(tom, 'utf-8'): c.__name__.upper() for tom, c in _message_registry.items()} + return template.render( + directives=self._directives, + message_types=message_types, + message_registry=_message_registry, + extensions=self._extensions, + ahk_version=self._version, + **kwargs, + ) + + @property + def lock(self) -> Any: + return self._execution_lock + + def _create_process(self, template: Optional[jinja2.Template] = None, **template_kwargs: Any) -> SyncAHKProcess: + if template is None: + if template_kwargs: + raise ValueError('template kwargs were specified, but no template was provided') + if self._temp_script is None or not os.path.exists(self._temp_script): + script_text = self._render_script() + with tempfile.NamedTemporaryFile( + mode='w', prefix='python-ahk-', suffix='.ahk', delete=False + ) as tempscriptfile: + tempscriptfile.write(script_text) # XXX: can we make this async? + self._temp_script = tempscriptfile.name + daemon_script = self._temp_script + atexit.register(try_remove, tempscriptfile.name) + else: + daemon_script = self._temp_script + else: + script_text = self._render_script(template=template, **template_kwargs) + with tempfile.NamedTemporaryFile(mode='w', prefix='python-ahk-', suffix='.ahk', delete=False) as tempscript: + tempscript.write(script_text) + daemon_script = tempscript.name + atexit.register(try_remove, tempscript.name) + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] + proc = SyncAHKProcess(runargs=runargs) + return proc + + def _send_nonblocking( + self, request: RequestMessage, engine: Optional[AHK[Any]] = None + ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: + msg = request.format() + with self._create_process() as proc: + proc.write(msg) + proc.drain_stdin() + tom = proc.readline() + num_lines = proc.readline() + content_buffer = BytesIO() + content_buffer.write(tom) + content_buffer.write(num_lines) + try: + lines_to_read = int(num_lines) + 1 + except ValueError as e: + try: + stdout = tom + num_lines + proc.read() + except Exception: + stdout = b'' + raise AHKProtocolError( + 'Unexpected data received. This is usually the result of an unhandled error in the AHK process' + + (f': {stdout!r}' if stdout else '') + ) from e + for _ in range(lines_to_read): + part = proc.readline() + content_buffer.write(part) + content = content_buffer.getvalue()[:-1] + response = ResponseMessage.from_bytes(content, engine=engine) + return response.unpack() # type: ignore + + + def send_nonblocking( + self, request: RequestMessage, engine: Optional[AHK[Any]] = None + ) -> FutureResult[Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]]: + # this is only used by the sync implementation + pool = ThreadPoolExecutor(max_workers=1) + fut = pool.submit(self._send_nonblocking, request=request, engine=engine) + pool.shutdown(wait=False) + assert async_assert_send_nonblocking_type_correct( + fut + ) # workaround to get mypy correctness in sync and async implementation + return FutureResult(fut) + + def send( + self, request: RequestMessage, engine: Optional[AHK[Any]] = None + ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: + msg = request.format() + assert self._proc is not None + with self.lock: + self._proc.write(msg) + self._proc.drain_stdin() + tom = self._proc.readline() + num_lines = self._proc.readline() + content_buffer = BytesIO() + content_buffer.write(tom) + content_buffer.write(num_lines) + try: + lines_to_read = int(num_lines) + 1 + except ValueError as e: + try: + stdout = tom + num_lines + self._proc.read() + except Exception: + stdout = b'' + raise AHKProtocolError( + 'Unexpected data received. This is usually the result of an unhandled error in the AHK process' + + (f': {stdout!r}' if stdout else '') + ) from e + for _ in range(lines_to_read): + part = self._proc.readline() + content_buffer.write(part) + content = content_buffer.getvalue()[:-1] + response = ResponseMessage.from_bytes(content, engine=engine) + return response.unpack() # type: ignore + + + def _sync_run_nonblocking( + self, + proc: Communicable, + script_bytes: Optional[bytes], + timeout: Optional[int] = None, + ) -> FutureResult[str]: + + def f() -> str: + try: + proc.start(atexit_cleanup=False) + stdout, stderr = proc.communicate(script_bytes, timeout) + finally: + try: + proc.kill() + except Exception: + pass + if proc.returncode != 0: + assert proc.returncode is not None + raise subprocess.CalledProcessError(proc.returncode, proc.runargs, stdout, stderr) + return stdout.decode('utf-8') + + pool = ThreadPoolExecutor(max_workers=1) + fut = pool.submit(f) + pool.shutdown(wait=False) + return FutureResult(fut) + + # fmt: off + @overload + def run_script(self, script_text_or_path: str, /, *, timeout: Optional[int] = None) -> str: ... + @overload + def run_script(self, script_text_or_path: str, /, *, blocking: Literal[False], timeout: Optional[int] = None) -> FutureResult[str]: ... + @overload + def run_script(self, script_text_or_path: str, /, *, blocking: Literal[True], timeout: Optional[int] = None) -> str: ... + @overload + def run_script(self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None) -> Union[str, FutureResult[str]]: ... + # fmt: on + def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, FutureResult[str]]: + if os.path.exists(script_text_or_path): + script_bytes = None + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', script_text_or_path] + else: + script_bytes = bytes(script_text_or_path, 'utf-8') + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', '*'] + proc = SyncAHKProcess(runargs) + if blocking: + with proc: + stdout, stderr = proc.communicate(script_bytes, timeout=timeout) + if proc.returncode != 0: + assert proc.returncode is not None + raise subprocess.CalledProcessError(proc.returncode, proc.runargs, stdout, stderr) + return stdout.decode('utf-8') + else: + return self._sync_run_nonblocking(proc, script_bytes, timeout=timeout) + + +if TYPE_CHECKING: + from .engine import AHK diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py new file mode 100644 index 00000000..5304f4c0 --- /dev/null +++ b/ahk/_sync/window.py @@ -0,0 +1,755 @@ +from __future__ import annotations + +import sys +import warnings +from functools import partial +from typing import Any +from typing import Callable +from typing import Coroutine +from typing import Literal +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING +from typing import TypedDict +from typing import TypeVar +from typing import Union + +from ahk._types import Position +from ahk.exceptions import WindowNotFoundException + +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + +if sys.version_info < (3, 11): + from typing_extensions import NotRequired +else: + from typing import NotRequired + +if TYPE_CHECKING: + from .engine import AHK + from .transport import FutureResult + + +SyncPropertyReturnStr: TypeAlias = str + +SyncPropertyReturnInt: TypeAlias = int + +SyncPropertyReturnTupleIntInt: TypeAlias = Tuple[int, int] + +SyncPropertyReturnBool: TypeAlias = bool + +_PROPERTY_DEPRECATION_WARNING_MESSAGE = 'Use of the {0} property is not recommended (in the async API only) and may be removed in a future version. Use the get_{0} method instead.' +_SETTERS_REMOVED_ERROR_MESSAGE = ( + 'Use of the {0} property setter is not supported in the async API. Use the set_{0} instead.' +) + +T_EngineVersion = TypeVar('T_EngineVersion', bound=Optional[Literal['v1', 'v2']]) + + +class Window: + def __init__(self, engine: AHK[T_EngineVersion], ahk_id: str): + self._engine: AHK[T_EngineVersion] = engine + if not ahk_id: + raise ValueError(f'Invalid ahk_id: {ahk_id!r}') + self._ahk_id: str = ahk_id + + def __repr__(self) -> str: + return f'<{self.__class__.__qualname__} ahk_id={self._ahk_id!r}>' + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Window): + return NotImplemented + return self._ahk_id == other._ahk_id + + def __hash__(self) -> int: + return hash(self._ahk_id) + + def __getattr__(self, name: str) -> Callable[..., Any]: + method = self._engine._get_window_extension_method(name) + if method is None: + raise AttributeError(f'{self.__class__.__name__!r} object has no attribute {name!r}') + else: + return partial(method, self) + + def close(self) -> None: + self._engine.win_close( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + return None + + def kill(self) -> None: + self._engine.win_kill( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + + def exists(self) -> bool: + return self._engine.win_exists( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + + @property + def id(self) -> str: + return self._ahk_id + + @property + def exist(self) -> SyncPropertyReturnBool: + return self.exists() + + def get_pid(self) -> int: + pid = self._engine.win_get_pid( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + if pid is None: + raise WindowNotFoundException( + f'Error when trying to get PID of window {self._ahk_id!r}. The window may have been closed before the operation could be completed' + ) + return pid + + @property + def pid(self) -> SyncPropertyReturnInt: + return self.get_pid() + + def get_process_name(self) -> str: + name = self._engine.win_get_process_name( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + if name is None: + raise WindowNotFoundException( + f'Error when trying to get process name of window {self._ahk_id!r}. The window may have been closed before the operation could be completed' + ) + return name + + @property + def process_name(self) -> SyncPropertyReturnStr: + return self.get_process_name() + + def get_process_path(self) -> str: + path = self._engine.win_get_process_path( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + if path is None: + raise WindowNotFoundException( + f'Error when trying to get process path of window {self._ahk_id!r}. The window may have been closed before the operation could be completed' + ) + return path + + @property + def process_path(self) -> SyncPropertyReturnStr: + return self.get_process_path() + + def get_minmax(self) -> int: + minmax = self._engine.win_get_minmax( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + if minmax is None: + raise WindowNotFoundException( + f'Error when trying to get minmax state of window {self._ahk_id}. The window may have been closed before the operation could be completed' + ) + return minmax + + def get_title(self) -> str: + title = self._engine.win_get_title( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + return title + + @property + def title(self) -> SyncPropertyReturnStr: + return self.get_title() + + @title.setter + def title(self, value: str) -> Any: + self.set_title(value) + + def set_title(self, new_title: str) -> None: + self._engine.win_set_title( + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + new_title=new_title, + title_match_mode=(1, 'Fast'), + ) + return None + + def list_controls(self) -> Sequence['Control']: + controls = self._engine.win_get_control_list( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + if controls is None: + raise WindowNotFoundException( + f'Error when trying to enumerate controls for window {self._ahk_id}. The window may have been closed before the operation could be completed' + ) + return controls + + # fmt: off + @overload + def minimize(self) -> None: ... + @overload + def minimize(self, blocking: Literal[True]) -> None: ... + @overload + def minimize(self, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def minimize(self, blocking: bool = True) -> Optional[FutureResult[None]]: ... + # fmt: on + def minimize(self, blocking: bool = True) -> Optional[FutureResult[None]]: + return self._engine.win_minimize( + title=f'ahk_id {self._ahk_id}', title_match_mode=(1, 'Fast'), detect_hidden_windows=True, blocking=blocking + ) + + # fmt: off + @overload + def maximize(self) -> None: ... + @overload + def maximize(self, blocking: Literal[True]) -> None: ... + @overload + def maximize(self, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def maximize(self, blocking: bool = True) -> Optional[FutureResult[None]]: ... + # fmt: on + def maximize(self, blocking: bool = True) -> Optional[FutureResult[None]]: + return self._engine.win_maximize( + title=f'ahk_id {self._ahk_id}', title_match_mode=(1, 'Fast'), detect_hidden_windows=True, blocking=blocking + ) + + # fmt: off + @overload + def restore(self) -> None: ... + @overload + def restore(self, blocking: Literal[True]) -> None: ... + @overload + def restore(self, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def restore(self, blocking: bool = True) -> Optional[FutureResult[None]]: ... + # fmt: on + def restore(self, blocking: bool = True) -> Optional[FutureResult[None]]: + return self._engine.win_restore( + title=f'ahk_id {self._ahk_id}', title_match_mode=(1, 'Fast'), detect_hidden_windows=True, blocking=blocking + ) + + # fmt: off + @overload + def get_class(self) -> str: ... + @overload + def get_class(self, blocking: Literal[True]) -> str: ... + @overload + def get_class(self, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def get_class(self, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def get_class(self, blocking: bool = True) -> Union[str, FutureResult[str]]: + return self._engine.win_get_class( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast'), blocking=blocking + ) + + # fmt: off + @overload + def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> None: ... + @overload + def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: Literal[True]) -> None: ... + @overload + def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def set_always_on_top( + self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + return self._engine.win_set_always_on_top( + toggle=toggle, + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + def is_always_on_top(self) -> bool: ... + @overload + def is_always_on_top(self, *, blocking: Literal[False]) -> FutureResult[Optional[bool]]: ... + @overload + def is_always_on_top(self, *, blocking: Literal[True]) -> bool: ... + @overload + def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, FutureResult[Optional[bool]]]: ... + # fmt: on + def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, FutureResult[Optional[bool]]]: + args = [f'ahk_id {self._ahk_id}'] + resp = self._engine._transport.function_call( + 'AHKWinIsAlwaysOnTop', args, blocking=blocking + ) # XXX: maybe shouldn't access transport directly? + if resp is None: + raise WindowNotFoundException( + f'Error when trying to get always on top style for window {self._ahk_id}. The window may have been closed before the operation could be completed' + ) + return resp + + @property + def always_on_top(self) -> SyncPropertyReturnBool: + return self.is_always_on_top() + + @always_on_top.setter + def always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> Any: + self.set_always_on_top(toggle) + + # fmt: off + @overload + def send(self, keys: str, control: str = '') -> None: ... + @overload + def send(self, keys: str, control: str = '', *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def send(self, keys: str, control: str = '', *, blocking: Literal[True]) -> None: ... + @overload + def send(self, keys: str, control: str = '', *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def send( + self, keys: str, control: str = '', *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + return self._engine.control_send( + keys=keys, + control=control, + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + def click(self, x: int = 0, y: int = 0, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '') -> None: ... + @overload + def click(self, x: int = 0, y: int = 0, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def click(self, x: int = 0, y: int = 0, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: Literal[True]) -> None: ... + @overload + def click(self, x: int = 0, y: int = 0, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def click( + self, + x: int = 0, + y: int = 0, + *, + button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', + click_count: int = 1, + options: str = '', + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + pos = f'X{x} Y{y}' + return self._engine.control_click( + control=pos, + title=f'ahk_id {self._ahk_id}', + button=button, + click_count=click_count, + options=options, + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + def get_text(self) -> str: ... + @overload + def get_text(self, *, blocking: Literal[False]) -> FutureResult[str]: ... + @overload + def get_text(self, *, blocking: Literal[True]) -> str: ... + @overload + def get_text(self, *, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + # fmt: on + def get_text(self, *, blocking: bool = True) -> Union[str, FutureResult[str]]: + return self._engine.win_get_text( + title=f'ahk_id {self._ahk_id}', blocking=blocking, detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) + + @property + def text(self) -> SyncPropertyReturnStr: + return self.get_text() + + # fmt: off + @overload + def get_position(self) -> Position: ... + @overload + def get_position(self, *, blocking: Literal[False]) -> FutureResult[Optional[Position]]: ... + @overload + def get_position(self, *, blocking: Literal[True]) -> Position: ... + @overload + def get_position(self, *, blocking: bool = True) -> Union[Position, FutureResult[Optional[Position]], FutureResult[Position]]: ... + # fmt: on + def get_position( + self, *, blocking: bool = True + ) -> Union[Position, FutureResult[Optional[Position]], FutureResult[Position]]: + resp = self._engine.win_get_position( # type: ignore[misc] # this appears to be a mypy bug + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + if resp is None: + raise WindowNotFoundException( + f'Error when trying to get position for window {self._ahk_id}. The window may have been closed before the operation could be completed' + ) + return resp + + # fmt: off + @overload + def activate(self) -> None: ... + @overload + def activate(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def activate(self, *, blocking: Literal[True]) -> None: ... + @overload + def activate(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def activate(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + resp = self._engine.win_activate( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + return resp + + # fmt: off + @overload + def to_bottom(self, *, blocking: Literal[True]) -> None: ... + @overload + def to_bottom(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def to_bottom(self) -> None: ... + # fmt: on + def to_bottom(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + return self._engine.win_set_bottom( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + def to_top(self, *, blocking: Literal[True]) -> None: ... + @overload + def to_top(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def to_top(self) -> None: ... + # fmt: on + def to_top(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + return self._engine.win_set_top( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + def show(self, *, blocking: Literal[True]) -> None: ... + @overload + def show(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show(self) -> None: ... + # fmt: on + def show(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + return self._engine.win_show( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + def hide(self, *, blocking: Literal[True]) -> None: ... + @overload + def hide(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def hide(self) -> None: ... + # fmt: on + def hide(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + return self._engine.win_hide( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + def disable(self, *, blocking: Literal[True]) -> None: ... + @overload + def disable(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def disable(self) -> None: ... + # fmt: on + def disable(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + return self._engine.win_set_disable( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + def enable(self, *, blocking: Literal[True]) -> None: ... + @overload + def enable(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def enable(self) -> None: ... + # fmt: on + def enable(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + return self._engine.win_set_enable( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + # fmt: off + @overload + def redraw(self, *, blocking: Literal[True]) -> None: ... + @overload + def redraw(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def redraw(self) -> None: ... + @overload + def redraw(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def redraw(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + return self._engine.win_set_redraw( + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + @overload + def set_style(self, style: str) -> bool: ... + + @overload + def set_style(self, style: str, *, blocking: Literal[True]) -> bool: ... + + @overload + def set_style(self, style: str, *, blocking: Literal[False]) -> FutureResult[bool]: ... + + @overload + def set_style(self, style: str, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + + def set_style(self, style: str, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: + return self._engine.win_set_style( + style=style, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + @overload + def set_ex_style(self, style: str) -> bool: ... + + @overload + def set_ex_style(self, style: str, *, blocking: Literal[False]) -> FutureResult[bool]: ... + + @overload + def set_ex_style(self, style: str, *, blocking: Literal[True]) -> bool: ... + + @overload + def set_ex_style(self, style: str, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + + def set_ex_style(self, style: str, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: + return self._engine.win_set_ex_style( + style=style, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + @overload + def set_region(self, options: str) -> bool: ... + + @overload + def set_region(self, options: str, *, blocking: Literal[True]) -> bool: ... + + @overload + def set_region(self, options: str, *, blocking: Literal[False]) -> FutureResult[bool]: ... + + @overload + def set_region(self, options: str, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + + def set_region(self, options: str, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: + return self._engine.win_set_region( + options=options, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + def set_transparent( + self, transparency: Union[int, Literal['Off']], *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + return self._engine.win_set_transparent( + transparency=transparency, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + def set_trans_color( + self, color: Union[int, str], *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + return self._engine.win_set_trans_color( + color=color, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + @property + def active(self) -> SyncPropertyReturnBool: + return self.is_active() + + def is_active(self) -> bool: + return self._engine.win_is_active( + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + def move( + self, x: int, y: int, *, width: Optional[int] = None, height: Optional[int] = None, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + return self._engine.win_move( + x=x, + y=y, + width=width, + height=height, + title=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + blocking=blocking, + ) + + # fmt: off + @overload + @classmethod + def from_pid(cls, engine: AHK[Literal['v2']], pid: int) -> Window: ... + @overload + @classmethod + def from_pid(cls, engine: Union[AHK[Literal['v1']], AHK[None]], pid: int) -> Optional[Window]: ... + # fmt: on + @classmethod + def from_pid(cls, engine: AHK[Any], pid: int) -> Optional[Window]: + return engine.win_get(title=f'ahk_pid {pid}') + + @classmethod + def from_mouse_position(cls, engine: AHK[Any]) -> Optional[Window]: + return engine.win_get_from_mouse_position() + + +_ControlTargetKwargs = TypedDict('_ControlTargetKwargs', {'title': str, 'control': NotRequired[str]}) + + +class Control: + def __init__(self, window: Window, hwnd: str, control_class: str): + self.window: Window = window + self.hwnd: str = hwnd + self.control_class: str = control_class + self._engine = window._engine + self.use_hwnd: bool = False + + def _get_target_params(self, use_hwnd: Optional[bool] = None) -> _ControlTargetKwargs: + if use_hwnd is None: + use_hwnd = self.use_hwnd + if use_hwnd: + return {'title': f'ahk_id {self.hwnd}'} + else: + return {'title': f'ahk_id {self.window._ahk_id}', 'control': self.control_class} + + # fmt: off + @overload + def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', use_hwnd: Optional[bool] = None) -> None: ... + @overload + def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', use_hwnd: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', use_hwnd: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', use_hwnd: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def click( + self, + *, + button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', + click_count: int = 1, + options: str = '', + use_hwnd: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + return self._engine.control_click( + button=button, + click_count=click_count, + options=options, + title_match_mode=(1, 'Fast'), + detect_hidden_windows=True, + blocking=blocking, + **self._get_target_params(use_hwnd), + ) + + # fmt: off + @overload + def send(self, keys: str, *, use_hwnd: Optional[bool] = None) -> None: ... + @overload + def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: Literal[True]) -> None: ... + @overload + def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def send( + self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + return self._engine.control_send( + keys=keys, + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), + ) + + def get_text( + self, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[str, FutureResult[str]]: + return self._engine.control_get_text( + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), + ) + + # fmt: off + @overload + def get_position(self, *, use_hwnd: Optional[bool] = None) -> Position: ... + @overload + def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Position]: ... + @overload + def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: Literal[True]) -> Position: ... + @overload + def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: bool = True) -> Union[Position, FutureResult[Position]]: ... + # fmt: on + def get_position( + self, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[Position, FutureResult[Position]]: + return self._engine.control_get_position( + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), + ) + + def __repr__(self) -> str: + return f'<{self.__class__.__name__} window={self.window!r}, control_hwnd={self.hwnd!r}, control_class={self.control_class!r}>' diff --git a/ahk/_types.py b/ahk/_types.py new file mode 100644 index 00000000..69a920ab --- /dev/null +++ b/ahk/_types.py @@ -0,0 +1,185 @@ +from __future__ import annotations + +import sys +from typing import Literal +from typing import NamedTuple +from typing import Optional +from typing import Tuple +from typing import Union + +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + + +class Position(NamedTuple): + x: int + y: int + width: int + height: int + + +class Coordinates(NamedTuple): + x: int + y: int + + +CoordModeTargets: TypeAlias = Union[ + Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu'] +] +CoordModeRelativeTo: TypeAlias = Union[Literal['Screen', 'Relative', 'Window', 'Client', '']] + +CoordMode: TypeAlias = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] + +MatchModes: TypeAlias = Literal[1, 2, 3, 'RegEx', ''] +MatchSpeeds: TypeAlias = Literal['Fast', 'Slow', ''] + +TitleMatchMode: TypeAlias = Optional[ + Union[MatchModes, MatchSpeeds, Tuple[Union[MatchModes, MatchSpeeds], Union[MatchSpeeds, MatchModes]]] +] + +_BUTTONS: dict[Union[str, int], str] = { + 1: 'L', + 2: 'R', + 3: 'M', + 'left': 'L', + 'right': 'R', + 'middle': 'M', + 'wheelup': 'WU', + 'wheeldown': 'WD', + 'wheelleft': 'WL', + 'wheelright': 'WR', +} + +MouseButton: TypeAlias = Union[ + int, + Literal[ + 'L', + 'R', + 'M', + 'left', + 'right', + 'middle', + 'wheelup', + 'WU', + 'wheeldown', + 'WD', + 'wheelleft', + 'WL', + 'wheelright', + 'WR', + ], +] + +SendMode: TypeAlias = Literal['Event', 'Input', 'InputThenPlay', 'Play', ''] + +FunctionName = Literal[ + 'AHKBlockInput', + 'AHKClipWait', + 'AHKControlClick', + 'AHKControlGetPos', + 'AHKControlGetText', + 'AHKControlSend', + 'AHKFileSelectFile', + 'AHKFileSelectFolder', + 'AHKGetClipboard', + 'AHKGetClipboardAll', + 'AHKGetCoordMode', + 'AHKGetSendLevel', + 'AHKGetSendMode', + 'AHKGetTitleMatchMode', + 'AHKGetTitleMatchSpeed', + 'AHKGetVolume', + 'AHKGuiNew', + 'AHKImageSearch', + 'AHKInputBox', + 'AHKKeyState', + 'AHKKeyWait', + 'AHKMenuTrayIcon', + 'AHKMenuTrayShow', + 'AHKMenuTrayHide', + 'AHKMenuTrayTip', + 'AHKMsgBox', + 'AHKMouseClickDrag', + 'AHKMouseGetPos', + 'AHKMouseMove', + 'AHKPixelGetColor', + 'AHKPixelSearch', + 'AHKRegRead', + 'AHKRegWrite', + 'AHKRegDelete', + 'AHKSend', + 'AHKSendEvent', + 'AHKSendInput', + 'AHKSendPlay', + 'AHKSendRaw', + 'AHKSetClipboard', + 'AHKSetClipboardAll', + 'AHKSetCoordMode', + 'AHKSetDetectHiddenWindows', + 'AHKSetSendLevel', + 'AHKSetSendMode', + 'AHKSetTitleMatchMode', + 'AHKSetVolume', + 'AHKShowToolTip', + 'AHKSoundBeep', + 'AHKSoundGet', + 'AHKSoundPlay', + 'AHKSoundSet', + 'AHKTrayTip', + 'AHKWinActivate', + 'AHKWinClose', + 'AHKWinExist', + 'AHKWinFromMouse', + 'AHKWinGetControlList', + 'AHKWinGetControlListHwnd', + 'AHKWinGetCount', + 'AHKWinGetExStyle', + 'AHKWinGetID', + 'AHKWinGetIDLast', + 'AHKWinGetList', + 'AHKWinGetMinMax', + 'AHKWinGetPID', + 'AHKWinGetPos', + 'AHKWinGetProcessName', + 'AHKWinGetProcessPath', + 'AHKWinGetStyle', + 'AHKWinGetText', + 'AHKWinGetTitle', + 'AHKWinGetTransColor', + 'AHKWinGetTransparent', + 'AHKWinHide', + 'AHKWinIsActive', + 'AHKWinIsAlwaysOnTop', + 'AHKWinMove', + 'AHKWinSetAlwaysOnTop', + 'AHKWinSetBottom', + 'AHKWinSetDisable', + 'AHKWinSetEnable', + 'AHKWinSetExStyle', + 'AHKWinSetRedraw', + 'AHKWinSetRegion', + 'AHKWinSetStyle', + 'AHKWinSetTitle', + 'AHKWinSetTop', + 'AHKWinSetTransColor', + 'AHKWinSetTransparent', + 'AHKWinShow', + 'AHKWindowList', + 'AHKWinWait', + 'AHKWinWaitActive', + 'AHKWinWaitClose', + 'AHKWinWaitNotActive', + 'AHKClick', + 'AHKSetCapsLockState', + 'AHKSetNumLockState', + 'AHKSetScrollLockState', + 'SetKeyDelay', + 'WinActivateBottom', + 'AHKWinGetClass', + 'AHKWinKill', + 'AHKWinMaximize', + 'AHKWinMinimize', + 'AHKWinRestore', +] diff --git a/ahk/_utils.py b/ahk/_utils.py new file mode 100644 index 00000000..c61c8a26 --- /dev/null +++ b/ahk/_utils.py @@ -0,0 +1,180 @@ +import enum +import logging +import os +import re +import subprocess +import warnings +from shutil import which +from typing import Literal +from typing import Optional + +from ahk.exceptions import AhkExecutableNotFoundError + +HOTKEY_ESCAPE_SEQUENCE_MAP = { + '\n': '`n', + '\t': '`t', + '\r': '`r', + '\a': '`a', + '\b': '`b', + '\f': '`f', + '\v': '`v', + ',': '`,', + '%': '`%', + '`': '``', + ';': '`;', + ':': '`:', +} + +ESCAPE_SEQUENCE_MAP = { + '!': '{!}', + '^': '{^}', + '+': '{+}', + '{': '{{}', + '}': '{}}', + '#': '{#}', + '=': '{=}', +} + +_TRANSLATION_TABLE = str.maketrans(ESCAPE_SEQUENCE_MAP) + +_HOTKEY_TRANSLATION_TABLE = str.maketrans(HOTKEY_ESCAPE_SEQUENCE_MAP) + + +def hotkey_escape(s: str) -> str: + return s.translate(_HOTKEY_TRANSLATION_TABLE) + + +def type_escape(s: str) -> str: + return s.translate(_TRANSLATION_TABLE) + + +class MsgBoxButtons(enum.IntEnum): + OK = 0 + OK_CANCEL = 1 + ABORT_RETRY_IGNORE = 2 + YES_NO_CANCEL = 3 + YES_NO = 4 + RETRY_CANCEL = 5 + CANCEL_TRYAGAIN_CONTINUE = 6 + + +class MsgBoxIcon(enum.IntEnum): + HAND = 16 + QUESTION = 32 + EXCLAMATION = 48 + ASTERISK = 64 + + +class MsgBoxDefaultButton(enum.IntEnum): + SECOND = 256 + THIRD = 512 + FOURTH = 768 + + +class MsgBoxModality(enum.IntEnum): + SYSTEM_MODAL = 4096 + TASK_MODAL = 8192 + ALWAYS_ON_TOP = 262144 + + +class MsgBoxOtherOptions(enum.IntEnum): + HELP_BUTTON = 16384 + TEXT_RIGHT_JUSTIFIED = 524288 + RIGHT_TO_LEFT_READING_ORDER = 1048576 + + +DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' +DEFAULT_EXECUTABLE_PATH_V2 = r'C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe' + + +def _resolve_executable_path(executable_path: str = '', version: Optional[Literal['v1', 'v2']] = None) -> str: + if not executable_path: + executable_path = ( + os.environ.get('AHK_PATH', '') + or (which('AutoHotkeyV2.exe') if version == 'v2' else '') + or (which('AutoHotkey32.exe') if version == 'v2' else '') + or (which('AutoHotkey64.exe') if version == 'v2' else '') + or which('AutoHotkey.exe') + or (which('AutoHotkeyU64.exe') if version != 'v2' else '') + or (which('AutoHotkeyU32.exe') if version != 'v2' else '') + or (which('AutoHotkeyA32.exe') if version != 'v2' else '') + or '' + ) + + if not executable_path: + if version == 'v2': + if os.path.exists(DEFAULT_EXECUTABLE_PATH_V2): + executable_path = DEFAULT_EXECUTABLE_PATH_V2 + else: + if os.path.exists(DEFAULT_EXECUTABLE_PATH): + executable_path = DEFAULT_EXECUTABLE_PATH + + if not executable_path: + raise AhkExecutableNotFoundError( + 'Could not find AutoHotkey.exe on PATH. ' + 'Provide the absolute path with the `executable_path` keyword argument ' + 'or in the AHK_PATH environment variable. ' + 'You can likely resolve this error simply by installing the binary extra with the following command:\n\tpip install "ahk[binary]"' + ) + + if not os.path.exists(executable_path): + raise AhkExecutableNotFoundError(f"executable_path does not seems to exist: '{executable_path}' not found") + + if os.path.isdir(executable_path): + raise AhkExecutableNotFoundError( + f'The path {executable_path} appears to be a directory, but should be a file.' + ' Please specify the *full path* to the autohotkey.exe executable file' + ) + executable_path = str(executable_path) + if not executable_path.endswith('.exe'): + warnings.warn( + 'executable_path does not appear to have a .exe extension. This may be the result of a misconfiguration.' + ) + + return executable_path + + +_version_detection_script = '''\ +#NoTrayIcon +version := Format("{}", A_AhkVersion) +filename := "*" +encoding := "UTF-8" +mode := "w" +stdout := FileOpen(filename, mode, encoding) +stdout.Write(version) +stdout.Read(0) +''' + + +def _get_executable_version(executable_path: str) -> str: + process = subprocess.Popen( + [executable_path, '/ErrorStdout', '/CP65001', '*'], + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + stdout, stderr = process.communicate(_version_detection_script, timeout=2) + assert re.match(r'^\d+\.', stdout) + return stdout.strip() + + +def _get_executable_major_version(executable_path: str) -> Literal['v1', 'v2']: + version = _get_executable_version(executable_path) + match = re.match(r'^(\d+)\.', version) + if not match: + raise ValueError(f'Unexpected version {version!r}') + major_version = match.group(1) + if major_version == '1': + return 'v1' + elif major_version == '2': + return 'v2' + else: + raise ValueError(f'Unexpected version {version!r}') + + +def try_remove(name: str) -> None: + try: + os.remove(name) + except Exception as e: + logging.debug(f'Ignoring removal exception {e}') diff --git a/ahk/autohotkey.py b/ahk/autohotkey.py deleted file mode 100644 index 30c36785..00000000 --- a/ahk/autohotkey.py +++ /dev/null @@ -1,39 +0,0 @@ -from collections import deque -from ahk.mouse import MouseMixin -from ahk.window import Window, WindowMixin -from ahk.script import ScriptEngine -from ahk.screen import ScreenMixin -from ahk.keyboard import KeyboardMixin -from ahk.sound import SoundMixin - -class AHK(WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin): - pass - - -class ActionChain(AHK): - def __init__(self, *args, **kwargs): - self._actions = deque() - super().__init__(*args, **kwargs) - - def run_script(self, *args, **kwargs): - """ - override run_script to defer to queue - """ - kwargs['decode'] = False # - self._actions.appendleft((args, kwargs)) - - def perform(self): - results = [] - while self._actions: - args, kwargs = self._actions.pop() - results.append(super().run_script(*args, **kwargs)) - return results - - def sleep(self, n): - """ - :param n: how long (in seconds) to sleep - :return: - """ - n = n * 1000 # convert to milliseconds - script = self.render_template('base.ahk', body=f'Sleep {n}', directives={'#Persistent',}) - self.run_script(script) diff --git a/ahk/directives.py b/ahk/directives.py index 82f97f8d..dbb54824 100644 --- a/ahk/directives.py +++ b/ahk/directives.py @@ -1,4 +1,6 @@ from types import SimpleNamespace +from typing import Any +from typing import NoReturn class DirectiveMeta(type): @@ -6,14 +8,19 @@ class DirectiveMeta(type): Overrides __str__ so directives with no arguments can be used without instantiation Overrides __hash__ to make objects 'unique' based upon a hash of the str representation """ - def __str__(cls): - return f"#{cls.__name__}" - def __hash__(self): + def __str__(cls) -> str: + return f'#{cls.__name__}' + + def __hash__(self) -> int: return hash(str(self)) - def __eq__(cls, other): - return str(cls) == other + def __eq__(cls, other: Any) -> bool: + return bool(str(cls) == other) + + @property + def apply_to_hotkeys_process(cls) -> bool: + return False class Directive(SimpleNamespace, metaclass=DirectiveMeta): @@ -22,25 +29,27 @@ class Directive(SimpleNamespace, metaclass=DirectiveMeta): They are designed to be hashable and comparable with string equivalent of AHK directive. Directives that don't require arguments do not need to be instantiated. """ - def __init__(self, **kwargs): - super().__init__(name=self.name, **kwargs) + + def __init__(self, **kwargs: Any): + apply_to_hotkeys = kwargs.pop('apply_to_hotkeys_process', False) + super().__init__(name=self.name, apply_to_hotkeys_process=apply_to_hotkeys, **kwargs) self._kwargs = kwargs @property - def name(self): + def name(self) -> str: return self.__class__.__name__ - def __str__(self): + def __str__(self) -> str: if self._kwargs: arguments = ' '.join(str(value) for key, value in self._kwargs.items()) else: arguments = '' - return f"#{self.name} {arguments}".rstrip() + return f'#{self.name} {arguments}'.rstrip() - def __eq__(self, other): - return str(self) == other + def __eq__(self, other: Any) -> bool: + return bool(str(self) == other) - def __hash__(self): + def __hash__(self) -> int: # type: ignore[override] return hash(str(self)) @@ -49,7 +58,7 @@ class AllowSameLineComments(Directive): class ClipboardTimeout(Directive): - def __init__(self, milliseconds=0, **kwargs): + def __init__(self, milliseconds: int = 0, **kwargs: Any): kwargs['milliseconds'] = milliseconds super().__init__(**kwargs) @@ -67,7 +76,7 @@ class HotKeyModifierTimeout(HotKeyInterval): class Include(Directive): - def __init__(self, include_name, **kwargs): + def __init__(self, include_name: str, **kwargs: Any): kwargs['include_name'] = include_name super().__init__(**kwargs) @@ -77,7 +86,7 @@ class IncludeAgain(Include): class InputLevel(Directive): - def __init__(self, level, **kwargs): + def __init__(self, level: int, **kwargs: Any): kwargs['level'] = level super().__init__(**kwargs) @@ -91,19 +100,19 @@ class InstallMouseHook(Directive): class KeyHistory(Directive): - def __init__(self, limit=40, **kwargs): + def __init__(self, limit: int = 40, **kwargs: Any): kwargs['limit'] = limit super().__init__(**kwargs) class MaxHotkeysPerInterval(Directive): - def __init__(self, value, **kwargs): + def __init__(self, value: int, **kwargs: Any): kwargs['value'] = value super().__init__(**kwargs) class MaxMem(Directive): - def __init__(self, megabytes: int, **kwargs): + def __init__(self, megabytes: int, **kwargs: Any): if megabytes < 1: raise ValueError('megabytes cannot be less than 1') if megabytes > 4095: @@ -113,41 +122,29 @@ def __init__(self, megabytes: int, **kwargs): class MaxThreads(Directive): - def __init__(self): - raise NotImplemented + def __init__(self) -> NoReturn: + raise NotImplementedError() class MaxThreadsBuffer(Directive): - def __init__(self): - raise NotImplemented + def __init__(self) -> NoReturn: + raise NotImplementedError() class MaxThreadsPerHotkey(Directive): - def __init__(self): - raise NotImplemented + def __init__(self) -> NoReturn: + raise NotImplementedError() class MenuMaskKey(Directive): - def __init__(self): - raise NotImplemented - - -class NoEnv(Directive): - pass + def __init__(self) -> NoReturn: + raise NotImplementedError() class NoTrayIcon(Directive): pass -class Persistent(Directive): - pass - - -class SingleInstance(Directive): - pass - - class UseHook(Directive): pass diff --git a/ahk/exceptions.py b/ahk/exceptions.py new file mode 100644 index 00000000..0f850221 --- /dev/null +++ b/ahk/exceptions.py @@ -0,0 +1,17 @@ +class AHKBaseException(Exception): + # TODO: make existing exceptions subclasses of this + ... + + +class WindowNotFoundException(AHKBaseException): ... + + +class AHKProtocolError(AHKBaseException): ... + + +class AHKExecutionException(AHKBaseException): + pass + + +class AhkExecutableNotFoundError(AHKBaseException, EnvironmentError): + pass diff --git a/ahk/extensions.py b/ahk/extensions.py new file mode 100644 index 00000000..9b920421 --- /dev/null +++ b/ahk/extensions.py @@ -0,0 +1,186 @@ +from __future__ import annotations + +import asyncio +import itertools +import sys +import typing +import warnings +from collections import deque +from dataclasses import dataclass +from typing import Any +from typing import Callable +from typing import TypeVar + +if sys.version_info < (3, 10): + from typing_extensions import ParamSpec + from typing_extensions import Concatenate +else: + from typing import ParamSpec + from typing import Concatenate + +from .directives import Include + + +@dataclass +class _ExtensionEntry: + extension: Extension + method: Callable[..., Any] + + +T = TypeVar('T') +P = ParamSpec('P') + + +if typing.TYPE_CHECKING: + from ahk import AHK, AsyncAHK, Window, AsyncWindow + + TAHK = TypeVar('TAHK', bound=typing.Union[AHK[Any], AsyncAHK[Any]]) + TWindow = TypeVar('TWindow', bound=typing.Union[Window, AsyncWindow]) + + +@dataclass +class _ExtensionMethodRegistry: + sync_methods: dict[str, Callable[..., Any]] + async_methods: dict[str, Callable[..., Any]] + sync_window_methods: dict[str, Callable[..., Any]] + async_window_methods: dict[str, Callable[..., Any]] + + def register(self, f: Callable[Concatenate[TAHK, P], T]) -> Callable[Concatenate[TAHK, P], T]: + if asyncio.iscoroutinefunction(f): + if f.__name__ in self.async_methods: + warnings.warn( + f'Method of name {f.__name__!r} has already been registered. ' + f'Previously registered method {self.async_methods[f.__name__]!r} ' + f'will be overridden by {f!r}', + stacklevel=2, + ) + self.async_methods[f.__name__] = f + else: + if f.__name__ in self.sync_methods: + warnings.warn( + f'Method of name {f.__name__!r} has already been registered. ' + f'Previously registered method {self.sync_methods[f.__name__]!r} ' + f'will be overridden by {f!r}', + stacklevel=2, + ) + self.sync_methods[f.__name__] = f + return f + + def register_window_method(self, f: Callable[Concatenate[TWindow, P], T]) -> Callable[Concatenate[TWindow, P], T]: + if asyncio.iscoroutinefunction(f): + if f.__name__ in self.async_window_methods: + warnings.warn( + f'Method of name {f.__name__!r} has already been registered. ' + f'Previously registered method {self.async_window_methods[f.__name__]!r} ' + f'will be overridden by {f!r}', + stacklevel=2, + ) + self.async_window_methods[f.__name__] = f + else: + if f.__name__ in self.sync_window_methods: + warnings.warn( + f'Method of name {f.__name__!r} has already been registered. ' + f'Previously registered method {self.sync_window_methods[f.__name__]!r} ' + f'will be overridden by {f!r}', + stacklevel=2, + ) + self.sync_window_methods[f.__name__] = f + return f + + def merge(self, other: _ExtensionMethodRegistry) -> None: + for name, method in other.methods: + self.register(method) + for name, method in other.window_methods: + self.register_window_method(method) + + @property + def methods(self) -> list[tuple[str, Callable[..., Any]]]: + return list(itertools.chain(self.async_methods.items(), self.sync_methods.items())) + + @property + def window_methods(self) -> list[tuple[str, Callable[..., Any]]]: + return list(itertools.chain(self.async_window_methods.items(), self.sync_window_methods.items())) + + +_extension_registry: dict[Extension, _ExtensionMethodRegistry] = {} + + +class Extension: + def __init__( + self, + script_text: str | None = None, + includes: list[str] | None = None, + dependencies: list[Extension] | None = None, + requires_autohotkey: typing.Literal['v1', 'v2'] | None = None, + ): + self._requires = requires_autohotkey + self._text: str = script_text or '' + self._includes: list[str] = includes or [] + self.dependencies: list[Extension] = dependencies or [] + self._extension_method_registry: _ExtensionMethodRegistry = _ExtensionMethodRegistry( + sync_methods={}, async_methods={}, sync_window_methods={}, async_window_methods={} + ) + _extension_registry[self] = self._extension_method_registry + + @property + def script_text(self) -> str: + return self._text + + @script_text.setter + def script_text(self, new_script: str) -> None: + self._text = new_script + + @property + def includes(self) -> list[Include]: + return [Include(inc) for inc in self._includes] + + def register(self, f: Callable[Concatenate[TAHK, P], T]) -> Callable[Concatenate[TAHK, P], T]: + self._extension_method_registry.register(f) + return f + + register_method = register + + def register_window_method(self, f: Callable[Concatenate[TWindow, P], T]) -> Callable[Concatenate[TWindow, P], T]: + self._extension_method_registry.register_window_method(f) + return f + + def __hash__(self) -> int: + return hash((self._text, tuple(self.includes), tuple(self.dependencies))) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, Extension): + return hash(self) == hash(other) + return NotImplemented + + +def _resolve_extension(extension: Extension, seen: set[Extension]) -> list[Extension]: + ret: deque[Extension] = deque() + todo = [extension] + while todo: + ext = todo.pop() + if ext in seen: + continue + ret.appendleft(ext) + seen.add(ext) + todo.extend(ext.dependencies) + return list(ret) + + +def _resolve_extensions(extensions: list[Extension]) -> list[Extension]: + seen: set[Extension] = set() + ret: list[Extension] = [] + for ext in extensions: + ret.extend(_resolve_extension(ext, seen=seen)) + return ret + + +def _resolve_includes(extensions: list[Extension]) -> list[Include]: + extensions = _resolve_extensions(extensions) + ret = [] + seen: set[Include] = set() + for ext in extensions: + for include in ext.includes: + if include in seen: + continue + ret.append(include) + return ret diff --git a/ahk/keyboard.py b/ahk/keyboard.py deleted file mode 100644 index 5a5b9abf..00000000 --- a/ahk/keyboard.py +++ /dev/null @@ -1,211 +0,0 @@ -import ast -import warnings - -from ahk.script import ScriptEngine -from ahk.utils import escape_sequence_replace -from ahk.keys import Key -from ahk.directives import InstallKeybdHook, InstallMouseHook - -class Hotkey: - def __init__(self, engine: ScriptEngine, hotkey: str, script: str): - self.hotkey = hotkey - self.script = script - self.engine = engine - - @property - def running(self): - return hasattr(self, '_proc') - - def _start(self, script): - try: - proc = self.engine.run_script(script, blocking=False) - yield proc - finally: - self._stop() - - def start(self): - """ - Starts an AutoHotkey process with the hotkey script - """ - if self.running: - raise RuntimeError('Hotkey is already running') - script = self.engine.render_template('hotkey.ahk', blocking=False, script=self.script, hotkey=self.hotkey) - self._gen = self._start(script) - proc = next(self._gen) - self._proc = proc - - def _stop(self): - if not self.running: - return - self._proc.terminate() - del self._proc - - def stop(self): - """ - Stops the process if it is running - """ - if not self.running: - raise RuntimeError('Hotkey is not running') - try: - next(self._gen) - except StopIteration: - pass - finally: - del self._gen - - -class KeyboardMixin(ScriptEngine): - def hotkey(self, *args, **kwargs): - """ - Convenience function for creating ``Hotkey`` instance using current engine. - - :param args: - :param kwargs: - :return: - """ - return Hotkey(engine=self, *args, **kwargs) - - def key_state(self, key_name, mode=None) -> bool: - """ - Check the state of a key. - - https://autohotkey.com/docs/commands/GetKeyState.htm - - :param key_name: the name of the key (or virtual key code) - :param mode: see AHK docs - :return: True if pressed down, else False - """ - script = self.render_template('keyboard/key_state.ahk', key_name=key_name, mode=mode, directives=(InstallMouseHook, InstallKeybdHook)) - result = ast.literal_eval(self.run_script(script)) - return bool(result) - - def key_wait(self, key_name, timeout: int=None, logical_state=False, released=False): - """ - Wait for key to be pressed or released (default is pressed; specify ``released=True`` to wait for key release). - - https://autohotkey.com/docs/commands/KeyWait.htm - - :param key_name: The name of the key - :param timeout: how long (in seconds) to wait for the key. If not specified, waits indefinitely - :param logical_state: Check the logical state of the key, which is the state that the OS and the active window believe the key to be in (not necessarily the same as the physical state). This option is ignored for joystick buttons. - :param released: Set to True to wait for the key to be released rather than pressed - :return: - :raises TimeoutError: if the key was not pressed (or released, if specified) within timeout - """ - options = '' - if not released: - options += 'D' - if logical_state: - options += 'L' - if timeout: - options += f'T{timeout}' - script = self.render_template('keyboard/key_wait.ahk', key_name=key_name, options=options) - result = self.run_script(script) - if result == "1": - raise TimeoutError(f'timed out waiting for {key_name}') - - def type(self, s): - """ - Sends keystrokes using send_input, also escaping the string for use in AHK. - """ - s = escape_sequence_replace(s) - self.send_input(s) - - def send(self, s, raw=False, delay=None): - """ - https://autohotkey.com/docs/commands/Send.htm - - :param s: - :param raw: - :param delay: - :return: - """ - script = self.render_template('keyboard/send.ahk', s=s, raw=raw, delay=delay) - return self.run_script(script) - - def send_raw(self, s, delay=None): - """ - https://autohotkey.com/docs/commands/Send.htm - - :param s: - :param delay: - :return: - """ - return self.send(s, raw=True, delay=delay) - - def send_input(self, s): - """ - https://autohotkey.com/docs/commands/Send.htm - - :param s: - :return: - """ - if len(s) > 5000: - warnings.warn('String length greater than allowed. Characters beyond 5000 may not be sent. ' - 'See https://autohotkey.com/docs/commands/Send.htm#SendInputDetail for details.') - - script = self.render_template('keyboard/send_input.ahk', s=s) - self.run_script(script) - - def send_play(self, s): - """ - https://autohotkey.com/docs/commands/Send.htm - - - :param s: - :return: - """ - script = self.render_template('keyboard/send_play.ahk', s=s) - self.run_script(script) - - def send_event(self, s, delay=None): - """ - https://autohotkey.com/docs/commands/Send.htm - - :param s: - :param delay: - :return: - """ - script = self.render_template('keyboard/send_event.ahk', s=s, delay=delay) - self.run_script(script) - - def key_press(self, key, release=True): - """ - Press and (optionally) release a single key - - :param key: - :param release: - :return: - """ - - self.key_down(key) - if release: - self.key_up(key) - - def key_release(self, key): - """ - Release a key that is currently in pressed down state - - :param key: - :return: - """ - if isinstance(key, str): - key = Key(key_name=key) - return self.send_input(key.UP) - - def key_down(self, key): - """ - Press down a key (without releasing it) - - :param key: - :return: - """ - if isinstance(key, str): - key = Key(key_name=key) - return self.send_input(key.DOWN) - - def key_up(self, key): - """ - Alias for :meth:~`KeyboardMixin.key_release` - """ - return self.key_release(key) diff --git a/ahk/keys.py b/ahk/keys.py index 706fb493..6eb0c851 100644 --- a/ahk/keys.py +++ b/ahk/keys.py @@ -1,52 +1,61 @@ -""" -The ahk.keys module contains some useful classes for working with 'special' keys. -It also -""" +from __future__ import annotations + +from typing import Any +from typing import Dict +from typing import Final +from typing import List +from typing import Optional +from typing import Protocol +from typing import runtime_checkable +from typing import Union class Key: - is_modifier = False - symbol = '' + is_modifier: bool = False + symbol: str = '' - def __init__(self, key_name): - self._key_name = key_name + def __init__(self, key_name: str): + self._key_name: str = key_name @property - def name(self): + def name(self) -> str: return self._key_name @property - def DOWN(self): + def DOWN(self) -> str: return '{' + f'{self.name} down' + '}' @property - def UP(self): + def UP(self) -> str: return '{' + f'{self.name} up' + '}' - def __str__(self): + def __str__(self) -> str: return '{' + self.name + '}' - def __hash__(self): + def __hash__(self) -> int: return hash(str(self)) - def __mul__(self, n): + def __mul__(self, n: int) -> str: if not isinstance(n, int): - raise TypeError(f"Unsupported operand type(s) for *: '{self.__class__.__name__}' and '{type(n)}'") + return NotImplemented return '{' + f'{self.name} {n}' + '}' - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, Key) or isinstance(other, str): + return NotImplemented return hash(self) == hash(other) - def __add__(self, s): + def __add__(self, s: str) -> str: return str(self) + s - def __repr__(self): + def __repr__(self) -> str: return f'{self.__class__.__name__}(key_name={self.name!r})' - def __format__(self, format_spec): + def __format__(self, format_spec: Any) -> str: return str(self) +SYMBOLS: Dict[str, str] SYMBOLS = { 'Win': '#', 'LWin': '<#', @@ -60,23 +69,29 @@ def __format__(self, format_spec): 'Control': '^', 'LControl': '<^', 'RControl': '>^', - } class KeyCombo: - def __init__(self, *modifiers): - self._s = None - self.modifiers = list(modifiers) + def __init__(self, *modifiers: KeyModifier): + self._s: Optional[str] = None + self.modifiers: List[KeyModifier] = list(modifiers) assert all([isinstance(key, KeyModifier) for key in self.modifiers]), 'Keys must be modifiers' - def __str__(self): + def __str__(self) -> str: s = ''.join(mod.symbol for mod in self.modifiers) if self._s is not None: s += self._s return s - def __add__(self, other): + def __add__(self, other: object) -> Any: + if ( + not isinstance(other, KeyCombo) + and not isinstance(other, KeyModifier) + and not isinstance(other, Key) + and not isinstance(other, str) + ): + return NotImplemented if self._s is not None: raise ValueError('Key combo is already terminated') if isinstance(other, KeyCombo): @@ -85,31 +100,37 @@ def __add__(self, other): combo = combo + other._s return combo if isinstance(other, KeyModifier): - self.modifiers.append(other) + self.modifiers.append(other) # XXX: ???? elif isinstance(other, Key) or isinstance(other, str): self._s = str(other) return self - else: - raise TypeError(f"unsupported operand type(s) for +: '{self.__class__.__name__}' and '{type(other)}'") - def __repr__(self): + def __repr__(self) -> str: key_modifiers = ', '.join(repr(mod) for mod in self.modifiers) return f'{self.__class__.__name__}({key_modifiers}){f"+{self._s!r}" if self._s else f""}' +@runtime_checkable +class Stringable(Protocol): + def __str__(self) -> str: ... + + class KeyModifier(Key): is_modifier = True @property - def symbol(self): + def symbol(self) -> str: # type: ignore[override] return SYMBOLS.get(self.name, str(self)) - def __add__(self, other): + def __add__(self, other: object) -> Any: if isinstance(other, KeyModifier): return KeyCombo(self, other) elif isinstance(other, KeyCombo): return other + self + if not isinstance(other, Stringable): + return NotImplemented + return self.symbol + str(other) @@ -118,79 +139,78 @@ class KEYS: KEYS constants REF: https://autohotkey.com/docs/KeyList.htm """ - CAPS_LOCK = Key('CapsLock') - CapsLock = CAPS_LOCK - SCROLL_LOCK = Key('ScrollLock') - ScrollLock = SCROLL_LOCK - SPACE = Key('Space') - - TAB = Key('Tab') - Tab = TAB - ENTER = Key('Enter') - Enter = ENTER - ESCAPE = Key('Escape') - BACKSPACE = Key('Backspace') - Backspace = BACKSPACE - UP = Key('Up') - Up = UP - DOWN = Key('Down') - Down = DOWN - LEFT = Key('Left') - Left = LEFT - RIGHT = Key('Right') - Right = RIGHT - DELETE = Key('Delete') - DEL = DELETE - Delete = DELETE - Del = DELETE - - WIN = KeyModifier('Win') - Win = WIN - LEFT_WIN = KeyModifier('LWin') - LWin = LEFT_WIN - RIGHT_WIN = KeyModifier('RWin') - RWin = RIGHT_WIN - CONTROL = KeyModifier('Control') - Control = CONTROL - CTRL = CONTROL - Ctrl = CONTROL - LEFT_CONTROL = KeyModifier('LControl') - LCtrl = LEFT_CONTROL - LControl = LEFT_CONTROL - RIGHT_CONTROL = KeyModifier('RControl') - RCtrl = RIGHT_CONTROL - RControl = RIGHT_CONTROL - ALT = KeyModifier('Alt') - Alt = ALT - LEFT_ALT = KeyModifier('LAlt') - LAlt = LEFT_ALT - RIGHT_ALT = KeyModifier('RAlt') - RAlt = RIGHT_ALT - SHIFT = KeyModifier('Shift') - Shift = SHIFT - LEFT_SHIFT = KeyModifier("LShift") - LShift = LEFT_SHIFT - RIGHT_SHIFT = KeyModifier('RShift') - RShift = RIGHT_SHIFT - NUMPAD_DOT = Key('NumpadDot') - NumpadDot = NUMPAD_DOT - NUMPAD_DEL = Key('NumpadDel') - NumpadDel = NUMPAD_DEL - NUM_LOCK = Key('NumLock') - NumLock = NUM_LOCK - NUMPAD_ADD = Key('NumpadAdd') - NUMPAD_DIV = Key('NumpadDiv') - NUMPAD_SUB = Key('NumpadSub') - NUMPAD_MULT = Key('NumpadMult') - NUMPAD_ENTER = Key('NumpadEnter') - NumpadAdd = NUMPAD_ADD - NumpadDiv = NUMPAD_DIV - NumpadSub = NUMPAD_SUB - NumpadMult = NUMPAD_MULT - NumpadEnter = NUMPAD_ENTER - - -def _init_keys(): + + CAPS_LOCK: Final[Key] = Key('CapsLock') + CapsLock: Final[Key] = CAPS_LOCK + SCROLL_LOCK: Final[Key] = Key('ScrollLock') + ScrollLock: Final[Key] = SCROLL_LOCK + SPACE: Final[Key] = Key('Space') + TAB: Final[Key] = Key('Tab') + Tab: Final[Key] = TAB + ENTER: Final[Key] = Key('Enter') + Enter: Final[Key] = ENTER + ESCAPE: Final[Key] = Key('Escape') + BACKSPACE: Final[Key] = Key('Backspace') + Backspace: Final[Key] = BACKSPACE + UP: Final[Key] = Key('Up') + Up: Final[Key] = UP + DOWN: Final[Key] = Key('Down') + Down: Final[Key] = DOWN + LEFT: Final[Key] = Key('Left') + Left: Final[Key] = LEFT + RIGHT: Final[Key] = Key('Right') + Right: Final[Key] = RIGHT + DELETE: Final[Key] = Key('Delete') + DEL: Final[Key] = DELETE + Delete: Final[Key] = DELETE + Del: Final[Key] = DELETE + WIN: Final[KeyModifier] = KeyModifier('Win') + Win: Final[Key] = WIN + LEFT_WIN: Final[KeyModifier] = KeyModifier('LWin') + LWin: Final[Key] = LEFT_WIN + RIGHT_WIN: Final[KeyModifier] = KeyModifier('RWin') + RWin: Final[Key] = RIGHT_WIN + CONTROL: Final[KeyModifier] = KeyModifier('Control') + Control: Final[Key] = CONTROL + CTRL: Final[Key] = CONTROL + Ctrl: Final[Key] = CONTROL + LEFT_CONTROL: Final[KeyModifier] = KeyModifier('LControl') + LCtrl: Final[Key] = LEFT_CONTROL + LControl: Final[Key] = LEFT_CONTROL + RIGHT_CONTROL: Final[KeyModifier] = KeyModifier('RControl') + RCtrl: Final[Key] = RIGHT_CONTROL + RControl: Final[Key] = RIGHT_CONTROL + ALT: Final[KeyModifier] = KeyModifier('Alt') + Alt: Final[Key] = ALT + LEFT_ALT: Final[KeyModifier] = KeyModifier('LAlt') + LAlt: Final[Key] = LEFT_ALT + RIGHT_ALT: Final[KeyModifier] = KeyModifier('RAlt') + RAlt: Final[Key] = RIGHT_ALT + SHIFT: Final[KeyModifier] = KeyModifier('Shift') + Shift: Final[Key] = SHIFT + LEFT_SHIFT: Final[KeyModifier] = KeyModifier('LShift') + LShift: Final[Key] = LEFT_SHIFT + RIGHT_SHIFT: Final[KeyModifier] = KeyModifier('RShift') + RShift: Final[Key] = RIGHT_SHIFT + NUMPAD_DOT: Final[Key] = Key('NumpadDot') + NumpadDot: Final[Key] = NUMPAD_DOT + NUMPAD_DEL: Final[Key] = Key('NumpadDel') + NumpadDel: Final[Key] = NUMPAD_DEL + NUM_LOCK: Final[Key] = Key('NumLock') + NumLock: Final[Key] = NUM_LOCK + NUMPAD_ADD: Final[Key] = Key('NumpadAdd') + NUMPAD_DIV: Final[Key] = Key('NumpadDiv') + NUMPAD_SUB: Final[Key] = Key('NumpadSub') + NUMPAD_MULT: Final[Key] = Key('NumpadMult') + NUMPAD_ENTER: Final[Key] = Key('NumpadEnter') + NumpadAdd: Final[Key] = NUMPAD_ADD + NumpadDiv: Final[Key] = NUMPAD_DIV + NumpadSub: Final[Key] = NUMPAD_SUB + NumpadMult: Final[Key] = NUMPAD_MULT + NumpadEnter: Final[Key] = NUMPAD_ENTER + + +def _init_keys() -> None: '''put this in a function to avoid polluting global namespace''' for i in range(0, 10): # set numpad keys @@ -216,5 +236,8 @@ def _init_keys(): __all__ = [name for name in dir(KEYS) if not name.startswith('_')] -def __getattr__(name): - return getattr(KEYS, name) +def __getattr__(name: str) -> Union[Key, KeyModifier]: + obj = getattr(KEYS, name, None) + if not isinstance(obj, Key) and not isinstance(obj, KeyModifier): + raise AttributeError(f'module {__name__!r} has no attribute {name!r}') + return obj diff --git a/ahk/message.py b/ahk/message.py new file mode 100644 index 00000000..c33ffd8e --- /dev/null +++ b/ahk/message.py @@ -0,0 +1,343 @@ +from __future__ import annotations + +import ast +import base64 +import itertools +import string +import sys +from abc import abstractmethod +from base64 import b64encode +from typing import Any +from typing import cast +from typing import Generator +from typing import List +from typing import NoReturn +from typing import Optional +from typing import Protocol +from typing import runtime_checkable +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING + +if sys.version_info >= (3, 10): + from typing import TypeGuard +else: + from typing_extensions import TypeGuard +from typing import TypeVar +from typing import Union + +from ahk.exceptions import AHKExecutionException +from ahk._types import Position, Coordinates + + +class OutOfMessageTypes(Exception): ... + + +@runtime_checkable +class BytesLineReadable(Protocol): + def readline(self) -> bytes: ... + + +def is_window_control_list_response(resp_obj: object) -> TypeGuard[Tuple[str, List[Tuple[str, str]]]]: + if not isinstance(resp_obj, tuple): + return False + if len(resp_obj) != 2: + return False + if not isinstance(resp_obj[0], str): + return False + expected_win_list = resp_obj[1] + if not isinstance(expected_win_list, list): + return False + for obj in expected_win_list: + if not isinstance(obj, tuple): + return False + if len(obj) != 2: + return False + id_, klass = obj + if not isinstance(id_, str) or not isinstance(klass, str): + return False + return True + + +def is_winget_response_type( + obj: object, +) -> TypeGuard[ + Union[ + 'StringResponseMessage', + 'IntegerResponseMessage', + 'WindowListResponseMessage', + 'WindowControlListResponseMessage', + ] +]: + if isinstance(obj, StringResponseMessage): + return True + elif isinstance(obj, IntegerResponseMessage): + return True + elif isinstance(obj, WindowListResponseMessage): + return True + elif isinstance(obj, WindowControlListResponseMessage): + return True + elif isinstance(obj, NoValueResponseMessage): + return True + else: + return False + + +T_ResponseMessageType = TypeVar('T_ResponseMessageType', bound='ResponseMessage') + + +def tom_generator() -> Generator[bytes, None, None]: + characters = string.digits + string.ascii_letters + for a, b, c in itertools.product(characters, characters, characters): + yield bytes(f'{a}{b}{c}', encoding='ascii') + raise OutOfMessageTypes('Out of TOMS') + + +TOMS = tom_generator() + + +class ResponseMessage: + _type_order_mark = next(TOMS) + + @classmethod + def fqn(cls) -> str: + return f'{cls.__module__}.{cls.__qualname__}' + + @classmethod + def __init_subclass__(cls: Type[T_ResponseMessageType], **kwargs: Any) -> None: + tom = next(TOMS) + cls._type_order_mark = tom + assert tom not in _message_registry, f'cannot register class {cls!r} with TOM {tom!r} which is already in use' + _message_registry[tom] = cls + super().__init_subclass__(**kwargs) + + def __init__(self, raw_content: bytes, engine: Optional[Union[AsyncAHK[Any], AHK[Any]]] = None): + self._raw_content: bytes = raw_content + self._engine: Optional[Union[AsyncAHK[Any], AHK[Any]]] = engine + + def __repr__(self) -> str: + return f'ResponseMessage' + + @staticmethod + def _tom_lookup(tom: bytes) -> 'ResponseMessageClassTypes': + klass = _message_registry.get(tom) + if klass is None: + raise ValueError(f'No such TOM {tom!r}') + return klass + + @classmethod + def from_bytes( + cls: Type[T_ResponseMessageType], b: bytes, engine: Optional[Union[AsyncAHK[Any], AHK[Any]]] = None + ) -> 'ResponseMessageTypes': + tom, _, message_bytes = b.split(b'\n', 2) + klass = cls._tom_lookup(tom) + return klass(raw_content=message_bytes, engine=engine) + + def to_bytes(self) -> bytes: + content_lines = self._raw_content.count(b'\n') + return self._type_order_mark + b'\n' + bytes(str(content_lines), 'ascii') + b'\n' + self._raw_content + + @abstractmethod + def unpack(self) -> Any: + return NotImplemented + + +_message_registry: dict[bytes, 'ResponseMessageClassTypes'] = {} + + +class TupleResponseMessage(ResponseMessage): + def unpack(self) -> Tuple[Any, ...]: + s = self._raw_content.decode(encoding='utf-8') + val = ast.literal_eval(s) + assert isinstance(val, tuple) + return val + + +class CoordinateResponseMessage(ResponseMessage): + def unpack(self) -> Coordinates: + s = self._raw_content.decode(encoding='utf-8') + val = ast.literal_eval(s) + assert isinstance(val, tuple) + x, y = cast(Tuple[int, int], val) + return Coordinates(x, y) + + +class IntegerResponseMessage(ResponseMessage): + def unpack(self) -> int: + s = self._raw_content.decode(encoding='utf-8') + val = ast.literal_eval(s) + assert isinstance(val, int) + return val + + +class BooleanResponseMessage(IntegerResponseMessage): + def unpack(self) -> bool: + val = super().unpack() + assert val in (1, 0) + return bool(val) + + +class StringResponseMessage(ResponseMessage): + def unpack(self) -> str: + return self._raw_content.decode('utf-8') + + +class WindowListResponseMessage(ResponseMessage): + def unpack(self) -> Union[List[Window], List[AsyncWindow]]: + from ._async.engine import AsyncAHK + from ._async.window import AsyncWindow + from ._sync.window import Window + from ._sync.engine import AHK + + s = self._raw_content.decode(encoding='utf-8') + s = s.rstrip(',') + window_ids = s.split(',') + if isinstance(self._engine, AsyncAHK): + async_ret = [AsyncWindow(engine=self._engine, ahk_id=ahk_id) for ahk_id in window_ids if ahk_id] + return async_ret + elif isinstance(self._engine, AHK): + ret = [Window(engine=self._engine, ahk_id=ahk_id) for ahk_id in window_ids if ahk_id] + return ret + else: + raise ValueError(f'Invalid engine: {self._engine!r}') + + +class NoValueResponseMessage(ResponseMessage): + def unpack(self) -> None: + assert self._raw_content == b'\xee\x80\x80', f'Unexpected or Malformed response: {self._raw_content!r}' + return None + + +class ExceptionResponseMessage(ResponseMessage): + _exception_type: Type[Exception] = AHKExecutionException + + def unpack(self) -> NoReturn: + s = self._raw_content.decode(encoding='utf-8') + raise self._exception_type(s) + + +class WindowControlListResponseMessage(ResponseMessage): + def unpack(self) -> Union[List[AsyncControl], List[Control]]: + from ._async.engine import AsyncAHK + from ._async.window import AsyncWindow, AsyncControl + from ._sync.window import Window, Control + from ._sync.engine import AHK + + s = self._raw_content.decode(encoding='utf-8') + val = ast.literal_eval(s) + assert is_window_control_list_response(val) + assert self._engine is not None + assert val is not None + ahkid, controls = val + if isinstance(self._engine, AsyncAHK): + ret_async: List[AsyncControl] = [] + async_window = AsyncWindow(engine=self._engine, ahk_id=ahkid) + for control in controls: + hwnd, classname = control + async_ctrl = AsyncControl(window=async_window, hwnd=hwnd, control_class=classname) + ret_async.append(async_ctrl) + return ret_async + elif isinstance(self._engine, AHK): + ret_sync: List[Control] = [] + window = Window(engine=self._engine, ahk_id=ahkid) + for control in controls: + hwnd, classname = control + ctrl = Control(window=window, hwnd=hwnd, control_class=classname) + ret_sync.append(ctrl) + return ret_sync + else: + raise ValueError(f'Invalid engine: {self._engine!r}') + + +class WindowResponseMessage(ResponseMessage): + def unpack(self) -> Union[Window, AsyncWindow]: + from ._async.engine import AsyncAHK + from ._async.window import AsyncWindow + from ._sync.window import Window + from ._sync.engine import AHK + + s = self._raw_content.decode(encoding='utf-8') + ahk_id = s.strip() + if isinstance(self._engine, AsyncAHK): + async_ret = AsyncWindow(engine=self._engine, ahk_id=ahk_id) + return async_ret + elif isinstance(self._engine, AHK): + ret = Window(engine=self._engine, ahk_id=ahk_id) + return ret + else: + raise ValueError(f'Invalid engine: {self._engine!r}') + + +class PositionResponseMessage(TupleResponseMessage): + def unpack(self) -> Position: + resp = super().unpack() + if not len(resp) == 4: + raise ValueError(f'Unexpected response. Expected tuple of length 4, got tuple of length {len(resp)}') + pos = Position(*resp) + return pos + + +class FloatResponseMessage(ResponseMessage): + def unpack(self) -> float: + s = self._raw_content.decode(encoding='utf-8') + val = ast.literal_eval(s) + assert isinstance(val, float) + return val + + +class TimeoutResponseMessage(ExceptionResponseMessage): + _exception_type = TimeoutError + + +class B64BinaryResponseMessage(ResponseMessage): + def unpack(self) -> bytes: + b64_content = self._raw_content + b = base64.b64decode(b64_content) + return b + + +T_RequestMessageType = TypeVar('T_RequestMessageType', bound='RequestMessage') + + +class RequestMessage: + def __init__(self, function_name: str, args: Optional[List[str]] = None): + self.function_name: str = function_name + self.args: List[str] = args or [] + + def format(self) -> bytes: + arg_binary = b'|'.join(b64encode(bytes(arg, 'UTF-8')) for arg in self.args) + ret = bytes(self.function_name, 'UTF-8') + b'|' + arg_binary + b'\n' + return ret + + +ResponseMessageTypes = Union[ + ResponseMessage, + TupleResponseMessage, + CoordinateResponseMessage, + IntegerResponseMessage, + BooleanResponseMessage, + StringResponseMessage, + WindowListResponseMessage, + NoValueResponseMessage, + WindowControlListResponseMessage, + ExceptionResponseMessage, + PositionResponseMessage, +] +ResponseMessageClassTypes = Union[ + Type[PositionResponseMessage], + Type[TupleResponseMessage], + Type[CoordinateResponseMessage], + Type[IntegerResponseMessage], + Type[BooleanResponseMessage], + Type[StringResponseMessage], + Type[WindowListResponseMessage], + Type[NoValueResponseMessage], + Type[WindowControlListResponseMessage], + Type[ExceptionResponseMessage], + Type[ResponseMessage], +] +if TYPE_CHECKING: + from ._async.engine import AsyncAHK + from ._async.window import AsyncWindow, AsyncControl + from ._sync.window import Window, Control + from ._sync.engine import AHK diff --git a/ahk/mouse.py b/ahk/mouse.py deleted file mode 100644 index 7d712bc8..00000000 --- a/ahk/mouse.py +++ /dev/null @@ -1,250 +0,0 @@ -from collections import namedtuple -from ahk.script import ScriptEngine -from ahk.utils import make_logger -import ast - -logger = make_logger(__name__) - - -_BUTTONS = { - 1: 'L', - 2: 'R', - 3: 'M', - 'left': 'L', - 'right': 'R', - 'middle': 'M', - 'wheelup': 'WU', - 'wheeldown': 'WD', - 'wheelleft': 'WL', - 'wheelright': 'WR', -} - -def resolve_button(button): - """ - Resolve a string of a button name to a canonical name used for AHK script - :param button: - :type button: str - :return: - """ - if isinstance(button, str): - button = button.lower() - - if button in _BUTTONS: - button = _BUTTONS.get(button) - elif isinstance(button, int) and button > 3: - # for addtional mouse buttons - button = f'X{button-3}' - return button - - -class MouseMixin(ScriptEngine): - """ - Provides mouse functionality for the AHK class - """ - def __init__(self, mouse_speed=2, mode=None, **kwargs): - if mode is None: - mode = 'Screen' - self.mode = mode - self._mouse_speed = mouse_speed - super().__init__(**kwargs) - - @property - def mouse_speed(self): - if callable(self._mouse_speed): - return self._mouse_speed() - else: - return self._mouse_speed - - @mouse_speed.setter - def mouse_speed(self, value): - self._mouse_speed = value - - def _mouse_position(self, mode=None): - if mode is None: - mode = self.mode - return self.render_template('mouse/mouse_position.ahk', mode=mode) - - @property - def mouse_position(self): - script = self._mouse_position() - response = self.run_script(script) - return ast.literal_eval(response) - - @mouse_position.setter - def mouse_position(self, position): - x, y = position - self.mouse_move(x=x, y=y, speed=0, relative=False) - - def _mouse_move(self, x=None, y=None, speed=None, relative=False, mode=None, blocking=True): - if x is None and y is None: - raise ValueError('Position argument(s) missing. Must provide x and/or y coordinates') - if speed is None: - speed = self.mouse_speed - if callable(speed): - speed = speed() - if mode is None: - mode = self.mode - if relative and (x is None or y is None): - x = x or 0 - y = y or 0 - elif not relative and (x is None or y is None): - posx, posy = self.mouse_position - x = x or posx - y = y or posy - - return self.render_template('mouse/mouse_move.ahk', x=x, y=y, speed=speed, relative=relative, mode=mode, blocking=blocking) - - def mouse_move(self, *args, **kwargs): - """ - REF: https://www.autohotkey.com/docs/commands/MouseMove.htm - - :param x: the x coordinate to move to. If omitted, current position is used - :param y: the y coordinate to move to. If omitted, current position is used - :param speed: 0 (fastest) to 100 (slowest). Can be a callable or string AHK expression - :param relative: Move the mouse realtive to current position rather than absolute x,y coordinates - :param mode: - :param blocking: - :return: - - """ - blocking = kwargs.get('blocking', True) - script = self._mouse_move(*args, **kwargs) - self.run_script(script, blocking=blocking) - - def _click(self, *args, mode=None, blocking=True): - if mode is None: - mode = self.mode - return self.render_template('mouse/click.ahk', args=args, mode=mode, blocking=blocking) - - def click(self, x=None, y=None, *, button=None, n=None, direction=None, relative=None, blocking=True, mode=None): - """ - Click mouse button at a specified position. REF: https://www.autohotkey.com/docs/commands/Click.htm - - :param x: - :param y: - :param button: - :param n: number of times to click the button - :param direction: - :param relative: - :param blocking: - :param mode: - :return: - """ - if x or y: - if y is None and not isinstance(x, int) and len(x) == 2: - # alow position to be specified by a two-sequence - x, y = x - assert x is not None and y is not None, 'If provided, position must be specified by x AND y' - - button = resolve_button(button) - - if relative: - relative = 'Rel' - args = [arg for arg in (x, y, button, n, direction, relative) if arg is not None] - script = self._click(*args, blocking=blocking, mode=mode) - self.run_script(script, blocking=blocking) - - def double_click(self, *args, **kwargs): - """ - Convenience function to double click, equivalent to ``click`` with ``n=2`` - - :param args: - :param kwargs: - :return: - """ - n = kwargs.get('n', 1) - kwargs['n'] = n * 2 - self.click(*args, **kwargs) - - def right_click(self, *args, **kwargs): - """ - Convenience function clicking right mouse button. Equivalent to ``click`` with ``button='R'`` - - :param args: - :param kwargs: - :return: - """ - kwargs['button'] = 2 - self.click(*args, **kwargs) - - def mouse_wheel(self, direction, *args, **kwargs): - """ - Convenience function for 'clicking' the mouse wheel in a given direction. - - :param direction: the string 'up' or 'down' - :param args: args passed to ``click`` - :param kwargs: keyword args passed to ``click`` - :return: - """ - assert direction in ('up', 'down') - kwargs['button'] = f'Wheel{direction}' - self.click(*args, **kwargs) - - def wheel_up(self, *args, **kwargs): - """ - Convenience function for ``click`` with wheel up button - - :param args: - :param kwargs: - :return: - """ - self.mouse_wheel('up', *args, **kwargs) - - def wheel_down(self, *args, **kwargs): - """ - Convenience function for ``click`` with wheel down button - - :param args: - :param kwargs: - :return: - """ - self.mouse_wheel('down', *args, **kwargs) - - def mouse_drag(self, x, y=None, *, from_position=None, speed=None, button=1, relative=None, blocking=True, mode=None): - """ - Click and drag the mouse - - :param x: - :param y: - :param from_position: (x,y) tuple of an optional starting position. Current position is used if omitted - :param speed: - :param button: The button the click and drag; defaults to left mouse button - :param relative: click and drag to a relative position rather than an absolute position - :param blocking: - :param mode: - :return: - """ - if from_position is None: - x1, y1 = self.mouse_position - else: - x1, y1 = from_position - - if y is None: - x2, y2 = x - else: - x2 = x - y2 = y - - if relative: - x1, y1 = (0, 0) - - button = resolve_button(button) - - if speed is None: - speed = self.mouse_speed - - if mode is None: - mode = self.mode - - script = self.render_template('mouse/mouse_drag.ahk', - button=button, - x1=x1, - y1=y1, - x2=x2, - y2=y2, - speed=speed, - relative=relative, - blocking=blocking, - mode=mode) - - self.run_script(script, blocking=blocking) diff --git a/ahk/templates/window/win_get.ahk b/ahk/py.typed similarity index 100% rename from ahk/templates/window/win_get.ahk rename to ahk/py.typed diff --git a/ahk/screen.py b/ahk/screen.py deleted file mode 100644 index afba1fe0..00000000 --- a/ahk/screen.py +++ /dev/null @@ -1,127 +0,0 @@ -import ast -from ahk.script import ScriptEngine -from typing import Tuple, Union, Optional - - -class ScreenMixin(ScriptEngine): - def image_search(self, image_path: str, - upper_bound: Tuple[int, int]=(0, 0), lower_bound: Tuple[int, int]=None, - coord_mode: str='Screen', - scale_height: int=None, scale_width: int=None) -> Union[Tuple[int, int], None]: - """ - `AutoHotkey ImageSearch reference`_ - - .. _AutoHotkey ImageSearch reference: https://autohotkey.com/docs/commands/ImageSearch.htm - - - :param image_path: path to the image file e.g. C:\location\of\cats.png - :param upper_bound: a two-tuple of X,Y coordinates for the upper-left corner of the search area e.g. (200, 400) - defaults to (0,0) - - :param lower_bound: like ``upper_bound`` but for the lower-righthand corner of the search area e.g. (400, 800) - defaults to screen width and height (lower right-hand corner; ``%A_ScreenWidth%``, ``%A_ScreenHeight%``). - - :param coord_mode: the Pixel CoordMode to use. Default is 'Screen' - :param scale_height: Scale height in pixels. Equivalent of ``*hn`` option - :param scale_width: Scale width in pixels. Equivalent of ``*wn`` option - - :return: coordinates of the upper-left pixel of where the image was found on the screen; ``None`` if the image - was not found - - :rtype: Union[Tuple[int, int], None] - - Note: when only scale_height or only scale_width are provided, aspect ratio is maintained by default. - """ - - if scale_height and not scale_width: - scale_width = -1 - elif scale_width and not scale_height: - scale_height = -1 - - x1, y1 = upper_bound - if lower_bound: - x2, y2 = lower_bound - else: - x2, y2 = ('%A_ScreenWidth%', '%A_ScreenHeight%') - script = self.render_template('screen/image_search.ahk', - x1=x1, x2=x2, y1=y1, y2=y2, - coord_mode=coord_mode, - scale_width=scale_width, - scale_height=scale_height, - image_path=image_path) - resp = self.run_script(script) - try: - return ast.literal_eval(resp) - except SyntaxError: - return None - - def pixel_get_color(self, x: int, y: int, coord_mode: str='Screen', - alt: bool=False, slow: bool=False, rgb=True) -> Union[str, None]: - """ - `AutoHotkey PixelGetColor reference`_ - - .. _AutoHotkey PixelGetColor reference: https://autohotkey.com/docs/commands/PixelGetColor.htm - - :param x: x coordinate - :param y: y coordinate - :param coord_mode: - :param alt: - :param slow: - :param rgb: returns - :return: the color as an RGB hexidecimal string; - :rtype: str - """ - - options = [] - if slow: - options.append('Slow') - elif alt: - options.append('Alt') - if rgb: - options.append('RGB') - script = self.render_template('screen/pixel_get_color.ahk', - x=x, y=y, - coord_mode=coord_mode, - options=options) - resp = self.run_script(script) - return resp - - def pixel_search(self, color: Union[str, int], variation: int=0, - upper_bound: Tuple[int, int]=(0, 0), lower_bound: Tuple[int, int]=None, - coord_mode: str='Screen', fast: bool=True, rgb: bool=True) -> Union[Tuple[int, int], None]: - """ - `AutoHotkey PixelSearch reference`_ - - .. _AutoHotkey PixelSearch reference: https://autohotkey.com/docs/commands/PixelSearch.htm - - :param Union[str, int] color: - :param int variation: - :param Tuple[int, int] upper_bound: - :param Optional[Tuple[int, int]] lower_bound: - :param coord_mode - :param fast: - :param rgb: - :return: the coordinates of the pixel; None if the pixel is not found - """ - options = [] - if fast: - options.append('Fast') - if rgb: - options.append('RGB') - x1, y1 = upper_bound - if lower_bound: - x2, y2 = lower_bound - else: - x2, y2 = ('%A_ScreenWidth%', '%A_ScreenHeight%') - - script = self.render_template('screen/pixel_search.ahk', - x1=x1, y1=y1, x2=x2, y2=y2, - coord_mode=coord_mode, - color=color, - variation=variation, - options=options) - resp = self.run_script(script) - try: - return ast.literal_eval(resp) - except SyntaxError: - return None diff --git a/ahk/script.py b/ahk/script.py deleted file mode 100644 index 2bb6eb65..00000000 --- a/ahk/script.py +++ /dev/null @@ -1,80 +0,0 @@ -import os -import subprocess -from shutil import which -from ahk.utils import make_logger -from ahk.directives import Persistent -from jinja2 import Environment, FileSystemLoader - -logger = make_logger(__name__) - - -class ExecutableNotFoundError(EnvironmentError): - pass - - -class ScriptEngine(object): - def __init__(self, executable_path: str='', **kwargs): - """ - :param executable_path: the path to the AHK executable. - Defaults to environ['AHK_PATH'] if not explicitly provided - If environment variable not present, tries to look for 'AutoHotkey.exe' or 'AutoHotkeyA32.exe' with shutil.which - :param keep_scripts: - :raises ExecutableNotFound: if AHK executable is not provided and cannot be found in environment variables or PATH - """ - if not executable_path: - executable_path = os.environ.get('AHK_PATH') or which('AutoHotkey.exe') or which('AutoHotkeyA32.exe') - if not executable_path: - raise ExecutableNotFoundError('Could not find AutoHotkey.exe on PATH. ' - 'Provide the absolute path with the `executable_path` keyword argument ' - 'or in the AHK_PATH environment variable.') - self.executable_path = executable_path - templates_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates') - self.env = Environment( - loader=FileSystemLoader(templates_path), - autoescape=False, - trim_blocks=True - ) - - def render_template(self, template_name, directives=None, blocking=True, **kwargs): - if directives is None: - directives = set() - else: - directives = set(directives) - if blocking: - directives.add(Persistent) - elif Persistent in directives: - directives.remove(Persistent) - - kwargs['directives'] = directives - template = self.env.get_template(template_name) - return template.render(**kwargs) - - def _run_script(self, script_text, **kwargs): - blocking = kwargs.pop('blocking', True) - runargs = [self.executable_path, '/ErrorStdOut', '*'] - decode = kwargs.pop('decode', False) - script_bytes = bytes(script_text, 'utf-8') - if blocking: - result = subprocess.run(runargs, input=script_bytes, stderr=subprocess.PIPE, stdout=subprocess.PIPE, **kwargs) - if decode: - logger.debug('Stdout: %s', repr(result.stdout)) - logger.debug('Stderr: %s', repr(result.stderr)) - return result.stdout.decode() - else: - return result - else: - proc = subprocess.Popen(runargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) - try: - proc.communicate(script_bytes, timeout=0) - except subprocess.TimeoutExpired: - pass # for now, this seems needed to avoid blocking and use stdin - return proc - - def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): - logger.debug('Running script text: %s', script_text) - try: - result = self._run_script(script_text, decode=decode, blocking=blocking, **runkwargs) - except Exception as e: - logger.fatal('Error running temp script: %s', e) - raise - return result diff --git a/ahk/sound.py b/ahk/sound.py deleted file mode 100644 index 302cfdc9..00000000 --- a/ahk/sound.py +++ /dev/null @@ -1,84 +0,0 @@ -from ahk.script import ScriptEngine - - -class SoundMixin(ScriptEngine): - def sound_beep(self, frequency=523, duration=150): - """ - REF: https://autohotkey.com/docs/commands/SoundBeep.htm - - :param frequency: number between 37 and 32767 - :param duration: how long in milliseconds to play the beep - :return: None - """ - - script = self.render_template('sound/beep.ahk', frequency=frequency, duration=duration) - self.run_script(script) - - def sound_play(self, filename, blocking=True): - """ - REF: https://autohotkey.com/docs/commands/SoundPlay.htm - - :param filename: - :param blocking: - :param wait: - :return: - """ - - script = self.render_template('sound/play.ahk', filename=filename, wait=1, blocking=blocking) - self.run_script(script, blocking=blocking) - - def sound_get(self, device_number=1, component_type='MASTER', control_type='VOLUME'): - """ - REF: https://autohotkey.com/docs/commands/SoundGet.htm - - - :param device_number: - :param component_type: - :param control_type: - :return: - """ - - script = self.render_template('sound/sound_get.ahk') - return self.run_script(script) - - def get_volume(self, device_number=1): - """ - REF: https://autohotkey.com/docs/commands/SoundGetWaveVolume.htm - - - :param device_number: - :return: - """ - script = self.render_template('sound/get_volume.ahk', device_number=device_number) - result = self.run_script(script) - return result - - def sound_set(self, value, device_number=1, component_type='MASTER', control_type='VOLUME'): - """ - REF: https://autohotkey.com/docs/commands/SoundSet.htm - - - :param value: - :param device_number: - :param component_type: - :param control_type: - :return: - """ - - script = self.render_template('sound/sound_set.ahk', value=value, - device_number=device_number, - component_type=component_type, - control_type=control_type) - self.run_script(script) - - def set_volume(self, value, device_number=1): - """ - REF: https://autohotkey.com/docs/commands/SoundSetWaveVolume.htm - - :param value: percent volume to set volume to - :param device_number: - :return: - """ - - script = self.render_template('sound/set_volume.ahk', value=value, device_number=device_number) - self.run_script(script) diff --git a/ahk/templates/base.ahk b/ahk/templates/base.ahk deleted file mode 100644 index 4fa0e994..00000000 --- a/ahk/templates/base.ahk +++ /dev/null @@ -1,14 +0,0 @@ -{% block directives %} -#NoEnv -{% for directive in directives %} -{{ directive }} -{% endfor %} -{% endblock %} - -{% block body %} -{{ body }} -{% endblock body %} - -{% block exit %} -ExitApp -{% endblock exit %} diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk new file mode 100644 index 00000000..ebe4e4d4 --- /dev/null +++ b/ahk/templates/daemon-v2.ahk @@ -0,0 +1,3035 @@ +{% block daemon_script %} +{% block directives %} +;#NoEnv +#Requires Autohotkey >= 2.0- +Persistent +;#Warn All, Off +#SingleInstance Off +; BEGIN user-defined directives +{% block user_directives %} +{% for directive in directives %} +{{ directive }} + +{% endfor %} + +; END user-defined directives +{% endblock user_directives %} +{% endblock directives %} + +Critical 100 + + +{% block message_types %} +MESSAGE_TYPES := Map({% for tom, msg_class in message_registry.items() %}"{{ msg_class.fqn() }}", "{{ tom.decode('utf-8') }}"{% if not loop.last %}, {% endif %}{% endfor %}) +{% endblock message_types %} + +NOVALUE_SENTINEL := Chr(57344) + +StrCount(haystack, needle) { + StrReplace(haystack, needle, "",, &count) + return count +} + +FormatResponse(MessageType, payload) { + global MESSAGE_TYPES + newline_count := StrCount(payload, "`n") + response := Format("{}`n{}`n{}`n", MESSAGE_TYPES[MessageType], newline_count, payload) + return response +} + +FormatNoValueResponse() { + global NOVALUE_SENTINEL + return FormatResponse("ahk.message.NoValueResponseMessage", NOVALUE_SENTINEL) +} + +FormatBinaryResponse(bin) { + b64 := b64encode(bin) + return FormatResponse("ahk.message.B64BinaryResponseMessage", b64) +} + +AHKSetDetectHiddenWindows(args*) { + {% block AHKSetDetectHiddenWindows %} + value := args[1] + DetectHiddenWindows(value) + return FormatNoValueResponse() + {% endblock AHKSetDetectHiddenWindows %} +} + +AHKSetTitleMatchMode(args*) { + {% block AHKSetTitleMatchMode %} + val1 := args[1] + val2 := args[2] + if (val1 != "") { + SetTitleMatchMode(val1) + } + if (val2 != "") { + SetTitleMatchMode(val2) + } + return FormatNoValueResponse() + {% endblock AHKSetTitleMatchMode %} +} + +AHKGetTitleMatchMode(args*) { + {% block AHKGetTitleMatchMode %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) + {% endblock AHKGetTitleMatchMode %} +} + +AHKGetTitleMatchSpeed(args*) { + {% block AHKGetTitleMatchSpeed %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) + {% endblock AHKGetTitleMatchSpeed %} +} + +AHKSetSendLevel(args*) { + {% block AHKSetSendLevel %} + level := args[1] + SendLevel(level) + return FormatNoValueResponse() + {% endblock AHKSetSendLevel %} +} + +AHKGetSendLevel(args*) { + {% block AHKGetSendLevel %} + + return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) + {% endblock AHKGetSendLevel %} +} + +AHKWinExist(args*) { + {% block AHKWinExist %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + if WinExist(title, text, extitle, extext) { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } else { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return resp + {% endblock AHKWinExist %} +} + +AHKWinClose(args*) { + {% block AHKWinClose %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + secondstowait := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (secondstowait != "") { + WinClose(title, text, secondstowait, extitle, extext) + } else { + WinClose(title, text,, extitle, extext) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinClose %} +} + +AHKWinKill(args*) { + {% block AHKWinKill %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + secondstowait := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (secondstowait != "") { + WinKill(title, text, secondstowait, extitle, extext) + } else { + WinKill(title, text,, extitle, extext) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinKill %} +} + +AHKWinWait(args*) { + {% block AHKWinWait %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (timeout != "") { + output := WinWait(title, text, timeout, extitle, extext) + if (output = 0) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } else { + resp := resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } else { + output := WinWait(title, text,, extitle, extext) + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return resp + {% endblock AHKWinWait %} +} + +AHKWinWaitActive(args*) { + {% block AHKWinWaitActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (timeout != "") { + output := WinWaitActive(title, text, timeout, extitle, extext) + if (output = 0) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWaitActive timed out waiting for the window") + } else { + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } else { + output := WinWaitActive(title, text,, extitle, extext) + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return resp + {% endblock AHKWinWaitActive %} +} + +AHKWinWaitNotActive(args*) { + {% block AHKWinWaitNotActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (timeout != "") { + if (WinWaitNotActive(title, text, timeout, extitle, extext) = 1) { + output := WinGetID() + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } else { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } + } else { + WinWaitNotActive(title, text,, extitle, extext) + output := WinGetID() + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return resp + {% endblock AHKWinWaitNotActive %} +} + +AHKWinWaitClose(args*) { + {% block AHKWinWaitClose %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if (timeout != "") { + if (WinWaitClose(title, text, timeout, extitle, extext) = 1) { + resp := FormatNoValueResponse() + } else { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } + } else { + WinWaitClose(title, text,, extitle, extext) + resp := FormatNoValueResponse() + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return resp + {% endblock AHKWinWaitClose %} +} + +AHKWinMinimize(args*) { + {% block AHKWinMinimize %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinMinimize(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinMinimize %} +} + +AHKWinMaximize(args*) { + {% block AHKWinMaximize %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinMaximize(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinMaximize %} +} + +AHKWinRestore(args*) { + {% block AHKWinRestore %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinRestore(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinRestore %} +} + +AHKWinIsActive(args*) { + {% block AHKWinIsActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + if WinActive(title, text, extitle, extext) { + response := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } else { + response := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinIsActive %} +} + +AHKWinGetID(args*) { + {% block AHKWinGetID %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetID(title, text, extitle, extext) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetID %} +} + +AHKWinGetTitle(args*) { + {% block AHKWinGetTitle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + text := WinGetTitle(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatResponse("ahk.message.StringResponseMessage", text) + {% endblock AHKWinGetTitle %} +} + +AHKWinGetIDLast(args*) { + {% block AHKWinGetIDLast %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetIDLast(title, text, extitle, extext) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.WindowResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetIDLast %} +} + +AHKWinGetPID(args*) { + {% block AHKWinGetPID %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetPID(title, text, extitle, extext) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetPID %} +} + +AHKWinGetProcessName(args*) { + {% block AHKWinGetProcessName %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetProcessName(title, text, extitle, extext) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetProcessName %} +} + +AHKWinGetProcessPath(args*) { + {% block AHKWinGetProcessPath %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetProcessPath(title, text, extitle, extext) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetProcessPath %} +} + +AHKWinGetCount(args*) { + {% block AHKWinGetCount %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetCount(title, text, extitle, extext) + if (output = 0) { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetCount %} +} + +AHKWinGetMinMax(args*) { + {% block AHKWinGetMinMax %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetMinMax(title, text, extitle, extext) + if (output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetMinMax %} +} + +AHKWinGetControlList(args*) { + {% block AHKWinGetControlList %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + ahkid := WinGetID(title, text, extitle, extext) + if (ahkid = "") { + return FormatNoValueResponse() + } + ctrList := WinGetControls(title, text, extitle, extext) + ctrListID := WinGetControlsHwnd(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + if (ctrListID = "") { + return FormatResponse("ahk.message.WindowControlListResponseMessage", Format("('{}', [])", ahkid)) + } + + if (ctrList.Length != ctrListID.Length) { + return FormatResponse("ahk.message.ExceptionResponseMessage", "Control hwnd/class lists have unexpected lengths") + } + + output := Format("('{}', [", ahkid) + + for index, hwnd in ctrListID { + classname := ctrList[index] + output .= Format("('{}', '{}'), ", hwnd, classname) + + } + output .= "])" + response := FormatResponse("ahk.message.WindowControlListResponseMessage", output) + return response + {% endblock AHKWinGetControlList %} +} + +AHKWinGetTransparent(args*) { + {% block AHKWinGetTransparent %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetTransparent(title, text, extitle, extext) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetTransparent %} +} +AHKWinGetTransColor(args*) { + {% block AHKWinGetTransColor %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetTransColor(title, text, extitle, extext) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetTransColor %} +} +AHKWinGetStyle(args*) { + {% block AHKWinGetStyle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetStyle(title, text, extitle, extext) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetStyle %} +} + +AHKWinGetExStyle(args*) { + {% block AHKWinGetExStyle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetExStyle(title, text, extitle, extext) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKWinGetExStyle %} +} + +AHKWinGetText(args*) { + {% block AHKWinGetText %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + output := WinGetText(title,text,extitle,extext) + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + + return response + {% endblock AHKWinGetText %} +} + +AHKWinSetTitle(args*) { + {% block AHKWinSetTitle %} + new_title := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetTitle(new_title, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTitle %} +} + +AHKWinSetAlwaysOnTop(args*) { + {% block AHKWinSetAlwaysOnTop %} + toggle := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + if (toggle = "On") { + toggle := 1 + } else if (toggle = "Off") { + toggle := 0 + } else if (toggle = "") { + toggle := 1 + } + + try { + WinSetAlwaysOnTop(toggle, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetAlwaysOnTop %} +} + +AHKWinSetBottom(args*) { + {% block AHKWinSetBottom %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinMoveBottom(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetBottom %} +} + +AHKWinShow(args*) { + {% block AHKWinShow %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinShow(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinShow %} +} + +AHKWinHide(args*) { + {% block AHKWinHide %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinHide(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinHide %} +} + +AHKWinSetTop(args*) { + {% block AHKWinSetTop %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinMoveTop(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTop %} +} + +AHKWinSetEnable(args*) { + {% block AHKWinSetEnable %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetEnabled(1, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetEnable %} +} + +AHKWinSetDisable(args*) { + {% block AHKWinSetDisable %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetEnabled(0, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetDisable %} +} + +AHKWinSetRedraw(args*) { + {% block AHKWinSetRedraw %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinRedraw(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetRedraw %} +} + +AHKWinSetStyle(args*) { + {% block AHKWinSetStyle %} + + style := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetStyle(style, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + {% endblock AHKWinSetStyle %} +} + +AHKWinSetExStyle(args*) { + {% block AHKWinSetExStyle %} + + style := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + WinSetExStyle(style, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + {% endblock AHKWinSetExStyle %} +} + +AHKWinSetRegion(args*) { + {% block AHKWinSetRegion %} + + options := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetRegion(options, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + {% endblock AHKWinSetRegion %} +} + +AHKWinSetTransparent(args*) { + {% block AHKWinSetTransparent %} + + transparency := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetTransparent(transparency, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTransparent %} +} + +AHKWinSetTransColor(args*) { + {% block AHKWinSetTransColor %} + + color := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + WinSetTransColor(color, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTransColor %} +} + +AHKImageSearch(args*) { + {% block AHKImageSearch %} + + imagepath := args[5] + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + coord_mode := args[6] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode("Pixel", coord_mode) + } + + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } + + try { + if (ImageSearch(&xpos, &ypos, x1, y1, x2, y2, imagepath) = 1) { + s := FormatResponse("ahk.message.CoordinateResponseMessage", Format("({}, {})", xpos, ypos)) + } else { + s := FormatNoValueResponse() + } + } + finally { + if (coord_mode != "") { + CoordMode("Pixel", current_mode) + } + } + + return s + {% endblock AHKImageSearch %} +} + +AHKPixelGetColor(args*) { + {% block AHKPixelGetColor %} + + x := args[1] + y := args[2] + coord_mode := args[3] + options := args[4] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode("Pixel", coord_mode) + } + + try { + color := PixelGetColor(x, y, options) + } + finally { + if (coord_mode != "") { + CoordMode("Pixel", current_mode) + } + } + + return FormatResponse("ahk.message.StringResponseMessage", color) + {% endblock AHKPixelGetColor %} +} + +AHKPixelSearch(args*) { + {% block AHKPixelSearch %} + + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + color := args[5] + variation := args[6] + options := args[7] + coord_mode := args[8] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode("Pixel", coord_mode) + } + try { + if (PixelSearch(&resultx, &resulty, x1, y1, x2, y2, color, variation) = 1) { + payload := Format("({}, {})", resultx, resulty) + ret := FormatResponse("ahk.message.CoordinateResponseMessage", payload) + } else { + ret := FormatNoValueResponse() + } + } + finally { + if (coord_mode != "") { + CoordMode("Pixel", current_mode) + } + } + + return ret + + {% endblock AHKPixelSearch %} +} + +AHKMouseGetPos(args*) { + {% block AHKMouseGetPos %} + + coord_mode := args[1] + current_coord_mode := Format("{}", A_CoordModeMouse) + if (coord_mode != "") { + CoordMode("Mouse", coord_mode) + } + MouseGetPos(&xpos, &ypos) + + payload := Format("({}, {})", xpos, ypos) + resp := FormatResponse("ahk.message.CoordinateResponseMessage", payload) + + if (coord_mode != "") { + CoordMode("Mouse", current_coord_mode) + } + + return resp + {% endblock AHKMouseGetPos %} +} + +AHKKeyState(args*) { + {% block AHKKeyState %} + + keyname := args[1] + mode := args[2] + if (mode != "") { + state := GetKeyState(keyname, mode) + } else{ + state := GetKeyState(keyname) + } + + if (state = "") { + return FormatNoValueResponse() + } + + if IsInteger(state) + return FormatResponse("ahk.message.IntegerResponseMessage", state) + + if IsFloat(state) + return FormatResponse("ahk.message.FloatResponseMessage", state) + + return FormatResponse("ahk.message.StringResponseMessage", state) + + {% endblock AHKKeyState %} +} + +AHKMouseMove(args*) { + {% block AHKMouseMove %} + x := args[1] + y := args[2] + speed := args[3] + relative := args[4] + send_mode := args[5] + coord_mode := args[6] + current_send_mode := Format("{}", A_SendMode) + + current_coord_mode := Format("{}", A_CoordModeMouse) + if (coord_mode != "") { + CoordMode("Mouse", coord_mode) + } + + + + if (send_mode != "") { + SendMode send_mode + } + + if (relative != "") { + MouseMove(x, y, speed, "R") + } else { + MouseMove(x, y, speed) + } + + if (send_mode != "") { + SendMode current_send_mode + } + + if (coord_mode != "") { + CoordMode("Mouse", current_coord_mode) + } + + resp := FormatNoValueResponse() + return resp + {% endblock AHKMouseMove %} +} + +AHKClick(args*) { + {% block AHKClick %} + x := args[1] + y := args[2] + button := args[3] + click_count := args[4] + direction := args[5] + r := args[6] + relative_to := args[7] + send_mode := args[8] + current_coord_rel := Format("{}", A_CoordModeMouse) + current_send_mode := Format("{}", A_SendMode) + + if (send_mode != "") { + SendMode send_mode + } + + if (relative_to != "") { + CoordMode("Mouse", relative_to) + } + + Click(x, y, button, direction, r) + + if (relative_to != "") { + CoordMode("Mouse", current_coord_rel) + } + + if (send_mode != "") { + SendMode current_send_mode + } + return FormatNoValueResponse() + + {% endblock AHKClick %} +} + +AHKGetCoordMode(args*) { + {% block AHKGetCoordMode %} + + target := args[1] + + if (target = "ToolTip") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeToolTip) + } + if (target = "Pixel") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModePixel) + } + if (target = "Mouse") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMouse) + } + if (target = "Caret") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeCaret) + } + if (target = "Menu") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMenu) + } + return FormatResponse("ahk.message.ExceptionResponseMessage", "Invalid coord mode") + {% endblock AHKGetCoordMode %} +} + +AHKSetCoordMode(args*) { + {% block AHKSetCoordMode %} + target := args[1] + relative_to := args[2] + CoordMode(target, relative_to) + + return FormatNoValueResponse() + {% endblock AHKSetCoordMode %} +} + + +AHKGetSendMode(args*) { + return FormatResponse("ahk.message.StringResponseMessage", A_SendMode) +} + + +AHKSetSendMode(args*) { + mode := args[1] + SendMode mode + return FormatNoValueResponse() +} + + +AHKMouseClickDrag(args*) { + {% block AHKMouseClickDrag %} + button := args[1] + x1 := args[2] + y1 := args[3] + x2 := args[4] + y2 := args[5] + speed := args[6] + relative := args[7] + relative_to := args[8] + send_mode := args[9] + current_coord_rel := Format("{}", A_CoordModeMouse) + current_send_mode := Format("{}", A_SendMode) + + if (send_mode != "") { + SendMode send_mode + } + + if (relative_to != "") { + CoordMode("Mouse", relative_to) + } + + if (speed = "") { + speed := A_DefaultMouseSpeed + } + + if (x1 = "" and y1 = "") { + MouseClickDrag(button, , , x2, y2, speed, relative) + } + else { + MouseClickDrag(button, x1, y1, x2, y2, speed, relative) + } + + + if (relative_to != "") { + CoordMode("Mouse", current_coord_rel) + } + + if (send_mode != "") { + SendMode current_send_mode + } + + return FormatNoValueResponse() + + {% endblock AHKMouseClickDrag %} +} + +AHKRegRead(args*) { + {% block RegRead %} + + key_name := args[1] + value_name := args[2] + + output := RegRead(key_name, value_name) + resp := FormatResponse("ahk.message.StringResponseMessage", Format("{}", output)) + return resp + {% endblock RegRead %} +} + +AHKRegWrite(args*) { + {% block RegWrite %} + value_type := args[1] + key_name := args[2] + value_name := args[3] + value := args[4] +; RegWrite(value_type, key_name, value_name, value) + if (value_name != "") { + RegWrite(value, value_type, key_name, value_name) + } else { + RegWrite(value, value_type, key_name) + } + return FormatNoValueResponse() + {% endblock RegWrite %} +} + +AHKRegDelete(args*) { + {% block RegDelete %} + + key_name := args[1] + value_name := args[2] + if (value_name != "") { + RegDelete(key_name, value_name) + } else { + RegDelete(key_name) + } + return FormatNoValueResponse() + + {% endblock RegDelete %} +} + +AHKKeyWait(args*) { + {% block AHKKeyWait %} + + keyname := args[1] + options := args[2] + + if (options = "") { + ret := KeyWait(keyname) + } else { + ret := KeyWait(keyname, options) + } + + if (ret = 0) { + return FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else { + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + } + {% endblock AHKKeyWait %} +} + +;SetKeyDelay(args*) { +; {% block SetKeyDelay %} +; SetKeyDelay(args[1], args[2]) +; {% endblock SetKeyDelay %} +;} + +AHKSend(args*) { + {% block AHKSend %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + send_mode := args[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + current_send_mode := Format("{}", A_SendMode) + + if (send_mode != "") { + SendMode send_mode + } + + if (key_delay != "" or key_press_duration != "") { + if (key_delay != "" and key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } else if (key_delay != "" and key_press_duration = "") { + SetKeyDelay(key_delay) + } else if (key_delay = "" and key_press_duration != "") { + SetKeyDelay(current_delay, key_press_duration) + } + } + + + Send(str) + + if (send_mode != "") { + SendMode current_send_mode + } + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSend %} +} + +AHKSendRaw(args*) { + {% block AHKSendRaw %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + if (key_delay != "" and key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } else if (key_delay != "" and key_press_duration = "") { + SetKeyDelay(key_delay) + } else if (key_delay = "" and key_press_duration != "") { + SetKeyDelay(current_delay, key_press_duration) + } + } + + Send("{Raw}" str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSendRaw %} +} + +AHKSendInput(args*) { + {% block AHKSendInput %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + if (key_delay != "" and key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } else if (key_delay != "" and key_press_duration = "") { + SetKeyDelay(key_delay) + } else if (key_delay = "" and key_press_duration != "") { + SetKeyDelay(current_delay, key_press_duration) + } + } + + SendInput(str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSendInput %} +} + +AHKSendEvent(args*) { + {% block AHKSendEvent %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + if (key_delay != "" and key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } else if (key_delay != "" and key_press_duration = "") { + SetKeyDelay(key_delay) + } else if (key_delay = "" and key_press_duration != "") { + SetKeyDelay(current_delay, key_press_duration) + } + } + + SendEvent(str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSendEvent %} +} + +AHKSendPlay(args*) { + {% block AHKSendPlay %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelayPlay) + current_key_duration := Format("{}", A_KeyDurationPlay) + + if (key_delay != "" or key_press_duration != "") { + if (key_delay != "" and key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } else if (key_delay != "" and key_press_duration = "") { + SetKeyDelay(key_delay) + } else if (key_delay = "" and key_press_duration != "") { + SetKeyDelay(current_delay, key_press_duration) + } + } + + SendPlay(str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSendPlay %} +} + +AHKSetCapsLockState(args*) { + {% block AHKSetCapsLockState %} + state := args[1] + if (state = "") { + SetCapsLockState(!GetKeyState("CapsLock", "T")) + } else { + SetCapsLockState(state) + } + return FormatNoValueResponse() + {% endblock AHKSetCapsLockState %} +} + + +AHKSetNumLockState(args*) { + {% block AHKSetNumLockState %} + state := args[1] + if (state = "") { + SetNumLockState(!GetKeyState("NumLock", "T")) + } else { + SetNumLockState(state) + } + return FormatNoValueResponse() + {% endblock AHKSetNumLockState %} +} + + +AHKSetScrollLockState(args*) { + {% block AHKSetScrollLockState %} + state := args[1] + if (state = "") { + SetScrollLockState(!GetKeyState("ScrollLock", "T")) + } else { + SetScrollLockState(state) + } + return FormatNoValueResponse() + {% endblock AHKSetScrollLockState %} +} + +HideTrayTip(args*) { + {% block HideTrayTip %} + TrayTip ; Attempt to hide it the normal way. + if SubStr(A_OSVersion,1,3) = "10." { + A_IconHidden := true + Sleep 200 ; It may be necessary to adjust this sleep. + A_IconHidden := false + } + {% endblock HideTrayTip %} +} + +AHKWinGetClass(args*) { + {% block AHKWinGetClass %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + try { + output := WinGetClass(title,text,extitle,extext) + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + + return response + {% endblock AHKWinGetClass %} +} + +AHKWinActivate(args*) { + {% block AHKWinActivate %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + WinActivate(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinActivate %} +} + +AHKWindowList(args*) { + {% block AHKWindowList %} + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + if (detect_hw) { + DetectHiddenWindows(detect_hw) + } + try { + windows := WinGetList(title, text, extitle, extext) + r := "" + for id in windows + { + r .= id . "`," + } + resp := FormatResponse("ahk.message.WindowListResponseMessage", r) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return resp + {% endblock AHKWindowList %} +} + +AHKControlClick(args*) { + {% block AHKControlClick %} + + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] + title := args[2] + text := args[3] + button := args[4] + click_count := args[5] + options := args[6] + exclude_title := args[7] + exclude_text := args[8] + detect_hw := args[9] + match_mode := args[10] + match_speed := args[11] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + ControlClick(ctrl || unset, title, text, button, click_count, options, exclude_title, exclude_text) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + response := FormatNoValueResponse() + return response + {% endblock AHKControlClick %} +} + +AHKControlGetText(args*) { + {% block AHKControlGetText %} + + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + result := ControlGetText(ctrl, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + response := FormatResponse("ahk.message.StringResponseMessage", result) + + return response + {% endblock AHKControlGetText %} +} + +AHKControlGetPos(args*) { + {% block AHKControlGetPos %} + + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + ControlGetPos(&x, &y, &w, &h, ctrl, title, text, extitle, extext) + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse("ahk.message.PositionResponseMessage", result) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return response + {% endblock AHKControlGetPos %} +} + +AHKControlSend(args*) { + {% block AHKControlSend %} + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] + keys := args[2] + title := args[3] + text := args[4] + extitle := args[5] + extext := args[6] + detect_hw := args[7] + match_mode := args[8] + match_speed := args[9] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + ControlSend(keys, ctrl || unset, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKControlSend %} +} + +AHKWinFromMouse(args*) { + {% block AHKWinFromMouse %} + + MouseGetPos(,, &MouseWin) + + if (MouseWin = "") { + return FormatNoValueResponse() + } + + return FormatResponse("ahk.message.WindowResponseMessage", MouseWin) + {% endblock AHKWinFromMouse %} +} + +AHKWinIsAlwaysOnTop(args*) { + {% block AHKWinIsAlwaysOnTop %} + ; TODO: detect hidden windows / etc? + title := args[1] + ExStyle := WinGetExStyle(title) + if (ExStyle = "") + return FormatNoValueResponse() + + if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + else + return FormatResponse("ahk.message.BooleanResponseMessage", 0) + {% endblock AHKWinIsAlwaysOnTop %} +} + +AHKWinMove(args*) { + {% block AHKWinMove %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + x := args[8] + y := args[9] + width := args[10] + height := args[11] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + if (width = "" or height = "") { + WinGetPos(&_, &__, &w, &h, title, text, extitle, extext) + if (width = "") { + width := w + } + if (height = "") { + height := h + } + } + + try { + WinMove(x, y, width, height, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + + {% endblock AHKWinMove %} +} + +AHKWinGetPos(args*) { + {% block AHKWinGetPos %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode(match_mode) + } + if (match_speed != "") { + SetTitleMatchMode(match_speed) + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows(detect_hw) + } + + try { + WinGetPos(&x, &y, &w, &h, title, text, extitle, extext) + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse("ahk.message.PositionResponseMessage", result) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + + return response + {% endblock AHKWinGetPos %} +} + +AHKGetVolume(args*) { + {% block AHKGetVolume %} + + device_number := args[1] + + retval := SoundGetVolume(,device_number) + response := FormatResponse("ahk.message.FloatResponseMessage", Format("{}", retval)) + return response + {% endblock AHKGetVolume %} +} + +AHKSoundBeep(args*) { + {% block AHKSoundBeep %} + freq := args[1] + duration := args[2] + SoundBeep(freq, duration) + return FormatNoValueResponse() + {% endblock AHKSoundBeep %} +} + +AHKSoundGet(args*) { + {% block AHKSoundGet %} + return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundGet is not supported in ahk v2") + {% endblock AHKSoundGet %} +} + +AHKSoundSet(args*) { + {% block AHKSoundSet %} + return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundSet is not supported in ahk v2") + {% endblock AHKSoundSet %} +} + +AHKSoundPlay(args*) { + {% block AHKSoundPlay %} + filename := args[1] + SoundPlay(filename) + return FormatNoValueResponse() + {% endblock AHKSoundPlay %} +} + +AHKSetVolume(args*) { + {% block AHKSetVolume %} + device_number := args[1] + value := args[2] + SoundSetVolume(value,,device_number) + return FormatNoValueResponse() + {% endblock AHKSetVolume %} +} + + +AHKEcho(args*) { + {% block AHKEcho %} + arg := args[1] + return FormatResponse("ahk.message.StringResponseMessage", arg) + {% endblock AHKEcho %} +} + +AHKTraytip(args*) { + {% block AHKTraytip %} + title := args[1] + text := args[2] + second := args[3] + option := args[4] + + TrayTip(text, title, option) + return FormatNoValueResponse() + {% endblock AHKTraytip %} +} + +AHKShowToolTip(args*) { + {% block AHKShowToolTip %} + text := args[1] + x := args[2] + y := args[3] + which := args[4] + + ToolTip(text, IsNumber(x) ? Number(x) : unset, IsNumber(y) ? Number(y) : unset, which || unset) + ; In AHK v2, doubling the call to ToolTip seems necessary to ensure synchronous creation of the window + ; This seems to be more reliable than sleeping to wait for the tooltip callback + ; Without this doubled up call (or a sleep) we return the the blocking loop (awaiting next command from Python) + ; before the tooltip window is created, meaning the tooltip will not show until if/when processing the next command + ToolTip(text, IsNumber(x) ? Number(x) : unset, IsNumber(y) ? Number(y) : unset, which || unset) + return FormatNoValueResponse() + {% endblock AHKShowToolTip %} +} + +AHKGetClipboard(args*) { + {% block AHKGetClipboard %} + + return FormatResponse("ahk.message.StringResponseMessage", A_Clipboard) + {% endblock AHKGetClipboard %} +} + +AHKGetClipboardAll(args*) { + {% block AHKGetClipboardAll %} + data := ClipboardAll() + return FormatBinaryResponse(&data) + {% endblock AHKGetClipboardAll %} +} + +AHKSetClipboard(args*) { + {% block AHKSetClipboard %} + text := args[1] + A_Clipboard := text + return FormatNoValueResponse() + {% endblock AHKSetClipboard %} +} + +AHKSetClipboardAll(args*) { + {% block AHKSetClipboardAll %} + ; TODO there should be a way for us to accept a base64 string instead + filename := args[1] + contents := FileRead(filename, "RAW") + A_Clipboard := ClipboardAll(contents) + return FormatNoValueResponse() + {% endblock AHKSetClipboardAll %} +} + +AHKClipWait(args*) { + + timeout := args[1] + wait_for_any_data := args[2] + + if ClipWait(timeout, wait_for_any_data) + return FormatNoValueResponse() + else + return FormatResponse("ahk.message.TimeoutResponseMessage", "timed out waiting for clipboard data") + return FormatNoValueResponse() +} + +AHKBlockInput(args*) { + value := args[1] + BlockInput(value) + return FormatNoValueResponse() +} + +AHKMenuTrayTip(args*) { + value := args[1] + A_IconTip := value + return FormatNoValueResponse() +} + +AHKMenuTrayShow(args*) { + A_IconHidden := 0 + return FormatNoValueResponse() +} + +AHKMenuTrayHide(args*) { + A_IconHidden := 1 + return FormatNoValueResponse() +} + +AHKMenuTrayIcon(args*) { + filename := args[1] + icon_number := args[2] + freeze := args[3] + TraySetIcon(filename, icon_number, freeze) + return FormatNoValueResponse() +} + +;AHKGuiNew(args*) { +; +; options := args[1] +; title := args[2] +; Gui(New, options, title) +; return FormatResponse("ahk.message.StringResponseMessage", hwnd) +;} + +AHKMsgBox(args*) { + + options := args[1] + title := args[2] + text := args[3] + timeout := args[4] + if (timeout != "") { + options := "" options " T" timeout + } + res := MsgBox(text, title, options) + if (res = "Timeout") { + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "MsgBox timed out") + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", res) + } + return ret +} + +AHKInputBox(args*) { + + title := args[1] + prompt := args[2] + hide := args[3] + width := args[4] + height := args[5] + x := args[6] + y := args[7] + locale := args[8] + timeout := args[9] + default := args[10] + + ; TODO: support options correctly + options := "" + if (timeout != "") { + options .= "T" timeout + } + output := InputBox(prompt, title, options, default) + if (output.Result = "Timeout") { + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "Input box timed out") + } else if (output.Result = "Cancel") { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output.Value) + } + return ret +} + +AHKFileSelectFile(args*) { + + options := args[1] + root := args[2] + title := args[3] + filter := args[4] + output := FileSelect(options, root, title, filter) + if (output = "") { + ret := FormatNoValueResponse() + } else { + if IsObject(output) { + if (output.Length = 0) { + ret := FormatNoValueResponse() + } + else { + files := "" + for index, filename in output + if (A_Index != 1) { + files .= "`n" + } + files .= filename + ret := FormatResponse("ahk.message.StringResponseMessage", files) + } + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + } + return ret +} + +AHKFileSelectFolder(args*) { + + starting_folder := args[1] + options := args[2] + prompt := args[3] + + output := DirSelect(starting_folder, options, prompt) + + if (output = "") { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") + +b64decode(&pszString) { + ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw + ; [in] LPCSTR pszString, A pointer to a string that contains the formatted string to be converted. + ; [in] DWORD cchString, The number of characters of the formatted string to be converted, not including the terminating NULL character. If this parameter is zero, pszString is considered to be a null-terminated string. + ; [in] DWORD dwFlags, Indicates the format of the string to be converted. (see table in link above) + ; [in] BYTE *pbBinary, A pointer to a buffer that receives the returned sequence of bytes. If this parameter is NULL, the function calculates the length of the buffer needed and returns the size, in bytes, of required memory in the DWORD pointed to by pcbBinary. + ; [in, out] DWORD *pcbBinary, A pointer to a DWORD variable that, on entry, contains the size, in bytes, of the pbBinary buffer. After the function returns, this variable contains the number of bytes copied to the buffer. If this value is not large enough to contain all of the data, the function fails and GetLastError returns ERROR_MORE_DATA. + ; [out] DWORD *pdwSkip, A pointer to a DWORD value that receives the number of characters skipped to reach the beginning of the -----BEGIN ...----- header. If no header is present, then the DWORD is set to zero. This parameter is optional and can be NULL if it is not needed. + ; [out] DWORD *pdwFlags A pointer to a DWORD value that receives the flags actually used in the conversion. These are the same flags used for the dwFlags parameter. In many cases, these will be the same flags that were passed in the dwFlags parameter. If dwFlags contains one of the following flags, this value will receive a flag that indicates the actual format of the string. This parameter is optional and can be NULL if it is not needed. + + if (pszString = "") { + return "" + } + + cchString := StrLen(pszString) + + dwFlags := 0x00000001 ; CRYPT_STRING_BASE64: Base64, without headers. + getsize := 0 ; When this is NULL, the function returns the required size in bytes (for our first call, which is needed for our subsequent call) +; buff_size := 0 ; The function will write to this variable on our first call + pdwSkip := 0 ; We don't use any headers or preamble, so this is zero + pdwFlags := 0 ; We don't need this, so make it null + + ; The first call calculates the required size. The result is written to pbBinary + success := DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(pszString), "UInt", cchString, "UInt", dwFlags, "UInt", getsize, "UIntP", &buff_size := 0, "Int", pdwSkip, "Int", pdwFlags ) + if (success = 0) { + return "" + } + + ; We're going to give a pointer to a variable to the next call, but first we want to make the buffer the correct size using VarSetCapacity using the previous return value + ret := Buffer(buff_size) + + ; Now that we know the buffer size we need and have the variable's capacity set to the proper size, we'll pass a pointer to the variable for the decoded value to be written to + + success := DllCall( "Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(pszString), "UInt", cchString, "UInt", dwFlags, "Ptr", ret.Ptr, "UIntP", &buff_size, "Int", pdwSkip, "Int", pdwFlags ) + if (success=0) { + return "" + } + return StrGet(ret, "UTF-8") +} + + +b64encode(&data) { + ; REF: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptbinarytostringa + ; [in] const BYTE *pbBinary: A pointer to the array of bytes to be converted into a string. + ; [in] DWORD cbBinary: The number of elements in the pbBinary array. + ; [in] DWORD dwFlags: Specifies the format of the resulting formatted string (see table in REF) + ; [out, optional] LPSTR pszString: A pointer to the string, or null (0) to calculate size + ; [in, out] DWORD *pcchString: A pointer to a DWORD variable that contains the size, in TCHARs, of the pszString buffer + + cbBinary := data.Size + if (cbBinary = 0) { + return "" + } + dwFlags := 0x00000001 | 0x40000000 ; CRYPT_STRING_BASE64 + CRYPT_STRING_NOCRLF + + ; First step is to get the size so we can set the capacity of our return buffer correctly + success := DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", data, "UInt", cbBinary, "UInt", dwFlags, "Ptr", 0, "UIntP", &buff_size := 0) + if (success = 0) { + msg := Format("Problem converting data to base64 when calling CryptBinaryToString ({})", A_LastError) + throw Error(msg, -1) + } + + VarSetStrCapacity(&ret, buff_size * 2) + + ; Now we do the conversion to base64 and rteturn the string + + success := DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", data, "UInt", cbBinary, "UInt", dwFlags, "Str", ret, "UIntP", &buff_size) + if (success = 0) { + msg := Format("Problem converting data to base64 when calling CryptBinaryToString ({})", A_LastError) + throw Error(msg, -1) + } + return ret +} + + +CommandArrayFromQuery(text) { + decoded_commands := [] + encoded_array := StrSplit(text, "|") + function_name := encoded_array[1] + encoded_array.RemoveAt(1) + decoded_commands.push(function_name) + for index, encoded_value in encoded_array { + decoded_value := b64decode(&encoded_value) + decoded_commands.push(decoded_value) + } + return decoded_commands +} + +; BEGIN extension scripts +{% for ext in extensions %} +{{ ext.script_text }} + +{% endfor %} +; END extension scripts +{% block before_autoexecute %} +{% endblock before_autoexecute %} + +{% block autoexecute %} +stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] +stdout := FileOpen("*", "w", "UTF-8") +pyresp := "" + +Loop { + query := RTrim(stdin.ReadLine(), "`n") + if (query = "") { + ; Technically, this should only happen if the Python process has died, so sending a message is probably futile + ; But if this somehow triggers in some other case and the Python process is still listening, we'll try to have an informative error raised. + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", "Unexpected empty message; AHK exiting. This is likely a bug. Please report this issue at https://github.com/spyoungtech/ahk/issues") + stdout.Write(pyresp) + stdout.Read(0) + ; Exit to avoid leaving the process hanging around + ExitApp + } + argsArray := CommandArrayFromQuery(query) + try { + func_name := argsArray[1] + argsArray.RemoveAt(1) + {% block before_function %} + {% endblock before_function %} + pyresp := %func_name%(argsArray*) + {% block after_function %} + {% endblock after_function %} + } catch Any as e { + {% block function_error_handle %} + message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}`nStack:`n{}", e.what, e.line, e.message, e.extra, e.stack) + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) + {% endblock function_error_handle %} + } + {% block send_response %} + if (pyresp) { + stdout.Write(pyresp) + stdout.Read(0) + } else { + msg := FormatResponse("ahk.message.ExceptionResponseMessage", Format("Unknown Error when calling {}", func_name)) + stdout.Write(msg) + stdout.Read(0) + } + {% endblock send_response %} +} + +{% endblock autoexecute %} +{% endblock daemon_script %} diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk new file mode 100644 index 00000000..a6a19119 --- /dev/null +++ b/ahk/templates/daemon.ahk @@ -0,0 +1,2917 @@ +{% block daemon_script %} +{% block directives %} +#Requires AutoHotkey v1.1.17+ +#NoEnv +#Persistent +#SingleInstance Off +; BEGIN user-defined directives +{% block user_directives %} +{% for directive in directives %} +{{ directive }} + +{% endfor %} + +; END user-defined directives +{% endblock user_directives %} +{% endblock directives %} + +Critical, 100 + +{% block message_types %} +MESSAGE_TYPES := Object({% for tom, msg_class in message_registry.items() %}"{{ msg_class.fqn() }}", "{{ tom.decode('utf-8') }}"{% if not loop.last %}, {% endif %}{% endfor %}) +{% endblock message_types %} + +NOVALUE_SENTINEL := Chr(57344) + +FormatResponse(ByRef MessageType, ByRef payload) { + global MESSAGE_TYPES + newline_count := CountNewlines(payload) + response := Format("{}`n{}`n{}`n", MESSAGE_TYPES[MessageType], newline_count, payload) + return response +} + +FormatNoValueResponse() { + global NOVALUE_SENTINEL + return FormatResponse("ahk.message.NoValueResponseMessage", NOVALUE_SENTINEL) +} + +FormatBinaryResponse(ByRef bin) { + b64 := b64encode(bin) + return FormatResponse("ahk.message.B64BinaryResponseMessage", b64) +} + +AHKSetDetectHiddenWindows(args*) { + {% block AHKSetDetectHiddenWindows %} + value := args[1] + DetectHiddenWindows, %value% + return FormatNoValueResponse() + {% endblock AHKSetDetectHiddenWindows %} +} + +AHKSetTitleMatchMode(args*) { + {% block AHKSetTitleMatchMode %} + val1 := args[1] + val2 := args[2] + if (val1 != "") { + SetTitleMatchMode, %val1% + } + if (val2 != "") { + SetTitleMatchMode, %val2% + } + return FormatNoValueResponse() + {% endblock AHKSetTitleMatchMode %} +} + +AHKGetTitleMatchMode(args*) { + {% block AHKGetTitleMatchMode %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) + {% endblock AHKGetTitleMatchMode %} +} + +AHKGetTitleMatchSpeed(args*) { + {% block AHKGetTitleMatchSpeed %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) + {% endblock AHKGetTitleMatchSpeed %} +} + +AHKSetSendLevel(args*) { + {% block AHKSetSendLevel %} + level := args[1] + SendLevel, %level% + return FormatNoValueResponse() + {% endblock AHKSetSendLevel %} +} + +AHKGetSendLevel(args*) { + {% block AHKGetSendLevel %} + + return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) + {% endblock AHKGetSendLevel %} +} + +AHKWinExist(args*) { + {% block AHKWinExist %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + if WinExist(title, text, extitle, extext) { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } else { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp + {% endblock AHKWinExist %} +} + +AHKWinClose(args*) { + {% block AHKWinClose %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + secondstowait := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinClose %} +} + +AHKWinKill(args*) { + {% block AHKWinKill %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + secondstowait := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinKill, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinKill %} +} + +AHKWinWait(args*) { + {% block AHKWinWait %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + if (timeout != "") { + WinWait, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWait, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp + {% endblock AHKWinWait %} +} + +AHKWinWaitActive(args*) { + {% block AHKWinWaitActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + if (timeout != "") { + WinWaitActive, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitActive, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp + {% endblock AHKWinWaitActive %} +} + +AHKWinWaitNotActive(args*) { + {% block AHKWinWaitNotActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + if (timeout != "") { + WinWaitNotActive, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitNotActive, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp + {% endblock AHKWinWaitNotActive %} +} + +AHKWinWaitClose(args*) { + {% block AHKWinWaitClose %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + timeout := args[8] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + if (timeout != "") { + WinWaitClose, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitClose, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") + } else { + resp := FormatNoValueResponse() + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp + {% endblock AHKWinWaitClose %} +} + +AHKWinMinimize(args*) { + {% block AHKWinMinimize %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinMinimize, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinMinimize %} +} + +AHKWinMaximize(args*) { + {% block AHKWinMaximize %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinMaximize, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinMaximize %} +} + +AHKWinRestore(args*) { + {% block AHKWinRestore %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinRestore, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinRestore %} +} + +AHKWinIsActive(args*) { + {% block AHKWinIsActive %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + if WinActive(title, text, extitle, extext) { + response := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } else { + response := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinIsActive %} +} + +AHKWinGetID(args*) { + {% block AHKWinGetID %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, ID, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.WindowResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetID %} +} + +AHKWinGetTitle(args*) { + {% block AHKWinGetTitle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGetTitle, text, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatResponse("ahk.message.StringResponseMessage", text) + {% endblock AHKWinGetTitle %} +} + +AHKWinGetIDLast(args*) { + {% block AHKWinGetIDLast %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, IDLast, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.WindowResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetIDLast %} +} + +AHKWinGetPID(args*) { + {% block AHKWinGetPID %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, PID, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetPID %} +} + +AHKWinGetProcessName(args*) { + {% block AHKWinGetProcessName %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, ProcessName, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetProcessName %} +} + +AHKWinGetProcessPath(args*) { + {% block AHKWinGetProcessPath %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, ProcessPath, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetProcessPath %} +} + +AHKWinGetCount(args*) { + {% block AHKWinGetCount %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, Count, %title%, %text%, %extitle%, %extext% + if (output = 0) { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetCount %} +} + +AHKWinGetMinMax(args*) { + {% block AHKWinGetMinMax %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, MinMax, %title%, %text%, %extitle%, %extext% + if (output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetMinMax %} +} + +AHKWinGetControlList(args*) { + {% block AHKWinGetControlList %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, ahkid, ID, %title%, %text%, %extitle%, %extext% + + if (ahkid = "") { + return FormatNoValueResponse() + } + + WinGet, ctrList, ControlList, %title%, %text%, %extitle%, %extext% + WinGet, ctrListID, ControlListHWND, %title%, %text%, %extitle%, %extext% + + if (ctrListID = "") { + return FormatResponse("ahk.message.WindowControlListResponseMessage", Format("('{}', [])", ahkid)) + } + + ctrListArr := StrSplit(ctrList, "`n") + ctrListIDArr := StrSplit(ctrListID, "`n") + if (ctrListArr.Length() != ctrListIDArr.Length()) { + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatResponse("ahk.message.ExceptionResponseMessage", "Control hwnd/class lists have unexpected lengths") + } + + output := Format("('{}', [", ahkid) + + for index, hwnd in ctrListIDArr { + classname := ctrListArr[index] + output .= Format("('{}', '{}'), ", hwnd, classname) + + } + output .= "])" + response := FormatResponse("ahk.message.WindowControlListResponseMessage", output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetControlList %} +} + +AHKWinGetTransparent(args*) { + {% block AHKWinGetTransparent %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, Transparent, %title%, %text%, %extitle%, %extext% + response := FormatResponse("ahk.message.IntegerResponseMessage", output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetTransparent %} +} +AHKWinGetTransColor(args*) { + {% block AHKWinGetTransColor %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, TransColor, %title%, %text%, %extitle%, %extext% + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetTransColor %} +} +AHKWinGetStyle(args*) { + {% block AHKWinGetStyle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, Style, %title%, %text%, %extitle%, %extext% + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetStyle %} +} +AHKWinGetExStyle(args*) { + {% block AHKWinGetExStyle %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGet, output, ExStyle, %title%, %text%, %extitle%, %extext% + response := FormatResponse("ahk.message.NoValueResponseMessage", output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetExStyle %} +} + +AHKWinGetText(args*) { + {% block AHKWinGetText %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGetText, output,%title%,%text%,%extitle%,%extext% + + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was an error getting window text") + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetText %} +} + +AHKWinSetTitle(args*) { + {% block AHKWinSetTitle %} + new_title := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinSetTitle, %title%, %text%, %new_title%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetTitle %} +} + +AHKWinSetAlwaysOnTop(args*) { + {% block AHKWinSetAlwaysOnTop %} + toggle := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, AlwaysOntop, %toggle%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetAlwaysOnTop %} +} + +AHKWinSetBottom(args*) { + {% block AHKWinSetBottom %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Bottom,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetBottom %} +} + +AHKWinShow(args*) { + {% block AHKWinShow %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinShow, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinShow %} +} + +AHKWinHide(args*) { + {% block AHKWinHide %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinHide, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinHide %} +} + +AHKWinSetTop(args*) { + {% block AHKWinSetTop %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Top,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetTop %} +} + +AHKWinSetEnable(args*) { + {% block AHKWinSetEnable %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Enable,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetEnable %} +} + +AHKWinSetDisable(args*) { + {% block AHKWinSetDisable %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Disable,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetDisable %} +} + +AHKWinSetRedraw(args*) { + {% block AHKWinSetRedraw %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Redraw,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetRedraw %} +} + +AHKWinSetStyle(args*) { + {% block AHKWinSetStyle %} + + style := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Style, %style%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp + {% endblock AHKWinSetStyle %} +} + +AHKWinSetExStyle(args*) { + {% block AHKWinSetExStyle %} + + style := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, ExStyle, %style%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp + {% endblock AHKWinSetExStyle %} +} + +AHKWinSetRegion(args*) { + {% block AHKWinSetRegion %} + + options := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Region, %options%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else { + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp + {% endblock AHKWinSetRegion %} +} + +AHKWinSetTransparent(args*) { + {% block AHKWinSetTransparent %} + + transparency := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Transparent, %transparency%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKWinSetTransparent %} +} + +AHKWinSetTransColor(args*) { + {% block AHKWinSetTransColor %} + + color := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinSetTransColor %} +} + +AHKImageSearch(args*) { + {% block AHKImageSearch %} + + imagepath := args[5] + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + coord_mode := args[6] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode, Pixel, %coord_mode% + } + + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } + + ImageSearch, xpos, ypos,% x1,% y1,% x2,% y2, %imagepath% + + if (coord_mode != "") { + CoordMode, Pixel, %current_mode% + } + + if (ErrorLevel = 2) { + s := FormatResponse("ahk.message.ExceptionResponseMessage", "there was a problem that prevented the args from conducting the search (such as failure to open the image file or a badly formatted option)") + } else if (ErrorLevel = 1) { + s := FormatNoValueResponse() + } else { + s := FormatResponse("ahk.message.CoordinateResponseMessage", Format("({}, {})", xpos, ypos)) + } + + return s + {% endblock AHKImageSearch %} +} + +AHKPixelGetColor(args*) { + {% block AHKPixelGetColor %} + + x := args[1] + y := args[2] + coord_mode := args[3] + options := args[4] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode, Pixel, %coord_mode% + } + + PixelGetColor, color, %x%, %y%, %options% + ; TODO: check errorlevel + + if (coord_mode != "") { + CoordMode, Pixel, %current_mode% + } + + return FormatResponse("ahk.message.StringResponseMessage", color) + {% endblock AHKPixelGetColor %} +} + +AHKPixelSearch(args*) { + {% block AHKPixelSearch %} + + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + color := args[5] + variation := args[6] + options := args[7] + coord_mode := args[8] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode, Pixel, %coord_mode% + } + + PixelSearch, resultx, resulty, %x1%, %y1%, %x2%, %y2%, %color%, %variation%, %options% + + if (coord_mode != "") { + CoordMode, Pixel, %current_mode% + } + + if (ErrorLevel = 1) { + return FormatNoValueResponse() + } else if (ErrorLevel = 0) { + payload := Format("({}, {})", resultx, resulty) + return FormatResponse("ahk.message.CoordinateResponseMessage", payload) + } else if (ErrorLevel = 2) { + return FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem conducting the pixel search (ErrorLevel 2)") + } else { + return FormatResponse("ahk.message.ExceptionResponseMessage", "Unexpected error. This is probably a bug. Please report this at https://github.com/spyoungtech/ahk/issues") + } + + {% endblock AHKPixelSearch %} +} + +AHKMouseGetPos(args*) { + {% block AHKMouseGetPos %} + + coord_mode := args[1] + current_coord_mode := Format("{}", A_CoordModeMouse) + if (coord_mode != "") { + CoordMode, Mouse, %coord_mode% + } + MouseGetPos, xpos, ypos + + payload := Format("({}, {})", xpos, ypos) + resp := FormatResponse("ahk.message.CoordinateResponseMessage", payload) + + if (coord_mode != "") { + CoordMode, Mouse, %current_coord_mode% + } + + return resp + {% endblock AHKMouseGetPos %} +} + +AHKKeyState(args*) { + {% block AHKKeyState %} + + keyname := args[1] + mode := args[2] + if (mode != "") { + state := GetKeyState(keyname, mode) + } else{ + state := GetKeyState(keyname) + } + + if (state = "") { + return FormatNoValueResponse() + } + + if state is integer + return FormatResponse("ahk.message.IntegerResponseMessage", state) + + if state is float + return FormatResponse("ahk.message.FloatResponseMessage", state) + + return FormatResponse("ahk.message.StringResponseMessage", state) + + {% endblock AHKKeyState %} +} + +AHKMouseMove(args*) { + {% block AHKMouseMove %} + x := args[1] + y := args[2] + speed := args[3] + relative := args[4] + send_mode := args[5] + coord_mode := args[6] + + current_send_mode := Format("{}", A_SendMode) + current_coord_mode := Format("{}", A_CoordModeMouse) + + if (send_mode != "") { + SendMode, %send_mode% + } + + if (coord_mode != "") { + CoordMode, Mouse, %coord_mode% + } + + if (relative != "") { + MouseMove, %x%, %y%, %speed%, R + } else { + MouseMove, %x%, %y%, %speed% + } + + if (send_mode != "") { + SendMode, %current_send_mode% + } + + if (coord_mode != "") { + CoordMode, Mouse, %current_coord_mode% + } + + resp := FormatNoValueResponse() + return resp + {% endblock AHKMouseMove %} +} + +AHKClick(args*) { + {% block AHKClick %} + x := args[1] + y := args[2] + button := args[3] + click_count := args[4] + direction := args[5] + r := args[6] + relative_to := args[7] + send_mode := args[8] + current_coord_rel := Format("{}", A_CoordModeMouse) + current_send_mode := Format("{}", A_SendMode) + + if (send_mode != "") { + SendMode, %send_mode% + } + + if (relative_to != "") { + CoordMode, Mouse, %relative_to% + } + + + Click, %x%, %y%, %button%, %direction%, %r% + + if (send_mode != "") { + SendMode, %current_send_mode% + } + + if (relative_to != "") { + CoordMode, Mouse, %current_coord_rel% + } + + return FormatNoValueResponse() + + {% endblock AHKClick %} +} + +AHKGetCoordMode(args*) { + {% block AHKGetCoordMode %} + + target := args[1] + + if (target = "ToolTip") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeToolTip) + } + if (target = "Pixel") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModePixel) + } + if (target = "Mouse") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMouse) + } + if (target = "Caret") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeCaret) + } + if (target = "Menu") { + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMenu) + } + return FormatResponse("ahk.message.ExceptionResponseMessage", "Invalid coord mode") + {% endblock AHKGetCoordMode %} +} + +AHKSetCoordMode(args*) { + {% block AHKSetCoordMode %} + target := args[1] + relative_to := args[2] + CoordMode, %target%, %relative_to% + + return FormatNoValueResponse() + {% endblock AHKSetCoordMode %} +} + +AHKGetSendMode(args*) { + return FormatResponse("ahk.message.StringResponseMessage", A_SendMode) +} + + +AHKSetSendMode(args*) { + mode := args[1] + SendMode, %mode% + return FormatNoValueResponse() +} + + +AHKMouseClickDrag(args*) { + {% block AHKMouseClickDrag %} + button := args[1] + x1 := args[2] + y1 := args[3] + x2 := args[4] + y2 := args[5] + speed := args[6] + relative := args[7] + relative_to := args[8] + send_mode := args[8] + current_send_mode := Format("{}", A_SendMode) + if (send_mode != "") { + SendMode, %send_mode% + } + + current_coord_rel := Format("{}", A_CoordModeMouse) + + if (relative_to != "") { + CoordMode, Mouse, %relative_to% + } + + MouseClickDrag, %button%, %x1%, %y1%, %x2%, %y2%, %speed%, %relative% + + if (relative_to != "") { + CoordMode, Mouse, %current_coord_rel% + } + + if (send_mode != "") { + SendMode, %current_send_mode% + } + + return FormatNoValueResponse() + + {% endblock AHKMouseClickDrag %} +} + +AHKRegRead(args*) { + {% block RegRead %} + + key_name := args[1] + value_name := args[2] + + RegRead, output, %key_name%, %value_name% + + if (ErrorLevel = 1) { + resp := FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) + } + else { + resp := FormatResponse("ahk.message.StringResponseMessage", Format("{}", output)) + } + return resp + {% endblock RegRead %} +} + +AHKRegWrite(args*) { + {% block RegWrite %} + + value_type := args[1] + key_name := args[2] + value_name := args[3] + value := args[4] + RegWrite, %value_type%, %key_name%, %value_name%, %value% + if (ErrorLevel = 1) { + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) + } + + return FormatNoValueResponse() + {% endblock RegWrite %} +} + +AHKRegDelete(args*) { + {% block RegDelete %} + + key_name := args[1] + value_name := args[2] + RegDelete, %key_name%, %value_name% + if (ErrorLevel = 1) { + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) + } + return FormatNoValueResponse() + + {% endblock RegDelete %} +} + +AHKKeyWait(args*) { + {% block AHKKeyWait %} + + keyname := args[1] + options := args[2] + + if (options = "") { + KeyWait,% keyname + } else { + KeyWait,% keyname,% options + } + ret := ErrorLevel + + if (ret = 1) { + return FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else if (ret = 0) { + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + } else { + ; Unclear if this is even reachable + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("There was a problem. ErrorLevel: {}", ret)) + } + + {% endblock AHKKeyWait %} +} + +SetKeyDelay(args*) { + {% block SetKeyDelay %} + SetKeyDelay, args[1], args[2] + {% endblock SetKeyDelay %} +} + +AHKSend(args*) { + {% block AHKSend %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + send_mode := args[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + current_send_mode := Format("{}", A_SendMode) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + + if (send_mode != "") { + SendMode, %send_mode% + } + + Send,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + + if (send_mode != "") { + SendMode, %current_send_mode% + } + + return FormatNoValueResponse() + {% endblock AHKSend %} +} + +AHKSendRaw(args*) { + {% block AHKSendRaw %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + send_mode := args[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + current_send_mode := Format("{}", A_SendMode) + + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + + if (send_mode != "") { + SendMode, %send_mode% + } + + SendRaw,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + + if (send_mode != "") { + SendMode, %current_send_mode% + } + + return FormatNoValueResponse() + {% endblock AHKSendRaw %} +} + +AHKSendInput(args*) { + {% block AHKSendInput %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + + SendInput,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + return FormatNoValueResponse() + {% endblock AHKSendInput %} +} + +AHKSendEvent(args*) { + {% block AHKSendEvent %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + + SendEvent,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + return FormatNoValueResponse() + {% endblock AHKSendEvent %} +} + +AHKSendPlay(args*) { + {% block AHKSendPlay %} + str := args[1] + key_delay := args[2] + key_press_duration := args[3] + current_delay := Format("{}", A_KeyDelayPlay) + current_key_duration := Format("{}", A_KeyDurationPlay) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration%, Play + } + + SendPlay,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + return FormatNoValueResponse() + {% endblock AHKSendPlay %} +} + +AHKSetCapsLockState(args*) { + {% block AHKSetCapsLockState %} + state := args[1] + if (state = "") { + SetCapsLockState % !GetKeyState("CapsLock", "T") + } else { + SetCapsLockState, %state% + } + return FormatNoValueResponse() + {% endblock AHKSetCapsLockState %} +} + + +AHKSetNumLockState(args*) { + {% block AHKSetNumLockState %} + state := args[1] + if (state = "") { + SetNumLockState % !GetKeyState("NumLock", "T") + } else { + SetNumLockState, %state% + } + return FormatNoValueResponse() + {% endblock AHKSetNumLockState %} +} + +AHKSetScrollLockState(args*) { + {% block AHKSetScrollLockState %} + state := args[1] + if (state = "") { + SetScrollLockState % !GetKeyState("ScrollLock", "T") + } else { + SetScrollLockState, %state% + } + return FormatNoValueResponse() + {% endblock AHKSetScrollLockState %} +} + +HideTrayTip(args*) { + {% block HideTrayTip %} + TrayTip ; Attempt to hide it the normal way. + if SubStr(A_OSVersion,1,3) = "10." { + Menu Tray, NoIcon + Sleep 200 ; It may be necessary to adjust this sleep. + Menu Tray, Icon + } + {% endblock HideTrayTip %} +} + +AHKWinGetClass(args*) { + {% block AHKWinGetClass %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGetClass, output,%title%,%text%,%extitle%,%extext% + + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was an error getting window class") + } else { + response := FormatResponse("ahk.message.StringResponseMessage", output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response + {% endblock AHKWinGetClass %} +} + +AHKWinActivate(args*) { + {% block AHKWinActivate %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinActivate, %title%, %text%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + {% endblock AHKWinActivate %} +} + +AHKWindowList(args*) { + {% block AHKWindowList %} + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + if (detect_hw) { + DetectHiddenWindows, %detect_hw% + } + + WinGet windows, List, %title%, %text%, %extitle%, %extext% + r := "" + Loop %windows% + { + id := windows%A_Index% + r .= id . "`," + } + resp := FormatResponse("ahk.message.WindowListResponseMessage", r) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp + {% endblock AHKWindowList %} +} + +AHKControlClick(args*) { + {% block AHKControlClick %} + + ctrl := args[1] + title := args[2] + text := args[3] + button := args[4] + click_count := args[5] + options := args[6] + exclude_title := args[7] + exclude_text := args[8] + detect_hw := args[9] + match_mode := args[10] + match_speed := args[11] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + ControlClick, %ctrl%, %title%, %text%, %button%, %click_count%, %options%, %exclude_title%, %exclude_text% + + if (ErrorLevel != 0) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "Failed to click control") + } else { + response := FormatNoValueResponse() + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response + {% endblock AHKControlClick %} +} + +AHKControlGetText(args*) { + {% block AHKControlGetText %} + + ctrl := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + ControlGetText, result, %ctrl%, %title%, %text%, %extitle%, %extext% + + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the text") + } else { + response := FormatResponse("ahk.message.StringResponseMessage", result) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response + {% endblock AHKControlGetText %} +} + +AHKControlGetPos(args*) { + {% block AHKControlGetPos %} + + ctrl := args[1] + title := args[2] + text := args[3] + extitle := args[4] + extext := args[5] + detect_hw := args[6] + match_mode := args[7] + match_speed := args[8] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + ControlGetPos, x, y, w, h, %ctrl%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the text") + } else { + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse("ahk.message.PositionResponseMessage", result) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response + + {% endblock AHKControlGetPos %} +} + +AHKControlSend(args*) { + {% block AHKControlSend %} + ctrl := args[1] + keys := args[2] + title := args[3] + text := args[4] + extitle := args[5] + extext := args[6] + detect_hw := args[7] + match_mode := args[8] + match_speed := args[9] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + ControlSend, %ctrl%, %keys%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() + {% endblock AHKControlSend %} +} + +AHKWinFromMouse(args*) { + {% block AHKWinFromMouse %} + + MouseGetPos,,, MouseWin + + if (MouseWin = "") { + return FormatNoValueResponse() + } + + return FormatResponse("ahk.message.WindowResponseMessage", MouseWin) + {% endblock AHKWinFromMouse %} +} + +AHKWinIsAlwaysOnTop(args*) { + {% block AHKWinIsAlwaysOnTop %} + + title := args[1] + WinGet, ExStyle, ExStyle, %title% + if (ExStyle = "") + return FormatNoValueResponse() + + if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + else + return FormatResponse("ahk.message.BooleanResponseMessage", 0) + {% endblock AHKWinIsAlwaysOnTop %} +} + +AHKWinMove(args*) { + {% block AHKWinMove %} + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + x := args[8] + y := args[9] + width := args[10] + height := args[11] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinMove, %title%, %text%, %x%, %y%, %width%, %height%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() + + {% endblock AHKWinMove %} +} + +AHKWinGetPos(args*) { + {% block AHKWinGetPos %} + + title := args[1] + text := args[2] + extitle := args[3] + extext := args[4] + detect_hw := args[5] + match_mode := args[6] + match_speed := args[7] + + current_match_mode := Format("{}", A_TitleMatchMode) + current_match_speed := Format("{}", A_TitleMatchModeSpeed) + if (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGetPos, x, y, w, h, %title%, %text%, %extitle%, %extext% + + if (x = "") { + response := FormatNoValueResponse() + } else { + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse("ahk.message.PositionResponseMessage", result) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response + {% endblock AHKWinGetPos %} +} + +AHKGetVolume(args*) { + {% block AHKGetVolume %} + + device_number := args[1] + + try { + SoundGetWaveVolume, retval, %device_number% + } catch e { + response := FormatResponse("ahk.message.ExceptionResponseMessage", Format("There was a problem getting the volume with device of index {} ({})", device_number, e.message)) + return response + } + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", Format("There was a problem getting the volume with device of index {}", device_number)) + } else { + response := FormatResponse("ahk.message.FloatResponseMessage", Format("{}", retval)) + } + return response + {% endblock AHKGetVolume %} +} + +AHKSoundBeep(args*) { + {% block AHKSoundBeep %} + freq := args[1] + duration := args[2] + SoundBeep , %freq%, %duration% + return FormatNoValueResponse() + {% endblock AHKSoundBeep %} +} + +AHKSoundGet(args*) { + {% block AHKSoundGet %} + + device_number := args[1] + component_type := args[2] + control_type := args[3] + + SoundGet, retval, %component_type%, %control_type%, %device_number% + ; TODO interpret return type + return FormatResponse("ahk.message.StringResponseMessage", Format("{}", retval)) + {% endblock AHKSoundGet %} +} + +AHKSoundSet(args*) { + {% block AHKSoundSet %} + device_number := args[1] + component_type := args[2] + control_type := args[3] + value := args[4] + SoundSet, %value%, %component_type%, %control_type%, %device_number% + return FormatNoValueResponse() + {% endblock AHKSoundSet %} +} + +AHKSoundPlay(args*) { + {% block AHKSoundPlay %} + filename := args[1] + SoundPlay, %filename% + return FormatNoValueResponse() + {% endblock AHKSoundPlay %} +} + +AHKSetVolume(args*) { + {% block AHKSetVolume %} + device_number := args[1] + value := args[2] + SoundSetWaveVolume, %value%, %device_number% + return FormatNoValueResponse() + {% endblock AHKSetVolume %} +} + +CountNewlines(ByRef s) { + newline := "`n" + StringReplace, s, s, %newline%, %newline%, UseErrorLevel + count := ErrorLevel + return count +} + +AHKEcho(args*) { + {% block AHKEcho %} + arg := args[1] + return FormatResponse("ahk.message.StringResponseMessage", arg) + {% endblock AHKEcho %} +} + +AHKTraytip(args*) { + {% block AHKTraytip %} + title := args[1] + text := args[2] + second := args[3] + option := args[4] + + TrayTip, %title%, %text%, %second%, %option% + return FormatNoValueResponse() + {% endblock AHKTraytip %} +} + +AHKShowToolTip(args*) { + {% block AHKShowToolTip %} + text := args[1] + x := args[2] + y := args[3] + which := args[4] + ToolTip, %text%, %x%, %y%, %which% + return FormatNoValueResponse() + {% endblock AHKShowToolTip %} +} + +AHKGetClipboard(args*) { + {% block AHKGetClipboard %} + + return FormatResponse("ahk.message.StringResponseMessage", Clipboard) + {% endblock AHKGetClipboard %} +} + +AHKGetClipboardAll(args*) { + {% block AHKGetClipboardAll %} + data := ClipboardAll + return FormatBinaryResponse(data) + {% endblock AHKGetClipboardAll %} +} + +AHKSetClipboard(args*) { + {% block AHKSetClipboard %} + text := args[1] + Clipboard := text + return FormatNoValueResponse() + {% endblock AHKSetClipboard %} +} + +AHKSetClipboardAll(args*) { + {% block AHKSetClipboardAll %} + ; TODO there should be a way for us to accept a base64 string instead + filename := args[1] + FileRead, Clipboard, %filename% + return FormatNoValueResponse() + {% endblock AHKSetClipboardAll %} +} + +AHKClipWait(args*) { + + timeout := args[1] + wait_for_any_data := args[2] + + ClipWait, %timeout%, %wait_for_any_data% + + if (ErrorLevel = 1) { + return FormatResponse("ahk.message.TimeoutResponseMessage", "timed out waiting for clipboard data") + } + return FormatNoValueResponse() +} + +AHKBlockInput(args*) { + value := args[1] + BlockInput, %value% + return FormatNoValueResponse() +} + +AHKMenuTrayTip(args*) { + value := args[1] + Menu, Tray, Tip, %value% + return FormatNoValueResponse() +} + +AHKMenuTrayShow(args*) { + Menu, Tray, Icon + return FormatNoValueResponse() +} + +AHKMenuTrayHide(args*) { + Menu, Tray, NoIcon + return FormatNoValueResponse() +} + +AHKMenuTrayIcon(args*) { + filename := args[1] + icon_number := args[2] + freeze := args[3] + Menu, Tray, Icon, %filename%, %icon_number%,%freeze% + return FormatNoValueResponse() +} + +AHKGuiNew(args*) { + + options := args[1] + title := args[2] + Gui, New, %options%, %title% + return FormatResponse("ahk.message.StringResponseMessage", hwnd) +} + +AHKMsgBox(args*) { + + options := args[1] + title := args[2] + text := args[3] + timeout := args[4] + MsgBox,% options, %title%, %text%, %timeout% + IfMsgBox, Yes + ret := FormatResponse("ahk.message.StringResponseMessage", "Yes") + IfMsgBox, No + ret := FormatResponse("ahk.message.StringResponseMessage", "No") + IfMsgBox, OK + ret := FormatResponse("ahk.message.StringResponseMessage", "OK") + IfMsgBox, Cancel + ret := FormatResponse("ahk.message.StringResponseMessage", "Cancel") + IfMsgBox, Abort + ret := FormatResponse("ahk.message.StringResponseMessage", "Abort") + IfMsgBox, Ignore + ret := FormatResponse("ahk.message.StringResponseMessage", "Ignore") + IfMsgBox, Retry + ret := FormatResponse("ahk.message.StringResponseMessage", "Retry") + IfMsgBox, Continue + ret := FormatResponse("ahk.message.StringResponseMessage", "Continue") + IfMsgBox, TryAgain + ret := FormatResponse("ahk.message.StringResponseMessage", "TryAgain") + IfMsgBox, Timeout + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "MsgBox timed out") + return ret +} + +AHKInputBox(args*) { + + title := args[1] + prompt := args[2] + hide := args[3] + width := args[4] + height := args[5] + x := args[6] + y := args[7] + locale := args[8] + timeout := args[9] + default := args[10] + + InputBox, output, %title%, %prompt%, %hide%, %width%, %height%, %x%, %y%, %locale%, %timeout%, %default% + if (ErrorLevel = 2) { + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "Input box timed out") + } else if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +AHKFileSelectFile(byRef args) { + + options := args[1] + root := args[2] + title := args[3] + filter := args[4] + FileSelectFile, output, %options%, %root%, %title%, %filter% + if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +AHKFileSelectFolder(byRef args) { + + starting_folder := args[1] + options := args[2] + prompt := args[3] + + FileSelectFolder, output, %starting_folder%, %options%, %prompt% + + if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") + + +b64decode(ByRef pszString) { + ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw + ; [in] LPCSTR pszString, A pointer to a string that contains the formatted string to be converted. + ; [in] DWORD cchString, The number of characters of the formatted string to be converted, not including the terminating NULL character. If this parameter is zero, pszString is considered to be a null-terminated string. + ; [in] DWORD dwFlags, Indicates the format of the string to be converted. (see table in link above) + ; [in] BYTE *pbBinary, A pointer to a buffer that receives the returned sequence of bytes. If this parameter is NULL, the function calculates the length of the buffer needed and returns the size, in bytes, of required memory in the DWORD pointed to by pcbBinary. + ; [in, out] DWORD *pcbBinary, A pointer to a DWORD variable that, on entry, contains the size, in bytes, of the pbBinary buffer. After the function returns, this variable contains the number of bytes copied to the buffer. If this value is not large enough to contain all of the data, the function fails and GetLastError returns ERROR_MORE_DATA. + ; [out] DWORD *pdwSkip, A pointer to a DWORD value that receives the number of characters skipped to reach the beginning of the -----BEGIN ...----- header. If no header is present, then the DWORD is set to zero. This parameter is optional and can be NULL if it is not needed. + ; [out] DWORD *pdwFlags A pointer to a DWORD value that receives the flags actually used in the conversion. These are the same flags used for the dwFlags parameter. In many cases, these will be the same flags that were passed in the dwFlags parameter. If dwFlags contains one of the following flags, this value will receive a flag that indicates the actual format of the string. This parameter is optional and can be NULL if it is not needed. + + if (pszString = "") { + return "" + } + + cchString := StrLen(pszString) + + dwFlags := 0x00000001 ; CRYPT_STRING_BASE64: Base64, without headers. + getsize := 0 ; When this is NULL, the function returns the required size in bytes (for our first call, which is needed for our subsequent call) + buff_size := 0 ; The function will write to this variable on our first call + pdwSkip := 0 ; We don't use any headers or preamble, so this is zero + pdwFlags := 0 ; We don't need this, so make it null + + ; The first call calculates the required size. The result is written to pbBinary + success := DllCall("Crypt32\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "UInt", getsize, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags) + if (success = 0) { + return "" + } + + ; We're going to give a pointer to a variable to the next call, but first we want to make the buffer the correct size using VarSetCapacity using the previous return value + VarSetCapacity(ret, buff_size, 0) + + ; Now that we know the buffer size we need and have the variable's capacity set to the proper size, we'll pass a pointer to the variable for the decoded value to be written to + + success := DllCall("Crypt32\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "Ptr", &ret, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags) + if (success=0) { + return "" + } + + return StrGet(&ret, "UTF-8") +} + +b64encode(ByRef data) { + ; REF: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptbinarytostringa + ; [in] const BYTE *pbBinary: A pointer to the array of bytes to be converted into a string. + ; [in] DWORD cbBinary: The number of elements in the pbBinary array. + ; [in] DWORD dwFlags: Specifies the format of the resulting formatted string (see table in REF) + ; [out, optional] LPSTR pszString: A pointer to the string, or null (0) to calculate size + ; [in, out] DWORD *pcchString: A pointer to a DWORD variable that contains the size, in TCHARs, of the pszString buffer + + cbBinary := StrLen(data) * (A_IsUnicode ? 2 : 1) + if (cbBinary = 0) { + return "" + } + dwFlags := 0x00000001 | 0x40000000 ; CRYPT_STRING_BASE64 + CRYPT_STRING_NOCRLF + + ; First step is to get the size so we can set the capacity of our return buffer correctly + success := DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", &data, "UInt", cbBinary, "UInt", dwFlags, "Ptr", 0, "UIntP", buff_size) + if (success = 0) { + msg := Format("Problem converting data to base64 when calling CryptBinaryToString ({})", A_LastError) + throw Exception(msg, -1) + } + + VarSetCapacity(ret, buff_size * (A_IsUnicode ? 2 : 1)) + + ; Now we do the conversion to base64 and rteturn the string + + success := DllCall("Crypt32\CryptBinaryToString", "Ptr", &data, "UInt", cbBinary, "UInt", dwFlags, "Str", ret, "UIntP", buff_size) + if (success = 0) { + msg := Format("Problem converting data to base64 when calling CryptBinaryToString ({})", A_LastError) + throw Exception(msg, -1) + } + return ret +} + +; End of included content + +CommandArrayFromQuery(ByRef text) { + decoded_commands := [] + encoded_array := StrSplit(text, "|") + function_name := encoded_array[1] + encoded_array.RemoveAt(1) + decoded_commands.push(function_name) + for index, encoded_value in encoded_array { + decoded_value := b64decode(encoded_value) + decoded_commands.push(decoded_value) + } + return decoded_commands +} + +; BEGIN extension scripts +{% for ext in extensions %} +{{ ext.script_text }} + +{% endfor %} +; END extension scripts + +{% block before_autoexecute %} +{% endblock before_autoexecute %} + +{% block autoexecute %} +stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] +pyresp := "" + +Loop { + query := RTrim(stdin.ReadLine(), "`n") + if (query = "") { + ; Technically this should only happen if the Python process has died, so sending a message is probably futile + ; But if this somehow triggers in some other case, we'll try to have an informative error raised. + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", "Unexpected empty message; AHK exiting. This is likely a bug. Please report this issue at https://github.com/spyoungtech/ahk/issues") + FileAppend, %pyresp%, *, UTF-8 + + ; Exit to avoid leaving the process hanging around needlessly + ExitApp + } + argsArray := CommandArrayFromQuery(query) + try { + func := argsArray[1] + argsArray.RemoveAt(1) + {% block before_function %} + {% endblock before_function %} + pyresp := %func%(argsArray*) + {% block after_function %} + {% endblock after_function %} + } catch e { + {% block function_error_handle %} + message := Format("Error occurred in {}. The error message was: {}", e.What, e.message) + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) + {% endblock function_error_handle %} + } + {% block send_response %} + if (pyresp) { + FileAppend, %pyresp%, *, UTF-8 + } else { + msg := FormatResponse("ahk.message.ExceptionResponseMessage", Format("Unknown Error when calling {}", func)) + FileAppend, %msg%, *, UTF-8 + } + {% endblock send_response %} +} +{% endblock autoexecute %} +{% endblock daemon_script %} diff --git a/ahk/templates/hotkey.ahk b/ahk/templates/hotkey.ahk deleted file mode 100644 index 0503d284..00000000 --- a/ahk/templates/hotkey.ahk +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -{{ hotkey }}:: - {{ script }} - return -{% endblock body %} diff --git a/ahk/templates/hotkeys-v2.ahk b/ahk/templates/hotkeys-v2.ahk new file mode 100644 index 00000000..02f94edf --- /dev/null +++ b/ahk/templates/hotkeys-v2.ahk @@ -0,0 +1,121 @@ +#Requires AutoHotkey >= 2.0- +{% for directive in directives %} +{% if directive.apply_to_hotkeys_process %} + +{{ directive }} +{% endif %} +{% endfor %} + + +KEEPALIVE := Chr(57344) + +stdout := FileOpen("*", "w", "UTF-8") +stdin := FileOpen("*", "r `n", "UTF-8") + +WriteStdout(s) { + global stdout + Critical "On" + stdout.Write(s) + stdout.Read(0) + Critical "Off" +} + +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") + +b64decode(&pszString) { + ; TODO load DLL globally for performance + ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw + ; [in] LPCSTR pszString, A pointer to a string that contains the formatted string to be converted. + ; [in] DWORD cchString, The number of characters of the formatted string to be converted, not including the terminating NULL character. If this parameter is zero, pszString is considered to be a null-terminated string. + ; [in] DWORD dwFlags, Indicates the format of the string to be converted. (see table in link above) + ; [in] BYTE *pbBinary, A pointer to a buffer that receives the returned sequence of bytes. If this parameter is NULL, the function calculates the length of the buffer needed and returns the size, in bytes, of required memory in the DWORD pointed to by pcbBinary. + ; [in, out] DWORD *pcbBinary, A pointer to a DWORD variable that, on entry, contains the size, in bytes, of the pbBinary buffer. After the function returns, this variable contains the number of bytes copied to the buffer. If this value is not large enough to contain all of the data, the function fails and GetLastError returns ERROR_MORE_DATA. + ; [out] DWORD *pdwSkip, A pointer to a DWORD value that receives the number of characters skipped to reach the beginning of the -----BEGIN ...----- header. If no header is present, then the DWORD is set to zero. This parameter is optional and can be NULL if it is not needed. + ; [out] DWORD *pdwFlags A pointer to a DWORD value that receives the flags actually used in the conversion. These are the same flags used for the dwFlags parameter. In many cases, these will be the same flags that were passed in the dwFlags parameter. If dwFlags contains one of the following flags, this value will receive a flag that indicates the actual format of the string. This parameter is optional and can be NULL if it is not needed. + + if (pszString = "") { + return "" + } + + cchString := StrLen(pszString) + + dwFlags := 0x00000001 ; CRYPT_STRING_BASE64: Base64, without headers. + getsize := 0 ; When this is NULL, the function returns the required size in bytes (for our first call, which is needed for our subsequent call) +; buff_size := 0 ; The function will write to this variable on our first call + pdwSkip := 0 ; We don't use any headers or preamble, so this is zero + pdwFlags := 0 ; We don't need this, so make it null + + ; The first call calculates the required size. The result is written to pbBinary + success := DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(pszString), "UInt", cchString, "UInt", dwFlags, "UInt", getsize, "UIntP", &buff_size := 0, "Int", pdwSkip, "Int", pdwFlags ) + if (success = 0) { + return "" + } + + ; We're going to give a pointer to a variable to the next call, but first we want to make the buffer the correct size using VarSetCapacity using the previous return value + ret := Buffer(buff_size) + + ; Now that we know the buffer size we need and have the variable's capacity set to the proper size, we'll pass a pointer to the variable for the decoded value to be written to + + success := DllCall( "Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(pszString), "UInt", cchString, "UInt", dwFlags, "Ptr", ret.Ptr, "UIntP", &buff_size, "Int", pdwSkip, "Int", pdwFlags ) + if (success=0) { + return "" + } + return StrGet(ret, "UTF-8") +} + + + +{% for hotkey in hotkeys %} + +{{ hotkey.keyname }}:: +{ + WriteStdout("{{ hotkey._id }}`n") + return +} +{% endfor %} + +{% for hotstring in hotstrings %} +{% if hotstring.replacement %} +:{{ hotstring.options }}:{{ hotstring.trigger }}:: + hostring_{{ hotstring._id }}_func(hs) { + replacement_b64 := "{{ hotstring._replacement_as_b64 }}" + replacement := b64decode(&replacement_b64) + Send(replacement) + } +{% else %} +:{{ hotstring.options }}:{{ hotstring.trigger }}:: + hostring_{{ hotstring._id }}_func(hs) { + WriteStdout("{{ hotstring._id }}`n") + } +{% endif %} + + +{% endfor %} + +{% if on_clipboard %} + + +ClipChanged(Type) { + CLIPBOARD_SENTINEL := Chr(57345) + ret := Format("{}{}`n", CLIPBOARD_SENTINEL, Type) + WriteStdout(ret) + return +} + +OnClipboardChange(ClipChanged) + +{% endif %} +SetTimer KeepAliveFunc, 2000 + +KeepAliveFunc() { + global stdin + global KEEPALIVE + WriteStdout(Format("{}`n", KEEPALIVE)) + alive_message := RTrim(stdin.ReadLine(), "`n") + if (alive_message != KEEPALIVE) { + ; The parent Python process has terminated unexpectedly + ; Exit to avoid leaving the hotkey process around + ExitApp + } + return +} diff --git a/ahk/templates/hotkeys.ahk b/ahk/templates/hotkeys.ahk new file mode 100644 index 00000000..e04d0e20 --- /dev/null +++ b/ahk/templates/hotkeys.ahk @@ -0,0 +1,110 @@ +#Requires AutoHotkey v1.1.17+ +#Persistent + +{% for directive in directives %} +{% if directive.apply_to_hotkeys_process %} + +{{ directive }} +{% endif %} +{% endfor %} + +{% if on_clipboard %} +OnClipboardChange("ClipChanged") +{% endif %} +KEEPALIVE := Chr(57344) +stdin := FileOpen("*", "r `n", "UTF-8") +SetTimer, keepalive, 2000 + +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") + +b64decode(ByRef pszString) { + ; TODO load DLL globally for performance + ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw + ; [in] LPCSTR pszString, A pointer to a string that contains the formatted string to be converted. + ; [in] DWORD cchString, The number of characters of the formatted string to be converted, not including the terminating NULL character. If this parameter is zero, pszString is considered to be a null-terminated string. + ; [in] DWORD dwFlags, Indicates the format of the string to be converted. (see table in link above) + ; [in] BYTE *pbBinary, A pointer to a buffer that receives the returned sequence of bytes. If this parameter is NULL, the function calculates the length of the buffer needed and returns the size, in bytes, of required memory in the DWORD pointed to by pcbBinary. + ; [in, out] DWORD *pcbBinary, A pointer to a DWORD variable that, on entry, contains the size, in bytes, of the pbBinary buffer. After the function returns, this variable contains the number of bytes copied to the buffer. If this value is not large enough to contain all of the data, the function fails and GetLastError returns ERROR_MORE_DATA. + ; [out] DWORD *pdwSkip, A pointer to a DWORD value that receives the number of characters skipped to reach the beginning of the -----BEGIN ...----- header. If no header is present, then the DWORD is set to zero. This parameter is optional and can be NULL if it is not needed. + ; [out] DWORD *pdwFlags A pointer to a DWORD value that receives the flags actually used in the conversion. These are the same flags used for the dwFlags parameter. In many cases, these will be the same flags that were passed in the dwFlags parameter. If dwFlags contains one of the following flags, this value will receive a flag that indicates the actual format of the string. This parameter is optional and can be NULL if it is not needed. + + if (pszString = "") { + return "" + } + + cchString := StrLen(pszString) + dwFlags := 0x00000001 ; CRYPT_STRING_BASE64: Base64, without headers. + getsize := 0 ; When this is NULL, the function returns the required size in bytes (for our first call, which is needed for our subsequent call) + buff_size := 0 ; The function will write to this variable on our first call + pdwSkip := 0 ; We don't use any headers or preamble, so this is zero + pdwFlags := 0 ; We don't need this, so make it null + + + ; The first call calculates the required size. The result is written to pbBinary + success := DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "UInt", getsize, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) + if (success = 0) { + return "" + } + + ; We're going to give a pointer to a variable to the next call, but first we want to make the buffer the correct size using VarSetCapacity using the previous return value + VarSetCapacity(ret, buff_size, 0) + + ; Now that we know the buffer size we need and have the variable's capacity set to the proper size, we'll pass a pointer to the variable for the decoded value to be written to + + success := DllCall( "Crypt32.dll\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "Ptr", &ret, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) + if (success=0) { + return "" + } + + return StrGet(&ret, "UTF-8") +} + +{% for hotkey in hotkeys %} + +{{ hotkey.keyname }}:: + FileAppend, {{ hotkey._id }}`n, *, UTF-8 + return + +{% endfor %} + +{% for hotstring in hotstrings %} +{% if hotstring.replacement %} +:{{ hotstring.options }}:{{ hotstring.trigger }}:: + hostring_{{ hotstring._id }}_func() { + replacement_b64 := "{{ hotstring._replacement_as_b64 }}" + replacement := b64decode(replacement_b64) + Send, %replacement% + } +{% else %} +:{{ hotstring.options }}:{{ hotstring.trigger }}:: + hostring_{{ hotstring._id }}_func() { + FileAppend, {{ hotstring._id }}`n, *, UTF-8 + } +{% endif %} + + +{% endfor %} + +{% if on_clipboard %} + + +ClipChanged(Type) { + CLIPBOARD_SENTINEL := Chr(57345) + ret := Format("{}{}`n", CLIPBOARD_SENTINEL, Type) + FileAppend, %ret%, *, UTF-8 + return +} +{% endif %} + + +keepalive: + global KEEPALIVE + global stdin + FileAppend, %KEEPALIVE%`n, *, UTF-8 + alive_message := RTrim(stdin.ReadLine(), "`n") + if (alive_message != KEEPALIVE) { + ; The parent Python process has terminated unexpectedly + ; Exit to avoid leaving the hotkey process around + ExitApp + } + return diff --git a/ahk/templates/keyboard/key_state.ahk b/ahk/templates/keyboard/key_state.ahk deleted file mode 100644 index 258805dc..00000000 --- a/ahk/templates/keyboard/key_state.ahk +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -if (GetKeyState("{{ key_name }}"{% if mode %} , {{ mode }}{% endif %})) { - FileAppend, 1, * -} else { - FileAppend, 0, * -} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/keyboard/key_wait.ahk b/ahk/templates/keyboard/key_wait.ahk deleted file mode 100644 index e500aa83..00000000 --- a/ahk/templates/keyboard/key_wait.ahk +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -KeyWait, {{ key_name }}{% if options %} , {{ options }}{% endif %} - -FileAppend, %ErrorLevel%, * -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/keyboard/send.ahk b/ahk/templates/keyboard/send.ahk deleted file mode 100644 index 3f362918..00000000 --- a/ahk/templates/keyboard/send.ahk +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -{% if delay %}SetKeyDelay, {{ delay }}{% endif %} - -Send{% if raw %}Raw{% endif %} {{ s }} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/keyboard/send_event.ahk b/ahk/templates/keyboard/send_event.ahk deleted file mode 100644 index e892db4e..00000000 --- a/ahk/templates/keyboard/send_event.ahk +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -{% if delay %}SetKeyDelay, {{ delay }}{% endif %} - -SendEvent {{ s }} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/keyboard/send_input.ahk b/ahk/templates/keyboard/send_input.ahk deleted file mode 100644 index 85dadf51..00000000 --- a/ahk/templates/keyboard/send_input.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} - -SendInput {{ s }} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/keyboard/send_play.ahk b/ahk/templates/keyboard/send_play.ahk deleted file mode 100644 index bc3fc3a9..00000000 --- a/ahk/templates/keyboard/send_play.ahk +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -{% if delay %}SetKeyDelay, {{ delay }}{% endif %} - -SendPlay {{ s }} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/mouse/click.ahk b/ahk/templates/mouse/click.ahk deleted file mode 100644 index 6df573cc..00000000 --- a/ahk/templates/mouse/click.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -CoordMode Mouse, {{mode}} -Click{% for arg in args %}, {{ arg }}{% endfor %} -{% endblock %} diff --git a/ahk/templates/mouse/mouse_drag.ahk b/ahk/templates/mouse/mouse_drag.ahk deleted file mode 100644 index 73b99efe..00000000 --- a/ahk/templates/mouse/mouse_drag.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -CoordMode Mouse, {{mode}} -MouseClickDrag, {{button}}, {{x1}}, {{y1}}, {{x2}}, {{y2}}{% if speed %}, {{speed}}{% endif %}{% if relative %}, R{% endif %} -{% endblock body %} diff --git a/ahk/templates/mouse/mouse_move.ahk b/ahk/templates/mouse/mouse_move.ahk deleted file mode 100644 index 2ebd2a15..00000000 --- a/ahk/templates/mouse/mouse_move.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -CoordMode Mouse, {{mode}} -MouseMove, {{x}}, {{y}}, {{speed}}{% if relative %}, R{% endif %} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/mouse/mouse_position.ahk b/ahk/templates/mouse/mouse_position.ahk deleted file mode 100644 index b317cf8b..00000000 --- a/ahk/templates/mouse/mouse_position.ahk +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -CoordMode, Mouse, {{mode}} -MouseGetPos, xpos, ypos -s .= Format("({}, {})", xpos, ypos) -FileAppend, %s%, * -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/screen/image_search.ahk b/ahk/templates/screen/image_search.ahk deleted file mode 100644 index 421ada5c..00000000 --- a/ahk/templates/screen/image_search.ahk +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -CoordMode Pixel, {{ coord_mode }} -ImageSearch, xpos, ypos, {{ x1 }}, {{ y1 }}, {{ x2 }}, {{ y2 }}, {% if scale_width %}*w{{ scale_width}} *h{{ scale_height }} {% endif %}{{ image_path }} -s .= Format("({}, {})", xpos, ypos) -FileAppend, %s%, * -{% endblock body %} diff --git a/ahk/templates/screen/pixel_get_color.ahk b/ahk/templates/screen/pixel_get_color.ahk deleted file mode 100644 index 77a18437..00000000 --- a/ahk/templates/screen/pixel_get_color.ahk +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -CoordMode Pixel, {{ coord_mode }} -PixelGetColor, color, {{ x }}, {{ y }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} - -FileAppend, %color%, * -{% endblock body %} diff --git a/ahk/templates/screen/pixel_search.ahk b/ahk/templates/screen/pixel_search.ahk deleted file mode 100644 index 833b73ed..00000000 --- a/ahk/templates/screen/pixel_search.ahk +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -CoordMode Pixel, {{ coord_mode }} -PixelSearch, xpos, ypos, {{ x1 }}, {{ y1 }}, {{ x2 }}, {{ y2 }}, {{ color }} , {{ variation }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} - -s .= Format("({}, {})", xpos, ypos) -FileAppend, %s%, * -{% endblock body %} diff --git a/ahk/templates/sound/beep.ahk b/ahk/templates/sound/beep.ahk deleted file mode 100644 index c34a0be1..00000000 --- a/ahk/templates/sound/beep.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -SoundBeep, {{ frequency }}, {{ duration }} -{% endblock body %} diff --git a/ahk/templates/sound/get_volume.ahk b/ahk/templates/sound/get_volume.ahk deleted file mode 100644 index 420990a1..00000000 --- a/ahk/templates/sound/get_volume.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -SoundGetWaveVolume, retval, {{ device_number }} -FileAppend, %retval%, * -{% endblock body %} diff --git a/ahk/templates/sound/play.ahk b/ahk/templates/sound/play.ahk deleted file mode 100644 index 5e5231b0..00000000 --- a/ahk/templates/sound/play.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -SoundPlay, {{ filename }}{% if wait %}, {{ wait }}{% endif %} -{% endblock body %} diff --git a/ahk/templates/sound/set_volume.ahk b/ahk/templates/sound/set_volume.ahk deleted file mode 100644 index b8e6e1dd..00000000 --- a/ahk/templates/sound/set_volume.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -SoundSetWaveVolume, {{ value }}, {{ device_number }} -{% endblock body %} diff --git a/ahk/templates/sound/sound_get.ahk b/ahk/templates/sound/sound_get.ahk deleted file mode 100644 index 442e8233..00000000 --- a/ahk/templates/sound/sound_get.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -SoundGet, retval , {{ component_type }}, {{ control_type }}, {{ device_number }} -FileAppend, %retval%, * -{% endblock body %} diff --git a/ahk/templates/sound/sound_set.ahk b/ahk/templates/sound/sound_set.ahk deleted file mode 100644 index f213ea6a..00000000 --- a/ahk/templates/sound/sound_set.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -SoundSet, {{ value }}, {{ component_type }}, {{ control_type }}, {{ device_number }} -FileAppend, %retval%, * -{% endblock body %} diff --git a/ahk/templates/window/close.ahk b/ahk/templates/window/close.ahk deleted file mode 100644 index 8f99e60d..00000000 --- a/ahk/templates/window/close.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinClose, {{win.title}}, {{win.text}}, {{seconds_to_wait}}, {{win._exclude_title}}, {{win._exclude_text}} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/window/control_send.ahk b/ahk/templates/window/control_send.ahk deleted file mode 100644 index 72cf03c6..00000000 --- a/ahk/templates/window/control_send.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -ControlSend, {{ control }}, {{ keys }}, {{ win_title }}, {{ win_text }}, {{ exclude_title }}, {{ exclude_text }} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/window/from_mouse.ahk b/ahk/templates/window/from_mouse.ahk deleted file mode 100644 index f05e85a5..00000000 --- a/ahk/templates/window/from_mouse.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -MouseGetPos,,, MouseWin -FileAppend, %MouseWin%, * -{% endblock body %} diff --git a/ahk/templates/window/get.ahk b/ahk/templates/window/get.ahk deleted file mode 100644 index f0158ea6..00000000 --- a/ahk/templates/window/get.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinGet, output, {{ subcommand }}, {{ title }}, {{ text }}, {{ exclude_title }}, {{ exclude_text }} -FileAppend, %output%, * -{% endblock body %} diff --git a/ahk/templates/window/id_list.ahk b/ahk/templates/window/id_list.ahk deleted file mode 100644 index 085bf1ea..00000000 --- a/ahk/templates/window/id_list.ahk +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinGet windows, List -Loop %windows% -{ - id := windows%A_Index% - r .= id . "`n" -} -FileAppend, %r%, * -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/window/set.ahk b/ahk/templates/window/set.ahk deleted file mode 100644 index a8496aec..00000000 --- a/ahk/templates/window/set.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinSet, {{subcommand}}{% for arg in args %}, {{ arg }}{% endfor %} -{% endblock body %} diff --git a/ahk/templates/window/title_list.ahk b/ahk/templates/window/title_list.ahk deleted file mode 100644 index 1fd2027d..00000000 --- a/ahk/templates/window/title_list.ahk +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinGet windows, List -Loop %windows% -{ - id := windows%A_Index% - WinGetTitle wt, ahk_id %id% - r .= wt . "`n" -} -FileAppend, %r%, * -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/window/win_activate.ahk b/ahk/templates/window/win_activate.ahk deleted file mode 100644 index d39a2bf5..00000000 --- a/ahk/templates/window/win_activate.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinActivate, ahk_id {{ win.id }} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/window/win_close.ahk b/ahk/templates/window/win_close.ahk deleted file mode 100644 index 6898018b..00000000 --- a/ahk/templates/window/win_close.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinClose, ahk_id {{ win.id }}, {{seconds_to_wait}} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/window/win_get_text.ahk b/ahk/templates/window/win_get_text.ahk deleted file mode 100644 index 7a0c31fb..00000000 --- a/ahk/templates/window/win_get_text.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinGetText, text, ahk_id {{ win.id }} -FileAppend, %text%, * -{% endblock body %} diff --git a/ahk/templates/window/win_get_title.ahk b/ahk/templates/window/win_get_title.ahk deleted file mode 100644 index 6af8090a..00000000 --- a/ahk/templates/window/win_get_title.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinGetTitle, title, ahk_id {{ win.id }} -FileAppend, %title%, * -{% endblock body %} diff --git a/ahk/templates/window/win_is_active.ahk b/ahk/templates/window/win_is_active.ahk deleted file mode 100644 index 4b0413ea..00000000 --- a/ahk/templates/window/win_is_active.ahk +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -if WinActive("ahk_id {{ win.id }}") { - FileAppend, 1, * - ExitApp -} -FileAppend, 0, * -ExitApp -{% endblock body %} diff --git a/ahk/templates/window/win_is_always_on_top.ahk b/ahk/templates/window/win_is_always_on_top.ahk deleted file mode 100644 index 1d168235..00000000 --- a/ahk/templates/window/win_is_always_on_top.ahk +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinGet, ExStyle, ExStyle, ahk_id {{ win.id }} -if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. - FileAppend, 1, * -else - FileAppend, 0, * -{% endblock body %} diff --git a/ahk/templates/window/win_move.ahk b/ahk/templates/window/win_move.ahk deleted file mode 100644 index 4f09fd7a..00000000 --- a/ahk/templates/window/win_move.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinMove, ahk_id {{ win.id }}, , {{ x }}, {{ y }}{% if width %}, {{ width }}{% endif %}{% if height %}, {{ height }}{% endif %} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/window/win_position.ahk b/ahk/templates/window/win_position.ahk deleted file mode 100644 index 41b7a4f9..00000000 --- a/ahk/templates/window/win_position.ahk +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinGetPos, x, y, width, height, ahk_id {{ win.id }} -s .= Format("({}, {}, {}, {})", x, y, width, height) -FileAppend, %s%, * -{% endblock body %} diff --git a/ahk/templates/window/win_send.ahk b/ahk/templates/window/win_send.ahk deleted file mode 100644 index 321c974d..00000000 --- a/ahk/templates/window/win_send.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -{% if raw %}ControlSendRaw{% else %}ControlSend{% endif %}, , {{ keys }}, ahk_id {{ win.id }} -{% endblock body %} \ No newline at end of file diff --git a/ahk/templates/window/win_set.ahk b/ahk/templates/window/win_set.ahk deleted file mode 100644 index 29335efd..00000000 --- a/ahk/templates/window/win_set.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinSet, {{subcommand}}, {{value}}, ahk_id {{ win.id }} -{% endblock body %} diff --git a/ahk/utils.py b/ahk/utils.py deleted file mode 100644 index a8354fd1..00000000 --- a/ahk/utils.py +++ /dev/null @@ -1,48 +0,0 @@ -import logging - -ESCAPE_SEQUENCE_MAP = { - '\n': '`n', - '\t': '`t', - '\r': '`r', - '\a': '`a', - '\b': '`b', - '\f': '`f', - '\v': '`v', - ',': '`,', - '%': '`%', - '`': '``', - ';': '`;', - ':': '`:', - '!': '{!}', - '^': '{^}', - '+': '{+}', - '{': '{{}', - '}': '{}}', - '#': '{#}' -} - -_TRANSLATION_TABLE = str.maketrans(ESCAPE_SEQUENCE_MAP) - -def make_logger(name): - logger = logging.getLogger(name) - handler = logging.NullHandler() - formatter = logging.Formatter( - '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') - handler.setFormatter(formatter) - logger.addHandler(handler) - return logger - - -def escape_sequence_replace(s): - """ - Replace Python escape sequences with AHK equivalent escape sequences - Additionally escapes some other characters for AHK escape sequences. - Intended for use with AHK Send command functions. - - Note: This DOES NOT provide ANY assurances against accidental or malicious injection. Does NOT escape quotes. - - >>> escape_sequence_replace('Hello, World!') - 'Hello`, World{!}' - """ - return s.translate(_TRANSLATION_TABLE) - diff --git a/ahk/window.py b/ahk/window.py deleted file mode 100644 index 7d382610..00000000 --- a/ahk/window.py +++ /dev/null @@ -1,355 +0,0 @@ -from ahk.script import ScriptEngine -import ast -from ahk.utils import make_logger, escape_sequence_replace -from contextlib import suppress -logger = make_logger(__name__) - - -class WindowNotFoundError(ValueError): - pass - - -class Control: - def __init__(self): - raise NotImplementedError - - def click(self): - """ - REF: https://www.autohotkey.com/docs/commands/ControlClick.htm - :return: - """ - raise NotImplementedError - - def focus(self): - """ - REF: https://www.autohotkey.com/docs/commands/ControlFocus.htm - :return: - """ - raise NotImplementedError - - def get(self, key): - """ - REF: https://www.autohotkey.com/docs/commands/ControlGet.htm - :param key: - :return: - """ - raise NotImplementedError - - def has_focus(self): - raise NotImplementedError - - @property - def position(self): - """ - REF: https://www.autohotkey.com/docs/commands/ControlGetPos.htm - :return: - """ - raise NotImplementedError - - @property - def text(self): - """ - REF: https://www.autohotkey.com/docs/commands/ControlGetText.htm - :return: - """ - raise NotImplementedError - - @text.setter - def text(self, new_text): - """ - REF: https://www.autohotkey.com/docs/commands/ControlSetText.htm - :param new_text: - :return: - """ - raise NotImplementedError - - def move(self): - """ - REF: https://www.autohotkey.com/docs/commands/ControlMove.htm - :return: - """ - raise NotImplementedError - - def send(self, raw=False): - """ - REF: https://www.autohotkey.com/docs/commands/ControlSend.htm - :param raw: - :return: - """ - raise NotImplementedError - - -class Window(object): - _subcommands = { - 'id': 'ID', - 'process_name': 'ProcessName', - 'pid': 'PID', - 'process_path': 'ProcessPath', - 'process': 'ProcessPath', - 'controls': 'ControlList', - 'controls_hwnd': 'ControlListHwnd', - 'transparency': 'Transparent', - 'trans_color': 'TransColor', - 'style': 'Style', # This will probably get a property later - 'ex_style': 'ExStyle', # This will probably get a property later - } - # add reverse lookups - _subcommands.update({value: value for value in _subcommands.values()}) - - def __init__(self, engine: ScriptEngine, ahk_id: str, encoding=None): - self.engine = engine # should this be a weakref instead? - self.id = ahk_id - self.encoding = encoding - - @classmethod - def from_mouse_position(cls, engine: ScriptEngine, **kwargs): - script = engine.render_template('window/from_mouse.ahk') - ahk_id = engine.run_script(script) - return cls(engine=engine, ahk_id=ahk_id, **kwargs) - - @classmethod - def from_pid(cls, engine: ScriptEngine, pid, **kwargs): - script = engine.render_template('window/get.ahk', - subcommand="ID", - title=f'ahk_pid {pid}') - ahk_id = engine.run_script(script) - return cls(engine=engine, ahk_id=ahk_id, **kwargs) - - def __getattr__(self, attr): - if attr.lower() in self._subcommands: - return self.get(attr) - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'") - - def get(self, subcommand): - sub = self._subcommands.get(subcommand) - if not sub: - raise ValueError(f'No such subcommand {subcommand}') - script = self._render_template('window/get.ahk', - subcommand=sub, - title=f'ahk_id {self.id}') - - return self.engine.run_script(script) - - def __repr__(self): - return f'' - - def win_set(self, subcommand, value): - script = self._render_template('window/win_set.ahk', subcommand=subcommand, value=value) - self.engine.run_script(script) - - def _get_pos(self): - script = self._render_template('window/win_position.ahk') - resp = self.engine.run_script(script) - try: - value = ast.literal_eval(resp) - return value - except SyntaxError: - raise WindowNotFoundError('No window found') - - @property - def rect(self): - return self._get_pos() - - @rect.setter - def rect(self, new_position): - x, y, width, height = new_position - self.move(x=x, y=y, width=width, height=height) - - @property - def position(self): - x, y, _, _ = self._get_pos() - - return x, y - - @position.setter - def position(self, new_position): - x, y = new_position - self.move(x, y) - - @property - def width(self): - _, _, width, _ = self._get_pos() - return width - - @width.setter - def width(self, new_width): - self.move(width=new_width) - - @property - def height(self): - _, _, _, height = self._get_pos() - return height - - @height.setter - def height(self, new_height): - self.move(height=new_height) - - @property - def active(self): - script = self._render_template('window/win_is_active.ahk') - result = self.engine.run_script(script) - result = bool(ast.literal_eval(result)) - return result - - def disable(self): - self.win_set('Disable', '') - - def enable(self): - self.win_set('Enable', '') - - def redraw(self): - self.win_set('Redraw', '') - - @property - def title(self): - script = self._render_template('window/win_get_title.ahk') - result = self.engine.run_script(script, decode=False) - if self.encoding: - return result.stdout.decode(encoding=self.encoding) - return result.stdout - - @property - def text(self): - script = self._render_template('window/win_get_text.ahk') - result = self.engine.run_script(script, decode=False) - if self.encoding: - return result.stdout.decode(encoding=self.encoding) - return result.stdout - - @property - def always_on_top(self): - script = self._render_template('window/win_is_always_on_top.ahk') - resp = self.engine.run_script(script) - return bool(ast.literal_eval(resp)) - - @always_on_top.setter - def always_on_top(self, value): - if value in ('on', 'On', True, 1): - self.win_set('AlwaysOnTop', 'On') - elif value in ('off', 'Off', False, 0): - self.win_set('AlwaysOnTop', 'Off') - elif value in ('toggle', 'Toggle', -1): - self.win_set('AlwaysOnTop', 'Toggle') - else: - raise ValueError(f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') - - def close(self, seconds_to_wait=''): - script = self._render_template('window/win_close.ahk', seconds_to_wait=seconds_to_wait) - self.engine.run_script(script) - - def to_bottom(self): - """ - Send window to bottom (behind other windows) - :return: - """ - self.win_set('Bottom', '') - - def to_top(self): - self.win_set('Top', '') - - def _render_template(self, *args, **kwargs): - kwargs['win'] = self - return self.engine.render_template(*args, **kwargs) - - def activate(self): - script = self._render_template('window/win_activate.ahk') - self.engine.run_script(script) - - def move(self, x='', y='', width=None, height=None): - script = self._render_template('window/win_move.ahk', x=x, y=y, width=width, height=height) - self.engine.run_script(script) - - def send(self, keys, delay=None, raw=False, blocking=False, escape=False): - """ - Send keystrokes directly to the window. - Uses ControlSend - https://autohotkey.com/docs/commands/Send.htm - """ - if escape: - keys = escape_sequence_replace(keys) - script = self._render_template('window/win_send.ahk', keys=keys, raw=raw, delay=delay, blocking=blocking) - return self.engine.run_script(script, blocking=blocking) - - def __eq__(self, other): - if not isinstance(other, Window): - return False - return self.id == other.id - - def __hash__(self): - return hash(repr(self)) - - -class WindowMixin(ScriptEngine): - def __init__(self, *args, **kwargs): - self.window_encoding = kwargs.pop('window_encoding', None) - super().__init__(*args, **kwargs) - - def win_get(self, title='', text='', exclude_title='', exclude_text='', encoding=None): - encoding = encoding or self.window_encoding - script = self.render_template('window/get.ahk', - subcommand='ID', - title=title, - text=text, - exclude_text=exclude_text, - exclude_title=exclude_title) - ahk_id = self.run_script(script) - return Window(engine=self, ahk_id=ahk_id, encoding=encoding) - - def win_set(self, subcommand, *args, blocking=True): - script = self.render_template('window/set.ahk', subcommand=subcommand, *args, blocking=blocking) - self.run_script(script, blocking=blocking) - - @property - def active_window(self): - return self.win_get(title='A') - - def _all_window_ids(self): - script = self.render_template('window/id_list.ahk') - result = self.run_script(script) - return result.split('\n')[:-1] # last one is always an empty string - - def windows(self): - """ - Returns a list of windows - :return: - """ - windowze = [] - for ahk_id in self._all_window_ids(): - win = Window(engine=self, ahk_id=ahk_id, encoding=self.window_encoding) - windowze.append(win) - return windowze - - def find_windows(self, func=None, **kwargs): - if func is None: - exact = kwargs.pop('exact', False) - def func(win): - for attr, expected in kwargs.items(): - if exact: - result = getattr(win, attr) == expected - else: - result = expected in getattr(win, attr) - if result is False: - return False - return True - for window in filter(func, self.windows()): - yield window - - def find_window(self, func=None, **kwargs): - with suppress(StopIteration): - return next(self.find_windows(func=func, **kwargs)) - - def find_windows_by_title(self, title, exact=False): - for window in self.find_windows(title=title, exact=exact): - yield window - - def find_window_by_title(self, *args, **kwargs): - with suppress(StopIteration): - return next(self.find_windows_by_title(*args, **kwargs)) - - def find_windows_by_text(self, text, exact=False): - for window in self.find_windows(text=text, exact=exact): - yield window - - def find_window_by_text(self, *args, **kwargs): - with suppress(StopIteration): - return next(self.find_windows_by_text(*args, **kwargs)) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 7add99a2..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,47 +0,0 @@ -version: '0.1.{build}' - -environment: - AHK_PATH: C:\ahk\AutoHotkey.exe - AHK_DEBUG: true - -install: - - ps: | - if (!(Test-Path ahk_install.exe)) { - echo "Downloading AHK installer" - appveyor DownloadFile https://github.com/Lexikos/AutoHotkey_L/releases/download/v1.1.30.01/AutoHotkey_1.1.30.01_setup.exe -FileName ahk_install.exe - } else { - echo "Using cached installer" - } - - ahk_install.exe /S /D=C:\ahk - - cmd: .\ci\install.bat - -build_script: - - cmd: .\ci\build.bat - -artifacts: - - name: dist - path: dist\* - -test_script: - - powershell .\ci\runtests.ps1 - -on_finish: - - cmd: | - venv\Scripts\activate.bat - python -m coveralls - -cache: - - ahk_install.exe -> appveyor.yml - -deploy_script: - - ps: | - if ($env:APPVEYOR_REPO_TAG -eq "true") { - py -3.7 -m venv deploy_venv - .\deploy_venv\Scripts\activate.ps1 - python -m pip install --upgrade pip - pip install --upgrade wheel - pip install --upgrade twine - twine upload dist\* - } else { - echo "Skipping Deploy Because this is not a tagged commit" - } diff --git a/buildunasync.py b/buildunasync.py new file mode 100644 index 00000000..40323aff --- /dev/null +++ b/buildunasync.py @@ -0,0 +1,27 @@ +import unasync + +build_py = unasync.cmdclass_build_py( + rules=[ + unasync.Rule( + fromdir='/ahk/_async/', + todir='/ahk/_sync/', + additional_replacements={ + 'AsyncAHK': 'AHK', + 'AsyncTransport': 'Transport', + 'AsyncWindow': 'Window', + 'AsyncControl': 'Control', + 'AsyncDaemonProcessTransport': 'DaemonProcessTransport', + '_AIOP': '_SIOP', + 'async_create_process': 'sync_create_process', + 'adrain_stdin': 'drain_stdin', + 'a_send_nonblocking': 'send_nonblocking', + 'async_sleep': 'sleep', + 'AsyncFutureResult': 'FutureResult', + '_async_run_nonblocking': '_sync_run_nonblocking', + 'acommunicate': 'communicate', + 'astart': 'start', + # "__aenter__": "__aenter__", + }, + ), + ] +) diff --git a/ci/build.bat b/ci/build.bat deleted file mode 100644 index 160a6d6a..00000000 --- a/ci/build.bat +++ /dev/null @@ -1,3 +0,0 @@ -call venv\Scripts\activate.bat -python setup.py sdist bdist_wheel -call deactivate \ No newline at end of file diff --git a/ci/ci_requirements.txt b/ci/ci_requirements.txt deleted file mode 100644 index eccdb76a..00000000 --- a/ci/ci_requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -pytest -behave -behave-classy -coveralls -wheel -pillow \ No newline at end of file diff --git a/ci/install.bat b/ci/install.bat deleted file mode 100644 index 3b3917d7..00000000 --- a/ci/install.bat +++ /dev/null @@ -1,6 +0,0 @@ -py -3.7 -m venv venv -call venv\Scripts\activate.bat -python -m pip install --upgrade pip -python -m pip install --upgrade -r .\ci\ci_requirements.txt -python -m pip install --upgrade . -call deactivate \ No newline at end of file diff --git a/ci/runtests.ps1 b/ci/runtests.ps1 deleted file mode 100644 index 873896c9..00000000 --- a/ci/runtests.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -.\venv\Scripts\activate.ps1 -coverage run -m behave .\tests\features --format=progress2 --junit -if ($LastExitCode -ne 0) { - $failure = 1 -} else { - $failure = 0 -} -coverage run -a -m pytest .\tests\unittests --junitxml=reports\pytestresults.xml -if ($LastExitCode -ne 0) { - $failure = 1 -} -coverage report -$wc = New-Object 'System.Net.WebClient'; -Get-ChildItem .\reports | -Foreach-Object { - $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $_.FullName)) -} -if ($failure -ne 0) { throw } -deactivate \ No newline at end of file diff --git a/docs/.readthedocs.yaml b/docs/.readthedocs.yaml new file mode 100644 index 00000000..69a2a4d3 --- /dev/null +++ b/docs/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/docrequirements.txt diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..54296d7b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,692 @@ +# ahk + +A fully typed Python wrapper around AutoHotkey. + +[![Docs](https://readthedocs.org/projects/ahk/badge/?version=latest)](https://ahk.readthedocs.io/en/latest/?badge=latest) +[![Build](https://github.com/spyoungtech/ahk/actions/workflows/test.yaml/badge.svg)](https://github.com/spyoungtech/ahk/actions/workflows/test.yaml) +[![version](https://img.shields.io/pypi/v/ahk.svg?colorB=blue)](https://pypi.org/project/ahk/) +[![pyversion](https://img.shields.io/pypi/pyversions/ahk.svg?)](https://pypi.org/project/ahk/) +[![Coverage](https://coveralls.io/repos/github/spyoungtech/ahk/badge.svg?branch=master)](https://coveralls.io/github/spyoungtech/ahk?branch=master) +[![Downloads](https://pepy.tech/badge/ahk)](https://pepy.tech/project/ahk) + +# Installation + +``` +pip install ahk +``` + +Requires Python 3.8+ + +Supports AutoHotkey v1 and v2. See also: [Non-Python dependencies](#deps) + +# Usage + +```python +from ahk import AHK + +ahk = AHK() + +ahk.mouse_move(x=100, y=100, blocking=True) # Blocks until mouse finishes moving (the default) +ahk.mouse_move(x=150, y=150, speed=10, blocking=True) # Moves the mouse to x, y taking 'speed' seconds to move +print(ahk.mouse_position) # (150, 150) +``` + +![ahk](https://raw.githubusercontent.com/spyoungtech/ahk/9d049a327c7a10c9f19dfef89fc63668695023fc/docs/_static/ahk.gif) + +# Examples + +Non-exhaustive examples of some functions available with this package. See the [full documentation](https://ahk.readthedocs.io/en/latest/?badge=latest) +for complete API references and additional features. + +## Hotkeys + +Hotkeys can be configured to run python functions as callbacks. + +For example: + +```python +from ahk import AHK + +def my_callback(): + print('Hello callback!') + +ahk = AHK() +# when WIN + n is pressed, fire `my_callback` +ahk.add_hotkey('#n', callback=my_callback) +ahk.start_hotkeys() # start the hotkey process thread +ahk.block_forever() # not strictly needed in all scripts -- stops the script from exiting; sleep forever +``` + +Now whenever you press ![Windows Key][winlogo] + n, the `my_callback` callback function will be called in a background thread. + +You can also add an exception handler for your callback: + +```python +from ahk import AHK +ahk = AHK() + +def go_boom(): + raise Exception('boom!') + +def my_ex_handler(hotkey: str, exception: Exception): + print('exception with callback for hotkey', hotkey, 'Here was the error:', exception) + +ahk.add_hotkey('#n', callback=go_boom, ex_handler=my_ex_handler) +``` + +There are also methods for removing hotkeys: + +```python +# ... +ahk.remove_hotkey('#n') # remove a hotkey by its keyname +ahk.clear_hotkeys() # remove all hotkeys +``` + +Note that: + +- Hotkeys run in a separate process that must be started manually (with `ahk.start_hotkeys()`) +- Hotkeys can be stopped with `ahk.stop_hotkeys()` (will not stop actively running callbacks) +- Hotstrings (discussed below) share the same process with hotkeys and are started/stopped in the same manner +- If hotkeys or hotstrings are added or removed while the process is running, the underlying AHK process is restarted automatically + + +See also the [relevant AHK documentation](https://www.autohotkey.com/docs/Hotkeys.htm) + +## Hotstrings + + +[Hotstrings](https://www.autohotkey.com/docs/Hotstrings.htm) can also be added to the hotkey process thread. + +In addition to Hotstrings supporting normal AHK string replacements, you can also provide Python callbacks (with optional exception handlers) in response to hotstrings triggering. + +```python +from ahk import AHK +ahk = AHK() + +def my_callback(): + print('hello callback!') + +ahk.add_hotstring('btw', 'by the way') # string replacements +ahk.add_hotstring('btw', my_callback) # call python function in response to the hotstring +``` + +You can also remove hotstrings: + +```python +ahk.remove_hotstring('btw') # remove a hotstring by its trigger sequence +ahk.clear_hotstrings() # remove all registered hotstrings +``` + +## Mouse + +```python +from ahk import AHK + +ahk = AHK() + +ahk.mouse_position # Returns a tuple of mouse coordinates (x, y) (relative to active window) +ahk.get_mouse_position(coord_mode='Screen') # get coordinates relative to the screen +ahk.mouse_move(100, 100, speed=10, relative=True) # Moves the mouse reletave to the current position +ahk.mouse_position = (100, 100) # Moves the mouse instantly to absolute screen position +ahk.click() # Click the primary mouse button +ahk.click(200, 200) # Moves the mouse to a particular position and clicks (relative to active window) +ahk.click(100, 200, coord_mode='Screen') # click relative to the screen instead of active window +ahk.click(button='R', click_count=2) # Clicks the right mouse button twice +ahk.right_click() # Clicks the secondary mouse button +ahk.mouse_drag(100, 100, relative=True) # Holds down primary button and moves the mouse +``` + +## Keyboard + +```python +from ahk import AHK + +ahk = AHK() + +ahk.type('hello, world!') # Send keys, as if typed (performs string escapes for you) +ahk.send_input('Hello, {U+1F30E}{!}') # Like AHK SendInput + # Unlike `type`, control sequences must be escaped manually. + # For example the characters `!^+#=` and braces (`{` `}`) must be escaped manually. +ahk.key_state('Control') # Return True or False based on whether Control key is pressed down +ahk.key_state('CapsLock', mode='T') # Check toggle state of a key (like for NumLock, CapsLock, etc) +ahk.key_press('a') # Press and release a key +ahk.key_down('Control') # Press down (but do not release) Control key +ahk.key_up('Control') # Release the key +ahk.set_capslock_state("On") # Turn CapsLock on +if ahk.key_wait('x', timeout=3): # wait for a key to be pressed; returns a boolean + print('X was pressed within 3 seconds') +else: + print('X was not pressed within 3 seconds') +``` + +## Windows + +You can do stuff with windows, too. + + +### Getting windows + +```python +from ahk import AHK + +ahk = AHK() + +win = ahk.active_window # Get the active window +win = ahk.win_get(title='Untitled - Notepad') # by title +all_windows = ahk.list_windows() # list of all windows +win = ahk.win_get_from_mouse_position() # the window under the mouse cursor +win = ahk.win_get(title='ahk_pid 20366') # get window from pid + +# Wait for a window +try: + # wait up to 5 seconds for notepad + win = ahk.win_wait(title='Untitled - Notepad', timeout=5) + # see also: win_wait_active, win_wait_not_active +except TimeoutError: + print('Notepad was not found!') +``` + +### Working with windows + +```python +from ahk import AHK + +ahk = AHK() + +ahk.run_script('Run Notepad') # Open notepad +win = ahk.find_window(title='Untitled - Notepad') # Find the opened window; returns a `Window` object + +# Window object methods +win.send('hello', control='Edit1') # Send keys directly to the window (does not need focus!) +# OR ahk.control_send(title='Untitled - Notepad', control='Edit1') +win.move(x=200, y=300, width=500, height=800) + +win.activate() # Give the window focus +win.close() # Close the window +win.hide() # Hide the window +win.kill() # Kill the window +win.maximize() # Maximize the window +win.minimize() # Minimize the window +win.restore() # Restore the window +win.show() # Show the window +win.disable() # Make the window non-interactable +win.enable() # Enable it again +win.to_top() # Move the window on top of other windows +win.to_bottom() # Move the window to the bottom of the other windows +win.get_class() # Get the class name of the window +win.get_minmax() # Get the min/max status +win.get_process_name() # Get the process name (e.g., "notepad.exe") +win.process_name # Property; same as `.get_process_name()` above +win.is_always_on_top() # Whether the window has the 'always on top' style applied +win.list_controls() # Get a list of controls (list of `Control` objects) +win.redraw() # Redraw the window +win.set_style("-0xC00000") # Set a style on the window (in this case, removing the title bar) +win.set_ex_style("^0x80") # Set an ExStyle on the window (in this case, removes the window from alt-tab list) +win.set_region("") # See: https://www.autohotkey.com/docs/v2/lib/WinSetRegion.htm +win.set_trans_color("White") # Makes all pixels of the chosen color invisible inside the specified window. +win.set_transparent(155) # Makes the specified window semi-transparent (or "Off" to turn off transparency) + + +win.always_on_top = 'On' # Make the window always on top +# or +win.set_always_on_top('On') + +for window in ahk.list_windows(): # list all (non-hidden) windows -- ``detect_hidden_windows=True`` to include hidden + print(window.title) + + # Some more attributes + print(window.text) # window text -- or .get_text() + print(window.get_position()) # (x, y, width, height) + print(window.id) # the ahk_id of the window + print(window.pid) # process ID -- or .get_pid() + print(window.process_path) # or .get_process_path() + + +if win.active: # or win.is_active() + ... + +if win.exist: # or win.exists() + ... + +# Controls + +edit_control = win.list_controls()[0] # get the first control for the window, in this case "Edit1" for Notepad +edit_control.get_text() # get the text in Notepad +edit_control.get_position() # returns a `Postion` namedtuple: e.g. Position(x=6, y=49, width=2381, height=1013) + +``` + +Various window methods can also be called directly without first creating a `Window` object by using the underlying `win_*` methods on the `AHK` class. +For example, instead of `win.close()` as above, one could call `ahk.win_close(title='Untitled - Notepad')` instead. + + + +## Screen + +```python +from ahk import AHK + +ahk = AHK() + +ahk.image_search('C:\\path\\to\\image.jpg') # Find an image on screen + +# Find an image within a boundary on screen +ahk.image_search('C:\\path\\to\\image.jpg', upper_bound=(100, 100), # upper-left corner of search area + lower_bound=(400, 400)) # lower-right corner of search area +ahk.pixel_get_color(100, 100) # Get color of pixel located at coords (100, 100) +ahk.pixel_search(color='0x9d6346', search_region_start=(0, 0), search_region_end=(500, 500)) # Get coords of the first pixel with specified color +``` + +## Clipboard + +Get/set `Clipboard` data + +```python +from ahk import AHK +ahk = AHK() + +ahk.set_clipboard('hello \N{EARTH GLOBE AMERICAS}') # set clipboard text contents +ahk.get_clipboard() # get clipboard text contents +# 'hello 🌎' +ahk.set_clipboard("") # Clear the clipboard + +ahk.clip_wait(timeout=3) # Wait for clipboard contents to change (with text or file(s)) +ahk.clip_wait(timeout=3, wait_for_any_data=True) # wait for _any_ clipboard contents +``` + +You may also get/set `ClipboardAll` -- however, you should never try to call `set_clipboard_all` with any other +data than as _exactly_ as returned by `get_clipboard_all` or unexpected problems may occur. + +```python +from ahk import AHK +ahk = AHK() + +# save all clipboard contents in all formats +saved_clipboard = ahk.get_clipboard_all() +ahk.set_clipboard('something else') +... +ahk.set_clipboard_all(saved_clipboard) # restore saved content from earlier +``` + +You can also set a callback to execute when the clipboard contents change. As with Hotkey methods mentioned above, +you can also set an exception handler. Like hotkeys, `on_clipboard_change` callbacks also require `.start_hotkeys()` +to be called to take effect. + +The callback function must accept one positional argument, which is an integer indicating the clipboard datatype. + +```python +from ahk import AHK +ahk = AHK() +def my_clipboard_callback(change_type: int): + if change_type == 0: + print('Clipboard is now empty') + elif change_type == 1: + print('Clipboard has text contents') + elif change_type == 2: + print('Clipboard has non-text contents') + +ahk.on_clipboard_change(my_clipboard_callback) +ahk.start_hotkeys() # like with hotkeys, must be called at least once for listening to start +# ... +ahk.set_clipboard("hello") # will cause the message "Clipboard has text contents" to be printed by the callback +ahk.set_clipboard("") # Clears the clipboard, causing the message "Clipboard is now empty" to be printed by the callback +``` + +## Sound + +```python +from ahk import AHK + +ahk = AHK() + +ahk.sound_play('C:\\path\\to\\sound.wav') # Play an audio file +ahk.sound_beep(frequency=440, duration=1000) # Play a beep for 1 second (duration in microseconds) +ahk.get_volume(device_number=1) # Get volume of a device +ahk.set_volume(50, device_number=1) # Set volume of a device +ahk.sound_get(device_number=1, component_type='MASTER', control_type='VOLUME') # Get sound device property +ahk.sound_set(50, device_number=1, component_type='MASTER', control_type='VOLUME') # Set sound device property +``` + +## GUI + + +Tooltips/traytips + +```python +import time +from ahk import AHK + +ahk = AHK() +ahk.show_tooltip("hello4", x=10, y=10) +time.sleep(2) +ahk.hide_tooltip() # hide the tooltip +ahk.show_info_traytip("Info", "It's also info", silent=False, blocking=True) # Default info traytip +ahk.show_warning_traytip("Warning", "It's a warning") # Warning traytip +ahk.show_error_traytip("Error", "It's an error") # Error trytip +``` + +Dialog boxes + +```python +from ahk import AHK, MsgBoxButtons +ahk = AHK() + +ahk.msg_box(text='Do you like message boxes?', title='My Title', buttons=MsgBoxButtons.YES_NO) +ahk.input_box(prompt='Password', title='Enter your password', hide=True) +ahk.file_select_box(title='Select one or more mp3 files', multi=True, filter='*.mp3', file_must_exist=True) +ahk.folder_select_box(prompt='Select a folder') +``` + +## Global state changes + +You can change various global states such as `CoordMode`, `DetectHiddenWindows`, etc. so you don't have to pass +these parameters directly to function calls + +```python +from ahk import AHK + +ahk = AHK() + +ahk.set_coord_mode('Mouse', 'Screen') # set default Mouse CoordMode to be relative to Screen +ahk.set_detect_hidden_windows(True) # Turn on detect hidden windows by default +ahk.set_send_level(5) # Change send https://www.autohotkey.com/docs/v1/lib/SendLevel.htm + +ahk.set_title_match_mode('Slow') # change title match speed and/or mode +ahk.set_title_match_mode('RegEx') +ahk.set_title_match_mode(('RegEx', 'Slow')) # or both at the same time +ahk.set_send_mode('Event') # change the default SendMode +``` + +## Add directives + +You can add directives that will be added to all generated scripts. +For example, to prevent the AHK trayicon from appearing, you can add the NoTrayIcon directive. + +```python +from ahk import AHK +from ahk.directives import NoTrayIcon + +ahk = AHK(directives=[NoTrayIcon]) +``` + +By default, some directives are automatically added to ensure functionality and are merged with any user-provided directives. + +Directives are not applied for the AHK process used for handling hotkeys and hotstrings (discussed below) by default. To apply a directive +to the hotkeys process using the keyword argument `apply_to_hotkeys_process=True`: + +```python +from ahk import AHK +from ahk.directives import NoTrayIcon + +directives = [ + NoTrayIcon(apply_to_hotkeys_process=True) +] + +ahk = AHK(directives=directives) +``` + +## Menu tray icon + +As discussed above, you can hide the tray icon if you wish. Additionally, there are some methods available for +customizing the tray icon. + + +```python +from ahk import AHK +ahk = AHK() + +# change the tray icon (in this case, using a builtin system icon) +ahk.menu_tray_icon('Shell32.dll', 174) +# revert it back to the original: +ahk.menu_tray_icon() + +# change the tooltip that shows up when hovering the mouse over the tray icon +ahk.menu_tray_tooltip('My Program Name') + +# Hide the tray icon +ahk.menu_tray_icon_hide() + +# Show the tray icon that was previously hidden by ``NoTrayIcon`` or ``menu_tray_icon_hide`` +ahk.menu_tray_icon_show() +``` + +## Registry methods + +You can read/write/delete registry keys: + +```python +from ahk import AHK +ahk = AHK() + +ahk.reg_write('REG_SZ', r'HKEY_CURRENT_USER\SOFTWARE\my-software', value='test') +ahk.reg_write('REG_SZ', r'HKEY_CURRENT_USER\SOFTWARE\my-software', value_name='foo', value='bar') +ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\my-software') # 'test' +ahk.reg_delete(r'HKEY_CURRENT_USER\SOFTWARE\my-software') +``` + +If a key does not exist or some other problem occurs, an exception is raised. + +## non-blocking modes + +Most methods in this library supply a non-blocking interface, so your Python scripts can continue executing while +your AHK scripts run. + +By default, all calls are _blocking_ -- each function will execute completely before the next function is ran. + +However, sometimes you may want to run other code while AHK executes some code. When the `blocking` keyword +argument is supplied with `False`, function calls will return immediately while the AHK function is carried out +in the background. + + +As an example, you can move the mouse slowly and report its position as it moves: + +```python +import time + +from ahk import AHK + +ahk = AHK() + +ahk.mouse_position = (200, 200) # Moves the mouse instantly to the start position +start = time.time() + +# move the mouse very slowly +ahk.mouse_move(x=100, y=100, speed=30, blocking=False) + +# This code begins executing right away, even though the mouse is still moving +while True: + t = round(time.time() - start, 4) + position = ahk.mouse_position + print(t, position) # report mouse position while it moves + if position == (100, 100): + break +``` + + +When you specify `blocking=False` you will always receive a special `FutureResult` object (or `AsyncFutureResult` object in the async API, discussed below) +which allows you to wait on the function to complete and retrieve return value through a `get_result` function. Even +when a function normally returns `None`, this can be useful to ensure AHK has finished executing the function. + +nonblocking calls: + +- Are isolated in a new AHK process that will terminate after the call is complete +- Always start immediately +- Do not inherit previous global state changes (e.g., from `set_coord_mode` calls or similar) -- this may change in a future version. +- will not block other calls from starting +- will always return a special `FutureResult` object (or `AsyncFutureResult` object in the async API, discussed below) +which allows you to wait on the function to complete and retrieve return value through the `result` function. Even +when a function normally returns `None`, this can be useful to ensure AHK has finished executing the function. + +```python +from ahk import AHK +ahk = AHK() +future_result = ahk.mouse_move(100, 100, speed=40, blocking=False) +... +# wait on the mouse_move to finish +future_result.result(timeout=10) # timeout keyword is optional +``` + + + +## Async API (asyncio) + +An async API is provided so functions can be called using `async`/`await`. +All the same methods from the synchronous API are available in the async API. + +```python +from ahk import AsyncAHK +import asyncio +ahk = AsyncAHK() + +async def main(): + await ahk.mouse_move(100, 100) + x, y = await ahk.get_mouse_position() + print(x, y) + +asyncio.run(main()) +``` + +The async API is identical to that of the normal API, with a few notable differences: + +- While properties (like `.mouse_position` or `.title` for windows) can be `await`ed, +additional methods (like `get_mouse_position()` and `get_title()`) have been added for a more intuitive API and +are recommended over the use of properties. +- Property _setters_ (e.g., `ahk.mouse_postion = (200, 200)`) are not allowed in the async API (a RunTimeError is raised). +Property setters remain available in the sync API. +- `AsyncFutureResult` objects (returned when specifying `blocking=False`) work the same as the `FutureResult` objects in the sync API, except the `timeout` keyword is not supported for the `result` method). + +Note also that: +- by default, awaited tasks on a single `AsyncAHK` instance will not run concurrently. You must either +use `blocking=False`, as in the sync API, or use multiple instances of `AsyncAHK`. +- There is no difference in working with hotkeys (and their callbacks) in the async vs sync API. + + +## type-hints and mypy + +This library is fully type-hinted, allowing you to leverage tools like `mypy` to help validate the type-correctness +of your code. IDEs that implement type-checking features are also able to leverage type hints to help ensure your +code is safe. + + +## Run arbitrary AutoHotkey scripts + +You can also run arbitrary AutoHotkey code either as a `.ahk` script file or as a string containing AHK code. + +```python +from ahk import AHK +ahk = AHK() +my_script = '''\ +MouseMove, 100, 100 +; etc... +''' + +ahk.run_script(my_script) +``` + +```python +from ahk import AHK +ahk = AHK() +script_path = r'C:\Path\To\myscript.ahk' +ahk.run_script(script_path) +``` + + + + +# Non-Python dependencies + +To use this package, you need the [AutoHotkey executable](https://www.autohotkey.com/download/) (e.g., `AutoHotkey.exe`). +It's expected to be on PATH by default OR in a default installation location (`C:\Program Files\AutoHotkey\AutoHotkey.exe` for v1 or `C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe` for v2) + +AutoHotkey v1 and v2 are both fully supported, though some behavioral differences will occur depending on which version +you use. See notes below. + +The recommended way to supply the AutoHotkey binary (for both v1 and v2) is to install the `binary` extra for this package. This will +provide the necessary executables and help ensure they are correctly placed on PATH. + +``` +pip install "ahk[binary]" +``` + + +Alternatively, you may provide the path in code: + +```python +from ahk import AHK + +ahk = AHK(executable_path='C:\\path\\to\\AutoHotkey.exe') +``` + +You can also use the `AHK_PATH` environment variable to specify the executable location. + +```console +set AHK_PATH=C:\Path\To\AutoHotkey.exe +python myscript.py +``` + +## Using AHK v2 + +By default, when no `executable_path` parameter (or `AHK_PATH` environment variable) is set, only AutoHotkey v1 binary names +are searched for on PATH or default install locations. This behavior may change in future versions to allow v2 to be used by default. + +To use AutoHotkey version 2, you can do any of the following things: + +1. provide the `executable_path` keyword argument with the location of the AutoHotkey v2 binary +2. set the `AHK_PATH` environment variable with the location of an AutoHotkey v2 binary +3. Provide the `version` keyword argument with the value `v2` which enables finding the executable using AutoHotkey v2 binary names and default install locations. + +For example: + +```python +from ahk import AHK + + +ahk = AHK(executable_path=r'C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe') +# OR +ahk = AHK(version='v2') +``` + +When you provide the `version` keyword argument (with either `"v1"` or `"v2"`) a check is performed to ensure the provided (or discovered) binary matches the requested version. When +the `version` keyword is omitted, the version is determined automatically from the provided (or discovered) executable binary. + + + +### Differences when using AutoHotkey v1 vs AutoHotkey v2 + +The API of this project is originally designed against AutoHotkey v1 and function signatures are the same, even when using AutoHotkey v2. +While most of the behavior remains the same, some behavior does change when using AutoHotkey v2 compared to v1. This is mostly due to +[underlying differences](https://www.autohotkey.com/docs/v2/v2-changes.htm) between the two versions. + +Some of the notable differences that you may experience when using AutoHotkey v2 with this library include: + +1. Functions that find and return windows will often raise an exception rather than returning `None` (as in AutoHotkey v2, a TargetError is thrown in most cases where the window or control cannot be found) +2. The behavior of `ControlSend` (`ahk.control_send` or `Window.send` or `Control.send`) differs in AutoHotkey v2 when the `control` parameter is not specified. In v1, keys are sent to the topmost controls, which is usually the correct behavior. In v2, keys are sent directly to the window. This means in many cases, you need to specify the control explicitly when using V2. +3. Some functionality is not supported in v2 -- specifically: the `secondstowait` paramater for `TrayTip` (`ahk.show_traytip`) was removed in v2. Specifying this parameter in the Python wrapper will cause a warning to be emitted and the parameter is ignored. +4. Some functionality that is present in v1 is not yet implemented in v2 -- this is expected to change in future versions. Specifically: some [sound functions](https://www.autohotkey.com/docs/v2/lib/Sound.htm) are not implemented. +5. The default SendMode changes in v2 to `Input` rather than `Event` in v1 (as a consequence, for example, mouse speed parameters to `mouse_move` and `mouse_drag` will be ignored in V2 unless the send mode is changed) +6. The default [TitleMatchMode](https://www.autohotkey.com/docs/v2/lib/SetTitleMatchMode.htm) is `2` in AutoHotkey v2. It is `1` in AutoHotkey v1. Use the `title_match_mode` keyword arguments to `win_get` and other methods that accept this keyword to control this behavior or use `set_title_match_mode` to change the default behavior (non-blocking calls are run in separate processes and are not affected by `set_title_match_mode`) + +## Extending: add your own AutoHotkey code (beta) + +You can develop extensions for extending functionality of `ahk` -- that is: writing your own AutoHotkey code and adding +additional methods to the AHK class. See the [extending docs](https://ahk.readthedocs.io/en/latest/extending.html) for +more information. + +# Contributing + +All contributions are welcomed and appreciated. + +Please feel free to open a GitHub issue or PR for feedback, ideas, feature requests or questions. + +[winlogo]: http://i.stack.imgur.com/Rfuw7.png + + +# Similar projects + +These are some similar projects that are commonly used for automation with Python. + +* [Pyautogui](https://pyautogui.readthedocs.io) - Al Sweigart's creation for cross-platform automation +* [Pywinauto](https://pywinauto.readthedocs.io) - Automation on Windows platforms with Python. +* [keyboard](https://github.com/boppreh/keyboard) - Pure Python cross-platform keyboard hooks/control and hotkeys! +* [mouse](https://github.com/boppreh/mouse) - From the creators of `keyboard`, Pure Python *mouse* control! +* [pynput](https://github.com/moses-palmer/pynput) - Keyboard and mouse control diff --git a/docs/_static/ahk.gif b/docs/_static/ahk.gif deleted file mode 100644 index de24488e..00000000 Binary files a/docs/_static/ahk.gif and /dev/null differ diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 00000000..09e66c1f --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,7 @@ +a > code.xref > span { + color: rgb(85, 199, 255); +} + +a:visited > code.xref > span { + color: rgb(110, 140, 192) +} diff --git a/docs/api/async.rst b/docs/api/async.rst new file mode 100644 index 00000000..f67ce273 --- /dev/null +++ b/docs/api/async.rst @@ -0,0 +1,34 @@ +Async API +========= + +.. toctree:: + +The async API is mostly identical to the sync API. + +AsyncFutureResult +----------------- + +.. autoclass:: ahk._async.transport.AsyncFutureResult + :members: + :undoc-members: + +AsyncWindow +----------- + +.. autoclass:: ahk._async.window.AsyncWindow + :members: + :undoc-members: + +AsyncControl +------------ +.. autoclass:: ahk._async.window.AsyncControl + :members: + :undoc-members: + + +AsyncAHK +-------- + +.. autoclass:: ahk._async.engine.AsyncAHK + :members: + :undoc-members: diff --git a/docs/api/directives.rst b/docs/api/directives.rst new file mode 100644 index 00000000..54d84fc8 --- /dev/null +++ b/docs/api/directives.rst @@ -0,0 +1,9 @@ +Directives +========== + +Autogenerated reference. + +.. automodule:: ahk.directives + :members: + :undoc-members: + :special-members: __init__ diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 00000000..4a2b4612 --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,17 @@ +API +=== + +This part of the documentation is intended for developers looking to contribute to this project or discover more +about the programming interface. This is largely auto-generated documentation. + +``ahk`` + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + sync + async + methods + directives + message diff --git a/docs/api/message.rst b/docs/api/message.rst new file mode 100644 index 00000000..d1afe0f6 --- /dev/null +++ b/docs/api/message.rst @@ -0,0 +1,7 @@ +Message +======= + + +.. automodule:: ahk.message + :members: + :undoc-members: diff --git a/docs/api/methods.rst b/docs/api/methods.rst new file mode 100644 index 00000000..ef26710b --- /dev/null +++ b/docs/api/methods.rst @@ -0,0 +1,567 @@ +.. role:: raw-html-m2r(raw) + :format: html + + +Available Methods +================= + + +Most methods from autohotkey are implemented in this wrapper. This page can serve as a quick reference to find +Python equivalents of AutoHotkey commands/functions that are implemented in the wrapper. + +Methods that are not implemented are also noted here for reference. This is a work in progress and may not list all [un]available methods. +Check the full API reference for more complete information. + +Mouse and Keyboard +^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Status + - Notes + * - `#KeyHistory `_ + - Not Implemented + - + * - `BlockInput `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.block_input` + * - `Click `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.click` + * - `Send `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.send` / :py:meth:`~ahk._sync.engine.AHK.send_raw` / :py:meth:`~ahk._sync.engine.AHK.send_input` + * - `ControlClick `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.control_click` / :py:meth:`~ahk._sync.window.Window.click` (:py:class:`~ahk._sync.window.Window` method) + * - `ControlSend[Raw] `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.control_send` / :py:meth:`~ahk._sync.engine.AHK.Control.send` + * - `CoordMode `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.set_coord_mode` (or as a parameter to methods affected by the coord mode) + * - `GetKeyName() `_ + - Not Implemented + - + * - `GetKeySC() `_ + - Not Implemented + - + * - `GetKeyState `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.key_state` + * - `GetKeyVK() `_ + - Not Implemented + - + * - `KeyHistory `_ + - Not Implemented + - + * - `KeyWait `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.key_wait` + * - `Input `_ + - Not Implemented + - Use python ``input()`` instead + * - `InputHook() `_ + - Not Implemented + - + * - `MouseClick `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.click` + * - `MouseClickDrag `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.mouse_drag` + * - `MouseGetPos `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.get_mouse_position` / :py:attr:`~ahk._sync.engine.AHK.mouse_position` + * - `MouseMove `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.mouse_move` + * - `SendLevel `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.set_send_level` + * - `SendMode `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.set_send_mode` + * - `SetCapsLockState `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.set_capslock_state` + * - `SetDefaultMouseSpeed `_ + - Implemented + - Speed is controlled by the ``speed`` keyword argument of relevant methods (for example, see :py:meth:`~ahk._sync.engine.AHK.mouse_move`) + * - `SetKeyDelay `_ + - Implemented + - Delay is controlled by the ``delay`` keyword argument of relevant methods + * - `SetMouseDelay `_ + - Not Implemented + - Delays between mouse movements can be controlled in Python code between calls to ``mouse_move`` + * - `SetNumLockState `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.set_numlock_state` + * - `SetScrollLockState `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.set_scroll_lock_state` + * - `SetStoreCapsLockMode `_ + - Not Implemented + - + + +Hotkeys +^^^^^^^ + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Status + - Notes + * - `Hotkeys `_ + - Implemented + - Before 1.0, callbacks were only supported as Autohotkey Scripts\ :raw-html-m2r:`
` In 1.0 and later, callbacks are supported as Python functions + * - `Hotstrings `_ + - Implemented + - Available in 1.0+ + * - `Suspend `_ + - Implemented* + - Use stop_hotkeys and start_hotkeys to enable/disable hotkeys + + +ClipBoard +^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Status + - Notes + * - `OnClipboardChange() `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.on_clipboard_change` + * - `Clipboard/ClipboardAll `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.get_clipboard` / :py:meth:`~ahk._sync.engine.AHK.set_clipboard` / :py:meth:`~ahk._sync.engine.AHK.get_clipboard_all` / :py:meth:`~ahk._sync.engine.AHK.set_clipboard_all` + * - `ClipWait `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.clip_wait` + + +Screen/Image +^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Status + - Notes + * - `ImageSearch `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.image_search` + * - `PixelGetColor `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.pixel_get_color` + * - `PixelSearch `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.pixel_search` + + +Registry +^^^^^^^^ + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Status + - Notes + * - `RegDelete `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.reg_delete` + * - `RegRead `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.reg_read` + * - `RegWrite `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.reg_write` + * - `SetRegView `_ + - Not Implemented + - + + +Window +^^^^^^ + +Window | Controls +~~~~~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Status + - Notes + * - `Control `_ + - Implemented + - + * - `ControlClick `_ + - Implemented + - :py:meth:`~ahk._sync.window.Window.click` (uses :py:meth:`~ahk._sync.engine.AHK.control_click`) + * - `ControlFocus `_ + - Not Implemented + - + * - `ControlGet `_ + - Implemented + - + * - `ControlGetFocus `_ + - Not Implemented + - + * - `ControlGetPos `_ + - Implemented + - + * - `ControlGetText `_ + - Implemented + - + * - `ControlMove `_ + - Implemented + - + * - `ControlSend[Raw] `_ + - Implemented + - + * - `ControlSetText `_ + - Implemented + - + * - `Menu `_ + - Not Implemented + - + * - `PostMessage/SendMessage `_ + - Not Implemented + - + * - `SetControlDelay `_ + - Not Implemented + - + * - `WinMenuSelectItem `_ + - Not Implemented + - + + +Window | Groups +~~~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Status + - Notes + * - `GroupActivate `_ + - + - + * - `GroupAdd `_ + - + - + * - `GroupClose `_ + - + - + * - `GroupDeactivate `_ + - + - + + +Window functions +^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Status + - Notes + * - `#WinActivateForce `_ + - Implemented + - Any directive can be added to the daemon + * - `DetectHiddenText `_ + - Planned + - + * - `DetectHiddenWindows `_ + - Implemented + - Use ``detect_hidden_windows`` parameter of relevant functions or :py:meth:`~ahk._sync.engine.AHK.set_detect_hidden_windows` + * - `IfWin[Not]Active `_ + - Not Implemented + - Use Python ``if`` with ``win_active``\ /\ ``win.is_active`` + * - `IfWin[Not]Exist `_ + - Not Implemented + - Use Python ``if`` with ``win_exists``\ /\ ``win.exists`` + * - `SetTitleMatchMode `_ + - Implemented + - + * - `SetWinDelay `_ + - Not Implemented + - Delays can be controlled in Python code + * - `StatusBarGetText `_ + - Not Implemented + - + * - `StatusBarWait `_ + - Not Implemented + - + * - `WinActivate `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_activate` / :py:meth:`~ahk._sync.window.Window.activate` (:py:class:`~ahk._sync.window.Window` method) + * - `WinActivateBottom `_ + - Implemented + - + * - `WinActive() `_ + - Implemented + - :py:meth:`~ahk._sync.window.Window.activate` (:py:class:`~ahk._sync.window.Window` method) + * - `WinClose `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_close` / :py:meth:`~ahk._sync.window.Window.close` (:py:class:`~ahk._sync.window.Window` method) + * - `WinExist() `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_exist` / :py:meth:`~ahk._sync.window.Window.exists` (:py:class:`~ahk._sync.window.Window` method) + * - `WinGet `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_get` See also :py:meth:`~ahk._sync.engine.AHK.find_windows` and variants. + * - `WinGetActiveStats `_ + - Not Implemented + - + * - `WinGetActiveTitle `_ + - Not Implemented + - Use :py:meth:`~ahk._sync.engine.AHK.get_active_window` and :py:attr:`~ahk._sync.window.Window.title` property + * - `WinGetClass `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_get_class` / :py:meth:`~ahk._sync.window.Window.get_class` (:py:class:`~ahk._sync.window.Window` method) + * - `WinGetPos `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_get_position` / :py:meth:`~ahk._sync.window.Window.get_position` (:py:class:`~ahk._sync.window.Window` method) + * - `WinGetText `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_get_text` / :py:meth:`~ahk._sync.window.Window.get_text` (:py:class:`~ahk._sync.window.Window` method) + * - `WinGetTitle `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_get_title` / :py:meth:`~ahk._sync.window.Window.get_title` or :py:attr:`~ahk._sync.window.Window.title` (:py:class:`~ahk._sync.window.Window`) + * - `WinHide `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_hide` / :py:meth:`~ahk._sync.window.Window.hide` (:py:class:`~ahk._sync.window.Window`) + * - `WinKill `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_kill` / :py:meth:`~ahk._sync.window.Window.kill` (:py:class:`~ahk._sync.window.Window`) + * - `WinMaximize `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_maximize` / :py:meth:`~ahk._sync.window.Window.maximize` (:py:class:`~ahk._sync.window.Window`) + * - `WinMinimize `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_minimize` / :py:meth:`~ahk._sync.window.Window.minimize` (:py:class:`~ahk._sync.window.Window`) + * - `WinMinimizeAll[Undo] `_ + - Not Implemented + - + * - `WinMove `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_move` + * - `WinRestore `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_restore` + * - `WinSet `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_set_always_on_top` / :py:meth:`~ahk._sync.engine.AHK.win_set_bottom` / :py:meth:`~ahk._sync.engine.AHK.win_set_disable` / :py:meth:`~ahk._sync.engine.AHK.win_set_enable` / :py:meth:`~ahk._sync.engine.AHK.win_set_ex_style` / :py:meth:`~ahk._sync.engine.AHK.win_set_redraw` / :py:meth:`~ahk._sync.engine.AHK.win_set_region` / :py:meth:`~ahk._sync.engine.AHK.win_set_style` / :py:meth:`~ahk._sync.engine.AHK.win_set_title` / :py:meth:`~ahk._sync.engine.AHK.win_set_top` / :py:meth:`~ahk._sync.engine.AHK.win_set_trans_color` / :py:meth:`~ahk._sync.engine.AHK.win_set_transparent` + + * - `WinSetTitle `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_set_title` + * - `WinShow `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_show` + * - `WinWait `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_wait` + * - `WinWait[Not]Active `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_wait_not_active` + * - `WinWaitClose `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.win_wait_close` + + +Sound +^^^^^ + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Status + - Notes + * - `SoundBeep `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.sound_beep` + * - `SoundGet `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.sound_get` + * - `SoundGetWaveVolume `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.get_volume` + * - `SoundPlay `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.sound_play` + * - `SoundSet `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.sound_set` + * - `SoundSetWaveVolume `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.set_volume` + + +GUI +^^^ + +GUI methods are largely unimplmented, except ``ToolTip`` and ``TrayTip``. +We recommend using one of the many `Python GUI libraries `_, such as ``tkinter`` from the standard library or a third +party package such as `pyqt `_ , `FreeSimpleGUI `_ or similar. + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Status + - Notes + * - `Gui `_ + - Not Implemented + - + * - `Gui control types `_ + - Not Implemented + - + * - `GuiControl `_ + - Not Implemented + - + * - `GuiControlGet `_ + - Not Implemented + - + * - `Gui ListView control `_ + - Not Implemented + - + * - `Gui TreeView control `_ + - Not planned + - + * - `IfMsgBox `_ + - Not planned + - + * - `InputBox `_ + - Implemented + - :py:meth:`~ahk._sync.engine.input_box` + * - `FileSelectFile `_ + - Implemented + - :py:meth:`~ahk._sync.engine.file_select_box` + * - `FileSelectFolder `_ + - Implemented + - :py:meth:`~ahk._sync.engine.folder_select_box` + * - `LoadPicture() `_ + - Not Implemented + - + * - `Menu `_ + - Not Implemented + - + * - `MenuGetHandle() `_ + - Not Planned + - + * - `MenuGetName() `_ + - Not Planned + - + * - `MsgBox `_ + - Implemented + - :py:meth:`~ahk._sync.engine.msg_box` + * - `OnMessage() `_ + - Not Planned + - + * - `Progress `_ + - Not Planned + - + * - `SplashImage `_ + - Not Planned + - + * - `SplashTextOn/Off `_ + - Not Planned + - + * - `ToolTip `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.show_tooltip` + * - `TrayTip `_ + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.show_traytip` + + +Directives +^^^^^^^^^^ + +In general, all directives are technically usable, however many do not have applicable context in the Python library. + +Directives are mentioned in tables above and are omitted from this table. + + +For example, to use the :py:class:`~ahk.directives.NoTrayIcon` directive + + from ahk import AHK + from ahk.directives import NoTrayIcon + ahk = AHK(directives=[NoTrayIcon]) + +.. list-table:: + :header-rows: 1 + + * - AutoHotkey Command + - Notes + * - `#HotkeyInterval `_ + - + * - `#HotkeyModifierTimeout `_ + - + * - `#Hotstring `_ + - + * - `#Include[Again] `_ + - Using this directive is strongly discouraged as it is **very** likely to cause issues. Use with extreme caution. + * - `#InputLevel `_ + - + * - `#KeyHistory `_ + - + * - `#MaxHotkeysPerInterval `_ + - + * - `#MaxMem `_ + - + * - `#MaxThreads `_ + - + * - `#MaxThreadsBuffer `_ + - + * - `#MaxThreadsPerHotkey `_ + - Hotkey callbacks are run in Python, so this largely won't have any significant effect + * - `#MenuMaskKey `_ + - + * - `#NoEnv `_ + - Removed in ``ahk`` v1.0.0 -- Used by default when using AutoHotkey v1. Not available in AutoHotkey v2. + * - `#NoTrayIcon `_ + - If you use hotkeys or hotstrings, you probably also want to configure this as a hotkey transport option + * - `#Persistent `_ + - This is on by default in scripts run by this library + * - `#Requires `_ + - + * - `#SingleInstance `_ + - This directive is provided by default (SingleInstance Off for the main thread) + * - `#UseHook `_ + - + * - `#Warn `_ + - Not relevant for this library + * - `#AllowSameLineComments `_ + - Not relevant for this library + * - `#ClipboardTimeout `_ + - Not relevant for this library + * - `#CommentFlag `_ + - Not relevant for this library + * - `#ErrorStdOut `_ + - Not relevant for this library + * - `#EscapeChar `_ + - Not relevant for this library + * - `#InstallKeybdHook `_ + - Not relevant for this library + * - `#InstallMouseHook `_ + - Not relevant for this library + * - `#If `_ + - Not relevant for this library + * - `#IfTimeout `_ + - Not relevant for this library diff --git a/docs/api/sync.rst b/docs/api/sync.rst new file mode 100644 index 00000000..4a91d746 --- /dev/null +++ b/docs/api/sync.rst @@ -0,0 +1,37 @@ +Sync API +======== + +.. toctree:: + + +The sync API is generated automatically from the async API using ``unasync``. + +The sync API is the default API described in most of the README. + +FutureResult +------------ + +.. autoclass:: ahk._sync.transport.FutureResult + :members: + :undoc-members: + + +Window +------ + +.. autoclass:: ahk._sync.window.Window + :members: + :undoc-members: + +AsyncControl +------------ +.. autoclass:: ahk._sync.window.Control + :members: + :undoc-members: + +AHK +--- + +.. autoclass:: ahk._sync.engine.AHK + :members: + :undoc-members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..6bb5ce5d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,46 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import os +import sys + +sys.path.insert(0, os.path.abspath('../')) + +project = 'ahk' +copyright = '2023, Spencer Phillip Young' +author = 'Spencer Phillip Young' +release = '1.0.0' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx_autodoc_typehints', + 'sphinx.ext.viewcode', + 'm2r', +] +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +source_suffix = ['.rst', '.md'] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] + +autodoc_default_options = { + 'member-order': 'bysource', + 'undoc-members': True, + 'special-members': '__init__', +} + +always_document_param_types = True +html_css_files = [ + 'css/custom.css', +] diff --git a/docs/docrequirements.txt b/docs/docrequirements.txt new file mode 100644 index 00000000..eb4a375c --- /dev/null +++ b/docs/docrequirements.txt @@ -0,0 +1,6 @@ +sphinx<7 +mistune<2 +sphinx-rtd-theme +sphinx-autodoc-typehints +m2r +jinja2 diff --git a/docs/extending.rst b/docs/extending.rst new file mode 100644 index 00000000..468a84e1 --- /dev/null +++ b/docs/extending.rst @@ -0,0 +1,344 @@ +Extending AHK +============= + +.. attention:: + The extension feature is in early stages of development and may change at any time, including breaking changes in minor version releases. + +You can extend AHK to add more functionality. This is particularly useful for those who may want to +contribute their own solutions into the ecosystem that others can use. + +For users of an extension, their interface will typically look like this: + +1. Install the extension (e.g., ``pip install ...``) +2. import the extension(s) and enable extensions when instantiating the ``AHK`` class + +.. code-block:: + + from my_great_extension import the_extension + from ahk import AHK + ahk = AHK(extensions='auto') # use all available/imported extensions + ahk.my_great_method('foo', 'bar', 'baz') # new methods are available from the extension! + + +This document will describe how you can create your own extensions and also cover some basics of packaging and +distributing an extension for ``ahk`` on PyPI. + + +Background +---------- + +First, a little background is necessary into the inner mechanisms of how ``ahk`` does what it does. It is important for +extension authors to understand these key points: + +- Python calls AHK functions by name and can pass any number of strings as parameters. +- Functions written in AHK accept zero or more string arguments and must return a string in a specific message format (we'll discuss these specifics later) +- The message returned from AHK to Python indicates the type of the return value so Python can parse the response message into an appropriate Python type. There are several predefined message types available in the :py:mod:`ahk.message` module. Extension authors may also create their own message types (discussed later). + + + +Writing an extension +-------------------- + +The basics of writing an extension requires two key components: + + +- A function written in AHK that conforms to the required spec (accepts zero or more arguments and returns a formatted message). +- A python function that accepts an instance of ``AHK`` (or ``AsyncAHK`` for ``async`` functions) as its first parameter (think of it like a method of the ``AHK`` class). It may also accept any additional parameters. + + +Example +^^^^^^^ + +This simple example extension will provide a new method on the ``AHK`` class called ``simple_math``. This new method +accepts three arguments: two operands (``lhs`` and ``rhs``) and an operator (``+`` or ``*``). + +When complete, the interface will look something like this: + +.. code-block:: + + ahk = AHK(extensions='auto') + print(ahk.simple_math(2, 2, '+')) # 4 + + +Let's begin writing the extension. + +First, we'll start with the AutoHotkey code. This will be an AHK function that, in this case, accepts 3 arguments. + +Ultimately, the function will perform some operation utilizing these inputs and will return a formatted response. We use +the ``FormatResponse`` function (which is available by default) to do this. ``FormatResponse`` accepts two arguments: the message type name +and the raw payload as a string. By default, message type names are the fully qualified name of the Python class that +implements the message type (more on message types later). + + +.. code-block:: + + + SimpleMath(lhs, rhs, operator) { + if (operator = "+") { + result := (lhs + rhs) + } else if (operator = "*") { + result := (lhs * rhs) + } else { ; invalid operator argument + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("Invalid operator: {}", operator)) + } + return FormatResponse("ahk.message.IntegerResponseMessage", result) + } + + + +Next, we'll create the Python components of our extension: a Python function and the extension itself. The extension +itself is an instance of the ``Extension`` class and it accepts an argument ``script_text`` which will be a string +containing the AutoHotkey code we just wrote above. + + +.. code-block:: + + from ahk import AHK + from ahk.extensions import Extension + from typing import Literal + + script_text = r''' + ; a string of your AHK script + ; Omitted here for brevity -- copy/paste from the previous code block + ''' + simple_math_extension = Extension(script_text=script_text) + + @simple_math_extension.register # register the method for the extension + def simple_math(ahk: AHK, lhs: int, rhs: int, operator: Literal['+', '*']) -> int: + assert isinstance(lhs, int) + assert isinstance(rhs, int) + # assert operator in ('+', '*') # we'll leave this out so we can demo raising exceptions from AHK + args = [str(lhs), str(rhs), operator] # all args must be strings + result = ahk.function_call('SimpleMath', args, blocking=True) + return result + + +After the extension is created, it can be used automatically! + +.. code-block:: + + # ... above code omitted for brevity + ahk = AHK(extensions='auto') + + result = ahk.simple_math(2, 4, operator='+') + print('2 + 4 =', result) + assert result == 6 + + result = ahk.simple_math(2, 4, operator='*') + print('2 * 4 =', result) + assert result == 8 + + # this will raise our custom exception from our AHK code + try: + ahk.simple_math(0, 0, operator='invalid') + except Exception as e: + print('An exception was raised. Exception message was:', e) + +If you use this example code, it should output something like this: :: + + 2 + 4 = 6 + 2 * 4 = 8 + An exception was raised. Exception message was: Invalid operator: % + + +Extending ``Window`` methods +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Just as you can add methods that are accessible from ``AHK`` (and ``AsyncAHK``) instances, you can also add methods +that are accessible from the ``Window`` and ``AsyncWindow`` classes as well. This is identical to the process +described above, except you use the ``register_window_method`` decorator instead of the ``register`` decorator. The +first argument of such decorated functions should accept a ``Window`` object (or ``AsyncWindow`` object for async functions). + + +Includes +^^^^^^^^ + +In addition to supplying AutoHotkey extension code via ``script_text``, you may also do this using includes. + +.. code-block:: + + from ahk.extensions import Extension + my_extension = Extension(includes=['myscript.ahk']) # equivalent to "#Include myscript.ahk" + +AsyncIO considerations +^^^^^^^^^^^^^^^^^^^^^^ + +When registering an extension function, if the decorated function is a coroutine function (``async def function_name(...):``) +then it will be made available only when the Async API (via ``AsyncAHK()``) is used. Conversely, normal non-async functions will only be available +when the sync API (via ``AHK()``). + +To provide your extension functionality to both the Sync and Async APIs, you will need to provide both a synchronous and async version of your function. + +.. code-block:: + + + @my_extension.register + def my_function(ahk: AHK, foo, bar): + ... + + @my_extension.register + async def my_function(ahk: AsyncAHK, foo, bar): + ... + + +AutoHotkey V1 vs V2 compatibility +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Because extensions involve the inclusion of AutoHotkey source code, it is often the case that extensions are sensitive +to the version of AutoHotkey being used. Extensions can specify their compatibility with different AutoHotkey versions +by providing the ``requires_autohotkey`` keyword argument with a value of ``v1`` or ``v2``. If an extension omits this +keyword argument, it is assumed that the extension is compatible with both V1 and V2. + +When an AutoHotkey class is instantiated with ``extensions='auto'`` extensions are automatically filtered by version compatibility. + +That is to say, you may need multiple ``Extension`` objects to fully support users of both versions of AutoHotkey. However, this +doesn't necessarily mean you need multiple Python functions -- you can register multiple extensions to the same Python function. + +.. code-block:: + + my_extension_v1 = Extension(..., requires_autohotkey='v1') + my_extension_v2 = Extension(..., requires_autohotkey='v1') + + @my_extension_v1.register + @my_extension_v2.register + def my_extension_function(ahk: AHK, foo, bar, baz) -> Any: + ... + + +Extension dependencies +^^^^^^^^^^^^^^^^^^^^^^ + +Extensions can declare explicit dependencies on other extensions. This allows extension authors to re-use other extensions +and end-users do not need to specify your extension's dependencies when explicitly providing the ``extensions`` keyword argument. + +To specify dependencies, provide a list of ``Extension`` instance objects in the ``dependencies`` keyword argument. + +.. code-block:: + + from ahk_json import JXON # pip install ahk-json + my_extension_script = '''\ + MyAHKFunction(one, two) { + val := Array(one, two) + ret := Jxon_Dump(val) ; `Jxon_Dump` is provided by the dependent extension! + return FormatResponse("ahk_json.message.JsonResponseMessage", ret) ; this message type is also part of the extension + } + ''' + MY_EXTENSION = Extension(script_text=my_extension_script, dependencies=[JXON], requires_autohotkey='v1') + + @MY_EXTENSION.register + def my_function(ahk: AHK, one: str, two: str) -> list[str]: + args = [one, two] + return ahk.function_call('MyAHKFunction', args) + +Then users may use such an extension simply as follows, and both ``JXON`` and ``MY_EXTENSION`` will be used. + +.. code-block:: + + from ahk import AHK + from my_extension import MY_EXTENSION + + ahk = AHK(extensions=[MY_EXTENSION], version='v1') # same effect as extensions=[JXON, MY_EXTENSION] + +Best practices for extension authors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some conventions that authors are recommended to follow: + +- Extension functions should use namespaced naming conventions to avoid collisions (both in AutoHotkey code and Python function names); avoid generic function names like "load" or similar that may collide with other extensions +- Do not start AutoHotkey function names with ``AHK`` -- as it may conflict with functions implemented by this package. +- Extension packages published on PyPI should be named with a convention like so: ``ahk-`` + + +Available Message Types +^^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + + * - Message type + - Python return type + - Payload description + * - :py:class:`ahk.message.TupleResponseMessage` + - A ``tuple`` object containing any number of literal types (``Tuple[Any, ...]``) + - A string representing a tuple literal (i.e. usable with ``ast.literal_eval``) + * - :py:class:`ahk.message.CoordinateResponseMessage` + - A tuple containing two integers (``Tuple[int, int]``) + - A string representing the tuple literal + * - :py:class:`ahk.message.IntegerResponseMessage` + - An integer (``int``) + - A string literal representing an integer + * - :py:class:`ahk.message.BooleanResponseMessage` + - A boolean (``bool``) + - A string literal of either ``0`` or ``1`` + * - :py:class:`ahk.message.StringResponseMessage` + - A string (``str``) + - Any string + * - :py:class:`ahk.message.WindowListResponseMessage` + - A list of :py:class:`~ahk._sync.window.Window` (or :py:class:`~ahk._async.window.AsyncWindow`) objects + - A string containing a comma-delimited list of window IDs + * - :py:class:`ahk.message.NoValueResponseMessage` + - NoneType (``None``) + - A sentinel value (use ``FormatNoValueResponse()`` in AHK for returning this message) + * - :py:class:`ahk.message.ExceptionResponseMessage` + - raises an Exception. + - A string with the exception message + * - :py:class:`ahk.message.WindowControlListResponseMessage` + - A list of :py:class:`~ahk._sync.window.Control` (or :py:class:`~ahk._async.window.AsyncControl`) objects + - A string literal representing a tuple containing the window hwnd and a list of tuples each containing the control hwnd and class for each control + * - :py:class:`ahk.message.WindowResponseMessage` + - A :py:class:`~ahk._sync.Window` (or ``AsyncWindow``) object + - A string containing the ID of the window + * - :py:class:`ahk.message.PositionResponseMessage` + - A ``Postion`` namedtuple object, consisting of 4 integers with named attributes ``x``, ``y``, ``width``, and ``height`` + - A string representing the tuple literal + * - :py:class:`ahk.message.FloatResponseMessage` + - ``float`` + - A string literal representation of a float + * - :py:class:`ahk.message.TimeoutResponseMessage` + - raises a ``TimeoutException`` + - A string containing the exception message + * - :py:class:`ahk.message.B64BinaryResponseMessage` + - ``bytes`` object + - A string containing base64-encoded binary data + + +Returning custom types (make your own message type) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can design your extension functions to ultimately return different types by implementing your own message class. + +To do this, subclass :py:class:`ahk.message.ResponseMessage` (or any of its other subclasses) and implement the ``unpack`` method. + +For example, suppose you want your method to return a datetime object, you might do something like this: + +.. code-block:: + + import datetime + from ahk.message import IntegerResponseMessage + class DatetimeResponseMessage(IntegerResponseMessage): + def unpack(self) -> datetime.datetime: + val = super().unpack() # get the integer timestamp + return datetime.datetime.fromtimestamp(val) + +In AHK code, you can reference custom response messages by the their fully qualified name, including the namespace. +(if you're not sure what this means, you can see this value by calling the ``fqn()`` method, e.g. ``DateTimeResponseMessage.fqn()``) + + +Featured extension packages +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Since this feature is in early development, not many extensions exist yet. However, I've authored two small extensions +which can be used as references or examples of how to create and distribute an extension: + +- `ahk-wmutil `_ an extension providing utility support for working with multiple monitors. Includes examples of window extensions. +- `ahk-json `_ an extension providing custom a JSON message type that can be used by other extensions. + +If you have created an extension you'd like to share, consider opening an issue, PR, or discussion and it may be added to this list. + +Notes +^^^^^ + +- AHK functions MUST always return a message. Failing to return a message will result in an exception being raised. If the function should return nothing, use ``return FormatNoValueResponse()`` which will translate to ``None`` in Python. +- You cannot define hotkeys, hotstrings, or write any AutoHotkey code that would cause the end of the `auto-execute section `_ +- Extensions must be imported (anywhere, at least once) *before* instantiating the ``AHK`` instance +- Although extensions can be declared explicitly, using ``extensions='auto'`` can be used for convenience/portability. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..90e24c2a --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,30 @@ +.. ahk documentation master file, created by + sphinx-quickstart on Sat Apr 4 07:27:28 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +ahk Python wrapper documentation +================================ + +`GitHub`_ + +.. _GitHub: https://github.com/spyoungtech/ahk + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + + quickstart + README + api/index + extending + + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..954237b9 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 00000000..fba31362 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,38 @@ +Quickstart +========== + +This document assumes you have **Python 3.8 or newer** installed + +Installing AHK +-------------- + +AHK requires the AutoHotkey software in addition to the Python package + + +1. Install the Python ``ahk`` package :: + + py -m pip install ahk + + +2. Download and install AutoHotkey (1.1.x). It can be downloaded from the `autohotkey website`_; **OR** install using pip :: + + py -m pip install "ahk[binary]" + + +3. Write your first script:: + + from ahk import AHK + ahk = AHK() + ahk.run_script('Run Notepad') + notepad_window = ahk.win_get(title='Untitled - Notepad') + notepad_window.send('Hello World') + +Run the script! + +If you get an :py:class:`~ahk.script.ExecutableNotFoundError` it's because AutoHotkey was installed to a location that +is not on PATH or the default location (``C:\Program Files\AutoHotkey\AutoHotkey.exe``). You can either place the +executable on PATH, in the default location, or specify the location manually in code: :: + + ahk = AHK(executable_path='C:\\Path\\To\\AutoHotkey.exe') + +.. _autohotkey website: https://www.autohotkey.com/download/ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..9bee86a2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires = ["setuptools", "unasync @ https://github.com/spyoungtech/unasync/archive/refs/heads/unasync-remove.zip", "tokenize-rt"] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..4986a9fa --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,14 @@ +pytest +pillow +unasync@https://github.com/spyoungtech/unasync/archive/refs/heads/unasync-remove.zip +black +tokenize-rt +coverage +mypy +typing_extensions +jinja2 +pytest-rerunfailures +ahk-json +ahk-binary +pre-commit +tox diff --git a/setup.cfg b/setup.cfg index 8183238a..4660e8e7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,61 @@ [metadata] + +name = ahk +version = 1.8.4 +author_email = spencer.young@spyoung.com +author = Spencer Young +description = A Python wrapper for AHK +long_description = file: docs/README.md +long_description_content_type = text/markdown +url = https://github.com/spyoungtech/ahk +project_urls = + Documentation = https://ahk.readthedocs.io/en/latest/ + Funding = https://github.com/sponsors/spyoungtech/ + Source = https://github.com/spyoungtech/ahk + Tracker = https://github.com/spyoungtech/ahk/issues +keywords = + ahk + autohotkey + windows + mouse + keyboard + automation + pyautogui license_files = LICENSE +classifiers = + Intended Audience :: Developers + Topic :: Desktop Environment + Programming Language :: Python + Environment :: Win32 (MS Windows) + License :: OSI Approved :: MIT License + Operating System :: Microsoft :: Windows + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 + Typing :: Typed + +[options] +include_package_data = True +python_requires = >=3.8.0 +packages = + ahk + ahk.templates + ahk._async + ahk._sync +install_requires = + typing_extensions; python_version < "3.11" + jinja2>=3.0 +cmdclass = + build_py = buildunasync.build_py + +[options.extras_require] +binary = ahk-binary==2023.9.0 + +[options.package_data] +ahk = + py.typed + templates/*.ahk diff --git a/setup.py b/setup.py index 20c6f9f3..60684932 100644 --- a/setup.py +++ b/setup.py @@ -1,34 +1,3 @@ from setuptools import setup -from io import open -test_requirements = ['behave', 'behave-classy', 'pytest'] -extras = {'test': test_requirements} -with open('README.md', encoding='utf-8') as f: - long_description = f.read() - -setup( - name='ahk', - version='0.6.1', - url='https://github.com/spyoungtech/ahk', - description='A Python wrapper for AHK', - long_description=long_description, - long_description_content_type="text/markdown", - author_email='spencer.young@spyoung.com', - author='Spencer Young', - packages=['ahk'], - install_requires=['jinja2'], - classifiers=[ - 'Intended Audience :: Developers', - 'Topic :: Desktop Environment', - 'Programming Language :: Python', - 'Environment :: Win32 (MS Windows)', - 'License :: OSI Approved :: MIT License', - 'Operating System :: Microsoft :: Windows', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - ], - tests_require=test_requirements, - include_package_data=True, - zip_safe=False -) +setup() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/_async/__init__.py b/tests/_async/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/_async/test_clipboard.py b/tests/_async/test_clipboard.py new file mode 100644 index 00000000..516ae62f --- /dev/null +++ b/tests/_async/test_clipboard.py @@ -0,0 +1,46 @@ +import asyncio +import time +import unittest.mock + +from ahk import AsyncAHK + +async_sleep = asyncio.sleep # unasync: remove + +sleep = time.sleep + + +class TestWindowAsync(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + + async def asyncTearDown(self) -> None: + self.ahk._transport._proc.kill() + time.sleep(0.2) + + async def test_clipboard(self): + await self.ahk.set_clipboard('foo') + contents = await self.ahk.get_clipboard() + assert contents == 'foo' + + async def test_clipboard_all(self): + await self.ahk.set_clipboard('Hello \N{EARTH GLOBE AMERICAS}') + data = await self.ahk.get_clipboard_all() + await self.ahk.set_clipboard('foo') + assert data != await self.ahk.get_clipboard_all() + await self.ahk.set_clipboard_all(data) + assert data == await self.ahk.get_clipboard_all() + assert await self.ahk.get_clipboard() == 'Hello \N{EARTH GLOBE AMERICAS}' + + async def test_on_clipboard_change(self): + with unittest.mock.MagicMock(return_value=None) as m: + self.ahk.on_clipboard_change(m) + self.ahk.start_hotkeys() + await self.ahk.set_clipboard('foo') + await self.ahk.set_clipboard('bar') + await async_sleep(1) + m.assert_called() + + +class TestWindowAsyncV2(TestWindowAsync): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(version='v2') diff --git a/tests/_async/test_extensions.py b/tests/_async/test_extensions.py new file mode 100644 index 00000000..c61e7126 --- /dev/null +++ b/tests/_async/test_extensions.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import asyncio +import random +import string +import time +import unittest +from typing import Literal + +import pytest + +from ahk import AsyncAHK +from ahk.extensions import Extension + +async_sleep = asyncio.sleep # unasync: remove + +sleep = time.sleep + +function_name = 'AHKDoSomething' +function_name = 'AAHKDoSomething' # unasync: remove + +ext_text = f'''\ +{function_name}(first, second) {{ + return FormatResponse("ahk.message.StringResponseMessage", Format("{{}} and {{}}", first, second)) +}} +''' + +math_function_name = 'SimpleMath' +math_function_name = 'ASimpleMath' # unasync: remove + +math_test = rf''' +{math_function_name}(lhs, rhs, operator) {{ + if (operator = "+") {{ + result := (lhs + rhs) + }} else if (operator = "*") {{ + result := (lhs * rhs) + }} else {{ ; invalid operator argument + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("Invalid operator: {{}}", operator)) + }} + return FormatResponse("ahk.message.IntegerResponseMessage", result) +}} +''' + +from ahk_json import JXON + +dependency_func_name = 'MyFunc' +dependency_func_name = 'AMyFunc' # unasync: remove + +dependency_test_script = f'''\ +{dependency_func_name}(one, two) {{ + val := Array(one, two) + ret := Jxon_Dump(val) ; `Jxon_Dump` is provided by the dependent extension! + return FormatResponse("ahk_json.message.JsonResponseMessage", ret) ; this message type is also part of the extension +}} +''' + +dependency_extension = Extension(script_text=dependency_test_script, dependencies=[JXON], requires_autohotkey='v1') + + +@dependency_extension.register +async def my_function(ahk, one: str, two: str) -> list[str]: + args = [one, two] + return await ahk.function_call(dependency_func_name, args) + + +async_extension = Extension(script_text=ext_text) +async_math_extension = Extension(script_text=math_test) + + +@async_math_extension.register +async def simple_math(ahk: AsyncAHK, lhs: int, rhs: int, operator: Literal['+', '*']) -> int: + assert isinstance(lhs, int) + assert isinstance(rhs, int) + args = [str(lhs), str(rhs), operator] # all args must be strings + result = await ahk.function_call(math_function_name, args, blocking=True) + return result + + +@async_extension.register +async def do_something(ahk, first: str, second: str) -> str: + res = await ahk.function_call(function_name, [first, second]) + return res + + +class TestExtensions(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(extensions=[async_extension, dependency_extension]) + + async def asyncTearDown(self) -> None: + try: + self.ahk._transport._proc.kill() + except: + pass + time.sleep(0.2) + + async def test_ext_explicit(self): + res = await self.ahk.do_something('foo', 'bar') + assert res == 'foo and bar' + + async def test_dep_extension(self): + res = await self.ahk.my_function('foo', 'bar') + assert res == ['foo', 'bar'] + + +class TestExtensionsAuto(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(extensions='auto') + + async def asyncTearDown(self) -> None: + try: + self.ahk._transport._proc.kill() + except: + pass + time.sleep(0.2) + + async def test_ext_auto(self): + res = await self.ahk.do_something('foo', 'bar') + assert res == 'foo and bar' + + async def test_math_example(self): + res = await self.ahk.simple_math(1, 2, '+') + assert res == 3 + + async def test_math_example_exception(self): + with pytest.raises(Exception): + res = await self.ahk.simple_math(1, 2, 'x') + + +class TestNoExtensions(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + await self.ahk.get_mouse_position() # cause daemon to start + + async def asyncTearDown(self) -> None: + self.ahk._transport._proc.kill() + time.sleep(0.2) + + async def test_ext_no_ext(self): + assert not hasattr(self.ahk, 'do_something') + + +class TestExtensionsV2(TestExtensions): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(extensions=[async_extension], version='v2') + + async def test_dep_extension(self): + pytest.skip('this test does not run on v2') + + +class TestExtensionsAutoV2(TestExtensionsAuto): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(extensions='auto', version='v2') + + +class TestNoExtensionsV2(TestNoExtensions): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(version='v2') + await self.ahk.get_mouse_position() # cause daemon to start + + async def test_dep_extension(self): + pytest.skip('this test does not run on v2') + + +class TestExtensionCompatibility(unittest.IsolatedAsyncioTestCase): + def test_ext_incompatible(self): + with pytest.raises(ValueError): + AsyncAHK(version='v2', extensions=[dependency_extension]) diff --git a/tests/_async/test_gui.py b/tests/_async/test_gui.py new file mode 100644 index 00000000..69553135 --- /dev/null +++ b/tests/_async/test_gui.py @@ -0,0 +1,41 @@ +import asyncio +import time +import unittest + +import pytest + +from ahk import AsyncAHK + +async_sleep = asyncio.sleep # unasync: remove + +sleep = time.sleep + + +class TestGui(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + + async def asyncTearDown(self) -> None: + self.ahk._transport._proc.kill() + time.sleep(0.2) + + async def test_msg_box(self): + box = await self.ahk.msg_box(text='hello', title='test', timeout=3, blocking=False) + await async_sleep(1) + win = await self.ahk.win_get(title='test') + assert win is not None + with pytest.raises(TimeoutError): + r = await box.result() + + async def test_input_box(self): + box = await self.ahk.input_box(prompt='Question', title='prompt', timeout=3, blocking=False) + await async_sleep(1) + win = await self.ahk.win_get(title='prompt') + assert win is not None + with pytest.raises(TimeoutError): + r = await box.result() + + +class TestGuiV2(TestGui): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(version='v2') diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py new file mode 100644 index 00000000..5d87f71e --- /dev/null +++ b/tests/_async/test_hotkeys.py @@ -0,0 +1,73 @@ +import asyncio +import subprocess +import time +from unittest import IsolatedAsyncioTestCase # unasync: remove +from unittest import mock +from unittest import TestCase + +from ahk import AsyncAHK +from ahk import AsyncWindow + +async_sleep = asyncio.sleep # unasync: remove + +sleep = time.sleep + + +class TestHotkeysAsync(IsolatedAsyncioTestCase): + win: AsyncWindow + + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + + async def asyncTearDown(self) -> None: + self.ahk.stop_hotkeys() + self.ahk._transport._proc.kill() + subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey*.exe'], capture_output=True) + time.sleep(0.2) + + async def test_hotkey(self): + with mock.MagicMock(return_value=None) as m: + self.ahk.add_hotkey('a', callback=m) + self.ahk.start_hotkeys() + await self.ahk.key_down('a') + await self.ahk.key_press('a') + await async_sleep(1) + m.assert_called() + + async def test_hotkey_ex_handler(self): + def side_effect(): + raise Exception('oh no') + + with mock.MagicMock() as mock_cb, mock.MagicMock() as mock_ex_handler: + mock_cb.side_effect = side_effect + self.ahk.add_hotkey('a', callback=mock_cb, ex_handler=mock_ex_handler) + self.ahk.start_hotkeys() + await self.ahk.key_down('a') + await self.ahk.key_press('a') + await async_sleep(1) + mock_ex_handler.assert_called() + + async def test_remove_hotkey(self): + with mock.MagicMock(return_value=None) as m: + self.ahk.add_hotkey('a', callback=m) + self.ahk.start_hotkeys() + self.ahk.remove_hotkey('a') + await self.ahk.key_down('a') + await self.ahk.key_press('a') + await async_sleep(1) + m.assert_not_called() + + async def test_clear_hotkeys(self): + with mock.MagicMock(return_value=None) as m: + self.ahk.add_hotkey('a', callback=m) + self.ahk.start_hotkeys() + self.ahk.clear_hotkeys() + await self.ahk.key_down('a') + await self.ahk.key_press('a') + await async_sleep(1) + m.assert_not_called() + + +class TestHotkeysAsyncV2(TestHotkeysAsync): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(version='v2') diff --git a/tests/_async/test_keys.py b/tests/_async/test_keys.py new file mode 100644 index 00000000..669ce842 --- /dev/null +++ b/tests/_async/test_keys.py @@ -0,0 +1,116 @@ +import asyncio +import os +import subprocess +import sys +import time +import unittest.mock + +import pytest + +from ahk import AsyncAHK +from ahk import AsyncWindow + +async_sleep = asyncio.sleep # unasync: remove + +sleep = time.sleep + + +class TestKeysAsync(unittest.IsolatedAsyncioTestCase): + win: AsyncWindow + + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = await self.ahk.win_get(title='Untitled - Notepad') + self.assertIsNotNone(self.win) + await self.ahk.set_capslock_state('Off') + + async def asyncTearDown(self) -> None: + await self.ahk.set_capslock_state('Off') + try: + self.p.kill() + except Exception: + pass + self.p.communicate() + self.ahk._transport._proc.kill() + time.sleep(0.2) + + async def test_set_capslock(self): + await self.ahk.set_capslock_state('On') + assert await self.ahk.key_state('CapsLock', mode='T') == 1 + + async def test_hotstring(self): + self.ahk.add_hotstring('btw', 'by the way') + self.ahk.start_hotkeys() + await self.ahk.set_send_level(1) + await self.win.activate() + await self.ahk.send('btw ') + time.sleep(2) + + assert 'by the way' in await self.win.get_text() + + async def test_hotstring_cyrillic(self): + # https://github.com/spyoungtech/ahk/issues/328 + self.ahk.add_hotstring('тест', 'hello world') + self.ahk.start_hotkeys() + await self.ahk.set_send_level(1) + await self.win.activate() + await self.ahk.send('тест ') + time.sleep(2) + + assert 'hello world' in await self.win.get_text() + + async def test_remove_hotstring(self): + self.ahk.add_hotstring('btw', 'by the way') + self.ahk.start_hotkeys() + await self.ahk.set_send_level(1) + await self.win.activate() + self.ahk.remove_hotstring('btw') + await self.ahk.send('btw ') + time.sleep(2) + assert 'by the way' not in await self.win.get_text() + + async def test_clear_hotstrings(self): + self.ahk.add_hotstring('btw', 'by the way') + self.ahk.start_hotkeys() + await self.ahk.set_send_level(1) + await self.win.activate() + self.ahk.clear_hotstrings() + await self.ahk.send('btw ') + time.sleep(2) + assert 'by the way' not in await self.win.get_text() + + async def test_hotstring_callback(self): + with unittest.mock.MagicMock(return_value=None) as m: + self.ahk.add_hotstring('btw', m) + self.ahk.start_hotkeys() + await self.ahk.set_send_level(1) + await self.win.activate() + await self.ahk.send('btw ') + await async_sleep(1) + m.assert_called() + + async def test_key_wait(self): + res = await self.ahk.key_wait('x', timeout=3, blocking=False) + await self.ahk.set_send_level(1) + await async_sleep(1) + await self.ahk.key_down('x') + await async_sleep(1) + await self.ahk.key_up('x') + result = await res.result() + assert result is True + + async def test_key_wait_timeout(self): + res = await self.ahk.key_wait('x', timeout=1) + assert res is False + + +class TestKeysAsyncV2(TestKeysAsync): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(version='v2') + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = await self.ahk.win_get(title='Untitled - Notepad') + self.assertIsNotNone(self.win) + await self.ahk.set_capslock_state('Off') diff --git a/tests/_async/test_mouse.py b/tests/_async/test_mouse.py new file mode 100644 index 00000000..ea412711 --- /dev/null +++ b/tests/_async/test_mouse.py @@ -0,0 +1,102 @@ +import asyncio +import os +import subprocess +import sys +import time +from unittest import IsolatedAsyncioTestCase + +from ahk import AsyncAHK +from ahk import AsyncWindow + +async_sleep = asyncio.sleep # unasync: remove + +sleep = time.sleep + + +class TestMouseAsync(IsolatedAsyncioTestCase): + win: AsyncWindow + + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + + async def asyncTearDown(self) -> None: + try: + self.ahk._transport._proc.kill() + except: + pass + time.sleep(0.2) + + async def test_mouse_position(self) -> None: + pos = await self.ahk.get_mouse_position() + assert isinstance(pos, tuple) + assert len(pos) == 2 + x, y = pos + assert isinstance(x, int) + assert isinstance(y, int) + + async def test_mouse_move(self) -> None: + await self.ahk.mouse_move(x=100, y=100) + pos = await self.ahk.get_mouse_position() + assert pos == (100, 100) + await self.ahk.mouse_move(x=200, y=200) + pos2 = await self.ahk.get_mouse_position() + assert pos2 == (200, 200) + + async def test_mouse_move_rel(self): + await self.ahk.mouse_move(x=100, y=100) + await async_sleep(0.5) + pos = await self.ahk.get_mouse_position() + assert pos == (100, 100) + await self.ahk.mouse_move(x=10, y=10, relative=True) + await async_sleep(0.5) + pos2 = await self.ahk.get_mouse_position() + x1, y1 = pos + x2, y2 = pos2 + assert abs(x1 - x2) == 10 + assert abs(y1 - y2) == 10 + + async def test_mouse_move_nonblocking(self): + await self.ahk.mouse_move(100, 100) + res = await self.ahk.mouse_move(500, 500, speed=10, send_mode='Event', blocking=False) + current_pos = await self.ahk.get_mouse_position() + await async_sleep(0.1) + pos = await self.ahk.get_mouse_position() + assert pos != current_pos + assert pos != (500, 500) + await res.result() + + async def test_mouse_drag(self): + await self.ahk.mouse_move(x=100, y=100) + pos = await self.ahk.get_mouse_position() + assert pos == (100, 100) + await self.ahk.mouse_drag(x=200, y=200) + pos2 = await self.ahk.get_mouse_position() + assert pos2 == (200, 200) + + async def test_mouse_drag_relative(self): + await self.ahk.mouse_move(x=100, y=100) + await async_sleep(0.5) + pos = await self.ahk.get_mouse_position() + assert pos == (100, 100) + await self.ahk.mouse_drag(x=10, y=10, relative=True, button=1) + await async_sleep(0.5) + pos2 = await self.ahk.get_mouse_position() + x1, y1 = pos + x2, y2 = pos2 + assert abs(x1 - x2) == 10 + assert abs(y1 - y2) == 10 + + async def test_coord_mode(self): + await self.ahk.set_coord_mode(target='Mouse', relative_to='Client') + res = await self.ahk.get_coord_mode(target='Mouse') + assert res == 'Client' + + async def test_send_mode(self): + await self.ahk.set_send_mode('InputThenPlay') + res = await self.ahk.get_send_mode() + assert res == 'InputThenPlay' + + +class TestMouseAsyncV2(TestMouseAsync): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(version='v2') diff --git a/tests/_async/test_registry.py b/tests/_async/test_registry.py new file mode 100644 index 00000000..11f88121 --- /dev/null +++ b/tests/_async/test_registry.py @@ -0,0 +1,58 @@ +import pathlib +import subprocess +import tempfile +import time +import unittest.mock + +import pytest + +from ahk import AsyncAHK +from ahk import AsyncWindow +from ahk.message import AHKExecutionException + + +class TestScripts(unittest.IsolatedAsyncioTestCase): + win: AsyncWindow + + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + + async def asyncTearDown(self) -> None: + try: + self.ahk._transport._proc.kill() + except: + pass + try: + await self.ahk.reg_delete(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk') + except: + pass + time.sleep(0.2) + + async def test_reg_read_write_default_value_name(self): + await self.ahk.reg_write('REG_SZ', r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', value='test') + val = await self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk') + assert val == 'test' + + async def test_reg_write_explicit_value_name(self): + await self.ahk.reg_write('REG_SZ', r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'foo', value='testfoo') + val = await self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'foo') + assert val == 'testfoo' + + async def test_reg_delete(self): + await self.ahk.reg_write('REG_SZ', r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'bar', value='testbar') + await self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'bar') + await self.ahk.reg_delete(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'bar') + with pytest.raises(AHKExecutionException): + await self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'bar') + + async def test_reg_delete_default(self): + await self.ahk.reg_write('REG_SZ', r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', value='test') + await self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk') + await self.ahk.reg_delete(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk') + with pytest.raises(AHKExecutionException): + await self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk') + + +class TestScriptsV2(TestScripts): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(version='v2') diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py new file mode 100644 index 00000000..5cf283ca --- /dev/null +++ b/tests/_async/test_screen.py @@ -0,0 +1,92 @@ +import asyncio +import os +import pathlib +import threading +import time +from itertools import product +from unittest import IsolatedAsyncioTestCase + +from PIL import Image + +from ahk import AsyncAHK + + +class TestScreen(IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + self.before_windows = await self.ahk.list_windows() + self.im = Image.new('RGB', (20, 20)) + for coord in product(range(20), range(20)): + self.im.putpixel(coord, (255, 0, 0)) + time.sleep(1) + + async def asyncTearDown(self): + for win in await self.ahk.list_windows(): + if win not in self.before_windows: + await win.kill() + self.ahk._transport._proc.kill() + time.sleep(0.2) + + # + # async def test_pixel_search(self): + # result = await self.ahk.pixel_search(0xFF0000) + # self.assertIsNotNone(result) + + def _show_in_thread(self): + t = threading.Thread(target=self.im.show) + t.start() + return t + + async def test_image_search(self): + if os.environ.get('CI'): + self.skipTest('This test does not work in GitHub Actions') + return + self._show_in_thread() + time.sleep(3) + self.im.save('testimage.png') + position = await self.ahk.image_search(str(pathlib.Path('testimage.png').absolute())) + assert isinstance(position, tuple) + + async def test_pixel_search(self): + if os.environ.get('CI'): + self.skipTest('This test does not work in GitHub Actions') + return + self._show_in_thread() + time.sleep(3) + self.im.save('testimage.png') + position = await self.ahk.image_search(str(pathlib.Path('testimage.png').absolute())) + assert position is not None + x, y = position + color = await self.ahk.pixel_get_color(x, y) + region_start = (x - 1, y - 1) + region_end = (x + 1, y + 1) + pos = await self.ahk.pixel_search(region_start, region_end, color) + assert pos is not None + x2, y2 = pos + assert abs(x2 - x) < 3 and abs(y2 - y) < 3 + + async def test_image_search_with_option(self): + if os.environ.get('CI'): + self.skipTest('This test does not work in GitHub Actions') + return + self._show_in_thread() + time.sleep(3) + self.im.save('testimage.png') + position = await self.ahk.image_search(str(pathlib.Path('testimage.png').absolute()), color_variation=50) + assert isinstance(position, tuple) + + # async def test_pixel_get_color(self): + # x, y = await self.ahk.pixel_search(0xFF0000) + # result = await self.ahk.pixel_get_color(x, y) + # self.assertIsNotNone(result) + # self.assertEqual(int(result, 16), 0xFF0000) + + +class TestScreenV2(TestScreen): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(version='v2') + self.before_windows = await self.ahk.list_windows() + self.im = Image.new('RGB', (20, 20)) + for coord in product(range(20), range(20)): + self.im.putpixel(coord, (255, 0, 0)) + time.sleep(1) diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py new file mode 100644 index 00000000..00190061 --- /dev/null +++ b/tests/_async/test_scripts.py @@ -0,0 +1,99 @@ +import pathlib +import subprocess +import tempfile +import time +import unittest.mock + +from ahk import AsyncAHK +from ahk import AsyncWindow + + +class TestScripts(unittest.IsolatedAsyncioTestCase): + win: AsyncWindow + + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + + async def asyncTearDown(self) -> None: + try: + self.ahk._transport._proc.kill() + except: + pass + subprocess.run(['TASKKILL', '/F', '/IM', 'notepad.exe'], capture_output=True) + time.sleep(0.2) + + async def test_script_missing_makes_tempfile(self): + with unittest.mock.patch('os.path.exists', new=unittest.mock.Mock(return_value=False)): + pos = await self.ahk.get_mouse_position() + path = pathlib.Path(self.ahk._transport._proc.runargs[-1]) + filename = path.name + assert filename.startswith('python-ahk-') + assert filename.endswith('.ahk') + assert isinstance(pos, tuple) and isinstance(pos[0], int) + + async def test_run_script_text(self): + assert await self.ahk.win_get(title='Untitled - Notepad') is None + script = 'FileAppend, foobar, *, UTF-8' + result = await self.ahk.run_script(script) + assert result == 'foobar' + + async def test_run_script_file(self): + assert await self.ahk.win_get(title='Untitled - Notepad') is None + with tempfile.NamedTemporaryFile(suffix='.ahk', mode='w', delete=False) as f: + f.write('FileAppend, foobar, *, UTF-8') + res = await self.ahk.run_script(f.name) + assert res == 'foobar' + + async def test_run_script_file_unicode(self): + assert await self.ahk.win_get(title='Untitled - Notepad') is None + subprocess.Popen('Notepad') + await self.ahk.win_wait(title='Untitled - Notepad', timeout=3) + with tempfile.NamedTemporaryFile(suffix='.ahk', mode='w', delete=False, encoding='utf-8') as f: + f.write('WinActivate, "Untitled - Notepad"\nSend א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ') + await self.ahk.run_script(f.name) + notepad = await self.ahk.win_wait(title='*Untitled - Notepad', timeout=3) + assert notepad is not None + text = await notepad.get_text() + assert 'א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ' in text + + async def test_run_script_nonblocking(self): + script = 'FileAppend, foo, *, UTF-8' + fut = await self.ahk.run_script(script, blocking=False) + assert await fut.result() == 'foo' + + +class TestScriptsV2(TestScripts): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(version='v2') + + async def test_run_script_text(self): + assert not await self.ahk.win_exists(title='Untitled - Notepad') + script = 'stdout := FileOpen("*", "w", "UTF-8")\nstdout.Write("foobar")\nstdout.Read(0)' + result = await self.ahk.run_script(script) + assert result == 'foobar' + + async def test_run_script_file(self): + assert not await self.ahk.win_exists(title='Untitled - Notepad') + with tempfile.NamedTemporaryFile(suffix='.ahk', mode='w', delete=False) as f: + f.write('stdout := FileOpen("*", "w", "UTF-8")\nstdout.Write("foobar")\nstdout.Read(0)') + res = await self.ahk.run_script(f.name) + assert res == 'foobar' + + async def test_run_script_file_unicode(self): + assert not await self.ahk.win_exists(title='Untitled - Notepad') + subprocess.Popen('Notepad') + await self.ahk.win_wait(title='Untitled - Notepad', timeout=3) + with tempfile.NamedTemporaryFile(suffix='.ahk', mode='w', delete=False, encoding='utf-8') as f: + f.write( + 'WinActivate "Untitled - Notepad"\nSend "א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ"' + ) + await self.ahk.run_script(f.name) + notepad = await self.ahk.win_wait(title='*Untitled - Notepad', timeout=3) + assert notepad is not None + text = await notepad.get_text() + assert 'א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ' in text + + async def test_run_script_nonblocking(self): + script = 'stdout := FileOpen("*", "w", "UTF-8")\nstdout.Write("foo")\nstdout.Read(0)' + fut = await self.ahk.run_script(script, blocking=False) + assert await fut.result() == 'foo' diff --git a/tests/_async/test_versioning.py b/tests/_async/test_versioning.py new file mode 100644 index 00000000..845d44ff --- /dev/null +++ b/tests/_async/test_versioning.py @@ -0,0 +1,45 @@ +import shutil +import subprocess +import time +from unittest import IsolatedAsyncioTestCase + +import pytest + +from ahk import AsyncAHK + +V2_EXECUTABLE = shutil.which('AutoHotkeyV2.exe') +V1_EXECUTABLE = shutil.which('AutoHotkey.exe') + + +class TestVersion(IsolatedAsyncioTestCase): + async def asyncTearDown(self) -> None: + subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey*.exe'], capture_output=True) + time.sleep(0.2) + + async def test_default_is_v1(self): + ahk = AsyncAHK() + assert await ahk.get_major_version() == 'v1' + + async def test_v1_explicit(self): + ahk = AsyncAHK(version='v1') + assert await ahk.get_major_version() == 'v1' + + async def test_v2_explicit(self): + ahk = AsyncAHK(version='v2') + assert await ahk.get_major_version() == 'v2' + + async def test_autodetect_v2(self): + ahk = AsyncAHK(executable_path=V2_EXECUTABLE) + assert await ahk.get_major_version() == 'v2' + + async def test_autodetect_v1(self): + ahk = AsyncAHK(executable_path=V1_EXECUTABLE) + assert await ahk.get_major_version() == 'v1' + + async def test_mismatch_autodetect_raises_error_v1_v2(self): + with pytest.raises(RuntimeError): + ahk = AsyncAHK(executable_path=V1_EXECUTABLE, version='v2') + + async def test_mismatch_autodetect_raises_error_v2_v1(self): + with pytest.raises(RuntimeError): + ahk = AsyncAHK(executable_path=V2_EXECUTABLE, version='v1') diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py new file mode 100644 index 00000000..aedd298a --- /dev/null +++ b/tests/_async/test_window.py @@ -0,0 +1,226 @@ +import asyncio +import os +import subprocess +import sys +import time +import tracemalloc +from unittest import IsolatedAsyncioTestCase + +import pytest + +import ahk + +tracemalloc.start() + +from ahk import AsyncAHK +from ahk import AsyncWindow + + +class TestWindowAsync(IsolatedAsyncioTestCase): + win: AsyncWindow + + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = await self.ahk.win_get(title='Untitled - Notepad') + self.assertIsNotNone(self.win) + + async def asyncTearDown(self) -> None: + try: + self.p.kill() + except: + pass + self.p.communicate() + self.ahk._transport._proc.kill() + subprocess.run(['TASKKILL', '/F', '/IM', 'notepad.exe'], capture_output=True) + time.sleep(0.2) + + async def test_exists(self): + self.assertTrue(await self.ahk.win_exists(title='Untitled - Notepad')) + self.assertTrue(await self.win.exists()) + + async def test_close(self): + await self.win.close() + time.sleep(0.2) + self.assertFalse(await self.win.exists()) + + async def test_win_get_returns_none_nonexistent(self): + win = await self.ahk.win_get(title='DOES NOT EXIST') + assert win is None + + async def test_exists_nonexistent_is_false(self): + assert await self.ahk.win_exists(title='DOES NOT EXIST') is False + + async def test_win_pid(self): + pid = await self.win.get_pid() + assert isinstance(pid, int) + + async def test_win_process_name(self): + process_name = await self.win.get_process_name() + assert process_name == 'notepad.exe' + + async def test_win_process_path(self): + process_path = await self.win.get_process_path() + assert 'notepad.exe' in process_path + + async def test_win_minmax(self): + minmax = await self.win.get_minmax() + assert minmax == 0 + + async def test_win_set_always_on_top(self): + assert await self.win.is_always_on_top() is False + await self.win.set_always_on_top('On') + assert await self.win.is_always_on_top() is True + + async def test_window_list_controls(self): + controls = await self.win.list_controls() + assert isinstance(controls, list) + assert len(controls) == 2 + + async def test_set_detect_hidden_windows(self): + non_hidden = await self.ahk.list_windows() + await self.ahk.set_detect_hidden_windows(True) + all_windows = await self.ahk.list_windows() + assert len(all_windows) > len(non_hidden) + + async def test_detect_hidden_windows_false_works(self): + await self.ahk.set_detect_hidden_windows(True) + all_windows = await self.ahk.list_windows() + await self.ahk.set_detect_hidden_windows(False) + non_hidden = await self.ahk.list_windows() + assert len(non_hidden) < len(all_windows) + + async def test_list_windows_hidden_false(self): + non_hidden = await self.ahk.list_windows() + all_windows = await self.ahk.list_windows(detect_hidden_windows=False) + assert len(non_hidden) == len(all_windows) + + async def test_list_windows_hidden(self): + non_hidden = await self.ahk.list_windows() + all_windows = await self.ahk.list_windows(detect_hidden_windows=True) + assert len(all_windows) > len(non_hidden) + + async def test_win_get_title(self): + title = await self.win.get_title() + assert title == 'Untitled - Notepad' + + async def test_win_get_idlast(self): + await self.ahk.win_set_bottom(title='Untitled - Notepad') + w = await self.ahk.win_get_idlast(title='Untitled - Notepad') + assert w == self.win + + async def test_win_get_count(self): + count = await self.ahk.win_get_count(title='Untitled - Notepad') + assert count == 1 + + # async def test_win_get_count_hidden(self): + # count = await self.ahk.win_get_count() + # all_count = await self.ahk.win_get_count(detect_hidden_windows=True) + # assert all_count > count + + async def test_win_exists(self): + assert await self.win.exists() + await self.win.close() + assert not await self.win.exists() + + async def test_win_set_title(self): + await self.win.set_title(new_title='Foo') + assert await self.win.get_title() == 'Foo' + + async def test_control_send_window(self): + await self.win.send('hello world', control='Edit1') + text = await self.win.get_text() + assert 'hello world' in text + + async def test_send_literal_comma(self): + await self.win.send('hello, world', control='Edit1') + text = await self.win.get_text() + assert 'hello, world' in text + + async def test_type_escape(self): + await self.win.activate() + await self.ahk.type('hello, world!') + time.sleep(0.2) + text = await self.win.get_text() + assert '!' in text + + async def test_send_input_manual_escapes(self): + await self.win.activate() + await self.ahk.send_input('Hello{Enter}World{!}') + time.sleep(0.4) + text = await self.win.get_text() + assert 'Hello\r\nWorld!' in text + + async def test_send_literal_tilde_n(self): + expected_text = '```nim\nimport std/strformat\n```' + await self.win.send(expected_text, control='Edit1') + text = await self.win.get_text() + assert '```nim' in text + assert '\nimport std/strformat' in text + assert '\n```' in text + + async def test_set_title_match_mode_and_speed(self): + await self.ahk.set_title_match_mode(('RegEx', 'Slow')) + speed = await self.ahk.get_title_match_speed() + mode = await self.ahk.get_title_match_mode() + assert mode == 'RegEx' + assert speed == 'Slow' + + async def test_set_title_match_mode(self): + await self.ahk.set_title_match_mode('RegEx') + mode = await self.ahk.get_title_match_mode() + assert mode == 'RegEx' + + async def test_set_title_match_speed(self): + await self.ahk.set_title_match_mode('Slow') + speed = await self.ahk.get_title_match_speed() + assert speed == 'Slow' + + async def test_control_send_from_control(self): + controls = await self.win.list_controls() + edit_control = controls[0] + await edit_control.send('hello world') + text = await self.win.get_text() + assert 'hello world' in text + + async def test_control_position(self): + controls = await self.win.list_controls() + edit_control = controls[0] + pos = await edit_control.get_position() + assert pos + + async def test_win_position(self): + pos = await self.win.get_position() + assert pos + + async def test_win_activate(self): + await self.win.activate() + w = await self.ahk.get_active_window() + assert w == self.win + + async def test_win_get_class(self): + assert await self.win.get_class() == 'Notepad' + + async def test_win_move(self): + await self.win.move(100, 100, width=300, height=300) + await self.win.move(200, 200, width=400, height=500) + time.sleep(1) + assert await self.win.get_position() == (200, 200, 400, 500) + + async def test_win_is_active(self): + await self.win.activate() + assert await self.win.is_active() is True + + +class TestWindowAsyncV2(TestWindowAsync): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(version='v2') + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = await self.ahk.win_get(title='Untitled - Notepad') + self.assertIsNotNone(self.win) + + async def test_win_get_returns_none_nonexistent(self): + with pytest.raises(ahk.message.AHKExecutionException): + win = await self.ahk.win_get(title='DOES NOT EXIST') diff --git a/tests/_sync/__init__.py b/tests/_sync/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/_sync/test_clipboard.py b/tests/_sync/test_clipboard.py new file mode 100644 index 00000000..fbb305ac --- /dev/null +++ b/tests/_sync/test_clipboard.py @@ -0,0 +1,44 @@ +import asyncio +import time +import unittest.mock + +from ahk import AHK + +sleep = time.sleep + + +class TestWindowAsync(unittest.TestCase): + def setUp(self) -> None: + self.ahk = AHK() + + def tearDown(self) -> None: + self.ahk._transport._proc.kill() + time.sleep(0.2) + + def test_clipboard(self): + self.ahk.set_clipboard('foo') + contents = self.ahk.get_clipboard() + assert contents == 'foo' + + def test_clipboard_all(self): + self.ahk.set_clipboard('Hello \N{EARTH GLOBE AMERICAS}') + data = self.ahk.get_clipboard_all() + self.ahk.set_clipboard('foo') + assert data != self.ahk.get_clipboard_all() + self.ahk.set_clipboard_all(data) + assert data == self.ahk.get_clipboard_all() + assert self.ahk.get_clipboard() == 'Hello \N{EARTH GLOBE AMERICAS}' + + def test_on_clipboard_change(self): + with unittest.mock.MagicMock(return_value=None) as m: + self.ahk.on_clipboard_change(m) + self.ahk.start_hotkeys() + self.ahk.set_clipboard('foo') + self.ahk.set_clipboard('bar') + sleep(1) + m.assert_called() + + +class TestWindowAsyncV2(TestWindowAsync): + def setUp(self) -> None: + self.ahk = AHK(version='v2') diff --git a/tests/_sync/test_extensions.py b/tests/_sync/test_extensions.py new file mode 100644 index 00000000..bf26443d --- /dev/null +++ b/tests/_sync/test_extensions.py @@ -0,0 +1,162 @@ +from __future__ import annotations + +import asyncio +import random +import string +import time +import unittest +from typing import Literal + +import pytest + +from ahk import AHK +from ahk.extensions import Extension + +sleep = time.sleep + +function_name = 'AHKDoSomething' + +ext_text = f'''\ +{function_name}(first, second) {{ + return FormatResponse("ahk.message.StringResponseMessage", Format("{{}} and {{}}", first, second)) +}} +''' + +math_function_name = 'SimpleMath' + +math_test = rf''' +{math_function_name}(lhs, rhs, operator) {{ + if (operator = "+") {{ + result := (lhs + rhs) + }} else if (operator = "*") {{ + result := (lhs * rhs) + }} else {{ ; invalid operator argument + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("Invalid operator: {{}}", operator)) + }} + return FormatResponse("ahk.message.IntegerResponseMessage", result) +}} +''' + +from ahk_json import JXON + +dependency_func_name = 'MyFunc' + +dependency_test_script = f'''\ +{dependency_func_name}(one, two) {{ + val := Array(one, two) + ret := Jxon_Dump(val) ; `Jxon_Dump` is provided by the dependent extension! + return FormatResponse("ahk_json.message.JsonResponseMessage", ret) ; this message type is also part of the extension +}} +''' + +dependency_extension = Extension(script_text=dependency_test_script, dependencies=[JXON], requires_autohotkey='v1') + + +@dependency_extension.register +def my_function(ahk, one: str, two: str) -> list[str]: + args = [one, two] + return ahk.function_call(dependency_func_name, args) + + +async_extension = Extension(script_text=ext_text) +async_math_extension = Extension(script_text=math_test) + + +@async_math_extension.register +def simple_math(ahk: AHK, lhs: int, rhs: int, operator: Literal['+', '*']) -> int: + assert isinstance(lhs, int) + assert isinstance(rhs, int) + args = [str(lhs), str(rhs), operator] # all args must be strings + result = ahk.function_call(math_function_name, args, blocking=True) + return result + + +@async_extension.register +def do_something(ahk, first: str, second: str) -> str: + res = ahk.function_call(function_name, [first, second]) + return res + + +class TestExtensions(unittest.TestCase): + def setUp(self) -> None: + self.ahk = AHK(extensions=[async_extension, dependency_extension]) + + def tearDown(self) -> None: + try: + self.ahk._transport._proc.kill() + except: + pass + time.sleep(0.2) + + def test_ext_explicit(self): + res = self.ahk.do_something('foo', 'bar') + assert res == 'foo and bar' + + def test_dep_extension(self): + res = self.ahk.my_function('foo', 'bar') + assert res == ['foo', 'bar'] + + +class TestExtensionsAuto(unittest.TestCase): + def setUp(self) -> None: + self.ahk = AHK(extensions='auto') + + def tearDown(self) -> None: + try: + self.ahk._transport._proc.kill() + except: + pass + time.sleep(0.2) + + def test_ext_auto(self): + res = self.ahk.do_something('foo', 'bar') + assert res == 'foo and bar' + + def test_math_example(self): + res = self.ahk.simple_math(1, 2, '+') + assert res == 3 + + def test_math_example_exception(self): + with pytest.raises(Exception): + res = self.ahk.simple_math(1, 2, 'x') + + +class TestNoExtensions(unittest.TestCase): + def setUp(self) -> None: + self.ahk = AHK() + self.ahk.get_mouse_position() # cause daemon to start + + def tearDown(self) -> None: + self.ahk._transport._proc.kill() + time.sleep(0.2) + + def test_ext_no_ext(self): + assert not hasattr(self.ahk, 'do_something') + + +class TestExtensionsV2(TestExtensions): + def setUp(self) -> None: + self.ahk = AHK(extensions=[async_extension], version='v2') + + def test_dep_extension(self): + pytest.skip('this test does not run on v2') + + +class TestExtensionsAutoV2(TestExtensionsAuto): + def setUp(self) -> None: + self.ahk = AHK(extensions='auto', version='v2') + + +class TestNoExtensionsV2(TestNoExtensions): + def setUp(self) -> None: + self.ahk = AHK(version='v2') + self.ahk.get_mouse_position() # cause daemon to start + + def test_dep_extension(self): + pytest.skip('this test does not run on v2') + + +class TestExtensionCompatibility(unittest.TestCase): + def test_ext_incompatible(self): + with pytest.raises(ValueError): + AHK(version='v2', extensions=[dependency_extension]) diff --git a/tests/_sync/test_gui.py b/tests/_sync/test_gui.py new file mode 100644 index 00000000..dac14845 --- /dev/null +++ b/tests/_sync/test_gui.py @@ -0,0 +1,39 @@ +import asyncio +import time +import unittest + +import pytest + +from ahk import AHK + +sleep = time.sleep + + +class TestGui(unittest.TestCase): + def setUp(self) -> None: + self.ahk = AHK() + + def tearDown(self) -> None: + self.ahk._transport._proc.kill() + time.sleep(0.2) + + def test_msg_box(self): + box = self.ahk.msg_box(text='hello', title='test', timeout=3, blocking=False) + sleep(1) + win = self.ahk.win_get(title='test') + assert win is not None + with pytest.raises(TimeoutError): + r = box.result() + + def test_input_box(self): + box = self.ahk.input_box(prompt='Question', title='prompt', timeout=3, blocking=False) + sleep(1) + win = self.ahk.win_get(title='prompt') + assert win is not None + with pytest.raises(TimeoutError): + r = box.result() + + +class TestGuiV2(TestGui): + def setUp(self) -> None: + self.ahk = AHK(version='v2') diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py new file mode 100644 index 00000000..7c1e9955 --- /dev/null +++ b/tests/_sync/test_hotkeys.py @@ -0,0 +1,70 @@ +import asyncio +import subprocess +import time +from unittest import mock +from unittest import TestCase + +from ahk import AHK +from ahk import Window + +sleep = time.sleep + + +class TestHotkeysAsync(TestCase): + win: Window + + def setUp(self) -> None: + self.ahk = AHK() + + def tearDown(self) -> None: + self.ahk.stop_hotkeys() + self.ahk._transport._proc.kill() + subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey*.exe'], capture_output=True) + time.sleep(0.2) + + def test_hotkey(self): + with mock.MagicMock(return_value=None) as m: + self.ahk.add_hotkey('a', callback=m) + self.ahk.start_hotkeys() + self.ahk.key_down('a') + self.ahk.key_press('a') + sleep(1) + m.assert_called() + + def test_hotkey_ex_handler(self): + def side_effect(): + raise Exception('oh no') + + with mock.MagicMock() as mock_cb, mock.MagicMock() as mock_ex_handler: + mock_cb.side_effect = side_effect + self.ahk.add_hotkey('a', callback=mock_cb, ex_handler=mock_ex_handler) + self.ahk.start_hotkeys() + self.ahk.key_down('a') + self.ahk.key_press('a') + sleep(1) + mock_ex_handler.assert_called() + + def test_remove_hotkey(self): + with mock.MagicMock(return_value=None) as m: + self.ahk.add_hotkey('a', callback=m) + self.ahk.start_hotkeys() + self.ahk.remove_hotkey('a') + self.ahk.key_down('a') + self.ahk.key_press('a') + sleep(1) + m.assert_not_called() + + def test_clear_hotkeys(self): + with mock.MagicMock(return_value=None) as m: + self.ahk.add_hotkey('a', callback=m) + self.ahk.start_hotkeys() + self.ahk.clear_hotkeys() + self.ahk.key_down('a') + self.ahk.key_press('a') + sleep(1) + m.assert_not_called() + + +class TestHotkeysAsyncV2(TestHotkeysAsync): + def setUp(self) -> None: + self.ahk = AHK(version='v2') diff --git a/tests/_sync/test_keys.py b/tests/_sync/test_keys.py new file mode 100644 index 00000000..b95fd932 --- /dev/null +++ b/tests/_sync/test_keys.py @@ -0,0 +1,113 @@ +import asyncio +import os +import subprocess +import sys +import time +import unittest.mock + +import pytest + +from ahk import AHK +from ahk import Window + +sleep = time.sleep + + +class TestKeysAsync(unittest.TestCase): + win: Window + + def setUp(self) -> None: + self.ahk = AHK() + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = self.ahk.win_get(title='Untitled - Notepad') + self.assertIsNotNone(self.win) + self.ahk.set_capslock_state('Off') + + def tearDown(self) -> None: + self.ahk.set_capslock_state('Off') + try: + self.p.kill() + except Exception: + pass + self.p.communicate() + self.ahk._transport._proc.kill() + time.sleep(0.2) + + def test_set_capslock(self): + self.ahk.set_capslock_state('On') + assert self.ahk.key_state('CapsLock', mode='T') == 1 + + def test_hotstring(self): + self.ahk.add_hotstring('btw', 'by the way') + self.ahk.start_hotkeys() + self.ahk.set_send_level(1) + self.win.activate() + self.ahk.send('btw ') + time.sleep(2) + + assert 'by the way' in self.win.get_text() + + def test_hotstring_cyrillic(self): + self.ahk.add_hotstring('тест', 'hello world') + self.ahk.start_hotkeys() + self.ahk.set_send_level(1) + self.win.activate() + self.ahk.send('тест ') + time.sleep(2) + + assert 'hello world' in self.win.get_text() + + def test_remove_hotstring(self): + self.ahk.add_hotstring('btw', 'by the way') + self.ahk.start_hotkeys() + self.ahk.set_send_level(1) + self.win.activate() + self.ahk.remove_hotstring('btw') + self.ahk.send('btw ') + time.sleep(2) + assert 'by the way' not in self.win.get_text() + + def test_clear_hotstrings(self): + self.ahk.add_hotstring('btw', 'by the way') + self.ahk.start_hotkeys() + self.ahk.set_send_level(1) + self.win.activate() + self.ahk.clear_hotstrings() + self.ahk.send('btw ') + time.sleep(2) + assert 'by the way' not in self.win.get_text() + + def test_hotstring_callback(self): + with unittest.mock.MagicMock(return_value=None) as m: + self.ahk.add_hotstring('btw', m) + self.ahk.start_hotkeys() + self.ahk.set_send_level(1) + self.win.activate() + self.ahk.send('btw ') + sleep(1) + m.assert_called() + + def test_key_wait(self): + res = self.ahk.key_wait('x', timeout=3, blocking=False) + self.ahk.set_send_level(1) + sleep(1) + self.ahk.key_down('x') + sleep(1) + self.ahk.key_up('x') + result = res.result() + assert result is True + + def test_key_wait_timeout(self): + res = self.ahk.key_wait('x', timeout=1) + assert res is False + + +class TestKeysAsyncV2(TestKeysAsync): + def setUp(self) -> None: + self.ahk = AHK(version='v2') + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = self.ahk.win_get(title='Untitled - Notepad') + self.assertIsNotNone(self.win) + self.ahk.set_capslock_state('Off') diff --git a/tests/_sync/test_mouse.py b/tests/_sync/test_mouse.py new file mode 100644 index 00000000..50e56167 --- /dev/null +++ b/tests/_sync/test_mouse.py @@ -0,0 +1,100 @@ +import asyncio +import os +import subprocess +import sys +import time +from unittest import TestCase + +from ahk import AHK +from ahk import Window + +sleep = time.sleep + + +class TestMouseAsync(TestCase): + win: Window + + def setUp(self) -> None: + self.ahk = AHK() + + def tearDown(self) -> None: + try: + self.ahk._transport._proc.kill() + except: + pass + time.sleep(0.2) + + def test_mouse_position(self) -> None: + pos = self.ahk.get_mouse_position() + assert isinstance(pos, tuple) + assert len(pos) == 2 + x, y = pos + assert isinstance(x, int) + assert isinstance(y, int) + + def test_mouse_move(self) -> None: + self.ahk.mouse_move(x=100, y=100) + pos = self.ahk.get_mouse_position() + assert pos == (100, 100) + self.ahk.mouse_move(x=200, y=200) + pos2 = self.ahk.get_mouse_position() + assert pos2 == (200, 200) + + def test_mouse_move_rel(self): + self.ahk.mouse_move(x=100, y=100) + sleep(0.5) + pos = self.ahk.get_mouse_position() + assert pos == (100, 100) + self.ahk.mouse_move(x=10, y=10, relative=True) + sleep(0.5) + pos2 = self.ahk.get_mouse_position() + x1, y1 = pos + x2, y2 = pos2 + assert abs(x1 - x2) == 10 + assert abs(y1 - y2) == 10 + + def test_mouse_move_nonblocking(self): + self.ahk.mouse_move(100, 100) + res = self.ahk.mouse_move(500, 500, speed=10, send_mode='Event', blocking=False) + current_pos = self.ahk.get_mouse_position() + sleep(0.1) + pos = self.ahk.get_mouse_position() + assert pos != current_pos + assert pos != (500, 500) + res.result() + + def test_mouse_drag(self): + self.ahk.mouse_move(x=100, y=100) + pos = self.ahk.get_mouse_position() + assert pos == (100, 100) + self.ahk.mouse_drag(x=200, y=200) + pos2 = self.ahk.get_mouse_position() + assert pos2 == (200, 200) + + def test_mouse_drag_relative(self): + self.ahk.mouse_move(x=100, y=100) + sleep(0.5) + pos = self.ahk.get_mouse_position() + assert pos == (100, 100) + self.ahk.mouse_drag(x=10, y=10, relative=True, button=1) + sleep(0.5) + pos2 = self.ahk.get_mouse_position() + x1, y1 = pos + x2, y2 = pos2 + assert abs(x1 - x2) == 10 + assert abs(y1 - y2) == 10 + + def test_coord_mode(self): + self.ahk.set_coord_mode(target='Mouse', relative_to='Client') + res = self.ahk.get_coord_mode(target='Mouse') + assert res == 'Client' + + def test_send_mode(self): + self.ahk.set_send_mode('InputThenPlay') + res = self.ahk.get_send_mode() + assert res == 'InputThenPlay' + + +class TestMouseAsyncV2(TestMouseAsync): + def setUp(self) -> None: + self.ahk = AHK(version='v2') diff --git a/tests/_sync/test_registry.py b/tests/_sync/test_registry.py new file mode 100644 index 00000000..c9d604f3 --- /dev/null +++ b/tests/_sync/test_registry.py @@ -0,0 +1,58 @@ +import pathlib +import subprocess +import tempfile +import time +import unittest.mock + +import pytest + +from ahk import AHK +from ahk import Window +from ahk.message import AHKExecutionException + + +class TestScripts(unittest.TestCase): + win: Window + + def setUp(self) -> None: + self.ahk = AHK() + + def tearDown(self) -> None: + try: + self.ahk._transport._proc.kill() + except: + pass + try: + self.ahk.reg_delete(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk') + except: + pass + time.sleep(0.2) + + def test_reg_read_write_default_value_name(self): + self.ahk.reg_write('REG_SZ', r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', value='test') + val = self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk') + assert val == 'test' + + def test_reg_write_explicit_value_name(self): + self.ahk.reg_write('REG_SZ', r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'foo', value='testfoo') + val = self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'foo') + assert val == 'testfoo' + + def test_reg_delete(self): + self.ahk.reg_write('REG_SZ', r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'bar', value='testbar') + self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'bar') + self.ahk.reg_delete(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'bar') + with pytest.raises(AHKExecutionException): + self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', 'bar') + + def test_reg_delete_default(self): + self.ahk.reg_write('REG_SZ', r'HKEY_CURRENT_USER\SOFTWARE\python-ahk', value='test') + self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk') + self.ahk.reg_delete(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk') + with pytest.raises(AHKExecutionException): + self.ahk.reg_read(r'HKEY_CURRENT_USER\SOFTWARE\python-ahk') + + +class TestScriptsV2(TestScripts): + def setUp(self) -> None: + self.ahk = AHK(version='v2') diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py new file mode 100644 index 00000000..45e4d5f0 --- /dev/null +++ b/tests/_sync/test_screen.py @@ -0,0 +1,92 @@ +import asyncio +import os +import pathlib +import threading +import time +from itertools import product +from unittest import TestCase + +from PIL import Image + +from ahk import AHK + + +class TestScreen(TestCase): + def setUp(self) -> None: + self.ahk = AHK() + self.before_windows = self.ahk.list_windows() + self.im = Image.new('RGB', (20, 20)) + for coord in product(range(20), range(20)): + self.im.putpixel(coord, (255, 0, 0)) + time.sleep(1) + + def tearDown(self): + for win in self.ahk.list_windows(): + if win not in self.before_windows: + win.kill() + self.ahk._transport._proc.kill() + time.sleep(0.2) + + # + # async def test_pixel_search(self): + # result = await self.ahk.pixel_search(0xFF0000) + # self.assertIsNotNone(result) + + def _show_in_thread(self): + t = threading.Thread(target=self.im.show) + t.start() + return t + + def test_image_search(self): + if os.environ.get('CI'): + self.skipTest('This test does not work in GitHub Actions') + return + self._show_in_thread() + time.sleep(3) + self.im.save('testimage.png') + position = self.ahk.image_search(str(pathlib.Path('testimage.png').absolute())) + assert isinstance(position, tuple) + + def test_pixel_search(self): + if os.environ.get('CI'): + self.skipTest('This test does not work in GitHub Actions') + return + self._show_in_thread() + time.sleep(3) + self.im.save('testimage.png') + position = self.ahk.image_search(str(pathlib.Path('testimage.png').absolute())) + assert position is not None + x, y = position + color = self.ahk.pixel_get_color(x, y) + region_start = (x - 1, y - 1) + region_end = (x + 1, y + 1) + pos = self.ahk.pixel_search(region_start, region_end, color) + assert pos is not None + x2, y2 = pos + assert abs(x2 - x) < 3 and abs(y2 - y) < 3 + + def test_image_search_with_option(self): + if os.environ.get('CI'): + self.skipTest('This test does not work in GitHub Actions') + return + self._show_in_thread() + time.sleep(3) + self.im.save('testimage.png') + position = self.ahk.image_search(str(pathlib.Path('testimage.png').absolute()), color_variation=50) + assert isinstance(position, tuple) + + # async def test_pixel_get_color(self): + # x, y = await self.ahk.pixel_search(0xFF0000) + # result = await self.ahk.pixel_get_color(x, y) + # self.assertIsNotNone(result) + # self.assertEqual(int(result, 16), 0xFF0000) + + +class TestScreenV2(TestScreen): + def setUp(self) -> None: + self.ahk = AHK(version='v2') + self.before_windows = self.ahk.list_windows() + self.im = Image.new('RGB', (20, 20)) + for coord in product(range(20), range(20)): + self.im.putpixel(coord, (255, 0, 0)) + time.sleep(1) diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py new file mode 100644 index 00000000..2bfec2f9 --- /dev/null +++ b/tests/_sync/test_scripts.py @@ -0,0 +1,99 @@ +import pathlib +import subprocess +import tempfile +import time +import unittest.mock + +from ahk import AHK +from ahk import Window + + +class TestScripts(unittest.TestCase): + win: Window + + def setUp(self) -> None: + self.ahk = AHK() + + def tearDown(self) -> None: + try: + self.ahk._transport._proc.kill() + except: + pass + subprocess.run(['TASKKILL', '/F', '/IM', 'notepad.exe'], capture_output=True) + time.sleep(0.2) + + def test_script_missing_makes_tempfile(self): + with unittest.mock.patch('os.path.exists', new=unittest.mock.Mock(return_value=False)): + pos = self.ahk.get_mouse_position() + path = pathlib.Path(self.ahk._transport._proc.runargs[-1]) + filename = path.name + assert filename.startswith('python-ahk-') + assert filename.endswith('.ahk') + assert isinstance(pos, tuple) and isinstance(pos[0], int) + + def test_run_script_text(self): + assert self.ahk.win_get(title='Untitled - Notepad') is None + script = 'FileAppend, foobar, *, UTF-8' + result = self.ahk.run_script(script) + assert result == 'foobar' + + def test_run_script_file(self): + assert self.ahk.win_get(title='Untitled - Notepad') is None + with tempfile.NamedTemporaryFile(suffix='.ahk', mode='w', delete=False) as f: + f.write('FileAppend, foobar, *, UTF-8') + res = self.ahk.run_script(f.name) + assert res == 'foobar' + + def test_run_script_file_unicode(self): + assert self.ahk.win_get(title='Untitled - Notepad') is None + subprocess.Popen('Notepad') + self.ahk.win_wait(title='Untitled - Notepad', timeout=3) + with tempfile.NamedTemporaryFile(suffix='.ahk', mode='w', delete=False, encoding='utf-8') as f: + f.write('WinActivate, "Untitled - Notepad"\nSend א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ') + self.ahk.run_script(f.name) + notepad = self.ahk.win_wait(title='*Untitled - Notepad', timeout=3) + assert notepad is not None + text = notepad.get_text() + assert 'א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ' in text + + def test_run_script_nonblocking(self): + script = 'FileAppend, foo, *, UTF-8' + fut = self.ahk.run_script(script, blocking=False) + assert fut.result() == 'foo' + + +class TestScriptsV2(TestScripts): + def setUp(self) -> None: + self.ahk = AHK(version='v2') + + def test_run_script_text(self): + assert not self.ahk.win_exists(title='Untitled - Notepad') + script = 'stdout := FileOpen("*", "w", "UTF-8")\nstdout.Write("foobar")\nstdout.Read(0)' + result = self.ahk.run_script(script) + assert result == 'foobar' + + def test_run_script_file(self): + assert not self.ahk.win_exists(title='Untitled - Notepad') + with tempfile.NamedTemporaryFile(suffix='.ahk', mode='w', delete=False) as f: + f.write('stdout := FileOpen("*", "w", "UTF-8")\nstdout.Write("foobar")\nstdout.Read(0)') + res = self.ahk.run_script(f.name) + assert res == 'foobar' + + def test_run_script_file_unicode(self): + assert not self.ahk.win_exists(title='Untitled - Notepad') + subprocess.Popen('Notepad') + self.ahk.win_wait(title='Untitled - Notepad', timeout=3) + with tempfile.NamedTemporaryFile(suffix='.ahk', mode='w', delete=False, encoding='utf-8') as f: + f.write( + 'WinActivate "Untitled - Notepad"\nSend "א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ"' + ) + self.ahk.run_script(f.name) + notepad = self.ahk.win_wait(title='*Untitled - Notepad', timeout=3) + assert notepad is not None + text = notepad.get_text() + assert 'א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ' in text + + def test_run_script_nonblocking(self): + script = 'stdout := FileOpen("*", "w", "UTF-8")\nstdout.Write("foo")\nstdout.Read(0)' + fut = self.ahk.run_script(script, blocking=False) + assert fut.result() == 'foo' diff --git a/tests/_sync/test_versioning.py b/tests/_sync/test_versioning.py new file mode 100644 index 00000000..0570b937 --- /dev/null +++ b/tests/_sync/test_versioning.py @@ -0,0 +1,45 @@ +import shutil +import subprocess +import time +from unittest import TestCase + +import pytest + +from ahk import AHK + +V2_EXECUTABLE = shutil.which('AutoHotkeyV2.exe') +V1_EXECUTABLE = shutil.which('AutoHotkey.exe') + + +class TestVersion(TestCase): + def tearDown(self) -> None: + subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey*.exe'], capture_output=True) + time.sleep(0.2) + + def test_default_is_v1(self): + ahk = AHK() + assert ahk.get_major_version() == 'v1' + + def test_v1_explicit(self): + ahk = AHK(version='v1') + assert ahk.get_major_version() == 'v1' + + def test_v2_explicit(self): + ahk = AHK(version='v2') + assert ahk.get_major_version() == 'v2' + + def test_autodetect_v2(self): + ahk = AHK(executable_path=V2_EXECUTABLE) + assert ahk.get_major_version() == 'v2' + + def test_autodetect_v1(self): + ahk = AHK(executable_path=V1_EXECUTABLE) + assert ahk.get_major_version() == 'v1' + + def test_mismatch_autodetect_raises_error_v1_v2(self): + with pytest.raises(RuntimeError): + ahk = AHK(executable_path=V1_EXECUTABLE, version='v2') + + def test_mismatch_autodetect_raises_error_v2_v1(self): + with pytest.raises(RuntimeError): + ahk = AHK(executable_path=V2_EXECUTABLE, version='v1') diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py new file mode 100644 index 00000000..0f74ad2a --- /dev/null +++ b/tests/_sync/test_window.py @@ -0,0 +1,226 @@ +import asyncio +import os +import subprocess +import sys +import time +import tracemalloc +from unittest import TestCase + +import pytest + +import ahk + +tracemalloc.start() + +from ahk import AHK +from ahk import Window + + +class TestWindowAsync(TestCase): + win: Window + + def setUp(self) -> None: + self.ahk = AHK() + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = self.ahk.win_get(title='Untitled - Notepad') + self.assertIsNotNone(self.win) + + def tearDown(self) -> None: + try: + self.p.kill() + except: + pass + self.p.communicate() + self.ahk._transport._proc.kill() + subprocess.run(['TASKKILL', '/F', '/IM', 'notepad.exe'], capture_output=True) + time.sleep(0.2) + + def test_exists(self): + self.assertTrue(self.ahk.win_exists(title='Untitled - Notepad')) + self.assertTrue(self.win.exists()) + + def test_close(self): + self.win.close() + time.sleep(0.2) + self.assertFalse(self.win.exists()) + + def test_win_get_returns_none_nonexistent(self): + win = self.ahk.win_get(title='DOES NOT EXIST') + assert win is None + + def test_exists_nonexistent_is_false(self): + assert self.ahk.win_exists(title='DOES NOT EXIST') is False + + def test_win_pid(self): + pid = self.win.get_pid() + assert isinstance(pid, int) + + def test_win_process_name(self): + process_name = self.win.get_process_name() + assert process_name == 'notepad.exe' + + def test_win_process_path(self): + process_path = self.win.get_process_path() + assert 'notepad.exe' in process_path + + def test_win_minmax(self): + minmax = self.win.get_minmax() + assert minmax == 0 + + def test_win_set_always_on_top(self): + assert self.win.is_always_on_top() is False + self.win.set_always_on_top('On') + assert self.win.is_always_on_top() is True + + def test_window_list_controls(self): + controls = self.win.list_controls() + assert isinstance(controls, list) + assert len(controls) == 2 + + def test_set_detect_hidden_windows(self): + non_hidden = self.ahk.list_windows() + self.ahk.set_detect_hidden_windows(True) + all_windows = self.ahk.list_windows() + assert len(all_windows) > len(non_hidden) + + def test_detect_hidden_windows_false_works(self): + self.ahk.set_detect_hidden_windows(True) + all_windows = self.ahk.list_windows() + self.ahk.set_detect_hidden_windows(False) + non_hidden = self.ahk.list_windows() + assert len(non_hidden) < len(all_windows) + + def test_list_windows_hidden_false(self): + non_hidden = self.ahk.list_windows() + all_windows = self.ahk.list_windows(detect_hidden_windows=False) + assert len(non_hidden) == len(all_windows) + + def test_list_windows_hidden(self): + non_hidden = self.ahk.list_windows() + all_windows = self.ahk.list_windows(detect_hidden_windows=True) + assert len(all_windows) > len(non_hidden) + + def test_win_get_title(self): + title = self.win.get_title() + assert title == 'Untitled - Notepad' + + def test_win_get_idlast(self): + self.ahk.win_set_bottom(title='Untitled - Notepad') + w = self.ahk.win_get_idlast(title='Untitled - Notepad') + assert w == self.win + + def test_win_get_count(self): + count = self.ahk.win_get_count(title='Untitled - Notepad') + assert count == 1 + + # async def test_win_get_count_hidden(self): + # count = await self.ahk.win_get_count() + # all_count = await self.ahk.win_get_count(detect_hidden_windows=True) + # assert all_count > count + + def test_win_exists(self): + assert self.win.exists() + self.win.close() + assert not self.win.exists() + + def test_win_set_title(self): + self.win.set_title(new_title='Foo') + assert self.win.get_title() == 'Foo' + + def test_control_send_window(self): + self.win.send('hello world', control='Edit1') + text = self.win.get_text() + assert 'hello world' in text + + def test_send_literal_comma(self): + self.win.send('hello, world', control='Edit1') + text = self.win.get_text() + assert 'hello, world' in text + + def test_type_escape(self): + self.win.activate() + self.ahk.type('hello, world!') + time.sleep(0.2) + text = self.win.get_text() + assert '!' in text + + def test_send_input_manual_escapes(self): + self.win.activate() + self.ahk.send_input('Hello{Enter}World{!}') + time.sleep(0.4) + text = self.win.get_text() + assert 'Hello\r\nWorld!' in text + + def test_send_literal_tilde_n(self): + expected_text = '```nim\nimport std/strformat\n```' + self.win.send(expected_text, control='Edit1') + text = self.win.get_text() + assert '```nim' in text + assert '\nimport std/strformat' in text + assert '\n```' in text + + def test_set_title_match_mode_and_speed(self): + self.ahk.set_title_match_mode(('RegEx', 'Slow')) + speed = self.ahk.get_title_match_speed() + mode = self.ahk.get_title_match_mode() + assert mode == 'RegEx' + assert speed == 'Slow' + + def test_set_title_match_mode(self): + self.ahk.set_title_match_mode('RegEx') + mode = self.ahk.get_title_match_mode() + assert mode == 'RegEx' + + def test_set_title_match_speed(self): + self.ahk.set_title_match_mode('Slow') + speed = self.ahk.get_title_match_speed() + assert speed == 'Slow' + + def test_control_send_from_control(self): + controls = self.win.list_controls() + edit_control = controls[0] + edit_control.send('hello world') + text = self.win.get_text() + assert 'hello world' in text + + def test_control_position(self): + controls = self.win.list_controls() + edit_control = controls[0] + pos = edit_control.get_position() + assert pos + + def test_win_position(self): + pos = self.win.get_position() + assert pos + + def test_win_activate(self): + self.win.activate() + w = self.ahk.get_active_window() + assert w == self.win + + def test_win_get_class(self): + assert self.win.get_class() == 'Notepad' + + def test_win_move(self): + self.win.move(100, 100, width=300, height=300) + self.win.move(200, 200, width=400, height=500) + time.sleep(1) + assert self.win.get_position() == (200, 200, 400, 500) + + def test_win_is_active(self): + self.win.activate() + assert self.win.is_active() is True + + +class TestWindowAsyncV2(TestWindowAsync): + def setUp(self) -> None: + self.ahk = AHK(version='v2') + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = self.ahk.win_get(title='Untitled - Notepad') + self.assertIsNotNone(self.win) + + def test_win_get_returns_none_nonexistent(self): + with pytest.raises(ahk.message.AHKExecutionException): + win = self.ahk.win_get(title='DOES NOT EXIST') diff --git a/tests/features/mouse_move.feature b/tests/features/mouse_move.feature deleted file mode 100644 index eef7584c..00000000 --- a/tests/features/mouse_move.feature +++ /dev/null @@ -1,8 +0,0 @@ -# Acceptance test of mouse features -Feature: Mouse functionality - - Scenario: Moving the mouse - Given the mouse position is (100, 100) - When I move the mouse DOWN 100px - Then I expect the mouse position to be (100, 200) - diff --git a/tests/features/steps/ahk_steps.py b/tests/features/steps/ahk_steps.py deleted file mode 100644 index f2125363..00000000 --- a/tests/features/steps/ahk_steps.py +++ /dev/null @@ -1,31 +0,0 @@ -from behave.matchers import RegexMatcher -from ahk import AHK -from behave_classy import step_impl_base - -Base = step_impl_base() - - -class AHKSteps(AHK, Base): - @Base.given(u'the mouse position is ({xpos:d}, {ypos:d})') - def given_mouse_move(self, xpos, ypos): - self.mouse_move(x=xpos, y=ypos) - - @Base.when(u'I move the mouse (UP|DOWN|LEFT|RIGHT) (\d+)px', matcher=RegexMatcher) - def move_direction(self, direction, px): - px = int(px) - if direction in ('UP', 'DOWN'): - axis = 'y' - else: - axis = 'x' - if direction in ('LEFT', 'UP'): - px = px * -1 - kwargs = {axis: px, 'relative': True} - self.mouse_move(**kwargs) - - @Base.then(u'I expect the mouse position to be ({xpos:d}, {ypos:d})') - def check_position(self, xpos, ypos): - x, y = self.mouse_position - assert x == xpos - assert y == ypos - -AHKSteps().register() \ No newline at end of file diff --git a/tests/message_test.py b/tests/message_test.py new file mode 100644 index 00000000..272229df --- /dev/null +++ b/tests/message_test.py @@ -0,0 +1,25 @@ +import pytest + +from ahk.message import BooleanResponseMessage +from ahk.message import CoordinateResponseMessage +from ahk.message import ExceptionResponseMessage +from ahk.message import IntegerResponseMessage +from ahk.message import NoValueResponseMessage +from ahk.message import RequestMessage +from ahk.message import ResponseMessage +from ahk.message import StringResponseMessage +from ahk.message import TupleResponseMessage +from ahk.message import WindowListResponseMessage + + +def test_novalue_response_raises_exception_when_sentinel_not_present() -> None: + msg = NoValueResponseMessage(raw_content=b'something else') + with pytest.raises(AssertionError): + msg.unpack() + return None + + +def test_novalue_response_sentinel() -> None: + msg = NoValueResponseMessage(raw_content=b'\xee\x80\x80') + assert msg.unpack() is None + return None diff --git a/tests/unittests/test_blocking_mouse.py b/tests/unittests/test_blocking_mouse.py deleted file mode 100644 index 1c9daaa0..00000000 --- a/tests/unittests/test_blocking_mouse.py +++ /dev/null @@ -1,25 +0,0 @@ -import time -import sys -import os -project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) -sys.path.insert(0, project_root) -from ahk import AHK - -ahk = AHK() - - -def test_blocking_blocks(): - ahk.mouse_position = (100, 100) - assert ahk.mouse_position == (100, 100) - ahk.mouse_move(10, 10, speed=30) - assert ahk.mouse_position == (10, 10) - - -def test_nonblocking_does_not_block(): - ahk.mouse_position = (100, 100) - assert ahk.mouse_position == (100, 100) - ahk.mouse_move(10, 10, speed=30, blocking=False) - assert ahk.mouse_position != (10, 10) - time.sleep(0.1) - assert ahk.mouse_position != (100, 100) # make sure it actually moved! - diff --git a/tests/unittests/test_executable_location.py b/tests/unittests/test_executable_location.py deleted file mode 100644 index 1892780b..00000000 --- a/tests/unittests/test_executable_location.py +++ /dev/null @@ -1,52 +0,0 @@ -import sys -import os -from unittest import mock -import pytest -project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) -sys.path.insert(0, project_root) -from ahk import AHK -from ahk.script import ExecutableNotFoundError - -def check_pwd(): - """ - Check the presence of autohotkey in present working directory. - This can inadvertently affect test results, so we skip if it's present. - This is due to the behavior of shutil.which on Windows - REF: https://docs.python.org/3/library/shutil.html#shutil.which - :return: - """ - for name in os.listdir(os.getcwd()): - if name.lower() == 'autohotkey.exe' or name.lower() == 'autohotkeya32': - pytest.skip('Skipping because autohotkey is in present directory (and will therefore always be found)') - - -def test_no_executable_raises_error(): - check_pwd() - with mock.patch.dict(os.environ, {'PATH': ''}, clear=True): - with pytest.raises(ExecutableNotFoundError): - AHK() - - -def test_executable_path_from_env(): - check_pwd() - with mock.patch.dict(os.environ, {'PATH': '', 'AHK_PATH': 'C:\\expected\\path\\to\\ahk.exe'}): - ahk = AHK() - assert ahk.executable_path == 'C:\\expected\\path\\to\\ahk.exe' - - -def test_env_var_takes_precedence_over_path(): - check_pwd() - actual_path = AHK().executable_path - ahk_location = os.path.abspath(os.path.dirname(actual_path)) - with mock.patch.dict(os.environ, {'PATH': ahk_location, 'AHK_PATH':'C:\\expected\\path\\to\\ahk.exe'}): - ahk = AHK() - assert ahk.executable_path == 'C:\\expected\\path\\to\\ahk.exe' - - -def test_executable_from_path(): - check_pwd() - actual_path = AHK().executable_path - ahk_location = os.path.abspath(os.path.dirname(actual_path)) - with mock.patch.dict(os.environ, {'PATH': ahk_location}, clear=True): - ahk = AHK() - assert ahk.executable_path == actual_path diff --git a/tests/unittests/test_keyboard.py b/tests/unittests/test_keyboard.py deleted file mode 100644 index 53f49ddb..00000000 --- a/tests/unittests/test_keyboard.py +++ /dev/null @@ -1,128 +0,0 @@ -import sys -import os -project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) -sys.path.insert(0, project_root) -from ahk import AHK -from unittest import TestCase -from itertools import product -import time, subprocess -from ahk.keys import KEYS, ALT, CTRL -import threading -class TestKeyboard(TestCase): - def setUp(self): - """ - Record all open windows - :return: - """ - self.ahk = AHK() - self.before_windows = self.ahk.windows() - self.p = subprocess.Popen('notepad') - time.sleep(1) - self.notepad = self.ahk.find_window(title=b'Untitled - Notepad') - - def tearDown(self): - self.p.terminate() - time.sleep(0.2) - - def test_window_send(self): - self.notepad.send('hello world') - time.sleep(1) - self.assertIn(b'hello world', self.notepad.text) - - def test_send(self): - self.notepad.activate() - self.ahk.send('hello world') - assert b'hello world' in self.notepad.text - - def test_send_key_mult(self): - self.notepad.send(KEYS.TAB * 4) - time.sleep(0.5) - self.assertEqual(self.notepad.text.count(b'\t'), 4, self.notepad.text) - - def test_send_input(self): - self.notepad.activate() - self.ahk.send_input('Hello World') - assert b'Hello World' in self.notepad.text - - def test_type(self): - self.notepad.activate() - self.ahk.type('Hello, World!') - assert b'Hello, World!' in self.notepad.text - -def a_down(): - time.sleep(0.5) - ahk = AHK() - ahk.key_down('a') - - -def release_a(): - time.sleep(0.5) - ahk = AHK() - ahk.key_up('a') - -def press_a(): - time.sleep(0.5) - ahk = AHK() - ahk.key_press('a') - -class TestKeys(TestCase): - def setUp(self): - self.ahk = AHK() - self.thread = None - self.hotkey = None - - def tearDown(self): - if self.thread is not None: - self.thread.join(timeout=3) - if self.ahk.key_state('a'): - self.ahk.key_up('a') - if self.ahk.key_down('Control'): - self.ahk.key_up('Control') - - notepad = self.ahk.find_window(title=b'Untitled - Notepad') - if notepad: - notepad.close() - - if self.hotkey and self.hotkey.running: - self.hotkey.stop() - - def test_key_wait_pressed(self): - start = time.time() - self.thread = threading.Thread(target=a_down) - self.thread.start() - self.ahk.key_wait('a', timeout=5) - end = time.time() - assert end - start < 5 - - def test_key_wait_released(self): - start = time.time() - a_down() - self.thread = threading.Thread(target=release_a) - self.thread.start() - self.ahk.key_wait('a', timeout=2) - - def test_key_wait_timeout(self): - self.assertRaises(TimeoutError, self.ahk.key_wait, 'f', timeout=1) - - def test_key_state_when_not_pressed(self): - self.assertFalse(self.ahk.key_state('a')) - - def test_key_state_pressed(self): - self.ahk.key_down('Control') - self.assertTrue(self.ahk.key_state('Control')) - - def test_hotkey(self): - self.hotkey = self.ahk.hotkey(hotkey='a', script='Run Notepad') - self.thread = threading.Thread(target=a_down) - self.thread.start() - self.hotkey.start() - time.sleep(1) - self.assertIsNotNone(self.ahk.find_window(title=b'Untitled - Notepad')) - - def test_hotkey_stop(self): - self.hotkey = self.ahk.hotkey(hotkey='a', script='Run Notepad') - self.hotkey.start() - assert self.hotkey.running - self.hotkey.stop() - self.ahk.key_press('a') - self.assertIsNone(self.ahk.find_window(title=b'Untitled - Notepad')) diff --git a/tests/unittests/test_screen.py b/tests/unittests/test_screen.py deleted file mode 100644 index b2e81d4d..00000000 --- a/tests/unittests/test_screen.py +++ /dev/null @@ -1,46 +0,0 @@ -import sys -import os -project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) -sys.path.insert(0, project_root) -from ahk import AHK -from unittest import TestCase -from PIL import Image -from itertools import product -import time - - -class TestScreen(TestCase): - def setUp(self): - """ - Record all open windows - :return: - """ - self.ahk = AHK() - self.before_windows = self.ahk.windows() - im = Image.new('RGB', (20, 20)) - for coord in product(range(20), range(20)): - im.putpixel(coord, (255, 0, 0)) - self.im = im - im.show() - time.sleep(2) - - def tearDown(self): - for win in self.ahk.windows(): - if win not in self.before_windows: - win.close() - break - - def test_pixel_search(self): - result = self.ahk.pixel_search(0xFF0000) - self.assertIsNotNone(result) - - def test_image_search(self): - self.im.save('testimage.png') - position = self.ahk.image_search('testimage.png') - self.assertIsNotNone(position) - - def test_pixel_get_color(self): - x, y = self.ahk.pixel_search(0xFF0000) - result = self.ahk.pixel_get_color(x, y) - self.assertIsNotNone(result) - self.assertEqual(int(result, 16), 0xFF0000) diff --git a/tests/unittests/test_win_get.py b/tests/unittests/test_win_get.py deleted file mode 100644 index 18c145a7..00000000 --- a/tests/unittests/test_win_get.py +++ /dev/null @@ -1,50 +0,0 @@ -import sys -import os -import time -project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) -sys.path.insert(0, project_root) -from ahk import AHK -from ahk.window import WindowNotFoundError -import pytest -import subprocess -ahk = AHK() - -def test_get_calculator(): - p = None - try: - p = subprocess.Popen('notepad') - time.sleep(1) # give notepad time to start up - win = ahk.win_get(title='Untitled - Notepad') - assert win - assert win.position - finally: - if p is not None: - p.terminate() - -def test_win_close(): - p = None - try: - p = subprocess.Popen('notepad') - time.sleep(1) # give notepad time to start up - win = ahk.win_get(title='Untitled - Notepad') - assert win - assert win.position - win.close() - with pytest.raises(WindowNotFoundError): - ahk.win_get(title='Untitled - Notepad').position - finally: - if p is not None: - p.terminate() - -def test_find_window_func(): - p = None - try: - p = subprocess.Popen('notepad') - time.sleep(1) # give notepad time to start up - def func(win): - return b'Untitled' in win.title - win = ahk.find_window(title=b'Untitled - Notepad') - assert win == ahk.find_window(func=func) - finally: - if p is not None: - p.terminate() diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..0bbe7be9 --- /dev/null +++ b/tox.ini @@ -0,0 +1,11 @@ +[tox] +envlist = py38,py39,py310,py311 + +[testenv] +deps = -rrequirements-dev.txt +passenv = + CI + PYTHONUNBUFFERED +commands = + coverage run -m pytest -s -vvv --reruns 8 --only-rerun AssertionError + mypy --strict ahk