From 3eaf07e5b2937dcda6416e2317b000eaf9271821 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 18 Jun 2019 02:44:39 -0700 Subject: [PATCH 001/588] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ca3768f7..c05201a2 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ A Python wrapper around AHK. ``` pip install ahk ``` +Requires Python 3.6+ See also [Non-Python dependencies](#deps) From ace0384d7c6f1a543598af4a6815cfeade7093d9 Mon Sep 17 00:00:00 2001 From: Nickiel12 <35903114+Nickiel12@users.noreply.github.com> Date: Sun, 7 Jul 2019 17:18:01 -0700 Subject: [PATCH 002/588] uniformalized empty lines in python code snippets --- README.md | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c05201a2..1eeb714b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,9 @@ See also [Non-Python dependencies](#deps) ```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) ``` @@ -40,6 +42,7 @@ Non-exhaustive examples of some of the functions available with this package. Fu ```python from ahk import AHK + ahk = AHK() ahk.mouse_position # tuple of mouse coordinates (x,y) @@ -56,6 +59,7 @@ ahk.mouse_drag(100, 100, relative=True) ```python from ahk import AHK + ahk = AHK() ahk.type('hello, world!') # sends keys, as if typed (performs ahk string escapes) @@ -78,7 +82,9 @@ 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 @@ -90,7 +96,9 @@ 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!) @@ -106,19 +114,21 @@ 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) + # 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 @@ -131,6 +141,7 @@ ahk.pixel_search('0x9d6346') # get coords of first pixel with specified color ```python from ahk import AHK + ahk = AHK() ahk.sound_play('C:\\path\\to\\sound.wav') # play an audio file @@ -147,8 +158,11 @@ For some functions, you can also opt for a non-blocking interface, so you can do ```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) @@ -177,7 +191,9 @@ You should see an output something like ```python from ahk import AHK + ahk = AHK() + ahk_script = 'Run Notepad' ahk.run_script(ahk_script, blocking=False) ``` @@ -228,7 +244,9 @@ 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) @@ -258,7 +276,9 @@ 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 @@ -311,6 +331,7 @@ Alternatively, you may provide the path in code ```python from ahk import AHK + ahk = AHK(executable_path='C:\\path\\to\\AutoHotkey.exe') ``` From 8d570fe022280cbf4dbb3752ed494b2bb8687148 Mon Sep 17 00:00:00 2001 From: Nickiel12 <35903114+Nickiel12@users.noreply.github.com> Date: Sun, 7 Jul 2019 17:53:52 -0700 Subject: [PATCH 003/588] added references and more comments --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1eeb714b..3c71922b 100644 --- a/README.md +++ b/README.md @@ -45,14 +45,14 @@ 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.mouse_position # returns a tuple of mouse coordinates (x,y) +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 primary mouse button ahk.double_click() -ahk.click(200, 200) # click a particular position -ahk.right_click() -ahk.mouse_drag(100, 100, relative=True) +ahk.click(200, 200) # moves the mouse to a particular position and clicks +ahk.right_click() # clicks the secondary mouse button +ahk.mouse_drag(100, 100, relative=True) # holds down primary button and moves the mouse ``` ## Keyboard @@ -130,6 +130,7 @@ 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 @@ -165,7 +166,7 @@ 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) +ahk.mouse_move(x=100, y=100, speed=30, blocking=False) # Move mouse to x,y over 30 seconds while True: # report mouse position while it moves t = round(time.time() - start, 4) position = ahk.mouse_position @@ -279,6 +280,8 @@ from ahk import ActionChain ac = ActionChain() +# An Action Chain doesn't perform the actions until perform() is called on the chain + ac.mouse_move(100, 100, speed=10) # nothing yet ac.sleep(1) # still nothing happening ac.mouse_move(500, 500, speed=10) # not yet @@ -315,6 +318,7 @@ debugging information. import logging logging.basicConfig(level=logging.DEBUG) ``` +(See the ![logging module documentation](https://docs.python.org/3/library/logging.html) for more information) Also note that, for now, errors with running AHK scripts will often pass silently. In the future, better error handling will be added. From a5bac01e0a15065cb9ffcb6ec344916b8d158985 Mon Sep 17 00:00:00 2001 From: Nickiel12 <35903114+Nickiel12@users.noreply.github.com> Date: Sun, 7 Jul 2019 20:12:03 -0700 Subject: [PATCH 004/588] Changed first snippet to more resemble gif --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1eeb714b..c4699550 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ from ahk import AHK ahk = AHK() -ahk.mouse_move(x=100, y=100, speed=10, blocking=True) # blocks until mouse finishes moving (the default) +ahk.mouse_move(x=100, y=100, speed=10, blocking=True) # Moves mouse to 100, 100 print(ahk.mouse_position) # (100, 100) ``` From b623c0a4193b3df105330f3a19af871b172c3236 Mon Sep 17 00:00:00 2001 From: Nickiel12 <35903114+Nickiel12@users.noreply.github.com> Date: Sun, 7 Jul 2019 20:29:53 -0700 Subject: [PATCH 005/588] Capitalization, grammer corrections, and spacing --- README.md | 85 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 81b67e0a..7c68f62b 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ A Python wrapper around AHK. pip install ahk ``` Requires Python 3.6+ +Requires ![AutoHotKey](https://www.autohotkey.com/) to be installed See also [Non-Python dependencies](#deps) @@ -25,8 +26,9 @@ from ahk import AHK ahk = AHK() -ahk.mouse_move(x=100, y=100, speed=10, blocking=True) # Moves mouse to 100, 100 -print(ahk.mouse_position) # (100, 100) +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/master/docs/_static/ahk.gif) @@ -45,11 +47,11 @@ from ahk import AHK ahk = AHK() -ahk.mouse_position # returns a tuple of mouse coordinates (x,y) +ahk.mouse_position # returns a tuple of mouse coordinates (x, y) 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 primary mouse button -ahk.double_click() +ahk.click() # click the primary mouse button +ahk.double_click() # clicks the primary mouse button twice ahk.click(200, 200) # moves the mouse to a particular position and clicks ahk.right_click() # clicks the secondary mouse button ahk.mouse_drag(100, 100, relative=True) # holds down primary button and moves the mouse @@ -62,14 +64,15 @@ from ahk import AHK ahk = AHK() -ahk.type('hello, world!') # sends keys, as if typed (performs ahk string escapes) +ahk.type('hello, world!') # Send 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 +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.key_wait('a', timeout=3) # Wait up to 3 seconds for the "a" key to be pressed. NOTE: This throws + # a TimeoutError if the key isn't pressed within the timeout window ``` ## Windows @@ -85,11 +88,11 @@ from ahk.window import Window ahk = AHK() -win = ahk.active_window # get the active window +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_mouse_position(ahk) # the window under the mouse cursor win = Window.from_pid('20366') # by process ID ``` @@ -99,22 +102,22 @@ 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!) +ahk.run_script('Run Notepad') # Open notepad +win = ahk.find_window(title=b'Untitled - Notepad') # Find the opened window +win.send('hello') # Send keys directly to the 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() +win.activate() # Give the window focus +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.always_on_top = True # Make the window always on top +win.close() # Close the window for window in ahk.windows(): print(window.title) - # some more attributes + # Some more attributes print(window.text) print(window.rect) # (x, y, width, height) print(window.id) # ahk_id @@ -129,13 +132,13 @@ from ahk import AHK ahk = AHK() -ahk.image_search('C:\\path\\to\\image.jpg') # find an image on screen +ahk.image_search('C:\\path\\to\\image.jpg') # Find an image on screen -# find image within a boundary 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('0x9d6346') # get coords of first pixel with specified color +ahk.pixel_get_color(100, 100) # Get color of pixel located at coords (100, 100) +ahk.pixel_search('0x9d6346') # Get coords of the first pixel with specified color ``` ## Sound @@ -145,12 +148,12 @@ 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 +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 ``` ## non-blocking modes @@ -164,9 +167,9 @@ from ahk import AHK ahk = AHK() -ahk.mouse_position = (200, 200) # moves the mouse instantly to the position +ahk.mouse_position = (200, 200) # Moves the mouse instantly to the start position start = time.time() -ahk.mouse_move(x=100, y=100, speed=30, blocking=False) # Move mouse to x,y over 30 seconds +ahk.mouse_move(x=100, y=100, speed=30, blocking=False) # Move mouse to x, y over speed time while True: # report mouse position while it moves t = round(time.time() - start, 4) position = ahk.mouse_position @@ -248,10 +251,10 @@ from ahk import AHK, Hotkey ahk = AHK() -key_combo = '#n' -script = 'Run Notepad' -hotkey = Hotkey(ahk, key_combo, script) -hotkey.start() # listener process activated +key_combo = '#n' # Define an AutoHotKey key combonation +script = 'Run Notepad' # Define an ahk script +hotkey = Hotkey(ahk, key_combo, script) # Create Hotkey +hotkey.start() # Start listening for hotkey ``` At this point, the hotkey is active. If you press ![Windows Key][winlogo] + n, the script `Run Notepad` will execute. From e8bd9be082b3b7253785a00eb2f1426872ff3f42 Mon Sep 17 00:00:00 2001 From: Nickiel12 <35903114+Nickiel12@users.noreply.github.com> Date: Sun, 7 Jul 2019 20:45:08 -0700 Subject: [PATCH 006/588] Fixed the first columns capitalization --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7c68f62b..adbfd219 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ 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=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) ``` @@ -47,14 +47,14 @@ from ahk import AHK ahk = AHK() -ahk.mouse_position # returns a tuple of mouse coordinates (x, y) -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.double_click() # clicks the primary mouse button twice -ahk.click(200, 200) # moves the mouse to a particular position and clicks -ahk.right_click() # clicks the secondary mouse button -ahk.mouse_drag(100, 100, relative=True) # holds down primary button and moves the mouse +ahk.mouse_position # Returns a tuple of mouse coordinates (x, y) +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.double_click() # Clicks the primary mouse button twice +ahk.click(200, 200) # Moves the mouse to a particular position and clicks +ahk.right_click() # Clicks the secondary mouse button +ahk.mouse_drag(100, 100, relative=True) # Holds down primary button and moves the mouse ``` ## Keyboard From cc15de6c0fe987a0e6e64b4d6b30dd22d60b4219 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 8 Jul 2019 03:49:48 -0700 Subject: [PATCH 007/588] nitpick changes --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index adbfd219..00659758 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ ahk = AHK() ahk.mouse_position = (200, 200) # Moves the mouse instantly to the start position start = time.time() -ahk.mouse_move(x=100, y=100, speed=30, blocking=False) # Move mouse to x, y over speed 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 @@ -251,7 +251,7 @@ from ahk import AHK, Hotkey ahk = AHK() -key_combo = '#n' # Define an AutoHotKey key combonation +key_combo = '#n' # Define an AutoHotkey key combonation script = 'Run Notepad' # Define an ahk script hotkey = Hotkey(ahk, key_combo, script) # Create Hotkey hotkey.start() # Start listening for hotkey @@ -267,6 +267,7 @@ To stop the hotkey call the `stop()` method. hotkey.stop() ``` +See also the [relevant AHK documentation](https://www.autohotkey.com/docs/Hotkeys.htm) ### ActionChain From 5334d87318f11cd3cd2366e361bb01ea4f3cf22e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 8 Jul 2019 03:57:53 -0700 Subject: [PATCH 008/588] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 00659758..aeac4e4c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ A Python wrapper around AHK. pip install ahk ``` Requires Python 3.6+ -Requires ![AutoHotKey](https://www.autohotkey.com/) to be installed See also [Non-Python dependencies](#deps) From 7ba7118dffede49d025b2e09e17e535b54107e7c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 8 Jul 2019 03:58:14 -0700 Subject: [PATCH 009/588] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aeac4e4c..20a893a8 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,7 @@ debugging information. import logging logging.basicConfig(level=logging.DEBUG) ``` -(See the ![logging module documentation](https://docs.python.org/3/library/logging.html) for more information) +(See the [logging module documentation](https://docs.python.org/3/library/logging.html) for more information) Also note that, for now, errors with running AHK scripts will often pass silently. In the future, better error handling will be added. From 8b5415dcb03f7aba1e24b921d2f18ce38bf55988 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Jul 2019 04:53:42 -0700 Subject: [PATCH 010/588] Fix CI for external PRs (#49) Don't try to send coveralls report when required secret is not available to the PR. --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 7add99a2..3acbaaa9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -28,7 +28,8 @@ test_script: on_finish: - cmd: | venv\Scripts\activate.bat - python -m coveralls + IF DEFINED COVERALLS_REPO_TOKEN (python -m coverage) ELSE (echo skipping coveralls report for external pr) + cache: - ahk_install.exe -> appveyor.yml From 2bc42490340f9d2b9e403d594be3c6ad9f5cf7e6 Mon Sep 17 00:00:00 2001 From: Nickiel12 <35903114+Nickiel12@users.noreply.github.com> Date: Fri, 26 Jul 2019 15:55:36 -0700 Subject: [PATCH 011/588] Added a base gitignore for basic python files --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e928de15 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class \ No newline at end of file From 947cd607c0ff52d9b321e3d6c42227d573afc5be Mon Sep 17 00:00:00 2001 From: Sean Patiag Date: Mon, 12 Aug 2019 01:30:22 -0700 Subject: [PATCH 012/588] Fix empty string bug from send/send_raw --- ahk/keyboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/keyboard.py b/ahk/keyboard.py index 5a5b9abf..40d0cf5c 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -121,7 +121,7 @@ def send(self, s, raw=False, delay=None): :return: """ script = self.render_template('keyboard/send.ahk', s=s, raw=raw, delay=delay) - return self.run_script(script) + self.run_script(script) def send_raw(self, s, delay=None): """ From c4fb4ac70e3a3dbf4c41351df31dfa73160c9c73 Mon Sep 17 00:00:00 2001 From: Sean Patiag Date: Thu, 8 Aug 2019 06:02:16 -0700 Subject: [PATCH 013/588] Add missing image search options --- ahk/screen.py | 19 ++++++++++++++++--- ahk/templates/screen/image_search.ahk | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ahk/screen.py b/ahk/screen.py index afba1fe0..1c8bf0b0 100644 --- a/ahk/screen.py +++ b/ahk/screen.py @@ -6,8 +6,9 @@ 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]: + color_variation: int=None, coord_mode: str='Screen', + scale_height: int=None, scale_width: int=None, + transparent: str=None, icon: int=None) -> Union[Tuple[int, int], None]: """ `AutoHotkey ImageSearch reference`_ @@ -21,9 +22,18 @@ def image_search(self, image_path: str, :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 color_variation: Shades of variation (up or down) for the intensity of RGB for each pixel. Equivalent of + ``*n`` option. Defaults to 0. + :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 + :param transparent: Specific color in the image that will be ignored during the search. Pixels with the exact + color given will match any color. Can be used with color names e.g. (Black, Purple, Yellow) found + at https://https://www.autohotkey.com/docs/commands/Progress.htm#colors or hexadecimal values e.g. + (0xFFFFAA, 05FA15, 632511). Equivalent of ``*TransN`` option + + :param icon: Number of the icon group to use. Equivalent of ``*Icon`` option :return: coordinates of the upper-left pixel of where the image was found on the screen; ``None`` if the image was not found @@ -48,7 +58,10 @@ def image_search(self, image_path: str, coord_mode=coord_mode, scale_width=scale_width, scale_height=scale_height, - image_path=image_path) + image_path=image_path, + color_variation=color_variation, + transparent=transparent, + icon=icon) resp = self.run_script(script) try: return ast.literal_eval(resp) diff --git a/ahk/templates/screen/image_search.ahk b/ahk/templates/screen/image_search.ahk index 421ada5c..09deb624 100644 --- a/ahk/templates/screen/image_search.ahk +++ b/ahk/templates/screen/image_search.ahk @@ -1,7 +1,7 @@ {% 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 }} +ImageSearch, xpos, ypos, {{ x1 }}, {{ y1 }}, {{ x2 }}, {{ y2 }},{% if icon %} *Icon{{ icon }}{% endif %}{% if color_variation %} *{{ color_variation }}{% endif %}{% if transparent %} *Trans{{ transparent }}{% endif %}{% if scale_width %} *w{{ scale_width}} *h{{ scale_height }}{% endif %} {{ image_path }} s .= Format("({}, {})", xpos, ypos) FileAppend, %s%, * {% endblock body %} From fda64f2d03b05531e1287b3bfa63d52c768cb265 Mon Sep 17 00:00:00 2001 From: Sean Patiag Date: Mon, 12 Aug 2019 18:15:57 -0700 Subject: [PATCH 014/588] Use options list Put individual options into a list to be more consistent with other functions and to reduce clutter in the image_seach.ahk template. --- ahk/screen.py | 17 ++++++++++++----- ahk/templates/screen/image_search.ahk | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ahk/screen.py b/ahk/screen.py index 1c8bf0b0..056612d9 100644 --- a/ahk/screen.py +++ b/ahk/screen.py @@ -48,6 +48,17 @@ def image_search(self, image_path: str, elif scale_width and not scale_height: scale_height = -1 + options = [] + if icon: + options.append(f'Icon{icon}') + if color_variation: + options.append(color_variation) + if transparent: + 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 @@ -56,12 +67,8 @@ def image_search(self, image_path: str, 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, - color_variation=color_variation, - transparent=transparent, - icon=icon) + options=options) resp = self.run_script(script) try: return ast.literal_eval(resp) diff --git a/ahk/templates/screen/image_search.ahk b/ahk/templates/screen/image_search.ahk index 09deb624..1b7a3de8 100644 --- a/ahk/templates/screen/image_search.ahk +++ b/ahk/templates/screen/image_search.ahk @@ -1,7 +1,7 @@ {% extends "base.ahk" %} {% block body %} CoordMode Pixel, {{ coord_mode }} -ImageSearch, xpos, ypos, {{ x1 }}, {{ y1 }}, {{ x2 }}, {{ y2 }},{% if icon %} *Icon{{ icon }}{% endif %}{% if color_variation %} *{{ color_variation }}{% endif %}{% if transparent %} *Trans{{ transparent }}{% endif %}{% if scale_width %} *w{{ scale_width}} *h{{ scale_height }}{% endif %} {{ image_path }} +ImageSearch, xpos, ypos, {{ x1 }}, {{ y1 }}, {{ x2 }}, {{ y2 }}, {% if options %}{% for option in options %}*{{ option }} {% endfor %}{% endif %}{{ image_path }} s .= Format("({}, {})", xpos, ypos) FileAppend, %s%, * {% endblock body %} From 95da64d09ff452bbf47d33561d333a8c1fe53a4f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 14 Aug 2019 14:23:18 -0700 Subject: [PATCH 015/588] version 0.6.2 :package: --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 20c6f9f3..8006f8aa 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.6.1', + version='0.6.2', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From ce877d6c43785896ac9b03096a453933aa3d0456 Mon Sep 17 00:00:00 2001 From: Pestitschek Date: Thu, 21 Nov 2019 01:48:46 -0300 Subject: [PATCH 016/588] added method find_window_by_class (and its generator) + class_name property for obj Window + .ahk template script WinGetClass --- ahk/templates/window/win_get_class.ahk | 5 +++++ ahk/window.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 ahk/templates/window/win_get_class.ahk diff --git a/ahk/templates/window/win_get_class.ahk b/ahk/templates/window/win_get_class.ahk new file mode 100644 index 00000000..94d7b5f5 --- /dev/null +++ b/ahk/templates/window/win_get_class.ahk @@ -0,0 +1,5 @@ +{% extends "base.ahk" %} +{% block body %} +WinGetClass, text, ahk_id {{ win.id }} +FileAppend, %text%, * +{% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index 7d382610..8b7fc57c 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -208,6 +208,14 @@ def title(self): return result.stdout.decode(encoding=self.encoding) return result.stdout + @property + def class_name(self): + script = self._render_template('window/win_get_class.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') @@ -353,3 +361,11 @@ def find_windows_by_text(self, text, exact=False): def find_window_by_text(self, *args, **kwargs): with suppress(StopIteration): return next(self.find_windows_by_text(*args, **kwargs)) + + def find_windows_by_class(self, class_name, exact=False): + for window in self.find_windows(class_name=class_name, exact=exact): + yield window + + def find_window_by_class(self, *args, **kwargs): + with suppress(StopIteration): + return next(self.find_windows_by_class(*args, **kwargs)) From 64094157307901fbc7390d36abcca3f119d02957 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 3 Jan 2020 20:45:44 -0800 Subject: [PATCH 017/588] add pypi badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 20a893a8..886fcefb 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ 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) +[![Coverage](https://coveralls.io/repos/github/spyoungtech/ahk/badge.svg?branch=master)](https://coveralls.io/github/spyoungtech/ahk?branch=master) +![PyPI - Downloads](https://img.shields.io/pypi/dm/ahk) # Installation From 0cb164ae742795d329635989d69ea6c98dda1125 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 21 Feb 2020 02:18:23 -0800 Subject: [PATCH 018/588] Executable path safety (#70) * add more safeguards when misconfiguring executable_path --- ahk/script.py | 41 ++++++++++++++------- tests/unittests/test_executable_location.py | 19 ++++++++-- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/ahk/script.py b/ahk/script.py index 2bb6eb65..62824d8c 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -1,5 +1,6 @@ import os import subprocess +import warnings from shutil import which from ahk.utils import make_logger from ahk.directives import Persistent @@ -12,8 +13,31 @@ class ExecutableNotFoundError(EnvironmentError): pass +def _resolve_executable_path(executable_path: str = ''): + 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.' + ) + if not os.path.exists(executable_path): + raise ExecutableNotFoundError(f"executable_path does not seems to exist: '{executable_path}' not found") + if os.path.isdir(executable_path): + raise ExecutableNotFoundError( + 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" + ) + 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 + + class ScriptEngine(object): - def __init__(self, executable_path: str='', **kwargs): + 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 @@ -21,19 +45,10 @@ def __init__(self, executable_path: str='', **kwargs): :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 + self.executable_path = _resolve_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 - ) + 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: diff --git a/tests/unittests/test_executable_location.py b/tests/unittests/test_executable_location.py index 1892780b..aca49821 100644 --- a/tests/unittests/test_executable_location.py +++ b/tests/unittests/test_executable_location.py @@ -29,18 +29,20 @@ def test_no_executable_raises_error(): def test_executable_path_from_env(): check_pwd() - with mock.patch.dict(os.environ, {'PATH': '', 'AHK_PATH': 'C:\\expected\\path\\to\\ahk.exe'}): + with mock.patch.dict(os.environ, {'PATH': '', 'AHK_PATH': sys.executable}): + # using sys.executable as a standin for any file with exe extension ahk = AHK() - assert ahk.executable_path == 'C:\\expected\\path\\to\\ahk.exe' + assert ahk.executable_path == sys.executable 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'}): + with mock.patch.dict(os.environ, {'PATH': ahk_location, 'AHK_PATH': sys.executable}): + # using sys.executable as a standin for any file with exe extension ahk = AHK() - assert ahk.executable_path == 'C:\\expected\\path\\to\\ahk.exe' + assert ahk.executable_path == sys.executable def test_executable_from_path(): @@ -50,3 +52,12 @@ def test_executable_from_path(): with mock.patch.dict(os.environ, {'PATH': ahk_location}, clear=True): ahk = AHK() assert ahk.executable_path == actual_path + +def test_executable_as_dir_raises_error(): + some_dir = os.path.abspath(os.path.dirname(__file__)) + with pytest.raises(ExecutableNotFoundError): + AHK(executable_path=some_dir) + +def test_file_without_exe_extension_warns(): + with pytest.warns(UserWarning): + AHK(executable_path=os.path.abspath(__file__)) \ No newline at end of file From 7ba7076f14e969708eaf0117357e2151b7b7338d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 21 Feb 2020 02:19:24 -0800 Subject: [PATCH 019/588] version 0.7.0 :package: bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8006f8aa..50160ac2 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.6.2', + version='0.7.0', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From d9bd99407bda366eb31067cffa5258a745801bb2 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Fri, 21 Feb 2020 02:41:04 -0800 Subject: [PATCH 020/588] restore coveralls command --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 3acbaaa9..e5210322 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -28,7 +28,7 @@ test_script: on_finish: - cmd: | venv\Scripts\activate.bat - IF DEFINED COVERALLS_REPO_TOKEN (python -m coverage) ELSE (echo skipping coveralls report for external pr) + IF DEFINED COVERALLS_REPO_TOKEN (python -m coveralls) ELSE (echo skipping coveralls report for external pr) cache: From 99976b1ae1dbda071756ccf66268f3b063a4e9ae Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 00:15:30 +0300 Subject: [PATCH 021/588] =?UTF-8?q?=E2=9E=95=20Basic=20windows=20controls?= =?UTF-8?q?=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hide, minimize, restore, show --- ahk/templates/window/win_activate.ahk | 2 +- ahk/templates/window/win_hide.ahk | 4 ++++ ahk/templates/window/win_minimize.ahk | 4 ++++ ahk/templates/window/win_restore.ahk | 4 ++++ ahk/templates/window/win_show.ahk | 4 ++++ ahk/window.py | 26 +++++++++++++++++++++++--- 6 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 ahk/templates/window/win_hide.ahk create mode 100644 ahk/templates/window/win_minimize.ahk create mode 100644 ahk/templates/window/win_restore.ahk create mode 100644 ahk/templates/window/win_show.ahk diff --git a/ahk/templates/window/win_activate.ahk b/ahk/templates/window/win_activate.ahk index d39a2bf5..3382a1b9 100644 --- a/ahk/templates/window/win_activate.ahk +++ b/ahk/templates/window/win_activate.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} WinActivate, ahk_id {{ win.id }} -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/window/win_hide.ahk b/ahk/templates/window/win_hide.ahk new file mode 100644 index 00000000..d4e3fda3 --- /dev/null +++ b/ahk/templates/window/win_hide.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +WinHide, ahk_id {{ win.id }} +{% endblock body %} diff --git a/ahk/templates/window/win_minimize.ahk b/ahk/templates/window/win_minimize.ahk new file mode 100644 index 00000000..4791d318 --- /dev/null +++ b/ahk/templates/window/win_minimize.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +WinMinimize, ahk_id {{ win.id }} +{% endblock body %} diff --git a/ahk/templates/window/win_restore.ahk b/ahk/templates/window/win_restore.ahk new file mode 100644 index 00000000..b27ccb59 --- /dev/null +++ b/ahk/templates/window/win_restore.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +WinRestore, ahk_id {{ win.id }} +{% endblock body %} diff --git a/ahk/templates/window/win_show.ahk b/ahk/templates/window/win_show.ahk new file mode 100644 index 00000000..0d47bf25 --- /dev/null +++ b/ahk/templates/window/win_show.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +WinShow, ahk_id {{ win.id }} +{% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index 8b7fc57c..465df844 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -239,7 +239,8 @@ def always_on_top(self, value): 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') + 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) @@ -263,6 +264,22 @@ def activate(self): script = self._render_template('window/win_activate.ahk') self.engine.run_script(script) + def hide(self): + script = self._render_template('window/win_hide.ahk') + self.engine.run_script(script) + + def minimize(self): + script = self._render_template('window/win_minimize.ahk') + self.engine.run_script(script) + + def show(self): + script = self._render_template('window/win_show.ahk') + self.engine.run_script(script) + + def restore(self): + script = self._render_template('window/win_restore.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) @@ -275,7 +292,8 @@ def send(self, keys, delay=None, raw=False, blocking=False, escape=False): """ if escape: keys = escape_sequence_replace(keys) - script = self._render_template('window/win_send.ahk', keys=keys, raw=raw, delay=delay, blocking=blocking) + 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): @@ -304,7 +322,8 @@ def win_get(self, title='', text='', exclude_title='', exclude_text='', encoding 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) + script = self.render_template( + 'window/set.ahk', subcommand=subcommand, *args, blocking=blocking) self.run_script(script, blocking=blocking) @property @@ -330,6 +349,7 @@ def windows(self): 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: From 29644191695dfee8138e3a19c5f0071aba891125 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 21 Feb 2020 23:29:05 -0800 Subject: [PATCH 022/588] add new window commands to readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 886fcefb..c88d1454 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,10 @@ 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.minimize() # Minimize the window +win.restore() # un-minimize it +win.hide() # make the window hidden +win.show() # unhide the window win.always_on_top = True # Make the window always on top win.close() # Close the window From 26a4271b8acb1cb28cf1fec10dd9e705720639c3 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 18:08:47 +0300 Subject: [PATCH 023/588] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20Base=20method?= =?UTF-8?q?=20template=20created?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/templates/window/base_command.ahk | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ahk/templates/window/base_command.ahk diff --git a/ahk/templates/window/base_command.ahk b/ahk/templates/window/base_command.ahk new file mode 100644 index 00000000..62ed8697 --- /dev/null +++ b/ahk/templates/window/base_command.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +{{ command }}, ahk_id {{ win.id }}{% if seconds_to_wait %}, {{ seconds_to_wait }}{% endif %}{% if win._exclude_title %}, {{ win._exclude_title }}{% endif %}{% if win._exclude_text %}, {{ win._exclude_text }}{% endif %} +{% endblock body %} From 2556cad94f4328f9f718d3765f832379ef777ad3 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 18:16:32 +0300 Subject: [PATCH 024/588] =?UTF-8?q?=F0=9F=94=80=20All=20function=20that=20?= =?UTF-8?q?have=20same=20template=20merged?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚀 All window command added * 🙄 Except wait or conditional command --- ahk/templates/window/close.ahk | 4 --- ahk/templates/window/win_activate.ahk | 4 --- ahk/templates/window/win_close.ahk | 4 --- ahk/templates/window/win_hide.ahk | 4 --- ahk/templates/window/win_minimize.ahk | 4 --- ahk/templates/window/win_restore.ahk | 4 --- ahk/templates/window/win_show.ahk | 4 --- ahk/window.py | 41 ++++++++++++++++----------- 8 files changed, 25 insertions(+), 44 deletions(-) delete mode 100644 ahk/templates/window/close.ahk delete mode 100644 ahk/templates/window/win_activate.ahk delete mode 100644 ahk/templates/window/win_close.ahk delete mode 100644 ahk/templates/window/win_hide.ahk delete mode 100644 ahk/templates/window/win_minimize.ahk delete mode 100644 ahk/templates/window/win_restore.ahk delete mode 100644 ahk/templates/window/win_show.ahk 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/win_activate.ahk b/ahk/templates/window/win_activate.ahk deleted file mode 100644 index 3382a1b9..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 %} 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_hide.ahk b/ahk/templates/window/win_hide.ahk deleted file mode 100644 index d4e3fda3..00000000 --- a/ahk/templates/window/win_hide.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinHide, ahk_id {{ win.id }} -{% endblock body %} diff --git a/ahk/templates/window/win_minimize.ahk b/ahk/templates/window/win_minimize.ahk deleted file mode 100644 index 4791d318..00000000 --- a/ahk/templates/window/win_minimize.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinMinimize, ahk_id {{ win.id }} -{% endblock body %} diff --git a/ahk/templates/window/win_restore.ahk b/ahk/templates/window/win_restore.ahk deleted file mode 100644 index b27ccb59..00000000 --- a/ahk/templates/window/win_restore.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinRestore, ahk_id {{ win.id }} -{% endblock body %} diff --git a/ahk/templates/window/win_show.ahk b/ahk/templates/window/win_show.ahk deleted file mode 100644 index 0d47bf25..00000000 --- a/ahk/templates/window/win_show.ahk +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinShow, ahk_id {{ win.id }} -{% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index 465df844..c6c948bd 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -242,10 +242,6 @@ def always_on_top(self, value): 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) @@ -260,25 +256,38 @@ 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') + def _base_method(self, command, *args, **kwargs): + kwargs['command'] = command + + script = self._render_template("window/base_command.ahk", *args, *kwargs) self.engine.run_script(script) + def activate(self): + self._base_method("WinActivate") + + def activate_buttom(self): + self._base_method("WinActivateBottom") + + def close(self, seconds_to_wait=""): + self._base_method("WinClose", seconds_to_wait=seconds_to_wait) + def hide(self): - script = self._render_template('window/win_hide.ahk') - self.engine.run_script(script) + self._base_method("WinHide") - def minimize(self): - script = self._render_template('window/win_minimize.ahk') - self.engine.run_script(script) + def kill(self, seconds_to_wait=""): + self._base_method("WinKill", seconds_to_wait=seconds_to_wait) - def show(self): - script = self._render_template('window/win_show.ahk') - self.engine.run_script(script) + def maximize(self): + self._base_method("WinMaximize") + + def minimize(self): + self._base_method("WinMinimize") def restore(self): - script = self._render_template('window/win_restore.ahk') - self.engine.run_script(script) + self._base_method("WinRestore") + + def show(self): + self._base_method("WinShow") 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) From 9cf969855f732da4de4353095be9f98855edc987 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 18:35:23 +0300 Subject: [PATCH 025/588] =?UTF-8?q?=F0=9F=93=9D=20Added=20doc=20for=20new?= =?UTF-8?q?=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 886fcefb..1d2bd0af 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ A Python wrapper around AHK. [![Coverage](https://coveralls.io/repos/github/spyoungtech/ahk/badge.svg?branch=master)](https://coveralls.io/github/spyoungtech/ahk?branch=master) ![PyPI - Downloads](https://img.shields.io/pypi/dm/ahk) - # Installation ``` @@ -33,15 +32,12 @@ print(ahk.mouse_position) # (150, 150) ![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 @@ -80,7 +76,7 @@ ahk.key_wait('a', timeout=3) # Wait up to 3 seconds for the "a" key to be press You can do stuff with windows, too. -Getting windows +### Getting windows ```python from ahk import AHK @@ -88,15 +84,16 @@ from ahk.window import Window ahk = AHK() -win = ahk.active_window # Get the active window +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) # the window under the mouse cursor -win = Window.from_pid('20366') # by process ID +win = list(ahk.windows()) # list of all windows +win = Window(ahk, ahk_id='0xabc123') # by ahk_id +win = Window.from_mouse_position(ahk) # the window under the mouse cursor +win = Window.from_pid('20366') # by process ID ``` -Working with windows +### Working with windows + ```python from ahk import AHK @@ -104,15 +101,25 @@ ahk = AHK() ahk.run_script('Run Notepad') # Open notepad win = ahk.find_window(title=b'Untitled - Notepad') # Find the opened window + win.send('hello') # Send keys directly to the 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() # Move the window on top of other windows -win.to_bottom() # Move the window to the bottom of the other windows -win.always_on_top = True # Make the window always on top -win.close() # Close the window + +win.activate() # Give the window focus +win.activate_buttom() # Give the window focus +win.close() # Close the window +win.hide() # Hide the windwow +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.always_on_top = True # Make the window always on top for window in ahk.windows(): print(window.title) @@ -120,7 +127,7 @@ for window in ahk.windows(): # Some more attributes print(window.text) print(window.rect) # (x, y, width, height) - print(window.id) # ahk_id + print(window.id) # ahk_id print(window.pid) print(window.process) ``` From f3c257ff6d29fab08022ad3877c54798fb821b5b Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 18:53:18 +0300 Subject: [PATCH 026/588] =?UTF-8?q?=E2=9E=95=20Window=20check=20methods=20?= =?UTF-8?q?added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⭐ Exist, Active * 🏗️ With base_check.ahk template --- .../window/{win_is_active.ahk => base_check.ahk} | 8 +++----- ahk/window.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) rename ahk/templates/window/{win_is_active.ahk => base_check.ahk} (51%) diff --git a/ahk/templates/window/win_is_active.ahk b/ahk/templates/window/base_check.ahk similarity index 51% rename from ahk/templates/window/win_is_active.ahk rename to ahk/templates/window/base_check.ahk index 4b0413ea..ce6cd3d2 100644 --- a/ahk/templates/window/win_is_active.ahk +++ b/ahk/templates/window/base_check.ahk @@ -1,9 +1,7 @@ {% extends "base.ahk" %} {% block body %} -if WinActive("ahk_id {{ win.id }}") { +if {{ command }}("ahk_id {{ win.id }}") { FileAppend, 1, * - ExitApp -} -FileAppend, 0, * -ExitApp +else + FileAppend, 0, * {% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index c6c948bd..41e2cd12 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -184,13 +184,20 @@ def height(self): def height(self, new_height): self.move(height=new_height) - @property - def active(self): - script = self._render_template('window/win_is_active.ahk') + def _base_property(self, command): + script = self._render_template("window/base_check.ahk") result = self.engine.run_script(script) result = bool(ast.literal_eval(result)) return result + @property + def active(self): + return self._base_property(command="WinActive") + + @property + def exist(self): + return self._base_method(command="WinExist") + def disable(self): self.win_set('Disable', '') From 13c49df26c97ab1b2136e927235c9949a8f8f5a8 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 19:03:41 +0300 Subject: [PATCH 027/588] =?UTF-8?q?=F0=9F=91=A8=E2=80=8D=F0=9F=94=A7=20Kwa?= =?UTF-8?q?rgs=20bug=20resolved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/window.py b/ahk/window.py index 41e2cd12..fa53de50 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -266,7 +266,7 @@ def _render_template(self, *args, **kwargs): def _base_method(self, command, *args, **kwargs): kwargs['command'] = command - script = self._render_template("window/base_command.ahk", *args, *kwargs) + script = self._render_template("window/base_command.ahk", *args, **kwargs) self.engine.run_script(script) def activate(self): From e4cdc36b384433056de9ce7987b523a855712ff1 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 19:18:01 +0300 Subject: [PATCH 028/588] =?UTF-8?q?=F0=9F=91=A8=E2=80=8D=F0=9F=94=A7=20Min?= =?UTF-8?q?or=20bug=20fix=20and=20improvement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/templates/window/base_command.ahk | 2 +- ahk/window.py | 90 +++++++++++++++++++-------- 2 files changed, 65 insertions(+), 27 deletions(-) diff --git a/ahk/templates/window/base_command.ahk b/ahk/templates/window/base_command.ahk index 62ed8697..4b14f20b 100644 --- a/ahk/templates/window/base_command.ahk +++ b/ahk/templates/window/base_command.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -{{ command }}, ahk_id {{ win.id }}{% if seconds_to_wait %}, {{ seconds_to_wait }}{% endif %}{% if win._exclude_title %}, {{ win._exclude_title }}{% endif %}{% if win._exclude_text %}, {{ win._exclude_text }}{% endif %} +{{ command }}, ahk_id {{ win.id }}{% if seconds_to_wait %}, {{ seconds_to_wait }}{% endif %}, {{ title }}, {{ text }}, {{ exclude_title }}, {{ exclude_text }} {% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index fa53de50..f1332bb5 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -269,32 +269,70 @@ def _base_method(self, command, *args, **kwargs): script = self._render_template("window/base_command.ahk", *args, **kwargs) self.engine.run_script(script) - def activate(self): - self._base_method("WinActivate") - - def activate_buttom(self): - self._base_method("WinActivateBottom") - - def close(self, seconds_to_wait=""): - self._base_method("WinClose", seconds_to_wait=seconds_to_wait) - - def hide(self): - self._base_method("WinHide") - - def kill(self, seconds_to_wait=""): - self._base_method("WinKill", seconds_to_wait=seconds_to_wait) - - def maximize(self): - self._base_method("WinMaximize") - - def minimize(self): - self._base_method("WinMinimize") - - def restore(self): - self._base_method("WinRestore") - - def show(self): - self._base_method("WinShow") + def activate(self, title="", text="", exclude_title="", exclude_text=""): + self._base_method( + "WinActivate", + title=title, text=text, + exclude_title=exclude_title, exclude_text=exclude_text + ) + + def activate_buttom(self, title="", text="", exclude_title="", exclude_text=""): + self._base_method( + "WinActivateBottom", + title=title, text=text, + exclude_title=exclude_title, exclude_text=exclude_text + ) + + def close(self, seconds_to_wait="", title="", text="", exclude_title="", exclude_text=""): + self._base_method( + "WinClose", + seconds_to_wait=seconds_to_wait, + title=title, text=text, + exclude_title=exclude_title, exclude_text=exclude_text + ) + + def hide(self, title="", text="", exclude_title="", exclude_text=""): + self._base_method( + "WinHide", + title=title, text=text, + exclude_title=exclude_title, exclude_text=exclude_text + ) + + def kill(self, seconds_to_wait="", title="", text="", exclude_title="", exclude_text=""): + self._base_method( + "WinKill", + seconds_to_wait=seconds_to_wait, + title=title, text=text, + exclude_title=exclude_title, exclude_text=exclude_text + ) + + def maximize(self, title="", text="", exclude_title="", exclude_text=""): + self._base_method( + "WinMaximize", + title=title, text=text, + exclude_title=exclude_title, exclude_text=exclude_text + ) + + def minimize(self, title="", text="", exclude_title="", exclude_text=""): + self._base_method( + "WinMinimize", + title=title, text=text, + exclude_title=exclude_title, exclude_text=exclude_text + ) + + def restore(self, title="", text="", exclude_title="", exclude_text=""): + self._base_method( + "WinRestore", + title=title, text=text, + exclude_title=exclude_title, exclude_text=exclude_text + ) + + def show(self, title="", text="", exclude_title="", exclude_text=""): + self._base_method( + "WinShow", + title=title, text=text, + exclude_title=exclude_title, exclude_text=exclude_text + ) 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) From 9f291240062579decd72456b9bf429a1114d040f Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 19:26:01 +0300 Subject: [PATCH 029/588] =?UTF-8?q?=F0=9F=93=9D=20Window=20checks=20docs?= =?UTF-8?q?=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1d2bd0af..341ce7d2 100644 --- a/README.md +++ b/README.md @@ -126,10 +126,18 @@ for window in ahk.windows(): # Some more attributes print(window.text) - print(window.rect) # (x, y, width, height) - print(window.id) # ahk_id + print(window.rect) # (x, y, width, height) + print(window.id) # ahk_id print(window.pid) print(window.process) + + +if window.active: # Check if window active + window.minimize() + +if window.exist: # Check if window exist + window.maximize() + ``` ## Screen From 7dfca314f23f520184b503bc2d04c3bcdc9e2fc9 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 21:20:20 +0300 Subject: [PATCH 030/588] =?UTF-8?q?=E2=9E=95=20New=20property=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💁‍♂️ Also methods are sorted --- ahk/window.py | 53 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/ahk/window.py b/ahk/window.py index f1332bb5..4a669c7d 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -124,9 +124,12 @@ 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}') + + script = self._render_template( + 'window/get.ahk', + subcommand=sub, + title=f"ahk_id {self.id}", + ) return self.engine.run_script(script) @@ -198,15 +201,6 @@ def active(self): def exist(self): return self._base_method(command="WinExist") - 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') @@ -231,6 +225,18 @@ def text(self): return result.stdout.decode(encoding=self.encoding) return result.stdout + @property + def transparent(self, value): + return self.get("Transparent") + + @transparent.setter + def transparent(self, value): + if isinstance(value, int) and 0 <= value <= 255: + self.win_set("Transparent", value) + else: + raise ValueError( + f'"{value}" not a valid option. Please use [0, 255] integer') + @property def always_on_top(self): script = self._render_template('window/win_is_always_on_top.ahk') @@ -249,6 +255,15 @@ def always_on_top(self, value): raise ValueError( f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') + def disable(self): + self.win_set('Disable', '') + + def enable(self): + self.win_set('Enable', '') + + def redraw(self): + self.win_set('Redraw', '') + def to_bottom(self): """ Send window to bottom (behind other windows) @@ -366,12 +381,14 @@ def __init__(self, *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) + 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) From a943d7a714bd04bb27d48a031aacf92b415a9bf5 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 21:20:34 +0300 Subject: [PATCH 031/588] =?UTF-8?q?=F0=9F=92=A6=20Unnecessary=20file=20rem?= =?UTF-8?q?oved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/templates/window/win_get.ahk | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ahk/templates/window/win_get.ahk diff --git a/ahk/templates/window/win_get.ahk b/ahk/templates/window/win_get.ahk deleted file mode 100644 index e69de29b..00000000 From 622434449293fa20670bc94e2b9dc3957fccaa7d Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 22:12:27 +0300 Subject: [PATCH 032/588] =?UTF-8?q?=F0=9F=92=A6=20Unnecessary=20code=20rem?= =?UTF-8?q?oved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/templates/window/base_command.ahk | 2 +- ahk/window.py | 45 +++++++++++---------------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/ahk/templates/window/base_command.ahk b/ahk/templates/window/base_command.ahk index 4b14f20b..0b2959d6 100644 --- a/ahk/templates/window/base_command.ahk +++ b/ahk/templates/window/base_command.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -{{ command }}, ahk_id {{ win.id }}{% if seconds_to_wait %}, {{ seconds_to_wait }}{% endif %}, {{ title }}, {{ text }}, {{ exclude_title }}, {{ exclude_text }} +{{ command }}, {{ title }}{% if seconds_to_wait %}, {{ seconds_to_wait }}{% endif %} {% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index 4a669c7d..b52eb8b8 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -284,69 +284,60 @@ def _base_method(self, command, *args, **kwargs): script = self._render_template("window/base_command.ahk", *args, **kwargs) self.engine.run_script(script) - def activate(self, title="", text="", exclude_title="", exclude_text=""): + def activate(self): self._base_method( "WinActivate", - title=title, text=text, - exclude_title=exclude_title, exclude_text=exclude_text + title=f"ahk_id {self.id}" ) - def activate_buttom(self, title="", text="", exclude_title="", exclude_text=""): + def activate_buttom(self): self._base_method( "WinActivateBottom", - title=title, text=text, - exclude_title=exclude_title, exclude_text=exclude_text + title=f"ahk_id {self.id}" ) - def close(self, seconds_to_wait="", title="", text="", exclude_title="", exclude_text=""): + def close(self, seconds_to_wait=""): self._base_method( "WinClose", seconds_to_wait=seconds_to_wait, - title=title, text=text, - exclude_title=exclude_title, exclude_text=exclude_text + title=f"ahk_id {self.id}" ) - def hide(self, title="", text="", exclude_title="", exclude_text=""): + def hide(self): self._base_method( "WinHide", - title=title, text=text, - exclude_title=exclude_title, exclude_text=exclude_text + title=f"ahk_id {self.id}" ) - def kill(self, seconds_to_wait="", title="", text="", exclude_title="", exclude_text=""): + def kill(self, seconds_to_wait=""): self._base_method( "WinKill", seconds_to_wait=seconds_to_wait, - title=title, text=text, - exclude_title=exclude_title, exclude_text=exclude_text + title=f"ahk_id {self.id}" ) - def maximize(self, title="", text="", exclude_title="", exclude_text=""): + def maximize(self): self._base_method( "WinMaximize", - title=title, text=text, - exclude_title=exclude_title, exclude_text=exclude_text + title=f"ahk_id {self.id}" ) - def minimize(self, title="", text="", exclude_title="", exclude_text=""): + def minimize(self): self._base_method( "WinMinimize", - title=title, text=text, - exclude_title=exclude_title, exclude_text=exclude_text + title=f"ahk_id {self.id}" ) - def restore(self, title="", text="", exclude_title="", exclude_text=""): + def restore(self): self._base_method( "WinRestore", - title=title, text=text, - exclude_title=exclude_title, exclude_text=exclude_text + title=f"ahk_id {self.id}" ) - def show(self, title="", text="", exclude_title="", exclude_text=""): + def show(self): self._base_method( "WinShow", - title=title, text=text, - exclude_title=exclude_title, exclude_text=exclude_text + title=f"ahk_id {self.id}" ) def move(self, x='', y='', width=None, height=None): From 28404e4247dda32e81b29408cb53cb891c135176 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 22:24:00 +0300 Subject: [PATCH 033/588] =?UTF-8?q?=F0=9F=92=A6=20Clearify?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/templates/window/base_check.ahk | 2 +- ahk/window.py | 73 +++++++++++------------------ 2 files changed, 28 insertions(+), 47 deletions(-) diff --git a/ahk/templates/window/base_check.ahk b/ahk/templates/window/base_check.ahk index ce6cd3d2..253b97d6 100644 --- a/ahk/templates/window/base_check.ahk +++ b/ahk/templates/window/base_check.ahk @@ -1,6 +1,6 @@ {% extends "base.ahk" %} {% block body %} -if {{ command }}("ahk_id {{ win.id }}") { +if {{ command }}("{{ title }}") { FileAppend, 1, * else FileAppend, 0, * diff --git a/ahk/window.py b/ahk/window.py index b52eb8b8..78deadef 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -1,7 +1,9 @@ -from ahk.script import ScriptEngine import ast -from ahk.utils import make_logger, escape_sequence_replace from contextlib import suppress + +from ahk.script import ScriptEngine +from ahk.utils import escape_sequence_replace, make_logger + logger = make_logger(__name__) @@ -188,7 +190,11 @@ def height(self, new_height): self.move(height=new_height) def _base_property(self, command): - script = self._render_template("window/base_check.ahk") + script = self._render_template( + "window/base_check.ahk", + command=command, + title=f"ahk_id {self.id}" + ) result = self.engine.run_script(script) result = bool(ast.literal_eval(result)) return result @@ -199,7 +205,7 @@ def active(self): @property def exist(self): - return self._base_method(command="WinExist") + return self._base_property(command="WinExist") @property def title(self): @@ -278,67 +284,42 @@ def _render_template(self, *args, **kwargs): kwargs['win'] = self return self.engine.render_template(*args, **kwargs) - def _base_method(self, command, *args, **kwargs): - kwargs['command'] = command + def _base_method(self, command, seconds_to_wait=""): + script = self._render_template( + "window/base_command.ahk", + command=command, + title=f"ahk_id {self.id}", + seconds_to_wait=seconds_to_wait + ) - script = self._render_template("window/base_command.ahk", *args, **kwargs) - self.engine.run_script(script) + return self.engine.run_script(script) def activate(self): - self._base_method( - "WinActivate", - title=f"ahk_id {self.id}" - ) + self._base_method("WinActivate") def activate_buttom(self): - self._base_method( - "WinActivateBottom", - title=f"ahk_id {self.id}" - ) + self._base_method("WinActivateBottom") def close(self, seconds_to_wait=""): - self._base_method( - "WinClose", - seconds_to_wait=seconds_to_wait, - title=f"ahk_id {self.id}" - ) + self._base_method("WinClose", seconds_to_wait=seconds_to_wait) def hide(self): - self._base_method( - "WinHide", - title=f"ahk_id {self.id}" - ) + self._base_method("WinHide") def kill(self, seconds_to_wait=""): - self._base_method( - "WinKill", - seconds_to_wait=seconds_to_wait, - title=f"ahk_id {self.id}" - ) + self._base_method("WinKill", seconds_to_wait=seconds_to_wait) def maximize(self): - self._base_method( - "WinMaximize", - title=f"ahk_id {self.id}" - ) + self._base_method("WinMaximize") def minimize(self): - self._base_method( - "WinMinimize", - title=f"ahk_id {self.id}" - ) + self._base_method("WinMinimize") def restore(self): - self._base_method( - "WinRestore", - title=f"ahk_id {self.id}" - ) + self._base_method("WinRestore") def show(self): - self._base_method( - "WinShow", - title=f"ahk_id {self.id}" - ) + self._base_method("WinShow") 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) From 43c0b6537171ef9cdbae638360bd0fd0f32b1bc9 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 22:44:49 +0300 Subject: [PATCH 034/588] =?UTF-8?q?=F0=9F=94=80=20All=20get=20commands=20a?= =?UTF-8?q?re=20marged?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...{win_get_text.ahk => base_get_command.ahk} | 2 +- ahk/templates/window/win_get_class.ahk | 5 -- ahk/templates/window/win_get_title.ahk | 5 -- ahk/templates/window/win_is_always_on_top.ahk | 2 +- ahk/templates/window/win_move.ahk | 4 +- ahk/templates/window/win_position.ahk | 2 +- ahk/templates/window/win_send.ahk | 4 +- ahk/templates/window/win_set.ahk | 2 +- ahk/window.py | 81 ++++++++++++------- 9 files changed, 59 insertions(+), 48 deletions(-) rename ahk/templates/window/{win_get_text.ahk => base_get_command.ahk} (68%) delete mode 100644 ahk/templates/window/win_get_class.ahk delete mode 100644 ahk/templates/window/win_get_title.ahk diff --git a/ahk/templates/window/win_get_text.ahk b/ahk/templates/window/base_get_command.ahk similarity index 68% rename from ahk/templates/window/win_get_text.ahk rename to ahk/templates/window/base_get_command.ahk index 7a0c31fb..3f1ae7ac 100644 --- a/ahk/templates/window/win_get_text.ahk +++ b/ahk/templates/window/base_get_command.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} -WinGetText, text, ahk_id {{ win.id }} +{{ command }}, text, {{ title }} FileAppend, %text%, * {% endblock body %} diff --git a/ahk/templates/window/win_get_class.ahk b/ahk/templates/window/win_get_class.ahk deleted file mode 100644 index 94d7b5f5..00000000 --- a/ahk/templates/window/win_get_class.ahk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.ahk" %} -{% block body %} -WinGetClass, 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_always_on_top.ahk b/ahk/templates/window/win_is_always_on_top.ahk index 1d168235..97799dbc 100644 --- a/ahk/templates/window/win_is_always_on_top.ahk +++ b/ahk/templates/window/win_is_always_on_top.ahk @@ -1,6 +1,6 @@ {% extends "base.ahk" %} {% block body %} -WinGet, ExStyle, ExStyle, ahk_id {{ win.id }} +WinGet, ExStyle, ExStyle, {{ title }} if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. FileAppend, 1, * else diff --git a/ahk/templates/window/win_move.ahk b/ahk/templates/window/win_move.ahk index 4f09fd7a..c836645e 100644 --- a/ahk/templates/window/win_move.ahk +++ b/ahk/templates/window/win_move.ahk @@ -1,4 +1,4 @@ {% 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 +WinMove, {{ title }}, , {{ x }}, {{ y }}{% if width %}, {{ width }}{% endif %}{% if height %}, {{ height }}{% endif %} +{% endblock body %} diff --git a/ahk/templates/window/win_position.ahk b/ahk/templates/window/win_position.ahk index 41b7a4f9..2f23b289 100644 --- a/ahk/templates/window/win_position.ahk +++ b/ahk/templates/window/win_position.ahk @@ -1,6 +1,6 @@ {% extends "base.ahk" %} {% block body %} -WinGetPos, x, y, width, height, ahk_id {{ win.id }} +WinGetPos, x, y, width, height, {{ title }} 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 index 321c974d..39bbddd4 100644 --- a/ahk/templates/window/win_send.ahk +++ b/ahk/templates/window/win_send.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -{% if raw %}ControlSendRaw{% else %}ControlSend{% endif %}, , {{ keys }}, ahk_id {{ win.id }} -{% endblock body %} \ No newline at end of file +{% if raw %}ControlSendRaw{% else %}ControlSend{% endif %}, , {{ keys }}, {{ title }} +{% endblock body %} diff --git a/ahk/templates/window/win_set.ahk b/ahk/templates/window/win_set.ahk index 29335efd..7d12cd95 100644 --- a/ahk/templates/window/win_set.ahk +++ b/ahk/templates/window/win_set.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -WinSet, {{subcommand}}, {{value}}, ahk_id {{ win.id }} +WinSet, {{subcommand}}, {{value}}, {{ title }} {% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index 78deadef..2be48ab6 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -138,12 +138,24 @@ def get(self, subcommand): 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 set(self, subcommand, value): + sub = self._subcommands.get(subcommand) + if not sub: + raise ValueError(f'No such subcommand {subcommand}') + + script = self._render_template( + 'window/win_set.ahk', + subcommand=subcommand, + value=value, + title=f"ahk_id {self.id}" + ) + return self.engine.run_script(script) def _get_pos(self): - script = self._render_template('window/win_position.ahk') + script = self._render_template( + 'window/win_position.ahk', + title=f"ahk_id {self.id}" + ) resp = self.engine.run_script(script) try: value = ast.literal_eval(resp) @@ -207,29 +219,28 @@ def active(self): def exist(self): return self._base_property(command="WinExist") - @property - def title(self): - script = self._render_template('window/win_get_title.ahk') + def _base_get_method(self, command): + script = self._render_template( + "window/base_get_command.ahk", + command=command, + title=f"ahk_id {self.id}" + ) result = self.engine.run_script(script, decode=False) if self.encoding: return result.stdout.decode(encoding=self.encoding) return result.stdout + @property + def title(self): + return self._base_get_method("WinGetTitle") + @property def class_name(self): - script = self._render_template('window/win_get_class.ahk') - result = self.engine.run_script(script, decode=False) - if self.encoding: - return result.stdout.decode(encoding=self.encoding) - return result.stdout + return self._base_get_method("WinGetClass") @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 + return self._base_get_method("WinGetText") @property def transparent(self, value): @@ -238,47 +249,50 @@ def transparent(self, value): @transparent.setter def transparent(self, value): if isinstance(value, int) and 0 <= value <= 255: - self.win_set("Transparent", value) + self.set("Transparent", value) else: raise ValueError( f'"{value}" not a valid option. Please use [0, 255] integer') @property def always_on_top(self): - script = self._render_template('window/win_is_always_on_top.ahk') + script = self._render_template( + 'window/win_is_always_on_top.ahk', + title=f"ahk_id {self.id}" + ) 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') + self.set('AlwaysOnTop', 'On') elif value in ('off', 'Off', False, 0): - self.win_set('AlwaysOnTop', 'Off') + self.set('AlwaysOnTop', 'Off') elif value in ('toggle', 'Toggle', -1): - self.win_set('AlwaysOnTop', 'Toggle') + self.set('AlwaysOnTop', 'Toggle') else: raise ValueError( f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') def disable(self): - self.win_set('Disable', '') + self.set('Disable', '') def enable(self): - self.win_set('Enable', '') + self.set('Enable', '') def redraw(self): - self.win_set('Redraw', '') + self.set('Redraw', '') def to_bottom(self): """ Send window to bottom (behind other windows) :return: """ - self.win_set('Bottom', '') + self.set('Bottom', '') def to_top(self): - self.win_set('Top', '') + self.set('Top', '') def _render_template(self, *args, **kwargs): kwargs['win'] = self @@ -322,7 +336,11 @@ def show(self): self._base_method("WinShow") 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) + script = self._render_template( + 'window/win_move.ahk', + title=f"ahk_id {self.id}", + 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): @@ -333,8 +351,11 @@ def send(self, keys, delay=None, raw=False, blocking=False, escape=False): """ if escape: keys = escape_sequence_replace(keys) - script = self._render_template('window/win_send.ahk', keys=keys, - raw=raw, delay=delay, blocking=blocking) + script = self._render_template( + 'window/win_send.ahk', + title=f"ahk_id {self.id}", + keys=keys, raw=raw, delay=delay, blocking=blocking + ) return self.engine.run_script(script, blocking=blocking) def __eq__(self, other): From 093e2b2c9e14d74462742daeae9f27fe7864353d Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 22:47:41 +0300 Subject: [PATCH 035/588] =?UTF-8?q?=F0=9F=A7=90=20More=20detailed=20gitign?= =?UTF-8?q?ore=20file=20created?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e928de15..62954a03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,141 @@ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] -*$py.class \ No newline at end of file +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +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 +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/ + +# VS Code settings +.vscode + +# Windows desktop icon +desktop.ini From 08944255de0f640849c37873de98bc0e54330e0d Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 23:02:17 +0300 Subject: [PATCH 036/588] =?UTF-8?q?=E2=9E=95=20Blocking=20method=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/window.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ahk/window.py b/ahk/window.py index 2be48ab6..3d1bb0a9 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -298,7 +298,7 @@ def _render_template(self, *args, **kwargs): kwargs['win'] = self return self.engine.render_template(*args, **kwargs) - def _base_method(self, command, seconds_to_wait=""): + def _base_method(self, command, seconds_to_wait="", blocking=False): script = self._render_template( "window/base_command.ahk", command=command, @@ -306,7 +306,7 @@ def _base_method(self, command, seconds_to_wait=""): seconds_to_wait=seconds_to_wait ) - return self.engine.run_script(script) + return self.engine.run_script(script, blocking=blocking) def activate(self): self._base_method("WinActivate") @@ -335,6 +335,18 @@ def restore(self): def show(self): self._base_method("WinShow") + def wait(self, seconds_to_wait=""): + self._base_method("WinWait", seconds_to_wait=seconds_to_wait, blocking=True) + + def wait_active(self, seconds_to_wait=""): + self._base_method("WinWaitActive", seconds_to_wait=seconds_to_wait, blocking=True) + + def wait_not_active(self, seconds_to_wait=""): + self._base_method("WinWaitNotActive", seconds_to_wait=seconds_to_wait, blocking=True) + + def wait_close(self, seconds_to_wait=""): + self._base_method("WinWaitClose", seconds_to_wait=seconds_to_wait, blocking=True) + def move(self, x='', y='', width=None, height=None): script = self._render_template( 'window/win_move.ahk', From 8e1f16c09647141e7d386d398e165e4e81cba3c3 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 23:02:35 +0300 Subject: [PATCH 037/588] =?UTF-8?q?=E2=9E=95=20Title=20setter=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/templates/window/win_set_title.ahk | 4 ++++ ahk/window.py | 9 +++++++++ 2 files changed, 13 insertions(+) create mode 100644 ahk/templates/window/win_set_title.ahk diff --git a/ahk/templates/window/win_set_title.ahk b/ahk/templates/window/win_set_title.ahk new file mode 100644 index 00000000..6335bdd7 --- /dev/null +++ b/ahk/templates/window/win_set_title.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +WinSetTitle, {{ title }}, {{ text }}, {{ new_title }} +{% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index 3d1bb0a9..2eca592b 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -234,6 +234,15 @@ def _base_get_method(self, command): def title(self): return self._base_get_method("WinGetTitle") + @title.setter + def title(self, value): + script = self._render_template( + "window/win_set_title.ahk", + title=f"ahk_id {self.id}", + new_title=value + ) + return self.engine.run_script(script) + @property def class_name(self): return self._base_get_method("WinGetClass") From e801dcd530a4a815cd75715071633d2c36f0a335 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sat, 22 Feb 2020 23:21:09 +0300 Subject: [PATCH 038/588] =?UTF-8?q?=F0=9F=95=B5=EF=B8=8F=E2=80=8D=E2=99=82?= =?UTF-8?q?=EF=B8=8F=20Default=20ahk=20path=20finder=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/script.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/ahk/script.py b/ahk/script.py index 62824d8c..2f146d5e 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -15,29 +15,42 @@ class ExecutableNotFoundError(EnvironmentError): def _resolve_executable_path(executable_path: str = ''): if not executable_path: - executable_path = os.environ.get('AHK_PATH') or which('AutoHotkey.exe') or which('AutoHotkeyA32.exe') + executable_path = os.environ.get('AHK_PATH') or which( + 'AutoHotkey.exe') or which('AutoHotkeyA32.exe') + + if not executable_path: + ahk_default_path = r"C:\Program Files\AutoHotkey\AutoHotkey.exe" + if os.path.exists(executable_path): + executable_path = ahk_default_path + 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.' ) + if not os.path.exists(executable_path): - raise ExecutableNotFoundError(f"executable_path does not seems to exist: '{executable_path}' not found") + raise ExecutableNotFoundError( + f"executable_path does not seems to exist: '{executable_path}' not found") + if os.path.isdir(executable_path): raise ExecutableNotFoundError( 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" ) + 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 class ScriptEngine(object): - def __init__(self, executable_path: str = '', **kwargs): + + 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 @@ -48,7 +61,8 @@ def __init__(self, executable_path: str = '', **kwargs): self.executable_path = _resolve_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) + 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: @@ -70,7 +84,8 @@ def _run_script(self, script_text, **kwargs): 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) + 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)) @@ -78,7 +93,8 @@ def _run_script(self, script_text, **kwargs): else: return result else: - proc = subprocess.Popen(runargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) + proc = subprocess.Popen(runargs, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) try: proc.communicate(script_bytes, timeout=0) except subprocess.TimeoutExpired: From 6456c6da2a7c94c17040486d2f292f5e573986d7 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sun, 23 Feb 2020 00:07:00 +0300 Subject: [PATCH 039/588] =?UTF-8?q?=F0=9F=91=A8=E2=80=8D=F0=9F=94=AC=20Reg?= =?UTF-8?q?istery=20class=20added=20#12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 🙄 Needs review - 🙄 Loop method is not completed --- ahk/autohotkey.py | 9 +- ahk/registery.py | 117 +++++++++++++++++++++++ ahk/templates/registery/reg_delete.ahk | 4 + ahk/templates/registery/reg_loop.ahk | 4 + ahk/templates/registery/reg_read.ahk | 5 + ahk/templates/registery/reg_set_view.ahk | 4 + ahk/templates/registery/reg_write.ahk | 4 + 7 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 ahk/registery.py create mode 100644 ahk/templates/registery/reg_delete.ahk create mode 100644 ahk/templates/registery/reg_loop.ahk create mode 100644 ahk/templates/registery/reg_read.ahk create mode 100644 ahk/templates/registery/reg_set_view.ahk create mode 100644 ahk/templates/registery/reg_write.ahk diff --git a/ahk/autohotkey.py b/ahk/autohotkey.py index 30c36785..ed161983 100644 --- a/ahk/autohotkey.py +++ b/ahk/autohotkey.py @@ -5,8 +5,13 @@ from ahk.screen import ScreenMixin from ahk.keyboard import KeyboardMixin from ahk.sound import SoundMixin +from ahk.registery import RegisteryMixin -class AHK(WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin): + +class AHK( + WindowMixin, MouseMixin, KeyboardMixin, + ScreenMixin, SoundMixin, RegisteryMixin +): pass @@ -35,5 +40,5 @@ def sleep(self, n): :return: """ n = n * 1000 # convert to milliseconds - script = self.render_template('base.ahk', body=f'Sleep {n}', directives={'#Persistent',}) + script = self.render_template('base.ahk', body=f'Sleep {n}', directives={'#Persistent', }) self.run_script(script) diff --git a/ahk/registery.py b/ahk/registery.py new file mode 100644 index 00000000..4e6ba760 --- /dev/null +++ b/ahk/registery.py @@ -0,0 +1,117 @@ +from ahk.script import ScriptEngine +import os + + +class RegisteryMixin(ScriptEngine): + + def _render_template(self, template_name, *args, **kwargs): + return self.render_template( + os.path.join("registery", template_name), + ) + + def _run_template(self, template_name, *args, **kwargs): + script = self._render_template( + template_name, + *args, + **kwargs + ) + return self.run_script(script) + + def read(self, key_name: str, value_name="") -> str: + """Read registery + + Reference: + https://www.autohotkey.com/docs/commands/RegRead.htm + + Arguments: + key_name {str} -- RegEdit + + Keyword Arguments: + value_name {str} -- TODO (default: {""}) + + Returns: + str -- Registery value + """ + self._run_template( + "reg_read.ahk", + key_name=key_name, + value_name=value_name + ) + + def delete(self, key_name: str, value_name="") -> None: + """Delete registery + + Reference: + https://www.autohotkey.com/docs/commands/RegDelete.htm + + Arguments: + key_name {str} -- RegEdit + + Keyword Arguments: + value_name {str} -- TODO (default: {""}) + """ + self._run_template( + "reg_delete.ahk", + key_name=key_name, + value_name=value_name + ) + + def write(self, value_type: str, key_name: str, value_name="") -> None: + """Write registery + + Reference: + https://www.autohotkey.com/docs/commands/RegWrite.htm + + Arguments: + value_type {str} -- RegEdit value + key_name {str} -- RegEdit + + Keyword Arguments: + value_name {str} -- TODO (default: {""}) + """ + self._run_template( + "reg_write.ahk", + value_type=value_type, + key_name=key_name, + value_name=value_name + ) + + def set_view(self, reg_view: int) -> None: + """Set registery view + + Reference: + https://www.autohotkey.com/docs/commands/SetRegView.htm + + Arguments: + reg_view {str} -- Registery view + """ + + if reg_view not in [32, 64, "32", "64"]: + raise ValueError("No valid bit, please use 32 or 64") + + self._run_template( + "reg_set_view.ahk", + reg_view=reg_view, + ) + + def loop(self, reg: str, key_name: str, mode=""): + """Loop registery + + Reference: + https://www.autohotkey.com/docs/commands/LoopReg.htm + + Arguments: + reg {str} -- TODO + key_name {str} -- TODO + + Keyword Arguments: + mode {str} -- TODO (default: {""}) + """ + raise NotImplementedError + + self._run_template( + "reg_loop.ahk", + reg=reg, + key_name=key_name, + mode=mode + ) diff --git a/ahk/templates/registery/reg_delete.ahk b/ahk/templates/registery/reg_delete.ahk new file mode 100644 index 00000000..b7eb1fcd --- /dev/null +++ b/ahk/templates/registery/reg_delete.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +RegDelete, {{ key_name }}, {{ value_name }} +{% endblock body %} diff --git a/ahk/templates/registery/reg_loop.ahk b/ahk/templates/registery/reg_loop.ahk new file mode 100644 index 00000000..a659c40f --- /dev/null +++ b/ahk/templates/registery/reg_loop.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +Loop, {{ reg }}, {{ key_name }}, {{ mode }} +{% endblock body %} diff --git a/ahk/templates/registery/reg_read.ahk b/ahk/templates/registery/reg_read.ahk new file mode 100644 index 00000000..69b6fcc6 --- /dev/null +++ b/ahk/templates/registery/reg_read.ahk @@ -0,0 +1,5 @@ +{% extends "base.ahk" %} +{% block body %} +RegRead, output, {{ key_name }}, {{ value_name }} +FileAppend, %output%, * +{% endblock body %} diff --git a/ahk/templates/registery/reg_set_view.ahk b/ahk/templates/registery/reg_set_view.ahk new file mode 100644 index 00000000..5b2ccd50 --- /dev/null +++ b/ahk/templates/registery/reg_set_view.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +SetRegView, {{ reg_view }} +{% endblock body %} diff --git a/ahk/templates/registery/reg_write.ahk b/ahk/templates/registery/reg_write.ahk new file mode 100644 index 00000000..9fed241c --- /dev/null +++ b/ahk/templates/registery/reg_write.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +RegWrite, {{ value_type }}, {{ key_name }}, {{ value_name }} +{% endblock body %} From 5d9664835827b555c168d3592a9a5fd87ea18b27 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Sun, 23 Feb 2020 00:08:04 +0300 Subject: [PATCH 040/588] =?UTF-8?q?=F0=9F=91=A8=E2=80=8D=F0=9F=94=A7=20Exe?= =?UTF-8?q?cutable=20path=20bug=20fixed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/script.py b/ahk/script.py index 2f146d5e..24b2d695 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -20,7 +20,7 @@ def _resolve_executable_path(executable_path: str = ''): if not executable_path: ahk_default_path = r"C:\Program Files\AutoHotkey\AutoHotkey.exe" - if os.path.exists(executable_path): + if os.path.exists(ahk_default_path): executable_path = ahk_default_path if not executable_path: From 3ba574f7e44eaeb29da017611d6d4c36295d8954 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Tue, 25 Feb 2020 10:29:44 +0300 Subject: [PATCH 041/588] =?UTF-8?q?=E2=9E=95=20Add=20min=20max=20propertie?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 👨‍🔧 Also transperent fixed * ➕ Added more subcommand to the dict --- ahk/window.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/ahk/window.py b/ahk/window.py index 2eca592b..53f2620b 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -82,15 +82,24 @@ def send(self, raw=False): class Window(object): + + MINIMIZED = "-1" + MAXIMIZED = "1" + NON_MIN_NON_MAX = "0" + _subcommands = { 'id': 'ID', - 'process_name': 'ProcessName', + 'id_last': 'IDLast', 'pid': 'PID', + 'process_name': 'ProcessName', 'process_path': 'ProcessPath', 'process': 'ProcessPath', + 'count': 'count', + 'list': 'list', + 'min_max': "MinMax", 'controls': 'ControlList', 'controls_hwnd': 'ControlListHwnd', - 'transparency': 'Transparent', + 'transparent': 'Transparent', 'trans_color': 'TransColor', 'style': 'Style', # This will probably get a property later 'ex_style': 'ExStyle', # This will probably get a property later @@ -207,9 +216,8 @@ def _base_property(self, command): command=command, title=f"ahk_id {self.id}" ) - result = self.engine.run_script(script) - result = bool(ast.literal_eval(result)) - return result + resp = self.engine.run_script(script) + return bool(ast.literal_eval(resp)) @property def active(self): @@ -252,7 +260,19 @@ def text(self): return self._base_get_method("WinGetText") @property - def transparent(self, value): + def minimized(self): + return self.get("MinMax") == self.MINIMIZED + + @property + def maximized(self): + return self.get("MinMax") == self.MAXIMIZED + + @property + def non_max_non_min(self): + return self.get("MinMax") == self.NON_MIN_NON_MAX + + @property + def transparent(self): return self.get("Transparent") @transparent.setter From 64553a167bc7b755e9b854998d811dd014e8b01f Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Tue, 25 Feb 2020 10:30:17 +0300 Subject: [PATCH 042/588] =?UTF-8?q?=F0=9F=90=9B=20Wrong=20ahk=20code=20fix?= =?UTF-8?q?ed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/templates/window/base_check.ahk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/templates/window/base_check.ahk b/ahk/templates/window/base_check.ahk index 253b97d6..671d03e8 100644 --- a/ahk/templates/window/base_check.ahk +++ b/ahk/templates/window/base_check.ahk @@ -1,7 +1,7 @@ {% extends "base.ahk" %} {% block body %} -if {{ command }}("{{ title }}") { +if {{ command }}("{{ title }}") FileAppend, 1, * else - FileAppend, 0, * + FileAppend, 0, * {% endblock body %} From dec338aa7d70f51bbc61c2c022bfc797332ea17a Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Tue, 25 Feb 2020 10:30:41 +0300 Subject: [PATCH 043/588] =?UTF-8?q?=F0=9F=A7=AA=20Added=20window=20test=20?= =?UTF-8?q?methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unittests/test_window.py | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/unittests/test_window.py diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py new file mode 100644 index 00000000..1061cbea --- /dev/null +++ b/tests/unittests/test_window.py @@ -0,0 +1,42 @@ +from unittest import TestCase, main +import subprocess +import time +from ahk import AHK + + +class TestWindow(TestCase): + + def setUp(self): + 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 test_close(self): + self.win.close() + self.assertFalse(self.win.exist) + + def test_show_hide(self): + self.win.hide() + self.assertFalse(self.win.exist) + + self.win.show() + self.assertTrue(self.win.exist) + + def test_kill(self): + self.win.kill() + self.assertFalse(self.win.exist) + + def test_max_min(self): + self.win.maximize() + self.assertTrue(self.win.maximized) + + self.win.minimize() + self.assertTrue(self.win.minimized) + + self.win.restore() + self.assertTrue(self.win.maximized) + + def tearDown(self): + self.p.terminate() From 80eabd21126fb13aceaaa36d131ac67142a57b62 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Tue, 25 Feb 2020 10:50:27 +0300 Subject: [PATCH 044/588] =?UTF-8?q?=F0=9F=91=A8=E2=80=8D=F0=9F=92=BB=20=20?= =?UTF-8?q?Imports=20optimized?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unittests/test_window.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index 1061cbea..e4652d87 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -1,6 +1,7 @@ -from unittest import TestCase, main import subprocess import time +from unittest import TestCase + from ahk import AHK From 605844387bbc47a8f9f1ed711eb7dc33efe46877 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Tue, 25 Feb 2020 12:19:49 +0300 Subject: [PATCH 045/588] =?UTF-8?q?=E2=9C=A8=20Default=20path=20is=20store?= =?UTF-8?q?d=20in=20object=20for=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/script.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ahk/script.py b/ahk/script.py index 24b2d695..fa60a72f 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -13,15 +13,17 @@ class ExecutableNotFoundError(EnvironmentError): pass +DEFAULT_EXECUTABLE_PATH = r"C:\Program Files\AutoHotkey\AutoHotkey.exe" + + def _resolve_executable_path(executable_path: str = ''): if not executable_path: executable_path = os.environ.get('AHK_PATH') or which( 'AutoHotkey.exe') or which('AutoHotkeyA32.exe') if not executable_path: - ahk_default_path = r"C:\Program Files\AutoHotkey\AutoHotkey.exe" - if os.path.exists(ahk_default_path): - executable_path = ahk_default_path + if os.path.exists(DEFAULT_EXECUTABLE_PATH): + executable_path = DEFAULT_EXECUTABLE_PATH if not executable_path: raise ExecutableNotFoundError( From dc9e12f80ded609e60d7bdb5cde164eda035cb7e Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Tue, 25 Feb 2020 12:22:13 +0300 Subject: [PATCH 046/588] =?UTF-8?q?=E2=9C=A8=20Subcommand=20are=20=20divid?= =?UTF-8?q?ed=20inte=20subcategories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ahk/window.py | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/ahk/window.py b/ahk/window.py index 53f2620b..a6703e29 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -87,7 +87,21 @@ class Window(object): MAXIMIZED = "1" NON_MIN_NON_MAX = "0" - _subcommands = { + _set_subcommands = { + 'always_on_top': 'AlwaysOnTop', + 'bottom': 'Bottom', + 'top': 'Top', + 'disable': 'Disable', + 'enable': 'Enable', + 'redraw': 'Redraw', + 'style': 'Style', + 'ex_style': 'ExStyle', + 'region': 'Region', + 'transparent': 'Transparent', + 'transcolor': 'TransColor' + } + + _get_subcommands = { 'id': 'ID', 'id_last': 'IDLast', 'pid': 'PID', @@ -104,8 +118,10 @@ class Window(object): '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()}) + _set_subcommands.update({value: value for value in _set_subcommands.values()}) + _get_subcommands.update({value: value for value in _get_subcommands.values()}) def __init__(self, engine: ScriptEngine, ahk_id: str, encoding=None): self.engine = engine # should this be a weakref instead? @@ -127,12 +143,12 @@ def from_pid(cls, engine: ScriptEngine, pid, **kwargs): return cls(engine=engine, ahk_id=ahk_id, **kwargs) def __getattr__(self, attr): - if attr.lower() in self._subcommands: + if attr.lower() in self._get_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) + sub = self._get_subcommands.get(subcommand) if not sub: raise ValueError(f'No such subcommand {subcommand}') @@ -148,7 +164,7 @@ def __repr__(self): return f'' def set(self, subcommand, value): - sub = self._subcommands.get(subcommand) + sub = self._set_subcommands.get(subcommand) if not sub: raise ValueError(f'No such subcommand {subcommand}') @@ -272,8 +288,12 @@ def non_max_non_min(self): return self.get("MinMax") == self.NON_MIN_NON_MAX @property - def transparent(self): - return self.get("Transparent") + def transparent(self) -> int: + result = self.get("Transparent") + if result: + return int(result) + else: + return 255 @transparent.setter def transparent(self, value): @@ -284,7 +304,7 @@ def transparent(self, value): f'"{value}" not a valid option. Please use [0, 255] integer') @property - def always_on_top(self): + def always_on_top(self) -> bool: script = self._render_template( 'window/win_is_always_on_top.ahk', title=f"ahk_id {self.id}" From 8156d0903bac1937a830cde90b0ed9d06fc5547f Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Tue, 25 Feb 2020 12:22:31 +0300 Subject: [PATCH 047/588] =?UTF-8?q?=E2=9A=97=EF=B8=8F=20More=20test=20meth?= =?UTF-8?q?ods=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unittests/test_executable_location.py | 21 ++++++++----- tests/unittests/test_keyboard.py | 22 ++++++++----- tests/unittests/test_window.py | 34 +++++++++++++++++++++ 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/tests/unittests/test_executable_location.py b/tests/unittests/test_executable_location.py index aca49821..edad2c7d 100644 --- a/tests/unittests/test_executable_location.py +++ b/tests/unittests/test_executable_location.py @@ -1,11 +1,14 @@ -import sys import os +import sys from unittest import mock + import pytest +from ahk import AHK +from ahk.script import DEFAULT_EXECUTABLE_PATH, ExecutableNotFoundError + 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(): """ @@ -17,14 +20,16 @@ def check_pwd(): """ 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)') + 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() + if not os.path.isfile(DEFAULT_EXECUTABLE_PATH): + with pytest.raises(ExecutableNotFoundError): + AHK() def test_executable_path_from_env(): @@ -53,11 +58,13 @@ def test_executable_from_path(): ahk = AHK() assert ahk.executable_path == actual_path + def test_executable_as_dir_raises_error(): some_dir = os.path.abspath(os.path.dirname(__file__)) with pytest.raises(ExecutableNotFoundError): AHK(executable_path=some_dir) + def test_file_without_exe_extension_warns(): with pytest.warns(UserWarning): - AHK(executable_path=os.path.abspath(__file__)) \ No newline at end of file + AHK(executable_path=os.path.abspath(__file__)) diff --git a/tests/unittests/test_keyboard.py b/tests/unittests/test_keyboard.py index 53f49ddb..24963850 100644 --- a/tests/unittests/test_keyboard.py +++ b/tests/unittests/test_keyboard.py @@ -1,13 +1,18 @@ -import sys import os +import subprocess +import sys +import threading +import time +from itertools import product +from unittest import TestCase + +from ahk import AHK +from ahk.keys import ALT, CTRL, KEYS + 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): """ @@ -49,6 +54,7 @@ def test_type(self): self.ahk.type('Hello, World!') assert b'Hello, World!' in self.notepad.text + def a_down(): time.sleep(0.5) ahk = AHK() @@ -60,11 +66,13 @@ def release_a(): 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() diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index e4652d87..f37386aa 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -14,6 +14,24 @@ def setUp(self): self.win = self.ahk.win_get(title='Untitled - Notepad') self.assertIsNotNone(self.win) + def test_transparent(self): + self.assertEqual(self.win.transparent, 255) + + self.win.transparent = 220 + self.assertEqual(self.win.transparent, 220) + + self.win.transparent = 255 + self.assertEqual(self.win.transparent, 255) + + def test_pinned(self): + self.assertFalse(self.win.always_on_top) + + self.win.always_on_top = True + self.assertTrue(self.win.always_on_top) + + self.win.always_on_top = False + self.assertFalse(self.win.always_on_top) + def test_close(self): self.win.close() self.assertFalse(self.win.exist) @@ -30,6 +48,8 @@ def test_kill(self): self.assertFalse(self.win.exist) def test_max_min(self): + self.assertTrue(self.win.non_max_non_min) + self.win.maximize() self.assertTrue(self.win.maximized) @@ -39,5 +59,19 @@ def test_max_min(self): self.win.restore() self.assertTrue(self.win.maximized) + def test_names(self): + self.assertEqual(self.win.class_name, b'Notepad') + self.assertEqual(self.win.title, b'Untitled - Notepad') + self.assertEqual(self.win.text, b'') + def tearDown(self): self.p.terminate() + + +if __name__ == "__main__": + ahk = AHK() + p = subprocess.Popen('notepad') + time.sleep(1) + win = ahk.win_get(title='Untitled - Notepad') + print(win.transparent) + win.transparent = 255 From e8ecc488e1dad745618778553d77a84c6cab0a1f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 26 Feb 2020 20:39:59 -0800 Subject: [PATCH 048/588] add some waits for stability --- tests/unittests/test_window.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index f37386aa..5dfa5b48 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -38,25 +38,31 @@ def test_close(self): def test_show_hide(self): self.win.hide() + time.sleep(0.5) self.assertFalse(self.win.exist) self.win.show() + time.sleep(0.5) self.assertTrue(self.win.exist) def test_kill(self): self.win.kill() + time.sleep(0.5) self.assertFalse(self.win.exist) def test_max_min(self): self.assertTrue(self.win.non_max_non_min) self.win.maximize() + time.sleep(0.5) self.assertTrue(self.win.maximized) self.win.minimize() + time.sleep(0.5) self.assertTrue(self.win.minimized) self.win.restore() + time.sleep(0.5) self.assertTrue(self.win.maximized) def test_names(self): From 1ab46d330d69a511397713f6feb6227a83d7c03a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 26 Feb 2020 21:02:44 -0800 Subject: [PATCH 049/588] version 0.8.0 :package: --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 50160ac2..b7233524 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.7.0', + version='0.8.0', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From e2079d6af5c2b927a31f88b745633b6b598eaf3f Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Mon, 30 Mar 2020 02:39:46 -0700 Subject: [PATCH 050/588] add (non)blocking parameter for keyboard functions --- ahk/keyboard.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/ahk/keyboard.py b/ahk/keyboard.py index 40d0cf5c..dda6185a 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -104,14 +104,14 @@ def key_wait(self, key_name, timeout: int=None, logical_state=False, released=Fa if result == "1": raise TimeoutError(f'timed out waiting for {key_name}') - def type(self, s): + def type(self, s, blocking=True): """ Sends keystrokes using send_input, also escaping the string for use in AHK. """ s = escape_sequence_replace(s) - self.send_input(s) + self.send_input(s, blocking=blocking) - def send(self, s, raw=False, delay=None): + def send(self, s, raw=False, delay=None, blocking=True): """ https://autohotkey.com/docs/commands/Send.htm @@ -120,8 +120,8 @@ def send(self, s, raw=False, delay=None): :param delay: :return: """ - script = self.render_template('keyboard/send.ahk', s=s, raw=raw, delay=delay) - self.run_script(script) + script = self.render_template('keyboard/send.ahk', s=s, raw=raw, delay=delay, blocking=blocking) + self.run_script(script, blocking=blocking) def send_raw(self, s, delay=None): """ @@ -133,19 +133,20 @@ def send_raw(self, s, delay=None): """ return self.send(s, raw=True, delay=delay) - def send_input(self, s): + def send_input(self, s, blocking=True): """ https://autohotkey.com/docs/commands/Send.htm :param s: + :param blocking: :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) + script = self.render_template('keyboard/send_input.ahk', s=s, blocking=blocking) + self.run_script(script, blocking=blocking) def send_play(self, s): """ @@ -169,7 +170,7 @@ def send_event(self, s, delay=None): script = self.render_template('keyboard/send_event.ahk', s=s, delay=delay) self.run_script(script) - def key_press(self, key, release=True): + def key_press(self, key, release=True, blocking=True): """ Press and (optionally) release a single key @@ -178,11 +179,11 @@ def key_press(self, key, release=True): :return: """ - self.key_down(key) + self.key_down(key, blocking=blocking) if release: - self.key_up(key) + self.key_up(key, blocking=blocking) - def key_release(self, key): + def key_release(self, key, blocking=True): """ Release a key that is currently in pressed down state @@ -191,21 +192,22 @@ def key_release(self, key): """ if isinstance(key, str): key = Key(key_name=key) - return self.send_input(key.UP) + return self.send_input(key.UP, blocking=blocking) - def key_down(self, key): + def key_down(self, key, blocking=True): """ Press down a key (without releasing it) :param key: + :param blocking: :return: """ if isinstance(key, str): key = Key(key_name=key) - return self.send_input(key.DOWN) + return self.send_input(key.DOWN, blocking=blocking) - def key_up(self, key): + def key_up(self, key, blocking=True): """ Alias for :meth:~`KeyboardMixin.key_release` """ - return self.key_release(key) + return self.key_release(key, blocking=blocking) From c550424f0e62bc362c6510acba77cfe17b14f0ba Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 30 Mar 2020 02:48:56 -0700 Subject: [PATCH 051/588] version 0.8.1 :package: --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b7233524..9bb40eb1 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.8.0', + version='0.8.1', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From 0ad29403a187e8ed4f1cdf84770f4648ce281f10 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 5 Apr 2020 06:33:13 -0700 Subject: [PATCH 052/588] docstrings --- ahk/autohotkey.py | 13 ++++ ahk/directives.py | 4 ++ ahk/keyboard.py | 19 ++++-- ahk/keys.py | 3 +- ahk/mouse.py | 8 +++ ahk/screen.py | 24 +++---- ahk/script.py | 66 +++++++++++++++++-- ahk/window.py | 160 ++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 275 insertions(+), 22 deletions(-) diff --git a/ahk/autohotkey.py b/ahk/autohotkey.py index ed161983..d33f0815 100644 --- a/ahk/autohotkey.py +++ b/ahk/autohotkey.py @@ -12,10 +12,23 @@ class AHK( WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin, RegisteryMixin ): + """ + Inherits its methods from the following classes: + + | :py:class:`ahk.window.WindowMixin` + | :py:class:`ahk.mouse.MouseMixin` + | :py:class:`ahk.keyboard.KeyboardMixin` + | :py:class:`ahk.screen.ScreenMixin` + | :py:class:`ahk.sound.SoundMixin` + | :py:class:`ahk.registery.RegisteryMixin` + """ pass class ActionChain(AHK): + """ + Reusable action chain to execute various actions in order + """ def __init__(self, *args, **kwargs): self._actions = deque() super().__init__(*args, **kwargs) diff --git a/ahk/directives.py b/ahk/directives.py index 82f97f8d..c7bb0abc 100644 --- a/ahk/directives.py +++ b/ahk/directives.py @@ -1,3 +1,7 @@ +""" +Contains directive classes +""" + from types import SimpleNamespace diff --git a/ahk/keyboard.py b/ahk/keyboard.py index dda6185a..cf28da1f 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -6,8 +6,15 @@ from ahk.keys import Key from ahk.directives import InstallKeybdHook, InstallMouseHook + class Hotkey: def __init__(self, engine: ScriptEngine, hotkey: str, script: str): + """ + + :param engine: an :py:class:`~ahk.AHK` instance + :param hotkey: The hotkey to use (AutoHotkey syntax) + :param script: The script to execute when the hotkey is activated (AutoHotkey code as a string) + """ self.hotkey = hotkey self.script = script self.engine = engine @@ -59,9 +66,9 @@ def hotkey(self, *args, **kwargs): """ Convenience function for creating ``Hotkey`` instance using current engine. - :param args: - :param kwargs: - :return: + :param hotkey: The hotkey to use (AutoHotkey syntax) + :param script: The script to execute when the hotkey is activated (AutoHotkey code as a string) + :return: an :py:class:`~ahk.keyboard.Hotkey` instance """ return Hotkey(engine=self, *args, **kwargs) @@ -89,7 +96,7 @@ def key_wait(self, key_name, timeout: int=None, logical_state=False, released=Fa :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: + :return: None :raises TimeoutError: if the key was not pressed (or released, if specified) within timeout """ options = '' @@ -107,6 +114,9 @@ def key_wait(self, key_name, timeout: int=None, logical_state=False, released=Fa def type(self, s, blocking=True): """ Sends keystrokes using send_input, also escaping the string for use in AHK. + + :param s: the string to type + :param blocking: if ``True``, waits until script finishes, else returns immediately. """ s = escape_sequence_replace(s) self.send_input(s, blocking=blocking) @@ -118,6 +128,7 @@ def send(self, s, raw=False, delay=None, blocking=True): :param s: :param raw: :param delay: + :param blocking: if ``True``, waits until script finishes, else returns immediately. :return: """ script = self.render_template('keyboard/send.ahk', s=s, raw=raw, delay=delay, blocking=blocking) diff --git a/ahk/keys.py b/ahk/keys.py index 706fb493..c73a0e58 100644 --- a/ahk/keys.py +++ b/ahk/keys.py @@ -1,6 +1,5 @@ """ -The ahk.keys module contains some useful classes for working with 'special' keys. -It also +The ahk.keys module contains some useful constants and classes for working with keys. """ diff --git a/ahk/mouse.py b/ahk/mouse.py index 7d712bc8..159e2525 100644 --- a/ahk/mouse.py +++ b/ahk/mouse.py @@ -19,9 +19,11 @@ '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: @@ -42,6 +44,12 @@ class MouseMixin(ScriptEngine): Provides mouse functionality for the AHK class """ def __init__(self, mouse_speed=2, mode=None, **kwargs): + """ + + :param mouse_speed: default mouse speed + :param mode: + :param kwargs: + """ if mode is None: mode = 'Screen' self.mode = mode diff --git a/ahk/screen.py b/ahk/screen.py index 056612d9..758ca8f6 100644 --- a/ahk/screen.py +++ b/ahk/screen.py @@ -17,26 +17,26 @@ def image_search(self, image_path: str, :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) + 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%``). + defaults to screen width and height (lower right-hand corner; ``%A_ScreenWidth%``, ``%A_ScreenHeight%``). :param color_variation: Shades of variation (up or down) for the intensity of RGB for each pixel. Equivalent of - ``*n`` option. Defaults to 0. + ``*n`` option. Defaults to 0. :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 :param transparent: Specific color in the image that will be ignored during the search. Pixels with the exact - color given will match any color. Can be used with color names e.g. (Black, Purple, Yellow) found - at https://https://www.autohotkey.com/docs/commands/Progress.htm#colors or hexadecimal values e.g. - (0xFFFFAA, 05FA15, 632511). Equivalent of ``*TransN`` option + color given will match any color. Can be used with color names e.g. (Black, Purple, Yellow) found + at https://https://www.autohotkey.com/docs/commands/Progress.htm#colors or hexadecimal values e.g. + (0xFFFFAA, 05FA15, 632511). Equivalent of ``*TransN`` option :param icon: Number of the icon group to use. Equivalent of ``*Icon`` option :return: coordinates of the upper-left pixel of where the image was found on the screen; ``None`` if the image - was not found + was not found :rtype: Union[Tuple[int, int], None] @@ -114,11 +114,11 @@ def pixel_search(self, color: Union[str, int], variation: int=0, .. _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 color: + :param variation: + :param upper_bound: + :param lower_bound: + :param coord_mode: :param fast: :param rgb: :return: the coordinates of the pixel; None if the pixel is not found diff --git a/ahk/script.py b/ahk/script.py index fa60a72f..852e1c92 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -1,3 +1,15 @@ +""" +The :py:mod:`~ahk.script` module, most essentially, houses the :py:class:`~ahk.ScriptEngine` class. + +The :py:class:`~ahk.ScriptEngine` is responsible for rendering autohotkey code from jinja templates and executing that +code. This is the heart of how this package works. Every other major component either inherits from this class +or utilizes an instance of this class. + +The current implementation of how autohotkey code is executed is by calling the autohotkey +executable with ``subprocess``. + + +""" import os import subprocess import warnings @@ -14,6 +26,7 @@ class ExecutableNotFoundError(EnvironmentError): DEFAULT_EXECUTABLE_PATH = r"C:\Program Files\AutoHotkey\AutoHotkey.exe" +"""The deafult path to look for AutoHotkey, if not specified some other way""" def _resolve_executable_path(executable_path: str = ''): @@ -54,11 +67,18 @@ class ScriptEngine(object): def __init__(self, executable_path: str = "", **kwargs): """ + This class is typically not used directly. AHK components inherit from this class + and the arguments for this class should usually be passed in to :py:class:`~ahk.AHK`. + :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 provided explicitly in this argument, the path to the AHK executable is resolved in the following order: + + * The ``AHK_PATH`` environment variable, if present + * :py:data:`~ahk.script.DEFAULT_EXECUTABLE_PATH` if the file exists + + If environment variable not present, tries to look for 'AutoHotkey.exe' or 'AutoHotkeyA32.exe' with shutil.which + + :raises ExecutableNotFound: if AHK executable cannot be found or the specified file does not exist """ self.executable_path = _resolve_executable_path(executable_path) @@ -67,6 +87,22 @@ def __init__(self, executable_path: str = "", **kwargs): autoescape=False, trim_blocks=True) def render_template(self, template_name, directives=None, blocking=True, **kwargs): + """ + Renders a given jinja template and returns a string of script text + + :param template_name: the name of the jinja template to render + :param directives: additional AHK directives to add to the resulting script + :param blocking: whether the template should be rendered to block (use #Persistent directive) + :param kwargs: keywords passed to template rendering + :return: An AutoHotkey script as a string + + .. code-block:: python + + >>> from ahk import AHK + >>> ahk = AHK() + >>> ahk.render_template('keyboard/send_input.ahk', s='Hello') + '#NoEnv\\n#Persistent\\n\\n\\nSendInput Hello\\n\\nExitApp\\n' + """ if directives is None: directives = set() else: @@ -104,6 +140,28 @@ def _run_script(self, script_text, **kwargs): return proc def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): + """ + Given an AutoHotkey script as a string, execute it + + :param script_text: a string containing AutoHotkey code + :param decode: If ``True``, attempt to decode the stdout of the completed process. + If ``False``, returns the completed process. Only has effect when ``blocking=True`` + :param blocking: If ``True``, script must finish before returning. + If ``False``, function returns a ``subprocess.Popen`` object immediately without blocking + :param runkwargs: keyword arguments passed to ``subprocess.Popen`` or ``subprocess.run`` + :return: | A string of the decoded stdout if ``blocking`` and ``decode`` are True. + | ``subprocess.CompletedProcess`` if ``blocking`` is True and ``decode`` is False. + | ``subprocess.Popen`` object if ``blocking`` is False. + + >>> from ahk import AHK + >>> ahk = AHK() + >>> ahk.run_script('FileAppend, Hello World, *') + 'Hello World' + >>> ahk.run_script('FileAppend, Hello World, *', decode=False) + CompletedProcess(args=['C:\\\\pathto\\\\AutoHotkey.exe', '/ErrorStdOut', '*'], returncode=0, stdout=b'Hello World', stderr=b'') + >>> ahk.run_script('FileAppend, Hello World, *', blocking=False) + + """ logger.debug('Running script text: %s', script_text) try: result = self._run_script(script_text, decode=decode, blocking=blocking, **runkwargs) diff --git a/ahk/window.py b/ahk/window.py index a6703e29..d2688415 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -18,6 +18,7 @@ def __init__(self): def click(self): """ REF: https://www.autohotkey.com/docs/commands/ControlClick.htm + :return: """ raise NotImplementedError @@ -25,6 +26,7 @@ def click(self): def focus(self): """ REF: https://www.autohotkey.com/docs/commands/ControlFocus.htm + :return: """ raise NotImplementedError @@ -32,6 +34,7 @@ def focus(self): def get(self, key): """ REF: https://www.autohotkey.com/docs/commands/ControlGet.htm + :param key: :return: """ @@ -44,6 +47,7 @@ def has_focus(self): def position(self): """ REF: https://www.autohotkey.com/docs/commands/ControlGetPos.htm + :return: """ raise NotImplementedError @@ -52,6 +56,7 @@ def position(self): def text(self): """ REF: https://www.autohotkey.com/docs/commands/ControlGetText.htm + :return: """ raise NotImplementedError @@ -60,6 +65,7 @@ def text(self): def text(self, new_text): """ REF: https://www.autohotkey.com/docs/commands/ControlSetText.htm + :param new_text: :return: """ @@ -68,6 +74,7 @@ def text(self, new_text): def move(self): """ REF: https://www.autohotkey.com/docs/commands/ControlMove.htm + :return: """ raise NotImplementedError @@ -75,6 +82,7 @@ def move(self): def send(self, raw=False): """ REF: https://www.autohotkey.com/docs/commands/ControlSend.htm + :param raw: :return: """ @@ -325,9 +333,19 @@ def always_on_top(self, value): f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') def disable(self): + """ + Distable the window + + :return: + """ self.set('Disable', '') def enable(self): + """ + Enable the window + + :return: + """ self.set('Enable', '') def redraw(self): @@ -341,6 +359,11 @@ def to_bottom(self): self.set('Bottom', '') def to_top(self): + """ + Bring the window to the foreground (above other windows) + + :return: + """ self.set('Top', '') def _render_template(self, *args, **kwargs): @@ -358,45 +381,126 @@ def _base_method(self, command, seconds_to_wait="", blocking=False): return self.engine.run_script(script, blocking=blocking) def activate(self): + """ + Activate the window. + + See also: `WinActivate`_ + + .. _WinActivate: https://www.autohotkey.com/docs/commands/WinActivate.htm + + :return: + """ self._base_method("WinActivate") def activate_buttom(self): + """ + Calls `WinActivateBottom`_ on the window + + .. _WinActivateBottom: https://www.autohotkey.com/docs/commands/WinActivateBottom.htm + + :return: + """ self._base_method("WinActivateBottom") def close(self, seconds_to_wait=""): + """ + Closes the Window. See also: `WinClose`_ + + .. _WinClose: https://www.autohotkey.com/docs/commands/WinClose.htm + + :param seconds_to_wait: + :return: + """ self._base_method("WinClose", seconds_to_wait=seconds_to_wait) def hide(self): + """ + Hides the window. See also: `WinHide`_ + + .. _WinHide: https://www.autohotkey.com/docs/commands/WinHide.htm + + + :return: + """ self._base_method("WinHide") def kill(self, seconds_to_wait=""): self._base_method("WinKill", seconds_to_wait=seconds_to_wait) def maximize(self): + """ + maximize the window + + :return: + """ self._base_method("WinMaximize") def minimize(self): + """ + minimize the window + + :return: + """ self._base_method("WinMinimize") def restore(self): + """ + restore the window + + :return: + """ self._base_method("WinRestore") def show(self): + """ + show the window + + :return: + """ self._base_method("WinShow") def wait(self, seconds_to_wait=""): + """ + + :param seconds_to_wait: + :return: + """ self._base_method("WinWait", seconds_to_wait=seconds_to_wait, blocking=True) def wait_active(self, seconds_to_wait=""): + """ + + :param seconds_to_wait: + :return: + """ self._base_method("WinWaitActive", seconds_to_wait=seconds_to_wait, blocking=True) def wait_not_active(self, seconds_to_wait=""): + """ + + :param seconds_to_wait: + :return: + """ self._base_method("WinWaitNotActive", seconds_to_wait=seconds_to_wait, blocking=True) def wait_close(self, seconds_to_wait=""): + """ + + :param seconds_to_wait: + :return: + """ self._base_method("WinWaitClose", seconds_to_wait=seconds_to_wait, blocking=True) def move(self, x='', y='', width=None, height=None): + """ + Move the window to a position and/or change its geometry + + :param x: + :param y: + :param width: + :param height: + :return: + """ script = self._render_template( 'window/win_move.ahk', title=f"ahk_id {self.id}", @@ -463,6 +567,7 @@ def _all_window_ids(self): def windows(self): """ Returns a list of windows + :return: """ windowze = [] @@ -472,6 +577,15 @@ def windows(self): return windowze def find_windows(self, func=None, **kwargs): + """ + Find all matching windows + + :param func: a callable to filter windows + :param bool exact: if False (the default) partial matches are found. If True, only exact matches are returned + :param kwargs: keywords of attributes of the window (has no effect if ``func`` is provided) + + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + """ if func is None: exact = kwargs.pop('exact', False) @@ -488,29 +602,75 @@ def func(win): yield window def find_window(self, func=None, **kwargs): + """ + Like ``find_windows`` but only returns the first found window + + + :param func: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ with suppress(StopIteration): return next(self.find_windows(func=func, **kwargs)) def find_windows_by_title(self, title, exact=False): + """ + Equivalent to ``find_windows(title=title)``` + + Note that ``title`` is a ``bytes`` object + + :param bytes title: + :param exact: + :return: + """ for window in self.find_windows(title=title, exact=exact): yield window def find_window_by_title(self, *args, **kwargs): + """ + Like ``find_windows_by_title`` but only returns the first result. + + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ with suppress(StopIteration): return next(self.find_windows_by_title(*args, **kwargs)) def find_windows_by_text(self, text, exact=False): + """ + + :param text: + :param exact: + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + """ for window in self.find_windows(text=text, exact=exact): yield window def find_window_by_text(self, *args, **kwargs): + """ + + :param args: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ with suppress(StopIteration): return next(self.find_windows_by_text(*args, **kwargs)) def find_windows_by_class(self, class_name, exact=False): + """ + + :param class_name: + :param exact: + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + """ for window in self.find_windows(class_name=class_name, exact=exact): yield window def find_window_by_class(self, *args, **kwargs): + """ + + :param args: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ with suppress(StopIteration): return next(self.find_windows_by_class(*args, **kwargs)) From c2e0d953202f3a3626785d2712fc61a0879d0787 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 5 Apr 2020 06:35:19 -0700 Subject: [PATCH 053/588] docstrings --- ahk/autohotkey.py | 13 ++++ ahk/directives.py | 4 ++ ahk/keyboard.py | 19 ++++-- ahk/keys.py | 3 +- ahk/mouse.py | 8 +++ ahk/screen.py | 24 +++---- ahk/script.py | 66 +++++++++++++++++-- ahk/window.py | 160 ++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 275 insertions(+), 22 deletions(-) diff --git a/ahk/autohotkey.py b/ahk/autohotkey.py index ed161983..d33f0815 100644 --- a/ahk/autohotkey.py +++ b/ahk/autohotkey.py @@ -12,10 +12,23 @@ class AHK( WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin, RegisteryMixin ): + """ + Inherits its methods from the following classes: + + | :py:class:`ahk.window.WindowMixin` + | :py:class:`ahk.mouse.MouseMixin` + | :py:class:`ahk.keyboard.KeyboardMixin` + | :py:class:`ahk.screen.ScreenMixin` + | :py:class:`ahk.sound.SoundMixin` + | :py:class:`ahk.registery.RegisteryMixin` + """ pass class ActionChain(AHK): + """ + Reusable action chain to execute various actions in order + """ def __init__(self, *args, **kwargs): self._actions = deque() super().__init__(*args, **kwargs) diff --git a/ahk/directives.py b/ahk/directives.py index 82f97f8d..c7bb0abc 100644 --- a/ahk/directives.py +++ b/ahk/directives.py @@ -1,3 +1,7 @@ +""" +Contains directive classes +""" + from types import SimpleNamespace diff --git a/ahk/keyboard.py b/ahk/keyboard.py index dda6185a..cf28da1f 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -6,8 +6,15 @@ from ahk.keys import Key from ahk.directives import InstallKeybdHook, InstallMouseHook + class Hotkey: def __init__(self, engine: ScriptEngine, hotkey: str, script: str): + """ + + :param engine: an :py:class:`~ahk.AHK` instance + :param hotkey: The hotkey to use (AutoHotkey syntax) + :param script: The script to execute when the hotkey is activated (AutoHotkey code as a string) + """ self.hotkey = hotkey self.script = script self.engine = engine @@ -59,9 +66,9 @@ def hotkey(self, *args, **kwargs): """ Convenience function for creating ``Hotkey`` instance using current engine. - :param args: - :param kwargs: - :return: + :param hotkey: The hotkey to use (AutoHotkey syntax) + :param script: The script to execute when the hotkey is activated (AutoHotkey code as a string) + :return: an :py:class:`~ahk.keyboard.Hotkey` instance """ return Hotkey(engine=self, *args, **kwargs) @@ -89,7 +96,7 @@ def key_wait(self, key_name, timeout: int=None, logical_state=False, released=Fa :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: + :return: None :raises TimeoutError: if the key was not pressed (or released, if specified) within timeout """ options = '' @@ -107,6 +114,9 @@ def key_wait(self, key_name, timeout: int=None, logical_state=False, released=Fa def type(self, s, blocking=True): """ Sends keystrokes using send_input, also escaping the string for use in AHK. + + :param s: the string to type + :param blocking: if ``True``, waits until script finishes, else returns immediately. """ s = escape_sequence_replace(s) self.send_input(s, blocking=blocking) @@ -118,6 +128,7 @@ def send(self, s, raw=False, delay=None, blocking=True): :param s: :param raw: :param delay: + :param blocking: if ``True``, waits until script finishes, else returns immediately. :return: """ script = self.render_template('keyboard/send.ahk', s=s, raw=raw, delay=delay, blocking=blocking) diff --git a/ahk/keys.py b/ahk/keys.py index 706fb493..c73a0e58 100644 --- a/ahk/keys.py +++ b/ahk/keys.py @@ -1,6 +1,5 @@ """ -The ahk.keys module contains some useful classes for working with 'special' keys. -It also +The ahk.keys module contains some useful constants and classes for working with keys. """ diff --git a/ahk/mouse.py b/ahk/mouse.py index 7d712bc8..159e2525 100644 --- a/ahk/mouse.py +++ b/ahk/mouse.py @@ -19,9 +19,11 @@ '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: @@ -42,6 +44,12 @@ class MouseMixin(ScriptEngine): Provides mouse functionality for the AHK class """ def __init__(self, mouse_speed=2, mode=None, **kwargs): + """ + + :param mouse_speed: default mouse speed + :param mode: + :param kwargs: + """ if mode is None: mode = 'Screen' self.mode = mode diff --git a/ahk/screen.py b/ahk/screen.py index 056612d9..758ca8f6 100644 --- a/ahk/screen.py +++ b/ahk/screen.py @@ -17,26 +17,26 @@ def image_search(self, image_path: str, :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) + 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%``). + defaults to screen width and height (lower right-hand corner; ``%A_ScreenWidth%``, ``%A_ScreenHeight%``). :param color_variation: Shades of variation (up or down) for the intensity of RGB for each pixel. Equivalent of - ``*n`` option. Defaults to 0. + ``*n`` option. Defaults to 0. :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 :param transparent: Specific color in the image that will be ignored during the search. Pixels with the exact - color given will match any color. Can be used with color names e.g. (Black, Purple, Yellow) found - at https://https://www.autohotkey.com/docs/commands/Progress.htm#colors or hexadecimal values e.g. - (0xFFFFAA, 05FA15, 632511). Equivalent of ``*TransN`` option + color given will match any color. Can be used with color names e.g. (Black, Purple, Yellow) found + at https://https://www.autohotkey.com/docs/commands/Progress.htm#colors or hexadecimal values e.g. + (0xFFFFAA, 05FA15, 632511). Equivalent of ``*TransN`` option :param icon: Number of the icon group to use. Equivalent of ``*Icon`` option :return: coordinates of the upper-left pixel of where the image was found on the screen; ``None`` if the image - was not found + was not found :rtype: Union[Tuple[int, int], None] @@ -114,11 +114,11 @@ def pixel_search(self, color: Union[str, int], variation: int=0, .. _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 color: + :param variation: + :param upper_bound: + :param lower_bound: + :param coord_mode: :param fast: :param rgb: :return: the coordinates of the pixel; None if the pixel is not found diff --git a/ahk/script.py b/ahk/script.py index fa60a72f..852e1c92 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -1,3 +1,15 @@ +""" +The :py:mod:`~ahk.script` module, most essentially, houses the :py:class:`~ahk.ScriptEngine` class. + +The :py:class:`~ahk.ScriptEngine` is responsible for rendering autohotkey code from jinja templates and executing that +code. This is the heart of how this package works. Every other major component either inherits from this class +or utilizes an instance of this class. + +The current implementation of how autohotkey code is executed is by calling the autohotkey +executable with ``subprocess``. + + +""" import os import subprocess import warnings @@ -14,6 +26,7 @@ class ExecutableNotFoundError(EnvironmentError): DEFAULT_EXECUTABLE_PATH = r"C:\Program Files\AutoHotkey\AutoHotkey.exe" +"""The deafult path to look for AutoHotkey, if not specified some other way""" def _resolve_executable_path(executable_path: str = ''): @@ -54,11 +67,18 @@ class ScriptEngine(object): def __init__(self, executable_path: str = "", **kwargs): """ + This class is typically not used directly. AHK components inherit from this class + and the arguments for this class should usually be passed in to :py:class:`~ahk.AHK`. + :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 provided explicitly in this argument, the path to the AHK executable is resolved in the following order: + + * The ``AHK_PATH`` environment variable, if present + * :py:data:`~ahk.script.DEFAULT_EXECUTABLE_PATH` if the file exists + + If environment variable not present, tries to look for 'AutoHotkey.exe' or 'AutoHotkeyA32.exe' with shutil.which + + :raises ExecutableNotFound: if AHK executable cannot be found or the specified file does not exist """ self.executable_path = _resolve_executable_path(executable_path) @@ -67,6 +87,22 @@ def __init__(self, executable_path: str = "", **kwargs): autoescape=False, trim_blocks=True) def render_template(self, template_name, directives=None, blocking=True, **kwargs): + """ + Renders a given jinja template and returns a string of script text + + :param template_name: the name of the jinja template to render + :param directives: additional AHK directives to add to the resulting script + :param blocking: whether the template should be rendered to block (use #Persistent directive) + :param kwargs: keywords passed to template rendering + :return: An AutoHotkey script as a string + + .. code-block:: python + + >>> from ahk import AHK + >>> ahk = AHK() + >>> ahk.render_template('keyboard/send_input.ahk', s='Hello') + '#NoEnv\\n#Persistent\\n\\n\\nSendInput Hello\\n\\nExitApp\\n' + """ if directives is None: directives = set() else: @@ -104,6 +140,28 @@ def _run_script(self, script_text, **kwargs): return proc def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): + """ + Given an AutoHotkey script as a string, execute it + + :param script_text: a string containing AutoHotkey code + :param decode: If ``True``, attempt to decode the stdout of the completed process. + If ``False``, returns the completed process. Only has effect when ``blocking=True`` + :param blocking: If ``True``, script must finish before returning. + If ``False``, function returns a ``subprocess.Popen`` object immediately without blocking + :param runkwargs: keyword arguments passed to ``subprocess.Popen`` or ``subprocess.run`` + :return: | A string of the decoded stdout if ``blocking`` and ``decode`` are True. + | ``subprocess.CompletedProcess`` if ``blocking`` is True and ``decode`` is False. + | ``subprocess.Popen`` object if ``blocking`` is False. + + >>> from ahk import AHK + >>> ahk = AHK() + >>> ahk.run_script('FileAppend, Hello World, *') + 'Hello World' + >>> ahk.run_script('FileAppend, Hello World, *', decode=False) + CompletedProcess(args=['C:\\\\pathto\\\\AutoHotkey.exe', '/ErrorStdOut', '*'], returncode=0, stdout=b'Hello World', stderr=b'') + >>> ahk.run_script('FileAppend, Hello World, *', blocking=False) + + """ logger.debug('Running script text: %s', script_text) try: result = self._run_script(script_text, decode=decode, blocking=blocking, **runkwargs) diff --git a/ahk/window.py b/ahk/window.py index a6703e29..d2688415 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -18,6 +18,7 @@ def __init__(self): def click(self): """ REF: https://www.autohotkey.com/docs/commands/ControlClick.htm + :return: """ raise NotImplementedError @@ -25,6 +26,7 @@ def click(self): def focus(self): """ REF: https://www.autohotkey.com/docs/commands/ControlFocus.htm + :return: """ raise NotImplementedError @@ -32,6 +34,7 @@ def focus(self): def get(self, key): """ REF: https://www.autohotkey.com/docs/commands/ControlGet.htm + :param key: :return: """ @@ -44,6 +47,7 @@ def has_focus(self): def position(self): """ REF: https://www.autohotkey.com/docs/commands/ControlGetPos.htm + :return: """ raise NotImplementedError @@ -52,6 +56,7 @@ def position(self): def text(self): """ REF: https://www.autohotkey.com/docs/commands/ControlGetText.htm + :return: """ raise NotImplementedError @@ -60,6 +65,7 @@ def text(self): def text(self, new_text): """ REF: https://www.autohotkey.com/docs/commands/ControlSetText.htm + :param new_text: :return: """ @@ -68,6 +74,7 @@ def text(self, new_text): def move(self): """ REF: https://www.autohotkey.com/docs/commands/ControlMove.htm + :return: """ raise NotImplementedError @@ -75,6 +82,7 @@ def move(self): def send(self, raw=False): """ REF: https://www.autohotkey.com/docs/commands/ControlSend.htm + :param raw: :return: """ @@ -325,9 +333,19 @@ def always_on_top(self, value): f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') def disable(self): + """ + Distable the window + + :return: + """ self.set('Disable', '') def enable(self): + """ + Enable the window + + :return: + """ self.set('Enable', '') def redraw(self): @@ -341,6 +359,11 @@ def to_bottom(self): self.set('Bottom', '') def to_top(self): + """ + Bring the window to the foreground (above other windows) + + :return: + """ self.set('Top', '') def _render_template(self, *args, **kwargs): @@ -358,45 +381,126 @@ def _base_method(self, command, seconds_to_wait="", blocking=False): return self.engine.run_script(script, blocking=blocking) def activate(self): + """ + Activate the window. + + See also: `WinActivate`_ + + .. _WinActivate: https://www.autohotkey.com/docs/commands/WinActivate.htm + + :return: + """ self._base_method("WinActivate") def activate_buttom(self): + """ + Calls `WinActivateBottom`_ on the window + + .. _WinActivateBottom: https://www.autohotkey.com/docs/commands/WinActivateBottom.htm + + :return: + """ self._base_method("WinActivateBottom") def close(self, seconds_to_wait=""): + """ + Closes the Window. See also: `WinClose`_ + + .. _WinClose: https://www.autohotkey.com/docs/commands/WinClose.htm + + :param seconds_to_wait: + :return: + """ self._base_method("WinClose", seconds_to_wait=seconds_to_wait) def hide(self): + """ + Hides the window. See also: `WinHide`_ + + .. _WinHide: https://www.autohotkey.com/docs/commands/WinHide.htm + + + :return: + """ self._base_method("WinHide") def kill(self, seconds_to_wait=""): self._base_method("WinKill", seconds_to_wait=seconds_to_wait) def maximize(self): + """ + maximize the window + + :return: + """ self._base_method("WinMaximize") def minimize(self): + """ + minimize the window + + :return: + """ self._base_method("WinMinimize") def restore(self): + """ + restore the window + + :return: + """ self._base_method("WinRestore") def show(self): + """ + show the window + + :return: + """ self._base_method("WinShow") def wait(self, seconds_to_wait=""): + """ + + :param seconds_to_wait: + :return: + """ self._base_method("WinWait", seconds_to_wait=seconds_to_wait, blocking=True) def wait_active(self, seconds_to_wait=""): + """ + + :param seconds_to_wait: + :return: + """ self._base_method("WinWaitActive", seconds_to_wait=seconds_to_wait, blocking=True) def wait_not_active(self, seconds_to_wait=""): + """ + + :param seconds_to_wait: + :return: + """ self._base_method("WinWaitNotActive", seconds_to_wait=seconds_to_wait, blocking=True) def wait_close(self, seconds_to_wait=""): + """ + + :param seconds_to_wait: + :return: + """ self._base_method("WinWaitClose", seconds_to_wait=seconds_to_wait, blocking=True) def move(self, x='', y='', width=None, height=None): + """ + Move the window to a position and/or change its geometry + + :param x: + :param y: + :param width: + :param height: + :return: + """ script = self._render_template( 'window/win_move.ahk', title=f"ahk_id {self.id}", @@ -463,6 +567,7 @@ def _all_window_ids(self): def windows(self): """ Returns a list of windows + :return: """ windowze = [] @@ -472,6 +577,15 @@ def windows(self): return windowze def find_windows(self, func=None, **kwargs): + """ + Find all matching windows + + :param func: a callable to filter windows + :param bool exact: if False (the default) partial matches are found. If True, only exact matches are returned + :param kwargs: keywords of attributes of the window (has no effect if ``func`` is provided) + + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + """ if func is None: exact = kwargs.pop('exact', False) @@ -488,29 +602,75 @@ def func(win): yield window def find_window(self, func=None, **kwargs): + """ + Like ``find_windows`` but only returns the first found window + + + :param func: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ with suppress(StopIteration): return next(self.find_windows(func=func, **kwargs)) def find_windows_by_title(self, title, exact=False): + """ + Equivalent to ``find_windows(title=title)``` + + Note that ``title`` is a ``bytes`` object + + :param bytes title: + :param exact: + :return: + """ for window in self.find_windows(title=title, exact=exact): yield window def find_window_by_title(self, *args, **kwargs): + """ + Like ``find_windows_by_title`` but only returns the first result. + + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ with suppress(StopIteration): return next(self.find_windows_by_title(*args, **kwargs)) def find_windows_by_text(self, text, exact=False): + """ + + :param text: + :param exact: + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + """ for window in self.find_windows(text=text, exact=exact): yield window def find_window_by_text(self, *args, **kwargs): + """ + + :param args: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ with suppress(StopIteration): return next(self.find_windows_by_text(*args, **kwargs)) def find_windows_by_class(self, class_name, exact=False): + """ + + :param class_name: + :param exact: + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + """ for window in self.find_windows(class_name=class_name, exact=exact): yield window def find_window_by_class(self, *args, **kwargs): + """ + + :param args: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ with suppress(StopIteration): return next(self.find_windows_by_class(*args, **kwargs)) From 35b00e26a24f230b2f3115d53df0c852321e523b Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 5 Apr 2020 06:36:07 -0700 Subject: [PATCH 054/588] sphinx docs --- docs/Makefile | 19 +++++ docs/api/autohotkey.rst | 7 ++ docs/api/directives.rst | 5 ++ docs/api/index.rst | 14 +++ docs/api/keyboard.rst | 6 ++ docs/api/keys.rst | 12 +++ docs/api/mouse.rst | 6 ++ docs/api/registery.rst | 6 ++ docs/api/screen.rst | 6 ++ docs/api/script.rst | 9 ++ docs/api/sound.rst | 6 ++ docs/api/utils.rst | 6 ++ docs/api/window.rst | 6 ++ docs/conf.py | 183 ++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 24 ++++++ docs/make.bat | 35 ++++++++ docs/quickstart.rst | 33 ++++++++ 17 files changed, 383 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/api/autohotkey.rst create mode 100644 docs/api/directives.rst create mode 100644 docs/api/index.rst create mode 100644 docs/api/keyboard.rst create mode 100644 docs/api/keys.rst create mode 100644 docs/api/mouse.rst create mode 100644 docs/api/registery.rst create mode 100644 docs/api/screen.rst create mode 100644 docs/api/script.rst create mode 100644 docs/api/sound.rst create mode 100644 docs/api/utils.rst create mode 100644 docs/api/window.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/quickstart.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..298ea9e2 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +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) \ No newline at end of file diff --git a/docs/api/autohotkey.rst b/docs/api/autohotkey.rst new file mode 100644 index 00000000..2ddaa0b2 --- /dev/null +++ b/docs/api/autohotkey.rst @@ -0,0 +1,7 @@ +Autohotkey +========== + +.. automodule:: ahk.autohotkey + :members: + :undoc-members: + diff --git a/docs/api/directives.rst b/docs/api/directives.rst new file mode 100644 index 00000000..a099036f --- /dev/null +++ b/docs/api/directives.rst @@ -0,0 +1,5 @@ +Directives +---------- + +.. automodule:: ahk.directives + :members: diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 00000000..3737c2d5 --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,14 @@ +API +=== + +This part of the documentation is intended for developers looking to contribute to this project or discover more +about the programming interface. A lot of this is auto-generated documentation from the docstrings. + + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + :glob: + + * + diff --git a/docs/api/keyboard.rst b/docs/api/keyboard.rst new file mode 100644 index 00000000..56d7739e --- /dev/null +++ b/docs/api/keyboard.rst @@ -0,0 +1,6 @@ +Keyboard +======== + +.. automodule:: ahk.keyboard + :members: + :undoc-members: diff --git a/docs/api/keys.rst b/docs/api/keys.rst new file mode 100644 index 00000000..81a77c3d --- /dev/null +++ b/docs/api/keys.rst @@ -0,0 +1,12 @@ +Keys +==== + +.. automodule:: ahk.keys + :members: + :undoc-members: + + .. autoclass:: KEYS + :members: + :undoc-members: + + diff --git a/docs/api/mouse.rst b/docs/api/mouse.rst new file mode 100644 index 00000000..afa55935 --- /dev/null +++ b/docs/api/mouse.rst @@ -0,0 +1,6 @@ +Mouse +===== + +.. automodule:: ahk.mouse + :members: + :undoc-members: diff --git a/docs/api/registery.rst b/docs/api/registery.rst new file mode 100644 index 00000000..ea71fb6a --- /dev/null +++ b/docs/api/registery.rst @@ -0,0 +1,6 @@ +Registery +========= + +.. automodule:: ahk.registery + :members: + :undoc-members: diff --git a/docs/api/screen.rst b/docs/api/screen.rst new file mode 100644 index 00000000..cf3cea41 --- /dev/null +++ b/docs/api/screen.rst @@ -0,0 +1,6 @@ +Screen +====== + +.. automodule:: ahk.screen + :members: + :undoc-members: diff --git a/docs/api/script.rst b/docs/api/script.rst new file mode 100644 index 00000000..1b064773 --- /dev/null +++ b/docs/api/script.rst @@ -0,0 +1,9 @@ +The Script Engine +----------------- + +.. automodule:: ahk.script + :undoc-members: + :members: + + .. autodata:: DEFAULT_EXECUTABLE_PATH + :annotation: = C:\Program Files\AutoHotkey\AutoHotkey.exe diff --git a/docs/api/sound.rst b/docs/api/sound.rst new file mode 100644 index 00000000..1c66db57 --- /dev/null +++ b/docs/api/sound.rst @@ -0,0 +1,6 @@ +Sound +===== + +.. automodule:: ahk.sound + :members: + :undoc-members: diff --git a/docs/api/utils.rst b/docs/api/utils.rst new file mode 100644 index 00000000..b866f8fe --- /dev/null +++ b/docs/api/utils.rst @@ -0,0 +1,6 @@ +Utils +===== + +.. automodule:: ahk.utils + :members: + :undoc-members: diff --git a/docs/api/window.rst b/docs/api/window.rst new file mode 100644 index 00000000..4705bc6e --- /dev/null +++ b/docs/api/window.rst @@ -0,0 +1,6 @@ +Window +====== + +.. automodule:: ahk.window + :members: + :undoc-members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..72ea733a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../')) + + +# -- Project information ----------------------------------------------------- + +project = 'ahk' +copyright = '2020, Spencer Phillip Young' +author = 'Spencer Phillip Young' + +# The short X.Y version +version = '' +# The full version, including alpha/beta/rc tags +release = '0.8.1' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx_autodoc_typehints', + 'sphinx.ext.viewcode', +] + +autodoc_default_options = { + 'special-members': '__init__' +} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ahkdoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'ahk.tex', 'ahk Documentation', + 'Spencer Phillip Young', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'ahk', 'ahk Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'ahk', 'ahk Documentation', + author, 'ahk', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + +# -- Extension configuration ------------------------------------------------- \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..179e3c8c --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,24 @@ +.. 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. + +Welcome to ahk's documentation! +=============================== + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + + quickstart + api/index + + + + +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..7893348a --- /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 + +if "%1" == "" goto help + +%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.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 00000000..935a4e15 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,33 @@ +Quickstart +========== + +This document assumes you have Python 3.6 or newer installed + +Installing AHK +-------------- + +AHK requires the AutoHotkey software in addition to the Python package + +1. Download and install AutoHotkey (1.1.x). It can be downloaded from the `autohotkey website`_. + +2. Install the Python ``ahk`` package :: + + py -m pip install ahk + +3. Write your first script:: + + from ahk import AHK + ahk = AHK() + ahk.run_script('Run Notepad') + notepad_window = ahk.find_window_by_title(b'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/ From a0043b4d4f82cc8c30326badb404407e30c24c3b Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 5 Apr 2020 06:53:52 -0700 Subject: [PATCH 055/588] include readme in docs --- README.md | 390 +------------------------------------------- docs/README.md | 389 +++++++++++++++++++++++++++++++++++++++++++ docs/conf.py | 6 +- docs/index.rst | 9 +- docs/quickstart.rst | 2 +- setup.py | 2 +- 6 files changed, 402 insertions(+), 396 deletions(-) mode change 100644 => 120000 README.md create mode 100644 docs/README.md diff --git a/README.md b/README.md deleted file mode 100644 index 341ce7d2..00000000 --- a/README.md +++ /dev/null @@ -1,389 +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) -![PyPI - Downloads](https://img.shields.io/pypi/dm/ahk) - -# Installation - -``` -pip install ahk -``` -Requires Python 3.6+ - -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/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 # Returns a tuple of mouse coordinates (x, y) -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.double_click() # Clicks the primary mouse button twice -ahk.click(200, 200) # Moves the mouse to a particular position and clicks -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 ahk string escapes) -ahk.send_input('Hello`, World{!}') # Like AHK SendInput, must escape strings yourself! -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.key_wait('a', timeout=3) # Wait up to 3 seconds for the "a" key to be pressed. NOTE: This throws - # a TimeoutError if the key isn't pressed within the timeout window -``` - -## 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) # the 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') # Open notepad -win = ahk.find_window(title=b'Untitled - Notepad') # Find the opened window - -win.send('hello') # Send keys directly to the window (does not need focus!) -win.move(x=200, y=300, width=500, height=800) - -win.activate() # Give the window focus -win.activate_buttom() # Give the window focus -win.close() # Close the window -win.hide() # Hide the windwow -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.always_on_top = True # Make the window always on top - -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) - - -if window.active: # Check if window active - window.minimize() - -if window.exist: # Check if window exist - window.maximize() - -``` - -## 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('0x9d6346') # Get coords of the 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 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 -``` - -## 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 start 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' # Define an AutoHotkey key combonation -script = 'Run Notepad' # Define an ahk script -hotkey = Hotkey(ahk, key_combo, script) # Create Hotkey -hotkey.start() # Start listening for hotkey -``` -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() -``` - -See also the [relevant AHK documentation](https://www.autohotkey.com/docs/Hotkeys.htm) - -### 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() - -# An Action Chain doesn't perform the actions until perform() is called on the chain - -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) -``` -(See the [logging module documentation](https://docs.python.org/3/library/logging.html) for more information) - -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/README.md b/README.md new file mode 120000 index 00000000..0e01b430 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +docs/README.md \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..341ce7d2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,389 @@ +# 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) +![PyPI - Downloads](https://img.shields.io/pypi/dm/ahk) + +# Installation + +``` +pip install ahk +``` +Requires Python 3.6+ + +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/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 # Returns a tuple of mouse coordinates (x, y) +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.double_click() # Clicks the primary mouse button twice +ahk.click(200, 200) # Moves the mouse to a particular position and clicks +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 ahk string escapes) +ahk.send_input('Hello`, World{!}') # Like AHK SendInput, must escape strings yourself! +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.key_wait('a', timeout=3) # Wait up to 3 seconds for the "a" key to be pressed. NOTE: This throws + # a TimeoutError if the key isn't pressed within the timeout window +``` + +## 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) # the 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') # Open notepad +win = ahk.find_window(title=b'Untitled - Notepad') # Find the opened window + +win.send('hello') # Send keys directly to the window (does not need focus!) +win.move(x=200, y=300, width=500, height=800) + +win.activate() # Give the window focus +win.activate_buttom() # Give the window focus +win.close() # Close the window +win.hide() # Hide the windwow +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.always_on_top = True # Make the window always on top + +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) + + +if window.active: # Check if window active + window.minimize() + +if window.exist: # Check if window exist + window.maximize() + +``` + +## 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('0x9d6346') # Get coords of the 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 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 +``` + +## 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 start 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' # Define an AutoHotkey key combonation +script = 'Run Notepad' # Define an ahk script +hotkey = Hotkey(ahk, key_combo, script) # Create Hotkey +hotkey.start() # Start listening for hotkey +``` +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() +``` + +See also the [relevant AHK documentation](https://www.autohotkey.com/docs/Hotkeys.htm) + +### 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() + +# An Action Chain doesn't perform the actions until perform() is called on the chain + +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) +``` +(See the [logging module documentation](https://docs.python.org/3/library/logging.html) for more information) + +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/docs/conf.py b/docs/conf.py index 72ea733a..2c31e7c8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,6 +42,7 @@ 'sphinx.ext.autodoc', 'sphinx_autodoc_typehints', 'sphinx.ext.viewcode', + 'm2r', ] autodoc_default_options = { @@ -54,8 +55,7 @@ # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ['.rst', '.md'] # The master toctree document. master_doc = 'index' @@ -157,7 +157,7 @@ # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'ahk', 'ahk Documentation', - author, 'ahk', 'One line description of project.', + author, 'ahk', 'Python wrapper for AHK.', 'Miscellaneous'), ] diff --git a/docs/index.rst b/docs/index.rst index 179e3c8c..38280869 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,8 +3,12 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to ahk's documentation! -=============================== +ahk Python wrapper documentation +================================ + +`GitHub`_ + +.. _GitHub: https://github.com/spyoungtech/ahk .. toctree:: :maxdepth: 3 @@ -12,6 +16,7 @@ Welcome to ahk's documentation! quickstart api/index + README diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 935a4e15..cdc5a3d1 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -25,7 +25,7 @@ AHK requires the AutoHotkey software in addition to the Python package 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 +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') diff --git a/setup.py b/setup.py index 9bb40eb1..08e2d9f9 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ test_requirements = ['behave', 'behave-classy', 'pytest'] extras = {'test': test_requirements} -with open('README.md', encoding='utf-8') as f: +with open('docs/README.md', encoding='utf-8') as f: long_description = f.read() setup( From ab374a56428a5bb9586c54a7f2885068384a0ee4 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 5 Apr 2020 07:09:13 -0700 Subject: [PATCH 056/588] readthedocs requirements --- docs/docrequirements.txt | 73 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/docrequirements.txt diff --git a/docs/docrequirements.txt b/docs/docrequirements.txt new file mode 100644 index 00000000..5a1fecf9 --- /dev/null +++ b/docs/docrequirements.txt @@ -0,0 +1,73 @@ +alabaster==0.7.12 +appdirs==1.4.3 +atomicwrites==1.2.1 +attrs==18.2.0 +Babel==2.6.0 +backcall==0.1.0 +behave==1.2.6 +behave-classy==1.0.0 +black==19.3b0 +bleach==3.0.2 +certifi==2018.10.15 +chardet==3.0.4 +Click==7.0 +colorama==0.4.1 +commonmark==0.9.1 +coverage==4.5.2 +coveralls==1.8.1 +decorator==4.3.0 +docopt==0.6.2 +docutils==0.14 +idna==2.7 +imagesize==1.1.0 +ipython==7.1.1 +ipython-genutils==0.2.0 +jedi==0.13.1 +Jinja2==2.10 +keyboard==0.13.3 +m2r==0.2.1 +MarkupSafe==1.1.0 +mistune==0.8.4 +more-itertools==4.3.0 +packaging==18.0 +parse==1.9.0 +parse-type==0.4.2 +parso==0.3.1 +pickleshare==0.7.5 +Pillow==7.0.0 +pkginfo==1.4.2 +pluggy==0.8.0 +prompt-toolkit==2.0.7 +py==1.7.0 +PyAutoGUI==0.9.38 +Pygments==2.3.0 +PyMsgBox==1.0.6 +pyparsing==2.3.0 +PyScreeze==0.1.18 +pytest==4.0.1 +PyTweening==1.0.3 +pytz==2018.7 +pywin32==224 +readme-renderer==24.0 +recommonmark==0.6.0 +requests==2.20.1 +requests-toolbelt==0.8.0 +six==1.11.0 +snowballstemmer==1.2.1 +Sphinx==2.4.4 +sphinx-autodoc-typehints==1.10.3 +sphinx-rtd-theme==0.4.3 +sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==1.0.3 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.4 +sphinxcontrib-websupport==1.1.0 +toml==0.10.0 +tqdm==4.28.1 +traitlets==4.3.2 +twine==1.12.2 +urllib3==1.24.1 +wcwidth==0.1.7 +webencodings==0.5.1 From f0c723883874d0a589fa1248b5064af9c42e0045 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 5 Apr 2020 07:13:08 -0700 Subject: [PATCH 057/588] trim doc requirements --- docs/docrequirements.txt | 78 +++------------------------------------- 1 file changed, 5 insertions(+), 73 deletions(-) diff --git a/docs/docrequirements.txt b/docs/docrequirements.txt index 5a1fecf9..ff4815c8 100644 --- a/docs/docrequirements.txt +++ b/docs/docrequirements.txt @@ -1,73 +1,5 @@ -alabaster==0.7.12 -appdirs==1.4.3 -atomicwrites==1.2.1 -attrs==18.2.0 -Babel==2.6.0 -backcall==0.1.0 -behave==1.2.6 -behave-classy==1.0.0 -black==19.3b0 -bleach==3.0.2 -certifi==2018.10.15 -chardet==3.0.4 -Click==7.0 -colorama==0.4.1 -commonmark==0.9.1 -coverage==4.5.2 -coveralls==1.8.1 -decorator==4.3.0 -docopt==0.6.2 -docutils==0.14 -idna==2.7 -imagesize==1.1.0 -ipython==7.1.1 -ipython-genutils==0.2.0 -jedi==0.13.1 -Jinja2==2.10 -keyboard==0.13.3 -m2r==0.2.1 -MarkupSafe==1.1.0 -mistune==0.8.4 -more-itertools==4.3.0 -packaging==18.0 -parse==1.9.0 -parse-type==0.4.2 -parso==0.3.1 -pickleshare==0.7.5 -Pillow==7.0.0 -pkginfo==1.4.2 -pluggy==0.8.0 -prompt-toolkit==2.0.7 -py==1.7.0 -PyAutoGUI==0.9.38 -Pygments==2.3.0 -PyMsgBox==1.0.6 -pyparsing==2.3.0 -PyScreeze==0.1.18 -pytest==4.0.1 -PyTweening==1.0.3 -pytz==2018.7 -pywin32==224 -readme-renderer==24.0 -recommonmark==0.6.0 -requests==2.20.1 -requests-toolbelt==0.8.0 -six==1.11.0 -snowballstemmer==1.2.1 -Sphinx==2.4.4 -sphinx-autodoc-typehints==1.10.3 -sphinx-rtd-theme==0.4.3 -sphinxcontrib-applehelp==1.0.2 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==1.0.3 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.4 -sphinxcontrib-websupport==1.1.0 -toml==0.10.0 -tqdm==4.28.1 -traitlets==4.3.2 -twine==1.12.2 -urllib3==1.24.1 -wcwidth==0.1.7 -webencodings==0.5.1 +sphinx +sphinx-rtd-theme +sphinx-autodoc-typehints +m2r +jinja2 From d354a02f075c117f3ef3149fb54ec19f7fc68ed6 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 5 Apr 2020 07:15:51 -0700 Subject: [PATCH 058/588] add badge --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index 341ce7d2..fb68fcf1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,7 @@ A Python wrapper around AHK. +[![Docs](https://readthedocs.org/projects/ahk/badge/?version=latest)](https://ahk.readthedocs.io/en/latest/?badge=latest) [![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/) From 2fe7d764e10f537bba68d7fe2d947771a448a39b Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Mon, 6 Apr 2020 05:34:42 -0700 Subject: [PATCH 059/588] update manifest --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index bd895033..e0a09d6b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ include LICENSE -include README.md +include docs/README.md recursive-include ahk/templates * From eebac6a8160441db0ef72b8a1fb91d2be5ec8db3 Mon Sep 17 00:00:00 2001 From: Stephen Diniz Date: Sat, 2 May 2020 22:07:02 -0400 Subject: [PATCH 060/588] Adding Window.click functionality - using ControlClick --- ahk/templates/window/win_click.ahk | 4 ++++ ahk/window.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 ahk/templates/window/win_click.ahk diff --git a/ahk/templates/window/win_click.ahk b/ahk/templates/window/win_click.ahk new file mode 100644 index 00000000..f84a9393 --- /dev/null +++ b/ahk/templates/window/win_click.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +ControlClick, x{{ x }} y{{ y }}, {{ hwnd }} +{% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index d2688415..d61b05d9 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -523,6 +523,18 @@ def send(self, keys, delay=None, raw=False, blocking=False, escape=False): ) return self.engine.run_script(script, blocking=blocking) + def click(self, x, y, blocking=False): + """ + Click at an x/y location on the screen. + Uses ControlClick + https://autohotkey.com/docs/commands/ControlClick.htm + """ + script = self._render_template( + 'window/win_click.ahk', + x=x, y=y, hwnd=f"ahk_id {self.id}" + ) + return self.engine.run_script(script, blocking=blocking) + def __eq__(self, other): if not isinstance(other, Window): return False From 2ba8a66b39f974cf27554746a717a0e2bb689d46 Mon Sep 17 00:00:00 2001 From: Stephen Diniz Date: Mon, 4 May 2020 16:48:01 -0400 Subject: [PATCH 061/588] Adding Key Delay to the win_send.ahk template --- ahk/templates/window/win_send.ahk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ahk/templates/window/win_send.ahk b/ahk/templates/window/win_send.ahk index 39bbddd4..eecb5c40 100644 --- a/ahk/templates/window/win_send.ahk +++ b/ahk/templates/window/win_send.ahk @@ -1,4 +1,6 @@ {% extends "base.ahk" %} {% block body %} +{% if delay %}SetKeyDelay, {{ delay }}{% endif %} + {% if raw %}ControlSendRaw{% else %}ControlSend{% endif %}, , {{ keys }}, {{ title }} {% endblock body %} From 3581736e9fc040d0a1c61f00082bf500195facdb Mon Sep 17 00:00:00 2001 From: Stephen Diniz Date: Mon, 4 May 2020 17:38:45 -0400 Subject: [PATCH 062/588] Updating Window.send to also have a press duration --- ahk/templates/window/win_send.ahk | 2 +- ahk/window.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ahk/templates/window/win_send.ahk b/ahk/templates/window/win_send.ahk index eecb5c40..889de91c 100644 --- a/ahk/templates/window/win_send.ahk +++ b/ahk/templates/window/win_send.ahk @@ -1,6 +1,6 @@ {% extends "base.ahk" %} {% block body %} -{% if delay %}SetKeyDelay, {{ delay }}{% endif %} +SetKeyDelay, {{ delay }}, {{ press_duration }} {% if raw %}ControlSendRaw{% else %}ControlSend{% endif %}, , {{ keys }}, {{ title }} {% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index d2688415..3d612e4e 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -508,7 +508,7 @@ def move(self, x='', y='', width=None, height=None): ) self.engine.run_script(script) - def send(self, keys, delay=None, raw=False, blocking=False, escape=False): + def send(self, keys, delay=10, press_duration=-1, raw=False, blocking=False, escape=False): """ Send keystrokes directly to the window. Uses ControlSend @@ -519,7 +519,20 @@ def send(self, keys, delay=None, raw=False, blocking=False, escape=False): script = self._render_template( 'window/win_send.ahk', title=f"ahk_id {self.id}", - keys=keys, raw=raw, delay=delay, blocking=blocking + keys=keys, raw=raw, delay=delay, + press_duration=press_duration, blocking=blocking + ) + return self.engine.run_script(script, blocking=blocking) + + def click(self, x, y, blocking=False): + """ + Click at an x/y location on the screen. + Uses ControlClick + https://autohotkey.com/docs/commands/ControlClick.htm + """ + script = self._render_template( + 'window/win_click.ahk', + x=x, y=y, hwnd=f"ahk_id {self.id}" ) return self.engine.run_script(script, blocking=blocking) From 79c0350692708a568516c604b39c6061e522de2e Mon Sep 17 00:00:00 2001 From: Stephen Diniz Date: Mon, 4 May 2020 18:02:52 -0400 Subject: [PATCH 063/588] Removing other PR content --- ahk/window.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ahk/window.py b/ahk/window.py index 3d612e4e..eeef8551 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -524,18 +524,6 @@ def send(self, keys, delay=10, press_duration=-1, raw=False, blocking=False, esc ) return self.engine.run_script(script, blocking=blocking) - def click(self, x, y, blocking=False): - """ - Click at an x/y location on the screen. - Uses ControlClick - https://autohotkey.com/docs/commands/ControlClick.htm - """ - script = self._render_template( - 'window/win_click.ahk', - x=x, y=y, hwnd=f"ahk_id {self.id}" - ) - return self.engine.run_script(script, blocking=blocking) - def __eq__(self, other): if not isinstance(other, Window): return False From 231df62e755ccc2101f53573a05c7ea38857d32e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 15 May 2020 19:03:21 -0700 Subject: [PATCH 064/588] Update ahk/window.py ensure signature is backwards compatible --- ahk/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/window.py b/ahk/window.py index eeef8551..8756cb7e 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -508,7 +508,7 @@ def move(self, x='', y='', width=None, height=None): ) self.engine.run_script(script) - def send(self, keys, delay=10, press_duration=-1, raw=False, blocking=False, escape=False): + def send(self, keys, delay=10, raw=False, blocking=False, escape=False, press_duration=-1): """ Send keystrokes directly to the window. Uses ControlSend From 24aabb28c8e69ca57543256d0aeccd996a811782 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 30 May 2020 01:14:38 -0700 Subject: [PATCH 065/588] fix typo in `activate_bottom` --- ahk/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/window.py b/ahk/window.py index bf4df5c9..a09a3911 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -392,7 +392,7 @@ def activate(self): """ self._base_method("WinActivate") - def activate_buttom(self): + def activate_bottom(self): """ Calls `WinActivateBottom`_ on the window From 7e4d158faa77e163f3c18e3dd6c9bf7c0e108005 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 30 May 2020 01:25:20 -0700 Subject: [PATCH 066/588] version 0.9.0 :package: --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 08e2d9f9..06714fc3 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.8.1', + version='0.9.0', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From 96f373555b79dd8571a2b5c2d26682760da5fcbf Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 23 Jul 2020 16:20:32 -0700 Subject: [PATCH 067/588] fix hotkey parameter passing --- ahk/keyboard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ahk/keyboard.py b/ahk/keyboard.py index cf28da1f..d96aa27e 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -70,7 +70,8 @@ def hotkey(self, *args, **kwargs): :param script: The script to execute when the hotkey is activated (AutoHotkey code as a string) :return: an :py:class:`~ahk.keyboard.Hotkey` instance """ - return Hotkey(engine=self, *args, **kwargs) + engine = kwargs.pop('engine', self) + return Hotkey(engine, *args, **kwargs) def key_state(self, key_name, mode=None) -> bool: """ From a84798f58db56148799dbbf0db8ccfbcde91f7a7 Mon Sep 17 00:00:00 2001 From: Yunus Emre <49655146+yedhrab@users.noreply.github.com> Date: Mon, 7 Sep 2020 13:45:21 +0300 Subject: [PATCH 068/588] Added ToolTip, TrayTip (#92) Added ToolTip, TrayTip (#92) --- ahk/autohotkey.py | 19 ++--- ahk/gui.py | 126 ++++++++++++++++++++++++++++++++++ ahk/templates/gui/tooltip.ahk | 6 ++ ahk/templates/gui/traytip.ahk | 16 +++++ docs/README.md | 13 +++- tests/unittests/test_gui.py | 28 ++++++++ 6 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 ahk/gui.py create mode 100644 ahk/templates/gui/tooltip.ahk create mode 100644 ahk/templates/gui/traytip.ahk create mode 100644 tests/unittests/test_gui.py diff --git a/ahk/autohotkey.py b/ahk/autohotkey.py index d33f0815..30f01334 100644 --- a/ahk/autohotkey.py +++ b/ahk/autohotkey.py @@ -1,17 +1,15 @@ from collections import deque + +from ahk.keyboard import KeyboardMixin from ahk.mouse import MouseMixin -from ahk.window import Window, WindowMixin -from ahk.script import ScriptEngine +from ahk.registery import RegisteryMixin from ahk.screen import ScreenMixin -from ahk.keyboard import KeyboardMixin from ahk.sound import SoundMixin -from ahk.registery import RegisteryMixin +from ahk.window import WindowMixin +from ahk.gui import GUIMixin -class AHK( - WindowMixin, MouseMixin, KeyboardMixin, - ScreenMixin, SoundMixin, RegisteryMixin -): +class AHK(WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin, RegisteryMixin, GUIMixin): """ Inherits its methods from the following classes: @@ -21,7 +19,9 @@ class AHK( | :py:class:`ahk.screen.ScreenMixin` | :py:class:`ahk.sound.SoundMixin` | :py:class:`ahk.registery.RegisteryMixin` + | :py:class:`ahk.gui.GUIMixin` """ + pass @@ -29,6 +29,7 @@ class ActionChain(AHK): """ Reusable action chain to execute various actions in order """ + def __init__(self, *args, **kwargs): self._actions = deque() super().__init__(*args, **kwargs) @@ -53,5 +54,5 @@ def sleep(self, n): :return: """ n = n * 1000 # convert to milliseconds - script = self.render_template('base.ahk', body=f'Sleep {n}', directives={'#Persistent', }) + script = self.render_template('base.ahk', body=f'Sleep {n}', directives={'#Persistent',}) self.run_script(script) diff --git a/ahk/gui.py b/ahk/gui.py new file mode 100644 index 00000000..d8864607 --- /dev/null +++ b/ahk/gui.py @@ -0,0 +1,126 @@ +from ahk.script import ScriptEngine + + +class GUIMixin(ScriptEngine): + + TRAYTIP_INFO = 1 + TRAYTIP_WARNING = 2 + TRAYTIP_ERROR = 3 + + def __init__(self, *args, **kwargs): + self.window_encoding = kwargs.pop('window_encoding', None) + super().__init__(*args, **kwargs) + + def show_tooltip(self, text: str, second=1.0, x="", y="", id="", blocking=True): + """Show ToolTip + + https://www.autohotkey.com/docs/commands/ToolTip.htm + + :param text: Tooltip text + :type text: str + :param ms: Wait time (s), defaults to 1000 + :type ms: int, optional + :param x: X coordinate relative to active window, defaults to "" + :type x: str, optional + :param y: Y coordinate relative to active window, defaults to "" + :type y: str, optional + :param id: ID of ToolTip for more ToolTip message, defaults to "" + :type id: str, optional + :raises ValueError: ID must be between [1, 20] + """ + + if id and not (1 <= int(id) <= 20): + raise ValueError("ID value must be between [1, 20]") + + encoded_text = "% " + "".join([f"Chr({hex(ord(char))})" for char in text]) + script = self.render_template("gui/tooltip.ahk", text=encoded_text, second=second, x=x, y=y, id=id) + self.run_script(script, blocking=blocking) + + def _show_traytip( + self, title: str, text: str, second=1.0, type_id=1, slient=False, large_icon=False, blocking=True + ): + """Show TrayTip (Windows 10 toast notification) + + https://www.autohotkey.com/docs/commands/TrayTip.htm + + :param title: Title of notification + :type title: str + :param text: Content of notification + :type text: str + :param second: Wait time (s) to be disappeared, defaults to 1.0 + :type second: float, optional + :param type_id: Notification type `TRAYTIP_`, defaults to 1 + :type type_id: int, optional + :param slient: Shows toast without sound, defaults to False + :type slient: bool, optional + :param large_icon: Shows toast with large icon, defaults to False + :type large_icon: bool, optional + """ + + encoded_title = "% " + "".join([f"Chr({hex(ord(char))})" for char in title]) + encoded_text = "% " + "".join([f"Chr({hex(ord(char))})" for char in text]) + option = type_id + (16 if slient else 0) + (32 if large_icon else 0) + script = self.render_template( + "gui/traytip.ahk", title=encoded_title, text=encoded_text, second=second, option=option + ) + self.run_script(script, blocking=blocking) + + def show_info_traytip(self, title: str, text: str, second=1.0, slient=False, large_icon=False, blocking=True): + """Show TrayTip with info icon (Windows 10 toast notification) + + https://www.autohotkey.com/docs/commands/TrayTip.htm + + :param title: Title of notification + :type title: str + :param text: Content of notification + :type text: str + :param second: Wait time (s) to be disappeared, defaults to 1.0 + :type second: float, optional + :param slient: Shows toast without sound, defaults to False + :type slient: bool, optional + :param large_icon: Shows toast with large icon, defaults to False + :type large_icon: bool, optional + :param blocked: Block program, defaults to True + :type blocked: bool, optional + """ + return self._show_traytip(title, text, second, self.TRAYTIP_INFO, slient, large_icon, blocking) + + def show_warning_traytip(self, title: str, text: str, second=1.0, slient=False, large_icon=False, blocking=True): + """Show TrayTip with warning icon (Windows 10 toast notification) + + https://www.autohotkey.com/docs/commands/TrayTip.htm + + :param title: Title of notification + :type title: str + :param text: Content of notification + :type text: str + :param second: Wait time (s) to be disappeared, defaults to 1.0 + :type second: float, optional + :param slient: Shows toast without sound, defaults to False + :type slient: bool, optional + :param large_icon: Shows toast with large icon, defaults to False + :type large_icon: bool, optional + :param blocked: Block program, defaults to True + :type blocked: bool, optional + """ + return self._show_traytip(title, text, second, self.TRAYTIP_WARNING, slient, large_icon, blocking) + + def show_error_traytip(self, title: str, text: str, second=1.0, slient=False, large_icon=False, blocking=True): + """Show TrayTip with error icon (Windows 10 toast notification) + + https://www.autohotkey.com/docs/commands/TrayTip.htm + + :param title: Title of notification + :type title: str + :param text: Content of notification + :type text: str + :param second: Wait time (s) to be disappeared, defaults to 1.0 + :type second: float, optional + :param slient: Shows toast without sound, defaults to False + :type slient: bool, optional + :param large_icon: Shows toast with large icon, defaults to False + :type large_icon: bool, optional + :param blocked: Block program, defaults to True + :type blocked: bool, optional + """ + return self._show_traytip(title, text, second, self.TRAYTIP_ERROR, slient, large_icon, blocking) diff --git a/ahk/templates/gui/tooltip.ahk b/ahk/templates/gui/tooltip.ahk new file mode 100644 index 00000000..75b43067 --- /dev/null +++ b/ahk/templates/gui/tooltip.ahk @@ -0,0 +1,6 @@ +{% extends "base.ahk"%} +{% block body %} +ToolTip, {{ text }}, {{ x }}, {{ y }}, {{ id }} +Sleep, {{ second * 1000 }} +ToolTip ,,,, {{ id }} +{% endblock body %} diff --git a/ahk/templates/gui/traytip.ahk b/ahk/templates/gui/traytip.ahk new file mode 100644 index 00000000..8ca3d532 --- /dev/null +++ b/ahk/templates/gui/traytip.ahk @@ -0,0 +1,16 @@ +{% extends "base.ahk"%} +{% block body %} +TrayTip {{ title }}, {{ text }}, {{ second }}, {{ option }} +Sleep {{ second * 1000 }} +HideTrayTip() + +; Copy this function into your script to use it. +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 body %} diff --git a/docs/README.md b/docs/README.md index fb68fcf1..02ca9bf6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -172,6 +172,18 @@ ahk.sound_get(device_number=1, component_type='MASTER', control_type='VOLUME') # ahk.sound_set(50, device_number=1, component_type='MASTER', control_type='VOLUME') # Set sound device property ``` +## GUI + +```python +from ahk import AHK + +ahk = AHK() +ahk.show_tooltip("hello4", second=2, x=10, y=10) # ToolTip +ahk.show_info_traytip("Info", "It's also info", slient=False, blocking=True) # Default info traytip +ahk.show_warning_traytip("Warning", "It's warning") # Warning traytip +ahk.show_error_traytip("Error", "It's error") # Error trytip +``` + ## 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. @@ -387,4 +399,3 @@ These are some similar projects that are commonly used for automation with Pytho * [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/tests/unittests/test_gui.py b/tests/unittests/test_gui.py new file mode 100644 index 00000000..0f3adddd --- /dev/null +++ b/tests/unittests/test_gui.py @@ -0,0 +1,28 @@ +from pytest import fixture, raises + +from ahk import AHK + + +class TestGuiMixin: + @fixture(scope="class") + def ahk(self) -> AHK: + return AHK() + + def test_show_tooltip(self, ahk: AHK): + ahk.show_tooltip("hello") + ahk.show_tooltip("🚀 Hello unicode 🚀", second=2) + ahk.show_tooltip("⽲ hello3", x=10, y=10) + ahk.show_tooltip("hello4", second=2, x=10, y=10) + + with raises(ValueError): + ahk.show_tooltip("hello", id=30) + + def test_show_traytip(self, ahk: AHK): + ahk._show_traytip("⽲ Normal 🚀", "It's me") + ahk._show_traytip("🐌 Slow 🐌", "It's you", second=2) + ahk._show_traytip("Info", "It's info", type_id=ahk.TRAYTIP_INFO) + ahk.show_info_traytip("Info", "It's also info") + ahk.show_warning_traytip("Warning", "It's warning") + ahk.show_error_traytip("Error", "It's error") + ahk._show_traytip("Slient - Info", "It's info", type_id=ahk.TRAYTIP_INFO, slient=True) + ahk.show_info_traytip("Unicode Threaded", "şüğı", blocking=False) # Need help From 1b27b0a79381168271e2551812a9fc8d37289f31 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 7 Sep 2020 05:20:45 -0700 Subject: [PATCH 069/588] Update docrequirements.txt --- docs/docrequirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docrequirements.txt b/docs/docrequirements.txt index ff4815c8..61cb3b8e 100644 --- a/docs/docrequirements.txt +++ b/docs/docrequirements.txt @@ -1,5 +1,5 @@ -sphinx +sphinx<3 sphinx-rtd-theme -sphinx-autodoc-typehints +sphinx-autodoc-typehints<1.11 m2r jinja2 From f6716feb0487732620bb75873994526c4ba911e5 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Mon, 7 Sep 2020 05:32:11 -0700 Subject: [PATCH 070/588] add tests for equals --- tests/unittests/test_keyboard.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unittests/test_keyboard.py b/tests/unittests/test_keyboard.py index 24963850..f7e7e26e 100644 --- a/tests/unittests/test_keyboard.py +++ b/tests/unittests/test_keyboard.py @@ -54,6 +54,21 @@ def test_type(self): self.ahk.type('Hello, World!') assert b'Hello, World!' in self.notepad.text + def test_type_escapes_equals(self): + ''' + https://github.com/spyoungtech/ahk/issues/96 + ''' + self.notepad.activate() + self.ahk.type('=foo') + assert b'=foo' in self.notepad.text + + def test_sendraw_equals(self): + ''' + https://github.com/spyoungtech/ahk/issues/96 + ''' + self.notepad.activate() + self.ahk.send_raw('=foo') + assert b'=foo' in self.notepad.text def a_down(): time.sleep(0.5) From e245ddf4d3f9a3298ce85ec53372568ee0c168bb Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Mon, 7 Sep 2020 05:33:28 -0700 Subject: [PATCH 071/588] add equals (=) to escape sequences --- ahk/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ahk/utils.py b/ahk/utils.py index a8354fd1..5e57062e 100644 --- a/ahk/utils.py +++ b/ahk/utils.py @@ -18,7 +18,8 @@ '+': '{+}', '{': '{{}', '}': '{}}', - '#': '{#}' + '#': '{#}', + '=': '{=}' } _TRANSLATION_TABLE = str.maketrans(ESCAPE_SEQUENCE_MAP) From 6692f4b7f81441c822ebf868fc748fb2847793aa Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Mon, 7 Sep 2020 05:35:05 -0700 Subject: [PATCH 072/588] syntax fix for Send/SendRaw --- ahk/templates/keyboard/send.ahk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/templates/keyboard/send.ahk b/ahk/templates/keyboard/send.ahk index 3f362918..aadd1850 100644 --- a/ahk/templates/keyboard/send.ahk +++ b/ahk/templates/keyboard/send.ahk @@ -2,5 +2,5 @@ {% block body %} {% if delay %}SetKeyDelay, {{ delay }}{% endif %} -Send{% if raw %}Raw{% endif %} {{ s }} +Send{% if raw %}Raw{% endif %}, {{ s }} {% endblock body %} \ No newline at end of file From 04264b310d2309afe1152569575604367dac3121 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 7 Sep 2020 06:12:26 -0700 Subject: [PATCH 073/588] add deprecation warnings for renamed registry methods (#98) add deprecation warnings for renamed registry methods --- ahk/registery.py | 90 +++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/ahk/registery.py b/ahk/registery.py index 4e6ba760..be4f82de 100644 --- a/ahk/registery.py +++ b/ahk/registery.py @@ -3,21 +3,14 @@ class RegisteryMixin(ScriptEngine): - def _render_template(self, template_name, *args, **kwargs): - return self.render_template( - os.path.join("registery", template_name), - ) + return self.render_template(os.path.join("registery", template_name)) def _run_template(self, template_name, *args, **kwargs): - script = self._render_template( - template_name, - *args, - **kwargs - ) + script = self._render_template(template_name, *args, **kwargs) return self.run_script(script) - def read(self, key_name: str, value_name="") -> str: + def reg_read(self, key_name: str, value_name="") -> str: """Read registery Reference: @@ -32,13 +25,9 @@ def read(self, key_name: str, value_name="") -> str: Returns: str -- Registery value """ - self._run_template( - "reg_read.ahk", - key_name=key_name, - value_name=value_name - ) + self._run_template("reg_read.ahk", key_name=key_name, value_name=value_name) - def delete(self, key_name: str, value_name="") -> None: + def reg_delete(self, key_name: str, value_name="") -> None: """Delete registery Reference: @@ -50,13 +39,9 @@ def delete(self, key_name: str, value_name="") -> None: Keyword Arguments: value_name {str} -- TODO (default: {""}) """ - self._run_template( - "reg_delete.ahk", - key_name=key_name, - value_name=value_name - ) + self._run_template("reg_delete.ahk", key_name=key_name, value_name=value_name) - def write(self, value_type: str, key_name: str, value_name="") -> None: + def reg_write(self, value_type: str, key_name: str, value_name="") -> None: """Write registery Reference: @@ -69,14 +54,9 @@ def write(self, value_type: str, key_name: str, value_name="") -> None: Keyword Arguments: value_name {str} -- TODO (default: {""}) """ - self._run_template( - "reg_write.ahk", - value_type=value_type, - key_name=key_name, - value_name=value_name - ) + self._run_template("reg_write.ahk", value_type=value_type, key_name=key_name, value_name=value_name) - def set_view(self, reg_view: int) -> None: + def reg_set_view(self, reg_view: int) -> None: """Set registery view Reference: @@ -89,12 +69,9 @@ def set_view(self, reg_view: int) -> None: if reg_view not in [32, 64, "32", "64"]: raise ValueError("No valid bit, please use 32 or 64") - self._run_template( - "reg_set_view.ahk", - reg_view=reg_view, - ) + self._run_template("reg_set_view.ahk", reg_view=reg_view) - def loop(self, reg: str, key_name: str, mode=""): + def reg_loop(self, reg: str, key_name: str, mode=""): """Loop registery Reference: @@ -109,9 +86,44 @@ def loop(self, reg: str, key_name: str, mode=""): """ raise NotImplementedError - self._run_template( - "reg_loop.ahk", - reg=reg, - key_name=key_name, - mode=mode + self._run_template("reg_loop.ahk", reg=reg, key_name=key_name, mode=mode) + + def read(self, *args, **kwargs): + import warnings + + warnings.warn( + 'read() has been renamed and will be removed in a future version. use reg_read() instead', + DeprecationWarning, + stacklevel=2, + ) + return self.reg_read(*args, **kwargs) + + def write(self, *args, **kwargs): + import warnings + + warnings.warn( + 'write() has been renamed and will be removed in a future version. use reg_write() instead', + DeprecationWarning, + stacklevel=2, + ) + return self.reg_write(*args, **kwargs) + + def set_view(self, *args, **kwargs): + import warnings + + warnings.warn( + 'set_view() has been renamed and will be removed in a future version. use reg_set_view() instead', + DeprecationWarning, + stacklevel=2, + ) + return self.reg_set_view(*args, **kwargs) + + def delete(self, *args, **kwargs): + import warnings + + warnings.warn( + 'delete() has been renamed and will be removed in a future version. use reg_delete() instead', + DeprecationWarning, + stacklevel=2, ) + return self.reg_delete(*args, **kwargs) From 126c2e82c0e94a2bbaf14d50933e574cd82f8c33 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 7 Sep 2020 06:12:59 -0700 Subject: [PATCH 074/588] :package: version 0.10.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 06714fc3..483153dc 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.9.0', + version='0.10.0', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From 17c58db0535cbc5cb2ba1f6c9254d00671031bdc Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 13 Sep 2020 18:01:17 -0400 Subject: [PATCH 075/588] quotation marks added mode place holder --- ahk/templates/keyboard/key_state.ahk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/templates/keyboard/key_state.ahk b/ahk/templates/keyboard/key_state.ahk index 258805dc..6cd8e482 100644 --- a/ahk/templates/keyboard/key_state.ahk +++ b/ahk/templates/keyboard/key_state.ahk @@ -1,6 +1,6 @@ {% extends "base.ahk" %} {% block body %} -if (GetKeyState("{{ key_name }}"{% if mode %} , {{ mode }}{% endif %})) { +if (GetKeyState("{{ key_name }}"{% if mode %} , "{{ mode }}"{% endif %})) { FileAppend, 1, * } else { FileAppend, 0, * From f238e79f26717f565c5859024ea5036761d16f71 Mon Sep 17 00:00:00 2001 From: Josh Coles <57844507+Glutenberg@users.noreply.github.com> Date: Wed, 16 Sep 2020 12:33:39 -0400 Subject: [PATCH 076/588] added functionality for SetCapsLockState (#102) --- ahk/keyboard.py | 74 +++++++++++----- ahk/templates/keyboard/set_capslock_state.ahk | 4 + tests/unittests/test_keyboard.py | 85 ++++++++++--------- 3 files changed, 105 insertions(+), 58 deletions(-) create mode 100644 ahk/templates/keyboard/set_capslock_state.ahk diff --git a/ahk/keyboard.py b/ahk/keyboard.py index d96aa27e..8ea467eb 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -21,7 +21,7 @@ def __init__(self, engine: ScriptEngine, hotkey: str, script: str): @property def running(self): - return hasattr(self, '_proc') + return hasattr(self, "_proc") def _start(self, script): try: @@ -35,8 +35,10 @@ 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) + 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 @@ -52,7 +54,7 @@ def stop(self): Stops the process if it is running """ if not self.running: - raise RuntimeError('Hotkey is not running') + raise RuntimeError("Hotkey is not running") try: next(self._gen) except StopIteration: @@ -70,7 +72,7 @@ def hotkey(self, *args, **kwargs): :param script: The script to execute when the hotkey is activated (AutoHotkey code as a string) :return: an :py:class:`~ahk.keyboard.Hotkey` instance """ - engine = kwargs.pop('engine', self) + engine = kwargs.pop("engine", self) return Hotkey(engine, *args, **kwargs) def key_state(self, key_name, mode=None) -> bool: @@ -83,11 +85,18 @@ def key_state(self, key_name, mode=None) -> bool: :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)) + 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): + 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). @@ -100,17 +109,19 @@ def key_wait(self, key_name, timeout: int=None, logical_state=False, released=Fa :return: None :raises TimeoutError: if the key was not pressed (or released, if specified) within timeout """ - options = '' + options = "" if not released: - options += 'D' + options += "D" if logical_state: - options += 'L' + options += "L" if timeout: - options += f'T{timeout}' - script = self.render_template('keyboard/key_wait.ahk', key_name=key_name, options=options) + 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}') + raise TimeoutError(f"timed out waiting for {key_name}") def type(self, s, blocking=True): """ @@ -132,7 +143,9 @@ def send(self, s, raw=False, delay=None, blocking=True): :param blocking: if ``True``, waits until script finishes, else returns immediately. :return: """ - script = self.render_template('keyboard/send.ahk', s=s, raw=raw, delay=delay, blocking=blocking) + script = self.render_template( + "keyboard/send.ahk", s=s, raw=raw, delay=delay, blocking=blocking + ) self.run_script(script, blocking=blocking) def send_raw(self, s, delay=None): @@ -154,10 +167,12 @@ def send_input(self, s, blocking=True): :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.') + 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, blocking=blocking) + script = self.render_template("keyboard/send_input.ahk", s=s, blocking=blocking) self.run_script(script, blocking=blocking) def send_play(self, s): @@ -168,7 +183,7 @@ def send_play(self, s): :param s: :return: """ - script = self.render_template('keyboard/send_play.ahk', s=s) + script = self.render_template("keyboard/send_play.ahk", s=s) self.run_script(script) def send_event(self, s, delay=None): @@ -179,7 +194,7 @@ def send_event(self, s, delay=None): :param delay: :return: """ - script = self.render_template('keyboard/send_event.ahk', s=s, delay=delay) + script = self.render_template("keyboard/send_event.ahk", s=s, delay=delay) self.run_script(script) def key_press(self, key, release=True, blocking=True): @@ -223,3 +238,24 @@ def key_up(self, key, blocking=True): Alias for :meth:~`KeyboardMixin.key_release` """ return self.key_release(key, blocking=blocking) + + def set_capslock_state(self, state): + """ + Sets capslock state + + :param state: + :type state: str + :return: + """ + + if isinstance(state, str): + state = state.lower() + + elif isinstance(state, bool): + if state: + state = "on" + else: + state = "off" + + script = self.render_template("keyboard/set_capslock_state.ahk", state=state) + self.run_script(script) diff --git a/ahk/templates/keyboard/set_capslock_state.ahk b/ahk/templates/keyboard/set_capslock_state.ahk new file mode 100644 index 00000000..a2e5b6ff --- /dev/null +++ b/ahk/templates/keyboard/set_capslock_state.ahk @@ -0,0 +1,4 @@ +{% extends "base.ahk" %} +{% block body %} +SetCapsLockState, {{state}} +{% endblock body %} \ No newline at end of file diff --git a/tests/unittests/test_keyboard.py b/tests/unittests/test_keyboard.py index f7e7e26e..ec749c69 100644 --- a/tests/unittests/test_keyboard.py +++ b/tests/unittests/test_keyboard.py @@ -9,7 +9,9 @@ from ahk import AHK from ahk.keys import ALT, CTRL, KEYS -project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) +project_root = os.path.abspath( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") +) sys.path.insert(0, project_root) @@ -21,71 +23,76 @@ def setUp(self): """ self.ahk = AHK() self.before_windows = self.ahk.windows() - self.p = subprocess.Popen('notepad') + self.p = subprocess.Popen("notepad") time.sleep(1) - self.notepad = self.ahk.find_window(title=b'Untitled - Notepad') + 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') + self.notepad.send("hello world") time.sleep(1) - self.assertIn(b'hello world', self.notepad.text) + 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 + 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) + 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 + 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 + self.ahk.type("Hello, World!") + assert b"Hello, World!" in self.notepad.text def test_type_escapes_equals(self): - ''' + """ https://github.com/spyoungtech/ahk/issues/96 - ''' + """ self.notepad.activate() - self.ahk.type('=foo') - assert b'=foo' in self.notepad.text + self.ahk.type("=foo") + assert b"=foo" in self.notepad.text def test_sendraw_equals(self): - ''' + """ https://github.com/spyoungtech/ahk/issues/96 - ''' + """ self.notepad.activate() - self.ahk.send_raw('=foo') - assert b'=foo' in self.notepad.text + self.ahk.send_raw("=foo") + assert b"=foo" in self.notepad.text + + def test_set_capslock_state(self): + self.ahk.set_capslock_state("on") + assert self.ahk.key_state("CapsLock", "T") + def a_down(): time.sleep(0.5) ahk = AHK() - ahk.key_down('a') + ahk.key_down("a") def release_a(): time.sleep(0.5) ahk = AHK() - ahk.key_up('a') + ahk.key_up("a") def press_a(): time.sleep(0.5) ahk = AHK() - ahk.key_press('a') + ahk.key_press("a") class TestKeys(TestCase): @@ -97,12 +104,12 @@ def setUp(self): 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') + 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') + notepad = self.ahk.find_window(title=b"Untitled - Notepad") if notepad: notepad.close() @@ -113,7 +120,7 @@ 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) + self.ahk.key_wait("a", timeout=5) end = time.time() assert end - start < 5 @@ -122,30 +129,30 @@ def test_key_wait_released(self): a_down() self.thread = threading.Thread(target=release_a) self.thread.start() - self.ahk.key_wait('a', timeout=2) + self.ahk.key_wait("a", timeout=2) def test_key_wait_timeout(self): - self.assertRaises(TimeoutError, self.ahk.key_wait, 'f', timeout=1) + 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')) + 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')) + 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.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')) + 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 = 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')) + self.ahk.key_press("a") + self.assertIsNone(self.ahk.find_window(title=b"Untitled - Notepad")) From 80031b30d6a3d5bb58bd069a45109b1bec8d0d0e Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 17 Sep 2020 13:04:08 -0700 Subject: [PATCH 077/588] docs and minor changes for set_capslock_state --- ahk/keyboard.py | 8 ++++++-- ahk/templates/keyboard/set_capslock_state.ahk | 2 +- docs/README.md | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ahk/keyboard.py b/ahk/keyboard.py index 8ea467eb..c4354554 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -239,17 +239,21 @@ def key_up(self, key, blocking=True): """ return self.key_release(key, blocking=blocking) - def set_capslock_state(self, state): + def set_capslock_state(self, state=None): """ + https://www.autohotkey.com/docs/commands/SetNumScrollCapsLockState.htm + Sets capslock state - :param state: + :param state: the desired state ("on" or "off"). Can also be True/False. If omitted, toggles capslock state. :type state: str :return: """ if isinstance(state, str): state = state.lower() + if state not in ("on", "off", "alwayson", 'alwaysoff'): + raise ValueError(f'state value must be one of "On"|"Off"|"AlwaysOn"|"AlwaysOff" - not {repr(state)}') elif isinstance(state, bool): if state: diff --git a/ahk/templates/keyboard/set_capslock_state.ahk b/ahk/templates/keyboard/set_capslock_state.ahk index a2e5b6ff..353203db 100644 --- a/ahk/templates/keyboard/set_capslock_state.ahk +++ b/ahk/templates/keyboard/set_capslock_state.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -SetCapsLockState, {{state}} +{% if state %}SetCapsLockState, {{state}}{% else %}SetCapsLockState % !GetKeyState("CapsLock", "T"){% endif %} {% endblock body %} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 02ca9bf6..d52c6e0c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -70,6 +70,7 @@ ahk.key_down('Control') # Press down (but do not release) Control key ahk.key_up('Control') # Release the key ahk.key_wait('a', timeout=3) # Wait up to 3 seconds for the "a" key to be pressed. NOTE: This throws # a TimeoutError if the key isn't pressed within the timeout window +ahk.set_capslock_state("on") # Turn CapsLock on ``` ## Windows From e1183c85595e8ece68f864954206d34950473db9 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 17 Sep 2020 13:04:56 -0700 Subject: [PATCH 078/588] :package: version 0.11.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 483153dc..da240772 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.10.0', + version='0.11.0', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From af30c0de76b1524ce04a5b4bfa05c9202c52c2ad Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 17 Sep 2020 13:41:40 -0700 Subject: [PATCH 079/588] allow directives to be added to all scripts --- ahk/script.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ahk/script.py b/ahk/script.py index 852e1c92..0214ee54 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -17,7 +17,7 @@ from ahk.utils import make_logger from ahk.directives import Persistent from jinja2 import Environment, FileSystemLoader - +from typing import Set logger = make_logger(__name__) @@ -65,7 +65,7 @@ def _resolve_executable_path(executable_path: str = ''): class ScriptEngine(object): - def __init__(self, executable_path: str = "", **kwargs): + def __init__(self, executable_path: str = "", directives: Set = None, **kwargs): """ This class is typically not used directly. AHK components inherit from this class and the arguments for this class should usually be passed in to :py:class:`~ahk.AHK`. @@ -77,7 +77,7 @@ def __init__(self, executable_path: str = "", **kwargs): * :py:data:`~ahk.script.DEFAULT_EXECUTABLE_PATH` if the file exists If environment variable not present, tries to look for 'AutoHotkey.exe' or 'AutoHotkeyA32.exe' with shutil.which - + :param directives: a set of directives to apply to all generated AHK scripts :raises ExecutableNotFound: if AHK executable cannot be found or the specified file does not exist """ self.executable_path = _resolve_executable_path(executable_path) @@ -85,6 +85,9 @@ def __init__(self, executable_path: str = "", **kwargs): 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) + if directives is None: + directives = set() + self._directives = set(directives) def render_template(self, template_name, directives=None, blocking=True, **kwargs): """ @@ -111,6 +114,8 @@ def render_template(self, template_name, directives=None, blocking=True, **kwarg directives.add(Persistent) elif Persistent in directives: directives.remove(Persistent) + if self._directives: + directives.update(self._directives) kwargs['directives'] = directives template = self.env.get_template(template_name) From b82960c03f5040173672fe1275a489bf98c5c342 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 17 Sep 2020 13:47:06 -0700 Subject: [PATCH 080/588] add directives docs --- docs/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/README.md b/docs/README.md index d52c6e0c..ed7dca8e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -218,7 +218,19 @@ You should see an output something like 0.831 (100, 101) 0.873 (100, 100) ``` +## 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. ## Run arbitrary AutoHotkey scripts From 0bc74f5ddcbb988354b40021d5106ac5466281fb Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 17 Sep 2020 14:09:36 -0700 Subject: [PATCH 081/588] :package: version 0.11.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index da240772..3eacaf25 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.11.0', + version='0.11.1', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From 6d8b276313e87df1d27f79a51f66b27e159f40bb Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Oct 2020 08:41:38 -0700 Subject: [PATCH 082/588] fix typo --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index ed7dca8e..64d6f898 100644 --- a/docs/README.md +++ b/docs/README.md @@ -108,7 +108,7 @@ win.send('hello') # Send keys directly to the window (does not need focus!) win.move(x=200, y=300, width=500, height=800) win.activate() # Give the window focus -win.activate_buttom() # Give the window focus +win.activate_bottom() # Give the window focus win.close() # Close the window win.hide() # Hide the windwow win.kill() # Kill the window From 1cbadee7b9f04966323a561760f18951cff0f981 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 3 Dec 2020 17:48:41 -0800 Subject: [PATCH 083/588] badge update :shield: --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 64d6f898..ffeea2d1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,7 +7,7 @@ A Python wrapper around AHK. [![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) -![PyPI - Downloads](https://img.shields.io/pypi/dm/ahk) +[![Downloads](https://pepy.tech/badge/ahk)](https://pepy.tech/project/ahk) # Installation From 5cad99d625a7e00903eb315d2fbee7c25b161f2b Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Mon, 4 Jan 2021 19:14:18 -0800 Subject: [PATCH 084/588] async keyboard and mouse --- ahk/autohotkey.py | 26 +++++--- ahk/keyboard.py | 157 +++++++++++++++++++++++++++++++++++++--------- ahk/mouse.py | 111 +++++++++++++++++++++++--------- 3 files changed, 226 insertions(+), 68 deletions(-) diff --git a/ahk/autohotkey.py b/ahk/autohotkey.py index 30f01334..79a93bef 100644 --- a/ahk/autohotkey.py +++ b/ahk/autohotkey.py @@ -1,12 +1,12 @@ from collections import deque -from ahk.keyboard import KeyboardMixin -from ahk.mouse import MouseMixin -from ahk.registery import RegisteryMixin -from ahk.screen import ScreenMixin -from ahk.sound import SoundMixin -from ahk.window import WindowMixin -from ahk.gui import GUIMixin +from ahk.keyboard import KeyboardMixin, AsyncKeyboardMixin +from ahk.mouse import MouseMixin, AsyncMouseMixin +from ahk.registery import RegisteryMixin#, AsyncRegisteryMixin +from ahk.screen import ScreenMixin#, AsyncScreenMixin +from ahk.sound import SoundMixin#, AsyncSoundMixin +from ahk.window import WindowMixin#, AsyncWindowMixin +from ahk.gui import GUIMixin#, AsyncGUIMixin class AHK(WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin, RegisteryMixin, GUIMixin): @@ -24,6 +24,18 @@ class AHK(WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin, Regis pass +# +class AsyncAHK( + #AsyncWindowMixin, + AsyncMouseMixin, + AsyncKeyboardMixin, + #AsyncScreenMixin, + #AsyncSoundMixin, + #AsyncRegisteryMixin, + #AsyncGUIMixin +): + ... + class ActionChain(AHK): """ diff --git a/ahk/keyboard.py b/ahk/keyboard.py index c4354554..9b87e8f0 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -75,7 +75,7 @@ def hotkey(self, *args, **kwargs): engine = kwargs.pop("engine", self) return Hotkey(engine, *args, **kwargs) - def key_state(self, key_name, mode=None) -> bool: + def _key_state(self, key_name, mode=None) -> str: """ Check the state of a key. @@ -91,12 +91,31 @@ def key_state(self, key_name, mode=None) -> bool: mode=mode, directives=(InstallMouseHook, InstallKeybdHook), ) - result = ast.literal_eval(self.run_script(script)) + return script + + def key_state(self, key_name, mode=None) -> bool: + script = self._key_state(key_name, mode=mode) + result = self.run_script(script) + result = ast.literal_eval(result) return bool(result) + def _key_wait( + self, key_name, timeout: int = None, logical_state=False, released=False + ) -> str: + 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 + ) + return script def key_wait( self, key_name, timeout: int = None, logical_state=False, released=False - ): + ) -> None: """ Wait for key to be pressed or released (default is pressed; specify ``released=True`` to wait for key release). @@ -109,17 +128,9 @@ def key_wait( :return: None :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) + result = self.run_script(self._key_wait( + key_name, timeout=timeout, logical_state=logical_state, released=released + )) if result == "1": raise TimeoutError(f"timed out waiting for {key_name}") @@ -133,6 +144,12 @@ def type(self, s, blocking=True): s = escape_sequence_replace(s) self.send_input(s, blocking=blocking) + def _send(self, s, raw=False, delay=None, blocking=True): + script = self.render_template( + "keyboard/send.ahk", s=s, raw=raw, delay=delay, blocking=blocking + ) + return script + def send(self, s, raw=False, delay=None, blocking=True): """ https://autohotkey.com/docs/commands/Send.htm @@ -143,9 +160,7 @@ def send(self, s, raw=False, delay=None, blocking=True): :param blocking: if ``True``, waits until script finishes, else returns immediately. :return: """ - script = self.render_template( - "keyboard/send.ahk", s=s, raw=raw, delay=delay, blocking=blocking - ) + script = self._send(s, raw=raw, delay=delay, blocking=blocking) self.run_script(script, blocking=blocking) def send_raw(self, s, delay=None): @@ -158,7 +173,7 @@ def send_raw(self, s, delay=None): """ return self.send(s, raw=True, delay=delay) - def send_input(self, s, blocking=True): + def _send_input(self, s, blocking=True): """ https://autohotkey.com/docs/commands/Send.htm @@ -173,8 +188,16 @@ def send_input(self, s, blocking=True): ) script = self.render_template("keyboard/send_input.ahk", s=s, blocking=blocking) + return script + + def send_input(self, s, blocking=True): + script = self._send_input(s, blocking=blocking) self.run_script(script, blocking=blocking) + def _send_play(self, s): + script = self.render_template("keyboard/send_play.ahk", s=s) + return script + def send_play(self, s): """ https://autohotkey.com/docs/commands/Send.htm @@ -183,9 +206,14 @@ def send_play(self, s): :param s: :return: """ - script = self.render_template("keyboard/send_play.ahk", s=s) + script = self._send_play(s) self.run_script(script) + def _send_event(self, s, delay=None): + script = self.render_template("keyboard/send_event.ahk", s=s, delay=delay) + return script + + #self.run_script(script) def send_event(self, s, delay=None): """ https://autohotkey.com/docs/commands/Send.htm @@ -194,7 +222,7 @@ def send_event(self, s, delay=None): :param delay: :return: """ - script = self.render_template("keyboard/send_event.ahk", s=s, delay=delay) + script = self._send_event(s, delay=delay) self.run_script(script) def key_press(self, key, release=True, blocking=True): @@ -239,17 +267,7 @@ def key_up(self, key, blocking=True): """ return self.key_release(key, blocking=blocking) - def set_capslock_state(self, state=None): - """ - https://www.autohotkey.com/docs/commands/SetNumScrollCapsLockState.htm - - Sets capslock state - - :param state: the desired state ("on" or "off"). Can also be True/False. If omitted, toggles capslock state. - :type state: str - :return: - """ - + def _set_capslock_state(self, state=None): if isinstance(state, str): state = state.lower() if state not in ("on", "off", "alwayson", 'alwaysoff'): @@ -262,4 +280,81 @@ def set_capslock_state(self, state=None): state = "off" script = self.render_template("keyboard/set_capslock_state.ahk", state=state) + return script + # self.run_script(script) + + def set_capslock_state(self, state=None): + """ + https://www.autohotkey.com/docs/commands/SetNumScrollCapsLockState.htm + + Sets capslock state + + :param state: the desired state ("on" or "off"). Can also be True/False. If omitted, toggles capslock state. + :type state: str + :return: + """ + script = self._set_capslock_state(state) self.run_script(script) + + +class AsyncKeyboardMixin(KeyboardMixin): + async def send_input(self, s, blocking=True): + script = self._send_input(s, blocking=blocking) + return await self.a_run_script(script, blocking=blocking) or None + + + async def key_state(self, key_name, mode=None) -> bool: + script = self._key_state(key_name, mode=mode) + result = await self.a_run_script(script) + result = ast.literal_eval(result) + return bool(result) + + async def key_wait( + self, key_name, timeout: int = None, logical_state=False, released=False + ) -> None: + result = await self.a_run_script(self._key_wait( + key_name, timeout=timeout, logical_state=logical_state, released=released + )) + if result == "1": + raise TimeoutError(f"timed out waiting for {key_name}") + + async def type(self, s, blocking=True): + s = escape_sequence_replace(s) + await self.send_input(s, blocking=blocking) + + async def send(self, s, raw=False, delay=None, blocking=True): + script = self._send(s, raw=raw, delay=delay, blocking=blocking) + await self.a_run_script(script, blocking=blocking) + + async def send_raw(self, s, delay=None): + return await self.send(s, raw=True, delay=delay) + + async def send_play(self, s): + script = self._send_play(s) + await self.a_run_script(script) + + async def send_event(self, s, delay=None): + script = self._send_event(s, delay=delay) + await self.a_run_script(script) + + async def key_press(self, key, release=True, blocking=True): + await self.key_down(key, blocking=blocking) + if release: + await self.key_up(key, blocking=blocking) + + async def key_release(self, key, blocking=True): + if isinstance(key, str): + key = Key(key_name=key) + return await self.send_input(key.UP, blocking=blocking) + + async def key_down(self, key, blocking=True): + if isinstance(key, str): + key = Key(key_name=key) + await self.send_input(key.DOWN, blocking=blocking) + + async def key_up(self, key, blocking=True): + await self.key_release(key, blocking=blocking) + + async def set_capslock_state(self, state=None): + script = self._set_capslock_state(state) + await self.a_run_script(script) diff --git a/ahk/mouse.py b/ahk/mouse.py index 159e2525..0a5dd993 100644 --- a/ahk/mouse.py +++ b/ahk/mouse.py @@ -1,4 +1,6 @@ from collections import namedtuple +from typing import Union + from ahk.script import ScriptEngine from ahk.utils import make_logger import ast @@ -119,12 +121,24 @@ def mouse_move(self, *args, **kwargs): script = self._mouse_move(*args, **kwargs) self.run_script(script, blocking=blocking) - def _click(self, *args, mode=None, blocking=True): + def _click(self, x=None, y=None, *, button=None, n=None, direction=None, relative=None, blocking=True, mode=None): + 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] if mode is None: mode = self.mode - return self.render_template('mouse/click.ahk', args=args, mode=mode, blocking=blocking) + script = self.render_template('mouse/click.ahk', args=args, mode=mode, blocking=blocking) + return script - def click(self, x=None, y=None, *, button=None, n=None, direction=None, relative=None, blocking=True, mode=None): + def click(self, *args, **kwargs): """ Click mouse button at a specified position. REF: https://www.autohotkey.com/docs/commands/Click.htm @@ -138,18 +152,8 @@ def click(self, x=None, y=None, *, button=None, n=None, direction=None, relative :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) + blocking = kwargs.get('blocking', True) + script = self._click(*args, **kwargs) self.run_script(script, blocking=blocking) def double_click(self, *args, **kwargs): @@ -173,7 +177,7 @@ def right_click(self, *args, **kwargs): :return: """ kwargs['button'] = 2 - self.click(*args, **kwargs) + return self.click(*args, **kwargs) def mouse_wheel(self, direction, *args, **kwargs): """ @@ -208,20 +212,7 @@ def wheel_down(self, *args, **kwargs): """ 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: - """ + def _mouse_drag(self, x, y=None, *, from_position=None, speed=None, button: Union[str, int] =1, relative=None, blocking=True, mode=None): if from_position is None: x1, y1 = self.mouse_position else: @@ -255,4 +246,64 @@ def mouse_drag(self, x, y=None, *, from_position=None, speed=None, button=1, rel blocking=blocking, mode=mode) + return script + + def mouse_drag(self, *args, **kwargs): + """ + 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: + """ + blocking = kwargs.get('blocking', True) + script = self._mouse_drag(*args, **kwargs) self.run_script(script, blocking=blocking) + + +class AsyncMouseMixin(MouseMixin): + async def mouse_move(self, *args, **kwargs): + blocking = kwargs.get('blocking', True) + script = self._mouse_move(*args, **kwargs) + await self.a_run_script(script, blocking=blocking) + + async def click(self, *args, **kwargs): + blocking = kwargs.get('blocking', True) + script = self._click(*args, **kwargs) + await self.a_run_script(script, blocking=blocking) + + async def double_click(self, *args, **kwargs): + n = kwargs.get('n', 1) + kwargs['n'] = n * 2 + await self.click(*args, **kwargs) + + async def right_click(self, *args, **kwargs): + kwargs['button'] = 2 + await self.click(*args, **kwargs) + + async def mouse_wheel(self, direction, *args, **kwargs): + assert direction in ('up', 'down') + kwargs['button'] = f'Wheel{direction}' + await self.click(*args, **kwargs) + + async def wheel_up(self, *args, **kwargs): + await self.mouse_wheel('up', *args, **kwargs) + + async def wheel_down(self, *args, **kwargs): + await self.mouse_wheel('down', *args, **kwargs) + + async def mouse_drag(self, *args, **kwargs): + blocking = kwargs.get('blocking', True) + script = self._mouse_drag(*args, **kwargs) + await self.a_run_script(script, blocking=blocking) + + async def get_mouse_position(self, mode=None): + script = self._mouse_position(mode=mode) + return await self.a_run_script(script) + From 2488ea13385c0b74cef845ccf9f9362119996418 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Tue, 5 Jan 2021 03:15:22 -0800 Subject: [PATCH 085/588] async windowze --- ahk/__init__.py | 2 +- ahk/autohotkey.py | 3 +- ahk/script.py | 45 ++ ahk/templates/window/win_click.ahk | 2 +- ahk/window.py | 644 +++++++++++++++++++++++-- tests/unittests/test_keyboard_async.py | 173 +++++++ 6 files changed, 814 insertions(+), 55 deletions(-) create mode 100644 tests/unittests/test_keyboard_async.py diff --git a/ahk/__init__.py b/ahk/__init__.py index a528d9cb..c00dfbf5 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -1,3 +1,3 @@ -from ahk.autohotkey import AHK, ActionChain +from ahk.autohotkey import AHK, ActionChain, AsyncAHK from ahk.keyboard import Hotkey __all__ = ['AHK'] diff --git a/ahk/autohotkey.py b/ahk/autohotkey.py index 79a93bef..af715a13 100644 --- a/ahk/autohotkey.py +++ b/ahk/autohotkey.py @@ -5,7 +5,7 @@ from ahk.registery import RegisteryMixin#, AsyncRegisteryMixin from ahk.screen import ScreenMixin#, AsyncScreenMixin from ahk.sound import SoundMixin#, AsyncSoundMixin -from ahk.window import WindowMixin#, AsyncWindowMixin +from ahk.window import WindowMixin, AsyncWindowMixin from ahk.gui import GUIMixin#, AsyncGUIMixin @@ -29,6 +29,7 @@ class AsyncAHK( #AsyncWindowMixin, AsyncMouseMixin, AsyncKeyboardMixin, + AsyncWindowMixin #AsyncScreenMixin, #AsyncSoundMixin, #AsyncRegisteryMixin, diff --git a/ahk/script.py b/ahk/script.py index 0214ee54..b06cf8b8 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -10,6 +10,7 @@ """ +import asyncio import os import subprocess import warnings @@ -144,6 +145,50 @@ def _run_script(self, script_text, **kwargs): pass # for now, this seems needed to avoid blocking and use stdin return proc + async def _a_run_script(self, script_text, **kwargs): + blocking = kwargs.pop('blocking', True) + if blocking is not True: + warnings.warn("blocking=False will probably result in problems", stacklevel=2) + runargs = [self.executable_path, '/ErrorStdOut', '*'] + proc = await asyncio.subprocess.create_subprocess_exec(*runargs, + stdin=asyncio.subprocess.PIPE, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE) + script_bytes = bytes(script_text, 'utf-8') + if not blocking: + proc.stdin.write(script_bytes) + await proc.stdin.drain() + return proc + + stdout, stderr = await proc.communicate(script_bytes) + if kwargs.get('decode', False): + return stdout.decode() + return stdout + + + async def a_run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): + """ + async version of ``run_script`` + + :param script_text: a string containing AutoHotkey code + :param decode: If ``True``, attempt to decode the stdout of the completed process. + If ``False``, returns the completed process. Only has effect when ``blocking=True`` + :param blocking: If ``True``, script must finish before returning. + If ``False``, function returns a ``asyncio.process`` object immediately without blocking + :param runkwargs: keyword arguments passed to ``subprocess.Popen`` or ``subprocess.run`` + :return: | A string of the decoded stdout if ``blocking`` and ``decode`` are True. + | A bytes object of stdout if ``blocking`` is True and ``decode`` is False. + | ``asyncio.subprocess.Process`` object if ``blocking`` is False. + + """ + logger.debug('Running script text: %s', script_text) + try: + result = await self._a_run_script(script_text, decode=decode, blocking=blocking, **runkwargs) + except Exception as e: + logger.fatal('Error running temp script: %s', e) + raise + return result + def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): """ Given an AutoHotkey script as a string, execute it diff --git a/ahk/templates/window/win_click.ahk b/ahk/templates/window/win_click.ahk index f84a9393..8efeed2e 100644 --- a/ahk/templates/window/win_click.ahk +++ b/ahk/templates/window/win_click.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -ControlClick, x{{ x }} y{{ y }}, {{ hwnd }} +ControlClick, x{{ x }} y{{ y }}, {{ hwnd }},, {{ button }}, {{ n }}{% if options %}, {{ options }}{% endif %} {% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index a09a3911..d2d57085 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -1,8 +1,11 @@ import ast +import asyncio +import collections from contextlib import suppress - +import warnings +from types import CoroutineType from ahk.script import ScriptEngine -from ahk.utils import escape_sequence_replace, make_logger +from ahk.utils import escape_sequence_replace, make_logger, AsyncifyMeta, async_filter logger = make_logger(__name__) @@ -155,7 +158,7 @@ def __getattr__(self, attr): return self.get(attr) raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'") - def get(self, subcommand): + def _get(self, subcommand): sub = self._get_subcommands.get(subcommand) if not sub: raise ValueError(f'No such subcommand {subcommand}') @@ -166,12 +169,16 @@ def get(self, subcommand): title=f"ahk_id {self.id}", ) + return script + + def get(self, subcommand): + script = self._get(subcommand) return self.engine.run_script(script) def __repr__(self): return f'' - def set(self, subcommand, value): + def _set(self, subcommand, value): sub = self._set_subcommands.get(subcommand) if not sub: raise ValueError(f'No such subcommand {subcommand}') @@ -182,6 +189,10 @@ def set(self, subcommand, value): value=value, title=f"ahk_id {self.id}" ) + return script + + def set(self, subcommand, value): + script = self._set(subcommand, value) return self.engine.run_script(script) def _get_pos(self): @@ -189,6 +200,15 @@ def _get_pos(self): 'window/win_position.ahk', title=f"ahk_id {self.id}" ) + return script + + def get_pos(self): + """ + Same as ``rect`` + + :return: + """ + script = self._get_pos() resp = self.engine.run_script(script) try: value = ast.literal_eval(resp) @@ -198,7 +218,7 @@ def _get_pos(self): @property def rect(self): - return self._get_pos() + return self.get_pos() @rect.setter def rect(self, new_position): @@ -207,7 +227,7 @@ def rect(self, new_position): @property def position(self): - x, y, _, _ = self._get_pos() + x, y, _, _ = self.get_pos() return x, y @@ -218,7 +238,7 @@ def position(self, new_position): @property def width(self): - _, _, width, _ = self._get_pos() + _, _, width, _ = self.get_pos() return width @width.setter @@ -227,19 +247,23 @@ def width(self, new_width): @property def height(self): - _, _, _, height = self._get_pos() + _, _, _, height = self.get_pos() return height @height.setter def height(self, new_height): self.move(height=new_height) - def _base_property(self, command): + def _base_check(self, command): script = self._render_template( "window/base_check.ahk", command=command, title=f"ahk_id {self.id}" ) + return script + + def _base_property(self, command): + script = self._base_check(command) resp = self.engine.run_script(script) return bool(ast.literal_eval(resp)) @@ -247,16 +271,25 @@ def _base_property(self, command): def active(self): return self._base_property(command="WinActive") + def is_active(self): + return self.active + @property def exist(self): return self._base_property(command="WinExist") - def _base_get_method(self, command): + def exists(self): + return self.exist + + def _base_get_method_(self, command): script = self._render_template( "window/base_get_command.ahk", command=command, title=f"ahk_id {self.id}" ) + return script + def _base_get_method(self, command): + script = self._base_get_method_(command) result = self.engine.run_script(script, decode=False) if self.encoding: return result.stdout.decode(encoding=self.encoding) @@ -266,15 +299,26 @@ def _base_get_method(self, command): def title(self): return self._base_get_method("WinGetTitle") - @title.setter - def title(self, value): + def get_title(self): + return self.title + + def _set_title(self, value): script = self._render_template( "window/win_set_title.ahk", title=f"ahk_id {self.id}", new_title=value ) + return script + + @title.setter + def title(self, value): + script = self._set_title(value) return self.engine.run_script(script) + def set_title(self, value): + script = self._set_title(value) + self.engine.run_script(script) + @property def class_name(self): return self._base_get_method("WinGetClass") @@ -291,10 +335,25 @@ def minimized(self): def maximized(self): return self.get("MinMax") == self.MAXIMIZED + def is_minimized(self): + return self.minimized + + def is_maximized(self): + return self.maximized + @property def non_max_non_min(self): return self.get("MinMax") == self.NON_MIN_NON_MAX + def is_minmax(self): + return self.get("MinMax") != self.NON_MIN_NON_MAX + + def get_class_name(self): + return self.class_name + + def get_text(self): + return self.text + @property def transparent(self) -> int: result = self.get("Transparent") @@ -303,6 +362,9 @@ def transparent(self) -> int: else: return 255 + def get_transparency(self) -> int: + return self.transparent + @transparent.setter def transparent(self, value): if isinstance(value, int) and 0 <= value <= 255: @@ -311,15 +373,25 @@ def transparent(self, value): raise ValueError( f'"{value}" not a valid option. Please use [0, 255] integer') - @property - def always_on_top(self) -> bool: + def set_transparency(self, new_value): + self.transparent = new_value + + def _always_on_top(self): script = self._render_template( 'window/win_is_always_on_top.ahk', title=f"ahk_id {self.id}" ) + return script + + @property + def always_on_top(self) -> bool: + script = self._always_on_top() resp = self.engine.run_script(script) return bool(ast.literal_eval(resp)) + def is_always_on_top(self) -> bool: + return self.always_on_top + @always_on_top.setter def always_on_top(self, value): if value in ('on', 'On', True, 1): @@ -332,13 +404,16 @@ def always_on_top(self, value): raise ValueError( f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') + def set_always_on_top(self, value): + self.always_on_top = value + def disable(self): """ Distable the window :return: """ - self.set('Disable', '') + return self.set('Disable', '') or None def enable(self): """ @@ -346,17 +421,17 @@ def enable(self): :return: """ - self.set('Enable', '') + return self.set('Enable', '') or None def redraw(self): - self.set('Redraw', '') + return self.set('Redraw', '') or None def to_bottom(self): """ Send window to bottom (behind other windows) :return: """ - self.set('Bottom', '') + return self.set('Bottom', '') or None def to_top(self): """ @@ -364,20 +439,23 @@ def to_top(self): :return: """ - self.set('Top', '') + return self.set('Top', '') or None def _render_template(self, *args, **kwargs): kwargs['win'] = self return self.engine.render_template(*args, **kwargs) - def _base_method(self, command, seconds_to_wait="", blocking=False): + def _base_method_(self, command, seconds_to_wait="", blocking=False): script = self._render_template( "window/base_command.ahk", command=command, title=f"ahk_id {self.id}", seconds_to_wait=seconds_to_wait ) + return script + def _base_method(self, command, seconds_to_wait="", blocking=False): + script = self._base_method_(command, seconds_to_wait=seconds_to_wait) return self.engine.run_script(script, blocking=blocking) def activate(self): @@ -390,7 +468,7 @@ def activate(self): :return: """ - self._base_method("WinActivate") + return self._base_method("WinActivate") or None def activate_bottom(self): """ @@ -400,7 +478,7 @@ def activate_bottom(self): :return: """ - self._base_method("WinActivateBottom") + return self._base_method("WinActivateBottom") or None def close(self, seconds_to_wait=""): """ @@ -411,7 +489,7 @@ def close(self, seconds_to_wait=""): :param seconds_to_wait: :return: """ - self._base_method("WinClose", seconds_to_wait=seconds_to_wait) + return self._base_method("WinClose", seconds_to_wait=seconds_to_wait) or None def hide(self): """ @@ -422,10 +500,10 @@ def hide(self): :return: """ - self._base_method("WinHide") + return self._base_method("WinHide") or None def kill(self, seconds_to_wait=""): - self._base_method("WinKill", seconds_to_wait=seconds_to_wait) + return self._base_method("WinKill", seconds_to_wait=seconds_to_wait) or None def maximize(self): """ @@ -433,7 +511,7 @@ def maximize(self): :return: """ - self._base_method("WinMaximize") + return self._base_method("WinMaximize") or None def minimize(self): """ @@ -441,7 +519,7 @@ def minimize(self): :return: """ - self._base_method("WinMinimize") + return self._base_method("WinMinimize") or None def restore(self): """ @@ -449,7 +527,7 @@ def restore(self): :return: """ - self._base_method("WinRestore") + return self._base_method("WinRestore") or None def show(self): """ @@ -457,7 +535,7 @@ def show(self): :return: """ - self._base_method("WinShow") + return self._base_method("WinShow") or None def wait(self, seconds_to_wait=""): """ @@ -465,7 +543,7 @@ def wait(self, seconds_to_wait=""): :param seconds_to_wait: :return: """ - self._base_method("WinWait", seconds_to_wait=seconds_to_wait, blocking=True) + return self._base_method("WinWait", seconds_to_wait=seconds_to_wait, blocking=True) or None def wait_active(self, seconds_to_wait=""): """ @@ -473,7 +551,7 @@ def wait_active(self, seconds_to_wait=""): :param seconds_to_wait: :return: """ - self._base_method("WinWaitActive", seconds_to_wait=seconds_to_wait, blocking=True) + return self._base_method("WinWaitActive", seconds_to_wait=seconds_to_wait, blocking=True) or None def wait_not_active(self, seconds_to_wait=""): """ @@ -481,15 +559,23 @@ def wait_not_active(self, seconds_to_wait=""): :param seconds_to_wait: :return: """ - self._base_method("WinWaitNotActive", seconds_to_wait=seconds_to_wait, blocking=True) + return self._base_method("WinWaitNotActive", seconds_to_wait=seconds_to_wait, blocking=True) or None def wait_close(self, seconds_to_wait=""): """ - :param seconds_to_wait: + :param seconds_to_wait: :return: """ - self._base_method("WinWaitClose", seconds_to_wait=seconds_to_wait, blocking=True) + return self._base_method("WinWaitClose", seconds_to_wait=seconds_to_wait, blocking=True) or None + + def _move(self, x='', y='', width=None, height=None): + script = self._render_template( + 'window/win_move.ahk', + title=f"ahk_id {self.id}", + x=x, y=y, width=width, height=height + ) + return script def move(self, x='', y='', width=None, height=None): """ @@ -501,12 +587,19 @@ def move(self, x='', y='', width=None, height=None): :param height: :return: """ + script = self._move(x=x, y=y, width=width, height=height) + self.engine.run_script(script) + + def _send(self, keys, delay=10, raw=False, blocking=False, escape=False, press_duration=-1): + if escape: + keys = escape_sequence_replace(keys) script = self._render_template( - 'window/win_move.ahk', + 'window/win_send.ahk', title=f"ahk_id {self.id}", - x=x, y=y, width=width, height=height + keys=keys, raw=raw, delay=delay, + press_duration=press_duration, blocking=blocking ) - self.engine.run_script(script) + return script def send(self, keys, delay=10, raw=False, blocking=False, escape=False, press_duration=-1): """ @@ -514,26 +607,43 @@ def send(self, keys, delay=10, raw=False, blocking=False, escape=False, press_du Uses ControlSend https://autohotkey.com/docs/commands/Send.htm """ - if escape: - keys = escape_sequence_replace(keys) + script = self._send(keys, delay=delay, raw=raw, blocking=blocking, escape=escape, press_duration=press_duration) + return self.engine.run_script(script, blocking=blocking) + + def _click(self, x=None, y=None, *, button=None, n=1, options=None, blocking=True): + from ahk.mouse import resolve_button + if x or y: + if y is None and isinstance(x, collections.abc.Sequence) and len(x) == 2: + # alow position to be specified by a sequence of length 2 + 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) + script = self._render_template( - 'window/win_send.ahk', - title=f"ahk_id {self.id}", - keys=keys, raw=raw, delay=delay, - press_duration=press_duration, blocking=blocking + 'window/win_click.ahk', + x=x, y=y, hwnd=f"ahk_id {self.id}", button=button, n=n, options=options ) - return self.engine.run_script(script, blocking=blocking) - def click(self, x, y, blocking=False): + return script + + def click(self, *args, **kwargs): """ Click at an x/y location on the screen. Uses ControlClick https://autohotkey.com/docs/commands/ControlClick.htm + + x/y position params may also be specified as a 2-item sequence + + :param x: x offset relative to topleft corner of the window + :param y: y offset relative to the top of the window + :param button: the button to press (default is left mouse) + :param n: number of times to click + :param options: per ControlClick documentation + :param blocking: + :return: """ - script = self._render_template( - 'window/win_click.ahk', - x=x, y=y, hwnd=f"ahk_id {self.id}" - ) + blocking = kwargs.get('blocking', True) + script = self._click(*args, **kwargs) return self.engine.run_script(script, blocking=blocking) def __eq__(self, other): @@ -550,8 +660,7 @@ 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 + def _win_get(self, title='', text='', exclude_title='', exclude_text=''): script = self.render_template( 'window/get.ahk', subcommand='ID', @@ -560,9 +669,19 @@ def win_get(self, title='', text='', exclude_title='', exclude_text='', encoding exclude_text=exclude_text, exclude_title=exclude_title ) + return script + + def win_get(self, title='', text='', exclude_title='', exclude_text='', encoding=None): + script = self._win_get(title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text, encoding=encoding) + encoding = encoding or self.window_encoding 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) + return script + def win_set(self, subcommand, *args, blocking=True): script = self.render_template( 'window/set.ahk', subcommand=subcommand, *args, blocking=blocking) @@ -572,8 +691,14 @@ def win_set(self, subcommand, *args, blocking=True): def active_window(self): return self.win_get(title='A') - def _all_window_ids(self): + def get_active_window(self): + return self.active_window + + def _all_window_ids_(self): script = self.render_template('window/id_list.ahk') + return script + def _all_window_ids(self): + script = self._all_window_ids_() result = self.run_script(script) return result.split('\n')[:-1] # last one is always an empty string @@ -687,3 +812,418 @@ def find_window_by_class(self, *args, **kwargs): """ with suppress(StopIteration): return next(self.find_windows_by_class(*args, **kwargs)) + + +class AsyncWindow(Window): + # these methods are converted to async compatible versions automatically + _asyncifiable = ['disable', + 'enable', + 'redraw', + 'to_bottom', + 'to_top', + 'activate', + 'activate_bottom', + 'close', + 'hide', + 'kill', + 'maximize', + 'minimize', + 'restore', + 'show', + 'wait', + 'wait_active', + 'wait_not_active', + 'wait_close', + ] + + @classmethod + async def from_mouse_position(cls, engine: ScriptEngine, **kwargs): + script = engine.render_template('window/from_mouse.ahk') + ahk_id = await engine.a_run_script(script) + return cls(engine=engine, ahk_id=ahk_id, **kwargs) + + @classmethod + async def from_pid(cls, engine: ScriptEngine, pid, **kwargs): + script = engine.render_template('window/get.ahk', + subcommand="ID", + title=f'ahk_pid {pid}') + ahk_id = await engine.a_run_script(script) + return cls(engine=engine, ahk_id=ahk_id, **kwargs) + + def __getattr__(self, item): + if item in self._get_subcommands: + raise AttributeError(f"Unaccessable Attribute. Use get({item}) instead") + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'") + + + async def get(self, subcommand): + script = self._get(subcommand) + return await self.engine.a_run_script(script) + + async def set(self, subcommand, value): + script = self._set(subcommand, value) + await self.engine.a_run_script(script) + + async def get_pos(self): + script = self._get_pos() + resp = await self.engine.a_run_script(script) + try: + value = ast.literal_eval(resp) + return value + except SyntaxError: + raise WindowNotFoundError('No window found') + @staticmethod + async def _loop(): + return asyncio.get_event_loop() + + @property + async def rect(self): + warnings.warn("rect property blocks event loop. Use get_rect() instead", stacklevel=2) + return await self.get_pos() + + @rect.setter + def rect(self, new_position): + warnings.warn("rect setter only schedules coroutine. window may not change immediately. Use move() instead", stacklevel=2) + x, y, width, height = new_position + coro = self.move(x=x, y=y, width=width, height=height) + asyncio.create_task(coro) + + @property + async def position(self): + warnings.warn("position property blocks event loop. Use get_rect() instead", stacklevel=2) + x, y, _, _ = await self.get_pos() + return x, y + + @position.setter + def position(self, new_position): + warnings.warn("position setter only schedules coroutine. window may not change immediately. use move() instead", stacklevel=2) + x, y = new_position + coro = self.move(x, y) + asyncio.create_task(coro) + + @property + async def width(self): + warnings.warn("width property blocks event loop. Use get_rect() instead", stacklevel=2) + _, _, width, _ = await self.get_pos() + return width + + @width.setter + def width(self, new_width): + warnings.warn("width setter only schedules coroutine. window may not change immediately. use move() instead", stacklevel=2) + coro = self.move(width=new_width) + asyncio.create_task(coro) + + @property + async def height(self): + warnings.warn("height property blocks event loop. Use get_rect() instead", stacklevel=2) + _, _, _, height = await self.get_pos() + return height + + @height.setter + def height(self, new_height): + warnings.warn("height setter only schedules coroutine. window may not change immediately. use move() instead", stacklevel=2) + coro = self.move(height=new_height) + asyncio.create_task(coro) + + async def _base_property(self, command): + script = self._base_check(command) + resp = await self.engine.a_run_script(script) + return bool(ast.literal_eval(resp)) + + @property + async def active(self): + warnings.warn("active property blocks event loop. Use is_active() instead", stacklevel=2) + return await self._base_property(command="WinActive") + @property + async def exist(self): + warnings.warn("exist property blocks event loop. Use exists() instead", stacklevel=2) + return await self._base_property(command="WinExist") + + async def exists(self): + return await self._base_property('WinExist') + + async def _base_get_method(self, command): + script = self._base_get_method_(command) + result = await self.engine.a_run_script(script, decode=False) + if self.encoding: + return result.decode(encoding=self.encoding) + return result + + @property + async def title(self): + warnings.warn("title property blocks event loop. Use get_title() instead", stacklevel=2) + return await self._base_get_method("WinGetTitle") + + async def get_title(self): + await self._base_get_method("WinGetTitle") + + async def set_title(self, value): + script = self._set_title(value) + await self.engine.a_run_script(script) + + @title.setter + def title(self, new_title): + warnings.warn("title setter only schedules coroutine. window may not change immediately. use set_title() instead", stacklevel=2) + coro = self.set_title(new_title) + asyncio.create_task(coro) + + @property + async def class_name(self): + warnings.warn("class_name property blocks event loop. use get_class_name() instead.") + return await self._base_get_method("WinGetClass") + + async def get_class_name(self): + return await self._base_get_method("WinGetClass") + + @property + async def text(self): + warnings.warn("text property blocks event loop. use get_text() instead.") + return await self._base_get_method("WinGetText") + + async def get_text(self): + return await self._base_get_method("WinGetText") + + @property + async def minimized(self): + warnings.warn('property blocks event loop. use is_minimized() instead') + return await self.get("MinMax") == self.MINIMIZED + + @property + async def maximized(self): + warnings.warn('property blocks event loop. use is_maximized() instead') + return await self.get("MinMax") == self.MAXIMIZED + + async def is_minimized(self): + return await self._base_get_method("MinMax") == self.MINIMIZED + + async def is_maximized(self): + return await self._base_get_method("MinMax") == self.MAXIMIZED + + @property + async def non_max_non_min(self): + warnings.warn('property blocks event loop. use is_minmax() instead') + return await self.get("MinMax") == self.NON_MIN_NON_MAX + + async def is_minmax(self): + return await self.get("MinMax") != self.NON_MIN_NON_MAX + + @property + async def transparent(self) -> int: + warnings.warn('property blocks event loop. use get_transparency() instead') + result = await self.get("Transparent") + if result: + return int(result) + else: + return 255 + + @transparent.setter + def transparent(self, value): + warnings.warn("transparent setter only schedules coroutine. window may not change immediately. use set_transparency() instead", stacklevel=2) + + if isinstance(value, int) and 0 <= value <= 255: + coro = self.set("Transparent", value) + asyncio.create_task(coro) + else: + raise ValueError('transparency must be integer in range [0, 255]') + + async def get_transparency(self) -> int: + result = await self.get("Transparent") + if result: + return int(result) + else: + return 255 + + async def set_transparency(self, value): + if isinstance(value, int) and 0 <= value <= 255: + await self.set("Transparent", value) + else: + raise ValueError( + f'"{value}" not a valid option. Please use [0, 255] integer') + + @property + async def always_on_top(self) -> bool: + warnings.warn("always_on_top property blocks event loop. use is_always_on_top() instead") + script = self._always_on_top() + resp = await self.engine.a_run_script(script) + return bool(ast.literal_eval(resp)) + + async def is_always_on_top(self): + script = self._always_on_top() + resp = self.engine.run_script(script) + return bool(ast.literal_eval(resp)) + + @always_on_top.setter + def always_on_top(self, value): + warnings.warn(f"always_on_top setter only schedules coroutine. changes may not happen immediately. use set_always_on_top({repr(value)}) instead") + if value in ('on', 'On', True, 1): + coro = self.set('AlwaysOnTop', 'On') + elif value in ('off', 'Off', False, 0): + coro = self.set('AlwaysOnTop', 'Off') + elif value in ('toggle', 'Toggle', -1): + coro = self.set('AlwaysOnTop', 'Toggle') + else: + raise ValueError( + f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') + asyncio.create_task(coro) + + async def set_always_on_top(self, value): + if value in ('on', 'On', True, 1): + await self.set('AlwaysOnTop', 'On') + elif value in ('off', 'Off', False, 0): + await self.set('AlwaysOnTop', 'Off') + elif value in ('toggle', 'Toggle', -1): + await self.set('AlwaysOnTop', 'Toggle') + else: + raise ValueError( + f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') + + async def move(self, x='', y='', width=None, height=None): + script = self._move(x=x, y=y, width=width, height=height) + return await self.engine.a_run_script(script) + + async def send(self, keys, delay=10, raw=False, blocking=True, escape=False, press_duration=-1): + script = self._send(keys, delay=delay, raw=raw, blocking=blocking, escape=escape, press_duration=press_duration) + return await self.engine.a_run_script(script, blocking=blocking) + + async def activate(self): + return await self._base_method("WinActivate") + + async def _base_method(self, command, seconds_to_wait="", blocking=False): + script = self._base_method_(command, seconds_to_wait=seconds_to_wait) + return await self.engine.a_run_script(script, blocking=blocking) + + +class AsyncWindowMixin(WindowMixin): + async def win_get(self, *args, **kwargs): + script = self._win_get(*args, **kwargs) + encoding = kwargs.get('encoding', self.window_encoding) + ahk_id = await self.a_run_script(script) + return AsyncWindow(engine=self, ahk_id=ahk_id, encoding=encoding) + + async def win_set(self, subcommand, *args, blocking=True): + script = self.render_template( + 'window/set.ahk', subcommand=subcommand, *args, blocking=blocking) + await self.a_run_script(script, blocking=blocking) + + @property + def active_window(self): + warnings.warn("active_window property blocks event loop. use get_active_window() instead") + return self.win_get(title='A') + + async def _all_window_ids(self): + script = self._all_window_ids_() + result = await self.a_run_script(script) + return result.split('\n')[:-1] # last one is always an empty string + + async def windows(self): + """ + Returns a list of windows + + :return: + """ + windowze = [] + for ahk_id in await self._all_window_ids(): + win = AsyncWindow(engine=self, ahk_id=ahk_id, encoding=self.window_encoding) + windowze.append(win) + return windowze + + async def find_windows(self, func=None, **kwargs): + """ + Find all matching windows + + :param func: a callable to filter windows + :param bool exact: if False (the default) partial matches are found. If True, only exact matches are returned + :param kwargs: keywords of attributes of the window (has no effect if ``func`` is provided) + + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + """ + if func is None: + exact = kwargs.pop('exact', False) + + async def func(win): + for attr, expected in kwargs.items(): + if exact: + result = await getattr(win, attr) == expected + else: + result = expected in await getattr(win, attr) + if result is False: + return False + return True + async for window in async_filter(func, await self.windows()): + yield window + + async def find_window(self, func=None, **kwargs): + """ + Like ``find_windows`` but only returns the first found window + + + :param func: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ + async for window in self.find_windows(func=func, **kwargs): + return window # return the first result + raise WindowNotFoundError("yikes") + + async def find_windows_by_title(self, title, exact=False): + """ + Equivalent to ``find_windows(title=title)``` + + Note that ``title`` is a ``bytes`` object + + :param bytes title: + :param exact: + :return: + """ + async for window in self.find_windows(title=title, exact=exact): + yield window + + async def find_window_by_title(self, title): + """ + Like ``find_windows_by_title`` but only returns the first result. + + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ + + async for window in self.find_windows_by_title(): + return window + + async def find_windows_by_text(self, text, exact=False): + """ + + :param text: + :param exact: + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + """ + async for window in self.find_windows(text=text, exact=exact): + yield window + + async def find_window_by_text(self, *args, **kwargs): + """ + + :param args: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ + async for window in self.find_windows_by_text(*args, **kwargs): + return window + + async def find_windows_by_class(self, class_name, exact=False): + """ + + :param class_name: + :param exact: + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + """ + async for window in self.find_windows(class_name=class_name, exact=exact): + yield window + + async def find_window_by_class(self, *args, **kwargs): + """ + :param args: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + """ + async for window in self.find_windows_by_class(*args, **kwargs): + return window + diff --git a/tests/unittests/test_keyboard_async.py b/tests/unittests/test_keyboard_async.py new file mode 100644 index 00000000..905a3f02 --- /dev/null +++ b/tests/unittests/test_keyboard_async.py @@ -0,0 +1,173 @@ +import os +import subprocess +import sys +import threading +import time +import asyncio +from itertools import product +from unittest import TestCase + + +project_root = os.path.abspath( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") +) +sys.path.insert(0, project_root) +from ahk import AsyncAHK, AHK +from ahk.keys import ALT, CTRL, KEYS + + +class TestKeyboard(TestCase): + def setUp(self): + """ + Record all open windows + :return: + """ + self.ahk = AsyncAHK() + #self._normal_ahk = AHK() + #self.before_windows = self._normal_ahk.windows() + self.p = subprocess.Popen("notepad") + time.sleep(1) + + def tearDown(self): + self.p.terminate() + time.sleep(0.2) + + async def a_window_send(self): + notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + await notepad.send("hello world") + await asyncio.sleep(1) + self.assertIn(b'hello world', await notepad.get_text()) + + def test_window_send(self): + asyncio.run(self.a_window_send()) + # self.notepad.send("hello world") + # time.sleep(1) + # self.assertIn(b"hello world", self.notepad.text) + # + + async def a_send(self): + notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + await notepad.activate() + await self.ahk.send('hello world') + self.assertIn(b'hello world', await notepad.get_text()) + def test_send(self): + asyncio.run(self.a_send()) + # 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 test_type_escapes_equals(self): + # """ + # https://github.com/spyoungtech/ahk/issues/96 + # """ + # self.notepad.activate() + # self.ahk.type("=foo") + # assert b"=foo" in self.notepad.text + # + # def test_sendraw_equals(self): + # """ + # https://github.com/spyoungtech/ahk/issues/96 + # """ + # self.notepad.activate() + # self.ahk.send_raw("=foo") + # assert b"=foo" in self.notepad.text + # + # def test_set_capslock_state(self): + # self.ahk.set_capslock_state("on") + # assert self.ahk.key_state("CapsLock", "T") + +# +# 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")) From 6f747c75360c1494151d10b871f99b7d0fc5051b Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Tue, 5 Jan 2021 03:16:36 -0800 Subject: [PATCH 086/588] async utils --- ahk/utils.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ahk/utils.py b/ahk/utils.py index 5e57062e..20b72e14 100644 --- a/ahk/utils.py +++ b/ahk/utils.py @@ -1,4 +1,6 @@ import logging +import functools +from asyncio import coroutine ESCAPE_SEQUENCE_MAP = { '\n': '`n', @@ -47,3 +49,29 @@ def escape_sequence_replace(s): """ return s.translate(_TRANSLATION_TABLE) +def asyncify(cls, sync_method): + @functools.wraps(sync_method) + async def async_method(self, *args, **kwargs): + self_sync_method = getattr(super(cls, self), sync_method.__name__) + coro = self_sync_method(*args, **kwargs) + return await coro + return async_method + +class AsyncifyMeta(type): + def __new__(cls, *args, **kwargs): + asyncifyable = getattr(cls, '_asyncifyable', None) + if not asyncifyable: + return cls + + for name in asyncifyable: + obj = getattr(cls, name) + if not callable(obj) or isinstance(obj, type) or isinstance(obj, property): + raise ValueError(f'{repr(obj)} object is not asyncifyable)') + setattr(cls, f'{name}', asyncify(cls, obj)) + return super().__new__(cls, *args, **kwargs) + +async def async_filter(async_pred, iterable): + for item in iterable: + should_yield = await async_pred(item) + if should_yield: + yield item \ No newline at end of file From 93182054e672db8b9609bfdb6be41913919268eb Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Tue, 5 Jan 2021 03:47:25 -0800 Subject: [PATCH 087/588] fix asyncify meta --- ahk/utils.py | 12 +++++++----- ahk/window.py | 5 +---- tests/unittests/test_keyboard_async.py | 1 + 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ahk/utils.py b/ahk/utils.py index 20b72e14..dc4f4fed 100644 --- a/ahk/utils.py +++ b/ahk/utils.py @@ -49,16 +49,18 @@ def escape_sequence_replace(s): """ return s.translate(_TRANSLATION_TABLE) -def asyncify(cls, sync_method): +def asyncify(sync_method): + cls = sync_method.__class__ @functools.wraps(sync_method) async def async_method(self, *args, **kwargs): - self_sync_method = getattr(super(cls, self), sync_method.__name__) + self_sync_method = getattr(super(self.__class__, self), sync_method.__name__) coro = self_sync_method(*args, **kwargs) return await coro return async_method class AsyncifyMeta(type): - def __new__(cls, *args, **kwargs): + def __new__(typ, *args, **kwargs): + cls = super().__new__(typ, *args, **kwargs) asyncifyable = getattr(cls, '_asyncifyable', None) if not asyncifyable: return cls @@ -67,8 +69,8 @@ def __new__(cls, *args, **kwargs): obj = getattr(cls, name) if not callable(obj) or isinstance(obj, type) or isinstance(obj, property): raise ValueError(f'{repr(obj)} object is not asyncifyable)') - setattr(cls, f'{name}', asyncify(cls, obj)) - return super().__new__(cls, *args, **kwargs) + setattr(cls, f'{name}', asyncify(obj)) + return cls async def async_filter(async_pred, iterable): for item in iterable: diff --git a/ahk/window.py b/ahk/window.py index d2d57085..440b7bd8 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -814,7 +814,7 @@ def find_window_by_class(self, *args, **kwargs): return next(self.find_windows_by_class(*args, **kwargs)) -class AsyncWindow(Window): +class AsyncWindow(Window, metaclass=AsyncifyMeta): # these methods are converted to async compatible versions automatically _asyncifiable = ['disable', 'enable', @@ -1085,9 +1085,6 @@ async def send(self, keys, delay=10, raw=False, blocking=True, escape=False, pre script = self._send(keys, delay=delay, raw=raw, blocking=blocking, escape=escape, press_duration=press_duration) return await self.engine.a_run_script(script, blocking=blocking) - async def activate(self): - return await self._base_method("WinActivate") - async def _base_method(self, command, seconds_to_wait="", blocking=False): script = self._base_method_(command, seconds_to_wait=seconds_to_wait) return await self.engine.a_run_script(script, blocking=blocking) diff --git a/tests/unittests/test_keyboard_async.py b/tests/unittests/test_keyboard_async.py index 905a3f02..1655564e 100644 --- a/tests/unittests/test_keyboard_async.py +++ b/tests/unittests/test_keyboard_async.py @@ -31,6 +31,7 @@ def setUp(self): def tearDown(self): self.p.terminate() time.sleep(0.2) + asyncio.run(asyncio.sleep(0.2)) async def a_window_send(self): notepad = await self.ahk.find_window(title=b"Untitled - Notepad") From 4a5f42d8f8994a8001c8c7381f7f3501f829e94c Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Tue, 5 Jan 2021 03:51:50 -0800 Subject: [PATCH 088/588] fix win_get parameters --- ahk/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/window.py b/ahk/window.py index 440b7bd8..328e5bfb 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -672,7 +672,7 @@ def _win_get(self, title='', text='', exclude_title='', exclude_text=''): return script def win_get(self, title='', text='', exclude_title='', exclude_text='', encoding=None): - script = self._win_get(title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text, encoding=encoding) + script = self._win_get(title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text) encoding = encoding or self.window_encoding ahk_id = self.run_script(script) return Window(engine=self, ahk_id=ahk_id, encoding=encoding) @@ -1092,8 +1092,8 @@ async def _base_method(self, command, seconds_to_wait="", blocking=False): class AsyncWindowMixin(WindowMixin): async def win_get(self, *args, **kwargs): + encoding = kwargs.pop('encoding', self.window_encoding) script = self._win_get(*args, **kwargs) - encoding = kwargs.get('encoding', self.window_encoding) ahk_id = await self.a_run_script(script) return AsyncWindow(engine=self, ahk_id=ahk_id, encoding=encoding) From 737c64de439724dc0e773a58a22f7f7dfddd9cb0 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Tue, 5 Jan 2021 04:03:51 -0800 Subject: [PATCH 089/588] use python3.8 for appveyor --- appveyor.yml | 2 +- ci/install.bat | 2 +- setup.py | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index e5210322..252158d4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -37,7 +37,7 @@ cache: deploy_script: - ps: | if ($env:APPVEYOR_REPO_TAG -eq "true") { - py -3.7 -m venv deploy_venv + py -3.8 -m venv deploy_venv .\deploy_venv\Scripts\activate.ps1 python -m pip install --upgrade pip pip install --upgrade wheel diff --git a/ci/install.bat b/ci/install.bat index 3b3917d7..ad8331f7 100644 --- a/ci/install.bat +++ b/ci/install.bat @@ -1,4 +1,4 @@ -py -3.7 -m venv venv +py -3.8 -m venv venv call venv\Scripts\activate.bat python -m pip install --upgrade pip python -m pip install --upgrade -r .\ci\ci_requirements.txt diff --git a/setup.py b/setup.py index 3eacaf25..7723e68c 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,8 @@ 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', ], tests_require=test_requirements, include_package_data=True, From fcbf705b86b4d5ee17a4fd595acdb38071add11f Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Tue, 5 Jan 2021 04:54:51 -0800 Subject: [PATCH 090/588] tests! --- tests/unittests/test_keyboard_async.py | 206 ++++++++++++------------- 1 file changed, 101 insertions(+), 105 deletions(-) diff --git a/tests/unittests/test_keyboard_async.py b/tests/unittests/test_keyboard_async.py index 1655564e..b7301594 100644 --- a/tests/unittests/test_keyboard_async.py +++ b/tests/unittests/test_keyboard_async.py @@ -16,7 +16,7 @@ from ahk.keys import ALT, CTRL, KEYS -class TestKeyboard(TestCase): +class TestKeyboardAsync(TestCase): def setUp(self): """ Record all open windows @@ -41,10 +41,7 @@ async def a_window_send(self): def test_window_send(self): asyncio.run(self.a_window_send()) - # self.notepad.send("hello world") - # time.sleep(1) - # self.assertIn(b"hello world", self.notepad.text) - # + async def a_send(self): notepad = await self.ahk.find_window(title=b"Untitled - Notepad") @@ -53,110 +50,109 @@ async def a_send(self): self.assertIn(b'hello world', await notepad.get_text()) def test_send(self): asyncio.run(self.a_send()) - # 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 test_type_escapes_equals(self): - # """ - # https://github.com/spyoungtech/ahk/issues/96 - # """ - # self.notepad.activate() - # self.ahk.type("=foo") - # assert b"=foo" in self.notepad.text - # - # def test_sendraw_equals(self): - # """ - # https://github.com/spyoungtech/ahk/issues/96 - # """ - # self.notepad.activate() - # self.ahk.send_raw("=foo") - # assert b"=foo" in self.notepad.text - # - # def test_set_capslock_state(self): - # self.ahk.set_capslock_state("on") - # assert self.ahk.key_state("CapsLock", "T") + + async def a_send_input(self): + notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + await self.ahk.send_input("Hello World") + await asyncio.sleep(0.5) + assert b"Hello World" in await notepad.get_text() + + def test_send_input(self): + asyncio.run(self.a_send_input()) + + async def a_type(self): + notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + await notepad.activate() + await self.ahk.type("Hello, World!") + assert b"Hello, World!" in await notepad.get_text() + + def test_type(self): + asyncio.run(self.a_type()) # -# 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 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") + # -# def test_key_state_pressed(self): -# self.ahk.key_down("Control") -# self.assertTrue(self.ahk.key_state("Control")) +class TestKeys(TestCase): + def setUp(self): + self.ahk = AsyncAHK() + self._normal_ahk = AHK() + self.thread = None + self.hotkey = None + + def tearDown(self): + if self.thread is not None: + self.thread.join(timeout=3) + if self._normal_ahk.key_state("a"): + self._normal_ahk.key_up("a") + if self._normal_ahk.key_state("Control"): + self._normal_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() + + async def a_key_wait_pressed(self): + await self.ahk.key_wait("a", timeout=5) + + def test_key_wait_pressed(self): + start = time.time() + self.thread = threading.Thread(target=a_down) + self.thread.start() + asyncio.run(self.a_key_wait_pressed()) + end = time.time() + assert end - start < 5 + + async def a_key_wait_released(self): + await self.ahk.key_wait("a", timeout=2) + + def test_key_wait_released(self): + start = time.time() + a_down() + self.thread = threading.Thread(target=release_a) + self.thread.start() + asyncio.run(self.a_key_wait_released()) + end = time.time() + assert end - start < 2 + + async def a_key_wait_timeout(self): + await self.ahk.key_wait('f', timeout=1) + + def test_key_wait_timeout(self): + self.assertRaises(TimeoutError, asyncio.run, self.a_key_wait_timeout()) + + + async def a_key_state_when_not_pressed(self): + return await self.ahk.key_state("a") + + def test_key_state_when_not_pressed(self): + self.assertFalse(asyncio.run(self.a_key_state_when_not_pressed())) # + async def a_key_state_pressed(self): + await self.ahk.key_down("Control") + self.assertTrue(await self.ahk.key_state("Control")) + + def test_key_state_pressed(self): + asyncio.run(self.a_key_state_pressed()) + # def test_hotkey(self): # self.hotkey = self.ahk.hotkey(hotkey="a", script="Run Notepad") # self.thread = threading.Thread(target=a_down) From 7fd88e3813383b0092d6b70fbac25500eca28699 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Tue, 5 Jan 2021 05:37:29 -0800 Subject: [PATCH 091/588] more tests --- ahk/window.py | 8 +- tests/unittests/test_keyboard_async.py | 2 +- tests/unittests/test_window_async.py | 114 +++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 tests/unittests/test_window_async.py diff --git a/ahk/window.py b/ahk/window.py index 328e5bfb..f8945181 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -955,7 +955,7 @@ async def title(self): return await self._base_get_method("WinGetTitle") async def get_title(self): - await self._base_get_method("WinGetTitle") + return await self._base_get_method("WinGetTitle") async def set_title(self, value): script = self._set_title(value) @@ -994,10 +994,10 @@ async def maximized(self): return await self.get("MinMax") == self.MAXIMIZED async def is_minimized(self): - return await self._base_get_method("MinMax") == self.MINIMIZED + return await self.get("MinMax") == self.MINIMIZED async def is_maximized(self): - return await self._base_get_method("MinMax") == self.MAXIMIZED + return await self.get("MinMax") == self.MAXIMIZED @property async def non_max_non_min(self): @@ -1085,7 +1085,7 @@ async def send(self, keys, delay=10, raw=False, blocking=True, escape=False, pre script = self._send(keys, delay=delay, raw=raw, blocking=blocking, escape=escape, press_duration=press_duration) return await self.engine.a_run_script(script, blocking=blocking) - async def _base_method(self, command, seconds_to_wait="", blocking=False): + async def _base_method(self, command, seconds_to_wait="", blocking=True): script = self._base_method_(command, seconds_to_wait=seconds_to_wait) return await self.engine.a_run_script(script, blocking=blocking) diff --git a/tests/unittests/test_keyboard_async.py b/tests/unittests/test_keyboard_async.py index b7301594..4ede2229 100644 --- a/tests/unittests/test_keyboard_async.py +++ b/tests/unittests/test_keyboard_async.py @@ -88,7 +88,7 @@ def press_a(): ahk.key_press("a") # -class TestKeys(TestCase): +class TestKeysAsync(TestCase): def setUp(self): self.ahk = AsyncAHK() self._normal_ahk = AHK() diff --git a/tests/unittests/test_window_async.py b/tests/unittests/test_window_async.py new file mode 100644 index 00000000..ea772352 --- /dev/null +++ b/tests/unittests/test_window_async.py @@ -0,0 +1,114 @@ +import subprocess +import time +from unittest import TestCase +import asyncio +import os +import sys +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, AsyncAHK +from ahk.window import AsyncWindow + + +class TestWindowAsync(TestCase): + win: AsyncWindow + def setUp(self): + self.ahk = AsyncAHK() + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = asyncio.run(self.ahk.win_get(title='Untitled - Notepad')) + self.assertIsNotNone(self.win) + + async def a_transparent(self): + self.assertEqual(await self.win.get_transparency(), 255) + + self.win.transparent = 220 + self.assertEqual(await self.win.get_transparency(), 220) + + self.win.transparent = 255 + self.assertEqual(await self.win.transparent, 255) + + + def test_transparent(self): + asyncio.run(self.a_transparent()) +# + def test_pinned(self): + asyncio.run(self.a_pinned()) + async def a_pinned(self): + self.assertFalse(await self.win.always_on_top) + + await self.win.set_always_on_top(True) + self.assertTrue(await self.win.is_always_on_top()) + + self.win.always_on_top = False + await asyncio.sleep(1) + self.assertFalse(await self.win.always_on_top) + + async def a_close(self): + await self.win.close() + await asyncio.sleep(0.2) + self.assertFalse(await self.win.exists()) + self.assertFalse(await self.win.exist) + + def test_close(self): + asyncio.run(self.a_close()) + + async def a_show_hide(self): + await self.win.hide() + await asyncio.sleep(0.5) + self.assertFalse(await self.win.exist) + + await self.win.show() + await asyncio.sleep(0.5) + self.assertTrue(await self.win.exist) + + def test_show_hide(self): + asyncio.run(self.a_show_hide()) + + async def a_kill(self): + await self.win.kill() + await asyncio.sleep(0.5) + self.assertFalse(await self.win.exist) + + def test_kill(self): + asyncio.run(self.a_kill()) + + async def a_max_min(self): + self.assertTrue(await self.win.non_max_non_min) + self.assertFalse(await self.win.is_minmax()) + + await self.win.maximize() + await asyncio.sleep(1) + self.assertTrue(await self.win.maximized) + self.assertTrue(await self.win.is_maximized()) + + await self.win.minimize() + await asyncio.sleep(1) + self.assertTrue(await self.win.minimized) + self.assertTrue(await self.win.is_minimized()) + + await self.win.restore() + await asyncio.sleep(0.5) + self.assertTrue(await self.win.maximized) + self.assertTrue(await self.win.is_maximized()) + def test_max_min(self): + asyncio.run(self.a_max_min()) +# + async def a_names(self): + self.assertEqual(await self.win.class_name, b'Notepad') + self.assertEqual(await self.win.get_class_name(), b'Notepad') + + self.assertEqual(await self.win.title, b'Untitled - Notepad') + self.assertEqual(await self.win.get_title(), b'Untitled - Notepad') + + self.assertEqual(await self.win.text, b'') + self.assertEqual(await self.win.get_text(), b'') + + + def test_names(self): + asyncio.run(self.a_names()) +# + def tearDown(self): + self.p.terminate() From c92e9f545a89d1b437c6dd72a9ad25eaf2e6a8b5 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Tue, 5 Jan 2021 05:41:25 -0800 Subject: [PATCH 092/588] try to avoid random "I/O operation on closed pipe" errors --- tests/unittests/test_window_async.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unittests/test_window_async.py b/tests/unittests/test_window_async.py index ea772352..2b7b16d7 100644 --- a/tests/unittests/test_window_async.py +++ b/tests/unittests/test_window_async.py @@ -112,3 +112,4 @@ def test_names(self): # def tearDown(self): self.p.terminate() + asyncio.run(asyncio.sleep(0.5)) From bed040a2a9f72b5bebb9fe80c0282d38d93b8eb6 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 08:09:13 -0800 Subject: [PATCH 093/588] major simplification --- ahk/keyboard.py | 58 +----- ahk/mouse.py | 76 +++---- ahk/script.py | 3 + ahk/templates/window/win_position.ahk | 10 + ahk/window.py | 274 ++++++------------------- tests/unittests/test_keyboard.py | 9 +- tests/unittests/test_keyboard_async.py | 50 ++--- tests/unittests/test_mouse.py | 33 +++ tests/unittests/test_win_get_async.py | 43 ++++ tests/unittests/test_window_async.py | 36 ++-- 10 files changed, 229 insertions(+), 363 deletions(-) create mode 100644 tests/unittests/test_mouse.py create mode 100644 tests/unittests/test_win_get_async.py diff --git a/ahk/keyboard.py b/ahk/keyboard.py index 9b87e8f0..0c9ab8d8 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -1,7 +1,7 @@ import ast import warnings -from ahk.script import ScriptEngine +from ahk.script import ScriptEngine, AsyncScriptEngine from ahk.utils import escape_sequence_replace from ahk.keys import Key from ahk.directives import InstallKeybdHook, InstallMouseHook @@ -142,7 +142,7 @@ def type(self, s, blocking=True): :param blocking: if ``True``, waits until script finishes, else returns immediately. """ s = escape_sequence_replace(s) - self.send_input(s, blocking=blocking) + return self.send_input(s, blocking=blocking) or None def _send(self, s, raw=False, delay=None, blocking=True): script = self.render_template( @@ -161,7 +161,7 @@ def send(self, s, raw=False, delay=None, blocking=True): :return: """ script = self._send(s, raw=raw, delay=delay, blocking=blocking) - self.run_script(script, blocking=blocking) + return self.run_script(script, blocking=blocking) or None def send_raw(self, s, delay=None): """ @@ -192,7 +192,7 @@ def _send_input(self, s, blocking=True): def send_input(self, s, blocking=True): script = self._send_input(s, blocking=blocking) - self.run_script(script, blocking=blocking) + return self.run_script(script, blocking=blocking) or None def _send_play(self, s): script = self.render_template("keyboard/send_play.ahk", s=s) @@ -207,7 +207,7 @@ def send_play(self, s): :return: """ script = self._send_play(s) - self.run_script(script) + return self.run_script(script) or None def _send_event(self, s, delay=None): script = self.render_template("keyboard/send_event.ahk", s=s, delay=delay) @@ -223,7 +223,7 @@ def send_event(self, s, delay=None): :return: """ script = self._send_event(s, delay=delay) - self.run_script(script) + return self.run_script(script) or None def key_press(self, key, release=True, blocking=True): """ @@ -294,13 +294,13 @@ def set_capslock_state(self, state=None): :return: """ script = self._set_capslock_state(state) - self.run_script(script) + return self.run_script(script) or None -class AsyncKeyboardMixin(KeyboardMixin): - async def send_input(self, s, blocking=True): - script = self._send_input(s, blocking=blocking) - return await self.a_run_script(script, blocking=blocking) or None +class AsyncKeyboardMixin(AsyncScriptEngine, KeyboardMixin): + # async def send_input(self, s, blocking=True): + # script = self._send_input(s, blocking=blocking) + # return await self.a_run_script(script, blocking=blocking) or None async def key_state(self, key_name, mode=None) -> bool: @@ -318,43 +318,7 @@ async def key_wait( if result == "1": raise TimeoutError(f"timed out waiting for {key_name}") - async def type(self, s, blocking=True): - s = escape_sequence_replace(s) - await self.send_input(s, blocking=blocking) - - async def send(self, s, raw=False, delay=None, blocking=True): - script = self._send(s, raw=raw, delay=delay, blocking=blocking) - await self.a_run_script(script, blocking=blocking) - - async def send_raw(self, s, delay=None): - return await self.send(s, raw=True, delay=delay) - - async def send_play(self, s): - script = self._send_play(s) - await self.a_run_script(script) - - async def send_event(self, s, delay=None): - script = self._send_event(s, delay=delay) - await self.a_run_script(script) - async def key_press(self, key, release=True, blocking=True): await self.key_down(key, blocking=blocking) if release: await self.key_up(key, blocking=blocking) - - async def key_release(self, key, blocking=True): - if isinstance(key, str): - key = Key(key_name=key) - return await self.send_input(key.UP, blocking=blocking) - - async def key_down(self, key, blocking=True): - if isinstance(key, str): - key = Key(key_name=key) - await self.send_input(key.DOWN, blocking=blocking) - - async def key_up(self, key, blocking=True): - await self.key_release(key, blocking=blocking) - - async def set_capslock_state(self, state=None): - script = self._set_capslock_state(state) - await self.a_run_script(script) diff --git a/ahk/mouse.py b/ahk/mouse.py index 0a5dd993..f53af540 100644 --- a/ahk/mouse.py +++ b/ahk/mouse.py @@ -1,7 +1,8 @@ +import asyncio from collections import namedtuple from typing import Union - -from ahk.script import ScriptEngine +import warnings +from ahk.script import ScriptEngine, AsyncScriptEngine from ahk.utils import make_logger import ast @@ -76,15 +77,18 @@ def _mouse_position(self, mode=None): @property def mouse_position(self): - script = self._mouse_position() - response = self.run_script(script) - return ast.literal_eval(response) + return self.get_mouse_position() @mouse_position.setter def mouse_position(self, position): x, y = position self.mouse_move(x=x, y=y, speed=0, relative=False) + def get_mouse_position(self, mode=None): + script = self._mouse_position(mode=mode) + response = self.run_script(script) + return ast.literal_eval(response) + 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') @@ -119,7 +123,7 @@ def mouse_move(self, *args, **kwargs): """ blocking = kwargs.get('blocking', True) script = self._mouse_move(*args, **kwargs) - self.run_script(script, blocking=blocking) + return self.run_script(script, blocking=blocking) or None def _click(self, x=None, y=None, *, button=None, n=None, direction=None, relative=None, blocking=True, mode=None): if x or y: @@ -154,7 +158,7 @@ def click(self, *args, **kwargs): """ blocking = kwargs.get('blocking', True) script = self._click(*args, **kwargs) - self.run_script(script, blocking=blocking) + return self.run_script(script, blocking=blocking) or None def double_click(self, *args, **kwargs): """ @@ -166,7 +170,7 @@ def double_click(self, *args, **kwargs): """ n = kwargs.get('n', 1) kwargs['n'] = n * 2 - self.click(*args, **kwargs) + return self.click(*args, **kwargs) or None def right_click(self, *args, **kwargs): """ @@ -177,7 +181,7 @@ def right_click(self, *args, **kwargs): :return: """ kwargs['button'] = 2 - return self.click(*args, **kwargs) + return self.click(*args, **kwargs) or None def mouse_wheel(self, direction, *args, **kwargs): """ @@ -190,7 +194,7 @@ def mouse_wheel(self, direction, *args, **kwargs): """ assert direction in ('up', 'down') kwargs['button'] = f'Wheel{direction}' - self.click(*args, **kwargs) + return self.click(*args, **kwargs) or None def wheel_up(self, *args, **kwargs): """ @@ -200,7 +204,7 @@ def wheel_up(self, *args, **kwargs): :param kwargs: :return: """ - self.mouse_wheel('up', *args, **kwargs) + return self.mouse_wheel('up', *args, **kwargs) or None def wheel_down(self, *args, **kwargs): """ @@ -210,7 +214,7 @@ def wheel_down(self, *args, **kwargs): :param kwargs: :return: """ - self.mouse_wheel('down', *args, **kwargs) + return self.mouse_wheel('down', *args, **kwargs) or None def _mouse_drag(self, x, y=None, *, from_position=None, speed=None, button: Union[str, int] =1, relative=None, blocking=True, mode=None): if from_position is None: @@ -264,46 +268,18 @@ def mouse_drag(self, *args, **kwargs): """ blocking = kwargs.get('blocking', True) script = self._mouse_drag(*args, **kwargs) - self.run_script(script, blocking=blocking) + return self.run_script(script, blocking=blocking) or None -class AsyncMouseMixin(MouseMixin): - async def mouse_move(self, *args, **kwargs): - blocking = kwargs.get('blocking', True) - script = self._mouse_move(*args, **kwargs) - await self.a_run_script(script, blocking=blocking) - - async def click(self, *args, **kwargs): - blocking = kwargs.get('blocking', True) - script = self._click(*args, **kwargs) - await self.a_run_script(script, blocking=blocking) - - async def double_click(self, *args, **kwargs): - n = kwargs.get('n', 1) - kwargs['n'] = n * 2 - await self.click(*args, **kwargs) - - async def right_click(self, *args, **kwargs): - kwargs['button'] = 2 - await self.click(*args, **kwargs) - - async def mouse_wheel(self, direction, *args, **kwargs): - assert direction in ('up', 'down') - kwargs['button'] = f'Wheel{direction}' - await self.click(*args, **kwargs) - - async def wheel_up(self, *args, **kwargs): - await self.mouse_wheel('up', *args, **kwargs) - - async def wheel_down(self, *args, **kwargs): - await self.mouse_wheel('down', *args, **kwargs) - - async def mouse_drag(self, *args, **kwargs): - blocking = kwargs.get('blocking', True) - script = self._mouse_drag(*args, **kwargs) - await self.a_run_script(script, blocking=blocking) - +class AsyncMouseMixin(AsyncScriptEngine, MouseMixin): async def get_mouse_position(self, mode=None): script = self._mouse_position(mode=mode) - return await self.a_run_script(script) + response = await self.a_run_script(script) + return ast.literal_eval(response) + @MouseMixin.mouse_position.setter + def mouse_position(self, position): + warnings.warn("mouse_position setter only schedules coroutine. use mouse_move() (with speed=0) instead") + x, y = position + coro = self.mouse_move(x=x, y=y, speed=0, relative=False) + asyncio.create_task(coro) diff --git a/ahk/script.py b/ahk/script.py index b06cf8b8..a1dfb0a2 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -219,3 +219,6 @@ def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): logger.fatal('Error running temp script: %s', e) raise return result + +class AsyncScriptEngine(ScriptEngine): + run_script = ScriptEngine.a_run_script diff --git a/ahk/templates/window/win_position.ahk b/ahk/templates/window/win_position.ahk index 2f23b289..a011fbc2 100644 --- a/ahk/templates/window/win_position.ahk +++ b/ahk/templates/window/win_position.ahk @@ -1,6 +1,16 @@ {% extends "base.ahk" %} {% block body %} WinGetPos, x, y, width, height, {{ title }} +{% if pos_info %} +{% if pos_info == "position" %} +s .= Format("({}, {})", x, y) +{% elif pos_info == "height" %} +s .= Format("({})", height) +{% elif pos_info == "width" %} +s .= Format("({})", width) +{% endif %} +{% else %} s .= Format("({}, {}, {}, {})", x, y, width, height) +{% endif %} FileAppend, %s%, * {% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index f8945181..fc9c71a6 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -4,7 +4,7 @@ from contextlib import suppress import warnings from types import CoroutineType -from ahk.script import ScriptEngine +from ahk.script import ScriptEngine, AsyncScriptEngine from ahk.utils import escape_sequence_replace, make_logger, AsyncifyMeta, async_filter logger = make_logger(__name__) @@ -195,20 +195,19 @@ def set(self, subcommand, value): script = self._set(subcommand, value) return self.engine.run_script(script) - def _get_pos(self): + def _get_pos(self, info=None): script = self._render_template( 'window/win_position.ahk', - title=f"ahk_id {self.id}" + title=f"ahk_id {self.id}", + pos_info=info ) return script - def get_pos(self): + def get_pos(self, info=None): """ - Same as ``rect`` - :return: """ - script = self._get_pos() + script = self._get_pos(info=info) resp = self.engine.run_script(script) try: value = ast.literal_eval(resp) @@ -227,18 +226,19 @@ def rect(self, new_position): @property def position(self): - x, y, _, _ = self.get_pos() - - return x, y + return self.get_pos('position') @position.setter def position(self, new_position): + self.set_position(new_position) + + def set_position(self, new_position): x, y = new_position - self.move(x, y) + return self.move(x, y) @property def width(self): - _, _, width, _ = self.get_pos() + width = self.get_pos('width') return width @width.setter @@ -247,7 +247,7 @@ def width(self, new_width): @property def height(self): - _, _, _, height = self.get_pos() + height = self.get_pos('height') return height @height.setter @@ -269,17 +269,17 @@ def _base_property(self, command): @property def active(self): - return self._base_property(command="WinActive") + return self.is_active() def is_active(self): - return self.active + return self._base_property(command="WinActive") @property def exist(self): - return self._base_property(command="WinExist") + return self.exists() def exists(self): - return self.exist + return self._base_property(command="WinExist") def _base_get_method_(self, command): script = self._render_template( @@ -297,10 +297,10 @@ def _base_get_method(self, command): @property def title(self): - return self._base_get_method("WinGetTitle") + return self.get_title() def get_title(self): - return self.title + return self._base_get_method("WinGetTitle") def _set_title(self, value): script = self._render_template( @@ -312,34 +312,34 @@ def _set_title(self, value): @title.setter def title(self, value): - script = self._set_title(value) - return self.engine.run_script(script) + self.set_title(value) def set_title(self, value): script = self._set_title(value) - self.engine.run_script(script) + return self.engine.run_script(script) or None @property def class_name(self): - return self._base_get_method("WinGetClass") + return self.get_class_name() + @property def text(self): - return self._base_get_method("WinGetText") + return self.get_text() @property def minimized(self): - return self.get("MinMax") == self.MINIMIZED + return self.is_minimized() @property def maximized(self): - return self.get("MinMax") == self.MAXIMIZED + return self.is_maximized() def is_minimized(self): - return self.minimized + return self.get("MinMax") == self.MINIMIZED def is_maximized(self): - return self.maximized + return self.get("MinMax") == self.MAXIMIZED @property def non_max_non_min(self): @@ -349,32 +349,33 @@ def is_minmax(self): return self.get("MinMax") != self.NON_MIN_NON_MAX def get_class_name(self): - return self.class_name + return self._base_get_method("WinGetClass") def get_text(self): - return self.text + return self._base_get_method("WinGetText") @property def transparent(self) -> int: + return self.get_transparency() + + def get_transparency(self) -> int: result = self.get("Transparent") if result: return int(result) else: return 255 - def get_transparency(self) -> int: - return self.transparent - @transparent.setter def transparent(self, value): + self.set_transparency(value) + + def set_transparency(self, value): if isinstance(value, int) and 0 <= value <= 255: - self.set("Transparent", value) + return self.set("Transparent", value) or None else: raise ValueError( - f'"{value}" not a valid option. Please use [0, 255] integer') - - def set_transparency(self, new_value): - self.transparent = new_value + f'"{value}" not a valid option. Please use [0, 255] integer' + ) def _always_on_top(self): script = self._render_template( @@ -385,28 +386,28 @@ def _always_on_top(self): @property def always_on_top(self) -> bool: + return self.is_always_on_top() + + def is_always_on_top(self) -> bool: script = self._always_on_top() resp = self.engine.run_script(script) return bool(ast.literal_eval(resp)) - def is_always_on_top(self) -> bool: - return self.always_on_top - @always_on_top.setter def always_on_top(self, value): + self.set_always_on_top(value) + + def set_always_on_top(self, value): if value in ('on', 'On', True, 1): - self.set('AlwaysOnTop', 'On') + return self.set('AlwaysOnTop', 'On') or None elif value in ('off', 'Off', False, 0): - self.set('AlwaysOnTop', 'Off') + return self.set('AlwaysOnTop', 'Off') or None elif value in ('toggle', 'Toggle', -1): - self.set('AlwaysOnTop', 'Toggle') + return self.set('AlwaysOnTop', 'Toggle') or None else: raise ValueError( f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') - def set_always_on_top(self, value): - self.always_on_top = value - def disable(self): """ Distable the window @@ -454,7 +455,7 @@ def _base_method_(self, command, seconds_to_wait="", blocking=False): ) return script - def _base_method(self, command, seconds_to_wait="", blocking=False): + def _base_method(self, command, seconds_to_wait="", blocking=True): script = self._base_method_(command, seconds_to_wait=seconds_to_wait) return self.engine.run_script(script, blocking=blocking) @@ -685,7 +686,7 @@ def _win_set(self, subcommand, *args, blocking=True): 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) + return self.run_script(script, blocking=blocking) or None @property def active_window(self): @@ -814,27 +815,7 @@ def find_window_by_class(self, *args, **kwargs): return next(self.find_windows_by_class(*args, **kwargs)) -class AsyncWindow(Window, metaclass=AsyncifyMeta): - # these methods are converted to async compatible versions automatically - _asyncifiable = ['disable', - 'enable', - 'redraw', - 'to_bottom', - 'to_top', - 'activate', - 'activate_bottom', - 'close', - 'hide', - 'kill', - 'maximize', - 'minimize', - 'restore', - 'show', - 'wait', - 'wait_active', - 'wait_not_active', - 'wait_close', - ] +class AsyncWindow(Window): @classmethod async def from_mouse_position(cls, engine: ScriptEngine, **kwargs): @@ -850,76 +831,41 @@ async def from_pid(cls, engine: ScriptEngine, pid, **kwargs): ahk_id = await engine.a_run_script(script) return cls(engine=engine, ahk_id=ahk_id, **kwargs) - def __getattr__(self, item): - if item in self._get_subcommands: - raise AttributeError(f"Unaccessable Attribute. Use get({item}) instead") - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'") - - - async def get(self, subcommand): - script = self._get(subcommand) - return await self.engine.a_run_script(script) - - async def set(self, subcommand, value): - script = self._set(subcommand, value) - await self.engine.a_run_script(script) - - async def get_pos(self): - script = self._get_pos() + # def __getattr__(self, item): + # if item in self._get_subcommands: + # raise AttributeError(f"Unaccessable Attribute. Use get({item}) instead") + # raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'") + # + async def get_pos(self, info=None): + script = self._get_pos(info) resp = await self.engine.a_run_script(script) try: value = ast.literal_eval(resp) return value except SyntaxError: raise WindowNotFoundError('No window found') - @staticmethod - async def _loop(): - return asyncio.get_event_loop() - @property - async def rect(self): - warnings.warn("rect property blocks event loop. Use get_rect() instead", stacklevel=2) - return await self.get_pos() - - @rect.setter + @Window.rect.setter def rect(self, new_position): warnings.warn("rect setter only schedules coroutine. window may not change immediately. Use move() instead", stacklevel=2) x, y, width, height = new_position coro = self.move(x=x, y=y, width=width, height=height) asyncio.create_task(coro) - @property - async def position(self): - warnings.warn("position property blocks event loop. Use get_rect() instead", stacklevel=2) - x, y, _, _ = await self.get_pos() - return x, y - - @position.setter + @Window.position.setter def position(self, new_position): - warnings.warn("position setter only schedules coroutine. window may not change immediately. use move() instead", stacklevel=2) + warnings.warn("position setter only schedules coroutine. window may not change immediately. use set_position() instead", stacklevel=2) x, y = new_position coro = self.move(x, y) asyncio.create_task(coro) - @property - async def width(self): - warnings.warn("width property blocks event loop. Use get_rect() instead", stacklevel=2) - _, _, width, _ = await self.get_pos() - return width - - @width.setter + @Window.width.setter def width(self, new_width): warnings.warn("width setter only schedules coroutine. window may not change immediately. use move() instead", stacklevel=2) coro = self.move(width=new_width) asyncio.create_task(coro) - @property - async def height(self): - warnings.warn("height property blocks event loop. Use get_rect() instead", stacklevel=2) - _, _, _, height = await self.get_pos() - return height - - @height.setter + @Window.height.setter def height(self, new_height): warnings.warn("height setter only schedules coroutine. window may not change immediately. use move() instead", stacklevel=2) coro = self.move(height=new_height) @@ -930,18 +876,6 @@ async def _base_property(self, command): resp = await self.engine.a_run_script(script) return bool(ast.literal_eval(resp)) - @property - async def active(self): - warnings.warn("active property blocks event loop. Use is_active() instead", stacklevel=2) - return await self._base_property(command="WinActive") - @property - async def exist(self): - warnings.warn("exist property blocks event loop. Use exists() instead", stacklevel=2) - return await self._base_property(command="WinExist") - - async def exists(self): - return await self._base_property('WinExist') - async def _base_get_method(self, command): script = self._base_get_method_(command) result = await self.engine.a_run_script(script, decode=False) @@ -949,50 +883,12 @@ async def _base_get_method(self, command): return result.decode(encoding=self.encoding) return result - @property - async def title(self): - warnings.warn("title property blocks event loop. Use get_title() instead", stacklevel=2) - return await self._base_get_method("WinGetTitle") - - async def get_title(self): - return await self._base_get_method("WinGetTitle") - - async def set_title(self, value): - script = self._set_title(value) - await self.engine.a_run_script(script) - - @title.setter + @Window.title.setter def title(self, new_title): warnings.warn("title setter only schedules coroutine. window may not change immediately. use set_title() instead", stacklevel=2) coro = self.set_title(new_title) asyncio.create_task(coro) - @property - async def class_name(self): - warnings.warn("class_name property blocks event loop. use get_class_name() instead.") - return await self._base_get_method("WinGetClass") - - async def get_class_name(self): - return await self._base_get_method("WinGetClass") - - @property - async def text(self): - warnings.warn("text property blocks event loop. use get_text() instead.") - return await self._base_get_method("WinGetText") - - async def get_text(self): - return await self._base_get_method("WinGetText") - - @property - async def minimized(self): - warnings.warn('property blocks event loop. use is_minimized() instead') - return await self.get("MinMax") == self.MINIMIZED - - @property - async def maximized(self): - warnings.warn('property blocks event loop. use is_maximized() instead') - return await self.get("MinMax") == self.MAXIMIZED - async def is_minimized(self): return await self.get("MinMax") == self.MINIMIZED @@ -1040,19 +936,12 @@ async def set_transparency(self, value): raise ValueError( f'"{value}" not a valid option. Please use [0, 255] integer') - @property - async def always_on_top(self) -> bool: - warnings.warn("always_on_top property blocks event loop. use is_always_on_top() instead") - script = self._always_on_top() - resp = await self.engine.a_run_script(script) - return bool(ast.literal_eval(resp)) - async def is_always_on_top(self): script = self._always_on_top() - resp = self.engine.run_script(script) + resp = await self.engine.a_run_script(script) return bool(ast.literal_eval(resp)) - @always_on_top.setter + @Window.always_on_top.setter def always_on_top(self, value): warnings.warn(f"always_on_top setter only schedules coroutine. changes may not happen immediately. use set_always_on_top({repr(value)}) instead") if value in ('on', 'On', True, 1): @@ -1066,42 +955,14 @@ def always_on_top(self, value): f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') asyncio.create_task(coro) - async def set_always_on_top(self, value): - if value in ('on', 'On', True, 1): - await self.set('AlwaysOnTop', 'On') - elif value in ('off', 'Off', False, 0): - await self.set('AlwaysOnTop', 'Off') - elif value in ('toggle', 'Toggle', -1): - await self.set('AlwaysOnTop', 'Toggle') - else: - raise ValueError( - f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') - - async def move(self, x='', y='', width=None, height=None): - script = self._move(x=x, y=y, width=width, height=height) - return await self.engine.a_run_script(script) - async def send(self, keys, delay=10, raw=False, blocking=True, escape=False, press_duration=-1): - script = self._send(keys, delay=delay, raw=raw, blocking=blocking, escape=escape, press_duration=press_duration) - return await self.engine.a_run_script(script, blocking=blocking) - - async def _base_method(self, command, seconds_to_wait="", blocking=True): - script = self._base_method_(command, seconds_to_wait=seconds_to_wait) - return await self.engine.a_run_script(script, blocking=blocking) - - -class AsyncWindowMixin(WindowMixin): +class AsyncWindowMixin(AsyncScriptEngine, WindowMixin): async def win_get(self, *args, **kwargs): encoding = kwargs.pop('encoding', self.window_encoding) script = self._win_get(*args, **kwargs) ahk_id = await self.a_run_script(script) return AsyncWindow(engine=self, ahk_id=ahk_id, encoding=encoding) - async def win_set(self, subcommand, *args, blocking=True): - script = self.render_template( - 'window/set.ahk', subcommand=subcommand, *args, blocking=blocking) - await self.a_run_script(script, blocking=blocking) - @property def active_window(self): warnings.warn("active_window property blocks event loop. use get_active_window() instead") @@ -1223,4 +1084,3 @@ async def find_window_by_class(self, *args, **kwargs): """ async for window in self.find_windows_by_class(*args, **kwargs): return window - diff --git a/tests/unittests/test_keyboard.py b/tests/unittests/test_keyboard.py index ec749c69..77a48a14 100644 --- a/tests/unittests/test_keyboard.py +++ b/tests/unittests/test_keyboard.py @@ -6,14 +6,14 @@ from itertools import product from unittest import TestCase -from ahk import AHK -from ahk.keys import ALT, CTRL, KEYS - 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.keys import ALT, CTRL, KEYS + class TestKeyboard(TestCase): def setUp(self): @@ -106,8 +106,9 @@ def tearDown(self): self.thread.join(timeout=3) if self.ahk.key_state("a"): self.ahk.key_up("a") - if self.ahk.key_down("Control"): + if self.ahk.key_state("Control"): self.ahk.key_up("Control") + self.ahk.set_capslock_state('off') notepad = self.ahk.find_window(title=b"Untitled - Notepad") if notepad: diff --git a/tests/unittests/test_keyboard_async.py b/tests/unittests/test_keyboard_async.py index 4ede2229..ab48ff4e 100644 --- a/tests/unittests/test_keyboard_async.py +++ b/tests/unittests/test_keyboard_async.py @@ -5,7 +5,7 @@ import time import asyncio from itertools import product -from unittest import TestCase +from unittest import TestCase, IsolatedAsyncioTestCase project_root = os.path.abspath( @@ -14,7 +14,7 @@ sys.path.insert(0, project_root) from ahk import AsyncAHK, AHK from ahk.keys import ALT, CTRL, KEYS - +a class TestKeyboardAsync(TestCase): def setUp(self): @@ -33,62 +33,50 @@ def tearDown(self): time.sleep(0.2) asyncio.run(asyncio.sleep(0.2)) - async def a_window_send(self): + async def test_window_send(self): notepad = await self.ahk.find_window(title=b"Untitled - Notepad") await notepad.send("hello world") await asyncio.sleep(1) self.assertIn(b'hello world', await notepad.get_text()) - def test_window_send(self): - asyncio.run(self.a_window_send()) - - - async def a_send(self): + async def test_send(self): notepad = await self.ahk.find_window(title=b"Untitled - Notepad") await notepad.activate() await self.ahk.send('hello world') self.assertIn(b'hello world', await notepad.get_text()) - def test_send(self): - asyncio.run(self.a_send()) - async def a_send_input(self): + async def test_send_input(self): notepad = await self.ahk.find_window(title=b"Untitled - Notepad") await self.ahk.send_input("Hello World") await asyncio.sleep(0.5) assert b"Hello World" in await notepad.get_text() - def test_send_input(self): - asyncio.run(self.a_send_input()) - - async def a_type(self): + async def test_type(self): notepad = await self.ahk.find_window(title=b"Untitled - Notepad") await notepad.activate() await self.ahk.type("Hello, World!") assert b"Hello, World!" in await notepad.get_text() - def test_type(self): - asyncio.run(self.a_type()) -# def a_down(): - time.sleep(0.5) + #time.sleep(0.5) ahk = AHK() ahk.key_down("a") def release_a(): - time.sleep(0.5) + #time.sleep(0.5) ahk = AHK() ahk.key_up("a") def press_a(): - time.sleep(0.5) + #time.sleep(0.5) ahk = AHK() ahk.key_press("a") # -class TestKeysAsync(TestCase): +class TestKeysAsync(IsolatedAsyncioTestCase): def setUp(self): self.ahk = AsyncAHK() self._normal_ahk = AHK() @@ -103,9 +91,9 @@ def tearDown(self): if self._normal_ahk.key_state("Control"): self._normal_ahk.key_up("Control") - notepad = self.ahk.find_window(title=b"Untitled - Notepad") + notepad = self._normal_ahk.find_window(title=b"Untitled - Notepad") if notepad: - notepad.close() + notepad.kill() if self.hotkey and self.hotkey.running: self.hotkey.stop() @@ -134,24 +122,20 @@ def test_key_wait_released(self): assert end - start < 2 async def a_key_wait_timeout(self): - await self.ahk.key_wait('f', timeout=1) + await self.ahk.key_wait('f', timeout=0.1) def test_key_wait_timeout(self): self.assertRaises(TimeoutError, asyncio.run, self.a_key_wait_timeout()) - async def a_key_state_when_not_pressed(self): - return await self.ahk.key_state("a") + async def test_key_state_when_not_pressed(self): + self.assertFalse(await self.ahk.key_state("a")) - def test_key_state_when_not_pressed(self): - self.assertFalse(asyncio.run(self.a_key_state_when_not_pressed())) -# - async def a_key_state_pressed(self): + + async def test_key_state_pressed(self): await self.ahk.key_down("Control") self.assertTrue(await self.ahk.key_state("Control")) - def test_key_state_pressed(self): - asyncio.run(self.a_key_state_pressed()) # def test_hotkey(self): # self.hotkey = self.ahk.hotkey(hotkey="a", script="Run Notepad") diff --git a/tests/unittests/test_mouse.py b/tests/unittests/test_mouse.py new file mode 100644 index 00000000..81ecbca9 --- /dev/null +++ b/tests/unittests/test_mouse.py @@ -0,0 +1,33 @@ +import os +import subprocess +import sys +import threading +import time +import asyncio +from itertools import product +from unittest import TestCase, IsolatedAsyncioTestCase +project_root = os.path.abspath( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") +) +sys.path.insert(0, project_root) +from ahk import AsyncAHK, AHK + +class TestMouse(TestCase): + def setUp(self) -> None: + self.ahk = AHK() + + def test_mouse_move(self): + x, y = self.ahk.mouse_position + self.ahk.mouse_move(10, 10, relative=True) + assert self.ahk.mouse_position == (x+10, y+10) + +class TestMouseAsync(IsolatedAsyncioTestCase): + def setUp(self) -> None: + self.ahk = AsyncAHK() + + async def test_mouse_move(self): + x, y = await self.ahk.mouse_position + await self.ahk.mouse_move(10, 10, relative=True) + assert await self.ahk.mouse_position == (x+10, y+10) + + diff --git a/tests/unittests/test_win_get_async.py b/tests/unittests/test_win_get_async.py new file mode 100644 index 00000000..9e6231ab --- /dev/null +++ b/tests/unittests/test_win_get_async.py @@ -0,0 +1,43 @@ +import asyncio +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, AsyncAHK +from ahk.window import WindowNotFoundError +import pytest +import subprocess +from unittest import IsolatedAsyncioTestCase + +class TestWinGetAsync(IsolatedAsyncioTestCase): + def setUp(self): + self.ahk = AsyncAHK() + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = asyncio.run(self.ahk.win_get(title='Untitled - Notepad')) + self.assertIsNotNone(self.win) + + def tearDown(self): + self.p.terminate() + asyncio.run(asyncio.sleep(0.5)) + + + async def test_get_calculator(self): + assert await self.win.position + + async def a_win_get(self): + win = await self.ahk.win_get(title='Untitled - Notepad') + await win.position + + def test_win_close(self): + asyncio.run(self.win.close()) + self.assertRaises(WindowNotFoundError, asyncio.run, self.a_win_get()) + + async def test_find_window_func(self): + async def func(win): + return b'Untitled' in await win.title + assert self.win == await self.ahk.find_window(func=func) + + async def test_getattr_window_subcommand(self): + assert isinstance(await self.win.pid, str) diff --git a/tests/unittests/test_window_async.py b/tests/unittests/test_window_async.py index 2b7b16d7..1429a9ab 100644 --- a/tests/unittests/test_window_async.py +++ b/tests/unittests/test_window_async.py @@ -1,6 +1,6 @@ import subprocess import time -from unittest import TestCase +from unittest import IsolatedAsyncioTestCase import asyncio import os import sys @@ -12,7 +12,7 @@ from ahk.window import AsyncWindow -class TestWindowAsync(TestCase): +class TestWindowAsync(IsolatedAsyncioTestCase): win: AsyncWindow def setUp(self): self.ahk = AsyncAHK() @@ -24,10 +24,11 @@ def setUp(self): async def a_transparent(self): self.assertEqual(await self.win.get_transparency(), 255) - self.win.transparent = 220 + await self.win.set_transparency(220) self.assertEqual(await self.win.get_transparency(), 220) self.win.transparent = 255 + await asyncio.sleep(0.5) self.assertEqual(await self.win.transparent, 255) @@ -46,16 +47,13 @@ async def a_pinned(self): await asyncio.sleep(1) self.assertFalse(await self.win.always_on_top) - async def a_close(self): + async def test_close(self): await self.win.close() await asyncio.sleep(0.2) self.assertFalse(await self.win.exists()) self.assertFalse(await self.win.exist) - def test_close(self): - asyncio.run(self.a_close()) - - async def a_show_hide(self): + async def test_show_hide(self): await self.win.hide() await asyncio.sleep(0.5) self.assertFalse(await self.win.exist) @@ -64,18 +62,12 @@ async def a_show_hide(self): await asyncio.sleep(0.5) self.assertTrue(await self.win.exist) - def test_show_hide(self): - asyncio.run(self.a_show_hide()) - - async def a_kill(self): + async def test_kill(self): await self.win.kill() await asyncio.sleep(0.5) self.assertFalse(await self.win.exist) - def test_kill(self): - asyncio.run(self.a_kill()) - - async def a_max_min(self): + async def test_max_min(self): self.assertTrue(await self.win.non_max_non_min) self.assertFalse(await self.win.is_minmax()) @@ -93,10 +85,8 @@ async def a_max_min(self): await asyncio.sleep(0.5) self.assertTrue(await self.win.maximized) self.assertTrue(await self.win.is_maximized()) - def test_max_min(self): - asyncio.run(self.a_max_min()) # - async def a_names(self): + async def test_names(self): self.assertEqual(await self.win.class_name, b'Notepad') self.assertEqual(await self.win.get_class_name(), b'Notepad') @@ -106,10 +96,12 @@ async def a_names(self): self.assertEqual(await self.win.text, b'') self.assertEqual(await self.win.get_text(), b'') + async def test_title_setter(self): + starting_title = await self.win.title + + await self.win.set_title("new title") + assert await self.win.get_title() != starting_title - def test_names(self): - asyncio.run(self.a_names()) -# def tearDown(self): self.p.terminate() asyncio.run(asyncio.sleep(0.5)) From 6b8ff3ada06c4380e74892c859b58b1aa4ee22bb Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 08:34:23 -0800 Subject: [PATCH 094/588] remove typo --- tests/unittests/test_keyboard_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unittests/test_keyboard_async.py b/tests/unittests/test_keyboard_async.py index ab48ff4e..bbcf4ae9 100644 --- a/tests/unittests/test_keyboard_async.py +++ b/tests/unittests/test_keyboard_async.py @@ -14,7 +14,7 @@ sys.path.insert(0, project_root) from ahk import AsyncAHK, AHK from ahk.keys import ALT, CTRL, KEYS -a + class TestKeyboardAsync(TestCase): def setUp(self): From d26a6615a969e2c8e851d0ac306d1205f47db248 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 08:39:23 -0800 Subject: [PATCH 095/588] waiters for safety --- tests/unittests/test_keyboard_async.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unittests/test_keyboard_async.py b/tests/unittests/test_keyboard_async.py index bbcf4ae9..476d151c 100644 --- a/tests/unittests/test_keyboard_async.py +++ b/tests/unittests/test_keyboard_async.py @@ -59,19 +59,19 @@ async def test_type(self): def a_down(): - #time.sleep(0.5) + time.sleep(0.5) ahk = AHK() ahk.key_down("a") def release_a(): - #time.sleep(0.5) + time.sleep(0.5) ahk = AHK() ahk.key_up("a") def press_a(): - #time.sleep(0.5) + time.sleep(0.5) ahk = AHK() ahk.key_press("a") From 9345b14014794580f25f5e737522cef24947ca18 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 08:47:39 -0800 Subject: [PATCH 096/588] async screen --- ahk/screen.py | 206 ++++++++++++++++++--------- tests/unittests/test_screen_async.py | 47 ++++++ 2 files changed, 185 insertions(+), 68 deletions(-) create mode 100644 tests/unittests/test_screen_async.py diff --git a/ahk/screen.py b/ahk/screen.py index 758ca8f6..1e56331a 100644 --- a/ahk/screen.py +++ b/ahk/screen.py @@ -1,14 +1,67 @@ import ast -from ahk.script import ScriptEngine +from ahk.script import ScriptEngine, AsyncScriptEngine 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, - color_variation: int=None, coord_mode: str='Screen', - scale_height: int=None, scale_width: int=None, - transparent: str=None, icon: int=None) -> Union[Tuple[int, int], None]: + def _image_search( + self, + image_path: str, + upper_bound: Tuple[int, int] = (0, 0), + lower_bound: Tuple[int, int] = None, + color_variation: int = None, + coord_mode: str = 'Screen', + scale_height: int = None, + scale_width: int = None, + transparent: str = None, + icon: int = None, + ) -> str: + + if scale_height and not scale_width: + scale_width = -1 + elif scale_width and not scale_height: + scale_height = -1 + + options = [] + if icon: + options.append(f'Icon{icon}') + if color_variation: + options.append(color_variation) + if transparent: + 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%') + script = self.render_template( + 'screen/image_search.ahk', + x1=x1, + x2=x2, + y1=y1, + y2=y2, + coord_mode=coord_mode, + image_path=image_path, + options=options, + ) + return script + + def image_search( + self, + image_path: str, + upper_bound: Tuple[int, int] = (0, 0), + lower_bound: Tuple[int, int] = None, + color_variation: int = None, + coord_mode: str = 'Screen', + scale_height: int = None, + scale_width: int = None, + transparent: str = None, + icon: int = None, + ) -> Union[Tuple[int, int], None]: """ `AutoHotkey ImageSearch reference`_ @@ -24,7 +77,7 @@ def image_search(self, image_path: str, :param color_variation: Shades of variation (up or down) for the intensity of RGB for each pixel. Equivalent of ``*n`` option. Defaults to 0. - + :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 @@ -42,41 +95,37 @@ def image_search(self, image_path: str, 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 - - options = [] - if icon: - options.append(f'Icon{icon}') - if color_variation: - options.append(color_variation) - if transparent: - 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%') - script = self.render_template('screen/image_search.ahk', - x1=x1, x2=x2, y1=y1, y2=y2, - coord_mode=coord_mode, - image_path=image_path, - options=options) + script = self._image_search( + image_path=image_path, + upper_bound=upper_bound, + lower_bound=lower_bound, + color_variation=color_variation, + coord_mode=coord_mode, + scale_height=scale_height, + scale_width=scale_width, + transparent=transparent, + icon=icon, + ) 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]: + def _pixel_get_color( + self, x: int, y: int, coord_mode: str = 'Screen', alt: bool = False, slow: bool = False, rgb=True + ): + 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) + return script + + def pixel_get_color(self, *args, **kwargs): """ `AutoHotkey PixelGetColor reference`_ @@ -89,26 +138,46 @@ def pixel_get_color(self, x: int, y: int, coord_mode: str='Screen', :param slow: :param rgb: returns :return: the color as an RGB hexidecimal string; - :rtype: str """ - + script = self._pixel_get_color(*args, **kwargs) + return self.run_script(script) + + + 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, + ) -> str: options = [] - if slow: - options.append('Slow') - elif alt: - options.append('Alt') + if fast: + options.append('Fast') 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 + x1, y1 = upper_bound + if lower_bound: + x2, y2 = lower_bound + else: + x2, y2 = ('%A_ScreenWidth%', '%A_ScreenHeight%') - 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]: + 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, + ) + return script + + def pixel_search(self, *args, **kwargs): """ `AutoHotkey PixelSearch reference`_ @@ -123,25 +192,26 @@ def pixel_search(self, color: Union[str, int], variation: int=0, :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) + script = self._pixel_search(*args, **kwargs) resp = self.run_script(script) try: return ast.literal_eval(resp) except SyntaxError: return None + +class AsyncScreenMixin(AsyncScriptEngine, ScreenMixin): + async def pixel_search(self, *args, **kwargs): + script = self._pixel_search(*args, **kwargs) + resp = await self.a_run_script(script) + try: + return ast.literal_eval(resp) + except SyntaxError: + return None + + async def image_search(self, *args, **kwargs): + script = self._image_search(*args, **kwargs) + resp = await self.a_run_script(script) + try: + return ast.literal_eval(resp) + except SyntaxError: + return None diff --git a/tests/unittests/test_screen_async.py b/tests/unittests/test_screen_async.py new file mode 100644 index 00000000..267c75fa --- /dev/null +++ b/tests/unittests/test_screen_async.py @@ -0,0 +1,47 @@ +import asyncio +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, AsyncAHK +from unittest import TestCase, IsolatedAsyncioTestCase +from PIL import Image +from itertools import product +import time + + +class TestScreen(IsolatedAsyncioTestCase): + def setUp(self): + """ + Record all open windows + :return: + """ + self.ahk = AsyncAHK() + self.before_windows = asyncio.run(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) + + async def asyncTearDown(self): + async for win in await self.ahk.windows(): + if win not in self.before_windows: + await win.close() + break + + def test_pixel_search(self): + result = await self.ahk.pixel_search(0xFF0000) + self.assertIsNotNone(result) + + def test_image_search(self): + self.im.save('testimage.png') + position = await self.ahk.image_search('testimage.png') + self.assertIsNotNone(position) + + 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) From 28baaed238316a92615906d9035745130a5a1f37 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 08:50:31 -0800 Subject: [PATCH 097/588] fix async screen test --- tests/unittests/test_screen_async.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unittests/test_screen_async.py b/tests/unittests/test_screen_async.py index 267c75fa..178b3398 100644 --- a/tests/unittests/test_screen_async.py +++ b/tests/unittests/test_screen_async.py @@ -31,16 +31,16 @@ async def asyncTearDown(self): await win.close() break - def test_pixel_search(self): + async def test_pixel_search(self): result = await self.ahk.pixel_search(0xFF0000) self.assertIsNotNone(result) - def test_image_search(self): + async def test_image_search(self): self.im.save('testimage.png') position = await self.ahk.image_search('testimage.png') self.assertIsNotNone(position) - def test_pixel_get_color(self): + 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) From 05a732236414edf049bf19e9992a2a21718b68be Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 08:51:30 -0800 Subject: [PATCH 098/588] add mixins --- ahk/autohotkey.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ahk/autohotkey.py b/ahk/autohotkey.py index af715a13..c586f359 100644 --- a/ahk/autohotkey.py +++ b/ahk/autohotkey.py @@ -3,7 +3,7 @@ from ahk.keyboard import KeyboardMixin, AsyncKeyboardMixin from ahk.mouse import MouseMixin, AsyncMouseMixin from ahk.registery import RegisteryMixin#, AsyncRegisteryMixin -from ahk.screen import ScreenMixin#, AsyncScreenMixin +from ahk.screen import ScreenMixin, AsyncScreenMixin from ahk.sound import SoundMixin#, AsyncSoundMixin from ahk.window import WindowMixin, AsyncWindowMixin from ahk.gui import GUIMixin#, AsyncGUIMixin @@ -26,11 +26,10 @@ class AHK(WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin, Regis # class AsyncAHK( - #AsyncWindowMixin, AsyncMouseMixin, AsyncKeyboardMixin, - AsyncWindowMixin - #AsyncScreenMixin, + AsyncWindowMixin, + AsyncScreenMixin, #AsyncSoundMixin, #AsyncRegisteryMixin, #AsyncGUIMixin From 570b822b59584cb8d162c44ce2a313b8f74550cb Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 11:57:37 -0800 Subject: [PATCH 099/588] async registry --- ahk/{registery.py => registry.py} | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) rename ahk/{registery.py => registry.py} (84%) diff --git a/ahk/registery.py b/ahk/registry.py similarity index 84% rename from ahk/registery.py rename to ahk/registry.py index be4f82de..eae3fff6 100644 --- a/ahk/registery.py +++ b/ahk/registry.py @@ -1,8 +1,8 @@ -from ahk.script import ScriptEngine +from ahk.script import ScriptEngine, AsyncScriptEngine import os -class RegisteryMixin(ScriptEngine): +class RegistryMixin(ScriptEngine): def _render_template(self, template_name, *args, **kwargs): return self.render_template(os.path.join("registery", template_name)) @@ -25,7 +25,7 @@ def reg_read(self, key_name: str, value_name="") -> str: Returns: str -- Registery value """ - self._run_template("reg_read.ahk", key_name=key_name, value_name=value_name) + return self._run_template("reg_read.ahk", key_name=key_name, value_name=value_name) def reg_delete(self, key_name: str, value_name="") -> None: """Delete registery @@ -39,7 +39,7 @@ def reg_delete(self, key_name: str, value_name="") -> None: Keyword Arguments: value_name {str} -- TODO (default: {""}) """ - self._run_template("reg_delete.ahk", key_name=key_name, value_name=value_name) + return self._render_template("reg_delete.ahk", key_name=key_name, value_name=value_name) def reg_write(self, value_type: str, key_name: str, value_name="") -> None: """Write registery @@ -54,7 +54,7 @@ def reg_write(self, value_type: str, key_name: str, value_name="") -> None: Keyword Arguments: value_name {str} -- TODO (default: {""}) """ - self._run_template("reg_write.ahk", value_type=value_type, key_name=key_name, value_name=value_name) + return self._render_template("reg_write.ahk", value_type=value_type, key_name=key_name, value_name=value_name) def reg_set_view(self, reg_view: int) -> None: """Set registery view @@ -69,7 +69,7 @@ def reg_set_view(self, reg_view: int) -> None: if reg_view not in [32, 64, "32", "64"]: raise ValueError("No valid bit, please use 32 or 64") - self._run_template("reg_set_view.ahk", reg_view=reg_view) + return self._run_template("reg_set_view.ahk", reg_view=reg_view) or None def reg_loop(self, reg: str, key_name: str, mode=""): """Loop registery @@ -86,7 +86,7 @@ def reg_loop(self, reg: str, key_name: str, mode=""): """ raise NotImplementedError - self._run_template("reg_loop.ahk", reg=reg, key_name=key_name, mode=mode) + return self._run_template("reg_loop.ahk", reg=reg, key_name=key_name, mode=mode) or None def read(self, *args, **kwargs): import warnings @@ -127,3 +127,6 @@ def delete(self, *args, **kwargs): stacklevel=2, ) return self.reg_delete(*args, **kwargs) + +class AsyncRegistryMixin(AsyncScriptEngine, RegistryMixin): + pass From 8062822cda1cdc9204eef5d9b828df233f66ed94 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 11:59:22 -0800 Subject: [PATCH 100/588] async sound --- ahk/sound.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ahk/sound.py b/ahk/sound.py index 302cfdc9..5add27ed 100644 --- a/ahk/sound.py +++ b/ahk/sound.py @@ -1,4 +1,4 @@ -from ahk.script import ScriptEngine +from ahk.script import ScriptEngine, AsyncScriptEngine class SoundMixin(ScriptEngine): @@ -12,7 +12,7 @@ def sound_beep(self, frequency=523, duration=150): """ script = self.render_template('sound/beep.ahk', frequency=frequency, duration=duration) - self.run_script(script) + return self.run_script(script) or None def sound_play(self, filename, blocking=True): """ @@ -25,7 +25,7 @@ def sound_play(self, filename, blocking=True): """ script = self.render_template('sound/play.ahk', filename=filename, wait=1, blocking=blocking) - self.run_script(script, blocking=blocking) + return self.run_script(script, blocking=blocking) or None def sound_get(self, device_number=1, component_type='MASTER', control_type='VOLUME'): """ @@ -37,8 +37,7 @@ def sound_get(self, device_number=1, component_type='MASTER', control_type='VOLU :param control_type: :return: """ - - script = self.render_template('sound/sound_get.ahk') + script = self.render_template('sound/sound_get.ahk', device_number=device_number, component_type=component_type, control_type=control_type) return self.run_script(script) def get_volume(self, device_number=1): @@ -69,7 +68,7 @@ def sound_set(self, value, device_number=1, component_type='MASTER', control_typ device_number=device_number, component_type=component_type, control_type=control_type) - self.run_script(script) + return self.run_script(script) or None def set_volume(self, value, device_number=1): """ @@ -81,4 +80,8 @@ def set_volume(self, value, device_number=1): """ script = self.render_template('sound/set_volume.ahk', value=value, device_number=device_number) - self.run_script(script) + return self.run_script(script) or None + + +class AsyncSoundMixin(AsyncScriptEngine, SoundMixin): + pass From 1a0dcb8bbc78b3e356b88ef70e6289a0c058a9d9 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 11:59:42 -0800 Subject: [PATCH 101/588] async gui --- ahk/gui.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ahk/gui.py b/ahk/gui.py index d8864607..be60eb71 100644 --- a/ahk/gui.py +++ b/ahk/gui.py @@ -1,4 +1,4 @@ -from ahk.script import ScriptEngine +from ahk.script import ScriptEngine, AsyncScriptEngine class GUIMixin(ScriptEngine): @@ -28,13 +28,12 @@ def show_tooltip(self, text: str, second=1.0, x="", y="", id="", blocking=True): :type id: str, optional :raises ValueError: ID must be between [1, 20] """ - if id and not (1 <= int(id) <= 20): raise ValueError("ID value must be between [1, 20]") encoded_text = "% " + "".join([f"Chr({hex(ord(char))})" for char in text]) script = self.render_template("gui/tooltip.ahk", text=encoded_text, second=second, x=x, y=y, id=id) - self.run_script(script, blocking=blocking) + return self.run_script(script, blocking=blocking) or None def _show_traytip( self, title: str, text: str, second=1.0, type_id=1, slient=False, large_icon=False, blocking=True @@ -63,7 +62,7 @@ def _show_traytip( script = self.render_template( "gui/traytip.ahk", title=encoded_title, text=encoded_text, second=second, option=option ) - self.run_script(script, blocking=blocking) + return self.run_script(script, blocking=blocking) or None def show_info_traytip(self, title: str, text: str, second=1.0, slient=False, large_icon=False, blocking=True): """Show TrayTip with info icon (Windows 10 toast notification) @@ -124,3 +123,7 @@ def show_error_traytip(self, title: str, text: str, second=1.0, slient=False, la :type blocked: bool, optional """ return self._show_traytip(title, text, second, self.TRAYTIP_ERROR, slient, large_icon, blocking) + + +class AsyncGUIMixin(AsyncScriptEngine, GUIMixin): + pass From 8d6649213b5a191eee1e3d8e3a653f65c67354a4 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 12:00:19 -0800 Subject: [PATCH 102/588] add async mixins --- ahk/autohotkey.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ahk/autohotkey.py b/ahk/autohotkey.py index c586f359..2a95db3e 100644 --- a/ahk/autohotkey.py +++ b/ahk/autohotkey.py @@ -2,14 +2,14 @@ from ahk.keyboard import KeyboardMixin, AsyncKeyboardMixin from ahk.mouse import MouseMixin, AsyncMouseMixin -from ahk.registery import RegisteryMixin#, AsyncRegisteryMixin +from ahk.registry import RegistryMixin, AsyncRegistryMixin from ahk.screen import ScreenMixin, AsyncScreenMixin -from ahk.sound import SoundMixin#, AsyncSoundMixin +from ahk.sound import SoundMixin, AsyncSoundMixin from ahk.window import WindowMixin, AsyncWindowMixin -from ahk.gui import GUIMixin#, AsyncGUIMixin +from ahk.gui import GUIMixin, AsyncGUIMixin -class AHK(WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin, RegisteryMixin, GUIMixin): +class AHK(WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin, RegistryMixin, GUIMixin): """ Inherits its methods from the following classes: @@ -30,9 +30,9 @@ class AsyncAHK( AsyncKeyboardMixin, AsyncWindowMixin, AsyncScreenMixin, - #AsyncSoundMixin, - #AsyncRegisteryMixin, - #AsyncGUIMixin + AsyncSoundMixin, + AsyncRegistryMixin, + AsyncGUIMixin ): ... From 42d6cf023a89b5c30ca8112a1da8c6873db2b66b Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 12:11:04 -0800 Subject: [PATCH 103/588] async docs to readme --- docs/README.md | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index ffeea2d1..edb62819 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,6 +16,8 @@ pip install ahk ``` Requires Python 3.6+ +Async API requires Python 3.8+ + See also [Non-Python dependencies](#deps) @@ -270,13 +272,49 @@ print(result.stdout) # b'Hello Data!' ``` -## Experimental features +## Preview features -Experimental features are things that are minimally functional, (even more) likely to have breaking changes, even -for minor releases. +Preview features are experimental features that are may not be fully functional. +These features are (even more) likely to have breaking changes without warning. Github issues are provided for convenience to collect feedback on these features. +## Async API + +[GH-104] + +An async API is provided so functions can be called using `async`/`await`. + +For the most part, the async API is identical to that of the normal API, with a few exceptions. + +See full API documentation for more information. + + +```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()) +``` + +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. + +```python +ahk = AsyncAHK() +async def main(): + pos = ahk.mouse_position # BAD! Does not work! + pos = await ahk.mouse_position # OK. Works, but looks kind of weird + pos = await ahk.get_mouse_position() # GOOD! +``` + + ### Hotkeys From 59e17bcc179514575a54048f2b6aebd2c9d6582b Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 12:31:10 -0800 Subject: [PATCH 104/588] document async gotachs --- docs/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/README.md b/docs/README.md index edb62819..aebc66cb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -306,12 +306,26 @@ asyncio.run(main()) 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. +Property setters have different (probably undesired) behavior +in the async API. Instead, you should use a comparable method. +If you _do_ use the setters, the invocation is created using `asyncio.create_task()`, which means +that the task won't run until control is yielded back to the event loop. For now, this will also raise a warning to the same. + +Lastly, while it's possible to pass `blocking=True` in the async API, this sometimes will cause problems. + ```python ahk = AsyncAHK() async def main(): pos = ahk.mouse_position # BAD! Does not work! pos = await ahk.mouse_position # OK. Works, but looks kind of weird pos = await ahk.get_mouse_position() # GOOD! + + # You probably don't want to do this + ahk.mouse_position = (100, 100) # won't do anything right away. Raises warning + print(await ahk.get_mouse_position()) # probably won't be 100,100 + #Instead, do this: + await ahk.mouse_move(100, 100, speed=0) + assert await ahk.get_mouse_position() == (100, 100) ``` From d00190a7ce0c7db94e55e3ded3560feaf95e41b6 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Wed, 6 Jan 2021 12:33:17 -0800 Subject: [PATCH 105/588] fix async screen test --- tests/unittests/test_screen_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unittests/test_screen_async.py b/tests/unittests/test_screen_async.py index 178b3398..a96f52b8 100644 --- a/tests/unittests/test_screen_async.py +++ b/tests/unittests/test_screen_async.py @@ -26,7 +26,7 @@ def setUp(self): time.sleep(2) async def asyncTearDown(self): - async for win in await self.ahk.windows(): + for win in await self.ahk.windows(): if win not in self.before_windows: await win.close() break From 990f5e2465e72bac14d4e0df97a368476578a789 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 07:19:04 -0800 Subject: [PATCH 106/588] remove unneeded asyncify and metaclass --- ahk/utils.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/ahk/utils.py b/ahk/utils.py index dc4f4fed..0baa8452 100644 --- a/ahk/utils.py +++ b/ahk/utils.py @@ -49,28 +49,6 @@ def escape_sequence_replace(s): """ return s.translate(_TRANSLATION_TABLE) -def asyncify(sync_method): - cls = sync_method.__class__ - @functools.wraps(sync_method) - async def async_method(self, *args, **kwargs): - self_sync_method = getattr(super(self.__class__, self), sync_method.__name__) - coro = self_sync_method(*args, **kwargs) - return await coro - return async_method - -class AsyncifyMeta(type): - def __new__(typ, *args, **kwargs): - cls = super().__new__(typ, *args, **kwargs) - asyncifyable = getattr(cls, '_asyncifyable', None) - if not asyncifyable: - return cls - - for name in asyncifyable: - obj = getattr(cls, name) - if not callable(obj) or isinstance(obj, type) or isinstance(obj, property): - raise ValueError(f'{repr(obj)} object is not asyncifyable)') - setattr(cls, f'{name}', asyncify(obj)) - return cls async def async_filter(async_pred, iterable): for item in iterable: From 214cc55ba2f9c6ec20ba06c0b4966b897e27d8f8 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 07:21:47 -0800 Subject: [PATCH 107/588] remove usage of AsyncifyMeta --- ahk/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/window.py b/ahk/window.py index fc9c71a6..3e3e6181 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -5,7 +5,7 @@ import warnings from types import CoroutineType from ahk.script import ScriptEngine, AsyncScriptEngine -from ahk.utils import escape_sequence_replace, make_logger, AsyncifyMeta, async_filter +from ahk.utils import escape_sequence_replace, make_logger, async_filter logger = make_logger(__name__) From 4aa5eebae9f86f93c47344d1e750b2d2ae258875 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 07:45:04 -0800 Subject: [PATCH 108/588] update readme --- docs/README.md | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/README.md b/docs/README.md index aebc66cb..e570b8ba 100644 --- a/docs/README.md +++ b/docs/README.md @@ -281,14 +281,8 @@ Github issues are provided for convenience to collect feedback on these features ## Async API -[GH-104] - -An async API is provided so functions can be called using `async`/`await`. - -For the most part, the async API is identical to that of the normal API, with a few exceptions. - -See full API documentation for more information. - +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 @@ -302,14 +296,15 @@ async def main(): asyncio.run(main()) ``` +For the most part, the async API is identical to that of the normal API, with a few exceptions: 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. Property setters have different (probably undesired) behavior -in the async API. Instead, you should use a comparable method. -If you _do_ use the setters, the invocation is created using `asyncio.create_task()`, which means -that the task won't run until control is yielded back to the event loop. For now, this will also raise a warning to the same. +in the async API. Instead, you should use a comparable method. If you _do_ use the property setters, the invocation is created using `asyncio.create_task()`, which means +that the task won't run until control is yielded back to the event loop. For now, this will also raise a warning to the same. + Lastly, while it's possible to pass `blocking=True` in the async API, this sometimes will cause problems. @@ -320,16 +315,16 @@ async def main(): pos = await ahk.mouse_position # OK. Works, but looks kind of weird pos = await ahk.get_mouse_position() # GOOD! - # You probably don't want to do this + # BAD: You probably don't want to do this ahk.mouse_position = (100, 100) # won't do anything right away. Raises warning print(await ahk.get_mouse_position()) # probably won't be 100,100 - #Instead, do this: + + # GOOD: Instead, do this: await ahk.mouse_move(100, 100, speed=0) assert await ahk.get_mouse_position() == (100, 100) ``` - ### Hotkeys [GH-9] @@ -337,7 +332,8 @@ async def main(): 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. - +Right now, only AHK code is supported as callbacks for hotkeys. +Support for Python callbacks via the Async API is planned. ```python from ahk import AHK, Hotkey From 6c96b7504ed7f14c5291357501c530246ee2938d Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 07:46:36 -0800 Subject: [PATCH 109/588] update readme --- docs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index e570b8ba..b55b6436 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,9 +16,9 @@ pip install ahk ``` Requires Python 3.6+ -Async API requires Python 3.8+ +[Async API](#async-api) requires Python 3.8+ -See also [Non-Python dependencies](#deps) +See also [Non-Python dependencies](#non-python-dependencies) # Usage From 9cee211e7306a84678fc3844e8018080ccaea19e Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 08:13:36 -0800 Subject: [PATCH 110/588] daemon --- ahk/templates/_daemon.ahk | 92 ++++++++++++++++++++++++++++++ ahk/templates/asynchotkey.ahk | 6 ++ ahk/templates/base.ahk | 2 +- ahk/templates/daemon.ahk | 74 ++++++++++++++++++++++++ ahk/templates/daemon/key_state.ahk | 1 + 5 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 ahk/templates/_daemon.ahk create mode 100644 ahk/templates/asynchotkey.ahk create mode 100644 ahk/templates/daemon.ahk create mode 100644 ahk/templates/daemon/key_state.ahk diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk new file mode 100644 index 00000000..62ae6b90 --- /dev/null +++ b/ahk/templates/_daemon.ahk @@ -0,0 +1,92 @@ +#NoEnv + + + +MouseGetPos(ByRef command) { + MouseGetPos, xpos, ypos + s .= Format("({}, {})", xpos, ypos) + return s +} + +AHKKeyState(ByRef command) { + if (command.Length() = 3) { + if (GetKeyState(command[2], command[3])) { + return 1 + } else { + return 0 + } + } else{ + if (GetKeyState(command[2])) { + return 1 + } else { + return 0 + } + } +} + +MouseMove(ByRef command) { + if (command.Length() = 5) { + MouseMove, command[2], command[3], command[4], R + } else { + MouseMove, command[2], command[3], command[4] + } +} + +CoordMode(ByRef command) { + if (command.Length() = 2) { + CoordMode, command[2] + } else { + CoordMode, command[2], command[3] + } +} + + +Click(ByRef command) { + if command.Length() = 1 { + Click + } {% for i in range(2, 8) %} else if (command.Length() = {{ i }}) { + Click{% for index in range(2, i+1) %}, command[{{index}}]{% endfor %} + + }{% endfor %} + + return + +} + +KeyWait(ByRef command) { + if (command.Length() = 2) { + KeyWait, command[2] + } else { + KeyWait, command[2], command[3] + } + return %ErrorLevel% +} + +SetKeyDelay(ByRef command) { + SetKeyDelay, command[2] +} + +Join(sep, params*) { + for index,param in params + str .= param . sep + return SubStr(str, 1, -StrLen(sep)) +} + +Send(ByRef command) { + Send % Join(",", command*) +} + +SendRaw(ByRef command) { + SendRaw % Join("," command*) +} + +stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] + +Loop { + query := RTrim(stdin.ReadLine(), "`n") + commandArray := StrSplit(query, ",") + func := commandArray[1] + response := %func%(commandArray) + FileAppend, %response%`n, * +} + diff --git a/ahk/templates/asynchotkey.ahk b/ahk/templates/asynchotkey.ahk new file mode 100644 index 00000000..c066400b --- /dev/null +++ b/ahk/templates/asynchotkey.ahk @@ -0,0 +1,6 @@ +{% extends "base.ahk" %} +{% block body %} +{{ hotkey }}:: + FileAppend, `n, * + return +{% endblock body %} diff --git a/ahk/templates/base.ahk b/ahk/templates/base.ahk index 4fa0e994..20aa354c 100644 --- a/ahk/templates/base.ahk +++ b/ahk/templates/base.ahk @@ -10,5 +10,5 @@ {% endblock body %} {% block exit %} -ExitApp +{% if not _daemon %}ExitApp{% endif %} {% endblock exit %} diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk new file mode 100644 index 00000000..1dd2473d --- /dev/null +++ b/ahk/templates/daemon.ahk @@ -0,0 +1,74 @@ +#NoEnv + + + +MouseGetPos(ByRef command) { + MouseGetPos, xpos, ypos + s .= Format("({}, {})", xpos, ypos) + return s +} + +AHKKeyState(ByRef command) { + if (command.Length() = 3) { + if (GetKeyState(command[2], command[3])) { + return 1 + } else { + return 0 + } + } else{ + if (GetKeyState(command[2])) { + return 1 + } else { + return 0 + } + } +} + +MouseMove(ByRef command) { + if (command.Length() = 5) { + MouseMove, command[2], command[3], command[4], R + } else { + MouseMove, command[2], command[3], command[4] + } +} + +CoordMode(ByRef command) { + if (command.Length() = 2) { + CoordMode, command[2] + } else { + CoordMode, command[2], command[3] + } +} + + +Click(ByRef command) { + if command.Length() = 1 { + Click + } else if (command.Length() = 2) { + Click, command[2] + } else if (command.Length() = 3) { + Click, command[2], command[3] + } else if (command.Length() = 4) { + Click, command[2], command[3], command[4] + } else if (command.Length() = 5) { + Click, command[2], command[3], command[4], command[5] + } else if (command.Length() = 6) { + Click, command[2], command[3], command[4], command[5], command[6] + } else if (command.Length() = 7) { + Click, command[2], command[3], command[4], command[5], command[6], command[7] + } + return + +} + + + +stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] + +Loop { + query := RTrim(stdin.ReadLine(), "`n") + commandArray := StrSplit(query, ",") + func := commandArray[1] + response := %func%(commandArray) + FileAppend, %response%`n, * +} diff --git a/ahk/templates/daemon/key_state.ahk b/ahk/templates/daemon/key_state.ahk new file mode 100644 index 00000000..da7fb473 --- /dev/null +++ b/ahk/templates/daemon/key_state.ahk @@ -0,0 +1 @@ +AHKKeyState, {{ key_name }}{% if mode %}, {{ mode }}{% endif %} \ No newline at end of file From 1d5c44e57f4c74b6cfd1cfb7d619d1c6d2b10f60 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 08:14:07 -0800 Subject: [PATCH 111/588] daemon --- ahk/daemon.py | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 ahk/daemon.py diff --git a/ahk/daemon.py b/ahk/daemon.py new file mode 100644 index 00000000..9c247cff --- /dev/null +++ b/ahk/daemon.py @@ -0,0 +1,99 @@ +import os +import asyncio +from ahk.autohotkey import AsyncAHK + +class AHKDaemon(AsyncAHK): + proc: asyncio.subprocess.Process + _template_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates') + _template = os.path.join(_template_path, 'daemon.ahk') + _template_overrides = os.listdir(f'{_template_path}/daemon') + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.queue = asyncio.Queue() + self.result_queue = asyncio.Queue() + self.proc: asyncio.subprocess.Process + self.proc = None + self._is_running = False + self.run_lock = asyncio.Lock() + template = self.env.get_template('_daemon.ahk') + with open(self._template, 'w') as f: + f.write(template.render()) + + async def run(self): + if self._is_running: + raise RuntimeError("Already running") + self._is_running = True + runargs = [self.executable_path, self._template] + proc = await asyncio.subprocess.create_subprocess_exec(*runargs, + stdin=asyncio.subprocess.PIPE, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE) + self.proc = proc + + async def get_command(self): + return await self.queue.get() + + async def worker(self): + if not self._is_running: + await self.start() + while True: + command = await self.queue.get() + self.proc.stdin.write(command + b'\n') + await self.proc.stdin.drain() + res = await self.proc.stdout.readline() + self.result_queue.put_nowait(res[:-1]) + self.queue.task_done() + + def _start(self): + try: + asyncio.create_task(self.worker()) + yield + finally: + if self.proc is not None: + self.proc.kill() + + def stop(self): + if hasattr(self, '_gen') and self._gen is not None: + try: + next(self._gen) + except StopIteration: + pass + + async def start(self): + self._gen = self._start() + self._gen.send(None) + await self.run() + + def render_template(self, template_name, directives=None, blocking=True, **kwargs): + print(template_name) + name = template_name.split('/')[-1] + print(self._template_overrides) + if name in self._template_overrides: + template_name = f'daemon/{name}' + blocking = False + directives = None + kwargs['_daemon'] = True + return super().render_template(template_name, directives=directives, blocking=blocking, **kwargs) + + async def a_run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): + if not self._is_running: + raise RuntimeError("Not running! Must call .run() first!") + script_text = script_text.replace('#NoEnv', '', 1) + async with self.run_lock: + for line in script_text.split('\n'): + line = line.strip() + if not line: + continue + print(line) + self.queue.put_nowait(line.encode('utf-8')) + await self.queue.join() + res = [] + while not self.result_queue.empty(): + res.append(self.result_queue.get_nowait()) + res = b'\n'.join(i for i in res if i) + print(res) + if decode: + return res.decode('utf-8') + return res + + run_script = a_run_script From c664833c479eb0a8979c1b03201c22d5de269479 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 08:14:30 -0800 Subject: [PATCH 112/588] daemon tests --- tests/unittests/test_daemon.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/unittests/test_daemon.py diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py new file mode 100644 index 00000000..e9a5695e --- /dev/null +++ b/tests/unittests/test_daemon.py @@ -0,0 +1,26 @@ +import asyncio +import time +import sys +import os +from unittest import IsolatedAsyncioTestCase + +project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) +sys.path.insert(0, project_root) +from ahk.daemon import AHKDaemon + + +class TestMouseAsync(IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.ahk = AHKDaemon() + await self.ahk.start() + + def tearDown(self) -> None: + self.ahk.stop() + + async def test_mouse_move(self): + x, y = await self.ahk.mouse_position + await self.ahk.mouse_move(10, 10, relative=True) + assert await self.ahk.mouse_position == (x+10, y+10) + + + From c25725e77d483327e173bc3c90181d2e7c15a649 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 7 Jan 2021 08:15:23 -0800 Subject: [PATCH 113/588] :package: v0.12.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7723e68c..877ed755 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.11.1', + version='0.12.0', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From f52d9ae422cc7974a2469fd9cfe726075df7bc8c Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 08:30:42 -0800 Subject: [PATCH 114/588] fix keywait syntax --- ahk/templates/_daemon.ahk | 5 +++-- ahk/templates/daemon.ahk | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index 62ae6b90..e3d3ccca 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -54,10 +54,11 @@ Click(ByRef command) { } KeyWait(ByRef command) { + keyname := command[2] if (command.Length() = 2) { - KeyWait, command[2] + KeyWait, %keyname% } else { - KeyWait, command[2], command[3] + KeyWait, %keyname%, command[3] } return %ErrorLevel% } diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 1dd2473d..b1d84c15 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -61,7 +61,33 @@ Click(ByRef command) { } +KeyWait(ByRef command) { + keyname := command[2] + if (command.Length() = 2) { + KeyWait, %keyname% + } else { + KeyWait, %keyname%, command[3] + } + return %ErrorLevel% +} + +SetKeyDelay(ByRef command) { + SetKeyDelay, command[2] +} +Join(sep, params*) { + for index,param in params + str .= param . sep + return SubStr(str, 1, -StrLen(sep)) +} + +Send(ByRef command) { + Send % Join(",", command*) +} + +SendRaw(ByRef command) { + SendRaw % Join("," command*) +} stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] From 137670a55de9ddc7598129ae325ce91e436b3dfc Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 7 Jan 2021 10:51:22 -0800 Subject: [PATCH 115/588] Update README.md --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index b55b6436..61d29e49 100644 --- a/docs/README.md +++ b/docs/README.md @@ -306,7 +306,7 @@ in the async API. Instead, you should use a comparable method. If you _do_ use t that the task won't run until control is yielded back to the event loop. For now, this will also raise a warning to the same. -Lastly, while it's possible to pass `blocking=True` in the async API, this sometimes will cause problems. +Lastly, while it's possible to pass `blocking=False` in the async API, this sometimes will cause problems with certain functions. For now, a warning is raised in this case. ```python ahk = AsyncAHK() From af9b9b0eba215163e61093ddd1b84f6d7084576d Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 10:57:13 -0800 Subject: [PATCH 116/588] key_wait overrides --- ahk/daemon.py | 6 +++--- ahk/templates/_daemon.ahk | 8 +++++--- ahk/templates/daemon.ahk | 8 +++++--- ahk/templates/daemon/key_wait.ahk | 1 + tests/unittests/test_daemon.py | 3 --- 5 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 ahk/templates/daemon/key_wait.ahk diff --git a/ahk/daemon.py b/ahk/daemon.py index 9c247cff..e5d64d29 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -19,7 +19,7 @@ def __init__(self, *args, **kwargs): with open(self._template, 'w') as f: f.write(template.render()) - async def run(self): + async def _run(self): if self._is_running: raise RuntimeError("Already running") self._is_running = True @@ -30,7 +30,7 @@ async def run(self): stderr=asyncio.subprocess.PIPE) self.proc = proc - async def get_command(self): + async def _get_command(self): return await self.queue.get() async def worker(self): @@ -62,7 +62,7 @@ def stop(self): async def start(self): self._gen = self._start() self._gen.send(None) - await self.run() + await self._run() def render_template(self, template_name, directives=None, blocking=True, **kwargs): print(template_name) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index e3d3ccca..ff17289d 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -1,3 +1,4 @@ +#SingleInstance Force #NoEnv @@ -56,11 +57,12 @@ Click(ByRef command) { KeyWait(ByRef command) { keyname := command[2] if (command.Length() = 2) { - KeyWait, %keyname% + KeyWait,% keyname } else { - KeyWait, %keyname%, command[3] + options := command[3] + KeyWait,% keyname,% options } - return %ErrorLevel% + return ErrorLevel } SetKeyDelay(ByRef command) { diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index b1d84c15..cb80264e 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1,3 +1,4 @@ +#SingleInstance Force #NoEnv @@ -64,11 +65,12 @@ Click(ByRef command) { KeyWait(ByRef command) { keyname := command[2] if (command.Length() = 2) { - KeyWait, %keyname% + KeyWait,% keyname } else { - KeyWait, %keyname%, command[3] + options := command[3] + KeyWait,% keyname,% options } - return %ErrorLevel% + return ErrorLevel } SetKeyDelay(ByRef command) { diff --git a/ahk/templates/daemon/key_wait.ahk b/ahk/templates/daemon/key_wait.ahk new file mode 100644 index 00000000..c6bb5cd4 --- /dev/null +++ b/ahk/templates/daemon/key_wait.ahk @@ -0,0 +1 @@ +KeyWait,{{ key_name }}{% if options %},{{ options }}{% endif %} \ No newline at end of file diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index e9a5695e..a8ddb719 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -21,6 +21,3 @@ async def test_mouse_move(self): x, y = await self.ahk.mouse_position await self.ahk.mouse_move(10, 10, relative=True) assert await self.ahk.mouse_position == (x+10, y+10) - - - From 20d44d487bc75e02c7b9061c5cacc8830a3aed24 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 11:18:53 -0800 Subject: [PATCH 117/588] prevent rogue process from interfering with other tests --- tests/unittests/test_blocking_mouse.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unittests/test_blocking_mouse.py b/tests/unittests/test_blocking_mouse.py index 1c9daaa0..fb74f1be 100644 --- a/tests/unittests/test_blocking_mouse.py +++ b/tests/unittests/test_blocking_mouse.py @@ -18,8 +18,9 @@ def test_blocking_blocks(): 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) + proc = 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! - + proc.kill() + time.sleep(0.1) From 9244223e5905e2dc6b5a2a72832d8b7d4ee95675 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Thu, 7 Jan 2021 12:08:10 -0800 Subject: [PATCH 118/588] typing commands --- ahk/daemon.py | 11 +++++++++-- ahk/templates/_daemon.ahk | 22 ++++++++++++++++++++-- ahk/templates/daemon.ahk | 22 ++++++++++++++++++++-- ahk/templates/keyboard/send.ahk | 2 +- ahk/templates/keyboard/send_input.ahk | 2 +- 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/ahk/daemon.py b/ahk/daemon.py index e5d64d29..3e62e2bd 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -2,6 +2,9 @@ import asyncio from ahk.autohotkey import AsyncAHK +def escape(s): + return s.replace('\n', '`n') + class AHKDaemon(AsyncAHK): proc: asyncio.subprocess.Process _template_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates') @@ -65,11 +68,10 @@ async def start(self): await self._run() def render_template(self, template_name, directives=None, blocking=True, **kwargs): - print(template_name) name = template_name.split('/')[-1] - print(self._template_overrides) if name in self._template_overrides: template_name = f'daemon/{name}' + print(template_name) blocking = False directives = None kwargs['_daemon'] = True @@ -96,4 +98,9 @@ async def a_run_script(self, script_text: str, decode=True, blocking=True, **run return res.decode('utf-8') return res + async def type(self, s, *args, **kwargs): + kwargs['raw'] = True + s = escape(s) + await self.send(s, *args, **kwargs) + run_script = a_run_script diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index ff17289d..1e19dd59 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -75,12 +75,30 @@ Join(sep, params*) { return SubStr(str, 1, -StrLen(sep)) } +Unescape(HayStack) { + ReplacedStr := StrReplace(Haystack, "``n" , "`n") + return ReplacedStr +} + Send(ByRef command) { - Send % Join(",", command*) + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + Send,% str } SendRaw(ByRef command) { - SendRaw % Join("," command*) + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendRaw,% str +} + +SendInput(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendInput,% str } stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index cb80264e..542dc95e 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -83,12 +83,30 @@ Join(sep, params*) { return SubStr(str, 1, -StrLen(sep)) } +Unescape(HayStack) { + ReplacedStr := StrReplace(Haystack, "``n" , "`n") + return ReplacedStr +} + Send(ByRef command) { - Send % Join(",", command*) + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + Send,% str } SendRaw(ByRef command) { - SendRaw % Join("," command*) + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendRaw,% str +} + +SendInput(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendInput,% str } stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] diff --git a/ahk/templates/keyboard/send.ahk b/ahk/templates/keyboard/send.ahk index aadd1850..c1860597 100644 --- a/ahk/templates/keyboard/send.ahk +++ b/ahk/templates/keyboard/send.ahk @@ -2,5 +2,5 @@ {% block body %} {% if delay %}SetKeyDelay, {{ delay }}{% endif %} -Send{% if raw %}Raw{% endif %}, {{ s }} +Send{% if raw %}Raw{% endif %},{{ 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 index 85dadf51..199558b7 100644 --- a/ahk/templates/keyboard/send_input.ahk +++ b/ahk/templates/keyboard/send_input.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} -SendInput {{ s }} +SendInput,{{ s }} {% endblock body %} \ No newline at end of file From e58d27a133808d04456d3730a6535516c99372c7 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 10:43:13 -0800 Subject: [PATCH 119/588] daemon setcapslockstate --- ahk/templates/_daemon.ahk | 33 +++++++++++++++++++++ ahk/templates/daemon.ahk | 33 +++++++++++++++++++++ ahk/templates/daemon/set_capslock_state.ahk | 1 + ahk/templates/keyboard/send_event.ahk | 2 +- ahk/templates/keyboard/send_play.ahk | 2 +- 5 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 ahk/templates/daemon/set_capslock_state.ahk diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index 1e19dd59..16a67866 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -101,6 +101,39 @@ SendInput(ByRef command) { SendInput,% str } + +SendEvent(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendEvent,% str +} + +SendPlay(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendPlay,% str +} + +SetCapsLockState(ByRef command) { + if (command.Length() = 1) { + SetCapsLockState % !GetKeyState("CapsLock", "T") + } else { + state := command[2] + SetCapsLockState, %state% + } +} + +HideTrayTip(ByRef command) { + 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 + } +} + stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] Loop { diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 542dc95e..f15edd48 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -109,6 +109,39 @@ SendInput(ByRef command) { SendInput,% str } + +SendEvent(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendEvent,% str +} + +SendPlay(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendPlay,% str +} + +SetCapsLockState(ByRef command) { + if (command.Length() = 1) { + SetCapsLockState % !GetKeyState("CapsLock", "T") + } else { + state := command[2] + SetCapsLockState, %state% + } +} + +HideTrayTip(ByRef command) { + 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 + } +} + stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] Loop { diff --git a/ahk/templates/daemon/set_capslock_state.ahk b/ahk/templates/daemon/set_capslock_state.ahk new file mode 100644 index 00000000..dff84214 --- /dev/null +++ b/ahk/templates/daemon/set_capslock_state.ahk @@ -0,0 +1 @@ +{% if state %}SetCapsLockState,{{state}}{% else %}SetCapsLockState{% endif %} \ No newline at end of file diff --git a/ahk/templates/keyboard/send_event.ahk b/ahk/templates/keyboard/send_event.ahk index e892db4e..2cb0fd1f 100644 --- a/ahk/templates/keyboard/send_event.ahk +++ b/ahk/templates/keyboard/send_event.ahk @@ -2,5 +2,5 @@ {% block body %} {% if delay %}SetKeyDelay, {{ delay }}{% endif %} -SendEvent {{ s }} +SendEvent,{{ 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 index bc3fc3a9..4fefa931 100644 --- a/ahk/templates/keyboard/send_play.ahk +++ b/ahk/templates/keyboard/send_play.ahk @@ -2,5 +2,5 @@ {% block body %} {% if delay %}SetKeyDelay, {{ delay }}{% endif %} -SendPlay {{ s }} +SendPlay,{{ s }} {% endblock body %} \ No newline at end of file From 6d629b0e17e74fec13c0b75303996014f624c6fc Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 10:43:33 -0800 Subject: [PATCH 120/588] daemon tooltip/traytip --- ahk/daemon.py | 10 ++++++++++ ahk/templates/daemon/tooltip.ahk | 1 + ahk/templates/daemon/traytip.ahk | 1 + 3 files changed, 12 insertions(+) create mode 100644 ahk/templates/daemon/tooltip.ahk create mode 100644 ahk/templates/daemon/traytip.ahk diff --git a/ahk/daemon.py b/ahk/daemon.py index 3e62e2bd..c101109c 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -103,4 +103,14 @@ async def type(self, s, *args, **kwargs): s = escape(s) await self.send(s, *args, **kwargs) + def show_tooltip(self, text: str, second=None, x="", y="", id="", blocking=True): + return super().show_tooltip(text, second=second, x=x, y=y, id=id, blocking=blocking) + + def hide_tooltip(self, id): + return super().show_tooltip(text='', second='', x='', y='', id=id) + + async def hide_traytip(self): + await self.run_script("HideTrayTip") + + run_script = a_run_script diff --git a/ahk/templates/daemon/tooltip.ahk b/ahk/templates/daemon/tooltip.ahk new file mode 100644 index 00000000..bfc712cd --- /dev/null +++ b/ahk/templates/daemon/tooltip.ahk @@ -0,0 +1 @@ +ToolTip, {{ text }}, {{ x }}, {{ y }}, {{ id }} \ No newline at end of file diff --git a/ahk/templates/daemon/traytip.ahk b/ahk/templates/daemon/traytip.ahk new file mode 100644 index 00000000..ec907b92 --- /dev/null +++ b/ahk/templates/daemon/traytip.ahk @@ -0,0 +1 @@ +TrayTip {{ title }}, {{ text }}, {{ second }}, {{ option }} \ No newline at end of file From cc850b0c7ce15b4bb20d8886c8c55d2d6516a6fb Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 10:58:55 -0800 Subject: [PATCH 121/588] daemon mouse and registry --- ahk/templates/_daemon.ahk | 25 ++++++++++++++++++++++++- ahk/templates/mouse/click.ahk | 2 +- ahk/templates/mouse/mouse_drag.ahk | 4 ++-- ahk/templates/mouse/mouse_move.ahk | 2 +- ahk/templates/registery/reg_read.ahk | 2 +- ahk/templates/registery/reg_write.ahk | 2 +- 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index 16a67866..e03e0ca2 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -43,7 +43,7 @@ CoordMode(ByRef command) { Click(ByRef command) { - if command.Length() = 1 { + if (command.Length() = 1) { Click } {% for i in range(2, 8) %} else if (command.Length() = {{ i }}) { Click{% for index in range(2, i+1) %}, command[{{index}}]{% endfor %} @@ -54,6 +54,29 @@ Click(ByRef command) { } +MouseClickDrag(ByRef command) { + if (command.Length() = 6 { + MouseclickDrag,command[2],command[3],command[4],command[5],command[6] + } else if (command.Length() = 7 { + MouseclickDrag,command[2],command[3],command[4],command[5],command[6],command[7] + } else if (command.Length() = 8 { + MouseclickDrag,command[2],command[3],command[4],command[5],command[6],command[7],command[8] + } +} + +RegRead(ByRef command) { + RegRead, output, command[3], command[4] + return %output% +} + +SetRegView(ByRef command) { + SetRegView, command[2] +} + +RegWrite(ByRef command) { + RegWrite, command[2], command[3], command[4] +} + KeyWait(ByRef command) { keyname := command[2] if (command.Length() = 2) { diff --git a/ahk/templates/mouse/click.ahk b/ahk/templates/mouse/click.ahk index 6df573cc..43eb7b35 100644 --- a/ahk/templates/mouse/click.ahk +++ b/ahk/templates/mouse/click.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} -CoordMode Mouse, {{mode}} +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 index 73b99efe..61557e05 100644 --- a/ahk/templates/mouse/mouse_drag.ahk +++ b/ahk/templates/mouse/mouse_drag.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} -CoordMode Mouse, {{mode}} -MouseClickDrag, {{button}}, {{x1}}, {{y1}}, {{x2}}, {{y2}}{% if speed %}, {{speed}}{% endif %}{% if relative %}, R{% endif %} +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 index 2ebd2a15..88212602 100644 --- a/ahk/templates/mouse/mouse_move.ahk +++ b/ahk/templates/mouse/mouse_move.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} -CoordMode Mouse, {{mode}} +CoordMode, Mouse, {{mode}} MouseMove, {{x}}, {{y}}, {{speed}}{% if relative %}, R{% endif %} {% endblock body %} \ No newline at end of file diff --git a/ahk/templates/registery/reg_read.ahk b/ahk/templates/registery/reg_read.ahk index 69b6fcc6..f26afa0c 100644 --- a/ahk/templates/registery/reg_read.ahk +++ b/ahk/templates/registery/reg_read.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} -RegRead, output, {{ key_name }}, {{ value_name }} +RegRead,output,{{ key_name }},{{ value_name }} FileAppend, %output%, * {% endblock body %} diff --git a/ahk/templates/registery/reg_write.ahk b/ahk/templates/registery/reg_write.ahk index 9fed241c..4bc9e3ff 100644 --- a/ahk/templates/registery/reg_write.ahk +++ b/ahk/templates/registery/reg_write.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -RegWrite, {{ value_type }}, {{ key_name }}, {{ value_name }} +RegWrite,{{ value_type }},{{ key_name }},{{ value_name }} {% endblock body %} From bae7f09febfb40c260f26f5561b4dd2fa624e505 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 11:05:21 -0800 Subject: [PATCH 122/588] fix registry parameters --- ahk/templates/_daemon.ahk | 29 ++++++++++++++------- ahk/templates/daemon.ahk | 36 +++++++++++++++++++++++++- ahk/templates/registery/reg_delete.ahk | 2 +- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index e03e0ca2..6b350bb7 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -55,26 +55,37 @@ Click(ByRef command) { } MouseClickDrag(ByRef command) { - if (command.Length() = 6 { - MouseclickDrag,command[2],command[3],command[4],command[5],command[6] - } else if (command.Length() = 7 { - MouseclickDrag,command[2],command[3],command[4],command[5],command[6],command[7] - } else if (command.Length() = 8 { - MouseclickDrag,command[2],command[3],command[4],command[5],command[6],command[7],command[8] + button := command[2] + if (command.Length() = 6) { + MouseClickDrag,%button%,command[3],command[4],command[5],command[6] + } else if (command.Length() = 7) { + MouseClickDrag,%button%,command[3],command[4],command[5],command[6],command[7] + } else if (command.Length() = 8) { + MouseClickDrag,%button%,command[3],command[4],command[5],command[6],command[7],R } } RegRead(ByRef command) { - RegRead, output, command[3], command[4] + keyname := command[3] + RegRead, output, %keyname%, command[4] return %output% } SetRegView(ByRef command) { - SetRegView, command[2] + view := command[2] + SetRegView, %view% } RegWrite(ByRef command) { - RegWrite, command[2], command[3], command[4] + valuetype := command[2] + keyname := command[3] + + RegWrite, %valuetype%, %keyname%, command[4] +} + +RegDelete(ByRef command) { + keyname := command[2] + RegDelete, %keyname%, command[3] } KeyWait(ByRef command) { diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index f15edd48..9c5ad517 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -43,7 +43,7 @@ CoordMode(ByRef command) { Click(ByRef command) { - if command.Length() = 1 { + if (command.Length() = 1) { Click } else if (command.Length() = 2) { Click, command[2] @@ -62,6 +62,40 @@ Click(ByRef command) { } +MouseClickDrag(ByRef command) { + button := command[2] + if (command.Length() = 6) { + MouseClickDrag,%button%,command[3],command[4],command[5],command[6] + } else if (command.Length() = 7) { + MouseClickDrag,%button%,command[3],command[4],command[5],command[6],command[7] + } else if (command.Length() = 8) { + MouseClickDrag,%button%,command[3],command[4],command[5],command[6],command[7],R + } +} + +RegRead(ByRef command) { + keyname := command[3] + RegRead, output, %keyname%, command[4] + return %output% +} + +SetRegView(ByRef command) { + view := command[2] + SetRegView, %view% +} + +RegWrite(ByRef command) { + valuetype := command[2] + keyname := command[3] + + RegWrite, %valuetype%, %keyname%, command[4] +} + +RegDelete(ByRef command) { + keyname := command[2] + RegDelete, %keyname%, command[3] +} + KeyWait(ByRef command) { keyname := command[2] if (command.Length() = 2) { diff --git a/ahk/templates/registery/reg_delete.ahk b/ahk/templates/registery/reg_delete.ahk index b7eb1fcd..dbadefa9 100644 --- a/ahk/templates/registery/reg_delete.ahk +++ b/ahk/templates/registery/reg_delete.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -RegDelete, {{ key_name }}, {{ value_name }} +RegDelete,{{ key_name }},{{ value_name }} {% endblock body %} From d39ddebce9c5cd3511b935221fd46699cf336ed9 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 11:25:30 -0800 Subject: [PATCH 123/588] daemon screen commands --- ahk/templates/_daemon.ahk | 29 +++++++++++++++++++++++- ahk/templates/daemon.ahk | 29 +++++++++++++++++++++++- ahk/templates/daemon/image_search.ahk | 2 ++ ahk/templates/daemon/pixel_get_color.ahk | 2 ++ ahk/templates/daemon/pixel_search.ahk | 2 ++ ahk/templates/screen/image_search.ahk | 4 ++-- ahk/templates/screen/pixel_get_color.ahk | 4 ++-- ahk/templates/screen/pixel_search.ahk | 1 - 8 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 ahk/templates/daemon/image_search.ahk create mode 100644 ahk/templates/daemon/pixel_get_color.ahk create mode 100644 ahk/templates/daemon/pixel_search.ahk diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index 6b350bb7..4fe392d1 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -1,6 +1,33 @@ #SingleInstance Force #NoEnv +ImageSearch(ByRef command) { + imagepath := command[8] + ImageSearch, xpos, ypos, command[4], command[5], command[6], command[7], %imagepath% + s .= Format("({}, {})", xpos, ypos) + return s +} + +PixelGetColor(ByRef command) { + if (command.Length() = 4) { + PixelGetColor,color,command[3],command[4] + } else { + options := command[5] + PixelGetColor,color,command[3],command[4], %options% + } + return color +} + +PixelSearch(ByRef command) { + if (command.Length() = 9) { + PixelSearch, xpos, ypos, command[4], command[5], command[6], command[7], command[8], command[9] + } else { + options := command[10] + PixelSearch, xpos, ypos, command[4], command[5], command[6], command[7], command[8], command[9], %options% + } + s .= Format("({}, {})", xpos, ypos) + return s +} MouseGetPos(ByRef command) { @@ -68,7 +95,7 @@ MouseClickDrag(ByRef command) { RegRead(ByRef command) { keyname := command[3] RegRead, output, %keyname%, command[4] - return %output% + return output } SetRegView(ByRef command) { diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 9c5ad517..fb219621 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1,6 +1,33 @@ #SingleInstance Force #NoEnv +ImageSearch(ByRef command) { + imagepath := command[8] + ImageSearch, xpos, ypos, command[4], command[5], command[6], command[7], %imagepath% + s .= Format("({}, {})", xpos, ypos) + return s +} + +PixelGetColor(ByRef command) { + if (command.Length() = 4) { + PixelGetColor,color,command[3],command[4] + } else { + options := command[5] + PixelGetColor,color,command[3],command[4], %options% + } + return color +} + +PixelSearch(ByRef command) { + if (command.Length() = 9) { + PixelSearch, xpos, ypos, command[4], command[5], command[6], command[7], command[8], command[9] + } else { + options := command[10] + PixelSearch, xpos, ypos, command[4], command[5], command[6], command[7], command[8], command[9], %options% + } + s .= Format("({}, {})", xpos, ypos) + return s +} MouseGetPos(ByRef command) { @@ -76,7 +103,7 @@ MouseClickDrag(ByRef command) { RegRead(ByRef command) { keyname := command[3] RegRead, output, %keyname%, command[4] - return %output% + return output } SetRegView(ByRef command) { diff --git a/ahk/templates/daemon/image_search.ahk b/ahk/templates/daemon/image_search.ahk new file mode 100644 index 00000000..826f2065 --- /dev/null +++ b/ahk/templates/daemon/image_search.ahk @@ -0,0 +1,2 @@ +CoordMode, Pixel, {{ coord_mode }} +ImageSearch, xpos, ypos, {{ x1 }}, {{ y1 }}, {{ x2 }}, {{ y2 }}, {% if options %}{% for option in options %}*{{ option }} {% endfor %}{% endif %}{{ image_path }} \ No newline at end of file diff --git a/ahk/templates/daemon/pixel_get_color.ahk b/ahk/templates/daemon/pixel_get_color.ahk new file mode 100644 index 00000000..3c9ab720 --- /dev/null +++ b/ahk/templates/daemon/pixel_get_color.ahk @@ -0,0 +1,2 @@ +CoordMode, Pixel, {{ coord_mode }} +PixelGetColor,color,{{ x }},{{ y }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} \ No newline at end of file diff --git a/ahk/templates/daemon/pixel_search.ahk b/ahk/templates/daemon/pixel_search.ahk new file mode 100644 index 00000000..bd4b322f --- /dev/null +++ b/ahk/templates/daemon/pixel_search.ahk @@ -0,0 +1,2 @@ +CoordMode, Pixel, {{ coord_mode }} +PixelSearch, xpos, ypos,{{ x1 }},{{ y1 }},{{ x2 }},{{ y2 }},{{ color }},{{ variation }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} \ No newline at end of file diff --git a/ahk/templates/screen/image_search.ahk b/ahk/templates/screen/image_search.ahk index 1b7a3de8..e2bee7cf 100644 --- a/ahk/templates/screen/image_search.ahk +++ b/ahk/templates/screen/image_search.ahk @@ -1,7 +1,7 @@ {% extends "base.ahk" %} {% block body %} -CoordMode Pixel, {{ coord_mode }} -ImageSearch, xpos, ypos, {{ x1 }}, {{ y1 }}, {{ x2 }}, {{ y2 }}, {% if options %}{% for option in options %}*{{ option }} {% endfor %}{% endif %}{{ image_path }} +CoordMode, Pixel, {{ coord_mode }} +ImageSearch,xpos,ypos,{{ x1 }},{{ y1 }},{{ x2 }},{{ y2 }},{% if options %}{% for option in options %}*{{ option }} {% endfor %}{% 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 index 77a18437..0e9fba2a 100644 --- a/ahk/templates/screen/pixel_get_color.ahk +++ b/ahk/templates/screen/pixel_get_color.ahk @@ -1,7 +1,7 @@ {% extends "base.ahk" %} {% block body %} -CoordMode Pixel, {{ coord_mode }} -PixelGetColor, color, {{ x }}, {{ y }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} +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 index 833b73ed..fc29db8f 100644 --- a/ahk/templates/screen/pixel_search.ahk +++ b/ahk/templates/screen/pixel_search.ahk @@ -2,7 +2,6 @@ {% 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 %} From d423060b66c467e2989527b0878075319888d8ad Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 14:31:15 -0800 Subject: [PATCH 124/588] daemon window commands --- ahk/daemon.py | 7 +- ahk/keyboard.py | 3 +- ahk/script.py | 6 +- ahk/templates/_daemon.ahk | 268 ++++++++++++++++++ ahk/templates/daemon.ahk | 268 ++++++++++++++++++ ahk/templates/daemon/base_check.ahk | 1 + ahk/templates/daemon/control_send.ahk | 1 + ahk/templates/daemon/from_mouse.ahk | 1 + ahk/templates/daemon/id_list.ahk | 1 + ahk/templates/daemon/win_click.ahk | 1 + ahk/templates/daemon/win_is_always_on_top.ahk | 1 + ahk/templates/daemon/win_move.ahk | 1 + ahk/templates/daemon/win_position.ahk | 5 + ahk/templates/daemon/win_send.ahk | 2 + ahk/templates/window/base_command.ahk | 2 +- ahk/templates/window/base_get_command.ahk | 2 +- ahk/templates/window/control_send.ahk | 2 +- ahk/templates/window/get.ahk | 2 +- ahk/templates/window/id_list.ahk | 2 +- ahk/templates/window/win_move.ahk | 2 +- ahk/window.py | 10 +- 21 files changed, 573 insertions(+), 15 deletions(-) create mode 100644 ahk/templates/daemon/base_check.ahk create mode 100644 ahk/templates/daemon/control_send.ahk create mode 100644 ahk/templates/daemon/from_mouse.ahk create mode 100644 ahk/templates/daemon/id_list.ahk create mode 100644 ahk/templates/daemon/win_click.ahk create mode 100644 ahk/templates/daemon/win_is_always_on_top.ahk create mode 100644 ahk/templates/daemon/win_move.ahk create mode 100644 ahk/templates/daemon/win_position.ahk create mode 100644 ahk/templates/daemon/win_send.ahk diff --git a/ahk/daemon.py b/ahk/daemon.py index c101109c..2050c9ef 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -3,7 +3,8 @@ from ahk.autohotkey import AsyncAHK def escape(s): - return s.replace('\n', '`n') + s = s.replace('\n', '`n') + return s class AHKDaemon(AsyncAHK): proc: asyncio.subprocess.Process @@ -112,5 +113,9 @@ def hide_tooltip(self, id): async def hide_traytip(self): await self.run_script("HideTrayTip") + @staticmethod + def escape_sequence_replace(s): + s = escape(s) + return s run_script = a_run_script diff --git a/ahk/keyboard.py b/ahk/keyboard.py index 0c9ab8d8..7b8e2239 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -2,7 +2,6 @@ import warnings from ahk.script import ScriptEngine, AsyncScriptEngine -from ahk.utils import escape_sequence_replace from ahk.keys import Key from ahk.directives import InstallKeybdHook, InstallMouseHook @@ -141,7 +140,7 @@ def type(self, s, blocking=True): :param s: the string to type :param blocking: if ``True``, waits until script finishes, else returns immediately. """ - s = escape_sequence_replace(s) + s = self.escape_sequence_replace(s) return self.send_input(s, blocking=blocking) or None def _send(self, s, raw=False, delay=None, blocking=True): diff --git a/ahk/script.py b/ahk/script.py index a1dfb0a2..3d1b913c 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -15,7 +15,7 @@ import subprocess import warnings from shutil import which -from ahk.utils import make_logger +from ahk.utils import make_logger, escape_sequence_replace from ahk.directives import Persistent from jinja2 import Environment, FileSystemLoader from typing import Set @@ -90,6 +90,10 @@ def __init__(self, executable_path: str = "", directives: Set = None, **kwargs): directives = set() self._directives = set(directives) + @staticmethod + def escape_sequence_replace(*args, **kwargs): + return escape_sequence_replace(*args, **kwargs) + def render_template(self, template_name, directives=None, blocking=True, **kwargs): """ Renders a given jinja template and returns a string of script text diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index 4fe392d1..c6efc036 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -195,6 +195,274 @@ HideTrayTip(ByRef command) { } } +WinGetTitle(ByRef command) { + title := command[3] + WinGetTitle, text, %title% + return text +} +WinGetClass(ByRef command) { + title := command[3] + WinGetClass, text, %title% + return text +} +WinGetText(ByRef command) { + title := command[3] + WinGetText, text, %title% + return text +} + +WinActivate(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinActivate, %title% + } else { + secondstowait = command[3] + WinActivate, %title%, %secondstowait% + } +} + +WinActivateBottom(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinActivateBottom, %title% + } else { + secondstowait = command[3] + WinActivateBottom, %title%, %secondstowait% + } +} + +WinClose(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinClose, %title% + } else { + secondstowait = command[3] + WinClose, %title%, %secondstowait% + } +} + +WinHide(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinHide, %title% + } else { + secondstowait = command[3] + WinHide, %title%, %secondstowait% + } +} + +WinKill(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinKill, %title% + } else { + secondstowait = command[3] + WinKill, %title%, %secondstowait% + } +} + +WinMaximize(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinMaximize, %title% + } else { + secondstowait = command[3] + WinMaximize, %title%, %secondstowait% + } +} + +WinMinimize(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinMinimize, %title% + } else { + secondstowait = command[3] + WinMinimize, %title%, %secondstowait% + } +} + +WinRestore(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinRestore, %title% + } else { + secondstowait = command[3] + WinRestore, %title%, %secondstowait% + } +} + +WinShow(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinShow, %title% + } else { + secondstowait = command[3] + WinShow, %title%, %secondstowait% + } +} + +WinWait(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinWait, %title% + } else { + secondstowait = command[3] + WinWait, %title%, %secondstowait% + } +} + +WinWaitActive(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinWaitActive, %title% + } else { + secondstowait = command[3] + WinWaitActive, %title%, %secondstowait% + } +} + +WinWaitNotActive(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinWaitNotActive, %title% + } else { + secondstowait = command[3] + WinWaitNotActive, %title%, %secondstowait% + } +} + +WinWaitClose(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinWaitClose, %title% + } else { + secondstowait = command[3] + WinWaitClose, %title%, %secondstowait% + } +} + + +WindowList(ByRef command) { + WinGet windows, List + Loop %windows% + { + id := windows%A_Index% + r .= id . "`," + } + return r +} + +WinSend(ByRef command) { + title := command[2] + command.RemoveAt(1) + command.RemoveAt(1) + str := Join(",", command*) + keys := Unescape(str) + ControlSendRaw,,% keys, %title% +} + +ControlSend(ByRef command) { + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + str := Join(",", command*) + keys := Unescape(str) + ControlSend, %ctrl%,% keys, %title%, %text%, %extitle%, %extext% +} + + +BaseCheck(ByRef command) { + kommand := command[2] + title := command[3] + if %kommand%(title) { + return 1 + } + else { + return 0 + } +} + +FromMouse(ByRef command) { + MouseGetPos,,, MouseWin + return MouseWin +} + +WinGet(ByRef command) { + + WinGet, output,% command[3], command[4], command[5], command[6], command[7] + return output +} + +WinSet(ByRef command) { + WinSet,% command[2], command[3], command[4] +} + +WinIsAlwaysOnTop(ByRef command) { + WinGet, ExStyle, ExStyle, {{ title }} + if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. + return 1 + else + return 0 +} + +WinClick(ByRef command) { + x := command[2] + y := command[3] + hwnd := command[4] + button := command[5] + n := command[6] + if (command.Length() = 6) { + ControlClick,x%x% y%y%,%hwnd%,,%button%,%n% + } else { + options := command[6] + ControlClick, x%x% y%y%, %hwnd%,,%button%, %n%, options + } +} + +AHKWinMove(ByRef command) { + title := command [2] + x := command[3] + y := command[4] + if (command.Length()) = 4 { + WinMove,%title%,,%x%,%y% + } else if (command.Length() = 5) { + a := command[5] + WinMove,%title%,,%x%,%y%,%a% + } else if (command.Length() = 6) { + a := command[5] + b := command[6] + WinMove,%title%,,%x%,%y%,%a%,%b% + } +} + +AHKWinGetPos(ByRef command) { + title := command[2] + WinGetPos, x, y, width, height, %title% + if (command.Length() = 3) { + pos_info := command[3] + if (pos_info = "position") { + s .= Format("({}, {})", x, y) + } else if (pos_info = "height") { + s .= Format("({})", height) + } else if (pos_info = "width") { + s .= Format("({})", width) + } + } else { + s .= Format("({}, {}, {}, {})", x, y, width, height) + } + return s +} + + + stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] Loop { diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index fb219621..08bb78df 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -203,6 +203,274 @@ HideTrayTip(ByRef command) { } } +WinGetTitle(ByRef command) { + title := command[3] + WinGetTitle, text, %title% + return text +} +WinGetClass(ByRef command) { + title := command[3] + WinGetClass, text, %title% + return text +} +WinGetText(ByRef command) { + title := command[3] + WinGetText, text, %title% + return text +} + +WinActivate(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinActivate, %title% + } else { + secondstowait = command[3] + WinActivate, %title%, %secondstowait% + } +} + +WinActivateBottom(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinActivateBottom, %title% + } else { + secondstowait = command[3] + WinActivateBottom, %title%, %secondstowait% + } +} + +WinClose(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinClose, %title% + } else { + secondstowait = command[3] + WinClose, %title%, %secondstowait% + } +} + +WinHide(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinHide, %title% + } else { + secondstowait = command[3] + WinHide, %title%, %secondstowait% + } +} + +WinKill(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinKill, %title% + } else { + secondstowait = command[3] + WinKill, %title%, %secondstowait% + } +} + +WinMaximize(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinMaximize, %title% + } else { + secondstowait = command[3] + WinMaximize, %title%, %secondstowait% + } +} + +WinMinimize(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinMinimize, %title% + } else { + secondstowait = command[3] + WinMinimize, %title%, %secondstowait% + } +} + +WinRestore(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinRestore, %title% + } else { + secondstowait = command[3] + WinRestore, %title%, %secondstowait% + } +} + +WinShow(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinShow, %title% + } else { + secondstowait = command[3] + WinShow, %title%, %secondstowait% + } +} + +WinWait(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinWait, %title% + } else { + secondstowait = command[3] + WinWait, %title%, %secondstowait% + } +} + +WinWaitActive(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinWaitActive, %title% + } else { + secondstowait = command[3] + WinWaitActive, %title%, %secondstowait% + } +} + +WinWaitNotActive(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinWaitNotActive, %title% + } else { + secondstowait = command[3] + WinWaitNotActive, %title%, %secondstowait% + } +} + +WinWaitClose(ByRef command) { + title = command[2] + if (command.Length() = 2) { + WinWaitClose, %title% + } else { + secondstowait = command[3] + WinWaitClose, %title%, %secondstowait% + } +} + + +WindowList(ByRef command) { + WinGet windows, List + Loop %windows% + { + id := windows%A_Index% + r .= id . "`," + } + return r +} + +WinSend(ByRef command) { + title := command[2] + command.RemoveAt(1) + command.RemoveAt(1) + str := Join(",", command*) + keys := Unescape(str) + ControlSendRaw,,% keys, %title% +} + +ControlSend(ByRef command) { + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + str := Join(",", command*) + keys := Unescape(str) + ControlSend, %ctrl%,% keys, %title%, %text%, %extitle%, %extext% +} + + +BaseCheck(ByRef command) { + kommand := command[2] + title := command[3] + if %kommand%(title) { + return 1 + } + else { + return 0 + } +} + +FromMouse(ByRef command) { + MouseGetPos,,, MouseWin + return MouseWin +} + +WinGet(ByRef command) { + + WinGet, output,% command[3], command[4], command[5], command[6], command[7] + return output +} + +WinSet(ByRef command) { + WinSet,% command[2], command[3], command[4] +} + +WinIsAlwaysOnTop(ByRef command) { + WinGet, ExStyle, ExStyle, + if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. + return 1 + else + return 0 +} + +WinClick(ByRef command) { + x := command[2] + y := command[3] + hwnd := command[4] + button := command[5] + n := command[6] + if (command.Length() = 6) { + ControlClick,x%x% y%y%,%hwnd%,,%button%,%n% + } else { + options := command[6] + ControlClick, x%x% y%y%, %hwnd%,,%button%, %n%, options + } +} + +AHKWinMove(ByRef command) { + title := command [2] + x := command[3] + y := command[4] + if (command.Length()) = 4 { + WinMove,%title%,,%x%,%y% + } else if (command.Length() = 5) { + a := command[5] + WinMove,%title%,,%x%,%y%,%a% + } else if (command.Length() = 6) { + a := command[5] + b := command[6] + WinMove,%title%,,%x%,%y%,%a%,%b% + } +} + +AHKWinGetPos(ByRef command) { + title := command[2] + WinGetPos, x, y, width, height, %title% + if (command.Length() = 3) { + pos_info := command[3] + if (pos_info = "position") { + s .= Format("({}, {})", x, y) + } else if (pos_info = "height") { + s .= Format("({})", height) + } else if (pos_info = "width") { + s .= Format("({})", width) + } + } else { + s .= Format("({}, {}, {}, {})", x, y, width, height) + } + return s +} + + + stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] Loop { diff --git a/ahk/templates/daemon/base_check.ahk b/ahk/templates/daemon/base_check.ahk new file mode 100644 index 00000000..196bfcbe --- /dev/null +++ b/ahk/templates/daemon/base_check.ahk @@ -0,0 +1 @@ +BaseCheck,{{ command }},{{ title }} \ No newline at end of file diff --git a/ahk/templates/daemon/control_send.ahk b/ahk/templates/daemon/control_send.ahk new file mode 100644 index 00000000..539237df --- /dev/null +++ b/ahk/templates/daemon/control_send.ahk @@ -0,0 +1 @@ +ControlSend,{{ control }},{{ win_title }},{{ win_text }},{{ exclude_title }},{{ exclude_text }},{{keys}} \ No newline at end of file diff --git a/ahk/templates/daemon/from_mouse.ahk b/ahk/templates/daemon/from_mouse.ahk new file mode 100644 index 00000000..b91527dd --- /dev/null +++ b/ahk/templates/daemon/from_mouse.ahk @@ -0,0 +1 @@ +FromMouse \ No newline at end of file diff --git a/ahk/templates/daemon/id_list.ahk b/ahk/templates/daemon/id_list.ahk new file mode 100644 index 00000000..1155f511 --- /dev/null +++ b/ahk/templates/daemon/id_list.ahk @@ -0,0 +1 @@ +WindowList \ No newline at end of file diff --git a/ahk/templates/daemon/win_click.ahk b/ahk/templates/daemon/win_click.ahk new file mode 100644 index 00000000..2564f8ec --- /dev/null +++ b/ahk/templates/daemon/win_click.ahk @@ -0,0 +1 @@ +WinClick,{{ x }},{{ y }},{{ hwnd }},{{ button }},{{ n }}{% if options %},{{ options }}{% endif %} \ No newline at end of file diff --git a/ahk/templates/daemon/win_is_always_on_top.ahk b/ahk/templates/daemon/win_is_always_on_top.ahk new file mode 100644 index 00000000..691c6c60 --- /dev/null +++ b/ahk/templates/daemon/win_is_always_on_top.ahk @@ -0,0 +1 @@ +WinIsAlwaysOnTop \ No newline at end of file diff --git a/ahk/templates/daemon/win_move.ahk b/ahk/templates/daemon/win_move.ahk new file mode 100644 index 00000000..078a3340 --- /dev/null +++ b/ahk/templates/daemon/win_move.ahk @@ -0,0 +1 @@ +AHKWinMove,{{ title }},{{ x }},{{ y }}{% if width %},{{ width }}{% endif %}{% if height %},{{ height }}{% endif %} \ No newline at end of file diff --git a/ahk/templates/daemon/win_position.ahk b/ahk/templates/daemon/win_position.ahk new file mode 100644 index 00000000..02cdfda9 --- /dev/null +++ b/ahk/templates/daemon/win_position.ahk @@ -0,0 +1,5 @@ +{% if pos_info %} +AHKWinGetPos,{{ title }},{{ pos_info }} +{% else %} +AHKWinGetPos,{{ title }} +{% endif %} \ No newline at end of file diff --git a/ahk/templates/daemon/win_send.ahk b/ahk/templates/daemon/win_send.ahk new file mode 100644 index 00000000..4d7073ff --- /dev/null +++ b/ahk/templates/daemon/win_send.ahk @@ -0,0 +1,2 @@ +SetKeyDelay,{{ delay }},{{ press_duration }} +WinSend,{{ title }},{{ keys }} \ No newline at end of file diff --git a/ahk/templates/window/base_command.ahk b/ahk/templates/window/base_command.ahk index 0b2959d6..a0c7df92 100644 --- a/ahk/templates/window/base_command.ahk +++ b/ahk/templates/window/base_command.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -{{ command }}, {{ title }}{% if seconds_to_wait %}, {{ seconds_to_wait }}{% endif %} +{{ command }},{{ title }}{% if seconds_to_wait %},{{ seconds_to_wait }}{% endif %} {% endblock body %} diff --git a/ahk/templates/window/base_get_command.ahk b/ahk/templates/window/base_get_command.ahk index 3f1ae7ac..fd4867bc 100644 --- a/ahk/templates/window/base_get_command.ahk +++ b/ahk/templates/window/base_get_command.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} -{{ command }}, text, {{ title }} +{{ command }},text,{{ title }} FileAppend, %text%, * {% endblock body %} diff --git a/ahk/templates/window/control_send.ahk b/ahk/templates/window/control_send.ahk index 72cf03c6..8d4817f7 100644 --- a/ahk/templates/window/control_send.ahk +++ b/ahk/templates/window/control_send.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -ControlSend, {{ control }}, {{ keys }}, {{ win_title }}, {{ win_text }}, {{ exclude_title }}, {{ exclude_text }} +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/get.ahk b/ahk/templates/window/get.ahk index f0158ea6..01189496 100644 --- a/ahk/templates/window/get.ahk +++ b/ahk/templates/window/get.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} -WinGet, output, {{ subcommand }}, {{ title }}, {{ text }}, {{ exclude_title }}, {{ exclude_text }} +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 index 085bf1ea..64797959 100644 --- a/ahk/templates/window/id_list.ahk +++ b/ahk/templates/window/id_list.ahk @@ -4,7 +4,7 @@ WinGet windows, List Loop %windows% { id := windows%A_Index% - r .= id . "`n" + r .= id . "`," } FileAppend, %r%, * {% endblock body %} \ No newline at end of file diff --git a/ahk/templates/window/win_move.ahk b/ahk/templates/window/win_move.ahk index c836645e..c3d0ea7c 100644 --- a/ahk/templates/window/win_move.ahk +++ b/ahk/templates/window/win_move.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -WinMove, {{ title }}, , {{ x }}, {{ y }}{% if width %}, {{ width }}{% endif %}{% if height %}, {{ height }}{% endif %} +WinMove,{{ title }},,{{ x }},{{ y }}{% if width %},{{ width }}{% endif %}{% if height %},{{ height }}{% endif %} {% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index 3e3e6181..9b162a20 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -589,11 +589,11 @@ def move(self, x='', y='', width=None, height=None): :return: """ script = self._move(x=x, y=y, width=width, height=height) - self.engine.run_script(script) + return self.engine.run_script(script) or None def _send(self, keys, delay=10, raw=False, blocking=False, escape=False, press_duration=-1): if escape: - keys = escape_sequence_replace(keys) + keys = self.engine.escape_sequence_replace(keys) script = self._render_template( 'window/win_send.ahk', title=f"ahk_id {self.id}", @@ -701,7 +701,7 @@ def _all_window_ids_(self): def _all_window_ids(self): script = self._all_window_ids_() result = self.run_script(script) - return result.split('\n')[:-1] # last one is always an empty string + return result.split(',')[:-1] # last one is always an empty string def windows(self): """ @@ -971,7 +971,7 @@ def active_window(self): async def _all_window_ids(self): script = self._all_window_ids_() result = await self.a_run_script(script) - return result.split('\n')[:-1] # last one is always an empty string + return result.split(',')[:-1] # last one is always an empty string async def windows(self): """ @@ -1043,7 +1043,7 @@ async def find_window_by_title(self, title): :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found """ - async for window in self.find_windows_by_title(): + async for window in self.find_windows_by_title(title=title): return window async def find_windows_by_text(self, text, exact=False): From d524530585181793a96e92fa6184c79b89e5adfe Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 14:39:39 -0800 Subject: [PATCH 125/588] daemon set title --- ahk/templates/_daemon.ahk | 5 +++++ ahk/templates/daemon.ahk | 5 +++++ ahk/templates/window/win_set_title.ahk | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index c6efc036..f893d0a1 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -405,6 +405,11 @@ WinSet(ByRef command) { WinSet,% command[2], command[3], command[4] } +WinSetTitle(ByRef command) { + newtitle := command[4] + WinSetTitle,% command[2],, %newtitle% +} + WinIsAlwaysOnTop(ByRef command) { WinGet, ExStyle, ExStyle, {{ title }} if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 08bb78df..8f1637a6 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -413,6 +413,11 @@ WinSet(ByRef command) { WinSet,% command[2], command[3], command[4] } +WinSetTitle(ByRef command) { + newtitle := command[4] + WinSetTitle,% command[2],, %newtitle% +} + WinIsAlwaysOnTop(ByRef command) { WinGet, ExStyle, ExStyle, if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. diff --git a/ahk/templates/window/win_set_title.ahk b/ahk/templates/window/win_set_title.ahk index 6335bdd7..4ba1da1e 100644 --- a/ahk/templates/window/win_set_title.ahk +++ b/ahk/templates/window/win_set_title.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -WinSetTitle, {{ title }}, {{ text }}, {{ new_title }} -{% endblock body %} +WinSetTitle,{{ title }},{{ text }},{{ new_title }} +{% endblock body %} \ No newline at end of file From dde52698e3581e15088ac25a2013e242e2de9232 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 14:41:25 -0800 Subject: [PATCH 126/588] fix winset --- ahk/templates/window/win_set.ahk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/templates/window/win_set.ahk b/ahk/templates/window/win_set.ahk index 7d12cd95..597d032d 100644 --- a/ahk/templates/window/win_set.ahk +++ b/ahk/templates/window/win_set.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -WinSet, {{subcommand}}, {{value}}, {{ title }} +WinSet,{{subcommand}},{{value}},{{ title }} {% endblock body %} From 2b2c09859e7aa757712cee791abd7e3db045e437 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 14:44:51 -0800 Subject: [PATCH 127/588] fix winset --- ahk/templates/_daemon.ahk | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index f893d0a1..fecef5ed 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -396,13 +396,15 @@ FromMouse(ByRef command) { } WinGet(ByRef command) { - WinGet, output,% command[3], command[4], command[5], command[6], command[7] return output } WinSet(ByRef command) { - WinSet,% command[2], command[3], command[4] + title := command[4] + value := command[3] + + WinSet,% command[2], %value%, %title% } WinSetTitle(ByRef command) { From e68e84d5035557eb664c7e8be8c1b6cd85b827ed Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 15:10:52 -0800 Subject: [PATCH 128/588] fix some window commands --- ahk/templates/_daemon.ahk | 34 ++++++++++++++++++--------------- ahk/templates/daemon.ahk | 40 ++++++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index fecef5ed..49fe88f8 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -212,7 +212,7 @@ WinGetText(ByRef command) { } WinActivate(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinActivate, %title% } else { @@ -222,7 +222,7 @@ WinActivate(ByRef command) { } WinActivateBottom(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinActivateBottom, %title% } else { @@ -232,9 +232,9 @@ WinActivateBottom(ByRef command) { } WinClose(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { - WinClose, %title% + WinClose,% title } else { secondstowait = command[3] WinClose, %title%, %secondstowait% @@ -242,7 +242,7 @@ WinClose(ByRef command) { } WinHide(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinHide, %title% } else { @@ -252,7 +252,7 @@ WinHide(ByRef command) { } WinKill(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinKill, %title% } else { @@ -262,7 +262,7 @@ WinKill(ByRef command) { } WinMaximize(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinMaximize, %title% } else { @@ -272,7 +272,7 @@ WinMaximize(ByRef command) { } WinMinimize(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinMinimize, %title% } else { @@ -282,7 +282,7 @@ WinMinimize(ByRef command) { } WinRestore(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinRestore, %title% } else { @@ -292,7 +292,7 @@ WinRestore(ByRef command) { } WinShow(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinShow, %title% } else { @@ -302,7 +302,7 @@ WinShow(ByRef command) { } WinWait(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinWait, %title% } else { @@ -312,7 +312,7 @@ WinWait(ByRef command) { } WinWaitActive(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinWaitActive, %title% } else { @@ -322,7 +322,7 @@ WinWaitActive(ByRef command) { } WinWaitNotActive(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinWaitNotActive, %title% } else { @@ -332,7 +332,7 @@ WinWaitNotActive(ByRef command) { } WinWaitClose(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinWaitClose, %title% } else { @@ -396,7 +396,11 @@ FromMouse(ByRef command) { } WinGet(ByRef command) { - WinGet, output,% command[3], command[4], command[5], command[6], command[7] + title := command[4] + text := command[5] + extitle := command[6] + extext := command[7] + WinGet, output,% command[3], %title%, %text%, %extitle%, %extext% return output } diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 8f1637a6..b37fa400 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -220,7 +220,7 @@ WinGetText(ByRef command) { } WinActivate(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinActivate, %title% } else { @@ -230,7 +230,7 @@ WinActivate(ByRef command) { } WinActivateBottom(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinActivateBottom, %title% } else { @@ -240,9 +240,9 @@ WinActivateBottom(ByRef command) { } WinClose(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { - WinClose, %title% + WinClose,% title } else { secondstowait = command[3] WinClose, %title%, %secondstowait% @@ -250,7 +250,7 @@ WinClose(ByRef command) { } WinHide(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinHide, %title% } else { @@ -260,7 +260,7 @@ WinHide(ByRef command) { } WinKill(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinKill, %title% } else { @@ -270,7 +270,7 @@ WinKill(ByRef command) { } WinMaximize(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinMaximize, %title% } else { @@ -280,7 +280,7 @@ WinMaximize(ByRef command) { } WinMinimize(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinMinimize, %title% } else { @@ -290,7 +290,7 @@ WinMinimize(ByRef command) { } WinRestore(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinRestore, %title% } else { @@ -300,7 +300,7 @@ WinRestore(ByRef command) { } WinShow(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinShow, %title% } else { @@ -310,7 +310,7 @@ WinShow(ByRef command) { } WinWait(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinWait, %title% } else { @@ -320,7 +320,7 @@ WinWait(ByRef command) { } WinWaitActive(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinWaitActive, %title% } else { @@ -330,7 +330,7 @@ WinWaitActive(ByRef command) { } WinWaitNotActive(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinWaitNotActive, %title% } else { @@ -340,7 +340,7 @@ WinWaitNotActive(ByRef command) { } WinWaitClose(ByRef command) { - title = command[2] + title := command[2] if (command.Length() = 2) { WinWaitClose, %title% } else { @@ -404,13 +404,19 @@ FromMouse(ByRef command) { } WinGet(ByRef command) { - - WinGet, output,% command[3], command[4], command[5], command[6], command[7] + title := command[4] + text := command[5] + extitle := command[6] + extext := command[7] + WinGet, output,% command[3], %title%, %text%, %extitle%, %extext% return output } WinSet(ByRef command) { - WinSet,% command[2], command[3], command[4] + title := command[4] + value := command[3] + + WinSet,% command[2], %value%, %title% } WinSetTitle(ByRef command) { From 60b9a57389c7cd6801c1a7970770129f1863d723 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 15:22:36 -0800 Subject: [PATCH 129/588] fix always on top check --- ahk/templates/_daemon.ahk | 6 ++++-- ahk/templates/daemon.ahk | 6 ++++-- ahk/templates/daemon/win_is_always_on_top.ahk | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index 49fe88f8..791abb83 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -405,10 +405,11 @@ WinGet(ByRef command) { } WinSet(ByRef command) { + subcommand := command[2] title := command[4] value := command[3] - WinSet,% command[2], %value%, %title% + WinSet,%subcommand%,%value%,%title% } WinSetTitle(ByRef command) { @@ -417,7 +418,8 @@ WinSetTitle(ByRef command) { } WinIsAlwaysOnTop(ByRef command) { - WinGet, ExStyle, ExStyle, {{ title }} + title := command[2] + WinGet, ExStyle, ExStyle, %title% if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. return 1 else diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index b37fa400..d505cc6e 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -413,10 +413,11 @@ WinGet(ByRef command) { } WinSet(ByRef command) { + subcommand := command[2] title := command[4] value := command[3] - WinSet,% command[2], %value%, %title% + WinSet,%subcommand%,%value%,%title% } WinSetTitle(ByRef command) { @@ -425,7 +426,8 @@ WinSetTitle(ByRef command) { } WinIsAlwaysOnTop(ByRef command) { - WinGet, ExStyle, ExStyle, + title := command[2] + WinGet, ExStyle, ExStyle, %title% if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. return 1 else diff --git a/ahk/templates/daemon/win_is_always_on_top.ahk b/ahk/templates/daemon/win_is_always_on_top.ahk index 691c6c60..a3781a1e 100644 --- a/ahk/templates/daemon/win_is_always_on_top.ahk +++ b/ahk/templates/daemon/win_is_always_on_top.ahk @@ -1 +1 @@ -WinIsAlwaysOnTop \ No newline at end of file +WinIsAlwaysOnTop,{{ title }} \ No newline at end of file From 739e677c1003c74b840627948682ff4cc664806e Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sat, 9 Jan 2021 15:22:47 -0800 Subject: [PATCH 130/588] add window tests to daemon --- tests/unittests/test_daemon.py | 94 ++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index a8ddb719..5e82b91d 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -1,12 +1,15 @@ import asyncio +import subprocess import time import sys import os from unittest import IsolatedAsyncioTestCase + project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) sys.path.insert(0, project_root) from ahk.daemon import AHKDaemon +from ahk.window import AsyncWindow class TestMouseAsync(IsolatedAsyncioTestCase): @@ -21,3 +24,94 @@ async def test_mouse_move(self): x, y = await self.ahk.mouse_position await self.ahk.mouse_move(10, 10, relative=True) assert await self.ahk.mouse_position == (x+10, y+10) + + +class TestWindowAsync(IsolatedAsyncioTestCase): + win: AsyncWindow + async def asyncSetUp(self): + self.ahk = AHKDaemon() + await self.ahk.start() + 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_transparent(self): + self.assertEqual(await self.win.get_transparency(), 255) + + await self.win.set_transparency(220) + self.assertEqual(await self.win.get_transparency(), 220) + + self.win.transparent = 255 + await asyncio.sleep(0.5) + self.assertEqual(await self.win.transparent, 255) + + + async def test_pinned(self): + self.assertFalse(await self.win.always_on_top) + + await self.win.set_always_on_top(True) + self.assertTrue(await self.win.is_always_on_top()) + + self.win.always_on_top = False + await asyncio.sleep(1) + self.assertFalse(await self.win.always_on_top) + + async def test_close(self): + await self.win.close() + await asyncio.sleep(0.2) + self.assertFalse(await self.win.exists()) + self.assertFalse(await self.win.exist) + + async def test_show_hide(self): + await self.win.hide() + await asyncio.sleep(0.5) + self.assertFalse(await self.win.exist) + + await self.win.show() + await asyncio.sleep(0.5) + self.assertTrue(await self.win.exist) + + async def test_kill(self): + await self.win.kill() + await asyncio.sleep(0.5) + self.assertFalse(await self.win.exist) + + async def test_max_min(self): + self.assertTrue(await self.win.non_max_non_min) + self.assertFalse(await self.win.is_minmax()) + + await self.win.maximize() + await asyncio.sleep(1) + self.assertTrue(await self.win.maximized) + self.assertTrue(await self.win.is_maximized()) + + await self.win.minimize() + await asyncio.sleep(1) + self.assertTrue(await self.win.minimized) + self.assertTrue(await self.win.is_minimized()) + + await self.win.restore() + await asyncio.sleep(0.5) + self.assertTrue(await self.win.maximized) + self.assertTrue(await self.win.is_maximized()) +# + async def test_names(self): + self.assertEqual(await self.win.class_name, b'Notepad') + self.assertEqual(await self.win.get_class_name(), b'Notepad') + + self.assertEqual(await self.win.title, b'Untitled - Notepad') + self.assertEqual(await self.win.get_title(), b'Untitled - Notepad') + + self.assertEqual(await self.win.text, b'') + self.assertEqual(await self.win.get_text(), b'') + + async def test_title_setter(self): + starting_title = await self.win.title + await self.win.set_title("new title") + assert await self.win.get_title() != starting_title + + async def asyncTearDown(self): + self.ahk.stop() + self.p.terminate() + await asyncio.sleep(0.5) From 937be6e5c4848d3d6308069e2cf35d0babb1ac8e Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 12:14:33 -0800 Subject: [PATCH 131/588] fix trim block bug --- ahk/templates/screen/pixel_get_color.ahk | 2 +- ahk/templates/screen/pixel_search.ahk | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ahk/templates/screen/pixel_get_color.ahk b/ahk/templates/screen/pixel_get_color.ahk index 0e9fba2a..5f509852 100644 --- a/ahk/templates/screen/pixel_get_color.ahk +++ b/ahk/templates/screen/pixel_get_color.ahk @@ -1,7 +1,7 @@ {% extends "base.ahk" %} {% block body %} CoordMode, Pixel, {{ coord_mode }} -PixelGetColor, color,{{ x }},{{ y }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} +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 index fc29db8f..8a096c9e 100644 --- a/ahk/templates/screen/pixel_search.ahk +++ b/ahk/templates/screen/pixel_search.ahk @@ -1,7 +1,8 @@ {% extends "base.ahk" %} {% block body %} -CoordMode Pixel, {{ coord_mode }} +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 %} From bb4c34bb11705db431adf57861f97c6584cb0792 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 13:02:18 -0800 Subject: [PATCH 132/588] daemon screen commands --- ahk/screen.py | 4 +-- ahk/templates/_daemon.ahk | 33 +++++++++++++++---- ahk/templates/daemon.ahk | 33 +++++++++++++++---- ahk/templates/daemon/image_search.ahk | 2 +- tests/unittests/test_daemon.py | 47 ++++++++++++++++++++++++--- 5 files changed, 100 insertions(+), 19 deletions(-) diff --git a/ahk/screen.py b/ahk/screen.py index 1e56331a..317cd4db 100644 --- a/ahk/screen.py +++ b/ahk/screen.py @@ -37,7 +37,7 @@ def _image_search( if lower_bound: x2, y2 = lower_bound else: - x2, y2 = ('%A_ScreenWidth%', '%A_ScreenHeight%') + x2, y2 = ('A_ScreenWidth', 'A_ScreenHeight') script = self.render_template( 'screen/image_search.ahk', x1=x1, @@ -162,7 +162,7 @@ def _pixel_search( if lower_bound: x2, y2 = lower_bound else: - x2, y2 = ('%A_ScreenWidth%', '%A_ScreenHeight%') + x2, y2 = ('A_ScreenWidth', 'A_ScreenHeight') script = self.render_template( 'screen/pixel_search.ahk', diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index 791abb83..5e3f95ba 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -1,29 +1,50 @@ -#SingleInstance Force #NoEnv ImageSearch(ByRef command) { imagepath := command[8] - ImageSearch, xpos, ypos, command[4], command[5], command[6], command[7], %imagepath% + x1 := command[4] + y1 := command[5] + x2 := command[6] + y2 := command[7] + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } + ImageSearch, xpos, ypos,% x1,% y1,% x2,% y2, %imagepath% s .= Format("({}, {})", xpos, ypos) return s } PixelGetColor(ByRef command) { + x := command[3] + y := command[4] if (command.Length() = 4) { - PixelGetColor,color,command[3],command[4] + PixelGetColor,color,% x,% y } else { options := command[5] - PixelGetColor,color,command[3],command[4], %options% + PixelGetColor,color,% x,% y, %options% } return color } PixelSearch(ByRef command) { + x1 := command[4] + y1 := command[5] + x2 := command[6] + y2 := command[7] + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } if (command.Length() = 9) { - PixelSearch, xpos, ypos, command[4], command[5], command[6], command[7], command[8], command[9] + PixelSearch, xpos, ypos,% x1,% y1,% x2,% y2, command[8], command[9] } else { options := command[10] - PixelSearch, xpos, ypos, command[4], command[5], command[6], command[7], command[8], command[9], %options% + PixelSearch, xpos, ypos,% x1,% y1,% x2,% y2, command[8], command[9], %options% } s .= Format("({}, {})", xpos, ypos) return s diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index d505cc6e..0a34aae9 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1,29 +1,50 @@ -#SingleInstance Force #NoEnv ImageSearch(ByRef command) { imagepath := command[8] - ImageSearch, xpos, ypos, command[4], command[5], command[6], command[7], %imagepath% + x1 := command[4] + y1 := command[5] + x2 := command[6] + y2 := command[7] + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } + ImageSearch, xpos, ypos,% x1,% y1,% x2,% y2, %imagepath% s .= Format("({}, {})", xpos, ypos) return s } PixelGetColor(ByRef command) { + x := command[3] + y := command[4] if (command.Length() = 4) { - PixelGetColor,color,command[3],command[4] + PixelGetColor,color,% x,% y } else { options := command[5] - PixelGetColor,color,command[3],command[4], %options% + PixelGetColor,color,% x,% y, %options% } return color } PixelSearch(ByRef command) { + x1 := command[4] + y1 := command[5] + x2 := command[6] + y2 := command[7] + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } if (command.Length() = 9) { - PixelSearch, xpos, ypos, command[4], command[5], command[6], command[7], command[8], command[9] + PixelSearch, xpos, ypos,% x1,% y1,% x2,% y2, command[8], command[9] } else { options := command[10] - PixelSearch, xpos, ypos, command[4], command[5], command[6], command[7], command[8], command[9], %options% + PixelSearch, xpos, ypos,% x1,% y1,% x2,% y2, command[8], command[9], %options% } s .= Format("({}, {})", xpos, ypos) return s diff --git a/ahk/templates/daemon/image_search.ahk b/ahk/templates/daemon/image_search.ahk index 826f2065..03debc98 100644 --- a/ahk/templates/daemon/image_search.ahk +++ b/ahk/templates/daemon/image_search.ahk @@ -1,2 +1,2 @@ CoordMode, Pixel, {{ coord_mode }} -ImageSearch, xpos, ypos, {{ x1 }}, {{ y1 }}, {{ x2 }}, {{ y2 }}, {% if options %}{% for option in options %}*{{ option }} {% endfor %}{% endif %}{{ image_path }} \ No newline at end of file +ImageSearch,xpos,ypos,{{ x1 }},{{ y1 }},{{ x2 }},{{ y2 }},{% if options %}{% for option in options %}*{{ option }} {% endfor %}{% endif %}{{ image_path }} \ No newline at end of file diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index 5e82b91d..b19128a4 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -4,15 +4,15 @@ import sys import os from unittest import IsolatedAsyncioTestCase - +from itertools import product project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) sys.path.insert(0, project_root) from ahk.daemon import AHKDaemon from ahk.window import AsyncWindow +from PIL import Image - -class TestMouseAsync(IsolatedAsyncioTestCase): +class TestMouseDaemon(IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: self.ahk = AHKDaemon() await self.ahk.start() @@ -26,7 +26,7 @@ async def test_mouse_move(self): assert await self.ahk.mouse_position == (x+10, y+10) -class TestWindowAsync(IsolatedAsyncioTestCase): +class TestWindowDaemon(IsolatedAsyncioTestCase): win: AsyncWindow async def asyncSetUp(self): self.ahk = AHKDaemon() @@ -115,3 +115,42 @@ async def asyncTearDown(self): self.ahk.stop() self.p.terminate() await asyncio.sleep(0.5) + + +class TestScreenDaemon(IsolatedAsyncioTestCase): + async def asyncSetUp(self): + """ + Record all open windows + :return: + """ + self.ahk = AHKDaemon() + await self.ahk.start() + self.before_windows = await 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) + + async def asyncTearDown(self): + for win in await self.ahk.windows(): + if win not in self.before_windows: + await win.close() + break + self.ahk.stop() + + async def test_pixel_search(self): + result = await self.ahk.pixel_search(0xFF0000) + self.assertIsNotNone(result) + + async def test_image_search(self): + self.im.save('testimage.png') + position = await self.ahk.image_search('testimage.png') + self.assertIsNotNone(position) + + 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) From 3657675f575fe72177ed74b39b59f46c88c883a1 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 13:32:52 -0800 Subject: [PATCH 133/588] add winget tests --- tests/unittests/test_daemon.py | 42 +++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index b19128a4..bf9e32ca 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -9,7 +9,7 @@ project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) sys.path.insert(0, project_root) from ahk.daemon import AHKDaemon -from ahk.window import AsyncWindow +from ahk.window import AsyncWindow, WindowNotFoundError from PIL import Image class TestMouseDaemon(IsolatedAsyncioTestCase): @@ -154,3 +154,43 @@ async def test_pixel_get_color(self): result = await self.ahk.pixel_get_color(x, y) self.assertIsNotNone(result) self.assertEqual(int(result, 16), 0xFF0000) + + +class TestWinGetDaemon(IsolatedAsyncioTestCase): + async def asyncSetUp(self): + self.ahk = AHKDaemon() + await self.ahk.start() + 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): + self.p.terminate() + await asyncio.sleep(0.5) + self.ahk.stop() + + + async def test_get_calculator(self): + assert await self.win.position + + + async def test_win_close(self): + await self.win.close() + try: + win = await self.ahk.win_get(title='Untitled - Notepad') + await win.position + except WindowNotFoundError as e: + pass + else: + raise AssertionError("Expected WindowNotFoundError") + + async def test_find_window_func(self): + async def func(win): + return b'Untitled' in await win.title + assert self.win == await self.ahk.find_window(func=func) + + async def test_getattr_window_subcommand(self): + assert isinstance(await self.win.pid, str) + + From 2139787e40cd43e19f1dc918f89382e71cdeb7ae Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 13:33:02 -0800 Subject: [PATCH 134/588] fix window send --- ahk/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/window.py b/ahk/window.py index 9b162a20..2c6e9256 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -602,7 +602,7 @@ def _send(self, keys, delay=10, raw=False, blocking=False, escape=False, press_d ) return script - def send(self, keys, delay=10, raw=False, blocking=False, escape=False, press_duration=-1): + def send(self, keys, delay=10, raw=False, blocking=True, escape=False, press_duration=-1): """ Send keystrokes directly to the window. Uses ControlSend From eb36ed3aaecca84f76e35743ef70aa1a4bffb664 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 13:33:14 -0800 Subject: [PATCH 135/588] fix keyboard async tests --- tests/unittests/test_keyboard_async.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/unittests/test_keyboard_async.py b/tests/unittests/test_keyboard_async.py index 476d151c..73e73836 100644 --- a/tests/unittests/test_keyboard_async.py +++ b/tests/unittests/test_keyboard_async.py @@ -16,7 +16,7 @@ from ahk.keys import ALT, CTRL, KEYS -class TestKeyboardAsync(TestCase): +class TestKeyboardAsync(IsolatedAsyncioTestCase): def setUp(self): """ Record all open windows @@ -28,10 +28,9 @@ def setUp(self): self.p = subprocess.Popen("notepad") time.sleep(1) - def tearDown(self): + async def asyncTearDown(self): self.p.terminate() - time.sleep(0.2) - asyncio.run(asyncio.sleep(0.2)) + await asyncio.sleep(0.5) async def test_window_send(self): notepad = await self.ahk.find_window(title=b"Untitled - Notepad") From 8a6155308248b30ace5f2b913b6708d65647e966 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 13:44:40 -0800 Subject: [PATCH 136/588] more daemon tests --- tests/unittests/test_daemon.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index bf9e32ca..dfe92446 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -194,3 +194,43 @@ async def test_getattr_window_subcommand(self): assert isinstance(await self.win.pid, str) + +class TestKeyboardDaemon(IsolatedAsyncioTestCase): + async def asyncSetUp(self): + """ + Record all open windows + :return: + """ + self.ahk = AHKDaemon() + await self.ahk.start() + self.p = subprocess.Popen("notepad") + time.sleep(1) + + async def asyncTearDown(self): + self.p.terminate() + self.ahk.stop() + await asyncio.sleep(0.5) + + async def test_window_send(self): + notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + await notepad.send("hello world") + await asyncio.sleep(1) + self.assertIn(b'hello world', await notepad.get_text()) + + async def test_send(self): + notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + await notepad.activate() + await self.ahk.send('hello world') + self.assertIn(b'hello world', await notepad.get_text()) + + async def test_send_input(self): + notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + await self.ahk.send_input("Hello World") + await asyncio.sleep(0.5) + assert b"Hello World" in await notepad.get_text() + + async def test_type(self): + notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + await notepad.activate() + await self.ahk.type("Hello, World!") + assert b"Hello, World!" in await notepad.get_text() From 10b0e7224f8a568143ffdb1b85c198e2ec790f2a Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 13:55:38 -0800 Subject: [PATCH 137/588] remove debugging prints --- ahk/daemon.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ahk/daemon.py b/ahk/daemon.py index 2050c9ef..975f268a 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -72,7 +72,6 @@ def render_template(self, template_name, directives=None, blocking=True, **kwarg name = template_name.split('/')[-1] if name in self._template_overrides: template_name = f'daemon/{name}' - print(template_name) blocking = False directives = None kwargs['_daemon'] = True @@ -85,16 +84,14 @@ async def a_run_script(self, script_text: str, decode=True, blocking=True, **run async with self.run_lock: for line in script_text.split('\n'): line = line.strip() - if not line: + if not line or line.startswith("FileAppend"): continue - print(line) self.queue.put_nowait(line.encode('utf-8')) await self.queue.join() res = [] while not self.result_queue.empty(): res.append(self.result_queue.get_nowait()) res = b'\n'.join(i for i in res if i) - print(res) if decode: return res.decode('utf-8') return res From 393247a3851410126a7b46117e081a6c8f9ad604 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 14:07:02 -0800 Subject: [PATCH 138/588] correct warnings and remove unneeded active_window property --- ahk/window.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ahk/window.py b/ahk/window.py index 2c6e9256..c8668f01 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -897,7 +897,6 @@ async def is_maximized(self): @property async def non_max_non_min(self): - warnings.warn('property blocks event loop. use is_minmax() instead') return await self.get("MinMax") == self.NON_MIN_NON_MAX async def is_minmax(self): @@ -905,7 +904,6 @@ async def is_minmax(self): @property async def transparent(self) -> int: - warnings.warn('property blocks event loop. use get_transparency() instead') result = await self.get("Transparent") if result: return int(result) @@ -963,11 +961,6 @@ async def win_get(self, *args, **kwargs): ahk_id = await self.a_run_script(script) return AsyncWindow(engine=self, ahk_id=ahk_id, encoding=encoding) - @property - def active_window(self): - warnings.warn("active_window property blocks event loop. use get_active_window() instead") - return self.win_get(title='A') - async def _all_window_ids(self): script = self._all_window_ids_() result = await self.a_run_script(script) From 5c3108101b8bc5e3774265159e24749ebc186783 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 15:42:51 -0800 Subject: [PATCH 139/588] add synchronous daemon --- ahk/daemon.py | 128 +++++++++++++++++- .../{test_daemon.py => test_daemon_async.py} | 12 +- 2 files changed, 132 insertions(+), 8 deletions(-) rename tests/unittests/{test_daemon.py => test_daemon_async.py} (97%) diff --git a/ahk/daemon.py b/ahk/daemon.py index 975f268a..dbc5bc79 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -1,12 +1,135 @@ import os import asyncio -from ahk.autohotkey import AsyncAHK +from ahk.autohotkey import AsyncAHK, AHK +import subprocess +import threading +import queue +import atexit def escape(s): s = s.replace('\n', '`n') return s -class AHKDaemon(AsyncAHK): +class STOP: + """A sentinel value""" + ... + +class AHKDaemon(AHK): + proc: subprocess.Popen + _template_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates') + _template = os.path.join(_template_path, 'daemon.ahk') + _template_overrides = os.listdir(f'{_template_path}/daemon') + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.queue = queue.Queue() + self.result_queue = queue.Queue() + self.proc: asyncio.subprocess.Process + self.proc = None + self.thread = None + self._is_running = False + self.run_lock = threading.Lock() + template = self.env.get_template('_daemon.ahk') + with open(self._template, 'w') as f: + f.write(template.render()) + + def _run(self): + if self._is_running: + raise RuntimeError("Already running") + self._is_running = True + runargs = [self.executable_path, self._template] + proc = subprocess.Popen(runargs, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + self.proc = proc + atexit.register(self.proc.terminate) + + def worker(self): + while True: + command = self.queue.get() + if command is STOP: + break + self.proc.stdin.write(command + b'\n') + self.proc.stdin.flush() + res = self.proc.stdout.readline() + self.result_queue.put_nowait(res[:-1]) + self.queue.task_done() + + def _start(self): + try: + self.thread = threading.Thread(target=self.worker, daemon=True) + self.thread.start() + yield + finally: + if self.proc is not None: + self.proc.kill() + + def stop(self): + if self._is_running: + if hasattr(self, '_gen') and self._gen is not None: + try: + next(self._gen) + except StopIteration: + pass + self.queue.put_nowait(STOP) + if self.thread is not None: + self.thread.join() + self._is_running = False + + def start(self): + self._gen = self._start() + self._gen.send(None) + self._run() + + def render_template(self, template_name, directives=None, blocking=True, **kwargs): + name = template_name.split('/')[-1] + if name in self._template_overrides: + template_name = f'daemon/{name}' + blocking = False + directives = None + kwargs['_daemon'] = True + return super().render_template(template_name, directives=directives, blocking=blocking, **kwargs) + + def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): + if not self._is_running: + raise RuntimeError("Not running! Must call .run() first!") + script_text = script_text.replace('#NoEnv', '', 1) + with self.run_lock: + for line in script_text.split('\n'): + line = line.strip() + if not line or line.startswith("FileAppend"): + continue + self.queue.put_nowait(line.encode('utf-8')) + self.queue.join() + res = [] + while not self.result_queue.empty(): + res.append(self.result_queue.get_nowait()) + res = b'\n'.join(i for i in res if i) + if decode: + return res.decode('utf-8') + return res + + def type(self, s, *args, **kwargs): + kwargs['raw'] = True + s = escape(s) + self.send(s, *args, **kwargs) + + def show_tooltip(self, text: str, second=None, x="", y="", id="", blocking=True): + return super().show_tooltip(text, second=second, x=x, y=y, id=id, blocking=blocking) + + def hide_tooltip(self, id): + return super().show_tooltip(text='', second='', x='', y='', id=id) + + def hide_traytip(self): + self.run_script("HideTrayTip") + + @staticmethod + def escape_sequence_replace(s): + s = escape(s) + return s + + +class AsyncAHKDaemon(AsyncAHK): proc: asyncio.subprocess.Process _template_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates') _template = os.path.join(_template_path, 'daemon.ahk') @@ -62,6 +185,7 @@ def stop(self): next(self._gen) except StopIteration: pass + self._is_running = False async def start(self): self._gen = self._start() diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon_async.py similarity index 97% rename from tests/unittests/test_daemon.py rename to tests/unittests/test_daemon_async.py index dfe92446..607b7061 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon_async.py @@ -8,13 +8,13 @@ project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) sys.path.insert(0, project_root) -from ahk.daemon import AHKDaemon +from ahk.daemon import AsyncAHKDaemon from ahk.window import AsyncWindow, WindowNotFoundError from PIL import Image class TestMouseDaemon(IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: - self.ahk = AHKDaemon() + self.ahk = AsyncAHKDaemon() await self.ahk.start() def tearDown(self) -> None: @@ -29,7 +29,7 @@ async def test_mouse_move(self): class TestWindowDaemon(IsolatedAsyncioTestCase): win: AsyncWindow async def asyncSetUp(self): - self.ahk = AHKDaemon() + self.ahk = AsyncAHKDaemon() await self.ahk.start() self.p = subprocess.Popen('notepad') time.sleep(1) @@ -123,7 +123,7 @@ async def asyncSetUp(self): Record all open windows :return: """ - self.ahk = AHKDaemon() + self.ahk = AsyncAHKDaemon() await self.ahk.start() self.before_windows = await self.ahk.windows() im = Image.new('RGB', (20, 20)) @@ -158,7 +158,7 @@ async def test_pixel_get_color(self): class TestWinGetDaemon(IsolatedAsyncioTestCase): async def asyncSetUp(self): - self.ahk = AHKDaemon() + self.ahk = AsyncAHKDaemon() await self.ahk.start() self.p = subprocess.Popen('notepad') time.sleep(1) @@ -201,7 +201,7 @@ async def asyncSetUp(self): Record all open windows :return: """ - self.ahk = AHKDaemon() + self.ahk = AsyncAHKDaemon() await self.ahk.start() self.p = subprocess.Popen("notepad") time.sleep(1) From de0bc001957af46407be417b58198daa4b21b12b Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 16:31:16 -0800 Subject: [PATCH 140/588] make window class compatible with Daemon --- ahk/window.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ahk/window.py b/ahk/window.py index c8668f01..2a3c93fe 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -153,10 +153,10 @@ def from_pid(cls, engine: ScriptEngine, pid, **kwargs): ahk_id = engine.run_script(script) return cls(engine=engine, ahk_id=ahk_id, **kwargs) - def __getattr__(self, attr): - if attr.lower() in self._get_subcommands: - return self.get(attr) - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'") + # def __getattr__(self, attr): + # if attr.lower() in self._get_subcommands: + # return self.get(attr) + # raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'") def _get(self, subcommand): sub = self._get_subcommands.get(subcommand) @@ -292,7 +292,12 @@ def _base_get_method(self, command): script = self._base_get_method_(command) result = self.engine.run_script(script, decode=False) if self.encoding: - return result.stdout.decode(encoding=self.encoding) + if isinstance(result, bytes): + return result.decode(encoding=self.encoding) + else: + return result.stdout.decode(encoding=self.encoding) + if isinstance(result, bytes): + return result return result.stdout @property From e3930884fcbd3f6fca62ce46915e12bb24223869 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 16:31:53 -0800 Subject: [PATCH 141/588] add warning, fix error message --- ahk/daemon.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ahk/daemon.py b/ahk/daemon.py index dbc5bc79..b44d8150 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -1,5 +1,6 @@ import os import asyncio +import warnings from ahk.autohotkey import AsyncAHK, AHK import subprocess import threading @@ -91,8 +92,10 @@ def render_template(self, template_name, directives=None, blocking=True, **kwarg return super().render_template(template_name, directives=directives, blocking=blocking, **kwargs) def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): + if not blocking: + warnings.warn("blocking=False in daemon mode is not supported", stacklevel=3) if not self._is_running: - raise RuntimeError("Not running! Must call .run() first!") + raise RuntimeError("Not running! Must call .start() first!") script_text = script_text.replace('#NoEnv', '', 1) with self.run_lock: for line in script_text.split('\n'): @@ -203,7 +206,7 @@ def render_template(self, template_name, directives=None, blocking=True, **kwarg async def a_run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): if not self._is_running: - raise RuntimeError("Not running! Must call .run() first!") + raise RuntimeError("Not running! Must await .start() first!") script_text = script_text.replace('#NoEnv', '', 1) async with self.run_lock: for line in script_text.split('\n'): From cbbe48c10fffa4f7f9b924943b7736ee019c0ad4 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 16:51:17 -0800 Subject: [PATCH 142/588] fix daemon keystate --- ahk/templates/daemon/key_state.ahk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/templates/daemon/key_state.ahk b/ahk/templates/daemon/key_state.ahk index da7fb473..8dc70dd3 100644 --- a/ahk/templates/daemon/key_state.ahk +++ b/ahk/templates/daemon/key_state.ahk @@ -1 +1 @@ -AHKKeyState, {{ key_name }}{% if mode %}, {{ mode }}{% endif %} \ No newline at end of file +AHKKeyState,{{ key_name }}{% if mode %},{{ mode }}{% endif %} \ No newline at end of file From 08be9a0e3eaa884004152c0b159ce8c27d3e86be Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 16:52:37 -0800 Subject: [PATCH 143/588] sync daemon tests --- tests/unittests/test_daemon.py | 127 +++++++++++++++++++++++++++ tests/unittests/test_daemon_async.py | 10 +-- 2 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 tests/unittests/test_daemon.py diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py new file mode 100644 index 00000000..158e1363 --- /dev/null +++ b/tests/unittests/test_daemon.py @@ -0,0 +1,127 @@ +import asyncio +import subprocess +import time +import sys +import os +from unittest import TestCase +from itertools import product + + +project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) +sys.path.insert(0, project_root) +from ahk.daemon import AHKDaemon +from ahk.window import Window, WindowNotFoundError +from PIL import Image +from ahk.keys import KEYS + + + +class TestKeyboardDaemon(TestCase): + def setUp(self): + """ + Record all open windows + :return: + """ + self.ahk = AHKDaemon() + self.ahk.start() + 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.ahk.stop() + 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_input(self): + self.notepad.activate() + self.ahk.send_input("Hello World") + time.sleep(0.5) + 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 test_type_escapes_equals(self): + """ + https://github.com/spyoungtech/ahk/issues/96 + """ + self.notepad.activate() + self.ahk.type("=foo") + assert b"=foo" in self.notepad.text + + def test_sendraw_equals(self): + """ + https://github.com/spyoungtech/ahk/issues/96 + """ + self.notepad.activate() + self.ahk.send_raw("=foo") + assert b"=foo" in self.notepad.text + + def test_set_capslock_state(self): + self.ahk.set_capslock_state("on") + assert self.ahk.key_state("CapsLock", "T") + +class TestMouseDaemon(TestCase): + def setUp(self) -> None: + self.ahk = AHKDaemon() + self.ahk.start() + + def test_mouse_move(self): + x, y = self.ahk.mouse_position + self.ahk.mouse_move(10, 10, relative=True) + assert self.ahk.mouse_position == (x+10, y+10) + + def tearDown(self) -> None: + self.ahk.stop() + +class TestScreen(TestCase): + def setUp(self): + """ + Record all open windows + :return: + """ + self.ahk = AHKDaemon() + self.ahk.start() + 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 + self.ahk.stop() + + 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_daemon_async.py b/tests/unittests/test_daemon_async.py index 607b7061..c40289b6 100644 --- a/tests/unittests/test_daemon_async.py +++ b/tests/unittests/test_daemon_async.py @@ -12,7 +12,7 @@ from ahk.window import AsyncWindow, WindowNotFoundError from PIL import Image -class TestMouseDaemon(IsolatedAsyncioTestCase): +class TestMouseDaemonAsync(IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: self.ahk = AsyncAHKDaemon() await self.ahk.start() @@ -26,7 +26,7 @@ async def test_mouse_move(self): assert await self.ahk.mouse_position == (x+10, y+10) -class TestWindowDaemon(IsolatedAsyncioTestCase): +class TestWindowDaemonAsync(IsolatedAsyncioTestCase): win: AsyncWindow async def asyncSetUp(self): self.ahk = AsyncAHKDaemon() @@ -117,7 +117,7 @@ async def asyncTearDown(self): await asyncio.sleep(0.5) -class TestScreenDaemon(IsolatedAsyncioTestCase): +class TestScreenDaemonAsync(IsolatedAsyncioTestCase): async def asyncSetUp(self): """ Record all open windows @@ -156,7 +156,7 @@ async def test_pixel_get_color(self): self.assertEqual(int(result, 16), 0xFF0000) -class TestWinGetDaemon(IsolatedAsyncioTestCase): +class TestWinGetDaemonAsync(IsolatedAsyncioTestCase): async def asyncSetUp(self): self.ahk = AsyncAHKDaemon() await self.ahk.start() @@ -195,7 +195,7 @@ async def test_getattr_window_subcommand(self): -class TestKeyboardDaemon(IsolatedAsyncioTestCase): +class TestKeyboardDaemonAsync(IsolatedAsyncioTestCase): async def asyncSetUp(self): """ Record all open windows From 676383b149e9ce3e3cd3df3ebd22d3b90f5158ce Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 17:01:06 -0800 Subject: [PATCH 144/588] replace getattr --- ahk/window.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ahk/window.py b/ahk/window.py index 2a3c93fe..f960d0b7 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -836,11 +836,11 @@ async def from_pid(cls, engine: ScriptEngine, pid, **kwargs): ahk_id = await engine.a_run_script(script) return cls(engine=engine, ahk_id=ahk_id, **kwargs) - # def __getattr__(self, item): - # if item in self._get_subcommands: - # raise AttributeError(f"Unaccessable Attribute. Use get({item}) instead") - # raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'") - # + def __getattr__(self, item): + if item in self._get_subcommands: + raise AttributeError(f"Unaccessable Attribute. Use get({item}) instead") + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'") + async def get_pos(self, info=None): script = self._get_pos(info) resp = await self.engine.a_run_script(script) From 5cf55816f805db6c280b4fe6bc368572eb995024 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 17:41:45 -0800 Subject: [PATCH 145/588] restore the correct getattr --- ahk/window.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ahk/window.py b/ahk/window.py index f960d0b7..9b6f1f2b 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -153,10 +153,10 @@ def from_pid(cls, engine: ScriptEngine, pid, **kwargs): ahk_id = engine.run_script(script) return cls(engine=engine, ahk_id=ahk_id, **kwargs) - # def __getattr__(self, attr): - # if attr.lower() in self._get_subcommands: - # return self.get(attr) - # raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'") + def __getattr__(self, attr): + if attr.lower() in self._get_subcommands: + return self.get(attr) + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'") def _get(self, subcommand): sub = self._get_subcommands.get(subcommand) @@ -836,11 +836,11 @@ async def from_pid(cls, engine: ScriptEngine, pid, **kwargs): ahk_id = await engine.a_run_script(script) return cls(engine=engine, ahk_id=ahk_id, **kwargs) - def __getattr__(self, item): - if item in self._get_subcommands: - raise AttributeError(f"Unaccessable Attribute. Use get({item}) instead") - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'") - + # def __getattr__(self, item): + # if item in self._get_subcommands: + # raise AttributeError(f"Unaccessable Attribute. Use get({item}) instead") + # raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'") + # async def get_pos(self, info=None): script = self._get_pos(info) resp = await self.engine.a_run_script(script) From 438a3ec16de75ae2e5417340263e0151324a424c Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 17:54:45 -0800 Subject: [PATCH 146/588] unset capslock after tests --- tests/unittests/test_daemon.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index 158e1363..20d1dd10 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -30,6 +30,8 @@ def setUp(self): self.notepad = self.ahk.find_window(title=b"Untitled - Notepad") def tearDown(self): + if self.ahk.key_state("CapsLock", "T"): + self.ahk.set_capslock_state("off") self.ahk.stop() self.p.terminate() time.sleep(0.2) From ddf57bd10ce209a88bb55a030ab759d6d3a19574 Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 18:10:09 -0800 Subject: [PATCH 147/588] set capslock off after test --- tests/unittests/test_daemon.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index 20d1dd10..3254484f 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -30,8 +30,7 @@ def setUp(self): self.notepad = self.ahk.find_window(title=b"Untitled - Notepad") def tearDown(self): - if self.ahk.key_state("CapsLock", "T"): - self.ahk.set_capslock_state("off") + self.ahk.set_capslock_state('off') self.ahk.stop() self.p.terminate() time.sleep(0.2) From 47534cd995fce2dc3f049297bf8591fb897d2c0f Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 18:34:38 -0800 Subject: [PATCH 148/588] fix assignment operators --- ahk/templates/_daemon.ahk | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index 5e3f95ba..8a924d70 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -237,7 +237,7 @@ WinActivate(ByRef command) { if (command.Length() = 2) { WinActivate, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinActivate, %title%, %secondstowait% } } @@ -247,7 +247,7 @@ WinActivateBottom(ByRef command) { if (command.Length() = 2) { WinActivateBottom, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinActivateBottom, %title%, %secondstowait% } } @@ -257,7 +257,7 @@ WinClose(ByRef command) { if (command.Length() = 2) { WinClose,% title } else { - secondstowait = command[3] + secondstowait := command[3] WinClose, %title%, %secondstowait% } } @@ -267,7 +267,7 @@ WinHide(ByRef command) { if (command.Length() = 2) { WinHide, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinHide, %title%, %secondstowait% } } @@ -277,7 +277,7 @@ WinKill(ByRef command) { if (command.Length() = 2) { WinKill, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinKill, %title%, %secondstowait% } } @@ -287,7 +287,7 @@ WinMaximize(ByRef command) { if (command.Length() = 2) { WinMaximize, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinMaximize, %title%, %secondstowait% } } @@ -297,7 +297,7 @@ WinMinimize(ByRef command) { if (command.Length() = 2) { WinMinimize, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinMinimize, %title%, %secondstowait% } } @@ -307,7 +307,7 @@ WinRestore(ByRef command) { if (command.Length() = 2) { WinRestore, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinRestore, %title%, %secondstowait% } } @@ -317,7 +317,7 @@ WinShow(ByRef command) { if (command.Length() = 2) { WinShow, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinShow, %title%, %secondstowait% } } @@ -337,7 +337,7 @@ WinWaitActive(ByRef command) { if (command.Length() = 2) { WinWaitActive, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinWaitActive, %title%, %secondstowait% } } @@ -357,7 +357,7 @@ WinWaitClose(ByRef command) { if (command.Length() = 2) { WinWaitClose, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinWaitClose, %title%, %secondstowait% } } From 1d6f73eb9a4cef5cd6e456bae7f055ced85556fc Mon Sep 17 00:00:00 2001 From: Spencer Young Date: Sun, 10 Jan 2021 20:09:17 -0800 Subject: [PATCH 149/588] remove spacing from tooltip/traytip files --- ahk/templates/daemon/tooltip.ahk | 2 +- ahk/templates/daemon/traytip.ahk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/templates/daemon/tooltip.ahk b/ahk/templates/daemon/tooltip.ahk index bfc712cd..0858a880 100644 --- a/ahk/templates/daemon/tooltip.ahk +++ b/ahk/templates/daemon/tooltip.ahk @@ -1 +1 @@ -ToolTip, {{ text }}, {{ x }}, {{ y }}, {{ id }} \ No newline at end of file +ToolTip, {{ text }},{{ x }},{{ y }},{{ id }} \ No newline at end of file diff --git a/ahk/templates/daemon/traytip.ahk b/ahk/templates/daemon/traytip.ahk index ec907b92..0e6c53a1 100644 --- a/ahk/templates/daemon/traytip.ahk +++ b/ahk/templates/daemon/traytip.ahk @@ -1 +1 @@ -TrayTip {{ title }}, {{ text }}, {{ second }}, {{ option }} \ No newline at end of file +TrayTip,{{ title }},{{ text }},{{ second }},{{ option }} \ No newline at end of file From f2d84aec644c1ea83bda33e68bb91f6847006117 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 11 Aug 2021 10:22:32 -0700 Subject: [PATCH 150/588] add tests for multiline response --- tests/unittests/test_daemon.py | 11 +++++++++++ tests/unittests/test_daemon_async.py | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index 3254484f..6380262c 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -76,6 +76,17 @@ def test_set_capslock_state(self): self.ahk.set_capslock_state("on") assert self.ahk.key_state("CapsLock", "T") + def test_multi_line_response(self): + """ + Test that responses with multi-line strings are not truncated + Not really a 'keyboard' test, but whatever + """ + self.notepad.activate() + self.ahk.type('Hello\nWorld!') + assert b'Hello' in self.notepad.text + assert b'World!' in self.notepad.text + + class TestMouseDaemon(TestCase): def setUp(self) -> None: self.ahk = AHKDaemon() diff --git a/tests/unittests/test_daemon_async.py b/tests/unittests/test_daemon_async.py index c40289b6..688d82db 100644 --- a/tests/unittests/test_daemon_async.py +++ b/tests/unittests/test_daemon_async.py @@ -234,3 +234,16 @@ async def test_type(self): await notepad.activate() await self.ahk.type("Hello, World!") assert b"Hello, World!" in await notepad.get_text() + + async def test_multi_line_response(self): + """ + Test that responses with multi-line strings are not truncated + Not really a 'keyboard' test, but whatever + """ + notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + await notepad.activate() + await self.ahk.type('Hello\nWorld!') + text = await notepad.get_text() + assert b'Hello' in text + assert b'World!' in text + From f2ae65109401739176c6f37883cd43a69fd17aa8 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 11 Aug 2021 12:04:29 -0700 Subject: [PATCH 151/588] new protocol for daemon response to handle multiline response --- ahk/daemon.py | 10 +++++++-- ahk/templates/_daemon.ahk | 9 +++++++- ahk/templates/daemon.ahk | 31 +++++++++++++++++----------- tests/unittests/test_daemon.py | 2 +- tests/unittests/test_daemon_async.py | 2 +- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/ahk/daemon.py b/ahk/daemon.py index b44d8150..3141e16d 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -52,7 +52,8 @@ def worker(self): break self.proc.stdin.write(command + b'\n') self.proc.stdin.flush() - res = self.proc.stdout.readline() + num_lines = int(self.proc.stdout.readline().strip()) + res = b''.join(self.proc.stdout.readline() for _ in range(num_lines + 1)) self.result_queue.put_nowait(res[:-1]) self.queue.task_done() @@ -170,7 +171,12 @@ async def worker(self): command = await self.queue.get() self.proc.stdin.write(command + b'\n') await self.proc.stdin.drain() - res = await self.proc.stdout.readline() + num_lines = int(await self.proc.stdout.readline()) + lines = [] + for _ in range(num_lines + 1): + line = await self.proc.stdout.readline() + lines.append(line) + res = b''.join(lines) self.result_queue.put_nowait(res[:-1]) self.queue.task_done() diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index 8a924d70..f34ef048 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -495,7 +495,12 @@ AHKWinGetPos(ByRef command) { return s } - +CountNewlines(ByRef s) { + newline := "`n" + StringReplace, s, s, %newline%, %newline%, UseErrorLevel + count := ErrorLevel + return count +} stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] @@ -504,6 +509,8 @@ Loop { commandArray := StrSplit(query, ",") func := commandArray[1] response := %func%(commandArray) + newline_count := CountNewlines(response) + FileAppend, %newline_count%`n, * FileAppend, %response%`n, * } diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 0a34aae9..1cc23827 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -245,7 +245,7 @@ WinActivate(ByRef command) { if (command.Length() = 2) { WinActivate, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinActivate, %title%, %secondstowait% } } @@ -255,7 +255,7 @@ WinActivateBottom(ByRef command) { if (command.Length() = 2) { WinActivateBottom, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinActivateBottom, %title%, %secondstowait% } } @@ -265,7 +265,7 @@ WinClose(ByRef command) { if (command.Length() = 2) { WinClose,% title } else { - secondstowait = command[3] + secondstowait := command[3] WinClose, %title%, %secondstowait% } } @@ -275,7 +275,7 @@ WinHide(ByRef command) { if (command.Length() = 2) { WinHide, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinHide, %title%, %secondstowait% } } @@ -285,7 +285,7 @@ WinKill(ByRef command) { if (command.Length() = 2) { WinKill, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinKill, %title%, %secondstowait% } } @@ -295,7 +295,7 @@ WinMaximize(ByRef command) { if (command.Length() = 2) { WinMaximize, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinMaximize, %title%, %secondstowait% } } @@ -305,7 +305,7 @@ WinMinimize(ByRef command) { if (command.Length() = 2) { WinMinimize, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinMinimize, %title%, %secondstowait% } } @@ -315,7 +315,7 @@ WinRestore(ByRef command) { if (command.Length() = 2) { WinRestore, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinRestore, %title%, %secondstowait% } } @@ -325,7 +325,7 @@ WinShow(ByRef command) { if (command.Length() = 2) { WinShow, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinShow, %title%, %secondstowait% } } @@ -345,7 +345,7 @@ WinWaitActive(ByRef command) { if (command.Length() = 2) { WinWaitActive, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinWaitActive, %title%, %secondstowait% } } @@ -365,7 +365,7 @@ WinWaitClose(ByRef command) { if (command.Length() = 2) { WinWaitClose, %title% } else { - secondstowait = command[3] + secondstowait := command[3] WinWaitClose, %title%, %secondstowait% } } @@ -503,7 +503,12 @@ AHKWinGetPos(ByRef command) { return s } - +CountNewlines(ByRef s) { + newline := "`n" + StringReplace, s, s, %newline%, %newline%, UseErrorLevel + count := ErrorLevel + return count +} stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] @@ -512,5 +517,7 @@ Loop { commandArray := StrSplit(query, ",") func := commandArray[1] response := %func%(commandArray) + newline_count := CountNewlines(response) + FileAppend, %newline_count%`n, * FileAppend, %response%`n, * } diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index 6380262c..9829a381 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -83,7 +83,7 @@ def test_multi_line_response(self): """ self.notepad.activate() self.ahk.type('Hello\nWorld!') - assert b'Hello' in self.notepad.text + assert b'Hello\r\n' in self.notepad.text assert b'World!' in self.notepad.text diff --git a/tests/unittests/test_daemon_async.py b/tests/unittests/test_daemon_async.py index 688d82db..987f35bd 100644 --- a/tests/unittests/test_daemon_async.py +++ b/tests/unittests/test_daemon_async.py @@ -244,6 +244,6 @@ async def test_multi_line_response(self): await notepad.activate() await self.ahk.type('Hello\nWorld!') text = await notepad.get_text() - assert b'Hello' in text + assert b'Hello\r\n' in text assert b'World!' in text From 21d8ec08d417895220ec178792067b8706082368 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 11 Aug 2021 15:45:49 -0700 Subject: [PATCH 152/588] update template to remove noenv in daemon mode --- ahk/daemon.py | 2 -- ahk/templates/base.ahk | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ahk/daemon.py b/ahk/daemon.py index 3141e16d..701e1641 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -97,7 +97,6 @@ def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): warnings.warn("blocking=False in daemon mode is not supported", stacklevel=3) if not self._is_running: raise RuntimeError("Not running! Must call .start() first!") - script_text = script_text.replace('#NoEnv', '', 1) with self.run_lock: for line in script_text.split('\n'): line = line.strip() @@ -213,7 +212,6 @@ def render_template(self, template_name, directives=None, blocking=True, **kwarg async def a_run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): if not self._is_running: raise RuntimeError("Not running! Must await .start() first!") - script_text = script_text.replace('#NoEnv', '', 1) async with self.run_lock: for line in script_text.split('\n'): line = line.strip() diff --git a/ahk/templates/base.ahk b/ahk/templates/base.ahk index 20aa354c..1e51aac6 100644 --- a/ahk/templates/base.ahk +++ b/ahk/templates/base.ahk @@ -1,5 +1,5 @@ {% block directives %} -#NoEnv +{% if not _daemon %}#NoEnv{% endif %} {% for directive in directives %} {{ directive }} {% endfor %} From 37590e5fd0e0af6c1ae3482c19fa92b63b513ef3 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 11 Aug 2021 19:14:45 -0700 Subject: [PATCH 153/588] revert 21d8ec08d417895220ec178792067b8706082368 --- ahk/daemon.py | 2 ++ ahk/templates/base.ahk | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ahk/daemon.py b/ahk/daemon.py index 701e1641..3141e16d 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -97,6 +97,7 @@ def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): warnings.warn("blocking=False in daemon mode is not supported", stacklevel=3) if not self._is_running: raise RuntimeError("Not running! Must call .start() first!") + script_text = script_text.replace('#NoEnv', '', 1) with self.run_lock: for line in script_text.split('\n'): line = line.strip() @@ -212,6 +213,7 @@ def render_template(self, template_name, directives=None, blocking=True, **kwarg async def a_run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): if not self._is_running: raise RuntimeError("Not running! Must await .start() first!") + script_text = script_text.replace('#NoEnv', '', 1) async with self.run_lock: for line in script_text.split('\n'): line = line.strip() diff --git a/ahk/templates/base.ahk b/ahk/templates/base.ahk index 1e51aac6..20aa354c 100644 --- a/ahk/templates/base.ahk +++ b/ahk/templates/base.ahk @@ -1,5 +1,5 @@ {% block directives %} -{% if not _daemon %}#NoEnv{% endif %} +#NoEnv {% for directive in directives %} {{ directive }} {% endfor %} From 28c1d4fdc18baa09757bcfa3d5894cdec3fd220f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 12 Aug 2021 02:08:04 -0700 Subject: [PATCH 154/588] fix winmove when only height/width is provided --- ahk/templates/window/win_move.ahk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/templates/window/win_move.ahk b/ahk/templates/window/win_move.ahk index c3d0ea7c..587e403b 100644 --- a/ahk/templates/window/win_move.ahk +++ b/ahk/templates/window/win_move.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} -WinMove,{{ title }},,{{ x }},{{ y }}{% if width %},{{ width }}{% endif %}{% if height %},{{ height }}{% endif %} +WinMove,{{ title }},,{{ x }},{{ y }}{% if width or height %},{% if width %}{{ width }}{% endif %},{% if height %}{{ height }}{% endif %}{% endif %} {% endblock body %} From 60d31d19874ea8646918b348c47490957324a5e4 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 12 Aug 2021 02:15:05 -0700 Subject: [PATCH 155/588] add window geometry tests --- tests/unittests/test_window.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index 5dfa5b48..f9fa75bb 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -1,6 +1,11 @@ import subprocess import time from unittest import TestCase +import os, sys +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 @@ -70,6 +75,24 @@ def test_names(self): self.assertEqual(self.win.title, b'Untitled - Notepad') self.assertEqual(self.win.text, b'') + def test_height_change(self): + current_height = self.win.height + self.win.height = current_height + 100 + assert self.win.height == current_height + 100 + + def test_width_change(self): + current_width = self.win.width + self.win.width = current_width + 100 + assert self.win.width == current_width + 100 + + def test_rect_setter(self): + """ + get rect ;-) + """ + x, y, width, height = self.win.rect + self.win.rect = (x+10, y+10, width+10, height+10) + assert self.win.rect == (x+10, y+10, width+10, height+10) + def tearDown(self): self.p.terminate() From 740c8e00309e43c9d6889cdbda5150a47b26f0af Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 12 Aug 2021 02:22:09 -0700 Subject: [PATCH 156/588] add title change test --- tests/unittests/test_window.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index f9fa75bb..01a3e091 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -93,6 +93,11 @@ def test_rect_setter(self): self.win.rect = (x+10, y+10, width+10, height+10) assert self.win.rect == (x+10, y+10, width+10, height+10) + def test_title_change(self): + self.win.title = 'foo' + assert self.win.title == b'foo' + + def tearDown(self): self.p.terminate() From 5f2734f403940f2c67a7106f185f5311358903b1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 12 Aug 2021 03:23:02 -0700 Subject: [PATCH 157/588] more mouse tests --- tests/unittests/test_mouse.py | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/unittests/test_mouse.py b/tests/unittests/test_mouse.py index 81ecbca9..04597d51 100644 --- a/tests/unittests/test_mouse.py +++ b/tests/unittests/test_mouse.py @@ -1,3 +1,4 @@ +import inspect import os import subprocess import sys @@ -5,6 +6,7 @@ import time import asyncio from itertools import product +from functools import partial from unittest import TestCase, IsolatedAsyncioTestCase project_root = os.path.abspath( os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") @@ -12,15 +14,50 @@ sys.path.insert(0, project_root) from ahk import AsyncAHK, AHK + + + class TestMouse(TestCase): def setUp(self) -> None: self.ahk = AHK() + self.original_position = self.ahk.mouse_position + self.notepad_process = None + + def tearDown(self) -> None: + self.ahk.mouse_move(*self.original_position) + if self.notepad_process is not None: + self.notepad_process.terminate() def test_mouse_move(self): x, y = self.ahk.mouse_position self.ahk.mouse_move(10, 10, relative=True) assert self.ahk.mouse_position == (x+10, y+10) + def test_mouse_move_absolute(self): + original_x, original_y = self.original_position + new_x = original_x + 10 + new_y = original_y + 10 + self.ahk.mouse_move(new_x, new_y) + assert self.ahk.mouse_position == (new_x, new_y) + + def test_mouse_move_callable_speed(self): + x, y = self.ahk.mouse_position + self.ahk.mouse_move(10, 10, relative=True, speed=lambda: 10) + assert self.ahk.mouse_position == (x+10, y+10) + + def test_mouse_drag(self): + self.notepad_process = subprocess.Popen('notepad') + notepad = self.ahk.find_window(title=b"Untitled - Notepad") + win_width = notepad.width + win_height = notepad.height + self.ahk.mouse_move(*notepad.position) + # moving the mouse to the window position puts it in a position where it can be resized by dragging ↖ ↘ + # after this, we expect the window height/width to shrink by 10px + self.ahk.mouse_drag(10, 10, relative=True) + assert notepad.width == win_width - 10 + assert notepad.height == win_height - 10 + + class TestMouseAsync(IsolatedAsyncioTestCase): def setUp(self) -> None: self.ahk = AsyncAHK() From 922ab72b3a4b0ea6cfef0f2001166caa18b47965 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 14 Aug 2021 12:13:15 -0700 Subject: [PATCH 158/588] replace ci ahk install with ahk-binary --- appveyor.yml | 8 -------- ci/install.bat | 1 + setup.py | 5 ++++- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 252158d4..56634ea2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,14 +5,6 @@ environment: 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: diff --git a/ci/install.bat b/ci/install.bat index ad8331f7..d1d91494 100644 --- a/ci/install.bat +++ b/ci/install.bat @@ -3,4 +3,5 @@ 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 . +python -m pip install ".[binary]" call deactivate \ No newline at end of file diff --git a/setup.py b/setup.py index 7723e68c..89252e70 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.11.1', + version='0.12.0', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, @@ -16,6 +16,9 @@ author_email='spencer.young@spyoung.com', author='Spencer Young', packages=['ahk'], + extras_require={ + "binary": ["ahk-binary==1.1.33.9"], + }, install_requires=['jinja2'], classifiers=[ 'Intended Audience :: Developers', From 689df64c6a06920e574b05b621b332280c38250d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 14 Aug 2021 12:15:24 -0700 Subject: [PATCH 159/588] remove AHK_PATH variable --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 56634ea2..743038b6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,6 @@ version: '0.1.{build}' environment: - AHK_PATH: C:\ahk\AutoHotkey.exe AHK_DEBUG: true install: From c0947cd94b9dc4f9c0b8b478b0af3e034ed75358 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 21 Aug 2021 12:31:58 -0700 Subject: [PATCH 160/588] fix daemon mouse mode --- ahk/templates/_daemon.ahk | 15 ++++++++++++--- ahk/templates/daemon.ahk | 15 ++++++++++++--- ahk/templates/daemon/mouse_position.ahk | 2 ++ ahk/templates/mouse/mouse_drag.ahk | 2 +- ahk/templates/mouse/mouse_move.ahk | 4 ++-- ahk/templates/mouse/mouse_position.ahk | 2 +- tests/unittests/test_mouse.py | 15 +++++++++++++++ 7 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 ahk/templates/daemon/mouse_position.ahk diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index f34ef048..d76b06dc 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -83,9 +83,9 @@ MouseMove(ByRef command) { CoordMode(ByRef command) { if (command.Length() = 2) { - CoordMode, command[2] + CoordMode,% command[2] } else { - CoordMode, command[2], command[3] + CoordMode,% command[2],% command[3] } } @@ -148,7 +148,7 @@ KeyWait(ByRef command) { } SetKeyDelay(ByRef command) { - SetKeyDelay, command[2] + SetKeyDelay, command[2], command[3] } Join(sep, params*) { @@ -374,6 +374,15 @@ WindowList(ByRef command) { } WinSend(ByRef command) { + title := command[2] + command.RemoveAt(1) + command.RemoveAt(1) + str := Join(",", command*) + keys := Unescape(str) + ControlSend,,% keys, %title% +} + +WinSendRaw(ByRef command) { title := command[2] command.RemoveAt(1) command.RemoveAt(1) diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 1cc23827..72208ebc 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -83,9 +83,9 @@ MouseMove(ByRef command) { CoordMode(ByRef command) { if (command.Length() = 2) { - CoordMode, command[2] + CoordMode,% command[2] } else { - CoordMode, command[2], command[3] + CoordMode,% command[2],% command[3] } } @@ -156,7 +156,7 @@ KeyWait(ByRef command) { } SetKeyDelay(ByRef command) { - SetKeyDelay, command[2] + SetKeyDelay, command[2], command[3] } Join(sep, params*) { @@ -382,6 +382,15 @@ WindowList(ByRef command) { } WinSend(ByRef command) { + title := command[2] + command.RemoveAt(1) + command.RemoveAt(1) + str := Join(",", command*) + keys := Unescape(str) + ControlSend,,% keys, %title% +} + +WinSendRaw(ByRef command) { title := command[2] command.RemoveAt(1) command.RemoveAt(1) diff --git a/ahk/templates/daemon/mouse_position.ahk b/ahk/templates/daemon/mouse_position.ahk new file mode 100644 index 00000000..7a844d4b --- /dev/null +++ b/ahk/templates/daemon/mouse_position.ahk @@ -0,0 +1,2 @@ +CoordMode,Mouse,{{mode}} +MouseGetPos, xpos, ypos \ No newline at end of file diff --git a/ahk/templates/mouse/mouse_drag.ahk b/ahk/templates/mouse/mouse_drag.ahk index 61557e05..f96ef2d0 100644 --- a/ahk/templates/mouse/mouse_drag.ahk +++ b/ahk/templates/mouse/mouse_drag.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} -CoordMode, Mouse, {{mode}} +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 index 88212602..e070f08e 100644 --- a/ahk/templates/mouse/mouse_move.ahk +++ b/ahk/templates/mouse/mouse_move.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} -CoordMode, Mouse, {{mode}} -MouseMove, {{x}}, {{y}}, {{speed}}{% if relative %}, R{% endif %} +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 index b317cf8b..dd870adb 100644 --- a/ahk/templates/mouse/mouse_position.ahk +++ b/ahk/templates/mouse/mouse_position.ahk @@ -1,6 +1,6 @@ {% extends "base.ahk" %} {% block body %} -CoordMode, Mouse, {{mode}} +CoordMode,Mouse,{{mode}} MouseGetPos, xpos, ypos s .= Format("({}, {})", xpos, ypos) FileAppend, %s%, * diff --git a/tests/unittests/test_mouse.py b/tests/unittests/test_mouse.py index 04597d51..e384057d 100644 --- a/tests/unittests/test_mouse.py +++ b/tests/unittests/test_mouse.py @@ -13,6 +13,7 @@ ) sys.path.insert(0, project_root) from ahk import AsyncAHK, AHK +from ahk.daemon import AHKDaemon @@ -47,10 +48,13 @@ def test_mouse_move_callable_speed(self): def test_mouse_drag(self): self.notepad_process = subprocess.Popen('notepad') + time.sleep(0.5) notepad = self.ahk.find_window(title=b"Untitled - Notepad") win_width = notepad.width win_height = notepad.height + print(*notepad.position) self.ahk.mouse_move(*notepad.position) + time.sleep(1) # moving the mouse to the window position puts it in a position where it can be resized by dragging ↖ ↘ # after this, we expect the window height/width to shrink by 10px self.ahk.mouse_drag(10, 10, relative=True) @@ -58,6 +62,17 @@ def test_mouse_drag(self): assert notepad.height == win_height - 10 +class TestMouseDaemon(TestMouse): + def setUp(self) -> None: + self.ahk = AHKDaemon() + self.ahk.start() + self.original_position = self.ahk.mouse_position + self.notepad_process = None + + def tearDown(self) -> None: + super().tearDown() + self.ahk.stop() + class TestMouseAsync(IsolatedAsyncioTestCase): def setUp(self) -> None: self.ahk = AsyncAHK() From b3e889cefccd4835f22e48ff9785b4ec1498cb45 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 21 Aug 2021 12:32:32 -0700 Subject: [PATCH 161/588] fix daemon keyboard --- ahk/templates/daemon/win_send.ahk | 2 +- ci/ci_requirements.txt | 1 + tests/unittests/__init__.py | 0 tests/unittests/test_keyboard.py | 21 +++++++++++++++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/unittests/__init__.py diff --git a/ahk/templates/daemon/win_send.ahk b/ahk/templates/daemon/win_send.ahk index 4d7073ff..76221ae5 100644 --- a/ahk/templates/daemon/win_send.ahk +++ b/ahk/templates/daemon/win_send.ahk @@ -1,2 +1,2 @@ SetKeyDelay,{{ delay }},{{ press_duration }} -WinSend,{{ title }},{{ keys }} \ No newline at end of file +{% if raw %}WinSendRaw{% else %}WinSend{% endif %},{{ title }},{{ keys }} \ No newline at end of file diff --git a/ci/ci_requirements.txt b/ci/ci_requirements.txt index eccdb76a..8f1f76b6 100644 --- a/ci/ci_requirements.txt +++ b/ci/ci_requirements.txt @@ -1,4 +1,5 @@ pytest +pytest-rerunfailures behave behave-classy coveralls diff --git a/tests/unittests/__init__.py b/tests/unittests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unittests/test_keyboard.py b/tests/unittests/test_keyboard.py index 77a48a14..dab13bcd 100644 --- a/tests/unittests/test_keyboard.py +++ b/tests/unittests/test_keyboard.py @@ -5,6 +5,7 @@ import time from itertools import product from unittest import TestCase +import pytest project_root = os.path.abspath( os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") @@ -13,6 +14,7 @@ from ahk import AHK from ahk.keys import ALT, CTRL, KEYS +from ahk.daemon import AHKDaemon class TestKeyboard(TestCase): @@ -36,6 +38,12 @@ def test_window_send(self): time.sleep(1) self.assertIn(b"hello world", self.notepad.text) + @pytest.mark.flaky(reruns=5) + def test_window_send_raw(self): + self.notepad.send("{Tab 4}", raw=True, delay=10, press_duration=10) + time.sleep(0.5) + assert b'{Tab 4}' in self.notepad.text + def test_send(self): self.notepad.activate() self.ahk.send("hello world") @@ -49,6 +57,7 @@ def test_send_key_mult(self): def test_send_input(self): self.notepad.activate() self.ahk.send_input("Hello World") + time.sleep(0.5) assert b"Hello World" in self.notepad.text def test_type(self): @@ -76,6 +85,18 @@ def test_set_capslock_state(self): self.ahk.set_capslock_state("on") assert self.ahk.key_state("CapsLock", "T") +class TestKeyboardDaemon(TestKeyboard): + def setUp(self): + self.ahk = AHKDaemon() + self.ahk.start() + 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): + super().tearDown() + self.ahk.stop() def a_down(): time.sleep(0.5) From cee2d15fddb7a3ecf27593f608d4935d0633caf3 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 21 Aug 2021 12:37:34 -0700 Subject: [PATCH 162/588] add screen tests --- tests/unittests/test_screen.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/unittests/test_screen.py b/tests/unittests/test_screen.py index b2e81d4d..798d8e06 100644 --- a/tests/unittests/test_screen.py +++ b/tests/unittests/test_screen.py @@ -7,7 +7,7 @@ from PIL import Image from itertools import product import time - +from ahk.daemon import AHKDaemon class TestScreen(TestCase): def setUp(self): @@ -44,3 +44,19 @@ def test_pixel_get_color(self): result = self.ahk.pixel_get_color(x, y) self.assertIsNotNone(result) self.assertEqual(int(result, 16), 0xFF0000) + +class TestScreenDaemon(TestScreen): + def setUp(self): + self.ahk = AHKDaemon() + self.ahk.start() + 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): + super().tearDown() + self.ahk.stop() \ No newline at end of file From 57cd15fe4f525b568c65afa9a07cc7ca99686073 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 21 Aug 2021 12:43:27 -0700 Subject: [PATCH 163/588] fix daemon winmove --- ahk/templates/daemon/win_move.ahk | 2 +- tests/unittests/test_window.py | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ahk/templates/daemon/win_move.ahk b/ahk/templates/daemon/win_move.ahk index 078a3340..3c17d265 100644 --- a/ahk/templates/daemon/win_move.ahk +++ b/ahk/templates/daemon/win_move.ahk @@ -1 +1 @@ -AHKWinMove,{{ title }},{{ x }},{{ y }}{% if width %},{{ width }}{% endif %}{% if height %},{{ height }}{% endif %} \ No newline at end of file +AHKWinMove,{{ title }},{{ x }},{{ y }}{% if width or height %},{% if width %}{{ width }}{% endif %},{% if height %}{{ height }}{% endif %}{% endif %} \ No newline at end of file diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index 01a3e091..7dc4c1a7 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -8,7 +8,7 @@ sys.path.insert(0, project_root) from ahk import AHK - +from ahk.daemon import AHKDaemon class TestWindow(TestCase): @@ -101,11 +101,15 @@ def test_title_change(self): def tearDown(self): self.p.terminate() +class TestWindowDaemon(TestWindow): + def setUp(self): + self.ahk = AHKDaemon() + self.ahk.start() + self.p = subprocess.Popen('notepad') + time.sleep(1) + self.win = self.ahk.win_get(title='Untitled - Notepad') + self.assertIsNotNone(self.win) -if __name__ == "__main__": - ahk = AHK() - p = subprocess.Popen('notepad') - time.sleep(1) - win = ahk.win_get(title='Untitled - Notepad') - print(win.transparent) - win.transparent = 255 + def tearDown(self): + super().tearDown() + self.ahk.stop() \ No newline at end of file From 36b7314a36febc6559acf20890d784878181f53f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 21 Aug 2021 13:05:17 -0700 Subject: [PATCH 164/588] Improve executable finding --- ahk/script.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ahk/script.py b/ahk/script.py index 3d1b913c..c34883b3 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -32,8 +32,11 @@ class ExecutableNotFoundError(EnvironmentError): def _resolve_executable_path(executable_path: str = ''): if not executable_path: - executable_path = os.environ.get('AHK_PATH') or which( - 'AutoHotkey.exe') or which('AutoHotkeyA32.exe') + executable_path = os.environ.get('AHK_PATH') \ + or which('AutoHotkey.exe') \ + or which('AutoHotkeyU64.exe') \ + or which('AutoHotkeyU32.exe') \ + or which('AutoHotkeyA32.exe') if not executable_path: if os.path.exists(DEFAULT_EXECUTABLE_PATH): @@ -43,7 +46,8 @@ def _resolve_executable_path(executable_path: str = ''): 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.' + 'or in the AHK_PATH environment variable. ' + 'You may be able to resolve this error by installing the binary extra: pip install "ahk[binary]"' ) if not os.path.exists(executable_path): From 4fb3edf80bbe9f961767c7a5eaca67c40c8feb0f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 21 Aug 2021 13:07:52 -0700 Subject: [PATCH 165/588] update readme --- docs/README.md | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index b55b6436..facf4a00 100644 --- a/docs/README.md +++ b/docs/README.md @@ -279,6 +279,30 @@ These features are (even more) likely to have breaking changes without warning. Github issues are provided for convenience to collect feedback on these features. + +## AHKDaemon + +Normally, `AHK` works by creating a new subprocess for every command invocation. Because processes are expensive +to create in Windows, this can lead to performance issues for some use-cases. `AHKDaemon` allows all AHK commands to be +carried out in a single process, as opposed to running each command in a new subprocess, improving performance. + +Some other details change in Daemon mode, such as persistence of state (e.g. changes to CoordMode). + + +```python +from ahk.daemon import AHKDaemon +daemon = AHKDaemon() +daemon.start() +daemon.mouse_move(100, 100) +``` + +For the most part, the AHK Daemon works just like the regular `AHK` class, with a few caveats. Most notably, AHKDaemon +does not allow you to run arbitrary AutoHotkey scripts and does not yet support Hotkeys. However, you can always use +the normal `AHK` class alongside the daemon for these needs. + +In the future, AHKDaemon may become the default implementation. + + ## Async API An async API is provided so functions can be called using `async`/`await`. @@ -420,9 +444,25 @@ will be added. ## Non-Python dependencies -To use this package, you need the [AutoHotkey executable](https://www.autohotkey.com/download/). +To use this package, you need the [AutoHotkey executable](https://www.autohotkey.com/download/). It's expected to be on PATH by default. -It's expected to be on PATH by default. You can also use the `AHK_PATH` environment variable to specify the executable location. +A convenient way to do this is to install the `binary` extra (requires version 0.12 or higher of this package) + +``` +pip install "ahk[binary]" +``` + +For versions < 0.12 you can install the ahk-binary package directly: + +``` +pip install "ahk-binary<2" +``` + +You can also use the `AHK_PATH` environment variable to specify the executable location. + +```console +set AHK_PATH=C:\Path\To\AutoHotkey.exe +``` Alternatively, you may provide the path in code From 6e780cfb47ac3e7d6127d0202991eedff7b226ce Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 21 Aug 2021 13:28:21 -0700 Subject: [PATCH 166/588] :package: 0.13.0 --- docs/README.md | 6 ++++-- setup.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index facf4a00..2535f017 100644 --- a/docs/README.md +++ b/docs/README.md @@ -300,6 +300,8 @@ For the most part, the AHK Daemon works just like the regular `AHK` class, with does not allow you to run arbitrary AutoHotkey scripts and does not yet support Hotkeys. However, you can always use the normal `AHK` class alongside the daemon for these needs. +`AsyncAHKDaemon` is also available for asyncio support. + In the future, AHKDaemon may become the default implementation. @@ -446,13 +448,13 @@ will be added. To use this package, you need the [AutoHotkey executable](https://www.autohotkey.com/download/). It's expected to be on PATH by default. -A convenient way to do this is to install the `binary` extra (requires version 0.12 or higher of this package) +A convenient way to do this is to install the `binary` extra (requires version 0.13 or higher of this package) ``` pip install "ahk[binary]" ``` -For versions < 0.12 you can install the ahk-binary package directly: +For versions < 0.13 you can install the ahk-binary package directly: ``` pip install "ahk-binary<2" diff --git a/setup.py b/setup.py index 89252e70..6229bdc8 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='ahk', - version='0.12.0', + version='0.13.0', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From 105bacdbb7588d9bc7dc44ba30d6d3c2ab276e7e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 21 Aug 2021 13:56:57 -0700 Subject: [PATCH 167/588] :package: 0.13.0 --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6229bdc8..ce693131 100644 --- a/setup.py +++ b/setup.py @@ -35,5 +35,6 @@ ], tests_require=test_requirements, include_package_data=True, - zip_safe=False + zip_safe=False, + keywords=['ahk', 'autohotkey', 'windows', 'mouse', 'keyboard', 'automation', 'pyautogui'] ) From c82afd62d34535c17ef9d2791589b04671ce9569 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 8 Nov 2021 17:56:28 -0800 Subject: [PATCH 168/588] add pre-commit --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..a8f3bc8a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace From 2261ec39417ee58fadb2f773646d6aecc29d6424 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Nov 2021 01:57:57 +0000 Subject: [PATCH 169/588] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ahk/templates/_daemon.ahk | 1 - ahk/templates/daemon/base_check.ahk | 2 +- ahk/templates/daemon/control_send.ahk | 2 +- ahk/templates/daemon/from_mouse.ahk | 2 +- ahk/templates/daemon/id_list.ahk | 2 +- ahk/templates/daemon/image_search.ahk | 2 +- ahk/templates/daemon/key_state.ahk | 2 +- ahk/templates/daemon/key_wait.ahk | 2 +- ahk/templates/daemon/mouse_position.ahk | 2 +- ahk/templates/daemon/pixel_get_color.ahk | 2 +- ahk/templates/daemon/pixel_search.ahk | 2 +- ahk/templates/daemon/set_capslock_state.ahk | 2 +- ahk/templates/daemon/tooltip.ahk | 2 +- ahk/templates/daemon/traytip.ahk | 2 +- ahk/templates/daemon/win_click.ahk | 2 +- ahk/templates/daemon/win_is_always_on_top.ahk | 2 +- ahk/templates/daemon/win_move.ahk | 2 +- ahk/templates/daemon/win_position.ahk | 2 +- ahk/templates/daemon/win_send.ahk | 2 +- ahk/templates/keyboard/key_state.ahk | 2 +- ahk/templates/keyboard/key_wait.ahk | 2 +- ahk/templates/keyboard/send.ahk | 2 +- ahk/templates/keyboard/send_event.ahk | 2 +- ahk/templates/keyboard/send_input.ahk | 2 +- ahk/templates/keyboard/send_play.ahk | 2 +- ahk/templates/keyboard/set_capslock_state.ahk | 2 +- ahk/templates/mouse/mouse_move.ahk | 2 +- ahk/templates/mouse/mouse_position.ahk | 2 +- ahk/templates/window/control_send.ahk | 2 +- ahk/templates/window/id_list.ahk | 2 +- ahk/templates/window/title_list.ahk | 2 +- ahk/templates/window/win_set_title.ahk | 2 +- ahk/utils.py | 2 +- ahk/window.py | 122 +++++++++--------- appveyor.yml | 2 +- ci/build.bat | 2 +- ci/ci_requirements.txt | 2 +- ci/install.bat | 2 +- ci/runtests.ps1 | 2 +- docs/Makefile | 2 +- docs/README.md | 66 +++++----- docs/api/autohotkey.rst | 1 - docs/api/index.rst | 1 - docs/api/keys.rst | 2 - docs/conf.py | 2 +- tests/features/mouse_move.feature | 1 - tests/features/steps/ahk_steps.py | 2 +- tests/unittests/test_daemon_async.py | 1 - tests/unittests/test_mouse.py | 2 - tests/unittests/test_screen.py | 2 +- tests/unittests/test_window.py | 4 +- 51 files changed, 137 insertions(+), 146 deletions(-) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index d76b06dc..3fa6e86f 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -522,4 +522,3 @@ Loop { FileAppend, %newline_count%`n, * FileAppend, %response%`n, * } - diff --git a/ahk/templates/daemon/base_check.ahk b/ahk/templates/daemon/base_check.ahk index 196bfcbe..f9ea0ef8 100644 --- a/ahk/templates/daemon/base_check.ahk +++ b/ahk/templates/daemon/base_check.ahk @@ -1 +1 @@ -BaseCheck,{{ command }},{{ title }} \ No newline at end of file +BaseCheck,{{ command }},{{ title }} diff --git a/ahk/templates/daemon/control_send.ahk b/ahk/templates/daemon/control_send.ahk index 539237df..24bce962 100644 --- a/ahk/templates/daemon/control_send.ahk +++ b/ahk/templates/daemon/control_send.ahk @@ -1 +1 @@ -ControlSend,{{ control }},{{ win_title }},{{ win_text }},{{ exclude_title }},{{ exclude_text }},{{keys}} \ No newline at end of file +ControlSend,{{ control }},{{ win_title }},{{ win_text }},{{ exclude_title }},{{ exclude_text }},{{keys}} diff --git a/ahk/templates/daemon/from_mouse.ahk b/ahk/templates/daemon/from_mouse.ahk index b91527dd..91113a61 100644 --- a/ahk/templates/daemon/from_mouse.ahk +++ b/ahk/templates/daemon/from_mouse.ahk @@ -1 +1 @@ -FromMouse \ No newline at end of file +FromMouse diff --git a/ahk/templates/daemon/id_list.ahk b/ahk/templates/daemon/id_list.ahk index 1155f511..c7ca9838 100644 --- a/ahk/templates/daemon/id_list.ahk +++ b/ahk/templates/daemon/id_list.ahk @@ -1 +1 @@ -WindowList \ No newline at end of file +WindowList diff --git a/ahk/templates/daemon/image_search.ahk b/ahk/templates/daemon/image_search.ahk index 03debc98..f27c6fff 100644 --- a/ahk/templates/daemon/image_search.ahk +++ b/ahk/templates/daemon/image_search.ahk @@ -1,2 +1,2 @@ CoordMode, Pixel, {{ coord_mode }} -ImageSearch,xpos,ypos,{{ x1 }},{{ y1 }},{{ x2 }},{{ y2 }},{% if options %}{% for option in options %}*{{ option }} {% endfor %}{% endif %}{{ image_path }} \ No newline at end of file +ImageSearch,xpos,ypos,{{ x1 }},{{ y1 }},{{ x2 }},{{ y2 }},{% if options %}{% for option in options %}*{{ option }} {% endfor %}{% endif %}{{ image_path }} diff --git a/ahk/templates/daemon/key_state.ahk b/ahk/templates/daemon/key_state.ahk index 8dc70dd3..e00420c8 100644 --- a/ahk/templates/daemon/key_state.ahk +++ b/ahk/templates/daemon/key_state.ahk @@ -1 +1 @@ -AHKKeyState,{{ key_name }}{% if mode %},{{ mode }}{% endif %} \ No newline at end of file +AHKKeyState,{{ key_name }}{% if mode %},{{ mode }}{% endif %} diff --git a/ahk/templates/daemon/key_wait.ahk b/ahk/templates/daemon/key_wait.ahk index c6bb5cd4..b726717e 100644 --- a/ahk/templates/daemon/key_wait.ahk +++ b/ahk/templates/daemon/key_wait.ahk @@ -1 +1 @@ -KeyWait,{{ key_name }}{% if options %},{{ options }}{% endif %} \ No newline at end of file +KeyWait,{{ key_name }}{% if options %},{{ options }}{% endif %} diff --git a/ahk/templates/daemon/mouse_position.ahk b/ahk/templates/daemon/mouse_position.ahk index 7a844d4b..b8f9d34e 100644 --- a/ahk/templates/daemon/mouse_position.ahk +++ b/ahk/templates/daemon/mouse_position.ahk @@ -1,2 +1,2 @@ CoordMode,Mouse,{{mode}} -MouseGetPos, xpos, ypos \ No newline at end of file +MouseGetPos, xpos, ypos diff --git a/ahk/templates/daemon/pixel_get_color.ahk b/ahk/templates/daemon/pixel_get_color.ahk index 3c9ab720..1748a725 100644 --- a/ahk/templates/daemon/pixel_get_color.ahk +++ b/ahk/templates/daemon/pixel_get_color.ahk @@ -1,2 +1,2 @@ CoordMode, Pixel, {{ coord_mode }} -PixelGetColor,color,{{ x }},{{ y }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} \ No newline at end of file +PixelGetColor,color,{{ x }},{{ y }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} diff --git a/ahk/templates/daemon/pixel_search.ahk b/ahk/templates/daemon/pixel_search.ahk index bd4b322f..587e20af 100644 --- a/ahk/templates/daemon/pixel_search.ahk +++ b/ahk/templates/daemon/pixel_search.ahk @@ -1,2 +1,2 @@ CoordMode, Pixel, {{ coord_mode }} -PixelSearch, xpos, ypos,{{ x1 }},{{ y1 }},{{ x2 }},{{ y2 }},{{ color }},{{ variation }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} \ No newline at end of file +PixelSearch, xpos, ypos,{{ x1 }},{{ y1 }},{{ x2 }},{{ y2 }},{{ color }},{{ variation }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} diff --git a/ahk/templates/daemon/set_capslock_state.ahk b/ahk/templates/daemon/set_capslock_state.ahk index dff84214..19f67c8b 100644 --- a/ahk/templates/daemon/set_capslock_state.ahk +++ b/ahk/templates/daemon/set_capslock_state.ahk @@ -1 +1 @@ -{% if state %}SetCapsLockState,{{state}}{% else %}SetCapsLockState{% endif %} \ No newline at end of file +{% if state %}SetCapsLockState,{{state}}{% else %}SetCapsLockState{% endif %} diff --git a/ahk/templates/daemon/tooltip.ahk b/ahk/templates/daemon/tooltip.ahk index 0858a880..4903e2c0 100644 --- a/ahk/templates/daemon/tooltip.ahk +++ b/ahk/templates/daemon/tooltip.ahk @@ -1 +1 @@ -ToolTip, {{ text }},{{ x }},{{ y }},{{ id }} \ No newline at end of file +ToolTip, {{ text }},{{ x }},{{ y }},{{ id }} diff --git a/ahk/templates/daemon/traytip.ahk b/ahk/templates/daemon/traytip.ahk index 0e6c53a1..3eff6776 100644 --- a/ahk/templates/daemon/traytip.ahk +++ b/ahk/templates/daemon/traytip.ahk @@ -1 +1 @@ -TrayTip,{{ title }},{{ text }},{{ second }},{{ option }} \ No newline at end of file +TrayTip,{{ title }},{{ text }},{{ second }},{{ option }} diff --git a/ahk/templates/daemon/win_click.ahk b/ahk/templates/daemon/win_click.ahk index 2564f8ec..cdc6791f 100644 --- a/ahk/templates/daemon/win_click.ahk +++ b/ahk/templates/daemon/win_click.ahk @@ -1 +1 @@ -WinClick,{{ x }},{{ y }},{{ hwnd }},{{ button }},{{ n }}{% if options %},{{ options }}{% endif %} \ No newline at end of file +WinClick,{{ x }},{{ y }},{{ hwnd }},{{ button }},{{ n }}{% if options %},{{ options }}{% endif %} diff --git a/ahk/templates/daemon/win_is_always_on_top.ahk b/ahk/templates/daemon/win_is_always_on_top.ahk index a3781a1e..e7c101fc 100644 --- a/ahk/templates/daemon/win_is_always_on_top.ahk +++ b/ahk/templates/daemon/win_is_always_on_top.ahk @@ -1 +1 @@ -WinIsAlwaysOnTop,{{ title }} \ No newline at end of file +WinIsAlwaysOnTop,{{ title }} diff --git a/ahk/templates/daemon/win_move.ahk b/ahk/templates/daemon/win_move.ahk index 3c17d265..0b53a948 100644 --- a/ahk/templates/daemon/win_move.ahk +++ b/ahk/templates/daemon/win_move.ahk @@ -1 +1 @@ -AHKWinMove,{{ title }},{{ x }},{{ y }}{% if width or height %},{% if width %}{{ width }}{% endif %},{% if height %}{{ height }}{% endif %}{% endif %} \ No newline at end of file +AHKWinMove,{{ title }},{{ x }},{{ y }}{% if width or height %},{% if width %}{{ width }}{% endif %},{% if height %}{{ height }}{% endif %}{% endif %} diff --git a/ahk/templates/daemon/win_position.ahk b/ahk/templates/daemon/win_position.ahk index 02cdfda9..d45e1309 100644 --- a/ahk/templates/daemon/win_position.ahk +++ b/ahk/templates/daemon/win_position.ahk @@ -2,4 +2,4 @@ AHKWinGetPos,{{ title }},{{ pos_info }} {% else %} AHKWinGetPos,{{ title }} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/ahk/templates/daemon/win_send.ahk b/ahk/templates/daemon/win_send.ahk index 76221ae5..65b068b7 100644 --- a/ahk/templates/daemon/win_send.ahk +++ b/ahk/templates/daemon/win_send.ahk @@ -1,2 +1,2 @@ SetKeyDelay,{{ delay }},{{ press_duration }} -{% if raw %}WinSendRaw{% else %}WinSend{% endif %},{{ title }},{{ keys }} \ No newline at end of file +{% if raw %}WinSendRaw{% else %}WinSend{% endif %},{{ title }},{{ keys }} diff --git a/ahk/templates/keyboard/key_state.ahk b/ahk/templates/keyboard/key_state.ahk index 6cd8e482..ef6bcadb 100644 --- a/ahk/templates/keyboard/key_state.ahk +++ b/ahk/templates/keyboard/key_state.ahk @@ -5,4 +5,4 @@ if (GetKeyState("{{ key_name }}"{% if mode %} , "{{ mode }}"{% endif %})) { } else { FileAppend, 0, * } -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/keyboard/key_wait.ahk b/ahk/templates/keyboard/key_wait.ahk index e500aa83..9f2db0ea 100644 --- a/ahk/templates/keyboard/key_wait.ahk +++ b/ahk/templates/keyboard/key_wait.ahk @@ -3,4 +3,4 @@ KeyWait, {{ key_name }}{% if options %} , {{ options }}{% endif %} FileAppend, %ErrorLevel%, * -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/keyboard/send.ahk b/ahk/templates/keyboard/send.ahk index c1860597..e77afd5f 100644 --- a/ahk/templates/keyboard/send.ahk +++ b/ahk/templates/keyboard/send.ahk @@ -3,4 +3,4 @@ {% if delay %}SetKeyDelay, {{ delay }}{% endif %} Send{% if raw %}Raw{% endif %},{{ s }} -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/keyboard/send_event.ahk b/ahk/templates/keyboard/send_event.ahk index 2cb0fd1f..0c8d72fa 100644 --- a/ahk/templates/keyboard/send_event.ahk +++ b/ahk/templates/keyboard/send_event.ahk @@ -3,4 +3,4 @@ {% if delay %}SetKeyDelay, {{ delay }}{% endif %} SendEvent,{{ s }} -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/keyboard/send_input.ahk b/ahk/templates/keyboard/send_input.ahk index 199558b7..226d180f 100644 --- a/ahk/templates/keyboard/send_input.ahk +++ b/ahk/templates/keyboard/send_input.ahk @@ -2,4 +2,4 @@ {% block body %} SendInput,{{ s }} -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/keyboard/send_play.ahk b/ahk/templates/keyboard/send_play.ahk index 4fefa931..f5d7a57a 100644 --- a/ahk/templates/keyboard/send_play.ahk +++ b/ahk/templates/keyboard/send_play.ahk @@ -3,4 +3,4 @@ {% if delay %}SetKeyDelay, {{ delay }}{% endif %} SendPlay,{{ s }} -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/keyboard/set_capslock_state.ahk b/ahk/templates/keyboard/set_capslock_state.ahk index 353203db..7044a635 100644 --- a/ahk/templates/keyboard/set_capslock_state.ahk +++ b/ahk/templates/keyboard/set_capslock_state.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} {% if state %}SetCapsLockState, {{state}}{% else %}SetCapsLockState % !GetKeyState("CapsLock", "T"){% endif %} -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/mouse/mouse_move.ahk b/ahk/templates/mouse/mouse_move.ahk index e070f08e..8d4fffda 100644 --- a/ahk/templates/mouse/mouse_move.ahk +++ b/ahk/templates/mouse/mouse_move.ahk @@ -2,4 +2,4 @@ {% block body %} CoordMode,Mouse,{{mode}} MouseMove,{{x}},{{y}},{{speed}}{% if relative %},R{% endif %} -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/mouse/mouse_position.ahk b/ahk/templates/mouse/mouse_position.ahk index dd870adb..c0dfd832 100644 --- a/ahk/templates/mouse/mouse_position.ahk +++ b/ahk/templates/mouse/mouse_position.ahk @@ -4,4 +4,4 @@ CoordMode,Mouse,{{mode}} MouseGetPos, xpos, ypos s .= Format("({}, {})", xpos, ypos) FileAppend, %s%, * -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/window/control_send.ahk b/ahk/templates/window/control_send.ahk index 8d4817f7..9cb1f5bd 100644 --- a/ahk/templates/window/control_send.ahk +++ b/ahk/templates/window/control_send.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} ControlSend,{{ control }},{{ keys }},{{ win_title }},{{ win_text }},{{ exclude_title }},{{ exclude_text }} -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/window/id_list.ahk b/ahk/templates/window/id_list.ahk index 64797959..94c9162a 100644 --- a/ahk/templates/window/id_list.ahk +++ b/ahk/templates/window/id_list.ahk @@ -7,4 +7,4 @@ Loop %windows% r .= id . "`," } FileAppend, %r%, * -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/window/title_list.ahk b/ahk/templates/window/title_list.ahk index 1fd2027d..7bb1c460 100644 --- a/ahk/templates/window/title_list.ahk +++ b/ahk/templates/window/title_list.ahk @@ -8,4 +8,4 @@ Loop %windows% r .= wt . "`n" } FileAppend, %r%, * -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/templates/window/win_set_title.ahk b/ahk/templates/window/win_set_title.ahk index 4ba1da1e..aec6070d 100644 --- a/ahk/templates/window/win_set_title.ahk +++ b/ahk/templates/window/win_set_title.ahk @@ -1,4 +1,4 @@ {% extends "base.ahk" %} {% block body %} WinSetTitle,{{ title }},{{ text }},{{ new_title }} -{% endblock body %} \ No newline at end of file +{% endblock body %} diff --git a/ahk/utils.py b/ahk/utils.py index 0baa8452..829fdbfc 100644 --- a/ahk/utils.py +++ b/ahk/utils.py @@ -54,4 +54,4 @@ async def async_filter(async_pred, iterable): for item in iterable: should_yield = await async_pred(item) if should_yield: - yield item \ No newline at end of file + yield item diff --git a/ahk/window.py b/ahk/window.py index 9b6f1f2b..51a119b2 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -416,16 +416,16 @@ def set_always_on_top(self, value): def disable(self): """ Distable the window - - :return: + + :return: """ return self.set('Disable', '') or None def enable(self): """ Enable the window - - :return: + + :return: """ return self.set('Enable', '') or None @@ -442,8 +442,8 @@ def to_bottom(self): def to_top(self): """ Bring the window to the foreground (above other windows) - - :return: + + :return: """ return self.set('Top', '') or None @@ -502,8 +502,8 @@ def hide(self): Hides the window. See also: `WinHide`_ .. _WinHide: https://www.autohotkey.com/docs/commands/WinHide.htm - - + + :return: """ return self._base_method("WinHide") or None @@ -514,64 +514,64 @@ def kill(self, seconds_to_wait=""): def maximize(self): """ maximize the window - - :return: + + :return: """ return self._base_method("WinMaximize") or None def minimize(self): """ minimize the window - - :return: + + :return: """ return self._base_method("WinMinimize") or None def restore(self): """ restore the window - - :return: + + :return: """ return self._base_method("WinRestore") or None def show(self): """ show the window - - :return: + + :return: """ return self._base_method("WinShow") or None def wait(self, seconds_to_wait=""): """ - - :param seconds_to_wait: - :return: + + :param seconds_to_wait: + :return: """ return self._base_method("WinWait", seconds_to_wait=seconds_to_wait, blocking=True) or None def wait_active(self, seconds_to_wait=""): """ - - :param seconds_to_wait: - :return: + + :param seconds_to_wait: + :return: """ return self._base_method("WinWaitActive", seconds_to_wait=seconds_to_wait, blocking=True) or None def wait_not_active(self, seconds_to_wait=""): """ - - :param seconds_to_wait: - :return: + + :param seconds_to_wait: + :return: """ return self._base_method("WinWaitNotActive", seconds_to_wait=seconds_to_wait, blocking=True) or None def wait_close(self, seconds_to_wait=""): """ - + :param seconds_to_wait: - :return: + :return: """ return self._base_method("WinWaitClose", seconds_to_wait=seconds_to_wait, blocking=True) or None @@ -587,11 +587,11 @@ def move(self, x='', y='', width=None, height=None): """ Move the window to a position and/or change its geometry - :param x: - :param y: - :param width: - :param height: - :return: + :param x: + :param y: + :param width: + :param height: + :return: """ script = self._move(x=x, y=y, width=width, height=height) return self.engine.run_script(script) or None @@ -723,12 +723,12 @@ def windows(self): def find_windows(self, func=None, **kwargs): """ Find all matching windows - + :param func: a callable to filter windows :param bool exact: if False (the default) partial matches are found. If True, only exact matches are returned :param kwargs: keywords of attributes of the window (has no effect if ``func`` is provided) - - :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. """ if func is None: exact = kwargs.pop('exact', False) @@ -748,10 +748,10 @@ def func(win): def find_window(self, func=None, **kwargs): """ Like ``find_windows`` but only returns the first found window - - - :param func: - :param kwargs: + + + :param func: + :param kwargs: :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found """ with suppress(StopIteration): @@ -760,12 +760,12 @@ def find_window(self, func=None, **kwargs): def find_windows_by_title(self, title, exact=False): """ Equivalent to ``find_windows(title=title)``` - + Note that ``title`` is a ``bytes`` object - - :param bytes title: - :param exact: - :return: + + :param bytes title: + :param exact: + :return: """ for window in self.find_windows(title=title, exact=exact): yield window @@ -773,7 +773,7 @@ def find_windows_by_title(self, title, exact=False): def find_window_by_title(self, *args, **kwargs): """ Like ``find_windows_by_title`` but only returns the first result. - + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found """ with suppress(StopIteration): @@ -781,40 +781,40 @@ def find_window_by_title(self, *args, **kwargs): def find_windows_by_text(self, text, exact=False): """ - - :param text: - :param exact: - :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + + :param text: + :param exact: + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. """ for window in self.find_windows(text=text, exact=exact): yield window def find_window_by_text(self, *args, **kwargs): """ - - :param args: - :param kwargs: - :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + + :param args: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found """ with suppress(StopIteration): return next(self.find_windows_by_text(*args, **kwargs)) def find_windows_by_class(self, class_name, exact=False): """ - - :param class_name: - :param exact: - :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. + + :param class_name: + :param exact: + :return: a generator containing any matching :py:class:`~ahk.window.Window` objects. """ for window in self.find_windows(class_name=class_name, exact=exact): yield window def find_window_by_class(self, *args, **kwargs): """ - - :param args: - :param kwargs: - :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found + + :param args: + :param kwargs: + :return: a :py:class:`~ahk.window.Window` object or ``None`` if no matching window is found """ with suppress(StopIteration): return next(self.find_windows_by_class(*args, **kwargs)) diff --git a/appveyor.yml b/appveyor.yml index 743038b6..91196421 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,7 +20,7 @@ on_finish: - cmd: | venv\Scripts\activate.bat IF DEFINED COVERALLS_REPO_TOKEN (python -m coveralls) ELSE (echo skipping coveralls report for external pr) - + cache: - ahk_install.exe -> appveyor.yml diff --git a/ci/build.bat b/ci/build.bat index 160a6d6a..f96cd578 100644 --- a/ci/build.bat +++ b/ci/build.bat @@ -1,3 +1,3 @@ call venv\Scripts\activate.bat python setup.py sdist bdist_wheel -call deactivate \ No newline at end of file +call deactivate diff --git a/ci/ci_requirements.txt b/ci/ci_requirements.txt index 8f1f76b6..be420b8c 100644 --- a/ci/ci_requirements.txt +++ b/ci/ci_requirements.txt @@ -4,4 +4,4 @@ behave behave-classy coveralls wheel -pillow \ No newline at end of file +pillow diff --git a/ci/install.bat b/ci/install.bat index d1d91494..82cb276d 100644 --- a/ci/install.bat +++ b/ci/install.bat @@ -4,4 +4,4 @@ python -m pip install --upgrade pip python -m pip install --upgrade -r .\ci\ci_requirements.txt python -m pip install --upgrade . python -m pip install ".[binary]" -call deactivate \ No newline at end of file +call deactivate diff --git a/ci/runtests.ps1 b/ci/runtests.ps1 index 873896c9..08f73a0b 100644 --- a/ci/runtests.ps1 +++ b/ci/runtests.ps1 @@ -16,4 +16,4 @@ 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 +deactivate diff --git a/docs/Makefile b/docs/Makefile index 298ea9e2..51285967 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -16,4 +16,4 @@ help: # 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) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md index 504b806c..09cd21f1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,9 +3,9 @@ A Python wrapper around AHK. [![Docs](https://readthedocs.org/projects/ahk/badge/?version=latest)](https://ahk.readthedocs.io/en/latest/?badge=latest) -[![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/) +[![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) [![Downloads](https://pepy.tech/badge/ahk)](https://pepy.tech/project/ahk) @@ -18,7 +18,7 @@ Requires Python 3.6+ [Async API](#async-api) requires Python 3.8+ -See also [Non-Python dependencies](#non-python-dependencies) +See also [Non-Python dependencies](#non-python-dependencies) # Usage @@ -70,7 +70,7 @@ ahk.key_state('CapsLock', mode='T') # Check toggle state of a key (like for Num 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.key_wait('a', timeout=3) # Wait up to 3 seconds for the "a" key to be pressed. NOTE: This throws +ahk.key_wait('a', timeout=3) # Wait up to 3 seconds for the "a" key to be pressed. NOTE: This throws # a TimeoutError if the key isn't pressed within the timeout window ahk.set_capslock_state("on") # Turn CapsLock on ``` @@ -113,7 +113,7 @@ win.activate() # Give the window focus win.activate_bottom() # Give the window focus win.close() # Close the window win.hide() # Hide the windwow -win.kill() # Kill the window +win.kill() # Kill the window win.maximize() # Maximize the window win.minimize() # Minimize the window win.restore() # Restore the window @@ -127,7 +127,7 @@ win.always_on_top = True # Make the window always on top for window in ahk.windows(): print(window.title) - + # Some more attributes print(window.text) print(window.rect) # (x, y, width, height) @@ -222,14 +222,14 @@ You should see an output something like ``` ## Add directives -You can add directives that will be added to all generated scripts. +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]) +ahk = AHK(directives=[NoTrayIcon]) ``` By default, some directives are automatically added to ensure functionality and are merged with any user-provided directives. @@ -274,7 +274,7 @@ print(result.stdout) # b'Hello Data!' ## Preview features -Preview features are experimental features that are may not be fully functional. +Preview features are experimental features that are may not be fully functional. These features are (even more) likely to have breaking changes without warning. Github issues are provided for convenience to collect feedback on these features. @@ -282,8 +282,8 @@ Github issues are provided for convenience to collect feedback on these features ## AHKDaemon -Normally, `AHK` works by creating a new subprocess for every command invocation. Because processes are expensive -to create in Windows, this can lead to performance issues for some use-cases. `AHKDaemon` allows all AHK commands to be +Normally, `AHK` works by creating a new subprocess for every command invocation. Because processes are expensive +to create in Windows, this can lead to performance issues for some use-cases. `AHKDaemon` allows all AHK commands to be carried out in a single process, as opposed to running each command in a new subprocess, improving performance. Some other details change in Daemon mode, such as persistence of state (e.g. changes to CoordMode). @@ -296,8 +296,8 @@ daemon.start() daemon.mouse_move(100, 100) ``` -For the most part, the AHK Daemon works just like the regular `AHK` class, with a few caveats. Most notably, AHKDaemon -does not allow you to run arbitrary AutoHotkey scripts and does not yet support Hotkeys. However, you can always use +For the most part, the AHK Daemon works just like the regular `AHK` class, with a few caveats. Most notably, AHKDaemon +does not allow you to run arbitrary AutoHotkey scripts and does not yet support Hotkeys. However, you can always use the normal `AHK` class alongside the daemon for these needs. `AsyncAHKDaemon` is also available for asyncio support. @@ -307,7 +307,7 @@ In the future, AHKDaemon may become the default implementation. ## Async API -An async API is provided so functions can be called using `async`/`await`. +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 @@ -324,27 +324,27 @@ asyncio.run(main()) ``` For the most part, the async API is identical to that of the normal API, with a few exceptions: -While properties (like `.mouse_position` or `.title` for windows) can be `await`ed, +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. -Property setters have different (probably undesired) behavior -in the async API. Instead, you should use a comparable method. If you _do_ use the property setters, the invocation is created using `asyncio.create_task()`, which means -that the task won't run until control is yielded back to the event loop. For now, this will also raise a warning to the same. +Property setters have different (probably undesired) behavior +in the async API. Instead, you should use a comparable method. If you _do_ use the property setters, the invocation is created using `asyncio.create_task()`, which means +that the task won't run until control is yielded back to the event loop. For now, this will also raise a warning to the same. -Lastly, while it's possible to pass `blocking=False` in the async API, this sometimes will cause problems with certain functions. For now, a warning is raised in this case. +Lastly, while it's possible to pass `blocking=False` in the async API, this sometimes will cause problems with certain functions. For now, a warning is raised in this case. ```python ahk = AsyncAHK() async def main(): pos = ahk.mouse_position # BAD! Does not work! - pos = await ahk.mouse_position # OK. Works, but looks kind of weird - pos = await ahk.get_mouse_position() # GOOD! - + pos = await ahk.mouse_position # OK. Works, but looks kind of weird + pos = await ahk.get_mouse_position() # GOOD! + # BAD: You probably don't want to do this ahk.mouse_position = (100, 100) # won't do anything right away. Raises warning print(await ahk.get_mouse_position()) # probably won't be 100,100 - + # GOOD: Instead, do this: await ahk.mouse_move(100, 100, speed=0) assert await ahk.get_mouse_position() == (100, 100) @@ -355,10 +355,10 @@ async def main(): [GH-9] -Hotkeys now have a primitive implementation. You give it a hotkey (a string the same as in an ahk script, without the `::`) +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. -Right now, only AHK code is supported as callbacks for hotkeys. +Right now, only AHK code is supported as callbacks for hotkeys. Support for Python callbacks via the Async API is planned. ```python @@ -371,7 +371,7 @@ script = 'Run Notepad' # Define an ahk script hotkey = Hotkey(ahk, key_combo, script) # Create Hotkey hotkey.start() # Start listening for hotkey ``` -At this point, the hotkey is active. +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. @@ -407,7 +407,7 @@ 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 +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`) @@ -415,10 +415,10 @@ generally recommended. Currently, there is limited support for interacting with [GH-26] -Right now, these are implemented by iterating over all window handles and filtering with Python. +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_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. @@ -430,7 +430,7 @@ There are couple convenience functions, but not sure if these will stay around o ## Errors and Debugging -You can enable debug logging, which will output script text before execution, and some other potentially useful +You can enable debug logging, which will output script text before execution, and some other potentially useful debugging information. ```python @@ -439,7 +439,7 @@ logging.basicConfig(level=logging.DEBUG) ``` (See the [logging module documentation](https://docs.python.org/3/library/logging.html) for more information) -Also note that, for now, errors with running AHK scripts will often pass silently. In the future, better error handling +Also note that, for now, errors with running AHK scripts will often pass silently. In the future, better error handling will be added. @@ -481,7 +481,7 @@ 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 +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. diff --git a/docs/api/autohotkey.rst b/docs/api/autohotkey.rst index 2ddaa0b2..25b11a63 100644 --- a/docs/api/autohotkey.rst +++ b/docs/api/autohotkey.rst @@ -4,4 +4,3 @@ Autohotkey .. automodule:: ahk.autohotkey :members: :undoc-members: - diff --git a/docs/api/index.rst b/docs/api/index.rst index 3737c2d5..fbdf3efa 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -11,4 +11,3 @@ about the programming interface. A lot of this is auto-generated documentation f :glob: * - diff --git a/docs/api/keys.rst b/docs/api/keys.rst index 81a77c3d..fe58462d 100644 --- a/docs/api/keys.rst +++ b/docs/api/keys.rst @@ -8,5 +8,3 @@ Keys .. autoclass:: KEYS :members: :undoc-members: - - diff --git a/docs/conf.py b/docs/conf.py index 2c31e7c8..f83818fd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -180,4 +180,4 @@ epub_exclude_files = ['search.html'] -# -- Extension configuration ------------------------------------------------- \ No newline at end of file +# -- Extension configuration ------------------------------------------------- diff --git a/tests/features/mouse_move.feature b/tests/features/mouse_move.feature index eef7584c..19295629 100644 --- a/tests/features/mouse_move.feature +++ b/tests/features/mouse_move.feature @@ -5,4 +5,3 @@ Feature: Mouse functionality 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 index f2125363..bd545a02 100644 --- a/tests/features/steps/ahk_steps.py +++ b/tests/features/steps/ahk_steps.py @@ -28,4 +28,4 @@ def check_position(self, xpos, ypos): assert x == xpos assert y == ypos -AHKSteps().register() \ No newline at end of file +AHKSteps().register() diff --git a/tests/unittests/test_daemon_async.py b/tests/unittests/test_daemon_async.py index 987f35bd..75cc8357 100644 --- a/tests/unittests/test_daemon_async.py +++ b/tests/unittests/test_daemon_async.py @@ -246,4 +246,3 @@ async def test_multi_line_response(self): text = await notepad.get_text() assert b'Hello\r\n' in text assert b'World!' in text - diff --git a/tests/unittests/test_mouse.py b/tests/unittests/test_mouse.py index e384057d..773cc5fd 100644 --- a/tests/unittests/test_mouse.py +++ b/tests/unittests/test_mouse.py @@ -81,5 +81,3 @@ async def test_mouse_move(self): x, y = await self.ahk.mouse_position await self.ahk.mouse_move(10, 10, relative=True) assert await self.ahk.mouse_position == (x+10, y+10) - - diff --git a/tests/unittests/test_screen.py b/tests/unittests/test_screen.py index 798d8e06..fd78267b 100644 --- a/tests/unittests/test_screen.py +++ b/tests/unittests/test_screen.py @@ -59,4 +59,4 @@ def setUp(self): def tearDown(self): super().tearDown() - self.ahk.stop() \ No newline at end of file + self.ahk.stop() diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index 7dc4c1a7..96b1fd4a 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -79,7 +79,7 @@ def test_height_change(self): current_height = self.win.height self.win.height = current_height + 100 assert self.win.height == current_height + 100 - + def test_width_change(self): current_width = self.win.width self.win.width = current_width + 100 @@ -112,4 +112,4 @@ def setUp(self): def tearDown(self): super().tearDown() - self.ahk.stop() \ No newline at end of file + self.ahk.stop() From ea1e05dace4e1e6485bdbdda9532771100dee04d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Nov 2021 17:10:57 -0800 Subject: [PATCH 170/588] single quote strings --- .pre-commit-config.yaml | 1 + README.md | 2 +- ahk/daemon.py | 22 ++-- ahk/directives.py | 4 +- ahk/gui.py | 14 +-- ahk/keyboard.py | 50 ++++----- ahk/keys.py | 2 +- ahk/mouse.py | 2 +- ahk/registry.py | 24 ++--- ahk/script.py | 10 +- ahk/window.py | 138 ++++++++++++------------- setup.py | 4 +- tests/unittests/test_daemon.py | 32 +++--- tests/unittests/test_daemon_async.py | 26 ++--- tests/unittests/test_gui.py | 28 ++--- tests/unittests/test_keyboard.py | 80 +++++++------- tests/unittests/test_keyboard_async.py | 48 ++++----- tests/unittests/test_mouse.py | 4 +- tests/unittests/test_window.py | 2 +- tests/unittests/test_window_async.py | 4 +- 20 files changed, 249 insertions(+), 248 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8f3bc8a..c9ec7dae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,3 +5,4 @@ repos: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace + - id: double-quote-string-fixer diff --git a/README.md b/README.md index 0e01b430..ca5368c3 120000 --- a/README.md +++ b/README.md @@ -1 +1 @@ -docs/README.md \ No newline at end of file +docs/README.md diff --git a/ahk/daemon.py b/ahk/daemon.py index 3141e16d..f4c9feb3 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -35,7 +35,7 @@ def __init__(self, *args, **kwargs): def _run(self): if self._is_running: - raise RuntimeError("Already running") + raise RuntimeError('Already running') self._is_running = True runargs = [self.executable_path, self._template] proc = subprocess.Popen(runargs, @@ -94,14 +94,14 @@ def render_template(self, template_name, directives=None, blocking=True, **kwarg def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): if not blocking: - warnings.warn("blocking=False in daemon mode is not supported", stacklevel=3) + warnings.warn('blocking=False in daemon mode is not supported', stacklevel=3) if not self._is_running: - raise RuntimeError("Not running! Must call .start() first!") + raise RuntimeError('Not running! Must call .start() first!') script_text = script_text.replace('#NoEnv', '', 1) with self.run_lock: for line in script_text.split('\n'): line = line.strip() - if not line or line.startswith("FileAppend"): + if not line or line.startswith('FileAppend'): continue self.queue.put_nowait(line.encode('utf-8')) self.queue.join() @@ -118,14 +118,14 @@ def type(self, s, *args, **kwargs): s = escape(s) self.send(s, *args, **kwargs) - def show_tooltip(self, text: str, second=None, x="", y="", id="", blocking=True): + def show_tooltip(self, text: str, second=None, x='', y='', id='', blocking=True): return super().show_tooltip(text, second=second, x=x, y=y, id=id, blocking=blocking) def hide_tooltip(self, id): return super().show_tooltip(text='', second='', x='', y='', id=id) def hide_traytip(self): - self.run_script("HideTrayTip") + self.run_script('HideTrayTip') @staticmethod def escape_sequence_replace(s): @@ -152,7 +152,7 @@ def __init__(self, *args, **kwargs): async def _run(self): if self._is_running: - raise RuntimeError("Already running") + raise RuntimeError('Already running') self._is_running = True runargs = [self.executable_path, self._template] proc = await asyncio.subprocess.create_subprocess_exec(*runargs, @@ -212,12 +212,12 @@ def render_template(self, template_name, directives=None, blocking=True, **kwarg async def a_run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): if not self._is_running: - raise RuntimeError("Not running! Must await .start() first!") + raise RuntimeError('Not running! Must await .start() first!') script_text = script_text.replace('#NoEnv', '', 1) async with self.run_lock: for line in script_text.split('\n'): line = line.strip() - if not line or line.startswith("FileAppend"): + if not line or line.startswith('FileAppend'): continue self.queue.put_nowait(line.encode('utf-8')) await self.queue.join() @@ -234,14 +234,14 @@ async def type(self, s, *args, **kwargs): s = escape(s) await self.send(s, *args, **kwargs) - def show_tooltip(self, text: str, second=None, x="", y="", id="", blocking=True): + def show_tooltip(self, text: str, second=None, x='', y='', id='', blocking=True): return super().show_tooltip(text, second=second, x=x, y=y, id=id, blocking=blocking) def hide_tooltip(self, id): return super().show_tooltip(text='', second='', x='', y='', id=id) async def hide_traytip(self): - await self.run_script("HideTrayTip") + await self.run_script('HideTrayTip') @staticmethod def escape_sequence_replace(s): diff --git a/ahk/directives.py b/ahk/directives.py index c7bb0abc..4de4c645 100644 --- a/ahk/directives.py +++ b/ahk/directives.py @@ -11,7 +11,7 @@ class DirectiveMeta(type): Overrides __hash__ to make objects 'unique' based upon a hash of the str representation """ def __str__(cls): - return f"#{cls.__name__}" + return f'#{cls.__name__}' def __hash__(self): return hash(str(self)) @@ -39,7 +39,7 @@ def __str__(self): 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 diff --git a/ahk/gui.py b/ahk/gui.py index be60eb71..1d14431a 100644 --- a/ahk/gui.py +++ b/ahk/gui.py @@ -11,7 +11,7 @@ def __init__(self, *args, **kwargs): self.window_encoding = kwargs.pop('window_encoding', None) super().__init__(*args, **kwargs) - def show_tooltip(self, text: str, second=1.0, x="", y="", id="", blocking=True): + def show_tooltip(self, text: str, second=1.0, x='', y='', id='', blocking=True): """Show ToolTip https://www.autohotkey.com/docs/commands/ToolTip.htm @@ -29,10 +29,10 @@ def show_tooltip(self, text: str, second=1.0, x="", y="", id="", blocking=True): :raises ValueError: ID must be between [1, 20] """ if id and not (1 <= int(id) <= 20): - raise ValueError("ID value must be between [1, 20]") + raise ValueError('ID value must be between [1, 20]') - encoded_text = "% " + "".join([f"Chr({hex(ord(char))})" for char in text]) - script = self.render_template("gui/tooltip.ahk", text=encoded_text, second=second, x=x, y=y, id=id) + encoded_text = '% ' + ''.join([f'Chr({hex(ord(char))})' for char in text]) + script = self.render_template('gui/tooltip.ahk', text=encoded_text, second=second, x=x, y=y, id=id) return self.run_script(script, blocking=blocking) or None def _show_traytip( @@ -56,11 +56,11 @@ def _show_traytip( :type large_icon: bool, optional """ - encoded_title = "% " + "".join([f"Chr({hex(ord(char))})" for char in title]) - encoded_text = "% " + "".join([f"Chr({hex(ord(char))})" for char in text]) + encoded_title = '% ' + ''.join([f'Chr({hex(ord(char))})' for char in title]) + encoded_text = '% ' + ''.join([f'Chr({hex(ord(char))})' for char in text]) option = type_id + (16 if slient else 0) + (32 if large_icon else 0) script = self.render_template( - "gui/traytip.ahk", title=encoded_title, text=encoded_text, second=second, option=option + 'gui/traytip.ahk', title=encoded_title, text=encoded_text, second=second, option=option ) return self.run_script(script, blocking=blocking) or None diff --git a/ahk/keyboard.py b/ahk/keyboard.py index 7b8e2239..378e08ca 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -20,7 +20,7 @@ def __init__(self, engine: ScriptEngine, hotkey: str, script: str): @property def running(self): - return hasattr(self, "_proc") + return hasattr(self, '_proc') def _start(self, script): try: @@ -34,9 +34,9 @@ def start(self): Starts an AutoHotkey process with the hotkey script """ if self.running: - raise RuntimeError("Hotkey is already running") + raise RuntimeError('Hotkey is already running') script = self.engine.render_template( - "hotkey.ahk", blocking=False, script=self.script, hotkey=self.hotkey + 'hotkey.ahk', blocking=False, script=self.script, hotkey=self.hotkey ) self._gen = self._start(script) proc = next(self._gen) @@ -53,7 +53,7 @@ def stop(self): Stops the process if it is running """ if not self.running: - raise RuntimeError("Hotkey is not running") + raise RuntimeError('Hotkey is not running') try: next(self._gen) except StopIteration: @@ -71,7 +71,7 @@ def hotkey(self, *args, **kwargs): :param script: The script to execute when the hotkey is activated (AutoHotkey code as a string) :return: an :py:class:`~ahk.keyboard.Hotkey` instance """ - engine = kwargs.pop("engine", self) + engine = kwargs.pop('engine', self) return Hotkey(engine, *args, **kwargs) def _key_state(self, key_name, mode=None) -> str: @@ -85,7 +85,7 @@ def _key_state(self, key_name, mode=None) -> str: :return: True if pressed down, else False """ script = self.render_template( - "keyboard/key_state.ahk", + 'keyboard/key_state.ahk', key_name=key_name, mode=mode, directives=(InstallMouseHook, InstallKeybdHook), @@ -101,15 +101,15 @@ def key_state(self, key_name, mode=None) -> bool: def _key_wait( self, key_name, timeout: int = None, logical_state=False, released=False ) -> str: - options = "" + options = '' if not released: - options += "D" + options += 'D' if logical_state: - options += "L" + options += 'L' if timeout: - options += f"T{timeout}" + options += f'T{timeout}' script = self.render_template( - "keyboard/key_wait.ahk", key_name=key_name, options=options + 'keyboard/key_wait.ahk', key_name=key_name, options=options ) return script def key_wait( @@ -130,8 +130,8 @@ def key_wait( result = self.run_script(self._key_wait( key_name, timeout=timeout, logical_state=logical_state, released=released )) - if result == "1": - raise TimeoutError(f"timed out waiting for {key_name}") + if result == '1': + raise TimeoutError(f'timed out waiting for {key_name}') def type(self, s, blocking=True): """ @@ -145,7 +145,7 @@ def type(self, s, blocking=True): def _send(self, s, raw=False, delay=None, blocking=True): script = self.render_template( - "keyboard/send.ahk", s=s, raw=raw, delay=delay, blocking=blocking + 'keyboard/send.ahk', s=s, raw=raw, delay=delay, blocking=blocking ) return script @@ -182,11 +182,11 @@ def _send_input(self, s, blocking=True): """ 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." + '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, blocking=blocking) + script = self.render_template('keyboard/send_input.ahk', s=s, blocking=blocking) return script def send_input(self, s, blocking=True): @@ -194,7 +194,7 @@ def send_input(self, s, blocking=True): return self.run_script(script, blocking=blocking) or None def _send_play(self, s): - script = self.render_template("keyboard/send_play.ahk", s=s) + script = self.render_template('keyboard/send_play.ahk', s=s) return script def send_play(self, s): @@ -209,7 +209,7 @@ def send_play(self, s): return self.run_script(script) or None def _send_event(self, s, delay=None): - script = self.render_template("keyboard/send_event.ahk", s=s, delay=delay) + script = self.render_template('keyboard/send_event.ahk', s=s, delay=delay) return script #self.run_script(script) @@ -269,16 +269,16 @@ def key_up(self, key, blocking=True): def _set_capslock_state(self, state=None): if isinstance(state, str): state = state.lower() - if state not in ("on", "off", "alwayson", 'alwaysoff'): + if state not in ('on', 'off', 'alwayson', 'alwaysoff'): raise ValueError(f'state value must be one of "On"|"Off"|"AlwaysOn"|"AlwaysOff" - not {repr(state)}') elif isinstance(state, bool): if state: - state = "on" + state = 'on' else: - state = "off" + state = 'off' - script = self.render_template("keyboard/set_capslock_state.ahk", state=state) + script = self.render_template('keyboard/set_capslock_state.ahk', state=state) return script # self.run_script(script) @@ -314,8 +314,8 @@ async def key_wait( result = await self.a_run_script(self._key_wait( key_name, timeout=timeout, logical_state=logical_state, released=released )) - if result == "1": - raise TimeoutError(f"timed out waiting for {key_name}") + if result == '1': + raise TimeoutError(f'timed out waiting for {key_name}') async def key_press(self, key, release=True, blocking=True): await self.key_down(key, blocking=blocking) diff --git a/ahk/keys.py b/ahk/keys.py index c73a0e58..90c2cb23 100644 --- a/ahk/keys.py +++ b/ahk/keys.py @@ -167,7 +167,7 @@ class KEYS: RAlt = RIGHT_ALT SHIFT = KeyModifier('Shift') Shift = SHIFT - LEFT_SHIFT = KeyModifier("LShift") + LEFT_SHIFT = KeyModifier('LShift') LShift = LEFT_SHIFT RIGHT_SHIFT = KeyModifier('RShift') RShift = RIGHT_SHIFT diff --git a/ahk/mouse.py b/ahk/mouse.py index f53af540..73cd97d0 100644 --- a/ahk/mouse.py +++ b/ahk/mouse.py @@ -279,7 +279,7 @@ async def get_mouse_position(self, mode=None): @MouseMixin.mouse_position.setter def mouse_position(self, position): - warnings.warn("mouse_position setter only schedules coroutine. use mouse_move() (with speed=0) instead") + warnings.warn('mouse_position setter only schedules coroutine. use mouse_move() (with speed=0) instead') x, y = position coro = self.mouse_move(x=x, y=y, speed=0, relative=False) asyncio.create_task(coro) diff --git a/ahk/registry.py b/ahk/registry.py index eae3fff6..3ab962f3 100644 --- a/ahk/registry.py +++ b/ahk/registry.py @@ -4,13 +4,13 @@ class RegistryMixin(ScriptEngine): def _render_template(self, template_name, *args, **kwargs): - return self.render_template(os.path.join("registery", template_name)) + return self.render_template(os.path.join('registery', template_name)) def _run_template(self, template_name, *args, **kwargs): script = self._render_template(template_name, *args, **kwargs) return self.run_script(script) - def reg_read(self, key_name: str, value_name="") -> str: + def reg_read(self, key_name: str, value_name='') -> str: """Read registery Reference: @@ -25,9 +25,9 @@ def reg_read(self, key_name: str, value_name="") -> str: Returns: str -- Registery value """ - return self._run_template("reg_read.ahk", key_name=key_name, value_name=value_name) + return self._run_template('reg_read.ahk', key_name=key_name, value_name=value_name) - def reg_delete(self, key_name: str, value_name="") -> None: + def reg_delete(self, key_name: str, value_name='') -> None: """Delete registery Reference: @@ -39,9 +39,9 @@ def reg_delete(self, key_name: str, value_name="") -> None: Keyword Arguments: value_name {str} -- TODO (default: {""}) """ - return self._render_template("reg_delete.ahk", key_name=key_name, value_name=value_name) + return self._render_template('reg_delete.ahk', key_name=key_name, value_name=value_name) - def reg_write(self, value_type: str, key_name: str, value_name="") -> None: + def reg_write(self, value_type: str, key_name: str, value_name='') -> None: """Write registery Reference: @@ -54,7 +54,7 @@ def reg_write(self, value_type: str, key_name: str, value_name="") -> None: Keyword Arguments: value_name {str} -- TODO (default: {""}) """ - return self._render_template("reg_write.ahk", value_type=value_type, key_name=key_name, value_name=value_name) + return self._render_template('reg_write.ahk', value_type=value_type, key_name=key_name, value_name=value_name) def reg_set_view(self, reg_view: int) -> None: """Set registery view @@ -66,12 +66,12 @@ def reg_set_view(self, reg_view: int) -> None: reg_view {str} -- Registery view """ - if reg_view not in [32, 64, "32", "64"]: - raise ValueError("No valid bit, please use 32 or 64") + if reg_view not in [32, 64, '32', '64']: + raise ValueError('No valid bit, please use 32 or 64') - return self._run_template("reg_set_view.ahk", reg_view=reg_view) or None + return self._run_template('reg_set_view.ahk', reg_view=reg_view) or None - def reg_loop(self, reg: str, key_name: str, mode=""): + def reg_loop(self, reg: str, key_name: str, mode=''): """Loop registery Reference: @@ -86,7 +86,7 @@ def reg_loop(self, reg: str, key_name: str, mode=""): """ raise NotImplementedError - return self._run_template("reg_loop.ahk", reg=reg, key_name=key_name, mode=mode) or None + return self._run_template('reg_loop.ahk', reg=reg, key_name=key_name, mode=mode) or None def read(self, *args, **kwargs): import warnings diff --git a/ahk/script.py b/ahk/script.py index c34883b3..17ff969f 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -26,7 +26,7 @@ class ExecutableNotFoundError(EnvironmentError): pass -DEFAULT_EXECUTABLE_PATH = r"C:\Program Files\AutoHotkey\AutoHotkey.exe" +DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' """The deafult path to look for AutoHotkey, if not specified some other way""" @@ -56,8 +56,8 @@ def _resolve_executable_path(executable_path: str = ''): if os.path.isdir(executable_path): raise ExecutableNotFoundError( - 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" + 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' ) if not executable_path.endswith('.exe'): @@ -70,7 +70,7 @@ def _resolve_executable_path(executable_path: str = ''): class ScriptEngine(object): - def __init__(self, executable_path: str = "", directives: Set = None, **kwargs): + def __init__(self, executable_path: str = '', directives: Set = None, **kwargs): """ This class is typically not used directly. AHK components inherit from this class and the arguments for this class should usually be passed in to :py:class:`~ahk.AHK`. @@ -156,7 +156,7 @@ def _run_script(self, script_text, **kwargs): async def _a_run_script(self, script_text, **kwargs): blocking = kwargs.pop('blocking', True) if blocking is not True: - warnings.warn("blocking=False will probably result in problems", stacklevel=2) + warnings.warn('blocking=False will probably result in problems', stacklevel=2) runargs = [self.executable_path, '/ErrorStdOut', '*'] proc = await asyncio.subprocess.create_subprocess_exec(*runargs, stdin=asyncio.subprocess.PIPE, diff --git a/ahk/window.py b/ahk/window.py index 51a119b2..7077e1e2 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -94,9 +94,9 @@ def send(self, raw=False): class Window(object): - MINIMIZED = "-1" - MAXIMIZED = "1" - NON_MIN_NON_MAX = "0" + MINIMIZED = '-1' + MAXIMIZED = '1' + NON_MIN_NON_MAX = '0' _set_subcommands = { 'always_on_top': 'AlwaysOnTop', @@ -121,7 +121,7 @@ class Window(object): 'process': 'ProcessPath', 'count': 'count', 'list': 'list', - 'min_max': "MinMax", + 'min_max': 'MinMax', 'controls': 'ControlList', 'controls_hwnd': 'ControlListHwnd', 'transparent': 'Transparent', @@ -148,7 +148,7 @@ def from_mouse_position(cls, engine: ScriptEngine, **kwargs): @classmethod def from_pid(cls, engine: ScriptEngine, pid, **kwargs): script = engine.render_template('window/get.ahk', - subcommand="ID", + subcommand='ID', title=f'ahk_pid {pid}') ahk_id = engine.run_script(script) return cls(engine=engine, ahk_id=ahk_id, **kwargs) @@ -166,7 +166,7 @@ def _get(self, subcommand): script = self._render_template( 'window/get.ahk', subcommand=sub, - title=f"ahk_id {self.id}", + title=f'ahk_id {self.id}', ) return script @@ -187,7 +187,7 @@ def _set(self, subcommand, value): 'window/win_set.ahk', subcommand=subcommand, value=value, - title=f"ahk_id {self.id}" + title=f'ahk_id {self.id}' ) return script @@ -198,7 +198,7 @@ def set(self, subcommand, value): def _get_pos(self, info=None): script = self._render_template( 'window/win_position.ahk', - title=f"ahk_id {self.id}", + title=f'ahk_id {self.id}', pos_info=info ) return script @@ -256,9 +256,9 @@ def height(self, new_height): def _base_check(self, command): script = self._render_template( - "window/base_check.ahk", + 'window/base_check.ahk', command=command, - title=f"ahk_id {self.id}" + title=f'ahk_id {self.id}' ) return script @@ -272,20 +272,20 @@ def active(self): return self.is_active() def is_active(self): - return self._base_property(command="WinActive") + return self._base_property(command='WinActive') @property def exist(self): return self.exists() def exists(self): - return self._base_property(command="WinExist") + return self._base_property(command='WinExist') def _base_get_method_(self, command): script = self._render_template( - "window/base_get_command.ahk", + 'window/base_get_command.ahk', command=command, - title=f"ahk_id {self.id}" + title=f'ahk_id {self.id}' ) return script def _base_get_method(self, command): @@ -305,12 +305,12 @@ def title(self): return self.get_title() def get_title(self): - return self._base_get_method("WinGetTitle") + return self._base_get_method('WinGetTitle') def _set_title(self, value): script = self._render_template( - "window/win_set_title.ahk", - title=f"ahk_id {self.id}", + 'window/win_set_title.ahk', + title=f'ahk_id {self.id}', new_title=value ) return script @@ -341,30 +341,30 @@ def maximized(self): return self.is_maximized() def is_minimized(self): - return self.get("MinMax") == self.MINIMIZED + return self.get('MinMax') == self.MINIMIZED def is_maximized(self): - return self.get("MinMax") == self.MAXIMIZED + return self.get('MinMax') == self.MAXIMIZED @property def non_max_non_min(self): - return self.get("MinMax") == self.NON_MIN_NON_MAX + return self.get('MinMax') == self.NON_MIN_NON_MAX def is_minmax(self): - return self.get("MinMax") != self.NON_MIN_NON_MAX + return self.get('MinMax') != self.NON_MIN_NON_MAX def get_class_name(self): - return self._base_get_method("WinGetClass") + return self._base_get_method('WinGetClass') def get_text(self): - return self._base_get_method("WinGetText") + return self._base_get_method('WinGetText') @property def transparent(self) -> int: return self.get_transparency() def get_transparency(self) -> int: - result = self.get("Transparent") + result = self.get('Transparent') if result: return int(result) else: @@ -376,7 +376,7 @@ def transparent(self, value): def set_transparency(self, value): if isinstance(value, int) and 0 <= value <= 255: - return self.set("Transparent", value) or None + return self.set('Transparent', value) or None else: raise ValueError( f'"{value}" not a valid option. Please use [0, 255] integer' @@ -385,7 +385,7 @@ def set_transparency(self, value): def _always_on_top(self): script = self._render_template( 'window/win_is_always_on_top.ahk', - title=f"ahk_id {self.id}" + title=f'ahk_id {self.id}' ) return script @@ -451,16 +451,16 @@ def _render_template(self, *args, **kwargs): kwargs['win'] = self return self.engine.render_template(*args, **kwargs) - def _base_method_(self, command, seconds_to_wait="", blocking=False): + def _base_method_(self, command, seconds_to_wait='', blocking=False): script = self._render_template( - "window/base_command.ahk", + 'window/base_command.ahk', command=command, - title=f"ahk_id {self.id}", + title=f'ahk_id {self.id}', seconds_to_wait=seconds_to_wait ) return script - def _base_method(self, command, seconds_to_wait="", blocking=True): + def _base_method(self, command, seconds_to_wait='', blocking=True): script = self._base_method_(command, seconds_to_wait=seconds_to_wait) return self.engine.run_script(script, blocking=blocking) @@ -474,7 +474,7 @@ def activate(self): :return: """ - return self._base_method("WinActivate") or None + return self._base_method('WinActivate') or None def activate_bottom(self): """ @@ -484,9 +484,9 @@ def activate_bottom(self): :return: """ - return self._base_method("WinActivateBottom") or None + return self._base_method('WinActivateBottom') or None - def close(self, seconds_to_wait=""): + def close(self, seconds_to_wait=''): """ Closes the Window. See also: `WinClose`_ @@ -495,7 +495,7 @@ def close(self, seconds_to_wait=""): :param seconds_to_wait: :return: """ - return self._base_method("WinClose", seconds_to_wait=seconds_to_wait) or None + return self._base_method('WinClose', seconds_to_wait=seconds_to_wait) or None def hide(self): """ @@ -506,10 +506,10 @@ def hide(self): :return: """ - return self._base_method("WinHide") or None + return self._base_method('WinHide') or None - def kill(self, seconds_to_wait=""): - return self._base_method("WinKill", seconds_to_wait=seconds_to_wait) or None + def kill(self, seconds_to_wait=''): + return self._base_method('WinKill', seconds_to_wait=seconds_to_wait) or None def maximize(self): """ @@ -517,7 +517,7 @@ def maximize(self): :return: """ - return self._base_method("WinMaximize") or None + return self._base_method('WinMaximize') or None def minimize(self): """ @@ -525,7 +525,7 @@ def minimize(self): :return: """ - return self._base_method("WinMinimize") or None + return self._base_method('WinMinimize') or None def restore(self): """ @@ -533,7 +533,7 @@ def restore(self): :return: """ - return self._base_method("WinRestore") or None + return self._base_method('WinRestore') or None def show(self): """ @@ -541,44 +541,44 @@ def show(self): :return: """ - return self._base_method("WinShow") or None + return self._base_method('WinShow') or None - def wait(self, seconds_to_wait=""): + def wait(self, seconds_to_wait=''): """ :param seconds_to_wait: :return: """ - return self._base_method("WinWait", seconds_to_wait=seconds_to_wait, blocking=True) or None + return self._base_method('WinWait', seconds_to_wait=seconds_to_wait, blocking=True) or None - def wait_active(self, seconds_to_wait=""): + def wait_active(self, seconds_to_wait=''): """ :param seconds_to_wait: :return: """ - return self._base_method("WinWaitActive", seconds_to_wait=seconds_to_wait, blocking=True) or None + return self._base_method('WinWaitActive', seconds_to_wait=seconds_to_wait, blocking=True) or None - def wait_not_active(self, seconds_to_wait=""): + def wait_not_active(self, seconds_to_wait=''): """ :param seconds_to_wait: :return: """ - return self._base_method("WinWaitNotActive", seconds_to_wait=seconds_to_wait, blocking=True) or None + return self._base_method('WinWaitNotActive', seconds_to_wait=seconds_to_wait, blocking=True) or None - def wait_close(self, seconds_to_wait=""): + def wait_close(self, seconds_to_wait=''): """ :param seconds_to_wait: :return: """ - return self._base_method("WinWaitClose", seconds_to_wait=seconds_to_wait, blocking=True) or None + return self._base_method('WinWaitClose', seconds_to_wait=seconds_to_wait, blocking=True) or None def _move(self, x='', y='', width=None, height=None): script = self._render_template( 'window/win_move.ahk', - title=f"ahk_id {self.id}", + title=f'ahk_id {self.id}', x=x, y=y, width=width, height=height ) return script @@ -601,7 +601,7 @@ def _send(self, keys, delay=10, raw=False, blocking=False, escape=False, press_d keys = self.engine.escape_sequence_replace(keys) script = self._render_template( 'window/win_send.ahk', - title=f"ahk_id {self.id}", + title=f'ahk_id {self.id}', keys=keys, raw=raw, delay=delay, press_duration=press_duration, blocking=blocking ) @@ -627,7 +627,7 @@ def _click(self, x=None, y=None, *, button=None, n=1, options=None, blocking=Tru script = self._render_template( 'window/win_click.ahk', - x=x, y=y, hwnd=f"ahk_id {self.id}", button=button, n=n, options=options + x=x, y=y, hwnd=f'ahk_id {self.id}', button=button, n=n, options=options ) return script @@ -831,7 +831,7 @@ async def from_mouse_position(cls, engine: ScriptEngine, **kwargs): @classmethod async def from_pid(cls, engine: ScriptEngine, pid, **kwargs): script = engine.render_template('window/get.ahk', - subcommand="ID", + subcommand='ID', title=f'ahk_pid {pid}') ahk_id = await engine.a_run_script(script) return cls(engine=engine, ahk_id=ahk_id, **kwargs) @@ -852,27 +852,27 @@ async def get_pos(self, info=None): @Window.rect.setter def rect(self, new_position): - warnings.warn("rect setter only schedules coroutine. window may not change immediately. Use move() instead", stacklevel=2) + warnings.warn('rect setter only schedules coroutine. window may not change immediately. Use move() instead', stacklevel=2) x, y, width, height = new_position coro = self.move(x=x, y=y, width=width, height=height) asyncio.create_task(coro) @Window.position.setter def position(self, new_position): - warnings.warn("position setter only schedules coroutine. window may not change immediately. use set_position() instead", stacklevel=2) + warnings.warn('position setter only schedules coroutine. window may not change immediately. use set_position() instead', stacklevel=2) x, y = new_position coro = self.move(x, y) asyncio.create_task(coro) @Window.width.setter def width(self, new_width): - warnings.warn("width setter only schedules coroutine. window may not change immediately. use move() instead", stacklevel=2) + warnings.warn('width setter only schedules coroutine. window may not change immediately. use move() instead', stacklevel=2) coro = self.move(width=new_width) asyncio.create_task(coro) @Window.height.setter def height(self, new_height): - warnings.warn("height setter only schedules coroutine. window may not change immediately. use move() instead", stacklevel=2) + warnings.warn('height setter only schedules coroutine. window may not change immediately. use move() instead', stacklevel=2) coro = self.move(height=new_height) asyncio.create_task(coro) @@ -890,26 +890,26 @@ async def _base_get_method(self, command): @Window.title.setter def title(self, new_title): - warnings.warn("title setter only schedules coroutine. window may not change immediately. use set_title() instead", stacklevel=2) + warnings.warn('title setter only schedules coroutine. window may not change immediately. use set_title() instead', stacklevel=2) coro = self.set_title(new_title) asyncio.create_task(coro) async def is_minimized(self): - return await self.get("MinMax") == self.MINIMIZED + return await self.get('MinMax') == self.MINIMIZED async def is_maximized(self): - return await self.get("MinMax") == self.MAXIMIZED + return await self.get('MinMax') == self.MAXIMIZED @property async def non_max_non_min(self): - return await self.get("MinMax") == self.NON_MIN_NON_MAX + return await self.get('MinMax') == self.NON_MIN_NON_MAX async def is_minmax(self): - return await self.get("MinMax") != self.NON_MIN_NON_MAX + return await self.get('MinMax') != self.NON_MIN_NON_MAX @property async def transparent(self) -> int: - result = await self.get("Transparent") + result = await self.get('Transparent') if result: return int(result) else: @@ -917,16 +917,16 @@ async def transparent(self) -> int: @transparent.setter def transparent(self, value): - warnings.warn("transparent setter only schedules coroutine. window may not change immediately. use set_transparency() instead", stacklevel=2) + warnings.warn('transparent setter only schedules coroutine. window may not change immediately. use set_transparency() instead', stacklevel=2) if isinstance(value, int) and 0 <= value <= 255: - coro = self.set("Transparent", value) + coro = self.set('Transparent', value) asyncio.create_task(coro) else: raise ValueError('transparency must be integer in range [0, 255]') async def get_transparency(self) -> int: - result = await self.get("Transparent") + result = await self.get('Transparent') if result: return int(result) else: @@ -934,7 +934,7 @@ async def get_transparency(self) -> int: async def set_transparency(self, value): if isinstance(value, int) and 0 <= value <= 255: - await self.set("Transparent", value) + await self.set('Transparent', value) else: raise ValueError( f'"{value}" not a valid option. Please use [0, 255] integer') @@ -946,7 +946,7 @@ async def is_always_on_top(self): @Window.always_on_top.setter def always_on_top(self, value): - warnings.warn(f"always_on_top setter only schedules coroutine. changes may not happen immediately. use set_always_on_top({repr(value)}) instead") + warnings.warn(f'always_on_top setter only schedules coroutine. changes may not happen immediately. use set_always_on_top({repr(value)}) instead') if value in ('on', 'On', True, 1): coro = self.set('AlwaysOnTop', 'On') elif value in ('off', 'Off', False, 0): @@ -1019,7 +1019,7 @@ async def find_window(self, func=None, **kwargs): """ async for window in self.find_windows(func=func, **kwargs): return window # return the first result - raise WindowNotFoundError("yikes") + raise WindowNotFoundError('yikes') async def find_windows_by_title(self, title, exact=False): """ diff --git a/setup.py b/setup.py index ce693131..d45633fc 100644 --- a/setup.py +++ b/setup.py @@ -12,12 +12,12 @@ url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, - long_description_content_type="text/markdown", + long_description_content_type='text/markdown', author_email='spencer.young@spyoung.com', author='Spencer Young', packages=['ahk'], extras_require={ - "binary": ["ahk-binary==1.1.33.9"], + 'binary': ['ahk-binary==1.1.33.9'], }, install_requires=['jinja2'], classifiers=[ diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index 9829a381..1ff4750b 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -25,9 +25,9 @@ def setUp(self): self.ahk = AHKDaemon() self.ahk.start() self.before_windows = self.ahk.windows() - self.p = subprocess.Popen("notepad") + self.p = subprocess.Popen('notepad') time.sleep(1) - self.notepad = self.ahk.find_window(title=b"Untitled - Notepad") + self.notepad = self.ahk.find_window(title=b'Untitled - Notepad') def tearDown(self): self.ahk.set_capslock_state('off') @@ -36,45 +36,45 @@ def tearDown(self): time.sleep(0.2) def test_window_send(self): - self.notepad.send("hello world") + self.notepad.send('hello world') time.sleep(1) - self.assertIn(b"hello world", self.notepad.text) + 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 + self.ahk.send('hello world') + assert b'hello world' in self.notepad.text def test_send_input(self): self.notepad.activate() - self.ahk.send_input("Hello World") + self.ahk.send_input('Hello World') time.sleep(0.5) - assert b"Hello World" in self.notepad.text + 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 + self.ahk.type('Hello, World!') + assert b'Hello, World!' in self.notepad.text def test_type_escapes_equals(self): """ https://github.com/spyoungtech/ahk/issues/96 """ self.notepad.activate() - self.ahk.type("=foo") - assert b"=foo" in self.notepad.text + self.ahk.type('=foo') + assert b'=foo' in self.notepad.text def test_sendraw_equals(self): """ https://github.com/spyoungtech/ahk/issues/96 """ self.notepad.activate() - self.ahk.send_raw("=foo") - assert b"=foo" in self.notepad.text + self.ahk.send_raw('=foo') + assert b'=foo' in self.notepad.text def test_set_capslock_state(self): - self.ahk.set_capslock_state("on") - assert self.ahk.key_state("CapsLock", "T") + self.ahk.set_capslock_state('on') + assert self.ahk.key_state('CapsLock', 'T') def test_multi_line_response(self): """ diff --git a/tests/unittests/test_daemon_async.py b/tests/unittests/test_daemon_async.py index 75cc8357..f09cbb31 100644 --- a/tests/unittests/test_daemon_async.py +++ b/tests/unittests/test_daemon_async.py @@ -108,7 +108,7 @@ async def test_names(self): async def test_title_setter(self): starting_title = await self.win.title - await self.win.set_title("new title") + await self.win.set_title('new title') assert await self.win.get_title() != starting_title async def asyncTearDown(self): @@ -183,7 +183,7 @@ async def test_win_close(self): except WindowNotFoundError as e: pass else: - raise AssertionError("Expected WindowNotFoundError") + raise AssertionError('Expected WindowNotFoundError') async def test_find_window_func(self): async def func(win): @@ -203,7 +203,7 @@ async def asyncSetUp(self): """ self.ahk = AsyncAHKDaemon() await self.ahk.start() - self.p = subprocess.Popen("notepad") + self.p = subprocess.Popen('notepad') time.sleep(1) async def asyncTearDown(self): @@ -212,35 +212,35 @@ async def asyncTearDown(self): await asyncio.sleep(0.5) async def test_window_send(self): - notepad = await self.ahk.find_window(title=b"Untitled - Notepad") - await notepad.send("hello world") + notepad = await self.ahk.find_window(title=b'Untitled - Notepad') + await notepad.send('hello world') await asyncio.sleep(1) self.assertIn(b'hello world', await notepad.get_text()) async def test_send(self): - notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + notepad = await self.ahk.find_window(title=b'Untitled - Notepad') await notepad.activate() await self.ahk.send('hello world') self.assertIn(b'hello world', await notepad.get_text()) async def test_send_input(self): - notepad = await self.ahk.find_window(title=b"Untitled - Notepad") - await self.ahk.send_input("Hello World") + notepad = await self.ahk.find_window(title=b'Untitled - Notepad') + await self.ahk.send_input('Hello World') await asyncio.sleep(0.5) - assert b"Hello World" in await notepad.get_text() + assert b'Hello World' in await notepad.get_text() async def test_type(self): - notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + notepad = await self.ahk.find_window(title=b'Untitled - Notepad') await notepad.activate() - await self.ahk.type("Hello, World!") - assert b"Hello, World!" in await notepad.get_text() + await self.ahk.type('Hello, World!') + assert b'Hello, World!' in await notepad.get_text() async def test_multi_line_response(self): """ Test that responses with multi-line strings are not truncated Not really a 'keyboard' test, but whatever """ - notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + notepad = await self.ahk.find_window(title=b'Untitled - Notepad') await notepad.activate() await self.ahk.type('Hello\nWorld!') text = await notepad.get_text() diff --git a/tests/unittests/test_gui.py b/tests/unittests/test_gui.py index 0f3adddd..de1fa5be 100644 --- a/tests/unittests/test_gui.py +++ b/tests/unittests/test_gui.py @@ -4,25 +4,25 @@ class TestGuiMixin: - @fixture(scope="class") + @fixture(scope='class') def ahk(self) -> AHK: return AHK() def test_show_tooltip(self, ahk: AHK): - ahk.show_tooltip("hello") - ahk.show_tooltip("🚀 Hello unicode 🚀", second=2) - ahk.show_tooltip("⽲ hello3", x=10, y=10) - ahk.show_tooltip("hello4", second=2, x=10, y=10) + ahk.show_tooltip('hello') + ahk.show_tooltip('🚀 Hello unicode 🚀', second=2) + ahk.show_tooltip('⽲ hello3', x=10, y=10) + ahk.show_tooltip('hello4', second=2, x=10, y=10) with raises(ValueError): - ahk.show_tooltip("hello", id=30) + ahk.show_tooltip('hello', id=30) def test_show_traytip(self, ahk: AHK): - ahk._show_traytip("⽲ Normal 🚀", "It's me") - ahk._show_traytip("🐌 Slow 🐌", "It's you", second=2) - ahk._show_traytip("Info", "It's info", type_id=ahk.TRAYTIP_INFO) - ahk.show_info_traytip("Info", "It's also info") - ahk.show_warning_traytip("Warning", "It's warning") - ahk.show_error_traytip("Error", "It's error") - ahk._show_traytip("Slient - Info", "It's info", type_id=ahk.TRAYTIP_INFO, slient=True) - ahk.show_info_traytip("Unicode Threaded", "şüğı", blocking=False) # Need help + ahk._show_traytip('⽲ Normal 🚀', "It's me") + ahk._show_traytip('🐌 Slow 🐌', "It's you", second=2) + ahk._show_traytip('Info', "It's info", type_id=ahk.TRAYTIP_INFO) + ahk.show_info_traytip('Info', "It's also info") + ahk.show_warning_traytip('Warning', "It's warning") + ahk.show_error_traytip('Error', "It's error") + ahk._show_traytip('Slient - Info', "It's info", type_id=ahk.TRAYTIP_INFO, slient=True) + ahk.show_info_traytip('Unicode Threaded', 'şüğı', blocking=False) # Need help diff --git a/tests/unittests/test_keyboard.py b/tests/unittests/test_keyboard.py index dab13bcd..ba63cdb8 100644 --- a/tests/unittests/test_keyboard.py +++ b/tests/unittests/test_keyboard.py @@ -8,7 +8,7 @@ import pytest project_root = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") + os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..') ) sys.path.insert(0, project_root) @@ -25,74 +25,74 @@ def setUp(self): """ self.ahk = AHK() self.before_windows = self.ahk.windows() - self.p = subprocess.Popen("notepad") + self.p = subprocess.Popen('notepad') time.sleep(1) - self.notepad = self.ahk.find_window(title=b"Untitled - Notepad") + 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") + self.notepad.send('hello world') time.sleep(1) - self.assertIn(b"hello world", self.notepad.text) + self.assertIn(b'hello world', self.notepad.text) @pytest.mark.flaky(reruns=5) def test_window_send_raw(self): - self.notepad.send("{Tab 4}", raw=True, delay=10, press_duration=10) + self.notepad.send('{Tab 4}', raw=True, delay=10, press_duration=10) time.sleep(0.5) assert b'{Tab 4}' in self.notepad.text def test_send(self): self.notepad.activate() - self.ahk.send("hello world") - assert b"hello world" in self.notepad.text + 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) + 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") + self.ahk.send_input('Hello World') time.sleep(0.5) - assert b"Hello World" in self.notepad.text + 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 + self.ahk.type('Hello, World!') + assert b'Hello, World!' in self.notepad.text def test_type_escapes_equals(self): """ https://github.com/spyoungtech/ahk/issues/96 """ self.notepad.activate() - self.ahk.type("=foo") - assert b"=foo" in self.notepad.text + self.ahk.type('=foo') + assert b'=foo' in self.notepad.text def test_sendraw_equals(self): """ https://github.com/spyoungtech/ahk/issues/96 """ self.notepad.activate() - self.ahk.send_raw("=foo") - assert b"=foo" in self.notepad.text + self.ahk.send_raw('=foo') + assert b'=foo' in self.notepad.text def test_set_capslock_state(self): - self.ahk.set_capslock_state("on") - assert self.ahk.key_state("CapsLock", "T") + self.ahk.set_capslock_state('on') + assert self.ahk.key_state('CapsLock', 'T') class TestKeyboardDaemon(TestKeyboard): def setUp(self): self.ahk = AHKDaemon() self.ahk.start() self.before_windows = self.ahk.windows() - self.p = subprocess.Popen("notepad") + self.p = subprocess.Popen('notepad') time.sleep(1) - self.notepad = self.ahk.find_window(title=b"Untitled - Notepad") + self.notepad = self.ahk.find_window(title=b'Untitled - Notepad') def tearDown(self): super().tearDown() @@ -101,19 +101,19 @@ def tearDown(self): def a_down(): time.sleep(0.5) ahk = AHK() - ahk.key_down("a") + ahk.key_down('a') def release_a(): time.sleep(0.5) ahk = AHK() - ahk.key_up("a") + ahk.key_up('a') def press_a(): time.sleep(0.5) ahk = AHK() - ahk.key_press("a") + ahk.key_press('a') class TestKeys(TestCase): @@ -125,13 +125,13 @@ def setUp(self): 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_state("Control"): - self.ahk.key_up("Control") + if self.ahk.key_state('a'): + self.ahk.key_up('a') + if self.ahk.key_state('Control'): + self.ahk.key_up('Control') self.ahk.set_capslock_state('off') - notepad = self.ahk.find_window(title=b"Untitled - Notepad") + notepad = self.ahk.find_window(title=b'Untitled - Notepad') if notepad: notepad.close() @@ -142,7 +142,7 @@ 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) + self.ahk.key_wait('a', timeout=5) end = time.time() assert end - start < 5 @@ -151,30 +151,30 @@ def test_key_wait_released(self): a_down() self.thread = threading.Thread(target=release_a) self.thread.start() - self.ahk.key_wait("a", timeout=2) + self.ahk.key_wait('a', timeout=2) def test_key_wait_timeout(self): - self.assertRaises(TimeoutError, self.ahk.key_wait, "f", timeout=1) + 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")) + 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")) + 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.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")) + 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 = 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")) + self.ahk.key_press('a') + self.assertIsNone(self.ahk.find_window(title=b'Untitled - Notepad')) diff --git a/tests/unittests/test_keyboard_async.py b/tests/unittests/test_keyboard_async.py index 73e73836..2ddc3527 100644 --- a/tests/unittests/test_keyboard_async.py +++ b/tests/unittests/test_keyboard_async.py @@ -9,7 +9,7 @@ project_root = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") + os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..') ) sys.path.insert(0, project_root) from ahk import AsyncAHK, AHK @@ -25,7 +25,7 @@ def setUp(self): self.ahk = AsyncAHK() #self._normal_ahk = AHK() #self.before_windows = self._normal_ahk.windows() - self.p = subprocess.Popen("notepad") + self.p = subprocess.Popen('notepad') time.sleep(1) async def asyncTearDown(self): @@ -33,46 +33,46 @@ async def asyncTearDown(self): await asyncio.sleep(0.5) async def test_window_send(self): - notepad = await self.ahk.find_window(title=b"Untitled - Notepad") - await notepad.send("hello world") + notepad = await self.ahk.find_window(title=b'Untitled - Notepad') + await notepad.send('hello world') await asyncio.sleep(1) self.assertIn(b'hello world', await notepad.get_text()) async def test_send(self): - notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + notepad = await self.ahk.find_window(title=b'Untitled - Notepad') await notepad.activate() await self.ahk.send('hello world') self.assertIn(b'hello world', await notepad.get_text()) async def test_send_input(self): - notepad = await self.ahk.find_window(title=b"Untitled - Notepad") - await self.ahk.send_input("Hello World") + notepad = await self.ahk.find_window(title=b'Untitled - Notepad') + await self.ahk.send_input('Hello World') await asyncio.sleep(0.5) - assert b"Hello World" in await notepad.get_text() + assert b'Hello World' in await notepad.get_text() async def test_type(self): - notepad = await self.ahk.find_window(title=b"Untitled - Notepad") + notepad = await self.ahk.find_window(title=b'Untitled - Notepad') await notepad.activate() - await self.ahk.type("Hello, World!") - assert b"Hello, World!" in await notepad.get_text() + await self.ahk.type('Hello, World!') + assert b'Hello, World!' in await notepad.get_text() def a_down(): time.sleep(0.5) ahk = AHK() - ahk.key_down("a") + ahk.key_down('a') def release_a(): time.sleep(0.5) ahk = AHK() - ahk.key_up("a") + ahk.key_up('a') def press_a(): time.sleep(0.5) ahk = AHK() - ahk.key_press("a") + ahk.key_press('a') # class TestKeysAsync(IsolatedAsyncioTestCase): @@ -85,12 +85,12 @@ def setUp(self): def tearDown(self): if self.thread is not None: self.thread.join(timeout=3) - if self._normal_ahk.key_state("a"): - self._normal_ahk.key_up("a") - if self._normal_ahk.key_state("Control"): - self._normal_ahk.key_up("Control") + if self._normal_ahk.key_state('a'): + self._normal_ahk.key_up('a') + if self._normal_ahk.key_state('Control'): + self._normal_ahk.key_up('Control') - notepad = self._normal_ahk.find_window(title=b"Untitled - Notepad") + notepad = self._normal_ahk.find_window(title=b'Untitled - Notepad') if notepad: notepad.kill() @@ -98,7 +98,7 @@ def tearDown(self): self.hotkey.stop() async def a_key_wait_pressed(self): - await self.ahk.key_wait("a", timeout=5) + await self.ahk.key_wait('a', timeout=5) def test_key_wait_pressed(self): start = time.time() @@ -109,7 +109,7 @@ def test_key_wait_pressed(self): assert end - start < 5 async def a_key_wait_released(self): - await self.ahk.key_wait("a", timeout=2) + await self.ahk.key_wait('a', timeout=2) def test_key_wait_released(self): start = time.time() @@ -128,12 +128,12 @@ def test_key_wait_timeout(self): async def test_key_state_when_not_pressed(self): - self.assertFalse(await self.ahk.key_state("a")) + self.assertFalse(await self.ahk.key_state('a')) async def test_key_state_pressed(self): - await self.ahk.key_down("Control") - self.assertTrue(await self.ahk.key_state("Control")) + await self.ahk.key_down('Control') + self.assertTrue(await self.ahk.key_state('Control')) # def test_hotkey(self): diff --git a/tests/unittests/test_mouse.py b/tests/unittests/test_mouse.py index 773cc5fd..fd4239c0 100644 --- a/tests/unittests/test_mouse.py +++ b/tests/unittests/test_mouse.py @@ -9,7 +9,7 @@ from functools import partial from unittest import TestCase, IsolatedAsyncioTestCase project_root = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") + os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..') ) sys.path.insert(0, project_root) from ahk import AsyncAHK, AHK @@ -49,7 +49,7 @@ def test_mouse_move_callable_speed(self): def test_mouse_drag(self): self.notepad_process = subprocess.Popen('notepad') time.sleep(0.5) - notepad = self.ahk.find_window(title=b"Untitled - Notepad") + notepad = self.ahk.find_window(title=b'Untitled - Notepad') win_width = notepad.width win_height = notepad.height print(*notepad.position) diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index 96b1fd4a..63e366db 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -3,7 +3,7 @@ from unittest import TestCase import os, sys project_root = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") + os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..') ) sys.path.insert(0, project_root) diff --git a/tests/unittests/test_window_async.py b/tests/unittests/test_window_async.py index 1429a9ab..3ae87e50 100644 --- a/tests/unittests/test_window_async.py +++ b/tests/unittests/test_window_async.py @@ -5,7 +5,7 @@ import os import sys project_root = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") + os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..') ) sys.path.insert(0, project_root) from ahk import AHK, AsyncAHK @@ -99,7 +99,7 @@ async def test_names(self): async def test_title_setter(self): starting_title = await self.win.title - await self.win.set_title("new title") + await self.win.set_title('new title') assert await self.win.get_title() != starting_title def tearDown(self): From e5d04467533149221d86ac5b47617c76846b896e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Nov 2021 17:19:38 -0800 Subject: [PATCH 171/588] black --- .pre-commit-config.yaml | 8 ++ ahk/__init__.py | 1 + ahk/autohotkey.py | 11 +- ahk/daemon.py | 18 +-- ahk/directives.py | 2 + ahk/keyboard.py | 41 +++---- ahk/keys.py | 2 +- ahk/mouse.py | 40 +++++-- ahk/registry.py | 59 ++++----- ahk/screen.py | 2 +- ahk/script.py | 39 +++--- ahk/sound.py | 15 ++- ahk/utils.py | 6 +- ahk/window.py | 125 +++++++++----------- docs/conf.py | 20 +--- setup.py | 3 +- tests/features/steps/ahk_steps.py | 1 + tests/unittests/test_blocking_mouse.py | 1 + tests/unittests/test_daemon.py | 4 +- tests/unittests/test_daemon_async.py | 12 +- tests/unittests/test_executable_location.py | 3 +- tests/unittests/test_keyboard.py | 6 +- tests/unittests/test_keyboard_async.py | 11 +- tests/unittests/test_mouse.py | 14 +-- tests/unittests/test_screen.py | 3 + tests/unittests/test_screen_async.py | 1 + tests/unittests/test_win_get.py | 7 ++ tests/unittests/test_win_get_async.py | 4 +- tests/unittests/test_window.py | 13 +- tests/unittests/test_window_async.py | 14 ++- 30 files changed, 252 insertions(+), 234 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c9ec7dae..ce39d37f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,3 +6,11 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - id: double-quote-string-fixer +- repo: https://github.com/psf/black + rev: '21.10b0' + hooks: + - id: black + args: + - "-S" + - "-l" + - "120" diff --git a/ahk/__init__.py b/ahk/__init__.py index c00dfbf5..1634aca8 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -1,3 +1,4 @@ from ahk.autohotkey import AHK, ActionChain, AsyncAHK from ahk.keyboard import Hotkey + __all__ = ['AHK'] diff --git a/ahk/autohotkey.py b/ahk/autohotkey.py index 2a95db3e..33098d25 100644 --- a/ahk/autohotkey.py +++ b/ahk/autohotkey.py @@ -24,6 +24,7 @@ class AHK(WindowMixin, MouseMixin, KeyboardMixin, ScreenMixin, SoundMixin, Regis pass + # class AsyncAHK( AsyncMouseMixin, @@ -32,7 +33,7 @@ class AsyncAHK( AsyncScreenMixin, AsyncSoundMixin, AsyncRegistryMixin, - AsyncGUIMixin + AsyncGUIMixin, ): ... @@ -66,5 +67,11 @@ def sleep(self, n): :return: """ n = n * 1000 # convert to milliseconds - script = self.render_template('base.ahk', body=f'Sleep {n}', directives={'#Persistent',}) + script = self.render_template( + 'base.ahk', + body=f'Sleep {n}', + directives={ + '#Persistent', + }, + ) self.run_script(script) diff --git a/ahk/daemon.py b/ahk/daemon.py index f4c9feb3..cc305e26 100644 --- a/ahk/daemon.py +++ b/ahk/daemon.py @@ -7,19 +7,24 @@ import queue import atexit + def escape(s): s = s.replace('\n', '`n') return s + class STOP: """A sentinel value""" + ... + class AHKDaemon(AHK): proc: subprocess.Popen _template_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates') _template = os.path.join(_template_path, 'daemon.ahk') _template_overrides = os.listdir(f'{_template_path}/daemon') + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.queue = queue.Queue() @@ -38,10 +43,7 @@ def _run(self): raise RuntimeError('Already running') self._is_running = True runargs = [self.executable_path, self._template] - proc = subprocess.Popen(runargs, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + proc = subprocess.Popen(runargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.proc = proc atexit.register(self.proc.terminate) @@ -138,6 +140,7 @@ class AsyncAHKDaemon(AsyncAHK): _template_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates') _template = os.path.join(_template_path, 'daemon.ahk') _template_overrides = os.listdir(f'{_template_path}/daemon') + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.queue = asyncio.Queue() @@ -155,10 +158,9 @@ async def _run(self): raise RuntimeError('Already running') self._is_running = True runargs = [self.executable_path, self._template] - proc = await asyncio.subprocess.create_subprocess_exec(*runargs, - stdin=asyncio.subprocess.PIPE, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE) + proc = await asyncio.subprocess.create_subprocess_exec( + *runargs, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) self.proc = proc async def _get_command(self): diff --git a/ahk/directives.py b/ahk/directives.py index 4de4c645..25bbf165 100644 --- a/ahk/directives.py +++ b/ahk/directives.py @@ -10,6 +10,7 @@ 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__}' @@ -26,6 +27,7 @@ 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) self._kwargs = kwargs diff --git a/ahk/keyboard.py b/ahk/keyboard.py index 378e08ca..276f274a 100644 --- a/ahk/keyboard.py +++ b/ahk/keyboard.py @@ -35,9 +35,7 @@ def start(self): """ if self.running: raise RuntimeError('Hotkey is already running') - script = self.engine.render_template( - 'hotkey.ahk', blocking=False, script=self.script, hotkey=self.hotkey - ) + 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 @@ -98,9 +96,7 @@ def key_state(self, key_name, mode=None) -> bool: result = ast.literal_eval(result) return bool(result) - def _key_wait( - self, key_name, timeout: int = None, logical_state=False, released=False - ) -> str: + def _key_wait(self, key_name, timeout: int = None, logical_state=False, released=False) -> str: options = '' if not released: options += 'D' @@ -108,13 +104,10 @@ def _key_wait( options += 'L' if timeout: options += f'T{timeout}' - script = self.render_template( - 'keyboard/key_wait.ahk', key_name=key_name, options=options - ) + script = self.render_template('keyboard/key_wait.ahk', key_name=key_name, options=options) return script - def key_wait( - self, key_name, timeout: int = None, logical_state=False, released=False - ) -> None: + + def key_wait(self, key_name, timeout: int = None, logical_state=False, released=False) -> None: """ Wait for key to be pressed or released (default is pressed; specify ``released=True`` to wait for key release). @@ -127,9 +120,9 @@ def key_wait( :return: None :raises TimeoutError: if the key was not pressed (or released, if specified) within timeout """ - result = self.run_script(self._key_wait( - key_name, timeout=timeout, logical_state=logical_state, released=released - )) + result = self.run_script( + self._key_wait(key_name, timeout=timeout, logical_state=logical_state, released=released) + ) if result == '1': raise TimeoutError(f'timed out waiting for {key_name}') @@ -144,9 +137,7 @@ def type(self, s, blocking=True): return self.send_input(s, blocking=blocking) or None def _send(self, s, raw=False, delay=None, blocking=True): - script = self.render_template( - 'keyboard/send.ahk', s=s, raw=raw, delay=delay, blocking=blocking - ) + script = self.render_template('keyboard/send.ahk', s=s, raw=raw, delay=delay, blocking=blocking) return script def send(self, s, raw=False, delay=None, blocking=True): @@ -212,7 +203,8 @@ def _send_event(self, s, delay=None): script = self.render_template('keyboard/send_event.ahk', s=s, delay=delay) return script - #self.run_script(script) + # self.run_script(script) + def send_event(self, s, delay=None): """ https://autohotkey.com/docs/commands/Send.htm @@ -301,19 +293,16 @@ class AsyncKeyboardMixin(AsyncScriptEngine, KeyboardMixin): # script = self._send_input(s, blocking=blocking) # return await self.a_run_script(script, blocking=blocking) or None - async def key_state(self, key_name, mode=None) -> bool: script = self._key_state(key_name, mode=mode) result = await self.a_run_script(script) result = ast.literal_eval(result) return bool(result) - async def key_wait( - self, key_name, timeout: int = None, logical_state=False, released=False - ) -> None: - result = await self.a_run_script(self._key_wait( - key_name, timeout=timeout, logical_state=logical_state, released=released - )) + async def key_wait(self, key_name, timeout: int = None, logical_state=False, released=False) -> None: + result = await self.a_run_script( + self._key_wait(key_name, timeout=timeout, logical_state=logical_state, released=released) + ) if result == '1': raise TimeoutError(f'timed out waiting for {key_name}') diff --git a/ahk/keys.py b/ahk/keys.py index 90c2cb23..0e80a2e2 100644 --- a/ahk/keys.py +++ b/ahk/keys.py @@ -59,7 +59,6 @@ def __format__(self, format_spec): 'Control': '^', 'LControl': '<^', 'RControl': '>^', - } @@ -117,6 +116,7 @@ class KEYS: KEYS constants REF: https://autohotkey.com/docs/KeyList.htm """ + CAPS_LOCK = Key('CapsLock') CapsLock = CAPS_LOCK SCROLL_LOCK = Key('ScrollLock') diff --git a/ahk/mouse.py b/ahk/mouse.py index 73cd97d0..2be551e2 100644 --- a/ahk/mouse.py +++ b/ahk/mouse.py @@ -46,6 +46,7 @@ class MouseMixin(ScriptEngine): """ Provides mouse functionality for the AHK class """ + def __init__(self, mouse_speed=2, mode=None, **kwargs): """ @@ -106,7 +107,9 @@ def _mouse_move(self, x=None, y=None, speed=None, relative=False, mode=None, blo 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) + 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): """ @@ -216,7 +219,18 @@ def wheel_down(self, *args, **kwargs): """ return self.mouse_wheel('down', *args, **kwargs) or None - def _mouse_drag(self, x, y=None, *, from_position=None, speed=None, button: Union[str, int] =1, relative=None, blocking=True, mode=None): + def _mouse_drag( + self, + x, + y=None, + *, + from_position=None, + speed=None, + button: Union[str, int] = 1, + relative=None, + blocking=True, + mode=None, + ): if from_position is None: x1, y1 = self.mouse_position else: @@ -239,16 +253,18 @@ def _mouse_drag(self, x, y=None, *, from_position=None, speed=None, button: Unio 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) + 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, + ) return script diff --git a/ahk/registry.py b/ahk/registry.py index 3ab962f3..f45cd7d3 100644 --- a/ahk/registry.py +++ b/ahk/registry.py @@ -13,58 +13,58 @@ def _run_template(self, template_name, *args, **kwargs): def reg_read(self, key_name: str, value_name='') -> str: """Read registery - Reference: - https://www.autohotkey.com/docs/commands/RegRead.htm + Reference: + https://www.autohotkey.com/docs/commands/RegRead.htm - Arguments: - key_name {str} -- RegEdit + Arguments: + key_name {str} -- RegEdit - Keyword Arguments: - value_name {str} -- TODO (default: {""}) + Keyword Arguments: + value_name {str} -- TODO (default: {""}) - Returns: - str -- Registery value - """ + Returns: + str -- Registery value + """ return self._run_template('reg_read.ahk', key_name=key_name, value_name=value_name) def reg_delete(self, key_name: str, value_name='') -> None: """Delete registery - Reference: - https://www.autohotkey.com/docs/commands/RegDelete.htm + Reference: + https://www.autohotkey.com/docs/commands/RegDelete.htm - Arguments: - key_name {str} -- RegEdit + Arguments: + key_name {str} -- RegEdit - Keyword Arguments: - value_name {str} -- TODO (default: {""}) - """ + Keyword Arguments: + value_name {str} -- TODO (default: {""}) + """ return self._render_template('reg_delete.ahk', key_name=key_name, value_name=value_name) def reg_write(self, value_type: str, key_name: str, value_name='') -> None: """Write registery - Reference: - https://www.autohotkey.com/docs/commands/RegWrite.htm + Reference: + https://www.autohotkey.com/docs/commands/RegWrite.htm - Arguments: - value_type {str} -- RegEdit value - key_name {str} -- RegEdit + Arguments: + value_type {str} -- RegEdit value + key_name {str} -- RegEdit - Keyword Arguments: - value_name {str} -- TODO (default: {""}) - """ + Keyword Arguments: + value_name {str} -- TODO (default: {""}) + """ return self._render_template('reg_write.ahk', value_type=value_type, key_name=key_name, value_name=value_name) def reg_set_view(self, reg_view: int) -> None: """Set registery view - Reference: - https://www.autohotkey.com/docs/commands/SetRegView.htm + Reference: + https://www.autohotkey.com/docs/commands/SetRegView.htm - Arguments: - reg_view {str} -- Registery view - """ + Arguments: + reg_view {str} -- Registery view + """ if reg_view not in [32, 64, '32', '64']: raise ValueError('No valid bit, please use 32 or 64') @@ -128,5 +128,6 @@ def delete(self, *args, **kwargs): ) return self.reg_delete(*args, **kwargs) + class AsyncRegistryMixin(AsyncScriptEngine, RegistryMixin): pass diff --git a/ahk/screen.py b/ahk/screen.py index 317cd4db..8e680bf3 100644 --- a/ahk/screen.py +++ b/ahk/screen.py @@ -142,7 +142,6 @@ def pixel_get_color(self, *args, **kwargs): script = self._pixel_get_color(*args, **kwargs) return self.run_script(script) - def _pixel_search( self, color: Union[str, int], @@ -199,6 +198,7 @@ def pixel_search(self, *args, **kwargs): except SyntaxError: return None + class AsyncScreenMixin(AsyncScriptEngine, ScreenMixin): async def pixel_search(self, *args, **kwargs): script = self._pixel_search(*args, **kwargs) diff --git a/ahk/script.py b/ahk/script.py index 17ff969f..5256be82 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -19,6 +19,7 @@ from ahk.directives import Persistent from jinja2 import Environment, FileSystemLoader from typing import Set + logger = make_logger(__name__) @@ -32,11 +33,13 @@ class ExecutableNotFoundError(EnvironmentError): def _resolve_executable_path(executable_path: str = ''): if not executable_path: - executable_path = os.environ.get('AHK_PATH') \ - or which('AutoHotkey.exe') \ - or which('AutoHotkeyU64.exe') \ - or which('AutoHotkeyU32.exe') \ - or which('AutoHotkeyA32.exe') + executable_path = ( + os.environ.get('AHK_PATH') + or which('AutoHotkey.exe') + or which('AutoHotkeyU64.exe') + or which('AutoHotkeyU32.exe') + or which('AutoHotkeyA32.exe') + ) if not executable_path: if os.path.exists(DEFAULT_EXECUTABLE_PATH): @@ -51,8 +54,7 @@ def _resolve_executable_path(executable_path: str = ''): ) if not os.path.exists(executable_path): - raise ExecutableNotFoundError( - f"executable_path does not seems to exist: '{executable_path}' not found") + raise ExecutableNotFoundError(f"executable_path does not seems to exist: '{executable_path}' not found") if os.path.isdir(executable_path): raise ExecutableNotFoundError( @@ -69,7 +71,6 @@ def _resolve_executable_path(executable_path: str = ''): class ScriptEngine(object): - def __init__(self, executable_path: str = '', directives: Set = None, **kwargs): """ This class is typically not used directly. AHK components inherit from this class @@ -88,8 +89,7 @@ def __init__(self, executable_path: str = '', directives: Set = None, **kwargs): self.executable_path = _resolve_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) + self.env = Environment(loader=FileSystemLoader(templates_path), autoescape=False, trim_blocks=True) if directives is None: directives = set() self._directives = set(directives) @@ -136,8 +136,9 @@ def _run_script(self, script_text, **kwargs): 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) + 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)) @@ -145,8 +146,9 @@ def _run_script(self, script_text, **kwargs): else: return result else: - proc = subprocess.Popen(runargs, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) + proc = subprocess.Popen( + runargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs + ) try: proc.communicate(script_bytes, timeout=0) except subprocess.TimeoutExpired: @@ -158,10 +160,9 @@ async def _a_run_script(self, script_text, **kwargs): if blocking is not True: warnings.warn('blocking=False will probably result in problems', stacklevel=2) runargs = [self.executable_path, '/ErrorStdOut', '*'] - proc = await asyncio.subprocess.create_subprocess_exec(*runargs, - stdin=asyncio.subprocess.PIPE, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE) + proc = await asyncio.subprocess.create_subprocess_exec( + *runargs, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) script_bytes = bytes(script_text, 'utf-8') if not blocking: proc.stdin.write(script_bytes) @@ -173,7 +174,6 @@ async def _a_run_script(self, script_text, **kwargs): return stdout.decode() return stdout - async def a_run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): """ async version of ``run_script`` @@ -228,5 +228,6 @@ def run_script(self, script_text: str, decode=True, blocking=True, **runkwargs): raise return result + class AsyncScriptEngine(ScriptEngine): run_script = ScriptEngine.a_run_script diff --git a/ahk/sound.py b/ahk/sound.py index 5add27ed..2b2b7a72 100644 --- a/ahk/sound.py +++ b/ahk/sound.py @@ -37,7 +37,9 @@ def sound_get(self, device_number=1, component_type='MASTER', control_type='VOLU :param control_type: :return: """ - script = self.render_template('sound/sound_get.ahk', device_number=device_number, component_type=component_type, control_type=control_type) + script = self.render_template( + 'sound/sound_get.ahk', device_number=device_number, component_type=component_type, control_type=control_type + ) return self.run_script(script) def get_volume(self, device_number=1): @@ -64,10 +66,13 @@ def sound_set(self, value, device_number=1, component_type='MASTER', control_typ :return: """ - script = self.render_template('sound/sound_set.ahk', value=value, - device_number=device_number, - component_type=component_type, - control_type=control_type) + script = self.render_template( + 'sound/sound_set.ahk', + value=value, + device_number=device_number, + component_type=component_type, + control_type=control_type, + ) return self.run_script(script) or None def set_volume(self, value, device_number=1): diff --git a/ahk/utils.py b/ahk/utils.py index 829fdbfc..f2d34a8b 100644 --- a/ahk/utils.py +++ b/ahk/utils.py @@ -21,16 +21,16 @@ '{': '{{}', '}': '{}}', '#': '{#}', - '=': '{=}' + '=': '{=}', } _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') + formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) return logger diff --git a/ahk/window.py b/ahk/window.py index 7077e1e2..804a165b 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -109,7 +109,7 @@ class Window(object): 'ex_style': 'ExStyle', 'region': 'Region', 'transparent': 'Transparent', - 'transcolor': 'TransColor' + 'transcolor': 'TransColor', } _get_subcommands = { @@ -126,7 +126,7 @@ class Window(object): 'controls_hwnd': 'ControlListHwnd', 'transparent': 'Transparent', 'trans_color': 'TransColor', - 'style': 'Style', # This will probably get a property later + 'style': 'Style', # This will probably get a property later 'ex_style': 'ExStyle', # This will probably get a property later } @@ -147,9 +147,7 @@ def from_mouse_position(cls, engine: ScriptEngine, **kwargs): @classmethod def from_pid(cls, engine: ScriptEngine, pid, **kwargs): - script = engine.render_template('window/get.ahk', - subcommand='ID', - title=f'ahk_pid {pid}') + 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) @@ -184,10 +182,7 @@ def _set(self, subcommand, value): raise ValueError(f'No such subcommand {subcommand}') script = self._render_template( - 'window/win_set.ahk', - subcommand=subcommand, - value=value, - title=f'ahk_id {self.id}' + 'window/win_set.ahk', subcommand=subcommand, value=value, title=f'ahk_id {self.id}' ) return script @@ -196,11 +191,7 @@ def set(self, subcommand, value): return self.engine.run_script(script) def _get_pos(self, info=None): - script = self._render_template( - 'window/win_position.ahk', - title=f'ahk_id {self.id}', - pos_info=info - ) + script = self._render_template('window/win_position.ahk', title=f'ahk_id {self.id}', pos_info=info) return script def get_pos(self, info=None): @@ -255,11 +246,7 @@ def height(self, new_height): self.move(height=new_height) def _base_check(self, command): - script = self._render_template( - 'window/base_check.ahk', - command=command, - title=f'ahk_id {self.id}' - ) + script = self._render_template('window/base_check.ahk', command=command, title=f'ahk_id {self.id}') return script def _base_property(self, command): @@ -282,12 +269,9 @@ def exists(self): return self._base_property(command='WinExist') def _base_get_method_(self, command): - script = self._render_template( - 'window/base_get_command.ahk', - command=command, - title=f'ahk_id {self.id}' - ) + script = self._render_template('window/base_get_command.ahk', command=command, title=f'ahk_id {self.id}') return script + def _base_get_method(self, command): script = self._base_get_method_(command) result = self.engine.run_script(script, decode=False) @@ -308,11 +292,7 @@ def get_title(self): return self._base_get_method('WinGetTitle') def _set_title(self, value): - script = self._render_template( - 'window/win_set_title.ahk', - title=f'ahk_id {self.id}', - new_title=value - ) + script = self._render_template('window/win_set_title.ahk', title=f'ahk_id {self.id}', new_title=value) return script @title.setter @@ -327,7 +307,6 @@ def set_title(self, value): def class_name(self): return self.get_class_name() - @property def text(self): return self.get_text() @@ -378,15 +357,10 @@ def set_transparency(self, value): if isinstance(value, int) and 0 <= value <= 255: return self.set('Transparent', value) or None else: - raise ValueError( - f'"{value}" not a valid option. Please use [0, 255] integer' - ) + raise ValueError(f'"{value}" not a valid option. Please use [0, 255] integer') def _always_on_top(self): - script = self._render_template( - 'window/win_is_always_on_top.ahk', - title=f'ahk_id {self.id}' - ) + script = self._render_template('window/win_is_always_on_top.ahk', title=f'ahk_id {self.id}') return script @property @@ -410,8 +384,7 @@ def set_always_on_top(self, value): elif value in ('toggle', 'Toggle', -1): return self.set('AlwaysOnTop', 'Toggle') or None else: - raise ValueError( - f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') + raise ValueError(f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') def disable(self): """ @@ -453,10 +426,7 @@ def _render_template(self, *args, **kwargs): def _base_method_(self, command, seconds_to_wait='', blocking=False): script = self._render_template( - 'window/base_command.ahk', - command=command, - title=f'ahk_id {self.id}', - seconds_to_wait=seconds_to_wait + 'window/base_command.ahk', command=command, title=f'ahk_id {self.id}', seconds_to_wait=seconds_to_wait ) return script @@ -577,9 +547,7 @@ def wait_close(self, seconds_to_wait=''): def _move(self, x='', y='', width=None, height=None): script = self._render_template( - 'window/win_move.ahk', - title=f'ahk_id {self.id}', - x=x, y=y, width=width, height=height + 'window/win_move.ahk', title=f'ahk_id {self.id}', x=x, y=y, width=width, height=height ) return script @@ -602,8 +570,11 @@ def _send(self, keys, delay=10, raw=False, blocking=False, escape=False, press_d script = self._render_template( 'window/win_send.ahk', title=f'ahk_id {self.id}', - keys=keys, raw=raw, delay=delay, - press_duration=press_duration, blocking=blocking + keys=keys, + raw=raw, + delay=delay, + press_duration=press_duration, + blocking=blocking, ) return script @@ -618,6 +589,7 @@ def send(self, keys, delay=10, raw=False, blocking=True, escape=False, press_dur def _click(self, x=None, y=None, *, button=None, n=1, options=None, blocking=True): from ahk.mouse import resolve_button + if x or y: if y is None and isinstance(x, collections.abc.Sequence) and len(x) == 2: # alow position to be specified by a sequence of length 2 @@ -626,8 +598,7 @@ def _click(self, x=None, y=None, *, button=None, n=1, options=None, blocking=Tru button = resolve_button(button) script = self._render_template( - 'window/win_click.ahk', - x=x, y=y, hwnd=f'ahk_id {self.id}', button=button, n=n, options=options + 'window/win_click.ahk', x=x, y=y, hwnd=f'ahk_id {self.id}', button=button, n=n, options=options ) return script @@ -673,7 +644,7 @@ def _win_get(self, title='', text='', exclude_title='', exclude_text=''): title=title, text=text, exclude_text=exclude_text, - exclude_title=exclude_title + exclude_title=exclude_title, ) return script @@ -684,13 +655,11 @@ def win_get(self, title='', text='', exclude_title='', exclude_text='', encoding 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) + script = self.render_template('window/set.ahk', subcommand=subcommand, *args, blocking=blocking) return script def win_set(self, subcommand, *args, blocking=True): - script = self.render_template( - 'window/set.ahk', subcommand=subcommand, *args, blocking=blocking) + script = self.render_template('window/set.ahk', subcommand=subcommand, *args, blocking=blocking) return self.run_script(script, blocking=blocking) or None @property @@ -703,6 +672,7 @@ def get_active_window(self): def _all_window_ids_(self): script = self.render_template('window/id_list.ahk') return script + def _all_window_ids(self): script = self._all_window_ids_() result = self.run_script(script) @@ -742,6 +712,7 @@ def func(win): if result is False: return False return True + for window in filter(func, self.windows()): yield window @@ -821,7 +792,6 @@ def find_window_by_class(self, *args, **kwargs): class AsyncWindow(Window): - @classmethod async def from_mouse_position(cls, engine: ScriptEngine, **kwargs): script = engine.render_template('window/from_mouse.ahk') @@ -830,9 +800,7 @@ async def from_mouse_position(cls, engine: ScriptEngine, **kwargs): @classmethod async def from_pid(cls, engine: ScriptEngine, pid, **kwargs): - script = engine.render_template('window/get.ahk', - subcommand='ID', - title=f'ahk_pid {pid}') + script = engine.render_template('window/get.ahk', subcommand='ID', title=f'ahk_pid {pid}') ahk_id = await engine.a_run_script(script) return cls(engine=engine, ahk_id=ahk_id, **kwargs) @@ -852,27 +820,37 @@ async def get_pos(self, info=None): @Window.rect.setter def rect(self, new_position): - warnings.warn('rect setter only schedules coroutine. window may not change immediately. Use move() instead', stacklevel=2) + warnings.warn( + 'rect setter only schedules coroutine. window may not change immediately. Use move() instead', stacklevel=2 + ) x, y, width, height = new_position coro = self.move(x=x, y=y, width=width, height=height) asyncio.create_task(coro) @Window.position.setter def position(self, new_position): - warnings.warn('position setter only schedules coroutine. window may not change immediately. use set_position() instead', stacklevel=2) + warnings.warn( + 'position setter only schedules coroutine. window may not change immediately. use set_position() instead', + stacklevel=2, + ) x, y = new_position coro = self.move(x, y) asyncio.create_task(coro) @Window.width.setter def width(self, new_width): - warnings.warn('width setter only schedules coroutine. window may not change immediately. use move() instead', stacklevel=2) + warnings.warn( + 'width setter only schedules coroutine. window may not change immediately. use move() instead', stacklevel=2 + ) coro = self.move(width=new_width) asyncio.create_task(coro) @Window.height.setter def height(self, new_height): - warnings.warn('height setter only schedules coroutine. window may not change immediately. use move() instead', stacklevel=2) + warnings.warn( + 'height setter only schedules coroutine. window may not change immediately. use move() instead', + stacklevel=2, + ) coro = self.move(height=new_height) asyncio.create_task(coro) @@ -890,7 +868,10 @@ async def _base_get_method(self, command): @Window.title.setter def title(self, new_title): - warnings.warn('title setter only schedules coroutine. window may not change immediately. use set_title() instead', stacklevel=2) + warnings.warn( + 'title setter only schedules coroutine. window may not change immediately. use set_title() instead', + stacklevel=2, + ) coro = self.set_title(new_title) asyncio.create_task(coro) @@ -917,7 +898,10 @@ async def transparent(self) -> int: @transparent.setter def transparent(self, value): - warnings.warn('transparent setter only schedules coroutine. window may not change immediately. use set_transparency() instead', stacklevel=2) + warnings.warn( + 'transparent setter only schedules coroutine. window may not change immediately. use set_transparency() instead', + stacklevel=2, + ) if isinstance(value, int) and 0 <= value <= 255: coro = self.set('Transparent', value) @@ -936,8 +920,7 @@ async def set_transparency(self, value): if isinstance(value, int) and 0 <= value <= 255: await self.set('Transparent', value) else: - raise ValueError( - f'"{value}" not a valid option. Please use [0, 255] integer') + raise ValueError(f'"{value}" not a valid option. Please use [0, 255] integer') async def is_always_on_top(self): script = self._always_on_top() @@ -946,7 +929,9 @@ async def is_always_on_top(self): @Window.always_on_top.setter def always_on_top(self, value): - warnings.warn(f'always_on_top setter only schedules coroutine. changes may not happen immediately. use set_always_on_top({repr(value)}) instead') + warnings.warn( + f'always_on_top setter only schedules coroutine. changes may not happen immediately. use set_always_on_top({repr(value)}) instead' + ) if value in ('on', 'On', True, 1): coro = self.set('AlwaysOnTop', 'On') elif value in ('off', 'Off', False, 0): @@ -954,8 +939,7 @@ def always_on_top(self, value): elif value in ('toggle', 'Toggle', -1): coro = self.set('AlwaysOnTop', 'Toggle') else: - raise ValueError( - f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') + raise ValueError(f'"{value}" not a valid option. Please use On/Off/Toggle/True/False/0/1/-1') asyncio.create_task(coro) @@ -1005,6 +989,7 @@ async def func(win): if result is False: return False return True + async for window in async_filter(func, await self.windows()): yield window diff --git a/docs/conf.py b/docs/conf.py index f83818fd..09ec5498 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,6 +14,7 @@ # import os import sys + sys.path.insert(0, os.path.abspath('../')) @@ -45,9 +46,7 @@ 'm2r', ] -autodoc_default_options = { - 'special-members': '__init__' -} +autodoc_default_options = {'special-members': '__init__'} # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -117,15 +116,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -135,8 +131,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'ahk.tex', 'ahk Documentation', - 'Spencer Phillip Young', 'manual'), + (master_doc, 'ahk.tex', 'ahk Documentation', 'Spencer Phillip Young', 'manual'), ] @@ -144,10 +139,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'ahk', 'ahk Documentation', - [author], 1) -] +man_pages = [(master_doc, 'ahk', 'ahk Documentation', [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -156,9 +148,7 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'ahk', 'ahk Documentation', - author, 'ahk', 'Python wrapper for AHK.', - 'Miscellaneous'), + (master_doc, 'ahk', 'ahk Documentation', author, 'ahk', 'Python wrapper for AHK.', 'Miscellaneous'), ] diff --git a/setup.py b/setup.py index d45633fc..58391a57 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ from setuptools import setup from io import open + test_requirements = ['behave', 'behave-classy', 'pytest'] extras = {'test': test_requirements} @@ -36,5 +37,5 @@ tests_require=test_requirements, include_package_data=True, zip_safe=False, - keywords=['ahk', 'autohotkey', 'windows', 'mouse', 'keyboard', 'automation', 'pyautogui'] + keywords=['ahk', 'autohotkey', 'windows', 'mouse', 'keyboard', 'automation', 'pyautogui'], ) diff --git a/tests/features/steps/ahk_steps.py b/tests/features/steps/ahk_steps.py index bd545a02..639c06ed 100644 --- a/tests/features/steps/ahk_steps.py +++ b/tests/features/steps/ahk_steps.py @@ -28,4 +28,5 @@ def check_position(self, xpos, ypos): assert x == xpos assert y == ypos + AHKSteps().register() diff --git a/tests/unittests/test_blocking_mouse.py b/tests/unittests/test_blocking_mouse.py index fb74f1be..ee9c3352 100644 --- a/tests/unittests/test_blocking_mouse.py +++ b/tests/unittests/test_blocking_mouse.py @@ -1,6 +1,7 @@ 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 diff --git a/tests/unittests/test_daemon.py b/tests/unittests/test_daemon.py index 1ff4750b..04ba8895 100644 --- a/tests/unittests/test_daemon.py +++ b/tests/unittests/test_daemon.py @@ -15,7 +15,6 @@ from ahk.keys import KEYS - class TestKeyboardDaemon(TestCase): def setUp(self): """ @@ -95,11 +94,12 @@ def setUp(self) -> None: def test_mouse_move(self): x, y = self.ahk.mouse_position self.ahk.mouse_move(10, 10, relative=True) - assert self.ahk.mouse_position == (x+10, y+10) + assert self.ahk.mouse_position == (x + 10, y + 10) def tearDown(self) -> None: self.ahk.stop() + class TestScreen(TestCase): def setUp(self): """ diff --git a/tests/unittests/test_daemon_async.py b/tests/unittests/test_daemon_async.py index f09cbb31..d178b460 100644 --- a/tests/unittests/test_daemon_async.py +++ b/tests/unittests/test_daemon_async.py @@ -12,6 +12,7 @@ from ahk.window import AsyncWindow, WindowNotFoundError from PIL import Image + class TestMouseDaemonAsync(IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: self.ahk = AsyncAHKDaemon() @@ -23,11 +24,12 @@ def tearDown(self) -> None: async def test_mouse_move(self): x, y = await self.ahk.mouse_position await self.ahk.mouse_move(10, 10, relative=True) - assert await self.ahk.mouse_position == (x+10, y+10) + assert await self.ahk.mouse_position == (x + 10, y + 10) class TestWindowDaemonAsync(IsolatedAsyncioTestCase): win: AsyncWindow + async def asyncSetUp(self): self.ahk = AsyncAHKDaemon() await self.ahk.start() @@ -46,7 +48,6 @@ async def test_transparent(self): await asyncio.sleep(0.5) self.assertEqual(await self.win.transparent, 255) - async def test_pinned(self): self.assertFalse(await self.win.always_on_top) @@ -95,7 +96,8 @@ async def test_max_min(self): await asyncio.sleep(0.5) self.assertTrue(await self.win.maximized) self.assertTrue(await self.win.is_maximized()) -# + + # async def test_names(self): self.assertEqual(await self.win.class_name, b'Notepad') self.assertEqual(await self.win.get_class_name(), b'Notepad') @@ -170,11 +172,9 @@ async def asyncTearDown(self): await asyncio.sleep(0.5) self.ahk.stop() - async def test_get_calculator(self): assert await self.win.position - async def test_win_close(self): await self.win.close() try: @@ -188,13 +188,13 @@ async def test_win_close(self): async def test_find_window_func(self): async def func(win): return b'Untitled' in await win.title + assert self.win == await self.ahk.find_window(func=func) async def test_getattr_window_subcommand(self): assert isinstance(await self.win.pid, str) - class TestKeyboardDaemonAsync(IsolatedAsyncioTestCase): async def asyncSetUp(self): """ diff --git a/tests/unittests/test_executable_location.py b/tests/unittests/test_executable_location.py index edad2c7d..e8631d1c 100644 --- a/tests/unittests/test_executable_location.py +++ b/tests/unittests/test_executable_location.py @@ -20,8 +20,7 @@ def check_pwd(): """ 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)') + pytest.skip('Skipping because autohotkey is in present directory (and will therefore always be found)') def test_no_executable_raises_error(): diff --git a/tests/unittests/test_keyboard.py b/tests/unittests/test_keyboard.py index ba63cdb8..dfdfaead 100644 --- a/tests/unittests/test_keyboard.py +++ b/tests/unittests/test_keyboard.py @@ -7,9 +7,7 @@ from unittest import TestCase import pytest -project_root = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..') -) +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 @@ -85,6 +83,7 @@ def test_set_capslock_state(self): self.ahk.set_capslock_state('on') assert self.ahk.key_state('CapsLock', 'T') + class TestKeyboardDaemon(TestKeyboard): def setUp(self): self.ahk = AHKDaemon() @@ -98,6 +97,7 @@ def tearDown(self): super().tearDown() self.ahk.stop() + def a_down(): time.sleep(0.5) ahk = AHK() diff --git a/tests/unittests/test_keyboard_async.py b/tests/unittests/test_keyboard_async.py index 2ddc3527..f1199502 100644 --- a/tests/unittests/test_keyboard_async.py +++ b/tests/unittests/test_keyboard_async.py @@ -8,9 +8,7 @@ from unittest import TestCase, IsolatedAsyncioTestCase -project_root = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..') -) +project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) sys.path.insert(0, project_root) from ahk import AsyncAHK, AHK from ahk.keys import ALT, CTRL, KEYS @@ -23,8 +21,8 @@ def setUp(self): :return: """ self.ahk = AsyncAHK() - #self._normal_ahk = AHK() - #self.before_windows = self._normal_ahk.windows() + # self._normal_ahk = AHK() + # self.before_windows = self._normal_ahk.windows() self.p = subprocess.Popen('notepad') time.sleep(1) @@ -74,6 +72,7 @@ def press_a(): ahk = AHK() ahk.key_press('a') + # class TestKeysAsync(IsolatedAsyncioTestCase): def setUp(self): @@ -126,11 +125,9 @@ async def a_key_wait_timeout(self): def test_key_wait_timeout(self): self.assertRaises(TimeoutError, asyncio.run, self.a_key_wait_timeout()) - async def test_key_state_when_not_pressed(self): self.assertFalse(await self.ahk.key_state('a')) - async def test_key_state_pressed(self): await self.ahk.key_down('Control') self.assertTrue(await self.ahk.key_state('Control')) diff --git a/tests/unittests/test_mouse.py b/tests/unittests/test_mouse.py index fd4239c0..8d89e0dd 100644 --- a/tests/unittests/test_mouse.py +++ b/tests/unittests/test_mouse.py @@ -8,16 +8,13 @@ from itertools import product from functools import partial from unittest import TestCase, IsolatedAsyncioTestCase -project_root = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..') -) + +project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) sys.path.insert(0, project_root) from ahk import AsyncAHK, AHK from ahk.daemon import AHKDaemon - - class TestMouse(TestCase): def setUp(self) -> None: self.ahk = AHK() @@ -32,7 +29,7 @@ def tearDown(self) -> None: def test_mouse_move(self): x, y = self.ahk.mouse_position self.ahk.mouse_move(10, 10, relative=True) - assert self.ahk.mouse_position == (x+10, y+10) + assert self.ahk.mouse_position == (x + 10, y + 10) def test_mouse_move_absolute(self): original_x, original_y = self.original_position @@ -44,7 +41,7 @@ def test_mouse_move_absolute(self): def test_mouse_move_callable_speed(self): x, y = self.ahk.mouse_position self.ahk.mouse_move(10, 10, relative=True, speed=lambda: 10) - assert self.ahk.mouse_position == (x+10, y+10) + assert self.ahk.mouse_position == (x + 10, y + 10) def test_mouse_drag(self): self.notepad_process = subprocess.Popen('notepad') @@ -73,6 +70,7 @@ def tearDown(self) -> None: super().tearDown() self.ahk.stop() + class TestMouseAsync(IsolatedAsyncioTestCase): def setUp(self) -> None: self.ahk = AsyncAHK() @@ -80,4 +78,4 @@ def setUp(self) -> None: async def test_mouse_move(self): x, y = await self.ahk.mouse_position await self.ahk.mouse_move(10, 10, relative=True) - assert await self.ahk.mouse_position == (x+10, y+10) + assert await self.ahk.mouse_position == (x + 10, y + 10) diff --git a/tests/unittests/test_screen.py b/tests/unittests/test_screen.py index fd78267b..3e7d825e 100644 --- a/tests/unittests/test_screen.py +++ b/tests/unittests/test_screen.py @@ -1,5 +1,6 @@ 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 @@ -9,6 +10,7 @@ import time from ahk.daemon import AHKDaemon + class TestScreen(TestCase): def setUp(self): """ @@ -45,6 +47,7 @@ def test_pixel_get_color(self): self.assertIsNotNone(result) self.assertEqual(int(result, 16), 0xFF0000) + class TestScreenDaemon(TestScreen): def setUp(self): self.ahk = AHKDaemon() diff --git a/tests/unittests/test_screen_async.py b/tests/unittests/test_screen_async.py index a96f52b8..e3227121 100644 --- a/tests/unittests/test_screen_async.py +++ b/tests/unittests/test_screen_async.py @@ -1,6 +1,7 @@ import asyncio 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, AsyncAHK diff --git a/tests/unittests/test_win_get.py b/tests/unittests/test_win_get.py index 18c145a7..f48d2b83 100644 --- a/tests/unittests/test_win_get.py +++ b/tests/unittests/test_win_get.py @@ -1,14 +1,17 @@ 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: @@ -21,6 +24,7 @@ def test_get_calculator(): if p is not None: p.terminate() + def test_win_close(): p = None try: @@ -36,13 +40,16 @@ def test_win_close(): 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: diff --git a/tests/unittests/test_win_get_async.py b/tests/unittests/test_win_get_async.py index 9e6231ab..aa6215a7 100644 --- a/tests/unittests/test_win_get_async.py +++ b/tests/unittests/test_win_get_async.py @@ -2,6 +2,7 @@ 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, AsyncAHK @@ -10,6 +11,7 @@ import subprocess from unittest import IsolatedAsyncioTestCase + class TestWinGetAsync(IsolatedAsyncioTestCase): def setUp(self): self.ahk = AsyncAHK() @@ -22,7 +24,6 @@ def tearDown(self): self.p.terminate() asyncio.run(asyncio.sleep(0.5)) - async def test_get_calculator(self): assert await self.win.position @@ -37,6 +38,7 @@ def test_win_close(self): async def test_find_window_func(self): async def func(win): return b'Untitled' in await win.title + assert self.win == await self.ahk.find_window(func=func) async def test_getattr_window_subcommand(self): diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index 63e366db..604d7eb3 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -2,16 +2,15 @@ import time from unittest import TestCase import os, sys -project_root = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..') -) + +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.daemon import AHKDaemon -class TestWindow(TestCase): +class TestWindow(TestCase): def setUp(self): self.ahk = AHK() self.p = subprocess.Popen('notepad') @@ -90,17 +89,17 @@ def test_rect_setter(self): get rect ;-) """ x, y, width, height = self.win.rect - self.win.rect = (x+10, y+10, width+10, height+10) - assert self.win.rect == (x+10, y+10, width+10, height+10) + self.win.rect = (x + 10, y + 10, width + 10, height + 10) + assert self.win.rect == (x + 10, y + 10, width + 10, height + 10) def test_title_change(self): self.win.title = 'foo' assert self.win.title == b'foo' - def tearDown(self): self.p.terminate() + class TestWindowDaemon(TestWindow): def setUp(self): self.ahk = AHKDaemon() diff --git a/tests/unittests/test_window_async.py b/tests/unittests/test_window_async.py index 3ae87e50..c0de45e6 100644 --- a/tests/unittests/test_window_async.py +++ b/tests/unittests/test_window_async.py @@ -4,9 +4,8 @@ import asyncio import os import sys -project_root = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..') -) + +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, AsyncAHK from ahk.window import AsyncWindow @@ -14,6 +13,7 @@ class TestWindowAsync(IsolatedAsyncioTestCase): win: AsyncWindow + def setUp(self): self.ahk = AsyncAHK() self.p = subprocess.Popen('notepad') @@ -31,12 +31,13 @@ async def a_transparent(self): await asyncio.sleep(0.5) self.assertEqual(await self.win.transparent, 255) - def test_transparent(self): asyncio.run(self.a_transparent()) -# + + # def test_pinned(self): asyncio.run(self.a_pinned()) + async def a_pinned(self): self.assertFalse(await self.win.always_on_top) @@ -85,7 +86,8 @@ async def test_max_min(self): await asyncio.sleep(0.5) self.assertTrue(await self.win.maximized) self.assertTrue(await self.win.is_maximized()) -# + + # async def test_names(self): self.assertEqual(await self.win.class_name, b'Notepad') self.assertEqual(await self.win.get_class_name(), b'Notepad') From 81e0e13ff075f0b2bb2fb6ad6a0a69d672879c78 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Nov 2021 19:40:12 +0000 Subject: [PATCH 172/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 21.10b0 → 21.11b1](https://github.com/psf/black/compare/21.10b0...21.11b1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ce39d37f..ee03da09 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '21.10b0' + rev: '21.11b1' hooks: - id: black args: From de3f3ec86bf9eb386c2e54f7008d25a9752f6804 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 19:51:32 +0000 Subject: [PATCH 173/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 21.11b1 → 21.12b0](https://github.com/psf/black/compare/21.11b1...21.12b0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee03da09..36124689 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '21.11b1' + rev: '21.12b0' hooks: - id: black args: From f1e7c1f10d7362b333cdce99aedc259f0eb65cc6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 9 Dec 2021 10:19:05 -0800 Subject: [PATCH 174/588] fix docs May not be a permanent fix. See miyakogi/m2r#36 --- docs/docrequirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docrequirements.txt b/docs/docrequirements.txt index 61cb3b8e..91cc7b7e 100644 --- a/docs/docrequirements.txt +++ b/docs/docrequirements.txt @@ -1,4 +1,5 @@ sphinx<3 +mistune<2 sphinx-rtd-theme sphinx-autodoc-typehints<1.11 m2r From 0388ed2ad3d8e84d125ffd04add4387579efe467 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 10 Dec 2021 21:16:51 -0800 Subject: [PATCH 175/588] fix error in readme for Window.from_pid Resolves #119 --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 09cd21f1..27c75e9e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -93,7 +93,7 @@ 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) # the window under the mouse cursor -win = Window.from_pid('20366') # by process ID +win = Window.from_pid(ahk, pid='20366') # by process ID ``` ### Working with windows From 67e2c6d576b99db0184c845cea7ffae56cbd35bd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Dec 2021 19:53:45 +0000 Subject: [PATCH 176/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.0.1 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.0.1...v4.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 36124689..577a7847 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: check-yaml - id: end-of-file-fixer From c3d53a2c36396712829918b7af614f1c29bac94f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Jan 2022 20:41:43 +0000 Subject: [PATCH 177/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 21.12b0 → 22.1.0](https://github.com/psf/black/compare/21.12b0...22.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 577a7847..4b8fef7d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '21.12b0' + rev: '22.1.0' hooks: - id: black args: From f06bcf5eca3c307f7e2678de72c88ccfb6f487d4 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 2 Feb 2022 15:54:08 -0800 Subject: [PATCH 178/588] Ci fix (#139) * remove behave tests * use bat for tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- appveyor.yml | 3 ++- ci/runtests.bat | 5 +++++ ci/runtests.ps1 | 19 ------------------- ci/uploadjunit.ps1 | 5 +++++ 4 files changed, 12 insertions(+), 20 deletions(-) create mode 100644 ci/runtests.bat delete mode 100644 ci/runtests.ps1 create mode 100644 ci/uploadjunit.ps1 diff --git a/appveyor.yml b/appveyor.yml index 91196421..63e829df 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,10 +14,11 @@ artifacts: path: dist\* test_script: - - powershell .\ci\runtests.ps1 + - cmd: .\ci\runtests.bat on_finish: - cmd: | + powershell .\ci\uploadjunit.ps1 venv\Scripts\activate.bat IF DEFINED COVERALLS_REPO_TOKEN (python -m coveralls) ELSE (echo skipping coveralls report for external pr) diff --git a/ci/runtests.bat b/ci/runtests.bat new file mode 100644 index 00000000..203446fa --- /dev/null +++ b/ci/runtests.bat @@ -0,0 +1,5 @@ +call venv\Scripts\activate.bat +mkdir reports +coverage run -m pytest .\tests\unittests --junitxml=reports\pytestresults.xml +coverage report +call deactivate diff --git a/ci/runtests.ps1 b/ci/runtests.ps1 deleted file mode 100644 index 08f73a0b..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 diff --git a/ci/uploadjunit.ps1 b/ci/uploadjunit.ps1 new file mode 100644 index 00000000..154f08ed --- /dev/null +++ b/ci/uploadjunit.ps1 @@ -0,0 +1,5 @@ +$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)) +} From 355aec37bbce9d1167de40049c945baad6b2d639 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 19:44:06 +0000 Subject: [PATCH 179/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.1.0 → 22.3.0](https://github.com/psf/black/compare/22.1.0...22.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4b8fef7d..d302ba8d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '22.1.0' + rev: '22.3.0' hooks: - id: black args: From c4c916aa483063744c9dbc99661580defee4b921 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 20:35:02 +0000 Subject: [PATCH 180/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.1.0 → v4.2.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.1.0...v4.2.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d302ba8d..3fb35378 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: - id: check-yaml - id: end-of-file-fixer From 1f86053a5abce81cba007e20dbd83c48b1188696 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 21:24:42 +0000 Subject: [PATCH 181/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.2.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.2.0...v4.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3fb35378..0ca72d15 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: check-yaml - id: end-of-file-fixer From cb58732634ab11b82d41a59b44ac8f9557078e00 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 14 Jun 2022 15:47:33 -0700 Subject: [PATCH 182/588] make find_window and win_get consistent --- ahk/window.py | 6 +++++- tests/unittests/test_window.py | 15 +++++++++++++++ tests/unittests/test_window_async.py | 12 ++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/ahk/window.py b/ahk/window.py index 804a165b..459755a2 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -652,6 +652,8 @@ def win_get(self, title='', text='', exclude_title='', exclude_text='', encoding script = self._win_get(title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text) encoding = encoding or self.window_encoding ahk_id = self.run_script(script) + if not ahk_id: + return None return Window(engine=self, ahk_id=ahk_id, encoding=encoding) def _win_set(self, subcommand, *args, blocking=True): @@ -948,6 +950,8 @@ async def win_get(self, *args, **kwargs): encoding = kwargs.pop('encoding', self.window_encoding) script = self._win_get(*args, **kwargs) ahk_id = await self.a_run_script(script) + if not ahk_id: + return None return AsyncWindow(engine=self, ahk_id=ahk_id, encoding=encoding) async def _all_window_ids(self): @@ -1004,7 +1008,7 @@ async def find_window(self, func=None, **kwargs): """ async for window in self.find_windows(func=func, **kwargs): return window # return the first result - raise WindowNotFoundError('yikes') + return None async def find_windows_by_title(self, title, exact=False): """ diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index 604d7eb3..712e2c6a 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -3,11 +3,14 @@ from unittest import TestCase import os, sys +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.daemon import AHKDaemon +from ahk.window import WindowNotFoundError class TestWindow(TestCase): @@ -99,6 +102,18 @@ def test_title_change(self): def tearDown(self): self.p.terminate() + def test_find_window(self): + win = self.ahk.find_window(title=b'Untitled - Notepad') + assert win.id == self.win.id + + def test_find_window_nonexistent_is_none(self): + win = self.ahk.find_window(title=b'This should not exist') + assert win is None + + def test_winget_nonexistent_window_is_none(self): + win = self.ahk.win_get(title='This should not exist') + assert win is None + class TestWindowDaemon(TestWindow): def setUp(self): diff --git a/tests/unittests/test_window_async.py b/tests/unittests/test_window_async.py index c0de45e6..a51eaf54 100644 --- a/tests/unittests/test_window_async.py +++ b/tests/unittests/test_window_async.py @@ -104,6 +104,18 @@ async def test_title_setter(self): await self.win.set_title('new title') assert await self.win.get_title() != starting_title + async def test_find_window(self): + win = await self.ahk.find_window(title=b'Untitled - Notepad') + assert win.id == self.win.id + + async def test_find_window_nonexistent_is_none(self): + win = await self.ahk.find_window(title=b'This should not exist') + assert win is None + + async def test_winget_nonexistent_window_is_none(self): + win = await self.ahk.win_get(title='This should not exist') + assert win is None + def tearDown(self): self.p.terminate() asyncio.run(asyncio.sleep(0.5)) From 20e19c425dcbfebaea51a351e17104116df114c1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 15 Jun 2022 10:22:53 -0700 Subject: [PATCH 183/588] implement win_wait --- ahk/templates/_daemon.ahk | 14 +++++++++ ahk/templates/daemon.ahk | 14 +++++++++ ahk/templates/daemon/win_wait.ahk | 1 + ahk/templates/window/win_wait.ahk | 9 ++++++ ahk/window.py | 45 +++++++++++++++++++++++----- docs/README.md | 7 +++++ tests/unittests/test_window.py | 8 +++++ tests/unittests/test_window_async.py | 10 +++++++ 8 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 ahk/templates/daemon/win_wait.ahk create mode 100644 ahk/templates/window/win_wait.ahk diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index 3fa6e86f..c08731ab 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -504,6 +504,20 @@ AHKWinGetPos(ByRef command) { return s } +AHKWinWait(ByRef command) { + title := command[2] + text := command[3] + timeout := command[4] + extitle := command[5] + extext := command[6] + WinWait,%title%,%text%,%timeout%,%extitle%,%extext% + if !ErrorLevel + { + WinGet, output, ID + return output + } +} + CountNewlines(ByRef s) { newline := "`n" StringReplace, s, s, %newline%, %newline%, UseErrorLevel diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 72208ebc..0b1c0b44 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -512,6 +512,20 @@ AHKWinGetPos(ByRef command) { return s } +AHKWinWait(ByRef command) { + title := command[2] + text := command[3] + timeout := command[4] + extitle := command[5] + extext := command[6] + WinWait,%title%,%text%,%timeout%,%extitle%,%extext% + if !ErrorLevel + { + WinGet, output, ID + return output + } +} + CountNewlines(ByRef s) { newline := "`n" StringReplace, s, s, %newline%, %newline%, UseErrorLevel diff --git a/ahk/templates/daemon/win_wait.ahk b/ahk/templates/daemon/win_wait.ahk new file mode 100644 index 00000000..1d2f141d --- /dev/null +++ b/ahk/templates/daemon/win_wait.ahk @@ -0,0 +1 @@ +AHKWinWait,{{title}},{{text}},{{timeout}},{{exclude_title}},{{exclude_text}} diff --git a/ahk/templates/window/win_wait.ahk b/ahk/templates/window/win_wait.ahk new file mode 100644 index 00000000..97112e73 --- /dev/null +++ b/ahk/templates/window/win_wait.ahk @@ -0,0 +1,9 @@ +{% extends "base.ahk" %} +{% block body %} +WinWait,{{title}},{{text}},{{timeout}},{{exclude_title}},{{exclude_text}} +if !ErrorLevel +{ + WinGet, output, ID + FileAppend,%output%,* +} +{% endblock body %} diff --git a/ahk/window.py b/ahk/window.py index 459755a2..26cb86ec 100644 --- a/ahk/window.py +++ b/ahk/window.py @@ -513,14 +513,6 @@ def show(self): """ return self._base_method('WinShow') or None - def wait(self, seconds_to_wait=''): - """ - - :param seconds_to_wait: - :return: - """ - return self._base_method('WinWait', seconds_to_wait=seconds_to_wait, blocking=True) or None - def wait_active(self, seconds_to_wait=''): """ @@ -656,6 +648,28 @@ def win_get(self, title='', text='', exclude_title='', exclude_text='', encoding return None return Window(engine=self, ahk_id=ahk_id, encoding=encoding) + def win_wait(self, *, title='', text='', exclude_title='', timeout=0.5, exclude_text='', encoding=None): + """ + WinWait + Wait for a window. If found within the timeout (in seconds), returns a Window object. + If not found, raises a TimeoutError + + ref: https://www.autohotkey.com/docs/commands/WinWait.htm + """ + script = self.render_template( + 'window/win_wait.ahk', + title=title, + text=text, + exclude_title=exclude_title, + timeout=timeout, + exclude_text=exclude_text, + ) + encoding = encoding or self.window_encoding + ahk_id = self.run_script(script) + if not ahk_id: + raise TimeoutError(f'No window found after timeout ({timeout})') + 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) return script @@ -1071,3 +1085,18 @@ async def find_window_by_class(self, *args, **kwargs): """ async for window in self.find_windows_by_class(*args, **kwargs): return window + + async def win_wait(self, *, title='', text='', exclude_title='', timeout=0.5, exclude_text='', encoding=None): + script = self.render_template( + 'window/win_wait.ahk', + title=title, + text=text, + exclude_title=exclude_title, + timeout=timeout, + exclude_text=exclude_text, + ) + encoding = encoding or self.window_encoding + ahk_id = await self.a_run_script(script) + if not ahk_id: + raise TimeoutError(f'No window found after timeout ({timeout})') + return AsyncWindow(engine=self, ahk_id=ahk_id, encoding=encoding) diff --git a/docs/README.md b/docs/README.md index 27c75e9e..4fd6d8d2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -94,6 +94,13 @@ win = list(ahk.windows()) # list of all windows win = Window(ahk, ahk_id='0xabc123') # by ahk_id win = Window.from_mouse_position(ahk) # the window under the mouse cursor win = Window.from_pid(ahk, pid='20366') # by process ID + +# Wait for a window +try: + # wait up to 5 seconds for notepad + win = ahk.win_wait(title='Untitled - Notepad', timeout=5) +except TimeoutError: + print('Notepad was not found!') ``` ### Working with windows diff --git a/tests/unittests/test_window.py b/tests/unittests/test_window.py index 712e2c6a..afc58832 100644 --- a/tests/unittests/test_window.py +++ b/tests/unittests/test_window.py @@ -114,6 +114,14 @@ def test_winget_nonexistent_window_is_none(self): win = self.ahk.win_get(title='This should not exist') assert win is None + def test_winwait_nonexistent_raises_timeout_error(self): + with pytest.raises(TimeoutError): + win = self.ahk.win_wait(title='This should not exist') + + def test_winwait_existing_window(self): + win = self.ahk.win_wait(title='Untitled - Notepad') + assert win.id == self.win.id + class TestWindowDaemon(TestWindow): def setUp(self): diff --git a/tests/unittests/test_window_async.py b/tests/unittests/test_window_async.py index a51eaf54..1a905921 100644 --- a/tests/unittests/test_window_async.py +++ b/tests/unittests/test_window_async.py @@ -5,6 +5,8 @@ import os import sys +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, AsyncAHK @@ -116,6 +118,14 @@ async def test_winget_nonexistent_window_is_none(self): win = await self.ahk.win_get(title='This should not exist') assert win is None + async def test_winwait_nonexistent_raises_timeout_error(self): + with pytest.raises(TimeoutError): + win = await self.ahk.win_wait(title='This should not exist') + + async def test_winwait_existing_window(self): + win = await self.ahk.win_wait(title='Untitled - Notepad') + assert win.id == self.win.id + def tearDown(self): self.p.terminate() asyncio.run(asyncio.sleep(0.5)) From 4693f77ed2c301c9c3395e58c0a46bd52c24acba Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 15 Jun 2022 10:41:05 -0700 Subject: [PATCH 184/588] adapt tests for win_get API change --- tests/unittests/test_daemon_async.py | 9 ++------- tests/unittests/test_win_get.py | 3 +-- tests/unittests/test_win_get_async.py | 5 +++-- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/unittests/test_daemon_async.py b/tests/unittests/test_daemon_async.py index d178b460..a1f926af 100644 --- a/tests/unittests/test_daemon_async.py +++ b/tests/unittests/test_daemon_async.py @@ -177,13 +177,8 @@ async def test_get_calculator(self): async def test_win_close(self): await self.win.close() - try: - win = await self.ahk.win_get(title='Untitled - Notepad') - await win.position - except WindowNotFoundError as e: - pass - else: - raise AssertionError('Expected WindowNotFoundError') + win = await self.ahk.win_get(title='Untitled - Notepad') + assert win is None async def test_find_window_func(self): async def func(win): diff --git a/tests/unittests/test_win_get.py b/tests/unittests/test_win_get.py index f48d2b83..5771ebed 100644 --- a/tests/unittests/test_win_get.py +++ b/tests/unittests/test_win_get.py @@ -34,8 +34,7 @@ def test_win_close(): assert win assert win.position win.close() - with pytest.raises(WindowNotFoundError): - ahk.win_get(title='Untitled - Notepad').position + assert ahk.win_get(title='Untitled - Notepad') is None finally: if p is not None: p.terminate() diff --git a/tests/unittests/test_win_get_async.py b/tests/unittests/test_win_get_async.py index aa6215a7..4e72c060 100644 --- a/tests/unittests/test_win_get_async.py +++ b/tests/unittests/test_win_get_async.py @@ -29,11 +29,12 @@ async def test_get_calculator(self): async def a_win_get(self): win = await self.ahk.win_get(title='Untitled - Notepad') - await win.position + return win def test_win_close(self): asyncio.run(self.win.close()) - self.assertRaises(WindowNotFoundError, asyncio.run, self.a_win_get()) + win = asyncio.run(self.a_win_get()) + assert win is None async def test_find_window_func(self): async def func(win): From e7f707db2cd21a1f7645f835d8c577a5dd137d67 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 15 Jun 2022 11:17:09 -0700 Subject: [PATCH 185/588] add UTF-8 codepage explicitly --- ahk/script.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/script.py b/ahk/script.py index 5256be82..064fb803 100644 --- a/ahk/script.py +++ b/ahk/script.py @@ -132,7 +132,7 @@ def render_template(self, template_name, directives=None, blocking=True, **kwarg def _run_script(self, script_text, **kwargs): blocking = kwargs.pop('blocking', True) - runargs = [self.executable_path, '/ErrorStdOut', '*'] + runargs = [self.executable_path, '/CP65001', '/ErrorStdOut', '*'] decode = kwargs.pop('decode', False) script_bytes = bytes(script_text, 'utf-8') if blocking: @@ -159,7 +159,7 @@ async def _a_run_script(self, script_text, **kwargs): blocking = kwargs.pop('blocking', True) if blocking is not True: warnings.warn('blocking=False will probably result in problems', stacklevel=2) - runargs = [self.executable_path, '/ErrorStdOut', '*'] + runargs = [self.executable_path, '/CP65001', '/ErrorStdOut', '*'] proc = await asyncio.subprocess.create_subprocess_exec( *runargs, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) From 60e01d7cce840c379e36410ce95e227ef8d95534 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 15 Jun 2022 11:36:46 -0700 Subject: [PATCH 186/588] bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 58391a57..e98654b5 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='ahk', - version='0.13.0', + version='0.14.0', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From 8af0fe5eb9fb86dad26482ccf8334d2be7a3a03d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Jul 2022 21:21:08 +0000 Subject: [PATCH 187/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.3.0 → 22.6.0](https://github.com/psf/black/compare/22.3.0...22.6.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0ca72d15..a8b55ccb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '22.3.0' + rev: '22.6.0' hooks: - id: black args: From 4e8868ecbe0cd2e9b23ee3b8fa606d899faf65e9 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 12 Jul 2022 20:22:57 -0700 Subject: [PATCH 188/588] inital empty commit From 7a9e3e19183649424767b3ce479bbc909fa5105b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 12 Jul 2022 20:31:17 -0700 Subject: [PATCH 189/588] initial progress --- .build.py | 6 + .pre-commit-config.yaml | 46 ++++ .unasync-rewrite.py | 74 ++++++ ahk/__init__.py | 0 ahk/_async/__init__.py | 1 + ahk/_async/engine.py | 24 ++ ahk/_async/transport.py | 469 ++++++++++++++++++++++++++++++++++ ahk/_async/window.py | 12 + ahk/_sync/__init__.py | 1 + ahk/_sync/engine.py | 24 ++ ahk/_sync/transport.py | 459 +++++++++++++++++++++++++++++++++ ahk/_sync/window.py | 12 + ahk/daemon.ahk | 553 ++++++++++++++++++++++++++++++++++++++++ ahk/executor.ahk | 0 ahk/message.py | 184 +++++++++++++ setup.py | 38 +++ tests/message_test.py | 0 17 files changed, 1903 insertions(+) create mode 100644 .build.py create mode 100644 .pre-commit-config.yaml create mode 100644 .unasync-rewrite.py create mode 100644 ahk/__init__.py create mode 100644 ahk/_async/__init__.py create mode 100644 ahk/_async/engine.py create mode 100644 ahk/_async/transport.py create mode 100644 ahk/_async/window.py create mode 100644 ahk/_sync/__init__.py create mode 100644 ahk/_sync/engine.py create mode 100644 ahk/_sync/transport.py create mode 100644 ahk/_sync/window.py create mode 100644 ahk/daemon.ahk create mode 100644 ahk/executor.ahk create mode 100644 ahk/message.py create mode 100644 setup.py create mode 100644 tests/message_test.py diff --git a/.build.py b/.build.py new file mode 100644 index 00000000..8c4b344d --- /dev/null +++ b/.build.py @@ -0,0 +1,6 @@ +def main() -> int: + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..c2a3d4c5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,46 @@ +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) + pass_filenames: false + additional_dependencies: + - unasync + - tokenize_rt + - black + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.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 + rev: '22.6.0' + hooks: + - id: black + args: + - "-S" + - "-l" + - "120" + exclude: ^(ahk/_sync/.*\.py) +- repo: https://github.com/asottile/reorder_python_imports + rev: v3.7.0 + hooks: + - id: reorder-python-imports + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.931' + hooks: + - id: mypy + args: + - "--strict" + exclude: ^(tests/.*|setup\.py|\.build\.py|\.unasync-rewrite.py) diff --git a/.unasync-rewrite.py b/.unasync-rewrite.py new file mode 100644 index 00000000..dee81619 --- /dev/null +++ b/.unasync-rewrite.py @@ -0,0 +1,74 @@ +import ast +import os +import shutil +import subprocess + +import black +from black import check_stability_and_equivalence +from tokenize_rt import reversed_enumerate +from tokenize_rt import src_to_tokens +from tokenize_rt import tokens_to_src + +changes = 0 + + +def _rewrite_file(filename: str) -> int: + with open(filename, encoding='UTF-8') as f: + contents = f.read() + tree = ast.parse(contents, filename=filename) + tokens = src_to_tokens(contents) + nodes_on_lines_to_remove = [] + for tok in tokens: + if tok.name == 'COMMENT' and 'unasync: remove' in tok.src: + nodes_on_lines_to_remove.append(tok.line) + lines_to_remove = set() + for node in ast.walk(tree): + if hasattr(node, 'lineno') and node.lineno in nodes_on_lines_to_remove: + for lineno in range(node.lineno, node.end_lineno + 1): + lines_to_remove.add(lineno) + for i, tok in reversed_enumerate(tokens): + if tok.line in lines_to_remove: + tokens.pop(i) + new_contents = tokens_to_src(tokens) + if new_contents != contents: + with open(filename, 'w') as f: + f.write(new_contents) + return new_contents != contents + + +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 AssertionError: + changes += 1 + print('MODIFIED', dst) + shutil.copy2(src, dst, follow_symlinks=follow_symlinks) + + return dst + + +def main() -> int: + if os.path.isdir('build'): + shutil.rmtree('build') + subprocess.run(['python', 'setup.py', 'build_py'], check=True, shell=True) + for root, dirs, files in os.walk('build/lib/ahk/_sync'): + for fname in files: + if fname.endswith('.py'): + fp = os.path.join(root, fname) + _rewrite_file(fp) + + shutil.copytree('build/lib/ahk/_sync', 'ahk/_sync', dirs_exist_ok=True, copy_function=_copyfunc) + return changes + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/ahk/__init__.py b/ahk/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ahk/_async/__init__.py b/ahk/_async/__init__.py new file mode 100644 index 00000000..cdc15cde --- /dev/null +++ b/ahk/_async/__init__.py @@ -0,0 +1 @@ +from .engine import AsyncAHK diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py new file mode 100644 index 00000000..bd22bf0b --- /dev/null +++ b/ahk/_async/engine.py @@ -0,0 +1,24 @@ +from collections import deque +from typing import Optional +from typing import Tuple + +from .transport import AsyncDaemonProcessTransport +from .transport import AsyncTransport +from .window import AsyncWindow + + +class AsyncAHK: + def __init__(self, transport: Optional[AsyncTransport] = None): + if transport is None: + transport = AsyncDaemonProcessTransport() + self._transport: AsyncTransport = transport + + async def list_windows(self) -> list[AsyncWindow]: + resp = await self._transport.function_call('WindowList') + window_ids = resp.unpack() + ret = [AsyncWindow(engine=self, ahk_id=ahk_id) for ahk_id in window_ids] + return ret + + async def get_mouse_position(self) -> Tuple[int, int]: + resp = await self._transport.function_call('MouseGetPos') + return resp.unpack() diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py new file mode 100644 index 00000000..66214926 --- /dev/null +++ b/ahk/_async/transport.py @@ -0,0 +1,469 @@ +import asyncio.subprocess +import io +import os +import subprocess +import typing +import warnings +from abc import ABC +from abc import abstractmethod +from io import BytesIO +from shutil import which +from typing import Literal +from typing import Optional + +from ahk.message import BooleanResponseMessage +from ahk.message import CoordinateResponseMessage +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 WindowIDListResponseMessage + +DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' + + +AsyncIOProcess = asyncio.subprocess.Process # unasync: remove + +SyncIOProcess = subprocess.Popen[bytes] + + +class AsyncAHKProcess: + def __init__(self, runargs: list[str]): + self.runargs = runargs + self._proc: Optional[AsyncIOProcess] = None + + async def start(self) -> None: + self._proc = await async_create_process(self.runargs) + 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) + 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 + return await self._proc.stdout.readline() + + +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.PIPE + ) + + +def sync_create_process(runargs: list[str]) -> subprocess.Popen[bytes]: + return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + + +class AhkExecutableNotFoundError(EnvironmentError): + pass + + +def _resolve_executable_path(executable_path: str = '') -> str: + if not executable_path: + executable_path = ( + os.environ.get('AHK_PATH', '') + or which('AutoHotkey.exe') + or which('AutoHotkeyU64.exe') + or which('AutoHotkeyU32.exe') + or which('AutoHotkeyA32.exe') + or '' + ) + + if not executable_path: + 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 may be able to resolve this error by installing the binary extra: pip 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' + ) + + 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 + + +class AsyncTransport(ABC): + async def init(self) -> None: + return None + + @typing.overload + async def function_call( + self, function_name: Literal['ImageSearch'], args: Optional[list[str]] = None + ) -> TupleResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['PixelGetColor'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['PixelSearch'], args: Optional[list[str]] = None + ) -> CoordinateResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['MouseGetPos'], args: Optional[list[str]] = None + ) -> CoordinateResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['AHKKeyState'], args: Optional[list[str]] = None + ) -> BooleanResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['MouseMove'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['CoordMode'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['Click'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['MouseClickDrag'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + # @typing.overload + # async def function_call(self, function_name: Literal['RegRead'], args: Optional[list[str]] = None) -> : + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[list[str]] = None): + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[list[str]] = None): + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[list[str]] = None): + # ... + + @typing.overload + async def function_call( + self, function_name: Literal['KeyWait'], args: Optional[list[str]] = None + ) -> IntegerResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['SetKeyDelay'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['Send'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['SendRaw'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['SendInput'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['SendEvent'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['SendPlay'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['SetCapsLockState'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + # @typing.overload + # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[list[str]] = None) -> NoValueResponseMessage: + # ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinGetTitle'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinGetClass'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinGetText'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinActivate'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinActivateBottom'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinClose'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinHide'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinKill'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinMaximize'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinMinimize'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinRestore'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinShow'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + # + # @typing.overload + # async def function_call(self, function_name: Literal['WinWait'], args: Optional[list[str]] = None) -> StringResponseMessage: + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[list[str]] = None) -> StringResponseMessage: + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[list[str]] = None) -> StringResponseMessage: + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[list[str]] = None) -> : + # ... + + @typing.overload + async def function_call( + self, function_name: Literal['WindowList'], args: Optional[list[str]] = None + ) -> WindowIDListResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinSend'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinSendRaw'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['ControlSend'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + # @typing.overload + # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[list[str]] = None) -> : + # ... + + @typing.overload + async def function_call( + self, function_name: Literal['FromMouse'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinGet'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinSet'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinSetTitle'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[list[str]] = None + ) -> BooleanResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['WinClick'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['AHKWinMove'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + async def function_call( + self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None + ) -> TupleResponseMessage: + ... + + async def function_call(self, function_name: str, args: Optional[list[str]] = None) -> ResponseMessage: + request = RequestMessage(function_name=function_name, args=args) + return await self.send(request) + + @abstractmethod + async def send(self, request: RequestMessage) -> ResponseMessage: + return NotImplemented + + +# class Process: +# def __init__(self, runargs: list[str]): +# self.runargs = runargs +# +# # def __enter__(self) -> Generator[subprocess.Popen[bytes], None, None]: +# # yield subprocess.Popen(self.runargs, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) +# +# async def __aenter__(self) -> asyncio.subprocess.Process: +# return await async_create_process(self.runargs) +# +# +# async def __aexit__(self, *args: Any, **kwargs: Any) -> None: +# return None +# + + +class AsyncDaemonProcessTransport(AsyncTransport): + def __init__(self, executable_path: str = ''): + self._proc: Optional[AsyncAHKProcess] + self._proc = None + self._executable_path: str = _resolve_executable_path(executable_path=executable_path) + + async def init(self) -> None: + await self.start() + return None + + async def start(self) -> None: + assert self._proc is None, 'cannot start a process twice' + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', 'ahk\\daemon.ahk'] # TODO: build this dynamically + # async with Process(runargs) as proc: + # self._proc = proc + self._proc = AsyncAHKProcess(runargs=runargs) + await self._proc.start() + + async def send(self, request: RequestMessage) -> ResponseMessage: + newline = '\n' + + msg = f"{request.function_name}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode('utf-8') + print('msg', repr(msg)) + assert self._proc is not None + 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) + for _ in range(int(num_lines) + 1): + part = await self._proc.readline() + content_buffer.write(part) + content = content_buffer.getvalue() + response = ResponseMessage.from_bytes(content) + return response diff --git a/ahk/_async/window.py b/ahk/_async/window.py new file mode 100644 index 00000000..291aae98 --- /dev/null +++ b/ahk/_async/window.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .engine import AsyncAHK + + +class AsyncWindow: + def __init__(self, engine: AsyncAHK, ahk_id: str): + self._engine: AsyncAHK = engine + self._ahk_id: str = ahk_id diff --git a/ahk/_sync/__init__.py b/ahk/_sync/__init__.py new file mode 100644 index 00000000..da7e2c14 --- /dev/null +++ b/ahk/_sync/__init__.py @@ -0,0 +1 @@ +from .engine import AHK diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py new file mode 100644 index 00000000..0ca909dc --- /dev/null +++ b/ahk/_sync/engine.py @@ -0,0 +1,24 @@ +from collections import deque +from typing import Optional +from typing import Tuple + +from .transport import DaemonProcessTransport +from .transport import Transport +from .window import Window + + +class AHK: + def __init__(self, transport: Optional[Transport] = None): + if transport is None: + transport = DaemonProcessTransport() + self._transport: Transport = transport + + def list_windows(self) -> list[Window]: + resp = self._transport.function_call('WindowList') + window_ids = resp.unpack() + ret = [Window(engine=self, ahk_id=ahk_id) for ahk_id in window_ids] + return ret + + def get_mouse_position(self) -> Tuple[int, int]: + resp = self._transport.function_call('MouseGetPos') + return resp.unpack() diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py new file mode 100644 index 00000000..d5abea48 --- /dev/null +++ b/ahk/_sync/transport.py @@ -0,0 +1,459 @@ +import asyncio.subprocess +import io +import os +import subprocess +import typing +import warnings +from abc import ABC +from abc import abstractmethod +from io import BytesIO +from shutil import which +from typing import Literal +from typing import Optional + +from ahk.message import BooleanResponseMessage +from ahk.message import CoordinateResponseMessage +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 WindowIDListResponseMessage + +DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' + + + +SyncIOProcess = subprocess.Popen[bytes] + + +class SyncAHKProcess: + def __init__(self, runargs: list[str]): + self.runargs = runargs + self._proc: Optional[SyncIOProcess] = None + + def start(self) -> None: + self._proc = sync_create_process(self.runargs) + return None + + + def drain_stdin(self) -> None: + assert isinstance(self._proc, subprocess.Popen) + 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 + return self._proc.stdout.readline() + + + + +def sync_create_process(runargs: list[str]) -> subprocess.Popen[bytes]: + return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + + +class AhkExecutableNotFoundError(EnvironmentError): + pass + + +def _resolve_executable_path(executable_path: str = '') -> str: + if not executable_path: + executable_path = ( + os.environ.get('AHK_PATH', '') + or which('AutoHotkey.exe') + or which('AutoHotkeyU64.exe') + or which('AutoHotkeyU32.exe') + or which('AutoHotkeyA32.exe') + or '' + ) + + if not executable_path: + 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 may be able to resolve this error by installing the binary extra: pip 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' + ) + + 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 + + +class Transport(ABC): + def init(self) -> None: + return None + + @typing.overload + def function_call( + self, function_name: Literal['ImageSearch'], args: Optional[list[str]] = None + ) -> TupleResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['PixelGetColor'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['PixelSearch'], args: Optional[list[str]] = None + ) -> CoordinateResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['MouseGetPos'], args: Optional[list[str]] = None + ) -> CoordinateResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['AHKKeyState'], args: Optional[list[str]] = None + ) -> BooleanResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['MouseMove'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['CoordMode'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['Click'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['MouseClickDrag'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + # @typing.overload + # async def function_call(self, function_name: Literal['RegRead'], args: Optional[list[str]] = None) -> : + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[list[str]] = None): + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[list[str]] = None): + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[list[str]] = None): + # ... + + @typing.overload + def function_call( + self, function_name: Literal['KeyWait'], args: Optional[list[str]] = None + ) -> IntegerResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['SetKeyDelay'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['Send'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['SendRaw'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['SendInput'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['SendEvent'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['SendPlay'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['SetCapsLockState'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + # @typing.overload + # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[list[str]] = None) -> NoValueResponseMessage: + # ... + + @typing.overload + def function_call( + self, function_name: Literal['WinGetTitle'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinGetClass'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinGetText'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinActivate'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinActivateBottom'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinClose'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinHide'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinKill'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinMaximize'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinMinimize'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinRestore'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinShow'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + # + # @typing.overload + # async def function_call(self, function_name: Literal['WinWait'], args: Optional[list[str]] = None) -> StringResponseMessage: + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[list[str]] = None) -> StringResponseMessage: + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[list[str]] = None) -> StringResponseMessage: + # ... + # + # @typing.overload + # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[list[str]] = None) -> : + # ... + + @typing.overload + def function_call( + self, function_name: Literal['WindowList'], args: Optional[list[str]] = None + ) -> WindowIDListResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinSend'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinSendRaw'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['ControlSend'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + # @typing.overload + # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[list[str]] = None) -> : + # ... + + @typing.overload + def function_call( + self, function_name: Literal['FromMouse'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinGet'], args: Optional[list[str]] = None + ) -> StringResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinSet'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinSetTitle'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[list[str]] = None + ) -> BooleanResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['WinClick'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['AHKWinMove'], args: Optional[list[str]] = None + ) -> NoValueResponseMessage: + ... + + @typing.overload + def function_call( + self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None + ) -> TupleResponseMessage: + ... + + def function_call(self, function_name: str, args: Optional[list[str]] = None) -> ResponseMessage: + request = RequestMessage(function_name=function_name, args=args) + return self.send(request) + + @abstractmethod + def send(self, request: RequestMessage) -> ResponseMessage: + return NotImplemented + + +# class Process: +# def __init__(self, runargs: list[str]): +# self.runargs = runargs +# +# # def __enter__(self) -> Generator[subprocess.Popen[bytes], None, None]: +# # yield subprocess.Popen(self.runargs, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) +# +# async def __aenter__(self) -> asyncio.subprocess.Process: +# return await async_create_process(self.runargs) +# +# +# async def __aexit__(self, *args: Any, **kwargs: Any) -> None: +# return None +# + + +class DaemonProcessTransport(Transport): + def __init__(self, executable_path: str = ''): + self._proc: Optional[SyncAHKProcess] + self._proc = None + self._executable_path: str = _resolve_executable_path(executable_path=executable_path) + + def init(self) -> None: + self.start() + return None + + def start(self) -> None: + assert self._proc is None, 'cannot start a process twice' + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', 'ahk\\daemon.ahk'] # TODO: build this dynamically + # async with Process(runargs) as proc: + # self._proc = proc + self._proc = SyncAHKProcess(runargs=runargs) + self._proc.start() + + def send(self, request: RequestMessage) -> ResponseMessage: + newline = '\n' + + msg = f"{request.function_name}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode('utf-8') + print('msg', repr(msg)) + assert self._proc is not None + 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) + for _ in range(int(num_lines) + 1): + part = self._proc.readline() + content_buffer.write(part) + content = content_buffer.getvalue() + response = ResponseMessage.from_bytes(content) + return response diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py new file mode 100644 index 00000000..a4958974 --- /dev/null +++ b/ahk/_sync/window.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .engine import AHK + + +class Window: + def __init__(self, engine: AHK, ahk_id: str): + self._engine: AHK = engine + self._ahk_id: str = ahk_id diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk new file mode 100644 index 00000000..af1d5e4f --- /dev/null +++ b/ahk/daemon.ahk @@ -0,0 +1,553 @@ +#NoEnv +#Persistent + +RESPONSEMESSAGE := "000" ; ResponseMessage +TUPLERESPONSEMESSAGE := "001" ; TupleResponseMessage +COORDINATERESPONSEMESSAGE := "002" ; CoordinateResponseMessage +INTEGERRESPONSEMESSAGE := "003" ; IntegerResponseMessage +BOOLEANRESPONSEMESSAGE := "004" ; BooleanResponseMessage +STRINGRESPONSEMESSAGE := "005" ; StringResponseMessage +WINDOWIDLISTRESPONSEMESSAGE := "006" ; WindowIDListResponseMessage +NOVALUERESPONSEMESSAGE := "007" ; NoValueResponseMessage +EXCEPTIONRESPONSEMESSAGE := "008" ; ExceptionResponseMessage + +NOVALUE_SENTINEL := Chr(57344) + Chr(57344) + +FormatResponse(MessageType, payload) { + newline_count := CountNewlines(payload) + response .= Format("{}`n{}`n{}`n", MessageType, newline_count, payload) + return response +} + +ImageSearch(ByRef command) { + imagepath := command[8] + x1 := command[4] + y1 := command[5] + x2 := command[6] + y2 := command[7] + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } + ImageSearch, xpos, ypos,% x1,% y1,% x2,% y2, %imagepath% + s .= Format("({}, {})", xpos, ypos) + return s +} + +PixelGetColor(ByRef command) { + x := command[3] + y := command[4] + if (command.Length() = 4) { + PixelGetColor,color,% x,% y + } else { + options := command[5] + PixelGetColor,color,% x,% y, %options% + } + return color +} + +PixelSearch(ByRef command) { + x1 := command[4] + y1 := command[5] + x2 := command[6] + y2 := command[7] + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } + if (command.Length() = 9) { + PixelSearch, xpos, ypos,% x1,% y1,% x2,% y2, command[8], command[9] + } else { + options := command[10] + PixelSearch, xpos, ypos,% x1,% y1,% x2,% y2, command[8], command[9], %options% + } + s .= Format("({}, {})", xpos, ypos) + return s +} + + +MouseGetPos(ByRef command) { + global COORDINATERESPONSEMESSAGE + MouseGetPos, xpos, ypos + payload .= Format("({}, {})", xpos, ypos) + resp .= FormatResponse(COORDINATERESPONSEMESSAGE, payload) + MsgBox,% resp + return resp +} + +AHKKeyState(ByRef command) { + if (command.Length() = 3) { + if (GetKeyState(command[2], command[3])) { + return 1 + } else { + return 0 + } + } else{ + if (GetKeyState(command[2])) { + return 1 + } else { + return 0 + } + } +} + +MouseMove(ByRef command) { + if (command.Length() = 5) { + MouseMove, command[2], command[3], command[4], R + } else { + MouseMove, command[2], command[3], command[4] + } +} + +CoordMode(ByRef command) { + if (command.Length() = 2) { + CoordMode,% command[2] + } else { + CoordMode,% command[2],% command[3] + } +} + + +Click(ByRef command) { + if (command.Length() = 1) { + Click + } else if (command.Length() = 2) { + Click, command[2] + } else if (command.Length() = 3) { + Click, command[2], command[3] + } else if (command.Length() = 4) { + Click, command[2], command[3], command[4] + } else if (command.Length() = 5) { + Click, command[2], command[3], command[4], command[5] + } else if (command.Length() = 6) { + Click, command[2], command[3], command[4], command[5], command[6] + } else if (command.Length() = 7) { + Click, command[2], command[3], command[4], command[5], command[6], command[7] + } + return + +} + +MouseClickDrag(ByRef command) { + button := command[2] + if (command.Length() = 6) { + MouseClickDrag,%button%,command[3],command[4],command[5],command[6] + } else if (command.Length() = 7) { + MouseClickDrag,%button%,command[3],command[4],command[5],command[6],command[7] + } else if (command.Length() = 8) { + MouseClickDrag,%button%,command[3],command[4],command[5],command[6],command[7],R + } +} + +RegRead(ByRef command) { + keyname := command[3] + RegRead, output, %keyname%, command[4] + return output +} + +SetRegView(ByRef command) { + view := command[2] + SetRegView, %view% +} + +RegWrite(ByRef command) { + valuetype := command[2] + keyname := command[3] + + RegWrite, %valuetype%, %keyname%, command[4] +} + +RegDelete(ByRef command) { + keyname := command[2] + RegDelete, %keyname%, command[3] +} + +KeyWait(ByRef command) { + keyname := command[2] + if (command.Length() = 2) { + KeyWait,% keyname + } else { + options := command[3] + KeyWait,% keyname,% options + } + return ErrorLevel +} + +SetKeyDelay(ByRef command) { + SetKeyDelay, command[2], command[3] +} + +Join(sep, params*) { + for index,param in params + str .= param . sep + return SubStr(str, 1, -StrLen(sep)) +} + +Unescape(HayStack) { + ReplacedStr := StrReplace(Haystack, "``n" , "`n") + return ReplacedStr +} + +Send(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + Send,% str +} + +SendRaw(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendRaw,% str +} + +SendInput(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendInput,% str +} + + +SendEvent(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendEvent,% str +} + +SendPlay(ByRef command) { + command.RemoveAt(1) + s := Join(",", command*) + str := Unescape(s) + SendPlay,% str +} + +SetCapsLockState(ByRef command) { + if (command.Length() = 1) { + SetCapsLockState % !GetKeyState("CapsLock", "T") + } else { + state := command[2] + SetCapsLockState, %state% + } +} + +HideTrayTip(ByRef command) { + 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 + } +} + +WinGetTitle(ByRef command) { + title := command[3] + WinGetTitle, text, %title% + return text +} +WinGetClass(ByRef command) { + title := command[3] + WinGetClass, text, %title% + return text +} +WinGetText(ByRef command) { + title := command[3] + WinGetText, text, %title% + return text +} + +WinActivate(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinActivate, %title% + } else { + secondstowait := command[3] + WinActivate, %title%, %secondstowait% + } +} + +WinActivateBottom(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinActivateBottom, %title% + } else { + secondstowait := command[3] + WinActivateBottom, %title%, %secondstowait% + } +} + +WinClose(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinClose,% title + } else { + secondstowait := command[3] + WinClose, %title%, %secondstowait% + } +} + +WinHide(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinHide, %title% + } else { + secondstowait := command[3] + WinHide, %title%, %secondstowait% + } +} + +WinKill(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinKill, %title% + } else { + secondstowait := command[3] + WinKill, %title%, %secondstowait% + } +} + +WinMaximize(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinMaximize, %title% + } else { + secondstowait := command[3] + WinMaximize, %title%, %secondstowait% + } +} + +WinMinimize(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinMinimize, %title% + } else { + secondstowait := command[3] + WinMinimize, %title%, %secondstowait% + } +} + +WinRestore(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinRestore, %title% + } else { + secondstowait := command[3] + WinRestore, %title%, %secondstowait% + } +} + +WinShow(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinShow, %title% + } else { + secondstowait := command[3] + WinShow, %title%, %secondstowait% + } +} + +WinWait(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinWait, %title% + } else { + secondstowait = command[3] + WinWait, %title%, %secondstowait% + } +} + +WinWaitActive(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinWaitActive, %title% + } else { + secondstowait := command[3] + WinWaitActive, %title%, %secondstowait% + } +} + +WinWaitNotActive(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinWaitNotActive, %title% + } else { + secondstowait = command[3] + WinWaitNotActive, %title%, %secondstowait% + } +} + +WinWaitClose(ByRef command) { + title := command[2] + if (command.Length() = 2) { + WinWaitClose, %title% + } else { + secondstowait := command[3] + WinWaitClose, %title%, %secondstowait% + } +} + + +WindowList(ByRef command) { + WinGet windows, List + Loop %windows% + { + id := windows%A_Index% + r .= id . "`," + } + return r +} + +WinSend(ByRef command) { + title := command[2] + command.RemoveAt(1) + command.RemoveAt(1) + str := Join(",", command*) + keys := Unescape(str) + ControlSend,,% keys, %title% +} + +WinSendRaw(ByRef command) { + title := command[2] + command.RemoveAt(1) + command.RemoveAt(1) + str := Join(",", command*) + keys := Unescape(str) + ControlSendRaw,,% keys, %title% +} + +ControlSend(ByRef command) { + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + command.RemoveAt(1) + str := Join(",", command*) + keys := Unescape(str) + ControlSend, %ctrl%,% keys, %title%, %text%, %extitle%, %extext% +} + + +BaseCheck(ByRef command) { + kommand := command[2] + title := command[3] + if %kommand%(title) { + return 1 + } + else { + return 0 + } +} + +FromMouse(ByRef command) { + MouseGetPos,,, MouseWin + return MouseWin +} + +WinGet(ByRef command) { + title := command[4] + text := command[5] + extitle := command[6] + extext := command[7] + WinGet, output,% command[3], %title%, %text%, %extitle%, %extext% + return output +} + +WinSet(ByRef command) { + subcommand := command[2] + title := command[4] + value := command[3] + + WinSet,%subcommand%,%value%,%title% +} + +WinSetTitle(ByRef command) { + newtitle := command[4] + WinSetTitle,% command[2],, %newtitle% +} + +WinIsAlwaysOnTop(ByRef command) { + title := command[2] + WinGet, ExStyle, ExStyle, %title% + if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. + return 1 + else + return 0 +} + +WinClick(ByRef command) { + x := command[2] + y := command[3] + hwnd := command[4] + button := command[5] + n := command[6] + if (command.Length() = 6) { + ControlClick,x%x% y%y%,%hwnd%,,%button%,%n% + } else { + options := command[6] + ControlClick, x%x% y%y%, %hwnd%,,%button%, %n%, options + } +} + +AHKWinMove(ByRef command) { + title := command [2] + x := command[3] + y := command[4] + if (command.Length()) = 4 { + WinMove,%title%,,%x%,%y% + } else if (command.Length() = 5) { + a := command[5] + WinMove,%title%,,%x%,%y%,%a% + } else if (command.Length() = 6) { + a := command[5] + b := command[6] + WinMove,%title%,,%x%,%y%,%a%,%b% + } +} + +AHKWinGetPos(ByRef command) { + title := command[2] + WinGetPos, x, y, width, height, %title% + if (command.Length() = 3) { + pos_info := command[3] + if (pos_info = "position") { + s .= Format("({}, {})", x, y) + } else if (pos_info = "height") { + s .= Format("({})", height) + } else if (pos_info = "width") { + s .= Format("({})", width) + } + } else { + s .= Format("({}, {}, {}, {})", x, y, width, height) + } + return s +} + +CountNewlines(ByRef s) { + newline := "`n" + StringReplace, s, s, %newline%, %newline%, UseErrorLevel + count := ErrorLevel + return count +} + + +stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] + +Loop { + query := RTrim(stdin.ReadLine(), "`n") + commandArray := StrSplit(query, ",") + func := commandArray[1] + response := %func%(commandArray) + FileAppend, %response%, * +} diff --git a/ahk/executor.ahk b/ahk/executor.ahk new file mode 100644 index 00000000..e69de29b diff --git a/ahk/message.py b/ahk/message.py new file mode 100644 index 00000000..45b0f390 --- /dev/null +++ b/ahk/message.py @@ -0,0 +1,184 @@ +import ast +import io +import itertools +import string +import typing +from abc import ABC +from abc import abstractmethod +from typing import Any +from typing import cast +from typing import Generator +from typing import Generic +from typing import Literal +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 TypedDict +from typing import TypeVar +from typing import Union + + +class OutOfMessageTypes(Exception): + ... + + +@runtime_checkable +class BytesLineReadable(Protocol): + def readline(self) -> bytes: + ... + + +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: Optional[str] = None + _type_order_mark = next(TOMS) + + @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 + assert cls.type is not None, f'must assign a type for class {cls!r}' + super().__init_subclass__(**kwargs) + + def __init__(self, raw_content: bytes): + self._raw_content: bytes + self._raw_content = raw_content + + def __repr__(self) -> str: + return f'ResponseMessage' + + @staticmethod + def _tom_lookup(tom: bytes) -> Type['ResponseMessage']: + klass = _message_registry.get(tom) + if not klass: + raise ValueError(f'No such TOM {tom!r}') + return klass + + @classmethod + def from_bytes(cls: Type[T_ResponseMessageType], b: bytes) -> T_ResponseMessageType: + print('b', b) + tom, _, message_bytes = b.split(b'\n', 2) + print('mb', message_bytes) + klass = cls._tom_lookup(tom) + return klass(raw_content=message_bytes) # type: ignore[return-value] + + @classmethod + def from_stream(cls: Type[T_ResponseMessageType], stream: BytesLineReadable) -> T_ResponseMessageType: + content_buffer = io.BytesIO() + tom = stream.readline().strip() + content_lines = stream.readline().strip() + for _ in range(int(content_lines) + 1): + part = stream.readline() + content_buffer.write(part) + contents = content_buffer.getvalue() + message_bytes = tom + b'\n' + content_lines + b'\n' + contents + return cls.from_bytes(message_bytes) + + 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, Type[ResponseMessage]] +_message_registry = {ResponseMessage._type_order_mark: ResponseMessage} + + +class TupleResponseMessage(ResponseMessage): + type = 'tuple' + + def unpack(self) -> Tuple[Any, ...]: + s = self._raw_content.decode(encoding='utf-8') # TODO: validate encoding + val = ast.literal_eval(s) + assert isinstance(val, tuple) + return val + + +class CoordinateResponseMessage(ResponseMessage): + type = 'coordinate' + + def unpack(self) -> Tuple[int, int]: + s = self._raw_content.decode(encoding='utf-8') # TODO: validate encoding + print('s', repr(s)) + val = ast.literal_eval(s) + assert isinstance(val, tuple) + x, y = cast(Tuple[int, int], val) + return x, y + + +class IntegerResponseMessage(ResponseMessage): + type = 'integer' + + def unpack(self) -> int: + s = self._raw_content.decode(encoding='utf-8') # TODO: validate encoding + val = ast.literal_eval(s) + assert isinstance(val, int) + return val + + +class BooleanResponseMessage(IntegerResponseMessage): + type = 'boolean' + + def unpack(self) -> bool: + val = super().unpack() + assert val in (1, 0) + return bool(val) + + +class StringResponseMessage(ResponseMessage): + type = 'string' + + def unpack(self) -> str: + return self._raw_content.decode('utf-8') # TODO: validate encoding + + +class WindowIDListResponseMessage(ResponseMessage): + type = 'windowidlist' + + def unpack(self) -> list[str]: + s = self._raw_content.decode(encoding='utf-8') # TODO: validate encoding + return s.split(',') + + +class NoValueResponseMessage(ResponseMessage): + type = 'novalue' + + def unpack(self) -> None: + assert self._raw_content == b'\xee\x80\x80\xee\x80\x80' + return None + + +class ExceptionResponseMessage(ResponseMessage): + type = 'exception' + + def unpack(self) -> NoReturn: + ... + + +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 [] diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..0c7d2f2b --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +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=['ahk', 'ahk._async'], + cmdclass={ + 'build_py': unasync.cmdclass_build_py( + rules=[ + unasync.Rule( + fromdir='/ahk/_async/', + todir='/ahk/_sync/', + additional_replacements={ + 'AsyncAHK': 'AHK', + 'AsyncTransport': 'Transport', + 'AsyncWindow': 'Window', + 'AsyncDaemonProcessTransport': 'DaemonProcessTransport', + '_AIOP': '_SIOP', + 'async_create_process': 'sync_create_process', + 'adrain_stdin': 'drain_stdin', + # "__aenter__": "__aenter__", + }, + ), + # unasync.Rule( + # fromdir="/ahip/tests/", + # todir="/hip/tests/", + # additional_replacements={"ahip": "hip"}, + # ), + ] + ) + }, + # package_dir={"": "src"}, +) diff --git a/tests/message_test.py b/tests/message_test.py new file mode 100644 index 00000000..e69de29b From 58048b42db67a1d59f5614c940431fb7e4483e94 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 12 Jul 2022 21:36:53 -0700 Subject: [PATCH 190/588] minor changes --- .github/workflows/test.yaml | 26 ++++++ .gitignore | 160 ++++++++++++++++++++++++++++++++++++ ahk/__init__.py | 6 ++ ahk/_async/__init__.py | 3 + ahk/_async/engine.py | 3 +- ahk/_async/transport.py | 23 +----- ahk/_sync/__init__.py | 2 + ahk/_sync/engine.py | 3 +- ahk/_sync/transport.py | 23 +----- tests/__init__.py | 0 tests/message_test.py | 25 ++++++ 11 files changed, 232 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/test.yaml create mode 100644 .gitignore create mode 100644 tests/__init__.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..19364ab7 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,26 @@ +on: [ push, pull_request ] + +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.10 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install pytest + - name: Test with coverage/pytest + run: | + coverage run -m pytest tests + - name: Coveralls + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + pip install --upgrade coveralls + coveralls --service=github 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/ahk/__init__.py b/ahk/__init__.py index e69de29b..301b27fc 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -0,0 +1,6 @@ +from ._async import AsyncAHK +from ._async import AsyncWindow +from ._sync import AHK +from ._sync import Window + +__all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK'] diff --git a/ahk/_async/__init__.py b/ahk/_async/__init__.py index cdc15cde..8d39056e 100644 --- a/ahk/_async/__init__.py +++ b/ahk/_async/__init__.py @@ -1 +1,4 @@ from .engine import AsyncAHK +from .window import AsyncWindow + +__all__ = ['AsyncAHK', 'AsyncWindow'] diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index bd22bf0b..2e857ac5 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1,4 +1,3 @@ -from collections import deque from typing import Optional from typing import Tuple @@ -8,7 +7,7 @@ class AsyncAHK: - def __init__(self, transport: Optional[AsyncTransport] = None): + def __init__(self, *, transport: Optional[AsyncTransport] = None): if transport is None: transport = AsyncDaemonProcessTransport() self._transport: AsyncTransport = transport diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 66214926..ef76e47d 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -115,6 +115,8 @@ def _resolve_executable_path(executable_path: str = '') -> str: class AsyncTransport(ABC): + _started: bool = False + async def init(self) -> None: return None @@ -406,6 +408,8 @@ async def function_call( ... async def function_call(self, function_name: str, args: Optional[list[str]] = None) -> ResponseMessage: + if not self._started: + await self.init() request = RequestMessage(function_name=function_name, args=args) return await self.send(request) @@ -414,22 +418,6 @@ async def send(self, request: RequestMessage) -> ResponseMessage: return NotImplemented -# class Process: -# def __init__(self, runargs: list[str]): -# self.runargs = runargs -# -# # def __enter__(self) -> Generator[subprocess.Popen[bytes], None, None]: -# # yield subprocess.Popen(self.runargs, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) -# -# async def __aenter__(self) -> asyncio.subprocess.Process: -# return await async_create_process(self.runargs) -# -# -# async def __aexit__(self, *args: Any, **kwargs: Any) -> None: -# return None -# - - class AsyncDaemonProcessTransport(AsyncTransport): def __init__(self, executable_path: str = ''): self._proc: Optional[AsyncAHKProcess] @@ -443,8 +431,6 @@ async def init(self) -> None: async def start(self) -> None: assert self._proc is None, 'cannot start a process twice' runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', 'ahk\\daemon.ahk'] # TODO: build this dynamically - # async with Process(runargs) as proc: - # self._proc = proc self._proc = AsyncAHKProcess(runargs=runargs) await self._proc.start() @@ -452,7 +438,6 @@ async def send(self, request: RequestMessage) -> ResponseMessage: newline = '\n' msg = f"{request.function_name}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode('utf-8') - print('msg', repr(msg)) assert self._proc is not None self._proc.write(msg) await self._proc.adrain_stdin() diff --git a/ahk/_sync/__init__.py b/ahk/_sync/__init__.py index da7e2c14..ccc5c780 100644 --- a/ahk/_sync/__init__.py +++ b/ahk/_sync/__init__.py @@ -1 +1,3 @@ from .engine import AHK +from .window import Window +__all__ =['AHK', 'Window'] diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 0ca909dc..b9ed6982 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1,4 +1,3 @@ -from collections import deque from typing import Optional from typing import Tuple @@ -8,7 +7,7 @@ class AHK: - def __init__(self, transport: Optional[Transport] = None): + def __init__(self, *, transport: Optional[Transport] = None): if transport is None: transport = DaemonProcessTransport() self._transport: Transport = transport diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index d5abea48..650cf06c 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -105,6 +105,8 @@ def _resolve_executable_path(executable_path: str = '') -> str: class Transport(ABC): + _started: bool = False + def init(self) -> None: return None @@ -396,6 +398,8 @@ def function_call( ... def function_call(self, function_name: str, args: Optional[list[str]] = None) -> ResponseMessage: + if not self._started: + self.init() request = RequestMessage(function_name=function_name, args=args) return self.send(request) @@ -404,22 +408,6 @@ def send(self, request: RequestMessage) -> ResponseMessage: return NotImplemented -# class Process: -# def __init__(self, runargs: list[str]): -# self.runargs = runargs -# -# # def __enter__(self) -> Generator[subprocess.Popen[bytes], None, None]: -# # yield subprocess.Popen(self.runargs, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) -# -# async def __aenter__(self) -> asyncio.subprocess.Process: -# return await async_create_process(self.runargs) -# -# -# async def __aexit__(self, *args: Any, **kwargs: Any) -> None: -# return None -# - - class DaemonProcessTransport(Transport): def __init__(self, executable_path: str = ''): self._proc: Optional[SyncAHKProcess] @@ -433,8 +421,6 @@ def init(self) -> None: def start(self) -> None: assert self._proc is None, 'cannot start a process twice' runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', 'ahk\\daemon.ahk'] # TODO: build this dynamically - # async with Process(runargs) as proc: - # self._proc = proc self._proc = SyncAHKProcess(runargs=runargs) self._proc.start() @@ -442,7 +428,6 @@ def send(self, request: RequestMessage) -> ResponseMessage: newline = '\n' msg = f"{request.function_name}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode('utf-8') - print('msg', repr(msg)) assert self._proc is not None self._proc.write(msg) self._proc.drain_stdin() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/message_test.py b/tests/message_test.py index e69de29b..9f080a54 100644 --- a/tests/message_test.py +++ 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 WindowIDListResponseMessage + + +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\xee\x80\x80') + assert msg.unpack() is None + return None From 8857095d3b8bfcb7933519c84db8f8fd29e71e65 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 12 Jul 2022 21:38:19 -0700 Subject: [PATCH 191/588] yamlin --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 19364ab7..632ec18d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,7 +9,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: "3.10" - name: Install dependencies run: | From dbff175e9f6e396af508b606e3893e7483ff70d4 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 12 Jul 2022 21:39:24 -0700 Subject: [PATCH 192/588] yamlin --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 632ec18d..7037dda6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,10 +14,10 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install pytest + python -m pip install pytest coverage - name: Test with coverage/pytest run: | - coverage run -m pytest tests + coverage run -m pytest - name: Coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 8e1f67643d8c70324ae9810fb6b29fc762177894 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 12 Jul 2022 21:41:32 -0700 Subject: [PATCH 193/588] use windows --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7037dda6..2b0e5c1a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,7 +2,7 @@ on: [ push, pull_request ] jobs: build: - runs-on: ubuntu-latest + runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@v2 From 7e44d4ac27323f3ffbeae847898b32c1cfe0464c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 12 Jul 2022 23:22:09 -0700 Subject: [PATCH 194/588] specify UTF8 for all outputs --- ahk/templates/_daemon.ahk | 4 ++-- ahk/templates/asynchotkey.ahk | 2 +- ahk/templates/daemon.ahk | 4 ++-- ahk/templates/keyboard/key_state.ahk | 4 ++-- ahk/templates/keyboard/key_wait.ahk | 2 +- ahk/templates/mouse/mouse_position.ahk | 2 +- ahk/templates/registery/reg_read.ahk | 2 +- ahk/templates/screen/image_search.ahk | 2 +- ahk/templates/screen/pixel_get_color.ahk | 2 +- ahk/templates/screen/pixel_search.ahk | 2 +- ahk/templates/sound/get_volume.ahk | 2 +- ahk/templates/sound/sound_get.ahk | 2 +- ahk/templates/sound/sound_set.ahk | 2 +- ahk/templates/window/base_check.ahk | 4 ++-- ahk/templates/window/base_get_command.ahk | 2 +- ahk/templates/window/from_mouse.ahk | 2 +- ahk/templates/window/get.ahk | 2 +- ahk/templates/window/id_list.ahk | 2 +- ahk/templates/window/title_list.ahk | 2 +- ahk/templates/window/win_is_always_on_top.ahk | 4 ++-- ahk/templates/window/win_position.ahk | 2 +- ahk/templates/window/win_wait.ahk | 2 +- docs/README.md | 2 +- 23 files changed, 28 insertions(+), 28 deletions(-) diff --git a/ahk/templates/_daemon.ahk b/ahk/templates/_daemon.ahk index c08731ab..38008c7c 100644 --- a/ahk/templates/_daemon.ahk +++ b/ahk/templates/_daemon.ahk @@ -533,6 +533,6 @@ Loop { func := commandArray[1] response := %func%(commandArray) newline_count := CountNewlines(response) - FileAppend, %newline_count%`n, * - FileAppend, %response%`n, * + FileAppend, %newline_count%`n, *, UTF-8 + FileAppend, %response%`n, *, UTF-8 } diff --git a/ahk/templates/asynchotkey.ahk b/ahk/templates/asynchotkey.ahk index c066400b..6f709795 100644 --- a/ahk/templates/asynchotkey.ahk +++ b/ahk/templates/asynchotkey.ahk @@ -1,6 +1,6 @@ {% extends "base.ahk" %} {% block body %} {{ hotkey }}:: - FileAppend, `n, * + FileAppend, `n, *, UTF-8 return {% endblock body %} diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 0b1c0b44..194f2bd8 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -541,6 +541,6 @@ Loop { func := commandArray[1] response := %func%(commandArray) newline_count := CountNewlines(response) - FileAppend, %newline_count%`n, * - FileAppend, %response%`n, * + FileAppend, %newline_count%`n, *, UTF-8 + FileAppend, %response%`n, *, UTF-8 } diff --git a/ahk/templates/keyboard/key_state.ahk b/ahk/templates/keyboard/key_state.ahk index ef6bcadb..229e3931 100644 --- a/ahk/templates/keyboard/key_state.ahk +++ b/ahk/templates/keyboard/key_state.ahk @@ -1,8 +1,8 @@ {% extends "base.ahk" %} {% block body %} if (GetKeyState("{{ key_name }}"{% if mode %} , "{{ mode }}"{% endif %})) { - FileAppend, 1, * + FileAppend, 1, *, UTF-8 } else { - FileAppend, 0, * + FileAppend, 0, *, UTF-8 } {% endblock body %} diff --git a/ahk/templates/keyboard/key_wait.ahk b/ahk/templates/keyboard/key_wait.ahk index 9f2db0ea..4b667e7f 100644 --- a/ahk/templates/keyboard/key_wait.ahk +++ b/ahk/templates/keyboard/key_wait.ahk @@ -2,5 +2,5 @@ {% block body %} KeyWait, {{ key_name }}{% if options %} , {{ options }}{% endif %} -FileAppend, %ErrorLevel%, * +FileAppend, %ErrorLevel%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/mouse/mouse_position.ahk b/ahk/templates/mouse/mouse_position.ahk index c0dfd832..1977f0f7 100644 --- a/ahk/templates/mouse/mouse_position.ahk +++ b/ahk/templates/mouse/mouse_position.ahk @@ -3,5 +3,5 @@ CoordMode,Mouse,{{mode}} MouseGetPos, xpos, ypos s .= Format("({}, {})", xpos, ypos) -FileAppend, %s%, * +FileAppend, %s%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/registery/reg_read.ahk b/ahk/templates/registery/reg_read.ahk index f26afa0c..d928081c 100644 --- a/ahk/templates/registery/reg_read.ahk +++ b/ahk/templates/registery/reg_read.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} RegRead,output,{{ key_name }},{{ value_name }} -FileAppend, %output%, * +FileAppend, %output%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/screen/image_search.ahk b/ahk/templates/screen/image_search.ahk index e2bee7cf..72983292 100644 --- a/ahk/templates/screen/image_search.ahk +++ b/ahk/templates/screen/image_search.ahk @@ -3,5 +3,5 @@ CoordMode, Pixel, {{ coord_mode }} ImageSearch,xpos,ypos,{{ x1 }},{{ y1 }},{{ x2 }},{{ y2 }},{% if options %}{% for option in options %}*{{ option }} {% endfor %}{% endif %}{{ image_path }} s .= Format("({}, {})", xpos, ypos) -FileAppend, %s%, * +FileAppend, %s%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/screen/pixel_get_color.ahk b/ahk/templates/screen/pixel_get_color.ahk index 5f509852..7d79943f 100644 --- a/ahk/templates/screen/pixel_get_color.ahk +++ b/ahk/templates/screen/pixel_get_color.ahk @@ -3,5 +3,5 @@ CoordMode, Pixel, {{ coord_mode }} PixelGetColor,color, {{ x }}, {{ y }}{% if options %},{% for option in options %} {{ option }}{% endfor %}{% endif %} -FileAppend, %color%, * +FileAppend, %color%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/screen/pixel_search.ahk b/ahk/templates/screen/pixel_search.ahk index 8a096c9e..1ed12d4b 100644 --- a/ahk/templates/screen/pixel_search.ahk +++ b/ahk/templates/screen/pixel_search.ahk @@ -4,5 +4,5 @@ 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%, * +FileAppend, %s%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/sound/get_volume.ahk b/ahk/templates/sound/get_volume.ahk index 420990a1..8a1735a3 100644 --- a/ahk/templates/sound/get_volume.ahk +++ b/ahk/templates/sound/get_volume.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} SoundGetWaveVolume, retval, {{ device_number }} -FileAppend, %retval%, * +FileAppend, %retval%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/sound/sound_get.ahk b/ahk/templates/sound/sound_get.ahk index 442e8233..f974e231 100644 --- a/ahk/templates/sound/sound_get.ahk +++ b/ahk/templates/sound/sound_get.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} SoundGet, retval , {{ component_type }}, {{ control_type }}, {{ device_number }} -FileAppend, %retval%, * +FileAppend, %retval%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/sound/sound_set.ahk b/ahk/templates/sound/sound_set.ahk index f213ea6a..657292a7 100644 --- a/ahk/templates/sound/sound_set.ahk +++ b/ahk/templates/sound/sound_set.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} SoundSet, {{ value }}, {{ component_type }}, {{ control_type }}, {{ device_number }} -FileAppend, %retval%, * +FileAppend, %retval%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/window/base_check.ahk b/ahk/templates/window/base_check.ahk index 671d03e8..21383c3c 100644 --- a/ahk/templates/window/base_check.ahk +++ b/ahk/templates/window/base_check.ahk @@ -1,7 +1,7 @@ {% extends "base.ahk" %} {% block body %} if {{ command }}("{{ title }}") - FileAppend, 1, * + FileAppend, 1, *, UTF-8 else - FileAppend, 0, * + FileAppend, 0, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/window/base_get_command.ahk b/ahk/templates/window/base_get_command.ahk index fd4867bc..c1e731ea 100644 --- a/ahk/templates/window/base_get_command.ahk +++ b/ahk/templates/window/base_get_command.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} {{ command }},text,{{ title }} -FileAppend, %text%, * +FileAppend, %text%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/window/from_mouse.ahk b/ahk/templates/window/from_mouse.ahk index f05e85a5..07466bee 100644 --- a/ahk/templates/window/from_mouse.ahk +++ b/ahk/templates/window/from_mouse.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} MouseGetPos,,, MouseWin -FileAppend, %MouseWin%, * +FileAppend, %MouseWin%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/window/get.ahk b/ahk/templates/window/get.ahk index 01189496..ee0ae7b0 100644 --- a/ahk/templates/window/get.ahk +++ b/ahk/templates/window/get.ahk @@ -1,5 +1,5 @@ {% extends "base.ahk" %} {% block body %} WinGet, output,{{ subcommand }},{{ title }},{{ text }},{{ exclude_title }},{{ exclude_text }} -FileAppend, %output%, * +FileAppend, %output%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/window/id_list.ahk b/ahk/templates/window/id_list.ahk index 94c9162a..a08c4dec 100644 --- a/ahk/templates/window/id_list.ahk +++ b/ahk/templates/window/id_list.ahk @@ -6,5 +6,5 @@ Loop %windows% id := windows%A_Index% r .= id . "`," } -FileAppend, %r%, * +FileAppend, %r%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/window/title_list.ahk b/ahk/templates/window/title_list.ahk index 7bb1c460..d9922551 100644 --- a/ahk/templates/window/title_list.ahk +++ b/ahk/templates/window/title_list.ahk @@ -7,5 +7,5 @@ Loop %windows% WinGetTitle wt, ahk_id %id% r .= wt . "`n" } -FileAppend, %r%, * +FileAppend, %r%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/window/win_is_always_on_top.ahk b/ahk/templates/window/win_is_always_on_top.ahk index 97799dbc..cb95c7fd 100644 --- a/ahk/templates/window/win_is_always_on_top.ahk +++ b/ahk/templates/window/win_is_always_on_top.ahk @@ -2,7 +2,7 @@ {% block body %} WinGet, ExStyle, ExStyle, {{ title }} if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. - FileAppend, 1, * + FileAppend, 1, *, UTF-8 else - FileAppend, 0, * + FileAppend, 0, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/window/win_position.ahk b/ahk/templates/window/win_position.ahk index a011fbc2..9c17b269 100644 --- a/ahk/templates/window/win_position.ahk +++ b/ahk/templates/window/win_position.ahk @@ -12,5 +12,5 @@ s .= Format("({})", width) {% else %} s .= Format("({}, {}, {}, {})", x, y, width, height) {% endif %} -FileAppend, %s%, * +FileAppend, %s%, *, UTF-8 {% endblock body %} diff --git a/ahk/templates/window/win_wait.ahk b/ahk/templates/window/win_wait.ahk index 97112e73..868b700e 100644 --- a/ahk/templates/window/win_wait.ahk +++ b/ahk/templates/window/win_wait.ahk @@ -4,6 +4,6 @@ WinWait,{{title}},{{text}},{{timeout}},{{exclude_title}},{{exclude_text}} if !ErrorLevel { WinGet, output, ID - FileAppend,%output%,* + FileAppend,%output%,*, UTF-8 } {% endblock body %} diff --git a/docs/README.md b/docs/README.md index 4fd6d8d2..47f6d388 100644 --- a/docs/README.md +++ b/docs/README.md @@ -262,7 +262,7 @@ Suppose you have a script like so ```autohotkey #Persistent data := "Hello Data!" -FileAppend, %data%, * ; send data var to stdout +FileAppend, %data%, *, UTF-8 ; send data var to stdout ExitApp ``` From d84f9c9847b6e783ea3d78ee91c8ae793d953a04 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 12 Jul 2022 23:43:02 -0700 Subject: [PATCH 195/588] bump version 0.14.1 :package: --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e98654b5..85b56a95 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='ahk', - version='0.14.0', + version='0.14.1', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From 3702a70cbdafd53c20779e9b67d9f1433fc55fb0 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 13 Jul 2022 00:58:19 -0700 Subject: [PATCH 196/588] Mousemove (#2) * mouse move * install ahk binary * atexit --- .github/workflows/test.yaml | 2 +- ahk/_async/engine.py | 73 +++++++++++++++++++++++++++++++++++++ ahk/_async/transport.py | 23 +++++++++++- ahk/_sync/engine.py | 73 +++++++++++++++++++++++++++++++++++++ ahk/_sync/transport.py | 23 +++++++++++- ahk/daemon.ahk | 11 ++++-- ahk/message.py | 5 +-- tests/message_test.py | 2 +- tests/test_mouse.py | 7 ++++ 9 files changed, 205 insertions(+), 14 deletions(-) create mode 100644 tests/test_mouse.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2b0e5c1a..eb64da60 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,7 +14,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install pytest coverage + python -m pip install pytest coverage ahk-binary - name: Test with coverage/pytest run: | coverage run -m pytest diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 2e857ac5..f8b4ed5b 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1,11 +1,18 @@ +import typing +from typing import Literal from typing import Optional from typing import Tuple +from typing import Union from .transport import AsyncDaemonProcessTransport from .transport import AsyncTransport from .window import AsyncWindow +class FutureResult: + ... + + class AsyncAHK: def __init__(self, *, transport: Optional[AsyncTransport] = None): if transport is None: @@ -21,3 +28,69 @@ async def list_windows(self) -> list[AsyncWindow]: async def get_mouse_position(self) -> Tuple[int, int]: resp = await self._transport.function_call('MouseGetPos') return resp.unpack() + + @typing.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, + ) -> None: + ... + + @typing.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, + ) -> None: + ... + + @typing.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, + ) -> FutureResult: + ... + + 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: Optional[Union[Literal[True], Literal[False]]] = None, + ) -> Union[None, FutureResult]: + 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') + if blocking in (True, None): + resp = await self._transport.function_call('MouseMove', args) + resp.unpack() + return None + elif blocking is False: + return FutureResult() + else: + raise ValueError(f'Invalid value for argument blocking: {blocking!r}') diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index ef76e47d..4b84026f 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -1,4 +1,5 @@ import asyncio.subprocess +import atexit import io import os import subprocess @@ -8,8 +9,11 @@ from abc import abstractmethod from io import BytesIO from shutil import which +from typing import Any from typing import Literal from typing import Optional +from typing import Protocol +from typing import runtime_checkable from ahk.message import BooleanResponseMessage from ahk.message import CoordinateResponseMessage @@ -29,6 +33,16 @@ SyncIOProcess = subprocess.Popen[bytes] +@runtime_checkable +class Killable(Protocol): + def kill(self) -> None: + ... + + +def kill(proc: Killable) -> None: + proc.kill() + + class AsyncAHKProcess: def __init__(self, runargs: list[str]): self.runargs = runargs @@ -36,6 +50,7 @@ def __init__(self, runargs: list[str]): async def start(self) -> None: self._proc = await async_create_process(self.runargs) + atexit.register(kill, self._proc) return None async def adrain_stdin(self) -> None: # unasync: remove @@ -118,6 +133,7 @@ class AsyncTransport(ABC): _started: bool = False async def init(self) -> None: + self._started = True return None @typing.overload @@ -426,6 +442,7 @@ def __init__(self, executable_path: str = ''): async def init(self) -> None: await self.start() + await super().init() return None async def start(self) -> None: @@ -437,7 +454,9 @@ async def start(self) -> None: async def send(self, request: RequestMessage) -> ResponseMessage: newline = '\n' - msg = f"{request.function_name}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode('utf-8') + msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( + 'utf-8' + ) assert self._proc is not None self._proc.write(msg) await self._proc.adrain_stdin() @@ -449,6 +468,6 @@ async def send(self, request: RequestMessage) -> ResponseMessage: for _ in range(int(num_lines) + 1): part = await self._proc.readline() content_buffer.write(part) - content = content_buffer.getvalue() + content = content_buffer.getvalue()[:-1] response = ResponseMessage.from_bytes(content) return response diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index b9ed6982..90b55aae 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1,11 +1,18 @@ +import typing +from typing import Literal from typing import Optional from typing import Tuple +from typing import Union from .transport import DaemonProcessTransport from .transport import Transport from .window import Window +class FutureResult: + ... + + class AHK: def __init__(self, *, transport: Optional[Transport] = None): if transport is None: @@ -21,3 +28,69 @@ def list_windows(self) -> list[Window]: def get_mouse_position(self) -> Tuple[int, int]: resp = self._transport.function_call('MouseGetPos') return resp.unpack() + + @typing.overload + def mouse_move( + self, + x: Optional[Union[str, int]] = None, + y: Optional[Union[str, int]] = None, + *, + speed: Optional[int] = None, + relative: bool = False, + ) -> None: + ... + + @typing.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, + ) -> None: + ... + + @typing.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, + ) -> FutureResult: + ... + + def mouse_move( + self, + x: Optional[Union[str, int]] = None, + y: Optional[Union[str, int]] = None, + *, + speed: Optional[int] = None, + relative: bool = False, + blocking: Optional[Union[Literal[True], Literal[False]]] = None, + ) -> Union[None, FutureResult]: + 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') + if blocking in (True, None): + resp = self._transport.function_call('MouseMove', args) + resp.unpack() + return None + elif blocking is False: + return FutureResult() + else: + raise ValueError(f'Invalid value for argument blocking: {blocking!r}') diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 650cf06c..f459f7bc 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -1,4 +1,5 @@ import asyncio.subprocess +import atexit import io import os import subprocess @@ -8,8 +9,11 @@ from abc import abstractmethod from io import BytesIO from shutil import which +from typing import Any from typing import Literal from typing import Optional +from typing import Protocol +from typing import runtime_checkable from ahk.message import BooleanResponseMessage from ahk.message import CoordinateResponseMessage @@ -28,6 +32,16 @@ SyncIOProcess = subprocess.Popen[bytes] +@runtime_checkable +class Killable(Protocol): + def kill(self) -> None: + ... + + +def kill(proc: Killable) -> None: + proc.kill() + + class SyncAHKProcess: def __init__(self, runargs: list[str]): self.runargs = runargs @@ -35,6 +49,7 @@ def __init__(self, runargs: list[str]): def start(self) -> None: self._proc = sync_create_process(self.runargs) + atexit.register(kill, self._proc) return None @@ -108,6 +123,7 @@ class Transport(ABC): _started: bool = False def init(self) -> None: + self._started = True return None @typing.overload @@ -416,6 +432,7 @@ def __init__(self, executable_path: str = ''): def init(self) -> None: self.start() + super().init() return None def start(self) -> None: @@ -427,7 +444,9 @@ def start(self) -> None: def send(self, request: RequestMessage) -> ResponseMessage: newline = '\n' - msg = f"{request.function_name}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode('utf-8') + msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( + 'utf-8' + ) assert self._proc is not None self._proc.write(msg) self._proc.drain_stdin() @@ -439,6 +458,6 @@ def send(self, request: RequestMessage) -> ResponseMessage: for _ in range(int(num_lines) + 1): part = self._proc.readline() content_buffer.write(part) - content = content_buffer.getvalue() + content = content_buffer.getvalue()[:-1] response = ResponseMessage.from_bytes(content) return response diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index af1d5e4f..1cc7ab79 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -11,7 +11,7 @@ WINDOWIDLISTRESPONSEMESSAGE := "006" ; WindowIDListResponseMessage NOVALUERESPONSEMESSAGE := "007" ; NoValueResponseMessage EXCEPTIONRESPONSEMESSAGE := "008" ; ExceptionResponseMessage -NOVALUE_SENTINEL := Chr(57344) + Chr(57344) +NOVALUE_SENTINEL := Chr(57344) FormatResponse(MessageType, payload) { newline_count := CountNewlines(payload) @@ -75,7 +75,6 @@ MouseGetPos(ByRef command) { MouseGetPos, xpos, ypos payload .= Format("({}, {})", xpos, ypos) resp .= FormatResponse(COORDINATERESPONSEMESSAGE, payload) - MsgBox,% resp return resp } @@ -96,11 +95,15 @@ AHKKeyState(ByRef command) { } MouseMove(ByRef command) { + global NOVALUERESPONSEMESSAGE + global NOVALUE_SENTINEL if (command.Length() = 5) { MouseMove, command[2], command[3], command[4], R } else { MouseMove, command[2], command[3], command[4] } + resp .= FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) + return resp } CoordMode(ByRef command) { @@ -542,12 +545,12 @@ CountNewlines(ByRef s) { } -stdin := FileOpen("*", "r `n") ; Requires [v1.1.17+] +stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] Loop { query := RTrim(stdin.ReadLine(), "`n") commandArray := StrSplit(query, ",") func := commandArray[1] response := %func%(commandArray) - FileAppend, %response%, * + FileAppend, %response%, *, UTF-8 } diff --git a/ahk/message.py b/ahk/message.py index 45b0f390..4a4fc3d3 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -73,9 +73,7 @@ def _tom_lookup(tom: bytes) -> Type['ResponseMessage']: @classmethod def from_bytes(cls: Type[T_ResponseMessageType], b: bytes) -> T_ResponseMessageType: - print('b', b) tom, _, message_bytes = b.split(b'\n', 2) - print('mb', message_bytes) klass = cls._tom_lookup(tom) return klass(raw_content=message_bytes) # type: ignore[return-value] @@ -119,7 +117,6 @@ class CoordinateResponseMessage(ResponseMessage): def unpack(self) -> Tuple[int, int]: s = self._raw_content.decode(encoding='utf-8') # TODO: validate encoding - print('s', repr(s)) val = ast.literal_eval(s) assert isinstance(val, tuple) x, y = cast(Tuple[int, int], val) @@ -164,7 +161,7 @@ class NoValueResponseMessage(ResponseMessage): type = 'novalue' def unpack(self) -> None: - assert self._raw_content == b'\xee\x80\x80\xee\x80\x80' + assert self._raw_content == b'\xee\x80\x80', f'Unexpected or Malformed response: {self._raw_content!r}' return None diff --git a/tests/message_test.py b/tests/message_test.py index 9f080a54..85c27fb0 100644 --- a/tests/message_test.py +++ b/tests/message_test.py @@ -20,6 +20,6 @@ def test_novalue_response_raises_exception_when_sentinel_not_present() -> None: def test_novalue_response_sentinel() -> None: - msg = NoValueResponseMessage(raw_content=b'\xee\x80\x80\xee\x80\x80') + msg = NoValueResponseMessage(raw_content=b'\xee\x80\x80') assert msg.unpack() is None return None diff --git a/tests/test_mouse.py b/tests/test_mouse.py new file mode 100644 index 00000000..1d9a4cfd --- /dev/null +++ b/tests/test_mouse.py @@ -0,0 +1,7 @@ +from ahk import AHK + + +def test_mouse_move(): + ahk = AHK() + ahk.mouse_move(100, 100) + assert ahk.get_mouse_position() == (100, 100) From c66ba15b52cd2c154b6d61baf1ed81396956b13c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 13 Jul 2022 21:38:26 -0700 Subject: [PATCH 197/588] add exception handling --- ahk/_async/engine.py | 9 +++++---- ahk/_sync/engine.py | 9 +++++---- ahk/daemon.ahk | 18 +++++++++++++++--- ahk/message.py | 17 +++++++++++------ 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index f8b4ed5b..dfdf2d1a 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1,6 +1,7 @@ -import typing +from typing import Any from typing import Literal from typing import Optional +from typing import overload from typing import Tuple from typing import Union @@ -29,7 +30,7 @@ async def get_mouse_position(self) -> Tuple[int, int]: resp = await self._transport.function_call('MouseGetPos') return resp.unpack() - @typing.overload + @overload async def mouse_move( self, x: Optional[Union[str, int]] = None, @@ -40,7 +41,7 @@ async def mouse_move( ) -> None: ... - @typing.overload + @overload async def mouse_move( self, x: Optional[Union[str, int]] = None, @@ -52,7 +53,7 @@ async def mouse_move( ) -> None: ... - @typing.overload + @overload async def mouse_move( self, x: Optional[Union[str, int]] = None, diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 90b55aae..402eb76c 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1,6 +1,7 @@ -import typing +from typing import Any from typing import Literal from typing import Optional +from typing import overload from typing import Tuple from typing import Union @@ -29,7 +30,7 @@ def get_mouse_position(self) -> Tuple[int, int]: resp = self._transport.function_call('MouseGetPos') return resp.unpack() - @typing.overload + @overload def mouse_move( self, x: Optional[Union[str, int]] = None, @@ -40,7 +41,7 @@ def mouse_move( ) -> None: ... - @typing.overload + @overload def mouse_move( self, x: Optional[Union[str, int]] = None, @@ -52,7 +53,7 @@ def mouse_move( ) -> None: ... - @typing.overload + @overload def mouse_move( self, x: Optional[Union[str, int]] = None, diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 1cc7ab79..f45d4775 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -546,11 +546,23 @@ CountNewlines(ByRef s) { stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] +response := "" Loop { query := RTrim(stdin.ReadLine(), "`n") commandArray := StrSplit(query, ",") - func := commandArray[1] - response := %func%(commandArray) - FileAppend, %response%, *, UTF-8 + + try { + func := commandArray[1] + response .= %func%(commandArray) + } catch e { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, %e%) + } + + if (response) { + FileAppend, %response%, *, UTF-8 + } else { + msg := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("Unknown Error when calling {}", func)) + FileAppend, %msg%, *, UTF-8 + } } diff --git a/ahk/message.py b/ahk/message.py index 4a4fc3d3..69eeff82 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -106,7 +106,7 @@ class TupleResponseMessage(ResponseMessage): type = 'tuple' def unpack(self) -> Tuple[Any, ...]: - s = self._raw_content.decode(encoding='utf-8') # TODO: validate encoding + s = self._raw_content.decode(encoding='utf-8') val = ast.literal_eval(s) assert isinstance(val, tuple) return val @@ -116,7 +116,7 @@ class CoordinateResponseMessage(ResponseMessage): type = 'coordinate' def unpack(self) -> Tuple[int, int]: - s = self._raw_content.decode(encoding='utf-8') # TODO: validate encoding + s = self._raw_content.decode(encoding='utf-8') val = ast.literal_eval(s) assert isinstance(val, tuple) x, y = cast(Tuple[int, int], val) @@ -127,7 +127,7 @@ class IntegerResponseMessage(ResponseMessage): type = 'integer' def unpack(self) -> int: - s = self._raw_content.decode(encoding='utf-8') # TODO: validate encoding + s = self._raw_content.decode(encoding='utf-8') val = ast.literal_eval(s) assert isinstance(val, int) return val @@ -146,14 +146,14 @@ class StringResponseMessage(ResponseMessage): type = 'string' def unpack(self) -> str: - return self._raw_content.decode('utf-8') # TODO: validate encoding + return self._raw_content.decode('utf-8') class WindowIDListResponseMessage(ResponseMessage): type = 'windowidlist' def unpack(self) -> list[str]: - s = self._raw_content.decode(encoding='utf-8') # TODO: validate encoding + s = self._raw_content.decode(encoding='utf-8') return s.split(',') @@ -165,11 +165,16 @@ def unpack(self) -> None: return None +class AHKExecutionException(Exception): + pass + + class ExceptionResponseMessage(ResponseMessage): type = 'exception' def unpack(self) -> NoReturn: - ... + s = self._raw_content.decode(encoding='utf-8') + raise AHKExecutionException(s) T_RequestMessageType = TypeVar('T_RequestMessageType', bound='RequestMessage') From 5eebc1340a8298c25e9a4309a329d4efe9d690f1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 14 Jul 2022 16:22:26 -0700 Subject: [PATCH 198/588] fixup ahk code --- ahk/daemon.ahk | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index f45d4775..7a5e4436 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -15,7 +15,7 @@ NOVALUE_SENTINEL := Chr(57344) FormatResponse(MessageType, payload) { newline_count := CountNewlines(payload) - response .= Format("{}`n{}`n{}`n", MessageType, newline_count, payload) + response := Format("{}`n{}`n{}`n", MessageType, newline_count, payload) return response } @@ -32,7 +32,14 @@ ImageSearch(ByRef command) { y2 := A_ScreenHeight } ImageSearch, xpos, ypos,% x1,% y1,% x2,% y2, %imagepath% - s .= Format("({}, {})", xpos, ypos) + if (ErrorLevel = 2) { + s := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "there was a problem that prevented the command from conducting the search (such as failure to open the image file or a badly formatted option)") + } else if (ErrorLevel = 1) { + s := FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) + } else { + s := FormatResponse(COORDINATERESPONSEMESSAGE, Format("({}, {})", xpos, ypos)) + } + return s } @@ -65,7 +72,7 @@ PixelSearch(ByRef command) { options := command[10] PixelSearch, xpos, ypos,% x1,% y1,% x2,% y2, command[8], command[9], %options% } - s .= Format("({}, {})", xpos, ypos) + s := Format("({}, {})", xpos, ypos) return s } @@ -73,8 +80,8 @@ PixelSearch(ByRef command) { MouseGetPos(ByRef command) { global COORDINATERESPONSEMESSAGE MouseGetPos, xpos, ypos - payload .= Format("({}, {})", xpos, ypos) - resp .= FormatResponse(COORDINATERESPONSEMESSAGE, payload) + payload := Format("({}, {})", xpos, ypos) + resp := FormatResponse(COORDINATERESPONSEMESSAGE, payload) return resp } @@ -102,7 +109,7 @@ MouseMove(ByRef command) { } else { MouseMove, command[2], command[3], command[4] } - resp .= FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) + resp := FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) return resp } @@ -186,7 +193,7 @@ SetKeyDelay(ByRef command) { Join(sep, params*) { for index,param in params - str .= param . sep + str := param . sep return SubStr(str, 1, -StrLen(sep)) } @@ -397,13 +404,16 @@ WinWaitClose(ByRef command) { WindowList(ByRef command) { + global WINDOWIDLISTRESPONSEMESSAGE WinGet windows, List + r := "" Loop %windows% { id := windows%A_Index% - r .= id . "`," + r := id . "`," } - return r + resp := FormatResponse(WINDOWIDLISTRESPONSEMESSAGE, r) + return resp } WinSend(ByRef command) { @@ -525,14 +535,14 @@ AHKWinGetPos(ByRef command) { if (command.Length() = 3) { pos_info := command[3] if (pos_info = "position") { - s .= Format("({}, {})", x, y) + s := Format("({}, {})", x, y) } else if (pos_info = "height") { - s .= Format("({})", height) + s := Format("({})", height) } else if (pos_info = "width") { - s .= Format("({})", width) + s := Format("({})", width) } } else { - s .= Format("({}, {}, {}, {})", x, y, width, height) + s := Format("({}, {}, {}, {})", x, y, width, height) } return s } @@ -554,7 +564,7 @@ Loop { try { func := commandArray[1] - response .= %func%(commandArray) + response := %func%(commandArray) } catch e { response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, %e%) } From c0bb809d1dafde0121676b842413d4850527973a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 15 Jul 2022 12:44:54 -0700 Subject: [PATCH 199/588] function calling --- .github/workflows/test.yaml | 1 + .pre-commit-config.yaml | 4 +- .unasync-rewrite.py | 9 +- _tests_setup.py | 37 +++ ahk/_async/engine.py | 406 +++++++++++++++++++++++++++- ahk/_async/transport.py | 518 +++++++++++++++--------------------- ahk/_async/window.py | 21 ++ ahk/_sync/engine.py | 406 +++++++++++++++++++++++++++- ahk/_sync/transport.py | 518 +++++++++++++++--------------------- ahk/_sync/window.py | 20 ++ ahk/daemon.ahk | 194 +++++++++++++- ahk/message.py | 97 +++++-- setup.py | 12 +- tests/_async/__init__.py | 0 tests/_async/test_mouse.py | 1 + tests/_async/test_screen.py | 44 +++ tests/_async/test_window.py | 26 ++ tests/test_mouse.py | 7 - 18 files changed, 1679 insertions(+), 642 deletions(-) create mode 100644 _tests_setup.py create mode 100644 tests/_async/__init__.py create mode 100644 tests/_async/test_mouse.py create mode 100644 tests/_async/test_screen.py create mode 100644 tests/_async/test_window.py delete mode 100644 tests/test_mouse.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index eb64da60..5913611b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -3,6 +3,7 @@ on: [ push, pull_request ] jobs: build: runs-on: windows-latest + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c2a3d4c5..69f70253 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: entry: python .unasync-rewrite.py language: python types: [python] - files: ^(ahk/_async/.*\.py|\.unasync-rewrite\.py) + files: ^(ahk/_async/.*\.py|\.unasync-rewrite\.py|tests/_async/.*\.py) pass_filenames: false additional_dependencies: - unasync @@ -43,4 +43,4 @@ repos: - id: mypy args: - "--strict" - exclude: ^(tests/.*|setup\.py|\.build\.py|\.unasync-rewrite.py) + exclude: ^(tests/.*|setup\.py|\.build\.py|\.unasync-rewrite\.py|_tests_setup\.py) diff --git a/.unasync-rewrite.py b/.unasync-rewrite.py index dee81619..64ef6238 100644 --- a/.unasync-rewrite.py +++ b/.unasync-rewrite.py @@ -65,8 +65,15 @@ def main() -> int: if fname.endswith('.py'): fp = os.path.join(root, fname) _rewrite_file(fp) - + subprocess.run(['python', '_tests_setup.py', 'build_py'], check=True, shell=True) + for root, dirs, files in os.walk('build/lib/tests/_sync'): + for fname in files: + if fname.endswith('.py'): + fp = os.path.join(root, fname) + _rewrite_file(fp) 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 diff --git a/_tests_setup.py b/_tests_setup.py new file mode 100644 index 00000000..3024d4e1 --- /dev/null +++ b/_tests_setup.py @@ -0,0 +1,37 @@ +""" +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', + # "__aenter__": "__aenter__", + }, + ), + ] + ) + }, + # package_dir={"": "src"}, +) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index dfdf2d1a..dd9cc832 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1,12 +1,24 @@ +from __future__ import annotations + from typing import Any +from typing import Callable +from typing import Iterable from typing import Literal from typing import Optional from typing import overload +from typing import Sequence from typing import Tuple +from typing import Type from typing import Union +from ..message import IntegerResponseMessage +from ..message import is_winget_response_type +from ..message import StringResponseMessage +from ..message import WindowControlListResponseMessage +from ..message import WindowIDListResponseMessage from .transport import AsyncDaemonProcessTransport from .transport import AsyncTransport +from .window import AsyncControl from .window import AsyncWindow @@ -14,10 +26,33 @@ class FutureResult: ... +CoordModeTargets = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] +CoordModeRelativeTo = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] +WinGetFunctions = Literal[ + Literal['WinGetID'], + Literal['WinGetIDLast'], + Literal['WinGetPID'], + Literal['WinGetProcessName'], + Literal['WinGetProcessPath'], + Literal['WinGetCount'], + Literal['WinGetList'], + Literal['WinGetMinMax'], + Literal['WinGetControlList'], + Literal['WinGetControlListHwnd'], + Literal['WinGetTransparent'], + Literal['WinGetTransColor'], + Literal['WinGetStyle'], + Literal['WinGetExStyle'], +] +CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] + + class AsyncAHK: - def __init__(self, *, transport: Optional[AsyncTransport] = None): - if transport is None: - transport = AsyncDaemonProcessTransport() + def __init__(self, *, TransportClass: Optional[Type[AsyncTransport]] = None, **transport_kwargs: Any): + if TransportClass is None: + TransportClass = AsyncDaemonProcessTransport + assert TransportClass is not None + transport = TransportClass(**transport_kwargs) self._transport: AsyncTransport = transport async def list_windows(self) -> list[AsyncWindow]: @@ -95,3 +130,368 @@ async def mouse_move( return FutureResult() else: raise ValueError(f'Invalid value for argument blocking: {blocking!r}') + + async def a_run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: + raise NotImplementedError() + + async def get_active_window(self) -> Union[AsyncWindow, None]: + raise NotImplementedError() + + async def find_windows( + self, func: Optional[Callable[[AsyncWindow], bool]] = None, **kwargs: Any + ) -> Iterable[AsyncWindow]: + raise NotImplementedError() + + async def find_windows_by_class(self, class_name: str, exact: bool = False) -> Iterable[AsyncWindow]: + raise NotImplementedError() + + async def find_windows_by_text(self, text: str, exact: bool = False) -> Iterable[AsyncWindow]: + raise NotImplementedError() + + async def find_windows_by_title(self, title: str, exact: bool = False) -> Iterable[AsyncWindow]: + raise NotImplementedError() + + async def get_volume(self, device_number: int = 1) -> float: + raise NotImplementedError() + + async def key_down(self, key: str, blocking: bool = True) -> None: + raise NotImplementedError() + + async def key_press(self, key: str, release: bool = True, blocking: bool = True) -> None: + raise NotImplementedError() + + async def key_release(self, key: str, blocking: bool = True) -> None: + raise NotImplementedError() + + async def key_state(self, key_name: str, mode: Optional[Union[Literal['P'], Literal['T']]] = None) -> bool: + raise NotImplementedError() + + async def key_up(self, key: str, blocking: bool = True) -> None: + raise NotImplementedError() + + async def key_wait( + self, key_name: str, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False + ) -> None: + raise NotImplementedError() + + # async def mouse_position(self): + # raise NotImplementedError() + + async def mouse_wheel( + self, + direction: Union[ + Literal['up'], Literal['down'], Literal['UP'], Literal['DOWN'], Literal['Up'], Literal['Down'] + ], + *args: Any, + **kwargs: Any, + ) -> None: + raise NotImplementedError() + + # async def reg_delete(self, key_name: str, value_name: str = '') -> None: + # raise NotImplementedError() + # + # async def reg_loop(self, reg: str, key_name: str, mode=''): + # raise NotImplementedError() + # + # async def reg_read(self, key_name: str, value_name='') -> str: + # raise NotImplementedError() + # + # async def reg_set_view(self, reg_view: int) -> None: + # raise NotImplementedError() + # + # async def reg_write(self, value_type: str, key_name: str, value_name='') -> None: + # raise NotImplementedError() + + async def run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: + raise NotImplementedError() + + async def send(self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True) -> None: + raise NotImplementedError() + + async def send_event(self, s: str, delay: Optional[int] = None) -> None: + raise NotImplementedError() + + async def send_input(self, s: str, blocking: bool = True) -> None: + raise NotImplementedError() + + async def send_play(self, s: str) -> None: + raise NotImplementedError() + + async def send_raw(self, s: str, delay: Optional[int] = None) -> None: + raise NotImplementedError() + + async def set_capslock_state( + self, state: Optional[Union[Literal['On'], Literal['Off'], Literal['AlwaysOn'], Literal['AlwaysOff']]] = None + ) -> None: + raise NotImplementedError() + + async def set_volume(self, value: int, device_number: int = 1) -> None: + raise NotImplementedError() + + async def show_error_traytip( + self, + title: str, + text: str, + second: float = 1.0, + slient: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> None: + raise NotImplementedError() + + async def show_info_traytip( + self, + title: str, + text: str, + second: float = 1.0, + slient: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> None: + raise NotImplementedError() + + async def show_tooltip( + self, + text: str, + x: Optional[int] = None, + y: Optional[int] = None, + *, + second: float = 1.0, + id: Optional[str] = None, + blocking: bool = True, + ) -> None: + raise NotImplementedError() + + async def show_warning_traytip( + self, + title: str, + text: str, + second: float = 1.0, + slient: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> None: + raise NotImplementedError() + + async def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: + raise NotImplementedError() + + async def sound_get( + self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME' + ) -> None: + raise NotImplementedError() + + async def sound_play(self, filename: str, blocking: bool = True) -> None: + raise NotImplementedError() + + async def sound_set( + self, + value: Union[str, int, float], + device_number: int = 1, + component_type: str = 'MASTER', + control_type: str = 'VOLUME', + ) -> None: + raise NotImplementedError() + + async def type(self, s: str, blocking: bool = True) -> None: + raise NotImplementedError() + + # fmt: off + @overload + async def _win_get(self, subcommand_function: Literal['WinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetCount'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowIDListResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetMinMax'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetControlList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetControlListHwnd'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetTransparent'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetTransColor'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + async def _win_get(self, subcommand_function: Literal['WinGetExStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + # fmt: on + + async def _win_get( + self, + subcommand_function: WinGetFunctions, + /, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + ) -> Union[ + StringResponseMessage, + IntegerResponseMessage, + WindowIDListResponseMessage, + WindowControlListResponseMessage, + ]: + + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = await self._transport.function_call(subcommand_function, args) + assert is_winget_response_type(resp) + return resp + + async def win_get( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> AsyncWindow: + resp = await self._win_get( + 'WinGetID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + win_id = resp.unpack() + return AsyncWindow(engine=self, ahk_id=win_id) + + async def win_set(self, subcommand: str, *args: Any, blocking: bool = True) -> None: + # TODO: type hint subcommand literals + raise NotImplementedError() + + async def windows(self) -> Sequence[AsyncWindow]: + raise NotImplementedError() + + async def click( + self, + x: Optional[int] = None, + y: Optional[int] = None, + *, + button: Optional[str] = None, + n: Optional[int] = None, + direction: Optional[str] = None, + relative: Optional[bool] = None, + blocking: bool = True, + mode: Optional[CoordMode] = None, + ) -> None: + raise NotImplementedError() + + 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: str = 'Screen', + scale_height: Optional[int] = None, + scale_width: Optional[int] = None, + transparent: Optional[str] = None, + icon: Optional[int] = None, + ) -> Union[Tuple[int, int], None]: + """ + https://www.autohotkey.com/docs/commands/ImageSearch.htm + """ + + 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: + s = '' + for opt in options: + s += f'*{opt} ' + s += image_path + args.append(s) + else: + args.append(image_path) + resp = await self._transport.function_call('ImageSearch', args) + return resp.unpack() + + async def mouse_drag( + self, + x: int, + y: Optional[int] = None, + *, + from_position: Optional[Tuple[int, int]] = None, + speed: Optional[int] = None, + button: Union[str, int] = 1, + relative: Optional[bool] = None, + blocking: bool = True, + mode: Optional[CoordMode] = None, + ) -> None: + raise NotImplementedError() + + async def pixel_get_color( + self, x: int, y: int, coord_mode: str = 'Screen', alt: bool = False, slow: bool = False, rgb: bool = True + ) -> str: + raise NotImplementedError() + + async def pixel_search( + self, + color: Union[str, int], + variation: int = 0, + upper_bound: Tuple[int, int] = (0, 0), + lower_bound: Optional[Tuple[int, int]] = None, + coord_mode: str = 'Screen', + fast: bool = True, + rgb: bool = True, + ) -> Union[Tuple[int, int], None]: + raise NotImplementedError() + + async def show_traytip( + self, + title: str, + text: str, + second: float = 1.0, + type_id: int = 1, + slient: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> None: + raise NotImplementedError() + + async def win_close( + self, + title: Optional[str] = None, + *, + text: Optional[str] = None, + seconds_to_wait: Optional[int] = None, + exclude_title: Optional[str] = None, + exclude_text: Optional[str] = None, + ) -> None: + args = [] + optional_args = (text, seconds_to_wait, exclude_title, exclude_text) + if title is not None: + args.append(title) + if any(optional_args): + for arg in optional_args: + args.append(str(arg) or '') + resp = await self._transport.function_call('WinClose', args=args) + resp.unpack() + return None diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 4b84026f..8bb9475d 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -1,19 +1,22 @@ +from __future__ import annotations + import asyncio.subprocess import atexit -import io import os import subprocess -import typing import warnings from abc import ABC from abc import abstractmethod from io import BytesIO from shutil import which from typing import Any +from typing import AnyStr from typing import Literal from typing import Optional +from typing import overload from typing import Protocol from typing import runtime_checkable +from typing import Union from ahk.message import BooleanResponseMessage from ahk.message import CoordinateResponseMessage @@ -21,8 +24,10 @@ from ahk.message import NoValueResponseMessage from ahk.message import RequestMessage from ahk.message import ResponseMessage +from ahk.message import ResponseMessageTypes from ahk.message import StringResponseMessage from ahk.message import TupleResponseMessage +from ahk.message import WindowControlListResponseMessage from ahk.message import WindowIDListResponseMessage DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' @@ -32,6 +37,64 @@ SyncIOProcess = subprocess.Popen[bytes] +FunctionName = Literal[ + Literal['ImageSearch'], + Literal['PixelGetColor'], + Literal['PixelSearch'], + Literal['MouseGetPos'], + Literal['AHKKeyState'], + Literal['MouseMove'], + Literal['CoordMode'], + Literal['Click'], + Literal['MouseClickDrag'], + Literal['KeyWait'], + Literal['SetKeyDelay'], + Literal['Send'], + Literal['SendRaw'], + Literal['SendInput'], + Literal['SendEvent'], + Literal['SendPlay'], + Literal['SetCapsLockState'], + Literal['WinGetTitle'], + Literal['WinGetClass'], + Literal['WinGetText'], + Literal['WinActivate'], + Literal['WinActivateBottom'], + Literal['WinClose'], + Literal['WinHide'], + Literal['WinKill'], + Literal['WinMaximize'], + Literal['WinMinimize'], + Literal['WinRestore'], + Literal['WinShow'], + Literal['WindowList'], + Literal['WinSend'], + Literal['WinSendRaw'], + Literal['ControlSend'], + Literal['FromMouse'], + Literal['WinGet'], + Literal['WinSet'], + Literal['WinSetTitle'], + Literal['WinIsAlwaysOnTop'], + Literal['WinClick'], + Literal['AHKWinMove'], + Literal['AHKWinGetPos'], + Literal['WinGetID'], + Literal['WinGetIDLast'], + Literal['WinGetPID'], + Literal['WinGetProcessName'], + Literal['WinGetProcessPath'], + Literal['WinGetCount'], + Literal['WinGetList'], + Literal['WinGetMinMax'], + Literal['WinGetControlList'], + Literal['WinGetControlListHwnd'], + Literal['WinGetTransparent'], + Literal['WinGetTransColor'], + Literal['WinGetStyle'], + Literal['WinGetExStyle'], +] + @runtime_checkable class Killable(Protocol): @@ -40,7 +103,10 @@ def kill(self) -> None: def kill(proc: Killable) -> None: - proc.kill() + try: + proc.kill() + except: + pass class AsyncAHKProcess: @@ -74,6 +140,10 @@ async def readline(self) -> bytes: assert self._proc.stdout is not None return await self._proc.stdout.readline() + def kill(self) -> None: + assert self._proc is not None + self._proc.kill() + async def async_create_process(runargs: list[str]) -> asyncio.subprocess.Process: # unasync: remove return await asyncio.subprocess.create_subprocess_exec( @@ -89,7 +159,7 @@ class AhkExecutableNotFoundError(EnvironmentError): pass -def _resolve_executable_path(executable_path: str = '') -> str: +def _resolve_executable_path(executable_path: Union[str, os.PathLike[AnyStr]] = '') -> str: if not executable_path: executable_path = ( os.environ.get('AHK_PATH', '') @@ -120,7 +190,7 @@ def _resolve_executable_path(executable_path: str = '') -> str: 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.' @@ -132,310 +202,164 @@ def _resolve_executable_path(executable_path: str = '') -> str: class AsyncTransport(ABC): _started: bool = False + def __init__(self, /, **kwargs: Any): + pass + async def init(self) -> None: self._started = True return None - @typing.overload - async def function_call( - self, function_name: Literal['ImageSearch'], args: Optional[list[str]] = None - ) -> TupleResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['PixelGetColor'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['PixelSearch'], args: Optional[list[str]] = None - ) -> CoordinateResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['MouseGetPos'], args: Optional[list[str]] = None - ) -> CoordinateResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['AHKKeyState'], args: Optional[list[str]] = None - ) -> BooleanResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['MouseMove'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['CoordMode'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['Click'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['MouseClickDrag'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - # @typing.overload - # async def function_call(self, function_name: Literal['RegRead'], args: Optional[list[str]] = None) -> : - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[list[str]] = None): - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[list[str]] = None): - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[list[str]] = None): - # ... - - @typing.overload - async def function_call( - self, function_name: Literal['KeyWait'], args: Optional[list[str]] = None - ) -> IntegerResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['SetKeyDelay'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['Send'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['SendRaw'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['SendInput'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['SendEvent'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['SendPlay'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['SetCapsLockState'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - # @typing.overload - # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[list[str]] = None) -> NoValueResponseMessage: - # ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinGetTitle'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinGetClass'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinGetText'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinActivate'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinActivateBottom'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinClose'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinHide'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinKill'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinMaximize'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinMinimize'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinRestore'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinShow'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - # - # @typing.overload - # async def function_call(self, function_name: Literal['WinWait'], args: Optional[list[str]] = None) -> StringResponseMessage: - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[list[str]] = None) -> StringResponseMessage: - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[list[str]] = None) -> StringResponseMessage: - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[list[str]] = None) -> : - # ... - - @typing.overload - async def function_call( - self, function_name: Literal['WindowList'], args: Optional[list[str]] = None - ) -> WindowIDListResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinSend'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinSendRaw'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['ControlSend'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - # @typing.overload - # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[list[str]] = None) -> : - # ... - - @typing.overload - async def function_call( - self, function_name: Literal['FromMouse'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinGet'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinSet'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinSetTitle'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[list[str]] = None - ) -> BooleanResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['WinClick'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['AHKWinMove'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - async def function_call( - self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None - ) -> TupleResponseMessage: - ... - - async def function_call(self, function_name: str, args: Optional[list[str]] = None) -> ResponseMessage: + # fmt: off + @overload + async def function_call(self, function_name: Literal['ImageSearch'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['PixelSearch'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['MouseMove'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['CoordMode'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['Click'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['KeyWait'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['Send'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['SendRaw'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['SendInput'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['SendEvent'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['SendPlay'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetClass'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetText'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinActivate'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinClose'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinHide'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinKill'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinMaximize'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinMinimize'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinRestore'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinShow'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WindowList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinSend'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['ControlSend'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['FromMouse'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGet'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinSet'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinClick'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None) -> TupleResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetID'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetPID'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetProcessName'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetProcessPath'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetCount'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetMinMax'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetControlList'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetControlListHwnd'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetTransparent'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetTransColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + async def function_call(self, function_name: Literal['WinGetExStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + + # @overload + # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + # @overload + # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[list[str]] = None) -> None: ... + # @overload + # async def function_call(self, function_name: Literal['WinWait'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # @overload + # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # @overload + # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # @overload + # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[list[str]] = None) -> None: ... + # @overload + # async def function_call(self, function_name: Literal['RegRead'], args: Optional[list[str]] = None) -> None: ... + # @overload + # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[list[str]] = None) -> None: ... + # @overload + # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[list[str]] = None) -> None: ... + # @overload + # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[list[str]] = None) -> None: ... + + # fmt: on + + async def function_call( + self, function_name: FunctionName, args: Optional[list[str]] = None + ) -> ResponseMessageTypes: if not self._started: await self.init() request = RequestMessage(function_name=function_name, args=args) - return await self.send(request) + resp = await self.send(request) + return resp @abstractmethod - async def send(self, request: RequestMessage) -> ResponseMessage: + async def send(self, request: RequestMessage) -> ResponseMessageTypes: return NotImplemented class AsyncDaemonProcessTransport(AsyncTransport): - def __init__(self, executable_path: str = ''): + def __init__(self, *, executable_path: Union[str, os.PathLike[AnyStr]] = ''): self._proc: Optional[AsyncAHKProcess] self._proc = None self._executable_path: str = _resolve_executable_path(executable_path=executable_path) @@ -451,7 +375,7 @@ async def start(self) -> None: self._proc = AsyncAHKProcess(runargs=runargs) await self._proc.start() - async def send(self, request: RequestMessage) -> ResponseMessage: + async def send(self, request: RequestMessage) -> ResponseMessageTypes: newline = '\n' msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 291aae98..fea3754d 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -10,3 +10,24 @@ class AsyncWindow: def __init__(self, engine: AsyncAHK, ahk_id: str): self._engine: AsyncAHK = engine self._ahk_id: str = ahk_id + + def __repr__(self) -> str: + return f'<{self.__class__.__qualname__} ahk_id={self._ahk_id}>' + + 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) + + async def close(self) -> None: + await self._engine.win_close(title=f'ahk_id {self._ahk_id}') + return None + + +class AsyncControl: + def __init__(self, window: AsyncWindow, control_class: str): + self.window: AsyncWindow = window + self.control_class: str = control_class diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 402eb76c..4d67e215 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1,12 +1,24 @@ +from __future__ import annotations + from typing import Any +from typing import Callable +from typing import Iterable from typing import Literal from typing import Optional from typing import overload +from typing import Sequence from typing import Tuple +from typing import Type from typing import Union +from ..message import IntegerResponseMessage +from ..message import is_winget_response_type +from ..message import StringResponseMessage +from ..message import WindowControlListResponseMessage +from ..message import WindowIDListResponseMessage from .transport import DaemonProcessTransport from .transport import Transport +from .window import SyncControl from .window import Window @@ -14,10 +26,33 @@ class FutureResult: ... +CoordModeTargets = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] +CoordModeRelativeTo = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] +WinGetFunctions = Literal[ + Literal['WinGetID'], + Literal['WinGetIDLast'], + Literal['WinGetPID'], + Literal['WinGetProcessName'], + Literal['WinGetProcessPath'], + Literal['WinGetCount'], + Literal['WinGetList'], + Literal['WinGetMinMax'], + Literal['WinGetControlList'], + Literal['WinGetControlListHwnd'], + Literal['WinGetTransparent'], + Literal['WinGetTransColor'], + Literal['WinGetStyle'], + Literal['WinGetExStyle'], +] +CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] + + class AHK: - def __init__(self, *, transport: Optional[Transport] = None): - if transport is None: - transport = DaemonProcessTransport() + def __init__(self, *, TransportClass: Optional[Type[Transport]] = None, **transport_kwargs: Any): + if TransportClass is None: + TransportClass = DaemonProcessTransport + assert TransportClass is not None + transport = TransportClass(**transport_kwargs) self._transport: Transport = transport def list_windows(self) -> list[Window]: @@ -95,3 +130,368 @@ def mouse_move( return FutureResult() else: raise ValueError(f'Invalid value for argument blocking: {blocking!r}') + + def a_run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: + raise NotImplementedError() + + def get_active_window(self) -> Union[Window, None]: + raise NotImplementedError() + + def find_windows( + self, func: Optional[Callable[[Window], bool]] = None, **kwargs: Any + ) -> Iterable[Window]: + raise NotImplementedError() + + def find_windows_by_class(self, class_name: str, exact: bool = False) -> Iterable[Window]: + raise NotImplementedError() + + def find_windows_by_text(self, text: str, exact: bool = False) -> Iterable[Window]: + raise NotImplementedError() + + def find_windows_by_title(self, title: str, exact: bool = False) -> Iterable[Window]: + raise NotImplementedError() + + def get_volume(self, device_number: int = 1) -> float: + raise NotImplementedError() + + def key_down(self, key: str, blocking: bool = True) -> None: + raise NotImplementedError() + + def key_press(self, key: str, release: bool = True, blocking: bool = True) -> None: + raise NotImplementedError() + + def key_release(self, key: str, blocking: bool = True) -> None: + raise NotImplementedError() + + def key_state(self, key_name: str, mode: Optional[Union[Literal['P'], Literal['T']]] = None) -> bool: + raise NotImplementedError() + + def key_up(self, key: str, blocking: bool = True) -> None: + raise NotImplementedError() + + def key_wait( + self, key_name: str, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False + ) -> None: + raise NotImplementedError() + + # async def mouse_position(self): + # raise NotImplementedError() + + def mouse_wheel( + self, + direction: Union[ + Literal['up'], Literal['down'], Literal['UP'], Literal['DOWN'], Literal['Up'], Literal['Down'] + ], + *args: Any, + **kwargs: Any, + ) -> None: + raise NotImplementedError() + + # async def reg_delete(self, key_name: str, value_name: str = '') -> None: + # raise NotImplementedError() + # + # async def reg_loop(self, reg: str, key_name: str, mode=''): + # raise NotImplementedError() + # + # async def reg_read(self, key_name: str, value_name='') -> str: + # raise NotImplementedError() + # + # async def reg_set_view(self, reg_view: int) -> None: + # raise NotImplementedError() + # + # async def reg_write(self, value_type: str, key_name: str, value_name='') -> None: + # raise NotImplementedError() + + def run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: + raise NotImplementedError() + + def send(self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True) -> None: + raise NotImplementedError() + + def send_event(self, s: str, delay: Optional[int] = None) -> None: + raise NotImplementedError() + + def send_input(self, s: str, blocking: bool = True) -> None: + raise NotImplementedError() + + def send_play(self, s: str) -> None: + raise NotImplementedError() + + def send_raw(self, s: str, delay: Optional[int] = None) -> None: + raise NotImplementedError() + + def set_capslock_state( + self, state: Optional[Union[Literal['On'], Literal['Off'], Literal['AlwaysOn'], Literal['AlwaysOff']]] = None + ) -> None: + raise NotImplementedError() + + def set_volume(self, value: int, device_number: int = 1) -> None: + raise NotImplementedError() + + def show_error_traytip( + self, + title: str, + text: str, + second: float = 1.0, + slient: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> None: + raise NotImplementedError() + + def show_info_traytip( + self, + title: str, + text: str, + second: float = 1.0, + slient: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> None: + raise NotImplementedError() + + def show_tooltip( + self, + text: str, + x: Optional[int] = None, + y: Optional[int] = None, + *, + second: float = 1.0, + id: Optional[str] = None, + blocking: bool = True, + ) -> None: + raise NotImplementedError() + + def show_warning_traytip( + self, + title: str, + text: str, + second: float = 1.0, + slient: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> None: + raise NotImplementedError() + + def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: + raise NotImplementedError() + + def sound_get( + self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME' + ) -> None: + raise NotImplementedError() + + def sound_play(self, filename: str, blocking: bool = True) -> None: + raise NotImplementedError() + + def sound_set( + self, + value: Union[str, int, float], + device_number: int = 1, + component_type: str = 'MASTER', + control_type: str = 'VOLUME', + ) -> None: + raise NotImplementedError() + + def type(self, s: str, blocking: bool = True) -> None: + raise NotImplementedError() + + # fmt: off + @overload + def _win_get(self, subcommand_function: Literal['WinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetCount'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowIDListResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetMinMax'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetControlList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetControlListHwnd'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetTransparent'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetTransColor'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + @overload + def _win_get(self, subcommand_function: Literal['WinGetExStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + # fmt: on + + def _win_get( + self, + subcommand_function: WinGetFunctions, + /, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + ) -> Union[ + StringResponseMessage, + IntegerResponseMessage, + WindowIDListResponseMessage, + WindowControlListResponseMessage, + ]: + + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = self._transport.function_call(subcommand_function, args) + assert is_winget_response_type(resp) + return resp + + def win_get( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Window: + resp = self._win_get( + 'WinGetID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + win_id = resp.unpack() + return Window(engine=self, ahk_id=win_id) + + def win_set(self, subcommand: str, *args: Any, blocking: bool = True) -> None: + # TODO: type hint subcommand literals + raise NotImplementedError() + + def windows(self) -> Sequence[Window]: + raise NotImplementedError() + + def click( + self, + x: Optional[int] = None, + y: Optional[int] = None, + *, + button: Optional[str] = None, + n: Optional[int] = None, + direction: Optional[str] = None, + relative: Optional[bool] = None, + blocking: bool = True, + mode: Optional[CoordMode] = None, + ) -> None: + raise NotImplementedError() + + 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: str = 'Screen', + scale_height: Optional[int] = None, + scale_width: Optional[int] = None, + transparent: Optional[str] = None, + icon: Optional[int] = None, + ) -> Union[Tuple[int, int], None]: + """ + https://www.autohotkey.com/docs/commands/ImageSearch.htm + """ + + 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: + s = '' + for opt in options: + s += f'*{opt} ' + s += image_path + args.append(s) + else: + args.append(image_path) + resp = self._transport.function_call('ImageSearch', args) + return resp.unpack() + + def mouse_drag( + self, + x: int, + y: Optional[int] = None, + *, + from_position: Optional[Tuple[int, int]] = None, + speed: Optional[int] = None, + button: Union[str, int] = 1, + relative: Optional[bool] = None, + blocking: bool = True, + mode: Optional[CoordMode] = None, + ) -> None: + raise NotImplementedError() + + def pixel_get_color( + self, x: int, y: int, coord_mode: str = 'Screen', alt: bool = False, slow: bool = False, rgb: bool = True + ) -> str: + raise NotImplementedError() + + def pixel_search( + self, + color: Union[str, int], + variation: int = 0, + upper_bound: Tuple[int, int] = (0, 0), + lower_bound: Optional[Tuple[int, int]] = None, + coord_mode: str = 'Screen', + fast: bool = True, + rgb: bool = True, + ) -> Union[Tuple[int, int], None]: + raise NotImplementedError() + + def show_traytip( + self, + title: str, + text: str, + second: float = 1.0, + type_id: int = 1, + slient: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> None: + raise NotImplementedError() + + def win_close( + self, + title: Optional[str] = None, + *, + text: Optional[str] = None, + seconds_to_wait: Optional[int] = None, + exclude_title: Optional[str] = None, + exclude_text: Optional[str] = None, + ) -> None: + args = [] + optional_args = (text, seconds_to_wait, exclude_title, exclude_text) + if title is not None: + args.append(title) + if any(optional_args): + for arg in optional_args: + args.append(str(arg) or '') + resp = self._transport.function_call('WinClose', args=args) + resp.unpack() + return None diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index f459f7bc..dc8c80c9 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -1,19 +1,22 @@ +from __future__ import annotations + import asyncio.subprocess import atexit -import io import os import subprocess -import typing import warnings from abc import ABC from abc import abstractmethod from io import BytesIO from shutil import which from typing import Any +from typing import AnyStr from typing import Literal from typing import Optional +from typing import overload from typing import Protocol from typing import runtime_checkable +from typing import Union from ahk.message import BooleanResponseMessage from ahk.message import CoordinateResponseMessage @@ -21,8 +24,10 @@ from ahk.message import NoValueResponseMessage from ahk.message import RequestMessage from ahk.message import ResponseMessage +from ahk.message import ResponseMessageTypes from ahk.message import StringResponseMessage from ahk.message import TupleResponseMessage +from ahk.message import WindowControlListResponseMessage from ahk.message import WindowIDListResponseMessage DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' @@ -31,6 +36,64 @@ SyncIOProcess = subprocess.Popen[bytes] +FunctionName = Literal[ + Literal['ImageSearch'], + Literal['PixelGetColor'], + Literal['PixelSearch'], + Literal['MouseGetPos'], + Literal['AHKKeyState'], + Literal['MouseMove'], + Literal['CoordMode'], + Literal['Click'], + Literal['MouseClickDrag'], + Literal['KeyWait'], + Literal['SetKeyDelay'], + Literal['Send'], + Literal['SendRaw'], + Literal['SendInput'], + Literal['SendEvent'], + Literal['SendPlay'], + Literal['SetCapsLockState'], + Literal['WinGetTitle'], + Literal['WinGetClass'], + Literal['WinGetText'], + Literal['WinActivate'], + Literal['WinActivateBottom'], + Literal['WinClose'], + Literal['WinHide'], + Literal['WinKill'], + Literal['WinMaximize'], + Literal['WinMinimize'], + Literal['WinRestore'], + Literal['WinShow'], + Literal['WindowList'], + Literal['WinSend'], + Literal['WinSendRaw'], + Literal['ControlSend'], + Literal['FromMouse'], + Literal['WinGet'], + Literal['WinSet'], + Literal['WinSetTitle'], + Literal['WinIsAlwaysOnTop'], + Literal['WinClick'], + Literal['AHKWinMove'], + Literal['AHKWinGetPos'], + Literal['WinGetID'], + Literal['WinGetIDLast'], + Literal['WinGetPID'], + Literal['WinGetProcessName'], + Literal['WinGetProcessPath'], + Literal['WinGetCount'], + Literal['WinGetList'], + Literal['WinGetMinMax'], + Literal['WinGetControlList'], + Literal['WinGetControlListHwnd'], + Literal['WinGetTransparent'], + Literal['WinGetTransColor'], + Literal['WinGetStyle'], + Literal['WinGetExStyle'], +] + @runtime_checkable class Killable(Protocol): @@ -39,7 +102,10 @@ def kill(self) -> None: def kill(proc: Killable) -> None: - proc.kill() + try: + proc.kill() + except: + pass class SyncAHKProcess: @@ -68,6 +134,10 @@ def readline(self) -> bytes: assert self._proc.stdout is not None return self._proc.stdout.readline() + def kill(self) -> None: + assert self._proc is not None + self._proc.kill() + @@ -79,7 +149,7 @@ class AhkExecutableNotFoundError(EnvironmentError): pass -def _resolve_executable_path(executable_path: str = '') -> str: +def _resolve_executable_path(executable_path: Union[str, os.PathLike[AnyStr]] = '') -> str: if not executable_path: executable_path = ( os.environ.get('AHK_PATH', '') @@ -110,7 +180,7 @@ def _resolve_executable_path(executable_path: str = '') -> str: 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.' @@ -122,310 +192,164 @@ def _resolve_executable_path(executable_path: str = '') -> str: class Transport(ABC): _started: bool = False + def __init__(self, /, **kwargs: Any): + pass + def init(self) -> None: self._started = True return None - @typing.overload - def function_call( - self, function_name: Literal['ImageSearch'], args: Optional[list[str]] = None - ) -> TupleResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['PixelGetColor'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['PixelSearch'], args: Optional[list[str]] = None - ) -> CoordinateResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['MouseGetPos'], args: Optional[list[str]] = None - ) -> CoordinateResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['AHKKeyState'], args: Optional[list[str]] = None - ) -> BooleanResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['MouseMove'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['CoordMode'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['Click'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['MouseClickDrag'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - # @typing.overload - # async def function_call(self, function_name: Literal['RegRead'], args: Optional[list[str]] = None) -> : - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[list[str]] = None): - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[list[str]] = None): - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[list[str]] = None): - # ... - - @typing.overload - def function_call( - self, function_name: Literal['KeyWait'], args: Optional[list[str]] = None - ) -> IntegerResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['SetKeyDelay'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['Send'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['SendRaw'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['SendInput'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['SendEvent'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['SendPlay'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['SetCapsLockState'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - # @typing.overload - # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[list[str]] = None) -> NoValueResponseMessage: - # ... - - @typing.overload - def function_call( - self, function_name: Literal['WinGetTitle'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinGetClass'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinGetText'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinActivate'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinActivateBottom'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinClose'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinHide'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinKill'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinMaximize'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinMinimize'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinRestore'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinShow'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - # - # @typing.overload - # async def function_call(self, function_name: Literal['WinWait'], args: Optional[list[str]] = None) -> StringResponseMessage: - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[list[str]] = None) -> StringResponseMessage: - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[list[str]] = None) -> StringResponseMessage: - # ... - # - # @typing.overload - # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[list[str]] = None) -> : - # ... - - @typing.overload - def function_call( - self, function_name: Literal['WindowList'], args: Optional[list[str]] = None - ) -> WindowIDListResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinSend'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinSendRaw'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['ControlSend'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - # @typing.overload - # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[list[str]] = None) -> : - # ... - - @typing.overload - def function_call( - self, function_name: Literal['FromMouse'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinGet'], args: Optional[list[str]] = None - ) -> StringResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinSet'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinSetTitle'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[list[str]] = None - ) -> BooleanResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['WinClick'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['AHKWinMove'], args: Optional[list[str]] = None - ) -> NoValueResponseMessage: - ... - - @typing.overload - def function_call( - self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None - ) -> TupleResponseMessage: - ... - - def function_call(self, function_name: str, args: Optional[list[str]] = None) -> ResponseMessage: + # fmt: off + @overload + def function_call(self, function_name: Literal['ImageSearch'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + @overload + def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['PixelSearch'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + @overload + def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + @overload + def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + @overload + def function_call(self, function_name: Literal['MouseMove'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['CoordMode'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['Click'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['KeyWait'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + @overload + def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['Send'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['SendRaw'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['SendInput'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['SendEvent'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['SendPlay'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetClass'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetText'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinActivate'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinClose'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinHide'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinKill'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinMaximize'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinMinimize'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinRestore'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinShow'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WindowList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinSend'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['ControlSend'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['FromMouse'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGet'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinSet'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinClick'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None) -> TupleResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetID'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetPID'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetProcessName'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetProcessPath'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetCount'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetMinMax'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetControlList'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetControlListHwnd'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetTransparent'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetTransColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + @overload + def function_call(self, function_name: Literal['WinGetExStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + + # @overload + # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + # @overload + # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[list[str]] = None) -> None: ... + # @overload + # async def function_call(self, function_name: Literal['WinWait'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # @overload + # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # @overload + # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # @overload + # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[list[str]] = None) -> None: ... + # @overload + # async def function_call(self, function_name: Literal['RegRead'], args: Optional[list[str]] = None) -> None: ... + # @overload + # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[list[str]] = None) -> None: ... + # @overload + # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[list[str]] = None) -> None: ... + # @overload + # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[list[str]] = None) -> None: ... + + # fmt: on + + def function_call( + self, function_name: FunctionName, args: Optional[list[str]] = None + ) -> ResponseMessageTypes: if not self._started: self.init() request = RequestMessage(function_name=function_name, args=args) - return self.send(request) + resp = self.send(request) + return resp @abstractmethod - def send(self, request: RequestMessage) -> ResponseMessage: + def send(self, request: RequestMessage) -> ResponseMessageTypes: return NotImplemented class DaemonProcessTransport(Transport): - def __init__(self, executable_path: str = ''): + def __init__(self, *, executable_path: Union[str, os.PathLike[AnyStr]] = ''): self._proc: Optional[SyncAHKProcess] self._proc = None self._executable_path: str = _resolve_executable_path(executable_path=executable_path) @@ -441,7 +365,7 @@ def start(self) -> None: self._proc = SyncAHKProcess(runargs=runargs) self._proc.start() - def send(self, request: RequestMessage) -> ResponseMessage: + def send(self, request: RequestMessage) -> ResponseMessageTypes: newline = '\n' msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index a4958974..099761d1 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -10,3 +10,23 @@ class Window: def __init__(self, engine: AHK, ahk_id: str): self._engine: AHK = engine self._ahk_id: str = ahk_id + + def __repr__(self) -> str: + return f'<{self.__class__.__qualname__} ahk_id={self._ahk_id}>' + + 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 close(self) -> None: + self._engine.win_close(title=f'ahk_id {self._ahk_id}') + return None + +class SyncControl: + def __init__(self, window: Window, control_class: str): + self.window: Window = window + self.control_class: str = control_class diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 7a5e4436..d36c4877 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1,5 +1,6 @@ #NoEnv #Persistent +#SingleInstance force RESPONSEMESSAGE := "000" ; ResponseMessage TUPLERESPONSEMESSAGE := "001" ; TupleResponseMessage @@ -19,12 +20,193 @@ FormatResponse(MessageType, payload) { return response } +FormatNoValueResponse() { + global NOVALUE_SENTINEL + global NOVALUERESPONSEMESSAGE + return FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) +} + + +WinGetID(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, ID, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetIDLast(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, IDLast, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetPID(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, PID, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetProcessName(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, ProcessName, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetProcessPath(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, ProcessPath, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetCount(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, Count, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetList(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, List, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetMinMax(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, MinMax, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetControlList(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, ControlList, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetControlListHwnd(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, ControlListHwnd, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetTransparent(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, Transparent, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetTransColor(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, TransColor, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetStyle(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, Style, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} +WinGetExStyle(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGet, output, ExStyle, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + return response +} + + + ImageSearch(ByRef command) { - imagepath := command[8] - x1 := command[4] - y1 := command[5] - x2 := command[6] - y2 := command[7] + global COORDINATERESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + imagepath := command[6] + x1 := command[2] + y1 := command[3] + x2 := command[4] + y2 := command[5] + if (x2 = "A_ScreenWidth") { x2 := A_ScreenWidth } @@ -35,7 +217,7 @@ ImageSearch(ByRef command) { if (ErrorLevel = 2) { s := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "there was a problem that prevented the command from conducting the search (such as failure to open the image file or a badly formatted option)") } else if (ErrorLevel = 1) { - s := FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) + s := FormatNoValueResponse() } else { s := FormatResponse(COORDINATERESPONSEMESSAGE, Format("({}, {})", xpos, ypos)) } diff --git a/ahk/message.py b/ahk/message.py index 69eeff82..50b3bd0c 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -17,6 +17,7 @@ from typing import Tuple from typing import Type from typing import TypedDict +from typing import TypeGuard from typing import TypeVar from typing import Union @@ -31,6 +32,44 @@ def readline(self) -> bytes: ... +def is_window_control_list_response(resp_obj: object) -> TypeGuard[Tuple[str, Tuple[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, tuple): + return False + for obj in expected_win_list: + if not isinstance(obj, str): + return False + return True + + +def is_winget_response_type( + obj: object, +) -> TypeGuard[ + Union[ + 'StringResponseMessage', + 'IntegerResponseMessage', + 'WindowIDListResponseMessage', + 'WindowControlListResponseMessage', + ] +]: + if isinstance(obj, StringResponseMessage): + return True + elif isinstance(obj, IntegerResponseMessage): + return True + elif isinstance(obj, WindowIDListResponseMessage): + return True + elif isinstance(obj, WindowControlListResponseMessage): + return True + else: + return False + + T_ResponseMessageType = TypeVar('T_ResponseMessageType', bound='ResponseMessage') @@ -65,29 +104,17 @@ def __repr__(self) -> str: return f'ResponseMessage' @staticmethod - def _tom_lookup(tom: bytes) -> Type['ResponseMessage']: + def _tom_lookup(tom: bytes) -> 'ResponseMessageClassTypes': klass = _message_registry.get(tom) - if not klass: + if klass is None: raise ValueError(f'No such TOM {tom!r}') return klass @classmethod - def from_bytes(cls: Type[T_ResponseMessageType], b: bytes) -> T_ResponseMessageType: + def from_bytes(cls: Type[T_ResponseMessageType], b: bytes) -> 'ResponseMessageTypes': tom, _, message_bytes = b.split(b'\n', 2) klass = cls._tom_lookup(tom) - return klass(raw_content=message_bytes) # type: ignore[return-value] - - @classmethod - def from_stream(cls: Type[T_ResponseMessageType], stream: BytesLineReadable) -> T_ResponseMessageType: - content_buffer = io.BytesIO() - tom = stream.readline().strip() - content_lines = stream.readline().strip() - for _ in range(int(content_lines) + 1): - part = stream.readline() - content_buffer.write(part) - contents = content_buffer.getvalue() - message_bytes = tom + b'\n' + content_lines + b'\n' + contents - return cls.from_bytes(message_bytes) + return klass(raw_content=message_bytes) def to_bytes(self) -> bytes: content_lines = self._raw_content.count(b'\n') @@ -98,7 +125,7 @@ def unpack(self) -> Any: return NotImplemented -_message_registry: dict[bytes, Type[ResponseMessage]] +_message_registry: dict[bytes, 'ResponseMessageClassTypes'] _message_registry = {ResponseMessage._type_order_mark: ResponseMessage} @@ -165,6 +192,16 @@ def unpack(self) -> None: return None +class WindowControlListResponseMessage(ResponseMessage): + type = 'windowcontrollist' + + def unpack(self) -> Tuple[str, Tuple[str, ...]]: + s = self._raw_content.decode(encoding='utf-8') + val = ast.literal_eval(s) + assert is_window_control_list_response(val) + return val + + class AHKExecutionException(Exception): pass @@ -184,3 +221,29 @@ 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 [] + + +ResponseMessageTypes = Union[ + ResponseMessage, + TupleResponseMessage, + CoordinateResponseMessage, + IntegerResponseMessage, + BooleanResponseMessage, + StringResponseMessage, + WindowIDListResponseMessage, + NoValueResponseMessage, + WindowControlListResponseMessage, + ExceptionResponseMessage, +] +ResponseMessageClassTypes = Union[ + Type[TupleResponseMessage], + Type[CoordinateResponseMessage], + Type[IntegerResponseMessage], + Type[BooleanResponseMessage], + Type[StringResponseMessage], + Type[WindowIDListResponseMessage], + Type[NoValueResponseMessage], + Type[WindowControlListResponseMessage], + Type[ExceptionResponseMessage], + Type[ResponseMessage], +] diff --git a/setup.py b/setup.py index 0c7d2f2b..f168699f 100644 --- a/setup.py +++ b/setup.py @@ -4,10 +4,10 @@ setuptools.setup( name='ahk', version='0.0.1', - author='Example Author', - author_email='author@example.com', + author_email='spencer.young@spyoung.com', + author='Spencer Young', description='A package used to test customized unasync', - url='https://github.com/pypa/sampleproject', + url='https://github.com/spyoungtech/ahk', packages=['ahk', 'ahk._async'], cmdclass={ 'build_py': unasync.cmdclass_build_py( @@ -26,13 +26,7 @@ # "__aenter__": "__aenter__", }, ), - # unasync.Rule( - # fromdir="/ahip/tests/", - # todir="/hip/tests/", - # additional_replacements={"ahip": "hip"}, - # ), ] ) }, - # package_dir={"": "src"}, ) 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_mouse.py b/tests/_async/test_mouse.py new file mode 100644 index 00000000..e19aad92 --- /dev/null +++ b/tests/_async/test_mouse.py @@ -0,0 +1 @@ +from unittest import IsolatedAsyncioTestCase diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py new file mode 100644 index 00000000..3394f856 --- /dev/null +++ b/tests/_async/test_screen.py @@ -0,0 +1,44 @@ +import asyncio +import time +from itertools import product +from unittest import IsolatedAsyncioTestCase +from unittest import TestCase + +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() + 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) + + async def asyncTearDown(self): + for win in await self.ahk.list_windows(): + if win not in self.before_windows: + await win.close() + break + self.ahk._transport._proc.kill() + + # + # async def test_pixel_search(self): + # result = await self.ahk.pixel_search(0xFF0000) + # self.assertIsNotNone(result) + + async def test_image_search(self): + self.im.save('testimage.png') + position = await self.ahk.image_search('testimage.png') + 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) diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py new file mode 100644 index 00000000..2e15b613 --- /dev/null +++ b/tests/_async/test_window.py @@ -0,0 +1,26 @@ +import asyncio +import os +import subprocess +import sys +import time +from unittest import IsolatedAsyncioTestCase + +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 test_close(self): +# await self.win.close() +# await asyncio.sleep(0.2) +# self.assertFalse(await self.win.exists()) +# self.assertFalse(await self.win.exist) diff --git a/tests/test_mouse.py b/tests/test_mouse.py deleted file mode 100644 index 1d9a4cfd..00000000 --- a/tests/test_mouse.py +++ /dev/null @@ -1,7 +0,0 @@ -from ahk import AHK - - -def test_mouse_move(): - ahk = AHK() - ahk.mouse_move(100, 100) - assert ahk.get_mouse_position() == (100, 100) From 83b8c689e27af22528736a4b46050372f608bdc7 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 15 Jul 2022 12:46:20 -0700 Subject: [PATCH 200/588] add sync tests --- tests/_sync/__init__.py | 0 tests/_sync/test_mouse.py | 1 + tests/_sync/test_screen.py | 44 ++++++++++++++++++++++++++++++++++++++ tests/_sync/test_window.py | 26 ++++++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 tests/_sync/__init__.py create mode 100644 tests/_sync/test_mouse.py create mode 100644 tests/_sync/test_screen.py create mode 100644 tests/_sync/test_window.py 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_mouse.py b/tests/_sync/test_mouse.py new file mode 100644 index 00000000..e19aad92 --- /dev/null +++ b/tests/_sync/test_mouse.py @@ -0,0 +1 @@ +from unittest import IsolatedAsyncioTestCase diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py new file mode 100644 index 00000000..3623efa5 --- /dev/null +++ b/tests/_sync/test_screen.py @@ -0,0 +1,44 @@ +import asyncio +import time +from itertools import product +from unittest import IsolatedAsyncioTestCase +from unittest import TestCase + +from PIL import Image + +from ahk import AHK + + +class TestScreen(IsolatedAsyncioTestCase): + def asyncSetUp(self) -> None: + self.ahk = AHK() + self.before_windows = self.ahk.list_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 asyncTearDown(self): + for win in self.ahk.list_windows(): + if win not in self.before_windows: + win.close() + break + self.ahk._transport._proc.kill() + + # + # async def test_pixel_search(self): + # result = await 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') + 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) diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py new file mode 100644 index 00000000..74e05468 --- /dev/null +++ b/tests/_sync/test_window.py @@ -0,0 +1,26 @@ +import asyncio +import os +import subprocess +import sys +import time +from unittest import IsolatedAsyncioTestCase + +from ahk import AHK +from ahk import Window + + +# 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 test_close(self): +# await self.win.close() +# await asyncio.sleep(0.2) +# self.assertFalse(await self.win.exists()) +# self.assertFalse(await self.win.exist) From 08f309037bfb34084584f2c88243e59952252842 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 15 Jul 2022 17:59:22 -0700 Subject: [PATCH 201/588] testing setup --- _tests_setup.py | 3 +++ ahk/daemon.ahk | 2 +- tests/_async/test_screen.py | 2 -- tests/_sync/test_mouse.py | 2 +- tests/_sync/test_screen.py | 8 +++----- tests/_sync/test_window.py | 2 +- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/_tests_setup.py b/_tests_setup.py index 3024d4e1..edd343fc 100644 --- a/_tests_setup.py +++ b/_tests_setup.py @@ -27,6 +27,9 @@ '_AIOP': '_SIOP', 'async_create_process': 'sync_create_process', 'adrain_stdin': 'drain_stdin', + 'IsolatedAsyncioTestCase': 'TestCase', + 'asyncSetUp': 'setUp', + 'asyncTearDown': 'tearDown' # "__aenter__": "__aenter__", }, ), diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index d36c4877..84f8d2c4 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1,6 +1,6 @@ #NoEnv #Persistent -#SingleInstance force +#SingleInstance Off RESPONSEMESSAGE := "000" ; ResponseMessage TUPLERESPONSEMESSAGE := "001" ; TupleResponseMessage diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 3394f856..8d66b967 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -1,8 +1,6 @@ -import asyncio import time from itertools import product from unittest import IsolatedAsyncioTestCase -from unittest import TestCase from PIL import Image diff --git a/tests/_sync/test_mouse.py b/tests/_sync/test_mouse.py index e19aad92..2071d065 100644 --- a/tests/_sync/test_mouse.py +++ b/tests/_sync/test_mouse.py @@ -1 +1 @@ -from unittest import IsolatedAsyncioTestCase +from unittest import TestCase diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index 3623efa5..ff109100 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -1,7 +1,5 @@ -import asyncio import time from itertools import product -from unittest import IsolatedAsyncioTestCase from unittest import TestCase from PIL import Image @@ -9,8 +7,8 @@ from ahk import AHK -class TestScreen(IsolatedAsyncioTestCase): - def asyncSetUp(self) -> None: +class TestScreen(TestCase): + def setUp(self) -> None: self.ahk = AHK() self.before_windows = self.ahk.list_windows() im = Image.new('RGB', (20, 20)) @@ -20,7 +18,7 @@ def asyncSetUp(self) -> None: im.show() time.sleep(2) - def asyncTearDown(self): + def tearDown(self): for win in self.ahk.list_windows(): if win not in self.before_windows: win.close() diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 74e05468..aa785360 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -3,7 +3,7 @@ import subprocess import sys import time -from unittest import IsolatedAsyncioTestCase +from unittest import TestCase from ahk import AHK from ahk import Window From e2c9f3cc161cb681f1bafb14da9009d02a9b656c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 05:46:28 -0700 Subject: [PATCH 202/588] add dev requirements --- .github/workflows/test.yaml | 2 +- requirements-dev.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 requirements-dev.txt diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5913611b..bf8148bb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -15,7 +15,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install pytest coverage ahk-binary + python -m pip install -r requirements-dev.txt - name: Test with coverage/pytest run: | coverage run -m pytest diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..9f8e5da3 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,6 @@ +pytest +pillow +unasync +black +tokenize-rt +coverage From 778dee8ea54924683c121ddf2eef77f4297387b6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 05:52:25 -0700 Subject: [PATCH 203/588] install ahk --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bf8148bb..2d874324 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,6 +16,7 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -r requirements-dev.txt + python -m pip install ahk-binary - name: Test with coverage/pytest run: | coverage run -m pytest From c4a812dd6a3f1683c6bd8cff525795cb5beebb3f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 07:06:36 -0700 Subject: [PATCH 204/588] exists/close/tests --- ahk/_async/engine.py | 39 ++++++++++++++++++++-------------- ahk/_async/transport.py | 9 +++++--- ahk/_async/window.py | 3 +++ ahk/_sync/engine.py | 39 ++++++++++++++++++++-------------- ahk/_sync/transport.py | 9 +++++--- ahk/_sync/window.py | 3 +++ ahk/daemon.ahk | 42 ++++++++++++++++++++++++++----------- ahk/message.py | 20 +++++++++--------- tests/_async/test_window.py | 40 ++++++++++++++++++++++------------- tests/_sync/test_window.py | 40 ++++++++++++++++++++++------------- 10 files changed, 154 insertions(+), 90 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index dd9cc832..502b1683 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -13,6 +13,7 @@ from ..message import IntegerResponseMessage from ..message import is_winget_response_type +from ..message import NoValueResponseMessage from ..message import StringResponseMessage from ..message import WindowControlListResponseMessage from ..message import WindowIDListResponseMessage @@ -298,7 +299,7 @@ async def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - async def _win_get(self, subcommand_function: Literal['WinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['WinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[StringResponseMessage, NoValueResponseMessage]: ... @overload async def _win_get(self, subcommand_function: Literal['WinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... @overload @@ -340,21 +341,32 @@ async def _win_get( IntegerResponseMessage, WindowIDListResponseMessage, WindowControlListResponseMessage, + NoValueResponseMessage, ]: args = [title, text, exclude_title, exclude_title, exclude_text] resp = await self._transport.function_call(subcommand_function, args) - assert is_winget_response_type(resp) + assert is_winget_response_type(resp), f'Unexpected response: {resp!r}' return resp async def win_get( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> AsyncWindow: + ) -> Union[AsyncWindow, None]: resp = await self._win_get( 'WinGetID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) win_id = resp.unpack() - return AsyncWindow(engine=self, ahk_id=win_id) + if win_id is None: + return None + else: + return AsyncWindow(engine=self, ahk_id=win_id) + + async def win_exists( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> bool: + args = [title, text, exclude_title, exclude_text] + resp = await self._transport.function_call('AHKWinExist', args) + return resp.unpack() async def win_set(self, subcommand: str, *args: Any, blocking: bool = True) -> None: # TODO: type hint subcommand literals @@ -478,20 +490,15 @@ async def show_traytip( async def win_close( self, - title: Optional[str] = None, + title: str = '', *, - text: Optional[str] = None, + text: str = '', seconds_to_wait: Optional[int] = None, - exclude_title: Optional[str] = None, - exclude_text: Optional[str] = None, + exclude_title: str = '', + exclude_text: str = '', ) -> None: - args = [] - optional_args = (text, seconds_to_wait, exclude_title, exclude_text) - if title is not None: - args.append(title) - if any(optional_args): - for arg in optional_args: - args.append(str(arg) or '') - resp = await self._transport.function_call('WinClose', args=args) + args: list[str] + args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] + resp = await self._transport.function_call('AHKWinClose', args=args) resp.unpack() return None diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 8bb9475d..ccf05453 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -38,6 +38,7 @@ SyncIOProcess = subprocess.Popen[bytes] FunctionName = Literal[ + Literal['AHKWinExist'], Literal['ImageSearch'], Literal['PixelGetColor'], Literal['PixelSearch'], @@ -60,7 +61,7 @@ Literal['WinGetText'], Literal['WinActivate'], Literal['WinActivateBottom'], - Literal['WinClose'], + Literal['AHKWinClose'], Literal['WinHide'], Literal['WinKill'], Literal['WinMaximize'], @@ -211,6 +212,8 @@ async def init(self) -> None: # fmt: off @overload + async def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + @overload async def function_call(self, function_name: Literal['ImageSearch'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... @overload async def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @@ -255,7 +258,7 @@ async def function_call(self, function_name: Literal['WinActivate'], args: Optio @overload async def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinClose'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... @overload async def function_call(self, function_name: Literal['WinHide'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... @overload @@ -293,7 +296,7 @@ async def function_call(self, function_name: Literal['AHKWinMove'], args: Option @overload async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None) -> TupleResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetID'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['WinGetID'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload async def function_call(self, function_name: Literal['WinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index fea3754d..af2d2263 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -26,6 +26,9 @@ async def close(self) -> None: await self._engine.win_close(title=f'ahk_id {self._ahk_id}') return None + async def exists(self) -> bool: + return await self._engine.win_exists(title=f'ahk_id {self._ahk_id}') + class AsyncControl: def __init__(self, window: AsyncWindow, control_class: str): diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 4d67e215..a982e7db 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -13,6 +13,7 @@ from ..message import IntegerResponseMessage from ..message import is_winget_response_type +from ..message import NoValueResponseMessage from ..message import StringResponseMessage from ..message import WindowControlListResponseMessage from ..message import WindowIDListResponseMessage @@ -298,7 +299,7 @@ def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - def _win_get(self, subcommand_function: Literal['WinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + def _win_get(self, subcommand_function: Literal['WinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[StringResponseMessage, NoValueResponseMessage]: ... @overload def _win_get(self, subcommand_function: Literal['WinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... @overload @@ -340,21 +341,32 @@ def _win_get( IntegerResponseMessage, WindowIDListResponseMessage, WindowControlListResponseMessage, + NoValueResponseMessage, ]: args = [title, text, exclude_title, exclude_title, exclude_text] resp = self._transport.function_call(subcommand_function, args) - assert is_winget_response_type(resp) + assert is_winget_response_type(resp), f'Unexpected response: {resp!r}' return resp def win_get( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Window: + ) -> Union[Window, None]: resp = self._win_get( 'WinGetID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) win_id = resp.unpack() - return Window(engine=self, ahk_id=win_id) + if win_id is None: + return None + else: + return Window(engine=self, ahk_id=win_id) + + def win_exists( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> bool: + args = [title, text, exclude_title, exclude_text] + resp = self._transport.function_call('AHKWinExist', args) + return resp.unpack() def win_set(self, subcommand: str, *args: Any, blocking: bool = True) -> None: # TODO: type hint subcommand literals @@ -478,20 +490,15 @@ def show_traytip( def win_close( self, - title: Optional[str] = None, + title: str = '', *, - text: Optional[str] = None, + text: str = '', seconds_to_wait: Optional[int] = None, - exclude_title: Optional[str] = None, - exclude_text: Optional[str] = None, + exclude_title: str = '', + exclude_text: str = '', ) -> None: - args = [] - optional_args = (text, seconds_to_wait, exclude_title, exclude_text) - if title is not None: - args.append(title) - if any(optional_args): - for arg in optional_args: - args.append(str(arg) or '') - resp = self._transport.function_call('WinClose', args=args) + args: list[str] + args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] + resp = self._transport.function_call('AHKWinClose', args=args) resp.unpack() return None diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index dc8c80c9..047441a7 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -37,6 +37,7 @@ SyncIOProcess = subprocess.Popen[bytes] FunctionName = Literal[ + Literal['AHKWinExist'], Literal['ImageSearch'], Literal['PixelGetColor'], Literal['PixelSearch'], @@ -59,7 +60,7 @@ Literal['WinGetText'], Literal['WinActivate'], Literal['WinActivateBottom'], - Literal['WinClose'], + Literal['AHKWinClose'], Literal['WinHide'], Literal['WinKill'], Literal['WinMaximize'], @@ -201,6 +202,8 @@ def init(self) -> None: # fmt: off @overload + def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + @overload def function_call(self, function_name: Literal['ImageSearch'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... @overload def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @@ -245,7 +248,7 @@ def function_call(self, function_name: Literal['WinActivate'], args: Optional[li @overload def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinClose'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... @overload def function_call(self, function_name: Literal['WinHide'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... @overload @@ -283,7 +286,7 @@ def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[lis @overload def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None) -> TupleResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetID'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['WinGetID'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload def function_call(self, function_name: Literal['WinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @overload diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 099761d1..0caa436f 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -26,6 +26,9 @@ def close(self) -> None: self._engine.win_close(title=f'ahk_id {self._ahk_id}') return None + def exists(self) -> bool: + return self._engine.win_exists(title=f'ahk_id {self._ahk_id}') + class SyncControl: def __init__(self, window: Window, control_class: str): self.window: Window = window diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 84f8d2c4..9a2bda63 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -26,19 +26,46 @@ FormatNoValueResponse() { return FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) } +AHKWinExist(ByRef command) { + global BOOLEANRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + if WinExist(title, text, extitle, extext) { + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + } else { + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + } + return resp +} + +AHKWinClose(ByRef command) { + title := command[2] + text := command[3] + secondstowait := command[4] + extitle := command[5] + extext := command[6] + WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% + return FormatNoValueResponse() +} WinGetID(ByRef command) { global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] extext := command[5] WinGet, output, ID, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + if (output = 0) { + response := FormatNoValueResponse() + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + return response } + WinGetIDLast(ByRef command) { global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE @@ -474,15 +501,6 @@ WinActivateBottom(ByRef command) { } } -WinClose(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinClose,% title - } else { - secondstowait := command[3] - WinClose, %title%, %secondstowait% - } -} WinHide(ByRef command) { title := command[2] diff --git a/ahk/message.py b/ahk/message.py index 50b3bd0c..a2b29282 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -192,16 +192,6 @@ def unpack(self) -> None: return None -class WindowControlListResponseMessage(ResponseMessage): - type = 'windowcontrollist' - - def unpack(self) -> Tuple[str, Tuple[str, ...]]: - s = self._raw_content.decode(encoding='utf-8') - val = ast.literal_eval(s) - assert is_window_control_list_response(val) - return val - - class AHKExecutionException(Exception): pass @@ -214,6 +204,16 @@ def unpack(self) -> NoReturn: raise AHKExecutionException(s) +class WindowControlListResponseMessage(ResponseMessage): + type = 'windowcontrollist' + + def unpack(self) -> Tuple[str, Tuple[str, ...]]: + s = self._raw_content.decode(encoding='utf-8') + val = ast.literal_eval(s) + assert is_window_control_list_response(val) + return val + + T_RequestMessageType = TypeVar('T_RequestMessageType', bound='RequestMessage') diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 2e15b613..0b1f58a6 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -9,18 +9,28 @@ 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 test_close(self): -# await self.win.close() -# await asyncio.sleep(0.2) -# self.assertFalse(await self.win.exists()) -# self.assertFalse(await self.win.exist) +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: + await self.win.close() + except Exception: + pass + self.ahk._transport._proc.kill() + + 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() + await asyncio.sleep(0.2) + self.assertFalse(await self.win.exists()) diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index aa785360..760f08f3 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -9,18 +9,28 @@ from ahk import Window -# 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 test_close(self): -# await self.win.close() -# await asyncio.sleep(0.2) -# self.assertFalse(await self.win.exists()) -# self.assertFalse(await self.win.exist) +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.win.close() + except Exception: + pass + self.ahk._transport._proc.kill() + + 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() + asyncio.sleep(0.2) + self.assertFalse(self.win.exists()) From df6691fb4f300b7c80094e9eb90dc0abd501beb1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 07:18:59 -0700 Subject: [PATCH 205/588] fix window list --- ahk/daemon.ahk | 2 +- ahk/message.py | 1 + tests/_async/test_screen.py | 1 - tests/_sync/test_screen.py | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 9a2bda63..113f3077 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -610,7 +610,7 @@ WindowList(ByRef command) { Loop %windows% { id := windows%A_Index% - r := id . "`," + r .= id . "`," } resp := FormatResponse(WINDOWIDLISTRESPONSEMESSAGE, r) return resp diff --git a/ahk/message.py b/ahk/message.py index a2b29282..c0a307f6 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -181,6 +181,7 @@ class WindowIDListResponseMessage(ResponseMessage): def unpack(self) -> list[str]: s = self._raw_content.decode(encoding='utf-8') + s.rstrip(',') return s.split(',') diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 8d66b967..22e35b41 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -22,7 +22,6 @@ async def asyncTearDown(self): for win in await self.ahk.list_windows(): if win not in self.before_windows: await win.close() - break self.ahk._transport._proc.kill() # diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index ff109100..07693cdd 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -22,7 +22,6 @@ def tearDown(self): for win in self.ahk.list_windows(): if win not in self.before_windows: win.close() - break self.ahk._transport._proc.kill() # From 912ecd56e63861d60880ca97596bd21d16cbbcb2 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 07:30:26 -0700 Subject: [PATCH 206/588] try appveyor --- .appveyor.yml | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..d8b80cf1 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,47 @@ +version: '0.1.{build}' + +environment: + AHK_DEBUG: true + +install: + - cmd: | + py -3.10 -m venv venv + call venv\Scripts\activate.bat + python -m pip install --upgrade pip + python -m pip install --upgrade -r requirements-dev.txt + python -m pip install "ahk-binary" + call deactivate + +#build_script: +# - cmd: .\ci\build.bat + +#artifacts: +# - name: dist +# path: dist\* + +test_script: + - ps: | + .\venv\Scripts\activate.ps1 + coverage run -a -m pytest .\tests\unittests --junitxml=reports\pytestresults.xml + if ($LastExitCode -ne 0) { + $failure = 1 + } else { + $failure = 0 + } + 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 } + + + + + +on_finish: + - cmd: | + venv\Scripts\activate.bat + python -m pip install coveralls + IF DEFINED COVERALLS_REPO_TOKEN (python -m coveralls) ELSE (echo skipping coveralls report for external pr) From eaa080d03a6c78a4523ea7fdab8bfc7e8fc8e1a6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 07:33:41 -0700 Subject: [PATCH 207/588] try appveyor --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index d8b80cf1..c47ac8b4 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,7 +5,7 @@ environment: install: - cmd: | - py -3.10 -m venv venv + py -3.9 -m venv venv call venv\Scripts\activate.bat python -m pip install --upgrade pip python -m pip install --upgrade -r requirements-dev.txt From 9f83c562f2f7bee5818e209a5d3fa19426c2b276 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 07:40:14 -0700 Subject: [PATCH 208/588] specify image --- .appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.appveyor.yml b/.appveyor.yml index c47ac8b4..b9bce959 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,3 +1,4 @@ +image: Visual Studio 2022 version: '0.1.{build}' environment: From 028b38c0b287b8eb0c66c5cac452f384dcac64ef Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 07:43:18 -0700 Subject: [PATCH 209/588] specify build --- .appveyor.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index b9bce959..804b9b66 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,8 +13,9 @@ install: python -m pip install "ahk-binary" call deactivate -#build_script: -# - cmd: .\ci\build.bat +build_script: + - cmd: | + py -3.9 setup.py sdist #artifacts: # - name: dist From ad738f25a5ec7671d27d1e2156bd29c74eecf8ae Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 07:45:50 -0700 Subject: [PATCH 210/588] build venv --- .appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index 804b9b66..0ff4329d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -15,7 +15,9 @@ install: build_script: - cmd: | + call venv\Scripts\activate.bat py -3.9 setup.py sdist + call deactivate #artifacts: # - name: dist From 062b4cba51e11ea5a872c71c2e375db2eadec050 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 07:47:59 -0700 Subject: [PATCH 211/588] use venv for build --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 0ff4329d..cc35e7ff 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -16,7 +16,7 @@ install: build_script: - cmd: | call venv\Scripts\activate.bat - py -3.9 setup.py sdist + python setup.py sdist call deactivate #artifacts: From 1089076d04cd3bde40aa2c53338676fa5dab7899 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 07:50:45 -0700 Subject: [PATCH 212/588] fix test run --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index cc35e7ff..c76781b8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -26,7 +26,7 @@ build_script: test_script: - ps: | .\venv\Scripts\activate.ps1 - coverage run -a -m pytest .\tests\unittests --junitxml=reports\pytestresults.xml + coverage run -m pytest --junitxml=reports\pytestresults.xml if ($LastExitCode -ne 0) { $failure = 1 } else { From 3ca89d583eaed2bf578267b38dad825a09e0ad98 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 07:54:28 -0700 Subject: [PATCH 213/588] use 3.10 --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index c76781b8..5503fc74 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,7 +6,7 @@ environment: install: - cmd: | - py -3.9 -m venv venv + py -3.10 -m venv venv call venv\Scripts\activate.bat python -m pip install --upgrade pip python -m pip install --upgrade -r requirements-dev.txt From c077e3f6e3459b91583bb49c7ebdf839dc91430a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 08:03:31 -0700 Subject: [PATCH 214/588] remove appveoyr... debugging --- .appveyor.yml | 51 ------------------------------------- .github/workflows/test.yaml | 2 +- tests/_async/test_screen.py | 5 ++++ tests/_sync/test_screen.py | 5 ++++ 4 files changed, 11 insertions(+), 52 deletions(-) delete mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 5503fc74..00000000 --- a/.appveyor.yml +++ /dev/null @@ -1,51 +0,0 @@ -image: Visual Studio 2022 -version: '0.1.{build}' - -environment: - AHK_DEBUG: true - -install: - - cmd: | - py -3.10 -m venv venv - call venv\Scripts\activate.bat - python -m pip install --upgrade pip - python -m pip install --upgrade -r requirements-dev.txt - python -m pip install "ahk-binary" - call deactivate - -build_script: - - cmd: | - call venv\Scripts\activate.bat - python setup.py sdist - call deactivate - -#artifacts: -# - name: dist -# path: dist\* - -test_script: - - ps: | - .\venv\Scripts\activate.ps1 - coverage run -m pytest --junitxml=reports\pytestresults.xml - if ($LastExitCode -ne 0) { - $failure = 1 - } else { - $failure = 0 - } - 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 } - - - - - -on_finish: - - cmd: | - venv\Scripts\activate.bat - python -m pip install coveralls - IF DEFINED COVERALLS_REPO_TOKEN (python -m coveralls) ELSE (echo skipping coveralls report for external pr) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2d874324..6d0d5994 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -19,7 +19,7 @@ jobs: python -m pip install ahk-binary - name: Test with coverage/pytest run: | - coverage run -m pytest + coverage run -m pytest --capture=no - name: Coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 22e35b41..9380f691 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -9,19 +9,24 @@ class TestScreen(IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: + print('setting up') self.ahk = AsyncAHK() self.before_windows = await self.ahk.list_windows() im = Image.new('RGB', (20, 20)) for coord in product(range(20), range(20)): im.putpixel(coord, (255, 0, 0)) self.im = im + print('showing im') im.show() time.sleep(2) async def asyncTearDown(self): + print('tearing down') for win in await self.ahk.list_windows(): if win not in self.before_windows: + print('closing', win) await win.close() + print('killing proc') self.ahk._transport._proc.kill() # diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index 07693cdd..0979b77e 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -9,19 +9,24 @@ class TestScreen(TestCase): def setUp(self) -> None: + print('setting up') self.ahk = AHK() self.before_windows = self.ahk.list_windows() im = Image.new('RGB', (20, 20)) for coord in product(range(20), range(20)): im.putpixel(coord, (255, 0, 0)) self.im = im + print('showing im') im.show() time.sleep(2) def tearDown(self): + print('tearing down') for win in self.ahk.list_windows(): if win not in self.before_windows: + print('closing', win) win.close() + print('killing proc') self.ahk._transport._proc.kill() # From c741cedb20cc88dbaa2be2d9229aecdbb27cda0e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 08:25:19 -0700 Subject: [PATCH 215/588] try unittest --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6d0d5994..ef0de2e8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -19,7 +19,7 @@ jobs: python -m pip install ahk-binary - name: Test with coverage/pytest run: | - coverage run -m pytest --capture=no + coverage run -m unittest - name: Coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 3625d447cf6392428e1c1b901aa53c6088c91d64 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 08:36:07 -0700 Subject: [PATCH 216/588] add PYTHONUNBUFFERED --- .github/workflows/test.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ef0de2e8..233209fe 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -3,7 +3,7 @@ on: [ push, pull_request ] jobs: build: runs-on: windows-latest - timeout-minutes: 10 + timeout-minutes: 5 steps: - name: Checkout uses: actions/checkout@v2 @@ -18,6 +18,8 @@ jobs: python -m pip install -r requirements-dev.txt python -m pip install ahk-binary - name: Test with coverage/pytest + env: + PYTHONUNBUFFERED: "1" run: | coverage run -m unittest - name: Coveralls From 6f65f95c3df4366a49d6164057767238f6dc757f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 08:36:31 -0700 Subject: [PATCH 217/588] back to pytest --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 233209fe..d23c3b68 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,7 +21,7 @@ jobs: env: PYTHONUNBUFFERED: "1" run: | - coverage run -m unittest + coverage run -m pytest -s - name: Coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 5bee1ea4c13a5c332ed9340b492b41e0fdde2660 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 08:39:53 -0700 Subject: [PATCH 218/588] more debug output --- tests/_async/test_screen.py | 1 + tests/_sync/test_screen.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 9380f691..0e25f986 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -18,6 +18,7 @@ async def asyncSetUp(self) -> None: self.im = im print('showing im') im.show() + print('shown') time.sleep(2) async def asyncTearDown(self): diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index 0979b77e..1b879d1b 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -18,6 +18,7 @@ def setUp(self) -> None: self.im = im print('showing im') im.show() + print('shown') time.sleep(2) def tearDown(self): From 56e51f7ce4df7ab794155927006265400e48d04d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 08:49:59 -0700 Subject: [PATCH 219/588] more debug output --- tests/_async/test_screen.py | 13 +++++-------- tests/_sync/test_screen.py | 13 +++++-------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 0e25f986..1ed72fe9 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -1,4 +1,4 @@ -import time +import asyncio from itertools import product from unittest import IsolatedAsyncioTestCase @@ -12,14 +12,10 @@ async def asyncSetUp(self) -> None: print('setting up') self.ahk = AsyncAHK() self.before_windows = await self.ahk.list_windows() - im = Image.new('RGB', (20, 20)) + self.im = Image.new('RGB', (20, 20)) for coord in product(range(20), range(20)): - im.putpixel(coord, (255, 0, 0)) - self.im = im - print('showing im') - im.show() - print('shown') - time.sleep(2) + self.im.putpixel(coord, (255, 0, 0)) + await asyncio.sleep(2) async def asyncTearDown(self): print('tearing down') @@ -36,6 +32,7 @@ async def asyncTearDown(self): # self.assertIsNotNone(result) async def test_image_search(self): + self.im.show() self.im.save('testimage.png') position = await self.ahk.image_search('testimage.png') assert isinstance(position, tuple) diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index 1b879d1b..2461923a 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -1,4 +1,4 @@ -import time +import asyncio from itertools import product from unittest import TestCase @@ -12,14 +12,10 @@ def setUp(self) -> None: print('setting up') self.ahk = AHK() self.before_windows = self.ahk.list_windows() - im = Image.new('RGB', (20, 20)) + self.im = Image.new('RGB', (20, 20)) for coord in product(range(20), range(20)): - im.putpixel(coord, (255, 0, 0)) - self.im = im - print('showing im') - im.show() - print('shown') - time.sleep(2) + self.im.putpixel(coord, (255, 0, 0)) + asyncio.sleep(2) def tearDown(self): print('tearing down') @@ -36,6 +32,7 @@ def tearDown(self): # self.assertIsNotNone(result) def test_image_search(self): + self.im.show() self.im.save('testimage.png') position = self.ahk.image_search('testimage.png') assert isinstance(position, tuple) From cabb6655065ff4bdb77f50e237c36a3e0aaa26f9 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 08:56:32 -0700 Subject: [PATCH 220/588] skip screen test in CI --- tests/_async/test_screen.py | 4 ++++ tests/_sync/test_screen.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 1ed72fe9..63859c44 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -1,4 +1,5 @@ import asyncio +import os from itertools import product from unittest import IsolatedAsyncioTestCase @@ -32,6 +33,9 @@ async def asyncTearDown(self): # self.assertIsNotNone(result) async def test_image_search(self): + if os.environ.get('CI'): + self.skipTest('Does not work in GitHub Actions') + return self.im.show() self.im.save('testimage.png') position = await self.ahk.image_search('testimage.png') diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index 2461923a..3c554436 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -1,4 +1,5 @@ import asyncio +import os from itertools import product from unittest import TestCase @@ -32,6 +33,9 @@ def tearDown(self): # self.assertIsNotNone(result) def test_image_search(self): + if os.environ.get('CI'): + self.skipTest('Does not work in GitHub Actions') + return self.im.show() self.im.save('testimage.png') position = self.ahk.image_search('testimage.png') From b4475ff829d3c6e9f712e48c1583afe0c6f1f1d2 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 10:17:25 -0700 Subject: [PATCH 221/588] more window methods --- ahk/_async/engine.py | 81 +++++++++++++++++++++++++++++++++-- ahk/_async/transport.py | 6 +-- ahk/_async/window.py | 40 +++++++++++++++++ ahk/_sync/engine.py | 81 +++++++++++++++++++++++++++++++++-- ahk/_sync/transport.py | 6 +-- ahk/_sync/window.py | 41 ++++++++++++++++++ ahk/daemon.ahk | 85 +++++++++++++++++++++++-------------- ahk/message.py | 10 ++--- tests/_async/test_window.py | 23 ++++++++++ tests/_sync/test_window.py | 23 ++++++++++ 10 files changed, 346 insertions(+), 50 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 502b1683..49904fe8 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -303,11 +303,11 @@ async def _win_get(self, subcommand_function: Literal['WinGetID'], /, title: str @overload async def _win_get(self, subcommand_function: Literal['WinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['WinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['WinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['WinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload async def _win_get(self, subcommand_function: Literal['WinGetCount'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... @overload @@ -361,6 +361,81 @@ async def win_get( else: return AsyncWindow(engine=self, ahk_id=win_id) + async def win_get_idlast( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[AsyncWindow, None]: + resp = await self._win_get( + 'WinGetIDLast', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + win_id = resp.unpack() + if win_id is None: + return None + else: + return AsyncWindow(engine=self, ahk_id=win_id) + + async def win_get_pid( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[int, None]: + resp = await self._win_get( + 'WinGetPID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + pid = resp.unpack() + if pid is None: + return None + else: + return pid + + async def win_get_process_name( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[str, None]: + resp = await self._win_get( + 'WinGetProcessName', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + process_name = resp.unpack() + if process_name is None: + return None + else: + return process_name + + async def win_get_process_path( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[str, None]: + resp = await self._win_get( + 'WinGetProcessPath', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + process_path = resp.unpack() + if process_path is None: + return None + else: + return process_path + + async def win_get_count( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> int: + resp = await self._win_get( + 'WinGetCount', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + return resp.unpack() + + async def win_get_minmax( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[Literal[0], Literal[1], Literal[-1], None]: + + resp = await self._win_get( + 'WinGetMinMax', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + val = resp.unpack() + if val is None: + return None + if val == -1: + return -1 + elif val == 0: + return 0 + elif val == 1: + return 1 + else: + raise ValueError(f'Unexpected value for minmax: {val!r}') + async def win_exists( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> bool: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index ccf05453..9742a9f1 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -300,11 +300,11 @@ async def function_call(self, function_name: Literal['WinGetID'], args: Optional @overload async def function_call(self, function_name: Literal['WinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetPID'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['WinGetPID'], args: Optional[list[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... @overload - async def function_call(self, function_name: Literal['WinGetProcessName'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['WinGetProcessName'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - async def function_call(self, function_name: Literal['WinGetProcessPath'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['WinGetProcessPath'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload async def function_call(self, function_name: Literal['WinGetCount'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index af2d2263..6ee65452 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -1,14 +1,22 @@ from __future__ import annotations +from typing import Literal from typing import TYPE_CHECKING +from typing import Union if TYPE_CHECKING: from .engine import AsyncAHK +class WindowNotFoundException(Exception): + ... + + class AsyncWindow: def __init__(self, engine: AsyncAHK, ahk_id: str): self._engine: AsyncAHK = engine + if not ahk_id: + raise ValueError(f'Invalid ahk_id: {ahk_id!r}') self._ahk_id: str = ahk_id def __repr__(self) -> str: @@ -29,6 +37,38 @@ async def close(self) -> None: async def exists(self) -> bool: return await self._engine.win_exists(title=f'ahk_id {self._ahk_id}') + async def get_pid(self) -> int: + pid = await self._engine.win_get_pid(title=f'ahk_id {self._ahk_id}') + 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 + + async def get_process_name(self) -> str: + name = await self._engine.win_get_process_name(title=f'ahk_id {self._ahk_id}') + 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 + + async def get_process_path(self) -> str: + path = await self._engine.win_get_process_path(title=f'ahk_id {self._ahk_id}') + 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 + + async def get_minmax(self) -> Union[Literal[0], Literal[1], Literal[-1]]: + minmax = await self._engine.win_get_minmax(title=f'ahk_id {self._ahk_id}') + 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 + class AsyncControl: def __init__(self, window: AsyncWindow, control_class: str): diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index a982e7db..130cd1c5 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -303,11 +303,11 @@ def _win_get(self, subcommand_function: Literal['WinGetID'], /, title: str = '', @overload def _win_get(self, subcommand_function: Literal['WinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + def _win_get(self, subcommand_function: Literal['WinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + def _win_get(self, subcommand_function: Literal['WinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + def _win_get(self, subcommand_function: Literal['WinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload def _win_get(self, subcommand_function: Literal['WinGetCount'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... @overload @@ -361,6 +361,81 @@ def win_get( else: return Window(engine=self, ahk_id=win_id) + def win_get_idlast( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[Window, None]: + resp = self._win_get( + 'WinGetIDLast', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + win_id = resp.unpack() + if win_id is None: + return None + else: + return Window(engine=self, ahk_id=win_id) + + def win_get_pid( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[int, None]: + resp = self._win_get( + 'WinGetPID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + pid = resp.unpack() + if pid is None: + return None + else: + return pid + + def win_get_process_name( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[str, None]: + resp = self._win_get( + 'WinGetProcessName', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + process_name = resp.unpack() + if process_name is None: + return None + else: + return process_name + + def win_get_process_path( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[str, None]: + resp = self._win_get( + 'WinGetProcessPath', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + process_path = resp.unpack() + if process_path is None: + return None + else: + return process_path + + def win_get_count( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> int: + resp = self._win_get( + 'WinGetCount', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + return resp.unpack() + + def win_get_minmax( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[Literal[0], Literal[1], Literal[-1], None]: + + resp = self._win_get( + 'WinGetMinMax', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + val = resp.unpack() + if val is None: + return None + if val == -1: + return -1 + elif val == 0: + return 0 + elif val == 1: + return 1 + else: + raise ValueError(f'Unexpected value for minmax: {val!r}') + def win_exists( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> bool: diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 047441a7..233dff8e 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -290,11 +290,11 @@ def function_call(self, function_name: Literal['WinGetID'], args: Optional[list[ @overload def function_call(self, function_name: Literal['WinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetPID'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['WinGetPID'], args: Optional[list[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... @overload - def function_call(self, function_name: Literal['WinGetProcessName'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['WinGetProcessName'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - def function_call(self, function_name: Literal['WinGetProcessPath'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['WinGetProcessPath'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload def function_call(self, function_name: Literal['WinGetCount'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... @overload diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 0caa436f..13052059 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -1,14 +1,22 @@ from __future__ import annotations +from typing import Literal from typing import TYPE_CHECKING +from typing import Union if TYPE_CHECKING: from .engine import AHK +class WindowNotFoundException(Exception): + ... + + class Window: def __init__(self, engine: AHK, ahk_id: str): self._engine: AHK = engine + if not ahk_id: + raise ValueError(f'Invalid ahk_id: {ahk_id!r}') self._ahk_id: str = ahk_id def __repr__(self) -> str: @@ -29,6 +37,39 @@ def close(self) -> None: def exists(self) -> bool: return self._engine.win_exists(title=f'ahk_id {self._ahk_id}') + def get_pid(self) -> int: + pid = self._engine.win_get_pid(title=f'ahk_id {self._ahk_id}') + 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 + + def get_process_name(self) -> str: + name = self._engine.win_get_process_name(title=f'ahk_id {self._ahk_id}') + 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 + + def get_process_path(self) -> str: + path = self._engine.win_get_process_path(title=f'ahk_id {self._ahk_id}') + 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 + + def get_minmax(self) -> Union[Literal[0], Literal[1], Literal[-1]]: + minmax = self._engine.win_get_minmax(title=f'ahk_id {self._ahk_id}') + 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 + + class SyncControl: def __init__(self, window: Window, control_class: str): self.window: Window = window diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 113f3077..b8cc0036 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -57,7 +57,7 @@ WinGetID(ByRef command) { extitle := command[4] extext := command[5] WinGet, output, ID, %title%, %text%, %extitle%, %extext% - if (output = 0) { + if (output = 0 || output = "") { response := FormatNoValueResponse() } else { response := FormatResponse(STRINGRESPONSEMESSAGE, output) @@ -68,88 +68,111 @@ WinGetID(ByRef command) { WinGetIDLast(ByRef command) { global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] extext := command[5] WinGet, output, IDLast, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) + } return response } + + WinGetPID(ByRef command) { - global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] extext := command[5] WinGet, output, PID, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + } return response } + + WinGetProcessName(ByRef command) { global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] extext := command[5] WinGet, output, ProcessName, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) + } return response } + WinGetProcessPath(ByRef command) { global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] extext := command[5] WinGet, output, ProcessPath, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) + } return response } + + WinGetCount(ByRef command) { - global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] extext := command[5] WinGet, output, Count, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) - return response -} -WinGetList(ByRef command) { - global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - WinGet, output, List, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + if (output = 0) { + response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + } else { + response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + } return response } + +;WinGetList(ByRef command) { +; global STRINGRESPONSEMESSAGE +; global INTEGERRESPONSEMESSAGE +; global NOVALUERESPONSEMESSAGE +; title := command[2] +; text := command[3] +; extitle := command[4] +; extext := command[5] +; WinGet, output, List, %title%, %text%, %extitle%, %extext% +; response := FormatResponse(NOVALUERESPONSEMESSAGE, output) +; return response +;} + + WinGetMinMax(ByRef command) { - global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] extext := command[5] WinGet, output, MinMax, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + if (output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + } return response } + WinGetControlList(ByRef command) { global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE diff --git a/ahk/message.py b/ahk/message.py index c0a307f6..d60736be 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -1,22 +1,16 @@ import ast -import io import itertools import string -import typing -from abc import ABC from abc import abstractmethod from typing import Any from typing import cast from typing import Generator -from typing import Generic -from typing import Literal 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 TypedDict from typing import TypeGuard from typing import TypeVar from typing import Union @@ -66,6 +60,8 @@ def is_winget_response_type( return True elif isinstance(obj, WindowControlListResponseMessage): return True + elif isinstance(obj, NoValueResponseMessage): + return True else: return False @@ -181,7 +177,7 @@ class WindowIDListResponseMessage(ResponseMessage): def unpack(self) -> list[str]: s = self._raw_content.decode(encoding='utf-8') - s.rstrip(',') + s = s.rstrip(',') return s.split(',') diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 0b1f58a6..5580dd12 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -34,3 +34,26 @@ async def test_close(self): await self.win.close() await asyncio.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 diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 760f08f3..04545bf3 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -34,3 +34,26 @@ def test_close(self): self.win.close() asyncio.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 From a3afd4a427a6625e14c1fadd78fab20407483c6e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Jul 2022 20:16:04 +0000 Subject: [PATCH 222/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.7.0 → v3.8.1](https://github.com/asottile/reorder_python_imports/compare/v3.7.0...v3.8.1) - [github.com/pre-commit/mirrors-mypy: v0.931 → v0.961](https://github.com/pre-commit/mirrors-mypy/compare/v0.931...v0.961) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c2a3d4c5..9f92dc01 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,12 +33,12 @@ repos: - "120" exclude: ^(ahk/_sync/.*\.py) - repo: https://github.com/asottile/reorder_python_imports - rev: v3.7.0 + rev: v3.8.1 hooks: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.931' + rev: 'v0.961' hooks: - id: mypy args: From 75fa001288fae03e54c43c38e299e49bd169ff5e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 14:10:26 -0700 Subject: [PATCH 223/588] controls --- ahk/_async/engine.py | 92 +++++++++++++++++++++++++---------------- ahk/_async/transport.py | 56 ++++++++++++------------- ahk/_async/window.py | 6 ++- ahk/_sync/engine.py | 92 +++++++++++++++++++++++++---------------- ahk/_sync/transport.py | 56 ++++++++++++------------- ahk/_sync/window.py | 6 ++- ahk/daemon.ahk | 88 +++++++++++++++++++++++++-------------- ahk/message.py | 14 +++++-- 8 files changed, 246 insertions(+), 164 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 49904fe8..a53a7545 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -9,6 +9,7 @@ from typing import Sequence from typing import Tuple from typing import Type +from typing import TYPE_CHECKING from typing import Union from ..message import IntegerResponseMessage @@ -30,20 +31,20 @@ class FutureResult: CoordModeTargets = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] CoordModeRelativeTo = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] WinGetFunctions = Literal[ - Literal['WinGetID'], - Literal['WinGetIDLast'], - Literal['WinGetPID'], - Literal['WinGetProcessName'], - Literal['WinGetProcessPath'], - Literal['WinGetCount'], - Literal['WinGetList'], - Literal['WinGetMinMax'], - Literal['WinGetControlList'], - Literal['WinGetControlListHwnd'], - Literal['WinGetTransparent'], - Literal['WinGetTransColor'], - Literal['WinGetStyle'], - Literal['WinGetExStyle'], + Literal['AHKWinGetID'], + Literal['AHKWinGetIDLast'], + Literal['AHKWinGetPID'], + Literal['AHKWinGetProcessName'], + Literal['AHKWinGetProcessPath'], + Literal['AHKWinGetCount'], + Literal['AHKWinGetList'], + Literal['AHKWinGetMinMax'], + Literal['AHKWinGetControlList'], + Literal['AHKWinGetControlListHwnd'], + Literal['AHKWinGetTransparent'], + Literal['AHKWinGetTransColor'], + Literal['AHKWinGetStyle'], + Literal['AHKWinGetExStyle'], ] CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] @@ -299,33 +300,33 @@ async def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - async def _win_get(self, subcommand_function: Literal['WinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[StringResponseMessage, NoValueResponseMessage]: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[StringResponseMessage, NoValueResponseMessage]: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetCount'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetCount'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowIDListResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowIDListResponseMessage: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetMinMax'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetMinMax'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetControlList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetControlList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetControlListHwnd'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetControlListHwnd'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetTransparent'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetTransparent'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetTransColor'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetTransColor'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... @overload - async def _win_get(self, subcommand_function: Literal['WinGetExStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def _win_get(self, subcommand_function: Literal['AHKWinGetExStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... # fmt: on async def _win_get( @@ -346,14 +347,15 @@ async def _win_get( args = [title, text, exclude_title, exclude_title, exclude_text] resp = await self._transport.function_call(subcommand_function, args) - assert is_winget_response_type(resp), f'Unexpected response: {resp!r}' + if TYPE_CHECKING: + assert is_winget_response_type(resp), f'Unexpected response: {resp!r}' return resp async def win_get( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> Union[AsyncWindow, None]: resp = await self._win_get( - 'WinGetID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) win_id = resp.unpack() if win_id is None: @@ -365,7 +367,7 @@ async def win_get_idlast( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> Union[AsyncWindow, None]: resp = await self._win_get( - 'WinGetIDLast', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetIDLast', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) win_id = resp.unpack() if win_id is None: @@ -377,7 +379,7 @@ async def win_get_pid( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> Union[int, None]: resp = await self._win_get( - 'WinGetPID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetPID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) pid = resp.unpack() if pid is None: @@ -389,7 +391,7 @@ async def win_get_process_name( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> Union[str, None]: resp = await self._win_get( - 'WinGetProcessName', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetProcessName', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) process_name = resp.unpack() if process_name is None: @@ -401,7 +403,7 @@ async def win_get_process_path( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> Union[str, None]: resp = await self._win_get( - 'WinGetProcessPath', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetProcessPath', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) process_path = resp.unpack() if process_path is None: @@ -413,7 +415,7 @@ async def win_get_count( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> int: resp = await self._win_get( - 'WinGetCount', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetCount', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) return resp.unpack() @@ -422,7 +424,7 @@ async def win_get_minmax( ) -> Union[Literal[0], Literal[1], Literal[-1], None]: resp = await self._win_get( - 'WinGetMinMax', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetMinMax', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) val = resp.unpack() if val is None: @@ -436,6 +438,24 @@ async def win_get_minmax( else: raise ValueError(f'Unexpected value for minmax: {val!r}') + async def win_get_control_list( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[Sequence[AsyncControl], None]: + resp = await self._win_get( + 'AHKWinGetControlList', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + val = resp.unpack() + if val is None: + return None + ahkid, controls = val + window = AsyncWindow(engine=self, ahk_id=ahkid) + ret = [] + for control in controls: + hwnd, classname = control + ctrl = AsyncControl(window=window, hwnd=hwnd, control_class=classname) + ret.append(ctrl) + return ret + async def win_exists( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> bool: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 9742a9f1..a951fac7 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -80,20 +80,20 @@ Literal['WinClick'], Literal['AHKWinMove'], Literal['AHKWinGetPos'], - Literal['WinGetID'], - Literal['WinGetIDLast'], - Literal['WinGetPID'], - Literal['WinGetProcessName'], - Literal['WinGetProcessPath'], - Literal['WinGetCount'], - Literal['WinGetList'], - Literal['WinGetMinMax'], - Literal['WinGetControlList'], - Literal['WinGetControlListHwnd'], - Literal['WinGetTransparent'], - Literal['WinGetTransColor'], - Literal['WinGetStyle'], - Literal['WinGetExStyle'], + Literal['AHKWinGetID'], + Literal['AHKWinGetIDLast'], + Literal['AHKWinGetPID'], + Literal['AHKWinGetProcessName'], + Literal['AHKWinGetProcessPath'], + Literal['AHKWinGetCount'], + Literal['AHKWinGetList'], + Literal['AHKWinGetMinMax'], + Literal['AHKWinGetControlList'], + Literal['AHKWinGetControlListHwnd'], + Literal['AHKWinGetTransparent'], + Literal['AHKWinGetTransColor'], + Literal['AHKWinGetStyle'], + Literal['AHKWinGetExStyle'], ] @@ -296,33 +296,33 @@ async def function_call(self, function_name: Literal['AHKWinMove'], args: Option @overload async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None) -> TupleResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetID'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - async def function_call(self, function_name: Literal['WinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetPID'], args: Optional[list[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[list[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... @overload - async def function_call(self, function_name: Literal['WinGetProcessName'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - async def function_call(self, function_name: Literal['WinGetProcessPath'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - async def function_call(self, function_name: Literal['WinGetCount'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetMinMax'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetControlList'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetControlListHwnd'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetTransparent'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetTransColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetExStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 6ee65452..81ecc6da 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -71,6 +71,10 @@ async def get_minmax(self) -> Union[Literal[0], Literal[1], Literal[-1]]: class AsyncControl: - def __init__(self, window: AsyncWindow, control_class: str): + def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): self.window: AsyncWindow = window + self.hwnd: str = hwnd self.control_class: str = control_class + + 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/_sync/engine.py b/ahk/_sync/engine.py index 130cd1c5..738434c4 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -9,6 +9,7 @@ from typing import Sequence from typing import Tuple from typing import Type +from typing import TYPE_CHECKING from typing import Union from ..message import IntegerResponseMessage @@ -30,20 +31,20 @@ class FutureResult: CoordModeTargets = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] CoordModeRelativeTo = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] WinGetFunctions = Literal[ - Literal['WinGetID'], - Literal['WinGetIDLast'], - Literal['WinGetPID'], - Literal['WinGetProcessName'], - Literal['WinGetProcessPath'], - Literal['WinGetCount'], - Literal['WinGetList'], - Literal['WinGetMinMax'], - Literal['WinGetControlList'], - Literal['WinGetControlListHwnd'], - Literal['WinGetTransparent'], - Literal['WinGetTransColor'], - Literal['WinGetStyle'], - Literal['WinGetExStyle'], + Literal['AHKWinGetID'], + Literal['AHKWinGetIDLast'], + Literal['AHKWinGetPID'], + Literal['AHKWinGetProcessName'], + Literal['AHKWinGetProcessPath'], + Literal['AHKWinGetCount'], + Literal['AHKWinGetList'], + Literal['AHKWinGetMinMax'], + Literal['AHKWinGetControlList'], + Literal['AHKWinGetControlListHwnd'], + Literal['AHKWinGetTransparent'], + Literal['AHKWinGetTransColor'], + Literal['AHKWinGetStyle'], + Literal['AHKWinGetExStyle'], ] CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] @@ -299,33 +300,33 @@ def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - def _win_get(self, subcommand_function: Literal['WinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[StringResponseMessage, NoValueResponseMessage]: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[StringResponseMessage, NoValueResponseMessage]: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetCount'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetCount'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowIDListResponseMessage: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowIDListResponseMessage: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetMinMax'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetMinMax'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetControlList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetControlList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetControlListHwnd'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetControlListHwnd'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetTransparent'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetTransparent'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetTransColor'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetTransColor'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... @overload - def _win_get(self, subcommand_function: Literal['WinGetExStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + def _win_get(self, subcommand_function: Literal['AHKWinGetExStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... # fmt: on def _win_get( @@ -346,14 +347,15 @@ def _win_get( args = [title, text, exclude_title, exclude_title, exclude_text] resp = self._transport.function_call(subcommand_function, args) - assert is_winget_response_type(resp), f'Unexpected response: {resp!r}' + if TYPE_CHECKING: + assert is_winget_response_type(resp), f'Unexpected response: {resp!r}' return resp def win_get( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> Union[Window, None]: resp = self._win_get( - 'WinGetID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) win_id = resp.unpack() if win_id is None: @@ -365,7 +367,7 @@ def win_get_idlast( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> Union[Window, None]: resp = self._win_get( - 'WinGetIDLast', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetIDLast', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) win_id = resp.unpack() if win_id is None: @@ -377,7 +379,7 @@ def win_get_pid( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> Union[int, None]: resp = self._win_get( - 'WinGetPID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetPID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) pid = resp.unpack() if pid is None: @@ -389,7 +391,7 @@ def win_get_process_name( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> Union[str, None]: resp = self._win_get( - 'WinGetProcessName', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetProcessName', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) process_name = resp.unpack() if process_name is None: @@ -401,7 +403,7 @@ def win_get_process_path( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> Union[str, None]: resp = self._win_get( - 'WinGetProcessPath', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetProcessPath', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) process_path = resp.unpack() if process_path is None: @@ -413,7 +415,7 @@ def win_get_count( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> int: resp = self._win_get( - 'WinGetCount', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetCount', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) return resp.unpack() @@ -422,7 +424,7 @@ def win_get_minmax( ) -> Union[Literal[0], Literal[1], Literal[-1], None]: resp = self._win_get( - 'WinGetMinMax', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + 'AHKWinGetMinMax', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text ) val = resp.unpack() if val is None: @@ -436,6 +438,24 @@ def win_get_minmax( else: raise ValueError(f'Unexpected value for minmax: {val!r}') + def win_get_control_list( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' + ) -> Union[Sequence[SyncControl], None]: + resp = self._win_get( + 'AHKWinGetControlList', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text + ) + val = resp.unpack() + if val is None: + return None + ahkid, controls = val + window = Window(engine=self, ahk_id=ahkid) + ret = [] + for control in controls: + hwnd, classname = control + ctrl = SyncControl(window=window, hwnd=hwnd, control_class=classname) + ret.append(ctrl) + return ret + def win_exists( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' ) -> bool: diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 233dff8e..f9c301eb 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -79,20 +79,20 @@ Literal['WinClick'], Literal['AHKWinMove'], Literal['AHKWinGetPos'], - Literal['WinGetID'], - Literal['WinGetIDLast'], - Literal['WinGetPID'], - Literal['WinGetProcessName'], - Literal['WinGetProcessPath'], - Literal['WinGetCount'], - Literal['WinGetList'], - Literal['WinGetMinMax'], - Literal['WinGetControlList'], - Literal['WinGetControlListHwnd'], - Literal['WinGetTransparent'], - Literal['WinGetTransColor'], - Literal['WinGetStyle'], - Literal['WinGetExStyle'], + Literal['AHKWinGetID'], + Literal['AHKWinGetIDLast'], + Literal['AHKWinGetPID'], + Literal['AHKWinGetProcessName'], + Literal['AHKWinGetProcessPath'], + Literal['AHKWinGetCount'], + Literal['AHKWinGetList'], + Literal['AHKWinGetMinMax'], + Literal['AHKWinGetControlList'], + Literal['AHKWinGetControlListHwnd'], + Literal['AHKWinGetTransparent'], + Literal['AHKWinGetTransColor'], + Literal['AHKWinGetStyle'], + Literal['AHKWinGetExStyle'], ] @@ -286,33 +286,33 @@ def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[lis @overload def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None) -> TupleResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetID'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - def function_call(self, function_name: Literal['WinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetPID'], args: Optional[list[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[list[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... @overload - def function_call(self, function_name: Literal['WinGetProcessName'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - def function_call(self, function_name: Literal['WinGetProcessPath'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - def function_call(self, function_name: Literal['WinGetCount'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetMinMax'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetControlList'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetControlListHwnd'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetTransparent'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetTransColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetExStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 13052059..a6aa5635 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -71,6 +71,10 @@ def get_minmax(self) -> Union[Literal[0], Literal[1], Literal[-1]]: class SyncControl: - def __init__(self, window: Window, control_class: str): + def __init__(self, window: Window, hwnd: str, control_class: str): self.window: Window = window + self.hwnd: str = hwnd self.control_class: str = control_class + + 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/daemon.ahk b/ahk/daemon.ahk index b8cc0036..a6ccbef4 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -11,6 +11,7 @@ STRINGRESPONSEMESSAGE := "005" ; StringResponseMessage WINDOWIDLISTRESPONSEMESSAGE := "006" ; WindowIDListResponseMessage NOVALUERESPONSEMESSAGE := "007" ; NoValueResponseMessage EXCEPTIONRESPONSEMESSAGE := "008" ; ExceptionResponseMessage +WINDOWCONTROLLISTRESPONSEMESSAGE := "009" ; WindowControlListResponseMessage NOVALUE_SENTINEL := Chr(57344) @@ -50,7 +51,7 @@ AHKWinClose(ByRef command) { return FormatNoValueResponse() } -WinGetID(ByRef command) { +AHKWinGetID(ByRef command) { global STRINGRESPONSEMESSAGE title := command[2] text := command[3] @@ -66,7 +67,7 @@ WinGetID(ByRef command) { return response } -WinGetIDLast(ByRef command) { +AHKWinGetIDLast(ByRef command) { global STRINGRESPONSEMESSAGE title := command[2] text := command[3] @@ -82,7 +83,7 @@ WinGetIDLast(ByRef command) { } -WinGetPID(ByRef command) { +AHKWinGetPID(ByRef command) { global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -98,7 +99,7 @@ WinGetPID(ByRef command) { } -WinGetProcessName(ByRef command) { +AHKWinGetProcessName(ByRef command) { global STRINGRESPONSEMESSAGE title := command[2] text := command[3] @@ -113,7 +114,7 @@ WinGetProcessName(ByRef command) { return response } -WinGetProcessPath(ByRef command) { +AHKWinGetProcessPath(ByRef command) { global STRINGRESPONSEMESSAGE title := command[2] text := command[3] @@ -129,7 +130,7 @@ WinGetProcessPath(ByRef command) { } -WinGetCount(ByRef command) { +AHKWinGetCount(ByRef command) { global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -158,7 +159,7 @@ WinGetCount(ByRef command) { ;} -WinGetMinMax(ByRef command) { +AHKWinGetMinMax(ByRef command) { global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -173,31 +174,59 @@ WinGetMinMax(ByRef command) { return response } -WinGetControlList(ByRef command) { - global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - WinGet, output, ControlList, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) - return response -} -WinGetControlListHwnd(ByRef command) { - global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE +AHKWinGetControlList(ByRef command) { + global EXCEPTIONRESPONSEMESSAGE + global WINDOWCONTROLLISTRESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] extext := command[5] - WinGet, output, ControlListHwnd, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + + 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 FormatNoValueResponse() + } + + ctrListArr := StrSplit(ctrList, "`n") + ctrListIDArr := StrSplit(ctrListID, "`n") + if (ctrListArr.Length() != ctrListIDArr.Length()) { + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Control hwnd/class lists have unexpected lengths") + } + + output := Format("('{}', [", ahkid) + + for index, hwnd in ctrListIDArr { + classname := ctrListArr[index] + output .= Format("('{}', '{}'), ", hwnd, classname) + + } + + output .= "])" + MsgBox,% output + response := FormatResponse(WINDOWCONTROLLISTRESPONSEMESSAGE, output) return response } -WinGetTransparent(ByRef command) { +;AHKWinGetControlListHwnd(ByRef command) { +; global STRINGRESPONSEMESSAGE +; global INTEGERRESPONSEMESSAGE +; global NOVALUERESPONSEMESSAGE +; title := command[2] +; text := command[3] +; extitle := command[4] +; extext := command[5] +; WinGet, output, ControlListHwnd, %title%, %text%, %extitle%, %extext% +; response := FormatResponse(NOVALUERESPONSEMESSAGE, output) +; return response +;} +AHKWinGetTransparent(ByRef command) { global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE global NOVALUERESPONSEMESSAGE @@ -209,7 +238,7 @@ WinGetTransparent(ByRef command) { response := FormatResponse(NOVALUERESPONSEMESSAGE, output) return response } -WinGetTransColor(ByRef command) { +AHKWinGetTransColor(ByRef command) { global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE global NOVALUERESPONSEMESSAGE @@ -221,7 +250,7 @@ WinGetTransColor(ByRef command) { response := FormatResponse(NOVALUERESPONSEMESSAGE, output) return response } -WinGetStyle(ByRef command) { +AHKWinGetStyle(ByRef command) { global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE global NOVALUERESPONSEMESSAGE @@ -233,7 +262,7 @@ WinGetStyle(ByRef command) { response := FormatResponse(NOVALUERESPONSEMESSAGE, output) return response } -WinGetExStyle(ByRef command) { +AHKWinGetExStyle(ByRef command) { global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE global NOVALUERESPONSEMESSAGE @@ -247,7 +276,6 @@ WinGetExStyle(ByRef command) { } - ImageSearch(ByRef command) { global COORDINATERESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE diff --git a/ahk/message.py b/ahk/message.py index d60736be..7c0fe87c 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -26,7 +26,7 @@ def readline(self) -> bytes: ... -def is_window_control_list_response(resp_obj: object) -> TypeGuard[Tuple[str, Tuple[str, ...]]]: +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: @@ -34,10 +34,15 @@ def is_window_control_list_response(resp_obj: object) -> TypeGuard[Tuple[str, Tu if not isinstance(resp_obj[0], str): return False expected_win_list = resp_obj[1] - if not isinstance(expected_win_list, tuple): + if not isinstance(expected_win_list, list): return False for obj in expected_win_list: - if not isinstance(obj, str): + 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 @@ -204,8 +209,9 @@ def unpack(self) -> NoReturn: class WindowControlListResponseMessage(ResponseMessage): type = 'windowcontrollist' - def unpack(self) -> Tuple[str, Tuple[str, ...]]: + def unpack(self) -> Tuple[str, list[Tuple[str, str]]]: s = self._raw_content.decode(encoding='utf-8') + breakpoint() val = ast.literal_eval(s) assert is_window_control_list_response(val) return val From 2e6443e0460f2cd160859bbe7d4cc73afaeef628 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 14:22:15 -0700 Subject: [PATCH 224/588] no controls should return empty list, not none --- ahk/_async/window.py | 9 +++++++++ ahk/_sync/window.py | 9 +++++++++ ahk/daemon.ahk | 2 +- ahk/message.py | 1 - 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 81ecc6da..00d21164 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import Literal +from typing import Sequence from typing import TYPE_CHECKING from typing import Union @@ -69,6 +70,14 @@ async def get_minmax(self) -> Union[Literal[0], Literal[1], Literal[-1]]: ) return minmax + async def list_controls(self) -> Sequence['AsyncControl']: + controls = await self._engine.win_get_control_list(title=f'ahk_id {self._ahk_id}') + 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 + class AsyncControl: def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index a6aa5635..5aeee276 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import Literal +from typing import Sequence from typing import TYPE_CHECKING from typing import Union @@ -69,6 +70,14 @@ def get_minmax(self) -> Union[Literal[0], Literal[1], Literal[-1]]: ) return minmax + def list_controls(self) -> Sequence['SyncControl']: + controls = self._engine.win_get_control_list(title=f'ahk_id {self._ahk_id}') + 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 + class SyncControl: def __init__(self, window: Window, hwnd: str, control_class: str): diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index a6ccbef4..a44d91a1 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -192,7 +192,7 @@ AHKWinGetControlList(ByRef command) { WinGet, ctrListID, ControlListHWND, %title%, %text%, %extitle%, %extext% if (ctrListID = "") { - return FormatNoValueResponse() + return FormatResponse(WINDOWCONTROLLISTRESPONSEMESSAGE, Format("('{}', [])", ahkid)) } ctrListArr := StrSplit(ctrList, "`n") diff --git a/ahk/message.py b/ahk/message.py index 7c0fe87c..4070c0b1 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -211,7 +211,6 @@ class WindowControlListResponseMessage(ResponseMessage): def unpack(self) -> Tuple[str, list[Tuple[str, str]]]: s = self._raw_content.decode(encoding='utf-8') - breakpoint() val = ast.literal_eval(s) assert is_window_control_list_response(val) return val From 0a2a0db1e9a5418a19ba5d08a673c165c7fc9392 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 20:55:34 -0700 Subject: [PATCH 225/588] python versions --- .github/workflows/test.yaml | 6 +++++- MANIFEST.in | 1 + ahk/daemon.ahk | 5 ++--- setup.py | 4 +++- 4 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 MANIFEST.in diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d23c3b68..73a5ce8f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,6 +2,9 @@ on: [ push, pull_request ] jobs: build: + strategy: + matrix: + python_version: ["3.10", "3.9", "3.8", "3.7"] runs-on: windows-latest timeout-minutes: 5 steps: @@ -10,12 +13,13 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: "3.10" + 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 . python -m pip install ahk-binary - name: Test with coverage/pytest env: diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..8a798e31 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include ahk/daemon.ahk diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index a44d91a1..3b8bda19 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -226,16 +226,15 @@ AHKWinGetControlList(ByRef command) { ; response := FormatResponse(NOVALUERESPONSEMESSAGE, output) ; return response ;} + AHKWinGetTransparent(ByRef command) { - global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] extext := command[5] WinGet, output, Transparent, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + response := FormatResponse(INTEGERRESPONSEMESSAGE, output) return response } AHKWinGetTransColor(ByRef command) { diff --git a/setup.py b/setup.py index f168699f..0fa61b63 100644 --- a/setup.py +++ b/setup.py @@ -2,13 +2,15 @@ import unasync setuptools.setup( + python_requires='>=3.7.0', name='ahk', version='0.0.1', author_email='spencer.young@spyoung.com', author='Spencer Young', description='A package used to test customized unasync', url='https://github.com/spyoungtech/ahk', - packages=['ahk', 'ahk._async'], + packages=['ahk', 'ahk._async', 'ahk._sync'], + install_requires=['typing_extensions; python_version < "3.10"'], cmdclass={ 'build_py': unasync.cmdclass_build_py( rules=[ From 85ea941d1feb6a6d27a3cdb748860144b40d23f9 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 21:02:22 -0700 Subject: [PATCH 226/588] do not fail fast --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 73a5ce8f..b18231dd 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -3,6 +3,7 @@ on: [ push, pull_request ] jobs: build: strategy: + fail-fast: false matrix: python_version: ["3.10", "3.9", "3.8", "3.7"] runs-on: windows-latest From bf04584a1008815819283711337a6a212de858b6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 21:06:16 -0700 Subject: [PATCH 227/588] add typing extensions import --- ahk/message.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ahk/message.py b/ahk/message.py index 4070c0b1..b3448a1a 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -11,7 +11,11 @@ from typing import runtime_checkable from typing import Tuple from typing import Type -from typing import TypeGuard + +try: + from typing import TypeGuard +except ImportError: + from typing_extensions import TypeGuard from typing import TypeVar from typing import Union From db01cc79ee1ff267955be3948387cc50cc13ced8 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 21:10:40 -0700 Subject: [PATCH 228/588] try future annotations --- ahk/message.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ahk/message.py b/ahk/message.py index b3448a1a..c8a8b6c3 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ast import itertools import string From 328b1e9f8550fe5bcd350db1a9d1be731befade3 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 21:15:11 -0700 Subject: [PATCH 229/588] use more compatible annotations --- ahk/_async/engine.py | 7 +- ahk/_async/transport.py | 141 ++++++++++++++++++++-------------------- ahk/_sync/engine.py | 7 +- ahk/_sync/transport.py | 139 +++++++++++++++++++-------------------- ahk/message.py | 11 ++-- 5 files changed, 155 insertions(+), 150 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index a53a7545..b2313fe1 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3,6 +3,7 @@ from typing import Any from typing import Callable from typing import Iterable +from typing import List from typing import Literal from typing import Optional from typing import overload @@ -57,7 +58,7 @@ def __init__(self, *, TransportClass: Optional[Type[AsyncTransport]] = None, **t transport = TransportClass(**transport_kwargs) self._transport: AsyncTransport = transport - async def list_windows(self) -> list[AsyncWindow]: + async def list_windows(self) -> List[AsyncWindow]: resp = await self._transport.function_call('WindowList') window_ids = resp.unpack() ret = [AsyncWindow(engine=self, ahk_id=ahk_id) for ahk_id in window_ids] @@ -506,7 +507,7 @@ async def image_search( elif scale_width and not scale_height: scale_height = -1 - options: list[Union[str, int]] = [] + options: List[Union[str, int]] = [] if icon: options.append(f'Icon{icon}') if color_variation is not None: @@ -592,7 +593,7 @@ async def win_close( exclude_title: str = '', exclude_text: str = '', ) -> None: - args: list[str] + args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] resp = await self._transport.function_call('AHKWinClose', args=args) resp.unpack() diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index a951fac7..1eeaf176 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -11,6 +11,7 @@ from shutil import which from typing import Any from typing import AnyStr +from typing import List from typing import Literal from typing import Optional from typing import overload @@ -111,7 +112,7 @@ def kill(proc: Killable) -> None: class AsyncAHKProcess: - def __init__(self, runargs: list[str]): + def __init__(self, runargs: List[str]): self.runargs = runargs self._proc: Optional[AsyncIOProcess] = None @@ -146,13 +147,13 @@ def kill(self) -> None: self._proc.kill() -async def async_create_process(runargs: list[str]) -> asyncio.subprocess.Process: # unasync: remove +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.PIPE ) -def sync_create_process(runargs: list[str]) -> subprocess.Popen[bytes]: +def sync_create_process(runargs: List[str]) -> subprocess.Popen[bytes]: return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) @@ -212,143 +213,143 @@ async def init(self) -> None: # fmt: off @overload - async def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... @overload - async def function_call(self, function_name: Literal['ImageSearch'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + async def function_call(self, function_name: Literal['ImageSearch'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... @overload - async def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['PixelSearch'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + async def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... @overload - async def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + async def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + async def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... @overload - async def function_call(self, function_name: Literal['MouseMove'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['MouseMove'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['CoordMode'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['Click'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['Click'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['KeyWait'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... @overload - async def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['Send'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['Send'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['SendRaw'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SendRaw'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['SendInput'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SendInput'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['SendEvent'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SendEvent'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['SendPlay'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SendPlay'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetClass'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGetText'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['WinGetText'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinActivate'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinHide'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinKill'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinKill'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinMaximize'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinMinimize'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinMinimize'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinRestore'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinRestore'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinShow'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WindowList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... + async def function_call(self, function_name: Literal['WindowList'], args: Optional[List[str]] = None) -> WindowIDListResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinSend'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['ControlSend'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['ControlSend'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['FromMouse'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinGet'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinSet'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinSet'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + async def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... @overload - async def function_call(self, function_name: Literal['WinClick'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None) -> TupleResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None) -> TupleResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[list[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[List[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[List[str]] = None) -> WindowIDListResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None) -> WindowControlListResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[List[str]] = None) -> WindowControlListResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... # @overload - # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... # @overload - # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['WinWait'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWait'], args: Optional[List[str]] = None) -> StringResponseMessage: ... # @overload - # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[List[str]] = None) -> StringResponseMessage: ... # @overload - # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[List[str]] = None) -> StringResponseMessage: ... # @overload - # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['RegRead'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['RegRead'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[List[str]] = None) -> None: ... # fmt: on async def function_call( - self, function_name: FunctionName, args: Optional[list[str]] = None + self, function_name: FunctionName, args: Optional[List[str]] = None ) -> ResponseMessageTypes: if not self._started: await self.init() diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 738434c4..756edf6e 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3,6 +3,7 @@ from typing import Any from typing import Callable from typing import Iterable +from typing import List from typing import Literal from typing import Optional from typing import overload @@ -57,7 +58,7 @@ def __init__(self, *, TransportClass: Optional[Type[Transport]] = None, **transp transport = TransportClass(**transport_kwargs) self._transport: Transport = transport - def list_windows(self) -> list[Window]: + def list_windows(self) -> List[Window]: resp = self._transport.function_call('WindowList') window_ids = resp.unpack() ret = [Window(engine=self, ahk_id=ahk_id) for ahk_id in window_ids] @@ -506,7 +507,7 @@ def image_search( elif scale_width and not scale_height: scale_height = -1 - options: list[Union[str, int]] = [] + options: List[Union[str, int]] = [] if icon: options.append(f'Icon{icon}') if color_variation is not None: @@ -592,7 +593,7 @@ def win_close( exclude_title: str = '', exclude_text: str = '', ) -> None: - args: list[str] + args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] resp = self._transport.function_call('AHKWinClose', args=args) resp.unpack() diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index f9c301eb..4feccb83 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -11,6 +11,7 @@ from shutil import which from typing import Any from typing import AnyStr +from typing import List from typing import Literal from typing import Optional from typing import overload @@ -110,7 +111,7 @@ def kill(proc: Killable) -> None: class SyncAHKProcess: - def __init__(self, runargs: list[str]): + def __init__(self, runargs: List[str]): self.runargs = runargs self._proc: Optional[SyncIOProcess] = None @@ -142,7 +143,7 @@ def kill(self) -> None: -def sync_create_process(runargs: list[str]) -> subprocess.Popen[bytes]: +def sync_create_process(runargs: List[str]) -> subprocess.Popen[bytes]: return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) @@ -202,143 +203,143 @@ def init(self) -> None: # fmt: off @overload - def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... @overload - def function_call(self, function_name: Literal['ImageSearch'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + def function_call(self, function_name: Literal['ImageSearch'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... @overload - def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['PixelSearch'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... @overload - def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[list[str]] = None) -> CoordinateResponseMessage: ... + def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... @overload - def function_call(self, function_name: Literal['MouseMove'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['MouseMove'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['CoordMode'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['Click'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['Click'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['KeyWait'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... @overload - def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['Send'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['Send'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['SendRaw'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SendRaw'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['SendInput'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SendInput'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['SendEvent'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SendEvent'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['SendPlay'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SendPlay'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetClass'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGetText'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['WinGetText'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinActivate'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinHide'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinKill'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinKill'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinMaximize'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinMinimize'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinMinimize'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinRestore'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinRestore'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinShow'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WindowList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... + def function_call(self, function_name: Literal['WindowList'], args: Optional[List[str]] = None) -> WindowIDListResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinSend'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['ControlSend'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['ControlSend'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['FromMouse'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinGet'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinSet'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinSet'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[list[str]] = None) -> BooleanResponseMessage: ... + def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... @overload - def function_call(self, function_name: Literal['WinClick'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[list[str]] = None) -> TupleResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None) -> TupleResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[list[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[List[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[list[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[list[str]] = None) -> WindowIDListResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[List[str]] = None) -> WindowIDListResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None) -> WindowControlListResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[list[str]] = None) -> WindowControlListResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[List[str]] = None) -> WindowControlListResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[list[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... @overload - def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... # @overload - # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[list[str]] = None) -> NoValueResponseMessage: ... + # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... # @overload - # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['WinWait'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWait'], args: Optional[List[str]] = None) -> StringResponseMessage: ... # @overload - # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[List[str]] = None) -> StringResponseMessage: ... # @overload - # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[list[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[List[str]] = None) -> StringResponseMessage: ... # @overload - # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['RegRead'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['RegRead'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[list[str]] = None) -> None: ... + # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[List[str]] = None) -> None: ... # fmt: on def function_call( - self, function_name: FunctionName, args: Optional[list[str]] = None + self, function_name: FunctionName, args: Optional[List[str]] = None ) -> ResponseMessageTypes: if not self._started: self.init() diff --git a/ahk/message.py b/ahk/message.py index c8a8b6c3..4e3267d4 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -7,6 +7,7 @@ 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 @@ -32,7 +33,7 @@ def readline(self) -> bytes: ... -def is_window_control_list_response(resp_obj: object) -> TypeGuard[Tuple[str, list[Tuple[str, str]]]]: +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: @@ -186,7 +187,7 @@ def unpack(self) -> str: class WindowIDListResponseMessage(ResponseMessage): type = 'windowidlist' - def unpack(self) -> list[str]: + def unpack(self) -> List[str]: s = self._raw_content.decode(encoding='utf-8') s = s.rstrip(',') return s.split(',') @@ -215,7 +216,7 @@ def unpack(self) -> NoReturn: class WindowControlListResponseMessage(ResponseMessage): type = 'windowcontrollist' - def unpack(self) -> Tuple[str, list[Tuple[str, str]]]: + def unpack(self) -> Tuple[str, List[Tuple[str, str]]]: s = self._raw_content.decode(encoding='utf-8') val = ast.literal_eval(s) assert is_window_control_list_response(val) @@ -226,9 +227,9 @@ def unpack(self) -> Tuple[str, list[Tuple[str, str]]]: class RequestMessage: - def __init__(self, function_name: str, args: Optional[list[str]] = None): + def __init__(self, function_name: str, args: Optional[List[str]] = None): self.function_name: str = function_name - self.args: list[str] = args or [] + self.args: List[str] = args or [] ResponseMessageTypes = Union[ From b0df85bc59ca741280a1ecac939a63d994f9db34 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 21:40:42 -0700 Subject: [PATCH 230/588] try to target at least 3.8 ... --- .github/workflows/test.yaml | 2 +- ahk/_async/transport.py | 7 +++++-- ahk/_sync/transport.py | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b18231dd..997631e6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,7 +5,7 @@ jobs: strategy: fail-fast: false matrix: - python_version: ["3.10", "3.9", "3.8", "3.7"] + python_version: ["3.10", "3.9", "3.8"] runs-on: windows-latest timeout-minutes: 5 steps: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 1eeaf176..3f38e097 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -4,6 +4,7 @@ import atexit import os import subprocess +import sys import warnings from abc import ABC from abc import abstractmethod @@ -35,8 +36,10 @@ AsyncIOProcess = asyncio.subprocess.Process # unasync: remove - -SyncIOProcess = subprocess.Popen[bytes] +if sys.version_info >= (3, 9): + SyncIOProcess = subprocess.Popen[bytes] +else: + SyncIOProcess = subprocess.Popen FunctionName = Literal[ Literal['AHKWinExist'], diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 4feccb83..8f267d18 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -4,6 +4,7 @@ import atexit import os import subprocess +import sys import warnings from abc import ABC from abc import abstractmethod @@ -34,8 +35,10 @@ DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' - -SyncIOProcess = subprocess.Popen[bytes] +if sys.version_info >= (3, 9): + SyncIOProcess = subprocess.Popen[bytes] +else: + SyncIOProcess = subprocess.Popen FunctionName = Literal[ Literal['AHKWinExist'], From 57a54bd546174d8932400d8f6769851b5c36ae80 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 22:08:03 -0700 Subject: [PATCH 231/588] use version to determine TypeGuard import --- ahk/message.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ahk/message.py b/ahk/message.py index 4e3267d4..454b9fb8 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -3,6 +3,7 @@ import ast import itertools import string +import sys from abc import abstractmethod from typing import Any from typing import cast @@ -15,9 +16,9 @@ from typing import Tuple from typing import Type -try: +if sys.version_info >= (3, 10): from typing import TypeGuard -except ImportError: +else: from typing_extensions import TypeGuard from typing import TypeVar from typing import Union From 37882ce230ab775686376c465dde7037bc67bf0e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 22:11:11 -0700 Subject: [PATCH 232/588] 3.8 compat --- ahk/_async/transport.py | 1 + ahk/_sync/transport.py | 1 + 2 files changed, 2 insertions(+) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 3f38e097..f08cc992 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -132,6 +132,7 @@ async def adrain_stdin(self) -> None: # unasync: remove def drain_stdin(self) -> None: assert isinstance(self._proc, subprocess.Popen) + assert self._proc.stdin is not None self._proc.stdin.flush() return None diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 8f267d18..c3ec6767 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -126,6 +126,7 @@ def start(self) -> 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 From e703ea5164115e587f27af7f8cc1662f2549c35b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 22:15:57 -0700 Subject: [PATCH 233/588] more 3.8 compat --- ahk/_async/transport.py | 4 +++- ahk/_sync/transport.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index f08cc992..56faece1 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -144,7 +144,9 @@ def write(self, content: bytes) -> None: async def readline(self) -> bytes: assert self._proc is not None assert self._proc.stdout is not None - return await self._proc.stdout.readline() + line = await self._proc.stdout.readline() + assert isinstance(line, bytes) + return line def kill(self) -> None: assert self._proc is not None diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index c3ec6767..f3629904 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -138,7 +138,9 @@ def write(self, content: bytes) -> None: def readline(self) -> bytes: assert self._proc is not None assert self._proc.stdout is not None - return self._proc.stdout.readline() + line = self._proc.stdout.readline() + assert isinstance(line, bytes) + return line def kill(self) -> None: assert self._proc is not None From b9e6c7ea132780e0e719d074a89976bfad83aec9 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Jul 2022 22:24:41 -0700 Subject: [PATCH 234/588] forward reference compat 3.8 --- ahk/_sync/transport.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index f3629904..f6f5ffd5 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -116,7 +116,10 @@ def kill(proc: Killable) -> None: class SyncAHKProcess: def __init__(self, runargs: List[str]): self.runargs = runargs - self._proc: Optional[SyncIOProcess] = None + if sys.version_info >= (3, 9): + self._proc: Optional[SyncIOProcess] = None + else: + self._proc: Optional[SyncIOProcess[bytes]] = None def start(self) -> None: self._proc = sync_create_process(self.runargs) From 7395d9268e95646168a0d1a777c25feebf0035ce Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Jul 2022 13:19:21 -0700 Subject: [PATCH 235/588] hotkeys! --- .pre-commit-config.yaml | 2 + ahk/_async/engine.py | 8 ++ ahk/_async/transport.py | 16 ++- ahk/_sync/engine.py | 5 + ahk/_sync/transport.py | 18 ++-- ahk/exceptions.py | 3 + ahk/executor.ahk | 11 ++ ahk/hotkey.py | 216 ++++++++++++++++++++++++++++++++++++++++ ahk/py.typed | 0 setup.py | 5 +- 10 files changed, 274 insertions(+), 10 deletions(-) create mode 100644 ahk/exceptions.py create mode 100644 ahk/hotkey.py create mode 100644 ahk/py.typed diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 69f70253..da13bc28 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,3 +44,5 @@ repos: args: - "--strict" exclude: ^(tests/.*|setup\.py|\.build\.py|\.unasync-rewrite\.py|_tests_setup\.py) + additional_dependencies: + - jinja2 diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index b2313fe1..4fa4334f 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -58,6 +58,14 @@ def __init__(self, *, TransportClass: Optional[Type[AsyncTransport]] = None, **t transport = TransportClass(**transport_kwargs) self._transport: AsyncTransport = transport + def add_hotkey( + self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None + ) -> None: + return self._transport.add_hotkey(hotkey=hotkey, callback=callback, ex_handler=ex_handler) + + def add_hotstring(self, trigger_string: str, replacement: str) -> None: + return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + async def list_windows(self) -> List[AsyncWindow]: resp = await self._transport.function_call('WindowList') window_ids = resp.unpack() diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 56faece1..ee4255db 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -12,6 +12,7 @@ from shutil import which from typing import Any from typing import AnyStr +from typing import Callable from typing import List from typing import Literal from typing import Optional @@ -20,6 +21,7 @@ from typing import runtime_checkable from typing import Union +from ahk.hotkey import ThreadedHotkeyTransport from ahk.message import BooleanResponseMessage from ahk.message import CoordinateResponseMessage from ahk.message import IntegerResponseMessage @@ -210,9 +212,19 @@ def _resolve_executable_path(executable_path: Union[str, os.PathLike[AnyStr]] = class AsyncTransport(ABC): _started: bool = False - def __init__(self, /, **kwargs: Any): + def __init__(self, /, executable_path: Union[str, os.PathLike[AnyStr]] = '', **kwargs: Any): + self._executable_path: str = _resolve_executable_path(executable_path=executable_path) + self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) pass + def add_hotkey( + self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None + ) -> None: + return self._hotkey_transport.add_hotkey(hotkey=hotkey, callback=callback, ex_handler=ex_handler) + + def add_hotstring(self, trigger_string: str, replacement: str) -> None: + return self._hotkey_transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + async def init(self) -> None: self._started = True return None @@ -372,7 +384,7 @@ class AsyncDaemonProcessTransport(AsyncTransport): def __init__(self, *, executable_path: Union[str, os.PathLike[AnyStr]] = ''): self._proc: Optional[AsyncAHKProcess] self._proc = None - self._executable_path: str = _resolve_executable_path(executable_path=executable_path) + super().__init__(executable_path=executable_path) async def init(self) -> None: await self.start() diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 756edf6e..e0213d13 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -58,6 +58,11 @@ def __init__(self, *, TransportClass: Optional[Type[Transport]] = None, **transp transport = TransportClass(**transport_kwargs) self._transport: Transport = transport + def add_hotkey(self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None) -> None: + return self._transport.add_hotkey(hotkey=hotkey, callback=callback, ex_handler=ex_handler) + def add_hotstring(self, trigger_string: str, replacement: str) -> None: + return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + def list_windows(self) -> List[Window]: resp = self._transport.function_call('WindowList') window_ids = resp.unpack() diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index f6f5ffd5..a667657b 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -12,6 +12,7 @@ from shutil import which from typing import Any from typing import AnyStr +from typing import Callable from typing import List from typing import Literal from typing import Optional @@ -20,6 +21,7 @@ from typing import runtime_checkable from typing import Union +from ahk.hotkey import ThreadedHotkeyTransport from ahk.message import BooleanResponseMessage from ahk.message import CoordinateResponseMessage from ahk.message import IntegerResponseMessage @@ -116,10 +118,7 @@ def kill(proc: Killable) -> None: class SyncAHKProcess: def __init__(self, runargs: List[str]): self.runargs = runargs - if sys.version_info >= (3, 9): - self._proc: Optional[SyncIOProcess] = None - else: - self._proc: Optional[SyncIOProcess[bytes]] = None + self._proc: Optional[SyncIOProcess] = None def start(self) -> None: self._proc = sync_create_process(self.runargs) @@ -203,9 +202,16 @@ def _resolve_executable_path(executable_path: Union[str, os.PathLike[AnyStr]] = class Transport(ABC): _started: bool = False - def __init__(self, /, **kwargs: Any): + def __init__(self, /, executable_path: Union[str, os.PathLike[AnyStr]] = '', **kwargs: Any): + self._executable_path: str = _resolve_executable_path(executable_path=executable_path) + self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) pass + def add_hotkey(self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None) -> None: + return self._hotkey_transport.add_hotkey(hotkey=hotkey, callback=callback, ex_handler=ex_handler) + def add_hotstring(self, trigger_string: str, replacement: str) -> None: + return self._hotkey_transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + def init(self) -> None: self._started = True return None @@ -365,7 +371,7 @@ class DaemonProcessTransport(Transport): def __init__(self, *, executable_path: Union[str, os.PathLike[AnyStr]] = ''): self._proc: Optional[SyncAHKProcess] self._proc = None - self._executable_path: str = _resolve_executable_path(executable_path=executable_path) + super().__init__(executable_path=executable_path) def init(self) -> None: self.start() diff --git a/ahk/exceptions.py b/ahk/exceptions.py new file mode 100644 index 00000000..337c3665 --- /dev/null +++ b/ahk/exceptions.py @@ -0,0 +1,3 @@ +class AHKBaseException(Exception): + # TODO: make existing exceptions subclasses of this + ... diff --git a/ahk/executor.ahk b/ahk/executor.ahk index e69de29b..187836b6 100644 --- a/ahk/executor.ahk +++ b/ahk/executor.ahk @@ -0,0 +1,11 @@ +KEEPALIVE := Chr(57344) + +SetTimer, keepalive, 1000 + +keepalive: +global KEEPALIVE +FileAppend, %KEEPALIVE%`n, *, UTF-8 + +#n:: +FileAppend, %A_ThisHotkey%`n, *, UTF-8 +return diff --git a/ahk/hotkey.py b/ahk/hotkey.py new file mode 100644 index 00000000..8208f3bc --- /dev/null +++ b/ahk/hotkey.py @@ -0,0 +1,216 @@ +from __future__ import annotations + +import os +import subprocess +import sys +import threading +from abc import ABC +from abc import abstractmethod +from textwrap import dedent +from typing import Any +from typing import Callable +from typing import Deque +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import Type +from typing import TypeVar +from typing import Union + +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec +import traceback +import logging +import tempfile +from jinja2 import Environment, BaseLoader +from queue import Queue + +P_HotkeyCallbackParam = ParamSpec('P_HotkeyCallbackParam') +T_HotkeyCallbackReturn = TypeVar('T_HotkeyCallbackReturn') + +_KEEPALIVE_SENTINEL = b'\xee\x80\x80' + + +def _default_ex_handler(hotkey: str, ex: Exception) -> None: + logging.error(f'Failure in hotkey {hotkey!r}', exc_info=True) + + +class HotkeyTransportBase(ABC): + def __init__(self, executable_path: str, default_ex_handler: Optional[Callable[[str, Exception], Any]] = None): + self._executable_path = executable_path + self._hotkeys: Dict[str, Tuple[Callable[[], Any], Optional[Callable[[str, Exception], Any]]]] = {} + self._default_ex_handler: Callable[[str, Exception], Any] = default_ex_handler or _default_ex_handler + # self._transport_options: Dict[Any, Any] = transport_options or {} + self._hotstrings: Dict[str, str] = {} + self._running: bool = False + + @abstractmethod + def restart(self) -> Any: + return NotImplemented + + @abstractmethod + def start(self) -> Any: + return NotImplemented + + @staticmethod + def _validate_hotkey(hotkey: str) -> None: + assert '\n' not in hotkey, 'Newlines not allowed in hotkeys' # TODO: perform better validation + + @staticmethod + def _validate_hotstring(trigger: str, replacement: str) -> None: + assert '\n' not in trigger, 'newlines not allowed in hotstrings' # TODO: perform better validation + assert '\n' not in replacement, 'newlines not allowed in hotstrings' + + def add_hotkey( + self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None + ) -> None: + self._validate_hotkey(hotkey) + self._hotkeys[hotkey] = (callback, ex_handler) + if self._running: + self.restart() + return None + + def add_hotstring(self, trigger_string: str, replacement: str) -> None: + replacement = replacement.replace('\n', '`n').replace('\r', '`n') + self._validate_hotstring(trigger_string, replacement) + self._hotstrings[trigger_string] = replacement + if self._running: + self.restart() + # TODO: add support for adding IfWinActive/IfWinExist + return None + + +class STOP: + ... + + +class ThreadedHotkeyTransport(HotkeyTransportBase): + def __init__(self, executable_path: str, default_ex_handler: Optional[Callable[[str, Exception], Any]] = None): + super().__init__(executable_path=executable_path, default_ex_handler=default_ex_handler) + 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 + + def _do_callback( + self, hotkey: str, cb: Callable[[], Any], ex_handler: Optional[Callable[[str, 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, cb_exc) + 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._proc is not None + self._running = False + self._proc.kill() + + self._callback_queue.empty() + self._callback_queue.put_nowait(STOP) + print('Waiting for stop...') + if self._dispatcher_thread is not None: + self._dispatcher_thread.join() + self._dispatcher_thread = None + + self._callback_queue.join() + if self._listener_thread is not None: + self._listener_thread.join() + self._listener_thread = None + + def restart(self) -> None: + self.stop() + self.start() + + def dispatcher(self) -> None: + while True: + job = self._callback_queue.get() + if job is STOP: + self._callback_queue.task_done() + break + + assert isinstance(job, str) + if job not in self._hotkeys: + logging.warning(f'Received request to dispatch unregistered hotkey: {job!r}. Ignoring.') + self._callback_queue.task_done() + continue + + cb, ex_handler = self._hotkeys[job] + 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_tempate(self) -> str: + env = Environment(loader=BaseLoader()) + template = env.from_string( + dedent( + """\ + KEEPALIVE := Chr(57344) + SetTimer, keepalive, 1000 + + {% for hotkey in hotkeys %} + + {{ hotkey }}:: + FileAppend, %A_ThisHotkey%`n, *, UTF-8 + return + + {% endfor %} + + {% for trigger, replacement in hotstrings %} + + ::{{ trigger }}::{{replacement}} + + {% endfor %} + keepalive: + global KEEPALIVE + FileAppend, %KEEPALIVE%`n, *, UTF-8 + """ + ) + ) + ret = template.render(hotkeys=list(self._hotkeys), hotstrings=self._hotstrings.items()) + assert isinstance(ret, str) + return ret + + def listener(self) -> None: + last_keepalive_received: Optional[float] = None + + hotkey_script_contents = self._render_hotkey_tempate() + logging.debug('hotkey script contents:\n%s', hotkey_script_contents) + with tempfile.TemporaryDirectory(prefix='python-ahk') as tmpdirname: + exc_file = os.path.join(tmpdirname, 'executor.ahk') + with open(exc_file, 'w') as f: + f.write(hotkey_script_contents) + self._proc = subprocess.Popen( + [self._executable_path, '/CP65001', '/ErrorStdOut', exc_file], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + while self._running: + assert self._proc.stdout is not None + line = self._proc.stdout.readline() + if line.rstrip(b'\n') == _KEEPALIVE_SENTINEL: + logging.debug('keepalive received') + continue + logging.debug(f'Received {line!r}') + self._callback_queue.put_nowait(line.decode('UTF-8').strip()) diff --git a/ahk/py.typed b/ahk/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/setup.py b/setup.py index 0fa61b63..6cad3b7f 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,14 @@ setuptools.setup( python_requires='>=3.7.0', name='ahk', - version='0.0.1', + version='0.0.1b', author_email='spencer.young@spyoung.com', author='Spencer Young', description='A package used to test customized unasync', url='https://github.com/spyoungtech/ahk', packages=['ahk', 'ahk._async', 'ahk._sync'], - install_requires=['typing_extensions; python_version < "3.10"'], + install_requires=['typing_extensions; python_version < "3.10"', 'jinja2>=3.0'], + package_data={'ahk': ['py.typed']}, cmdclass={ 'build_py': unasync.cmdclass_build_py( rules=[ From 9e1ad84c15429a336e813fcbc81164ad9860d77b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Jul 2022 14:17:04 -0700 Subject: [PATCH 236/588] tox configuration --- requirements-dev.txt | 3 +++ tox.ini | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 tox.ini diff --git a/requirements-dev.txt b/requirements-dev.txt index 9f8e5da3..a3c736c8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,3 +4,6 @@ unasync black tokenize-rt coverage +mypy +typing_extensions +jinja2 diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..9df333b3 --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] +envlist = py38,py39,py310 + +[testenv] +deps = -rrequirements-dev.txt + +commands = + pytest + mypy --strict ahk From 3213e2073462684df5af146bd4789db2a3044cfd Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Jul 2022 14:17:20 -0700 Subject: [PATCH 237/588] mypy happy in py38 --- ahk/_async/transport.py | 13 ++++++++----- ahk/_sync/transport.py | 16 +++++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index ee4255db..4522326d 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -21,6 +21,11 @@ from typing import runtime_checkable from typing import Union +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + from ahk.hotkey import ThreadedHotkeyTransport from ahk.message import BooleanResponseMessage from ahk.message import CoordinateResponseMessage @@ -37,11 +42,9 @@ DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' -AsyncIOProcess = asyncio.subprocess.Process # unasync: remove -if sys.version_info >= (3, 9): - SyncIOProcess = subprocess.Popen[bytes] -else: - SyncIOProcess = subprocess.Popen +AsyncIOProcess: TypeAlias = asyncio.subprocess.Process # unasync: remove + +SyncIOProcess: TypeAlias = 'subprocess.Popen[bytes]' FunctionName = Literal[ Literal['AHKWinExist'], diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index a667657b..52115a45 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -21,6 +21,11 @@ from typing import runtime_checkable from typing import Union +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + from ahk.hotkey import ThreadedHotkeyTransport from ahk.message import BooleanResponseMessage from ahk.message import CoordinateResponseMessage @@ -37,10 +42,8 @@ DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' -if sys.version_info >= (3, 9): - SyncIOProcess = subprocess.Popen[bytes] -else: - SyncIOProcess = subprocess.Popen + +SyncIOProcess: TypeAlias = 'subprocess.Popen[bytes]' FunctionName = Literal[ Literal['AHKWinExist'], @@ -207,8 +210,11 @@ def __init__(self, /, executable_path: Union[str, os.PathLike[AnyStr]] = '', **k self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) pass - def add_hotkey(self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None) -> None: + def add_hotkey( + self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None + ) -> None: return self._hotkey_transport.add_hotkey(hotkey=hotkey, callback=callback, ex_handler=ex_handler) + def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._hotkey_transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) From 30795699a8bfa7ae642ff8cdd146382fe366a73f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Jul 2022 15:07:43 -0700 Subject: [PATCH 238/588] use setup.cfg --- .build.py | 6 ----- .pre-commit-config.yaml | 2 +- MANIFEST.in | 2 ++ ahk/executor.ahk | 11 --------- buildunasync.py | 20 ++++++++++++++++ docs/README.md | 0 setup.cfg | 51 +++++++++++++++++++++++++++++++++++++++++ setup.py | 36 ++--------------------------- tox.ini | 2 +- 9 files changed, 77 insertions(+), 53 deletions(-) delete mode 100644 .build.py delete mode 100644 ahk/executor.ahk create mode 100644 buildunasync.py create mode 100644 docs/README.md create mode 100644 setup.cfg diff --git a/.build.py b/.build.py deleted file mode 100644 index 8c4b344d..00000000 --- a/.build.py +++ /dev/null @@ -1,6 +0,0 @@ -def main() -> int: - return 0 - - -if __name__ == '__main__': - raise SystemExit(main()) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da13bc28..8d20bc1f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,6 +43,6 @@ repos: - id: mypy args: - "--strict" - exclude: ^(tests/.*|setup\.py|\.build\.py|\.unasync-rewrite\.py|_tests_setup\.py) + exclude: ^(tests/.*|setup\.py|\.build\.py|\.unasync-rewrite\.py|_tests_setup\.py|buildunasync\.py) additional_dependencies: - jinja2 diff --git a/MANIFEST.in b/MANIFEST.in index 8a798e31..98730c44 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,3 @@ include ahk/daemon.ahk +include docs/README.md +include buildunasync.py diff --git a/ahk/executor.ahk b/ahk/executor.ahk deleted file mode 100644 index 187836b6..00000000 --- a/ahk/executor.ahk +++ /dev/null @@ -1,11 +0,0 @@ -KEEPALIVE := Chr(57344) - -SetTimer, keepalive, 1000 - -keepalive: -global KEEPALIVE -FileAppend, %KEEPALIVE%`n, *, UTF-8 - -#n:: -FileAppend, %A_ThisHotkey%`n, *, UTF-8 -return diff --git a/buildunasync.py b/buildunasync.py new file mode 100644 index 00000000..0a7b93cb --- /dev/null +++ b/buildunasync.py @@ -0,0 +1,20 @@ +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', + 'AsyncDaemonProcessTransport': 'DaemonProcessTransport', + '_AIOP': '_SIOP', + 'async_create_process': 'sync_create_process', + 'adrain_stdin': 'drain_stdin', + # "__aenter__": "__aenter__", + }, + ), + ] +) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..e69de29b diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..e6b291dc --- /dev/null +++ b/setup.cfg @@ -0,0 +1,51 @@ +[metadata] + +name = ahk +version = 1.0.0b +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 +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 + +[options] +include_package_data = True +python_requires = >=3.8.0 +packages = + ahk + ahk._async + ahk._sync +install_requires = + typing_extensions; python_version < "3.10" + jinja2>=3.0 +cmdclass = + build_py = buildunasync.build_py + +[options.package_data] +ahk = + py.typed + daemon.ahk + +[build-system] +requires = ["setuptools", "unasync", "tokenize-rt"] diff --git a/setup.py b/setup.py index 6cad3b7f..60684932 100644 --- a/setup.py +++ b/setup.py @@ -1,35 +1,3 @@ -import setuptools -import unasync +from setuptools import setup -setuptools.setup( - python_requires='>=3.7.0', - name='ahk', - version='0.0.1b', - author_email='spencer.young@spyoung.com', - author='Spencer Young', - description='A package used to test customized unasync', - url='https://github.com/spyoungtech/ahk', - packages=['ahk', 'ahk._async', 'ahk._sync'], - install_requires=['typing_extensions; python_version < "3.10"', 'jinja2>=3.0'], - package_data={'ahk': ['py.typed']}, - cmdclass={ - 'build_py': unasync.cmdclass_build_py( - rules=[ - unasync.Rule( - fromdir='/ahk/_async/', - todir='/ahk/_sync/', - additional_replacements={ - 'AsyncAHK': 'AHK', - 'AsyncTransport': 'Transport', - 'AsyncWindow': 'Window', - 'AsyncDaemonProcessTransport': 'DaemonProcessTransport', - '_AIOP': '_SIOP', - 'async_create_process': 'sync_create_process', - 'adrain_stdin': 'drain_stdin', - # "__aenter__": "__aenter__", - }, - ), - ] - ) - }, -) +setup() diff --git a/tox.ini b/tox.ini index 9df333b3..fda74555 100644 --- a/tox.ini +++ b/tox.ini @@ -5,5 +5,5 @@ envlist = py38,py39,py310 deps = -rrequirements-dev.txt commands = - pytest + coverage run -m pytest -s mypy --strict ahk From 6fdd5657e932951cd8d6612a856aebb639550257 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Jul 2022 15:08:37 -0700 Subject: [PATCH 239/588] dynamically find daemon script --- ahk/_async/transport.py | 3 ++- ahk/_sync/transport.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 4522326d..9f8c599a 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -396,7 +396,8 @@ async def init(self) -> None: async def start(self) -> None: assert self._proc is None, 'cannot start a process twice' - runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', 'ahk\\daemon.ahk'] # TODO: build this dynamically + daemon_script = os.path.abspath(os.path.join(os.path.dirname(__file__), '../daemon.ahk')) + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] self._proc = AsyncAHKProcess(runargs=runargs) await self._proc.start() diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 52115a45..29990472 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -386,7 +386,8 @@ def init(self) -> None: def start(self) -> None: assert self._proc is None, 'cannot start a process twice' - runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', 'ahk\\daemon.ahk'] # TODO: build this dynamically + daemon_script = os.path.abspath(os.path.join(os.path.dirname(__file__), '../daemon.ahk')) + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] self._proc = SyncAHKProcess(runargs=runargs) self._proc.start() From 715f01a37574b927b1027705444be98b1f744202 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Jul 2022 15:08:52 -0700 Subject: [PATCH 240/588] tox --- .github/workflows/test.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 997631e6..d5757916 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,12 +21,13 @@ jobs: python -m pip install --upgrade pip python -m pip install -r requirements-dev.txt python -m pip install . + python -m pip install tox python -m pip install ahk-binary - name: Test with coverage/pytest env: PYTHONUNBUFFERED: "1" run: | - coverage run -m pytest -s + tox -e py - name: Coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 96b041ae8a462711360804e335726a86ec126ce1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Jul 2022 15:21:23 -0700 Subject: [PATCH 241/588] pass ci env in tox --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fda74555..de39331f 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = py38,py39,py310 [testenv] deps = -rrequirements-dev.txt - +passenv = CI commands = coverage run -m pytest -s mypy --strict ahk From 270914d947cad39105e3f292db11d8b47e1268ce Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Jul 2022 15:35:10 -0700 Subject: [PATCH 242/588] fix for pre-commit on non-windows environments --- .unasync-rewrite.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.unasync-rewrite.py b/.unasync-rewrite.py index 64ef6238..855ed7fd 100644 --- a/.unasync-rewrite.py +++ b/.unasync-rewrite.py @@ -2,6 +2,7 @@ import os import shutil import subprocess +import sys import black from black import check_stability_and_equivalence @@ -59,13 +60,13 @@ def _copyfunc(src, dst, *, follow_symlinks=True): def main() -> int: if os.path.isdir('build'): shutil.rmtree('build') - subprocess.run(['python', 'setup.py', 'build_py'], check=True, shell=True) + subprocess.run([sys.executable, 'setup.py', 'build_py'], check=True) for root, dirs, files in os.walk('build/lib/ahk/_sync'): for fname in files: if fname.endswith('.py'): fp = os.path.join(root, fname) _rewrite_file(fp) - subprocess.run(['python', '_tests_setup.py', 'build_py'], check=True, shell=True) + subprocess.run([sys.executable, '_tests_setup.py', 'build_py'], check=True) for root, dirs, files in os.walk('build/lib/tests/_sync'): for fname in files: if fname.endswith('.py'): From 9fbbafab93d48d142a885d8c1628b35852228e58 Mon Sep 17 00:00:00 2001 From: forestsource Date: Wed, 20 Jul 2022 22:14:06 +0900 Subject: [PATCH 243/588] fix typo slient to silent --- ahk/gui.py | 32 ++++++++++++++++---------------- docs/README.md | 2 +- tests/unittests/test_gui.py | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ahk/gui.py b/ahk/gui.py index 1d14431a..4631898c 100644 --- a/ahk/gui.py +++ b/ahk/gui.py @@ -36,7 +36,7 @@ def show_tooltip(self, text: str, second=1.0, x='', y='', id='', blocking=True): return self.run_script(script, blocking=blocking) or None def _show_traytip( - self, title: str, text: str, second=1.0, type_id=1, slient=False, large_icon=False, blocking=True + self, title: str, text: str, second=1.0, type_id=1, silent=False, large_icon=False, blocking=True ): """Show TrayTip (Windows 10 toast notification) @@ -50,21 +50,21 @@ def _show_traytip( :type second: float, optional :param type_id: Notification type `TRAYTIP_`, defaults to 1 :type type_id: int, optional - :param slient: Shows toast without sound, defaults to False - :type slient: bool, optional + :param silent: Shows toast without sound, defaults to False + :type silent: bool, optional :param large_icon: Shows toast with large icon, defaults to False :type large_icon: bool, optional """ encoded_title = '% ' + ''.join([f'Chr({hex(ord(char))})' for char in title]) encoded_text = '% ' + ''.join([f'Chr({hex(ord(char))})' for char in text]) - option = type_id + (16 if slient else 0) + (32 if large_icon else 0) + option = type_id + (16 if silent else 0) + (32 if large_icon else 0) script = self.render_template( 'gui/traytip.ahk', title=encoded_title, text=encoded_text, second=second, option=option ) return self.run_script(script, blocking=blocking) or None - def show_info_traytip(self, title: str, text: str, second=1.0, slient=False, large_icon=False, blocking=True): + def show_info_traytip(self, title: str, text: str, second=1.0, silent=False, large_icon=False, blocking=True): """Show TrayTip with info icon (Windows 10 toast notification) https://www.autohotkey.com/docs/commands/TrayTip.htm @@ -75,16 +75,16 @@ def show_info_traytip(self, title: str, text: str, second=1.0, slient=False, lar :type text: str :param second: Wait time (s) to be disappeared, defaults to 1.0 :type second: float, optional - :param slient: Shows toast without sound, defaults to False - :type slient: bool, optional + :param silent: Shows toast without sound, defaults to False + :type silent: bool, optional :param large_icon: Shows toast with large icon, defaults to False :type large_icon: bool, optional :param blocked: Block program, defaults to True :type blocked: bool, optional """ - return self._show_traytip(title, text, second, self.TRAYTIP_INFO, slient, large_icon, blocking) + return self._show_traytip(title, text, second, self.TRAYTIP_INFO, silent, large_icon, blocking) - def show_warning_traytip(self, title: str, text: str, second=1.0, slient=False, large_icon=False, blocking=True): + def show_warning_traytip(self, title: str, text: str, second=1.0, silent=False, large_icon=False, blocking=True): """Show TrayTip with warning icon (Windows 10 toast notification) https://www.autohotkey.com/docs/commands/TrayTip.htm @@ -95,16 +95,16 @@ def show_warning_traytip(self, title: str, text: str, second=1.0, slient=False, :type text: str :param second: Wait time (s) to be disappeared, defaults to 1.0 :type second: float, optional - :param slient: Shows toast without sound, defaults to False - :type slient: bool, optional + :param silent: Shows toast without sound, defaults to False + :type silent: bool, optional :param large_icon: Shows toast with large icon, defaults to False :type large_icon: bool, optional :param blocked: Block program, defaults to True :type blocked: bool, optional """ - return self._show_traytip(title, text, second, self.TRAYTIP_WARNING, slient, large_icon, blocking) + return self._show_traytip(title, text, second, self.TRAYTIP_WARNING, silent, large_icon, blocking) - def show_error_traytip(self, title: str, text: str, second=1.0, slient=False, large_icon=False, blocking=True): + def show_error_traytip(self, title: str, text: str, second=1.0, silent=False, large_icon=False, blocking=True): """Show TrayTip with error icon (Windows 10 toast notification) https://www.autohotkey.com/docs/commands/TrayTip.htm @@ -115,14 +115,14 @@ def show_error_traytip(self, title: str, text: str, second=1.0, slient=False, la :type text: str :param second: Wait time (s) to be disappeared, defaults to 1.0 :type second: float, optional - :param slient: Shows toast without sound, defaults to False - :type slient: bool, optional + :param silent: Shows toast without sound, defaults to False + :type silent: bool, optional :param large_icon: Shows toast with large icon, defaults to False :type large_icon: bool, optional :param blocked: Block program, defaults to True :type blocked: bool, optional """ - return self._show_traytip(title, text, second, self.TRAYTIP_ERROR, slient, large_icon, blocking) + return self._show_traytip(title, text, second, self.TRAYTIP_ERROR, silent, large_icon, blocking) class AsyncGUIMixin(AsyncScriptEngine, GUIMixin): diff --git a/docs/README.md b/docs/README.md index 47f6d388..822e71e1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -189,7 +189,7 @@ from ahk import AHK ahk = AHK() ahk.show_tooltip("hello4", second=2, x=10, y=10) # ToolTip -ahk.show_info_traytip("Info", "It's also info", slient=False, blocking=True) # Default info traytip +ahk.show_info_traytip("Info", "It's also info", silent=False, blocking=True) # Default info traytip ahk.show_warning_traytip("Warning", "It's warning") # Warning traytip ahk.show_error_traytip("Error", "It's error") # Error trytip ``` diff --git a/tests/unittests/test_gui.py b/tests/unittests/test_gui.py index de1fa5be..21c011b9 100644 --- a/tests/unittests/test_gui.py +++ b/tests/unittests/test_gui.py @@ -24,5 +24,5 @@ def test_show_traytip(self, ahk: AHK): ahk.show_info_traytip('Info', "It's also info") ahk.show_warning_traytip('Warning', "It's warning") ahk.show_error_traytip('Error', "It's error") - ahk._show_traytip('Slient - Info', "It's info", type_id=ahk.TRAYTIP_INFO, slient=True) + ahk._show_traytip('silent - Info', "It's info", type_id=ahk.TRAYTIP_INFO, silent=True) ahk.show_info_traytip('Unicode Threaded', 'şüğı', blocking=False) # Need help From 53076c7caa626066faa97d6537232418e79f6ebf Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Jul 2022 15:16:08 -0700 Subject: [PATCH 244/588] nonblocking! --- ahk/__init__.py | 4 +- ahk/_async/__init__.py | 3 +- ahk/_async/engine.py | 336 ++++++++++++++++----------------------- ahk/_async/transport.py | 270 +++++++++++++++++++++---------- ahk/_async/window.py | 2 +- ahk/_sync/__init__.py | 3 +- ahk/_sync/engine.py | 341 +++++++++++++++++----------------------- ahk/_sync/transport.py | 253 +++++++++++++++++++---------- ahk/_sync/window.py | 2 +- ahk/daemon.ahk | 10 +- ahk/message.py | 93 +++++++++-- buildunasync.py | 1 + tests/message_test.py | 2 +- 13 files changed, 731 insertions(+), 589 deletions(-) diff --git a/ahk/__init__.py b/ahk/__init__.py index 301b27fc..e7365480 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -1,6 +1,8 @@ from ._async import AsyncAHK +from ._async import AsyncControl from ._async import AsyncWindow from ._sync import AHK +from ._sync import SyncControl from ._sync import Window -__all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK'] +__all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK', 'SyncControl', 'AsyncControl'] diff --git a/ahk/_async/__init__.py b/ahk/_async/__init__.py index 8d39056e..139416a1 100644 --- a/ahk/_async/__init__.py +++ b/ahk/_async/__init__.py @@ -1,4 +1,5 @@ from .engine import AsyncAHK +from .window import AsyncControl from .window import AsyncWindow -__all__ = ['AsyncAHK', 'AsyncWindow'] +__all__ = ['AsyncAHK', 'AsyncWindow', 'AsyncControl'] diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 4fa4334f..b90a091f 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio from typing import Any from typing import Callable from typing import Iterable @@ -13,13 +14,8 @@ from typing import TYPE_CHECKING from typing import Union -from ..message import IntegerResponseMessage -from ..message import is_winget_response_type -from ..message import NoValueResponseMessage -from ..message import StringResponseMessage -from ..message import WindowControlListResponseMessage -from ..message import WindowIDListResponseMessage from .transport import AsyncDaemonProcessTransport +from .transport import AsyncFutureResult from .transport import AsyncTransport from .window import AsyncControl from .window import AsyncWindow @@ -66,51 +62,32 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) - async def list_windows(self) -> List[AsyncWindow]: - resp = await self._transport.function_call('WindowList') - window_ids = resp.unpack() - ret = [AsyncWindow(engine=self, ahk_id=ahk_id) for ahk_id in window_ids] - return ret - - async def get_mouse_position(self) -> Tuple[int, int]: - resp = await self._transport.function_call('MouseGetPos') - return resp.unpack() + async def list_windows(self) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: + resp = await self._transport.function_call('WindowList', engine=self) + return resp + # 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, - ) -> None: - ... - + async def get_mouse_position(self, *, blocking: Literal[True]) -> Tuple[int, int]: ... @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, - ) -> None: - ... - + async def get_mouse_position(self, *, blocking: Literal[False]) -> AsyncFutureResult[Tuple[int, int]]: ... @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, - ) -> FutureResult: - ... + async def get_mouse_position(self) -> Tuple[int, int]: ... + # fmt: on + async def get_mouse_position( + self, *, blocking: bool = True + ) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: + resp = await self._transport.function_call('MouseGetPos', blocking=blocking) + return resp + # 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) -> 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) -> 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, ) -> AsyncFutureResult[None]: ... + # fmt: on async def mouse_move( self, x: Optional[Union[str, int]] = None, @@ -118,8 +95,8 @@ async def mouse_move( *, speed: Optional[int] = None, relative: bool = False, - blocking: Optional[Union[Literal[True], Literal[False]]] = None, - ) -> Union[None, FutureResult]: + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: if relative and (x is None or y is None): x = x or 0 y = y or 0 @@ -133,14 +110,8 @@ async def mouse_move( args = [str(x), str(y), str(speed)] if relative: args.append('R') - if blocking in (True, None): - resp = await self._transport.function_call('MouseMove', args) - resp.unpack() - return None - elif blocking is False: - return FutureResult() - else: - raise ValueError(f'Invalid value for argument blocking: {blocking!r}') + resp = await self._transport.function_call('MouseMove', args, blocking=blocking) + return resp async def a_run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() @@ -309,168 +280,138 @@ async def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[StringResponseMessage, NoValueResponseMessage]: ... + async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[AsyncWindow, None]: ... @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... - @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... - @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... + # fmt: on + async def win_get( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[None, AsyncWindow]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = await self._transport.function_call('AHKWinGetID', args, blocking=blocking, engine=self) + return resp + + # fmt: off @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetCount'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[AsyncWindow, None]: ... @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowIDListResponseMessage: ... + async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetMinMax'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... + # fmt: on + async def win_get_idlast( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[AsyncWindow, None]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = await self._transport.function_call('AHKWinGetIDLast', args, blocking=blocking) + return resp + + # fmt: off @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetControlList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[int, None]: ... @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetControlListHwnd'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[int, None]]: ... @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetTransparent'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[int, None]: ... + # fmt: on + async def win_get_pid( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[int, None, AsyncFutureResult[Union[int, None]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = await self._transport.function_call('AHKWinGetPID', args, blocking=blocking) + return resp + + # fmt: off @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetTransColor'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[str, None]: ... @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[str, None]]: ... @overload - async def _win_get(self, subcommand_function: Literal['AHKWinGetExStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + async def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[str, None]: ... # fmt: on - - async def _win_get( - self, - subcommand_function: WinGetFunctions, - /, - title: str = '', - text: str = '', - exclude_title: str = '', - exclude_text: str = '', - ) -> Union[ - StringResponseMessage, - IntegerResponseMessage, - WindowIDListResponseMessage, - WindowControlListResponseMessage, - NoValueResponseMessage, - ]: - + async def win_get_process_name( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[None, str, AsyncFutureResult[Optional[str]]]: args = [title, text, exclude_title, exclude_title, exclude_text] - resp = await self._transport.function_call(subcommand_function, args) - if TYPE_CHECKING: - assert is_winget_response_type(resp), f'Unexpected response: {resp!r}' + resp = await self._transport.function_call('AHKWinGetProcessName', args, blocking=blocking) return resp - async def win_get( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[AsyncWindow, None]: - resp = await self._win_get( - 'AHKWinGetID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - win_id = resp.unpack() - if win_id is None: - return None - else: - return AsyncWindow(engine=self, ahk_id=win_id) - - async def win_get_idlast( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[AsyncWindow, None]: - resp = await self._win_get( - 'AHKWinGetIDLast', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - win_id = resp.unpack() - if win_id is None: - return None - else: - return AsyncWindow(engine=self, ahk_id=win_id) - - async def win_get_pid( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[int, None]: - resp = await self._win_get( - 'AHKWinGetPID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - pid = resp.unpack() - if pid is None: - return None - else: - return pid - - async def win_get_process_name( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[str, None]: - resp = await self._win_get( - 'AHKWinGetProcessName', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - process_name = resp.unpack() - if process_name is None: - return None - else: - return process_name - + # fmt: off + @overload + async def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[str, None]: ... + @overload + async def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> Union[str, None]: ... + # fmt: on async def win_get_process_path( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[str, None]: - resp = await self._win_get( - 'AHKWinGetProcessPath', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - process_path = resp.unpack() - if process_path is None: - return None - else: - return process_path + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[str, None, Union[None, str, AsyncFutureResult[Optional[str]]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + 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 = '') -> int: ... + @overload + async def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[int]: ... + @overload + async def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> int: ... + # fmt: on async def win_get_count( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> int: - resp = await self._win_get( - 'AHKWinGetCount', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - return resp.unpack() + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[int, AsyncFutureResult[int]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + 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 = '') -> Union[int, None]: ... + @overload + async def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[int, None]]: ... + @overload + async def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[int, None]: ... + # fmt: on async def win_get_minmax( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[Literal[0], Literal[1], Literal[-1], None]: - - resp = await self._win_get( - 'AHKWinGetMinMax', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - val = resp.unpack() - if val is None: - return None - if val == -1: - return -1 - elif val == 0: - return 0 - elif val == 1: - return 1 - else: - raise ValueError(f'Unexpected value for minmax: {val!r}') + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[None, int, AsyncFutureResult[Optional[int]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + 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 = '') -> Union[List[AsyncControl], None]: ... + @overload + async def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> Union[List[AsyncControl], None]: ... + # fmt: on async def win_get_control_list( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[Sequence[AsyncControl], None]: - resp = await self._win_get( - 'AHKWinGetControlList', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - val = resp.unpack() - if val is None: - return None - ahkid, controls = val - window = AsyncWindow(engine=self, ahk_id=ahkid) - ret = [] - for control in controls: - hwnd, classname = control - ctrl = AsyncControl(window=window, hwnd=hwnd, control_class=classname) - ret.append(ctrl) - return ret + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[List[AsyncControl], None, AsyncFutureResult[Optional[List[AsyncControl]]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = await self._transport.function_call('AHKWinGetControlList', args, blocking=blocking) + return resp + # fmt: off + @overload + async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> bool: ... + @overload + async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + @overload + async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + # fmt: on async def win_exists( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> bool: + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[bool, AsyncFutureResult[bool]]: args = [title, text, exclude_title, exclude_text] - resp = await self._transport.function_call('AHKWinExist', args) - return resp.unpack() + resp = await self._transport.function_call('AHKWinExist', args, blocking=blocking) + return resp async def win_set(self, subcommand: str, *args: Any, blocking: bool = True) -> None: # TODO: type hint subcommand literals @@ -505,7 +446,7 @@ async def image_search( scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, - ) -> Union[Tuple[int, int], None]: + ) -> Union[Tuple[int, int], None, AsyncFutureResult[Optional[Tuple[int, int]]]]: """ https://www.autohotkey.com/docs/commands/ImageSearch.htm """ @@ -547,7 +488,7 @@ async def image_search( else: args.append(image_path) resp = await self._transport.function_call('ImageSearch', args) - return resp.unpack() + return resp async def mouse_drag( self, @@ -600,9 +541,8 @@ async def win_close( seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', - ) -> None: + ) -> Union[None, AsyncFutureResult[None]]: args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] resp = await self._transport.function_call('AHKWinClose', args=args) - resp.unpack() - return None + return resp diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 9f8c599a..73e20082 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -13,31 +13,28 @@ from typing import Any from typing import AnyStr from typing import Callable +from typing import Coroutine 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_CHECKING from typing import Union +if TYPE_CHECKING: + from ahk import AsyncControl + from ahk import AsyncWindow + if sys.version_info < (3, 10): - from typing_extensions import TypeAlias + from typing_extensions import TypeAlias, TypeGuard else: - from typing import TypeAlias + from typing import TypeAlias, TypeGuard from ahk.hotkey import ThreadedHotkeyTransport -from ahk.message import BooleanResponseMessage -from ahk.message import CoordinateResponseMessage -from ahk.message import IntegerResponseMessage -from ahk.message import NoValueResponseMessage -from ahk.message import RequestMessage -from ahk.message import ResponseMessage -from ahk.message import ResponseMessageTypes -from ahk.message import StringResponseMessage -from ahk.message import TupleResponseMessage -from ahk.message import WindowControlListResponseMessage -from ahk.message import WindowIDListResponseMessage +from concurrent.futures import Future, ThreadPoolExecutor DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' @@ -46,6 +43,10 @@ SyncIOProcess: TypeAlias = 'subprocess.Popen[bytes]' +AsyncFutureResult: TypeAlias = asyncio.Task # unasync: remove + +SyncFutureResult: TypeAlias = Future + FunctionName = Literal[ Literal['AHKWinExist'], Literal['ImageSearch'], @@ -119,6 +120,14 @@ def kill(proc: Killable) -> None: pass +def async_assert_send_nonblocking_type_correct( + obj: Any, +) -> TypeGuard[ + Future[Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]] +]: + return True + + class AsyncAHKProcess: def __init__(self, runargs: List[str]): self.runargs = runargs @@ -234,128 +243,128 @@ async def init(self) -> None: # fmt: off @overload - async def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... - @overload - async def function_call(self, function_name: Literal['ImageSearch'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[bool, AsyncFutureResult[bool]]: ... @overload - async def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['ImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], None, AsyncFutureResult[Union[Tuple[int, int], None]]]: ... @overload - async def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... + async def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload - async def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... + async def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... @overload - async def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... + async def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... @overload - async def function_call(self, function_name: Literal['MouseMove'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[bool, AsyncFutureResult[bool]]: ... @overload - async def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['MouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['Click'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['Click'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[int, AsyncFutureResult[int]]: ... @overload - async def function_call(self, function_name: Literal['Send'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SendRaw'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['Send'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SendInput'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SendEvent'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SendInput'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SendPlay'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SendEvent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['SendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload - async def function_call(self, function_name: Literal['WinGetText'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload - async def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload - async def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinKill'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinMinimize'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinRestore'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinMinimize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WindowList'], args: Optional[List[str]] = None) -> WindowIDListResponseMessage: ... + async def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... @overload - async def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['ControlSend'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['ControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload - async def function_call(self, function_name: Literal['WinSet'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload - async def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinSet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... + async def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[bool, AsyncFutureResult[bool]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + async def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None) -> TupleResponseMessage: ... - @overload - async def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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) = None -> Union[TupleResponseMessage, AsyncFutureResult[TupleResponseMessage]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[None, AsyncWindow], AsyncFutureResult[Union[None, AsyncWindow]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[List[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[None, AsyncWindow], AsyncFutureResult[Union[None, AsyncWindow]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[int, None], AsyncFutureResult[Union[int, None]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + async def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[List[str]] = None) -> WindowIDListResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[int, AsyncFutureResult[int]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None) -> WindowControlListResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[None, int], AsyncFutureResult[Union[None, int]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[List[str]] = None) -> WindowControlListResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncControl], None, AsyncFutureResult[Union[List[AsyncControl], None]]]: ... + # @overload + # async def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncControl], AsyncFutureResult[List[AsyncControl]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[None, int], AsyncFutureResult[Union[None, int]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... @overload - async def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + async def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... # @overload - # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['WinWait'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWait'], args: Optional[List[str]] = None) -> str: ... # @overload - # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[List[str]] = None) -> str: ... # @overload - # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[List[str]] = None) -> str: ... # @overload # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[List[str]] = None) -> None: ... # @overload @@ -370,16 +379,40 @@ async def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: # fmt: on async def function_call( - self, function_name: FunctionName, args: Optional[List[str]] = None - ) -> ResponseMessageTypes: + self, + function_name: FunctionName, + args: Optional[List[str]] = None, + blocking: bool = True, + engine: Optional[AsyncAHK] = None, + ) -> Any: if not self._started: await self.init() request = RequestMessage(function_name=function_name, args=args) - resp = await self.send(request) - return resp + 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] = None + ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: + return NotImplemented + + @abstractmethod # unasync: remove + async def a_send_nonblocking( # unasync: remove + self, request: RequestMessage, engine: Optional[AsyncAHK] = None + ) -> AsyncFutureResult[ + Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] + ]: + return NotImplemented @abstractmethod - async def send(self, request: RequestMessage) -> ResponseMessageTypes: + def send_nonblocking( + self, request: RequestMessage, engine: Optional[AsyncAHK] = None + ) -> SyncFutureResult[ + Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] + ]: return NotImplemented @@ -401,7 +434,69 @@ async def start(self) -> None: self._proc = AsyncAHKProcess(runargs=runargs) await self._proc.start() - async def send(self, request: RequestMessage) -> ResponseMessageTypes: + async def _create_process(self) -> AsyncAHKProcess: + daemon_script = os.path.abspath(os.path.join(os.path.dirname(__file__), '../daemon.ahk')) + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] + proc = AsyncAHKProcess(runargs=runargs) + await proc.start() + return proc + + async def _send_nonblocking( + self, request: RequestMessage, engine: Optional[AsyncAHK] = None + ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: + newline = '\n' + + msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( + 'utf-8' + ) + proc = await self._create_process() + try: + 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) + for _ in range(int(num_lines) + 1): + part = await proc.readline() + content_buffer.write(part) + content = content_buffer.getvalue()[:-1] + except Exception: + raise + finally: + try: + proc.kill() + except: + pass + 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] = None + ) -> AsyncFutureResult[ + Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] + ]: + loop = asyncio.get_running_loop() + return loop.create_task(self._send_nonblocking(request=request, engine=engine)) + + def send_nonblocking( + self, request: RequestMessage, engine: Optional[AsyncAHK] = None + ) -> SyncFutureResult[ + 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 fut + + async def send( + self, request: RequestMessage, engine: Optional[AsyncAHK] = None + ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: newline = '\n' msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( @@ -419,5 +514,14 @@ async def send(self, request: RequestMessage) -> ResponseMessageTypes: part = await self._proc.readline() content_buffer.write(part) content = content_buffer.getvalue()[:-1] - response = ResponseMessage.from_bytes(content) - return response + response = ResponseMessage.from_bytes(content, engine=engine) + return response.unpack() # type: ignore + + +from ahk.message import RequestMessage +from ahk.message import ResponseMessage + + +if TYPE_CHECKING: + from .engine import AsyncAHK + from ahk import AHK diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 00d21164..df1f4f47 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -62,7 +62,7 @@ async def get_process_path(self) -> str: ) return path - async def get_minmax(self) -> Union[Literal[0], Literal[1], Literal[-1]]: + async def get_minmax(self) -> int: minmax = await self._engine.win_get_minmax(title=f'ahk_id {self._ahk_id}') if minmax is None: raise WindowNotFoundException( diff --git a/ahk/_sync/__init__.py b/ahk/_sync/__init__.py index ccc5c780..33cc49dc 100644 --- a/ahk/_sync/__init__.py +++ b/ahk/_sync/__init__.py @@ -1,3 +1,4 @@ from .engine import AHK +from .window import SyncControl from .window import Window -__all__ =['AHK', 'Window'] +__all__ =['AHK', 'Window', 'SyncControl'] diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index e0213d13..3ea6c9c9 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio from typing import Any from typing import Callable from typing import Iterable @@ -13,13 +14,8 @@ from typing import TYPE_CHECKING from typing import Union -from ..message import IntegerResponseMessage -from ..message import is_winget_response_type -from ..message import NoValueResponseMessage -from ..message import StringResponseMessage -from ..message import WindowControlListResponseMessage -from ..message import WindowIDListResponseMessage from .transport import DaemonProcessTransport +from .transport import SyncFutureResult from .transport import Transport from .window import SyncControl from .window import Window @@ -58,56 +54,40 @@ def __init__(self, *, TransportClass: Optional[Type[Transport]] = None, **transp transport = TransportClass(**transport_kwargs) self._transport: Transport = transport - def add_hotkey(self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None) -> None: + def add_hotkey( + self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None + ) -> None: return self._transport.add_hotkey(hotkey=hotkey, callback=callback, ex_handler=ex_handler) + def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) - def list_windows(self) -> List[Window]: - resp = self._transport.function_call('WindowList') - window_ids = resp.unpack() - ret = [Window(engine=self, ahk_id=ahk_id) for ahk_id in window_ids] - return ret - - def get_mouse_position(self) -> Tuple[int, int]: - resp = self._transport.function_call('MouseGetPos') - return resp.unpack() + def list_windows(self) -> Union[List[Window], SyncFutureResult[List[Window]]]: + resp = self._transport.function_call('WindowList', engine=self) + return resp + # 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, - ) -> None: - ... - + def get_mouse_position(self, *, blocking: Literal[True]) -> Tuple[int, int]: ... @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, - ) -> None: - ... - + def get_mouse_position(self, *, blocking: Literal[False]) -> SyncFutureResult[Tuple[int, int]]: ... @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, - ) -> FutureResult: - ... + def get_mouse_position(self) -> Tuple[int, int]: ... + # fmt: on + def get_mouse_position( + self, *, blocking: bool = True + ) -> Union[Tuple[int, int], SyncFutureResult[Tuple[int, int]]]: + resp = self._transport.function_call('MouseGetPos', blocking=blocking) + return resp + # 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) -> 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) -> 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, ) -> SyncFutureResult[None]: ... + # fmt: on def mouse_move( self, x: Optional[Union[str, int]] = None, @@ -115,8 +95,8 @@ def mouse_move( *, speed: Optional[int] = None, relative: bool = False, - blocking: Optional[Union[Literal[True], Literal[False]]] = None, - ) -> Union[None, FutureResult]: + blocking: bool = True, + ) -> Union[None, SyncFutureResult[None]]: if relative and (x is None or y is None): x = x or 0 y = y or 0 @@ -130,14 +110,8 @@ def mouse_move( args = [str(x), str(y), str(speed)] if relative: args.append('R') - if blocking in (True, None): - resp = self._transport.function_call('MouseMove', args) - resp.unpack() - return None - elif blocking is False: - return FutureResult() - else: - raise ValueError(f'Invalid value for argument blocking: {blocking!r}') + resp = self._transport.function_call('MouseMove', args, blocking=blocking) + return resp def a_run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() @@ -306,168 +280,138 @@ def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[StringResponseMessage, NoValueResponseMessage]: ... - @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetIDLast'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... - @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetPID'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... + def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[Window, None]: ... @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetProcessName'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[Window, None]]: ... @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetProcessPath'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[Window, None]: ... + # fmt: on + def win_get( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[Window, None, SyncFutureResult[Union[None, Window]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = self._transport.function_call('AHKWinGetID', args, blocking=blocking, engine=self) + return resp + + # fmt: off @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetCount'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[Window, None]: ... @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowIDListResponseMessage: ... + def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[Window, None]]: ... @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetMinMax'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[Window, None]: ... + # fmt: on + def win_get_idlast( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[Window, None, SyncFutureResult[Union[Window, None]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = self._transport.function_call('AHKWinGetIDLast', args, blocking=blocking) + return resp + + # fmt: off @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetControlList'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[int, None]: ... @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetControlListHwnd'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> WindowControlListResponseMessage: ... + def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[int, None]]: ... @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetTransparent'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> IntegerResponseMessage: ... + def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[int, None]: ... + # fmt: on + def win_get_pid( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[int, None, SyncFutureResult[Union[int, None]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = self._transport.function_call('AHKWinGetPID', args, blocking=blocking) + return resp + + # fmt: off @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetTransColor'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[str, None]: ... @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[str, None]]: ... @overload - def _win_get(self, subcommand_function: Literal['AHKWinGetExStyle'], /, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> StringResponseMessage: ... + def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[str, None]: ... # fmt: on - - def _win_get( - self, - subcommand_function: WinGetFunctions, - /, - title: str = '', - text: str = '', - exclude_title: str = '', - exclude_text: str = '', - ) -> Union[ - StringResponseMessage, - IntegerResponseMessage, - WindowIDListResponseMessage, - WindowControlListResponseMessage, - NoValueResponseMessage, - ]: - + def win_get_process_name( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[None, str, SyncFutureResult[Optional[str]]]: args = [title, text, exclude_title, exclude_title, exclude_text] - resp = self._transport.function_call(subcommand_function, args) - if TYPE_CHECKING: - assert is_winget_response_type(resp), f'Unexpected response: {resp!r}' + resp = self._transport.function_call('AHKWinGetProcessName', args, blocking=blocking) return resp - def win_get( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[Window, None]: - resp = self._win_get( - 'AHKWinGetID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - win_id = resp.unpack() - if win_id is None: - return None - else: - return Window(engine=self, ahk_id=win_id) - - def win_get_idlast( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[Window, None]: - resp = self._win_get( - 'AHKWinGetIDLast', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - win_id = resp.unpack() - if win_id is None: - return None - else: - return Window(engine=self, ahk_id=win_id) - - def win_get_pid( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[int, None]: - resp = self._win_get( - 'AHKWinGetPID', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - pid = resp.unpack() - if pid is None: - return None - else: - return pid - - def win_get_process_name( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[str, None]: - resp = self._win_get( - 'AHKWinGetProcessName', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - process_name = resp.unpack() - if process_name is None: - return None - else: - return process_name - + # fmt: off + @overload + def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[str, None]: ... + @overload + def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[str, None]]: ... + @overload + def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[str, None]: ... + # fmt: on def win_get_process_path( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[str, None]: - resp = self._win_get( - 'AHKWinGetProcessPath', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - process_path = resp.unpack() - if process_path is None: - return None - else: - return process_path + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[str, None, Union[None, str, SyncFutureResult[Optional[str]]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + 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 = '') -> int: ... + @overload + def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[int]: ... + @overload + def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> int: ... + # fmt: on def win_get_count( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> int: - resp = self._win_get( - 'AHKWinGetCount', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - return resp.unpack() + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[int, SyncFutureResult[int]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + 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 = '') -> Union[int, None]: ... + @overload + def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[int, None]]: ... + @overload + def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[int, None]: ... + # fmt: on def win_get_minmax( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[Literal[0], Literal[1], Literal[-1], None]: - - resp = self._win_get( - 'AHKWinGetMinMax', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - val = resp.unpack() - if val is None: - return None - if val == -1: - return -1 - elif val == 0: - return 0 - elif val == 1: - return 1 - else: - raise ValueError(f'Unexpected value for minmax: {val!r}') + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[None, int, SyncFutureResult[Optional[int]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + 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 = '') -> Union[List[SyncControl], None]: ... + @overload + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[List[SyncControl], None]]: ... + @overload + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[List[SyncControl], None]: ... + # fmt: on def win_get_control_list( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> Union[Sequence[SyncControl], None]: - resp = self._win_get( - 'AHKWinGetControlList', title=title, text=text, exclude_title=exclude_title, exclude_text=exclude_text - ) - val = resp.unpack() - if val is None: - return None - ahkid, controls = val - window = Window(engine=self, ahk_id=ahkid) - ret = [] - for control in controls: - hwnd, classname = control - ctrl = SyncControl(window=window, hwnd=hwnd, control_class=classname) - ret.append(ctrl) - return ret + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[List[SyncControl], None, SyncFutureResult[Optional[List[SyncControl]]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = self._transport.function_call('AHKWinGetControlList', args, blocking=blocking) + return resp + # fmt: off + @overload + def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> bool: ... + @overload + def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + @overload + def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + # fmt: on def win_exists( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '' - ) -> bool: + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + ) -> Union[bool, SyncFutureResult[bool]]: args = [title, text, exclude_title, exclude_text] - resp = self._transport.function_call('AHKWinExist', args) - return resp.unpack() + resp = self._transport.function_call('AHKWinExist', args, blocking=blocking) + return resp def win_set(self, subcommand: str, *args: Any, blocking: bool = True) -> None: # TODO: type hint subcommand literals @@ -502,7 +446,7 @@ def image_search( scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, - ) -> Union[Tuple[int, int], None]: + ) -> Union[Tuple[int, int], None, SyncFutureResult[Optional[Tuple[int, int]]]]: """ https://www.autohotkey.com/docs/commands/ImageSearch.htm """ @@ -544,7 +488,7 @@ def image_search( else: args.append(image_path) resp = self._transport.function_call('ImageSearch', args) - return resp.unpack() + return resp def mouse_drag( self, @@ -597,9 +541,8 @@ def win_close( seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', - ) -> None: + ) -> Union[None, SyncFutureResult[None]]: args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] resp = self._transport.function_call('AHKWinClose', args=args) - resp.unpack() - return None + return resp diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 29990472..01b269f7 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -13,31 +13,28 @@ from typing import Any from typing import AnyStr from typing import Callable +from typing import Coroutine 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_CHECKING from typing import Union +if TYPE_CHECKING: + from ahk import SyncControl + from ahk import Window + if sys.version_info < (3, 10): - from typing_extensions import TypeAlias + from typing_extensions import TypeAlias, TypeGuard else: - from typing import TypeAlias + from typing import TypeAlias, TypeGuard from ahk.hotkey import ThreadedHotkeyTransport -from ahk.message import BooleanResponseMessage -from ahk.message import CoordinateResponseMessage -from ahk.message import IntegerResponseMessage -from ahk.message import NoValueResponseMessage -from ahk.message import RequestMessage -from ahk.message import ResponseMessage -from ahk.message import ResponseMessageTypes -from ahk.message import StringResponseMessage -from ahk.message import TupleResponseMessage -from ahk.message import WindowControlListResponseMessage -from ahk.message import WindowIDListResponseMessage +from concurrent.futures import Future, ThreadPoolExecutor DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' @@ -45,6 +42,9 @@ SyncIOProcess: TypeAlias = 'subprocess.Popen[bytes]' + +SyncFutureResult: TypeAlias = Future + FunctionName = Literal[ Literal['AHKWinExist'], Literal['ImageSearch'], @@ -118,6 +118,12 @@ def kill(proc: Killable) -> None: pass +def async_assert_send_nonblocking_type_correct( + obj: Any, +) -> TypeGuard[Future[Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]]]]: + return True + + class SyncAHKProcess: def __init__(self, runargs: List[str]): self.runargs = runargs @@ -224,128 +230,128 @@ def init(self) -> None: # fmt: off @overload - def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... - @overload - def function_call(self, function_name: Literal['ImageSearch'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... @overload - def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['ImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], None, SyncFutureResult[Union[Tuple[int, int], None]]]: ... @overload - def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... + def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[List[str]] = None) -> CoordinateResponseMessage: ... + def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], SyncFutureResult[Tuple[int, int]]]: ... @overload - def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... + def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], SyncFutureResult[Tuple[int, int]]]: ... @overload - def function_call(self, function_name: Literal['MouseMove'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... @overload - def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['MouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['Click'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['Click'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[int, SyncFutureResult[int]]: ... @overload - def function_call(self, function_name: Literal['Send'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SendRaw'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['Send'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SendInput'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SendEvent'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SendInput'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SendPlay'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SendEvent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['SendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinGetText'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinKill'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinMinimize'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinRestore'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinMinimize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WindowList'], args: Optional[List[str]] = None) -> WindowIDListResponseMessage: ... + def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[Window], SyncFutureResult[List[Window]]]: ... @overload - def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['ControlSend'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['ControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinSet'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinSet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[List[str]] = None) -> BooleanResponseMessage: ... + def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... @overload - def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None) -> TupleResponseMessage: ... - @overload - def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + # @overload + # async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK) = None -> Union[TupleResponseMessage, AsyncFutureResult[TupleResponseMessage]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, Window], SyncFutureResult[Union[None, Window]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[List[str]] = None) -> Union[IntegerResponseMessage, NoValueResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, Window], SyncFutureResult[Union[None, Window]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[int, None], SyncFutureResult[Union[int, None]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[List[str]] = None) -> Union[NoValueResponseMessage, StringResponseMessage]: ... + def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[List[str]] = None) -> WindowIDListResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[int, SyncFutureResult[int]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[Window], SyncFutureResult[List[Window]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None) -> WindowControlListResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, int], SyncFutureResult[Union[None, int]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[List[str]] = None) -> WindowControlListResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[SyncControl], None, SyncFutureResult[Union[List[SyncControl], None]]]: ... + # @overload + # async def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncControl], AsyncFutureResult[List[AsyncControl]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None) -> IntegerResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, int], SyncFutureResult[Union[None, int]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... # @overload - # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> NoValueResponseMessage: ... + # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload # async def function_call(self, function_name: Literal['BaseCheck'], args: Optional[List[str]] = None) -> None: ... # @overload - # async def function_call(self, function_name: Literal['WinWait'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWait'], args: Optional[List[str]] = None) -> str: ... # @overload - # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[List[str]] = None) -> str: ... # @overload - # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[List[str]] = None) -> StringResponseMessage: ... + # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[List[str]] = None) -> str: ... # @overload # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[List[str]] = None) -> None: ... # @overload @@ -360,16 +366,33 @@ def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Option # fmt: on def function_call( - self, function_name: FunctionName, args: Optional[List[str]] = None - ) -> ResponseMessageTypes: + self, + function_name: FunctionName, + args: Optional[List[str]] = None, + blocking: bool = True, + engine: Optional[AHK] = None, + ) -> Any: if not self._started: self.init() request = RequestMessage(function_name=function_name, args=args) - resp = self.send(request) - return resp + 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] = None + ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]]: + return NotImplemented + @abstractmethod - def send(self, request: RequestMessage) -> ResponseMessageTypes: + def send_nonblocking( + self, request: RequestMessage, engine: Optional[AHK] = None + ) -> SyncFutureResult[ + Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]] + ]: return NotImplemented @@ -391,7 +414,62 @@ def start(self) -> None: self._proc = SyncAHKProcess(runargs=runargs) self._proc.start() - def send(self, request: RequestMessage) -> ResponseMessageTypes: + def _create_process(self) -> SyncAHKProcess: + daemon_script = os.path.abspath(os.path.join(os.path.dirname(__file__), '../daemon.ahk')) + runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] + proc = SyncAHKProcess(runargs=runargs) + proc.start() + return proc + + def _send_nonblocking( + self, request: RequestMessage, engine: Optional[AHK] = None + ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]]: + newline = '\n' + + msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( + 'utf-8' + ) + proc = self._create_process() + try: + 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) + for _ in range(int(num_lines) + 1): + part = proc.readline() + content_buffer.write(part) + content = content_buffer.getvalue()[:-1] + except Exception: + raise + finally: + try: + proc.kill() + except: + pass + response = ResponseMessage.from_bytes(content, engine=engine) + return response.unpack() # type: ignore + + + def send_nonblocking( + self, request: RequestMessage, engine: Optional[AHK] = None + ) -> SyncFutureResult[ + Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]] + ]: + # 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 fut + + def send( + self, request: RequestMessage, engine: Optional[AHK] = None + ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]]: newline = '\n' msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( @@ -409,5 +487,14 @@ def send(self, request: RequestMessage) -> ResponseMessageTypes: part = self._proc.readline() content_buffer.write(part) content = content_buffer.getvalue()[:-1] - response = ResponseMessage.from_bytes(content) - return response + response = ResponseMessage.from_bytes(content, engine=engine) + return response.unpack() # type: ignore + + +from ahk.message import RequestMessage +from ahk.message import ResponseMessage + + +if TYPE_CHECKING: + from .engine import AHK + from ahk import AHK diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 5aeee276..11c249d7 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -62,7 +62,7 @@ def get_process_path(self) -> str: ) return path - def get_minmax(self) -> Union[Literal[0], Literal[1], Literal[-1]]: + def get_minmax(self) -> int: minmax = self._engine.win_get_minmax(title=f'ahk_id {self._ahk_id}') if minmax is None: raise WindowNotFoundException( diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 3b8bda19..ae8bbad5 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -12,7 +12,7 @@ WINDOWIDLISTRESPONSEMESSAGE := "006" ; WindowIDListResponseMessage NOVALUERESPONSEMESSAGE := "007" ; NoValueResponseMessage EXCEPTIONRESPONSEMESSAGE := "008" ; ExceptionResponseMessage WINDOWCONTROLLISTRESPONSEMESSAGE := "009" ; WindowControlListResponseMessage - +WINDOWRESPONSEMESSAGE := "00a" ; WindowResponseMessage NOVALUE_SENTINEL := Chr(57344) FormatResponse(MessageType, payload) { @@ -52,7 +52,7 @@ AHKWinClose(ByRef command) { } AHKWinGetID(ByRef command) { - global STRINGRESPONSEMESSAGE + global WINDOWRESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] @@ -61,14 +61,14 @@ AHKWinGetID(ByRef command) { if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) + response := FormatResponse(WINDOWRESPONSEMESSAGE, output) } return response } AHKWinGetIDLast(ByRef command) { - global STRINGRESPONSEMESSAGE + global WINDOWRESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] @@ -77,7 +77,7 @@ AHKWinGetIDLast(ByRef command) { if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) + response := FormatResponse(WINDOWRESPONSEMESSAGE, output) } return response } diff --git a/ahk/message.py b/ahk/message.py index 454b9fb8..28086213 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -15,6 +15,8 @@ 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 @@ -61,7 +63,7 @@ def is_winget_response_type( Union[ 'StringResponseMessage', 'IntegerResponseMessage', - 'WindowIDListResponseMessage', + 'WindowListResponseMessage', 'WindowControlListResponseMessage', ] ]: @@ -69,7 +71,7 @@ def is_winget_response_type( return True elif isinstance(obj, IntegerResponseMessage): return True - elif isinstance(obj, WindowIDListResponseMessage): + elif isinstance(obj, WindowListResponseMessage): return True elif isinstance(obj, WindowControlListResponseMessage): return True @@ -105,9 +107,9 @@ def __init_subclass__(cls: Type[T_ResponseMessageType], **kwargs: Any) -> None: assert cls.type is not None, f'must assign a type for class {cls!r}' super().__init_subclass__(**kwargs) - def __init__(self, raw_content: bytes): - self._raw_content: bytes - self._raw_content = raw_content + def __init__(self, raw_content: bytes, engine: Optional[Union[AsyncAHK, AHK]] = None): + self._raw_content: bytes = raw_content + self._engine: Optional[Union[AsyncAHK, AHK]] = engine def __repr__(self) -> str: return f'ResponseMessage' @@ -120,10 +122,12 @@ def _tom_lookup(tom: bytes) -> 'ResponseMessageClassTypes': return klass @classmethod - def from_bytes(cls: Type[T_ResponseMessageType], b: bytes) -> 'ResponseMessageTypes': + def from_bytes( + cls: Type[T_ResponseMessageType], b: bytes, engine: Optional[Union[AsyncAHK, AHK]] = None + ) -> 'ResponseMessageTypes': tom, _, message_bytes = b.split(b'\n', 2) klass = cls._tom_lookup(tom) - return klass(raw_content=message_bytes) + return klass(raw_content=message_bytes, engine=engine) def to_bytes(self) -> bytes: content_lines = self._raw_content.count(b'\n') @@ -185,13 +189,26 @@ def unpack(self) -> str: return self._raw_content.decode('utf-8') -class WindowIDListResponseMessage(ResponseMessage): - type = 'windowidlist' +class WindowListResponseMessage(ResponseMessage): + type = 'windowlist' + + def unpack(self) -> Union[List[Window], List[AsyncWindow]]: + from ._async.engine import AsyncAHK + from ._async.window import AsyncWindow, AsyncControl + from ._sync.window import Window, SyncControl + from ._sync.engine import AHK - def unpack(self) -> List[str]: s = self._raw_content.decode(encoding='utf-8') s = s.rstrip(',') - return s.split(',') + 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] + return async_ret + elif isinstance(self._engine, AHK): + ret = [Window(engine=self._engine, ahk_id=ahk_id) for ahk_id in window_ids] + return ret + else: + raise ValueError(f'Invalid engine: {self._engine!r}') class NoValueResponseMessage(ResponseMessage): @@ -217,11 +234,52 @@ def unpack(self) -> NoReturn: class WindowControlListResponseMessage(ResponseMessage): type = 'windowcontrollist' - def unpack(self) -> Tuple[str, List[Tuple[str, str]]]: + def unpack(self) -> Union[List[AsyncControl], List[SyncControl]]: s = self._raw_content.decode(encoding='utf-8') val = ast.literal_eval(s) assert is_window_control_list_response(val) - return 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[SyncControl] = [] + window = Window(engine=self._engine, ahk_id=ahkid) + for control in controls: + hwnd, classname = control + ctrl = SyncControl(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): + type = 'window' + + def unpack(self) -> Union[Window, AsyncWindow]: + from ._async.engine import AsyncAHK + from ._async.window import AsyncWindow, AsyncControl + from ._sync.window import Window, SyncControl + 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}') T_RequestMessageType = TypeVar('T_RequestMessageType', bound='RequestMessage') @@ -240,7 +298,7 @@ def __init__(self, function_name: str, args: Optional[List[str]] = None): IntegerResponseMessage, BooleanResponseMessage, StringResponseMessage, - WindowIDListResponseMessage, + WindowListResponseMessage, NoValueResponseMessage, WindowControlListResponseMessage, ExceptionResponseMessage, @@ -251,9 +309,14 @@ def __init__(self, function_name: str, args: Optional[List[str]] = None): Type[IntegerResponseMessage], Type[BooleanResponseMessage], Type[StringResponseMessage], - Type[WindowIDListResponseMessage], + 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, SyncControl + from ._sync.engine import AHK diff --git a/buildunasync.py b/buildunasync.py index 0a7b93cb..9ee951b1 100644 --- a/buildunasync.py +++ b/buildunasync.py @@ -13,6 +13,7 @@ '_AIOP': '_SIOP', 'async_create_process': 'sync_create_process', 'adrain_stdin': 'drain_stdin', + 'a_send_nonblocking': 'send_nonblocking' # "__aenter__": "__aenter__", }, ), diff --git a/tests/message_test.py b/tests/message_test.py index 85c27fb0..272229df 100644 --- a/tests/message_test.py +++ b/tests/message_test.py @@ -9,7 +9,7 @@ from ahk.message import ResponseMessage from ahk.message import StringResponseMessage from ahk.message import TupleResponseMessage -from ahk.message import WindowIDListResponseMessage +from ahk.message import WindowListResponseMessage def test_novalue_response_raises_exception_when_sentinel_not_present() -> None: From 382b51b4959d7a6ca24f2118b0d08431f6ef3e82 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Jul 2022 17:39:49 -0700 Subject: [PATCH 245/588] add overloads for implemented functions --- ahk/_async/engine.py | 34 +++++++++++++++++++++++++++++++--- ahk/_sync/engine.py | 31 ++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index b90a091f..93c0ef8a 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -62,8 +62,18 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) - async def list_windows(self) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: - resp = await self._transport.function_call('WindowList', engine=self) + # fmt: off + @overload + async def list_windows(self) -> List[AsyncWindow]: ... + @overload + async def list_windows(self, *, blocking: Literal[False]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + @overload + async def list_windows(self, *, blocking: Literal[True]) -> List[AsyncWindow]: ... + # fmt: on + async def list_windows( + self, blocking: bool = True + ) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: + resp = await self._transport.function_call('WindowList', engine=self, blocking=blocking) return resp # fmt: off @@ -434,6 +444,14 @@ async def click( ) -> None: raise NotImplementedError() + # 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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None) -> Optional[Tuple[int, int]]: ... + @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[Optional[Tuple[int, int]]]: ... + @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[True]) -> Optional[Tuple[int, int]]: ... + # fmt: on async def image_search( self, image_path: str, @@ -446,6 +464,7 @@ async def image_search( scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, + blocking: bool = True, ) -> Union[Tuple[int, int], None, AsyncFutureResult[Optional[Tuple[int, int]]]]: """ https://www.autohotkey.com/docs/commands/ImageSearch.htm @@ -533,6 +552,14 @@ async def show_traytip( ) -> None: raise NotImplementedError() + # fmt: off + @overload + async def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '') -> None: ... + @overload + async def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', 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 = '', blocking: Literal[False]) -> AsyncFutureResult[None]: ... + # fmt: on async def win_close( self, title: str = '', @@ -541,8 +568,9 @@ async def win_close( seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', + blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] - resp = await self._transport.function_call('AHKWinClose', args=args) + resp = await self._transport.function_call('AHKWinClose', args=args, blocking=blocking) return resp diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 3ea6c9c9..e4c02010 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -62,8 +62,16 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) - def list_windows(self) -> Union[List[Window], SyncFutureResult[List[Window]]]: - resp = self._transport.function_call('WindowList', engine=self) + # fmt: off + @overload + def list_windows(self) -> List[Window]: ... + @overload + def list_windows(self, *, blocking: Literal[False]) -> Union[List[Window], SyncFutureResult[List[Window]]]: ... + @overload + def list_windows(self, *, blocking: Literal[True]) -> List[Window]: ... + # fmt: on + def list_windows(self, blocking: bool = True) -> Union[List[Window], SyncFutureResult[List[Window]]]: + resp = self._transport.function_call('WindowList', engine=self, blocking=blocking) return resp # fmt: off @@ -434,6 +442,14 @@ def click( ) -> None: raise NotImplementedError() + # 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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None) -> Optional[Tuple[int, int]]: ... + @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[False]) -> SyncFutureResult[Optional[Tuple[int, int]]]: ... + @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[True]) -> Optional[Tuple[int, int]]: ... + # fmt: on def image_search( self, image_path: str, @@ -446,6 +462,7 @@ def image_search( scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, + blocking: bool = True, ) -> Union[Tuple[int, int], None, SyncFutureResult[Optional[Tuple[int, int]]]]: """ https://www.autohotkey.com/docs/commands/ImageSearch.htm @@ -533,6 +550,13 @@ def show_traytip( ) -> None: raise NotImplementedError() + @overload + def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '') -> None: ... + @overload + def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', 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 = '', blocking: Literal[False]) -> SyncFutureResult[None]: ... + def win_close( self, title: str = '', @@ -541,8 +565,9 @@ def win_close( seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', + blocking: bool = True ) -> Union[None, SyncFutureResult[None]]: args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] - resp = self._transport.function_call('AHKWinClose', args=args) + resp = self._transport.function_call('AHKWinClose', args=args, blocking=blocking) return resp From 6c2c27389a320a33206f37ab4fd57612e0d9cb3f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Jul 2022 18:11:24 -0700 Subject: [PATCH 246/588] add mouse tests --- ahk/daemon.ahk | 4 +--- tests/_async/test_mouse.py | 44 ++++++++++++++++++++++++++++++++++++++ tests/_sync/test_mouse.py | 44 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index ae8bbad5..8df3fc8c 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -361,14 +361,12 @@ AHKKeyState(ByRef command) { } MouseMove(ByRef command) { - global NOVALUERESPONSEMESSAGE - global NOVALUE_SENTINEL if (command.Length() = 5) { MouseMove, command[2], command[3], command[4], R } else { MouseMove, command[2], command[3], command[4] } - resp := FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) + resp := FormatNoValueResponse() return resp } diff --git a/tests/_async/test_mouse.py b/tests/_async/test_mouse.py index e19aad92..94b63e39 100644 --- a/tests/_async/test_mouse.py +++ b/tests/_async/test_mouse.py @@ -1 +1,45 @@ +import asyncio +import os +import subprocess +import sys +import time from unittest import IsolatedAsyncioTestCase + +from ahk import AsyncAHK +from ahk import AsyncWindow + + +class TestMouseAsync(IsolatedAsyncioTestCase): + win: AsyncWindow + + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK() + + 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) + pos = await self.ahk.get_mouse_position() + assert pos == (100, 100) + await self.ahk.mouse_move(x=10, y=10, relative=True) + await asyncio.sleep(1) + pos2 = await self.ahk.get_mouse_position() + x1, y1 = pos + x2, y2 = pos2 + await asyncio.sleep(1) + assert abs(x1 - x2) == 10 + assert abs(y1 - y2) == 10 diff --git a/tests/_sync/test_mouse.py b/tests/_sync/test_mouse.py index 2071d065..287904eb 100644 --- a/tests/_sync/test_mouse.py +++ b/tests/_sync/test_mouse.py @@ -1 +1,45 @@ +import asyncio +import os +import subprocess +import sys +import time from unittest import TestCase + +from ahk import AHK +from ahk import Window + + +class TestMouseAsync(TestCase): + win: Window + + def setUp(self) -> None: + self.ahk = AHK() + + 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) + pos = self.ahk.get_mouse_position() + assert pos == (100, 100) + self.ahk.mouse_move(x=10, y=10, relative=True) + asyncio.sleep(1) + pos2 = self.ahk.get_mouse_position() + x1, y1 = pos + x2, y2 = pos2 + asyncio.sleep(1) + assert abs(x1 - x2) == 10 + assert abs(y1 - y2) == 10 From 7dca6c73bee754c8ba699ad4135b269220420370 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Jul 2022 18:38:38 -0700 Subject: [PATCH 247/588] add nonblocking mouse tests --- _tests_setup.py | 3 ++- tests/_async/test_mouse.py | 20 ++++++++++++++++++-- tests/_sync/test_mouse.py | 17 +++++++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/_tests_setup.py b/_tests_setup.py index edd343fc..34639b64 100644 --- a/_tests_setup.py +++ b/_tests_setup.py @@ -29,7 +29,8 @@ 'adrain_stdin': 'drain_stdin', 'IsolatedAsyncioTestCase': 'TestCase', 'asyncSetUp': 'setUp', - 'asyncTearDown': 'tearDown' + 'asyncTearDown': 'tearDown', + 'async_sleep': 'sleep', # "__aenter__": "__aenter__", }, ), diff --git a/tests/_async/test_mouse.py b/tests/_async/test_mouse.py index 94b63e39..6bdc818e 100644 --- a/tests/_async/test_mouse.py +++ b/tests/_async/test_mouse.py @@ -8,6 +8,10 @@ from ahk import AsyncAHK from ahk import AsyncWindow +async_sleep = asyncio.sleep # unasync: remove + +sleep = time.sleep + class TestMouseAsync(IsolatedAsyncioTestCase): win: AsyncWindow @@ -33,13 +37,25 @@ async def test_mouse_move(self) -> None: 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 asyncio.sleep(1) + await async_sleep(0.5) pos2 = await self.ahk.get_mouse_position() x1, y1 = pos x2, y2 = pos2 - await asyncio.sleep(1) 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=5, 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 # unasync: remove + return # unasync: remove + sleep(1) diff --git a/tests/_sync/test_mouse.py b/tests/_sync/test_mouse.py index 287904eb..40cc7400 100644 --- a/tests/_sync/test_mouse.py +++ b/tests/_sync/test_mouse.py @@ -9,6 +9,9 @@ from ahk import Window +sleep = time.sleep + + class TestMouseAsync(TestCase): win: Window @@ -33,13 +36,23 @@ def test_mouse_move(self) -> None: 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) - asyncio.sleep(1) + sleep(0.5) pos2 = self.ahk.get_mouse_position() x1, y1 = pos x2, y2 = pos2 - asyncio.sleep(1) 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=5, 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) + sleep(1) From 6cebd49b3c313ed94cbb91986c0d83d0906d5868 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Jul 2022 21:30:39 -0700 Subject: [PATCH 248/588] keys, keyboard methods, tests --- .unasync-rewrite.py | 5 +- ahk/_async/engine.py | 126 ++++++++++++++++-- ahk/_async/transport.py | 6 + ahk/_sync/engine.py | 135 ++++++++++++++++--- ahk/_sync/transport.py | 10 +- ahk/daemon.ahk | 6 + ahk/hotkey.py | 17 +++ ahk/keys.py | 247 +++++++++++++++++++++++++++++++++++ tests/_async/test_hotkeys.py | 43 ++++++ 9 files changed, 562 insertions(+), 33 deletions(-) create mode 100644 ahk/keys.py create mode 100644 tests/_async/test_hotkeys.py diff --git a/.unasync-rewrite.py b/.unasync-rewrite.py index 855ed7fd..dc1f32d6 100644 --- a/.unasync-rewrite.py +++ b/.unasync-rewrite.py @@ -53,7 +53,10 @@ def _copyfunc(src, dst, *, follow_symlinks=True): 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) return dst diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 93c0ef8a..1cce59ef 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3,6 +3,7 @@ import asyncio from typing import Any from typing import Callable +from typing import Dict from typing import Iterable from typing import List from typing import Literal @@ -14,6 +15,7 @@ from typing import TYPE_CHECKING from typing import Union +from ..keys import Key from .transport import AsyncDaemonProcessTransport from .transport import AsyncFutureResult from .transport import AsyncTransport @@ -47,11 +49,18 @@ class FutureResult: class AsyncAHK: - def __init__(self, *, TransportClass: Optional[Type[AsyncTransport]] = None, **transport_kwargs: Any): + def __init__( + self, + *, + TransportClass: Optional[Type[AsyncTransport]] = None, + transport_options: Optional[Dict[str, Any]] = None, + ): + if transport_options is None: + transport_options = {} if TransportClass is None: TransportClass = AsyncDaemonProcessTransport assert TransportClass is not None - transport = TransportClass(**transport_kwargs) + transport = TransportClass(**transport_options) self._transport: AsyncTransport = transport def add_hotkey( @@ -62,6 +71,12 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + def start_hotkeys(self) -> None: + return self._transport.start_hotkeys() + + def stop_hotkeys(self) -> None: + return self._transport.stop_hotkeys() + # fmt: off @overload async def list_windows(self) -> List[AsyncWindow]: ... @@ -146,20 +161,77 @@ async def find_windows_by_title(self, title: str, exact: bool = False) -> Iterab async def get_volume(self, device_number: int = 1) -> float: raise NotImplementedError() - async def key_down(self, key: str, blocking: bool = True) -> None: - raise NotImplementedError() + # 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]: ... + # fmt: on + async def key_down(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + if isinstance(key, str): + key = Key(key_name=key) + if blocking: + return await self.send_input(key.DOWN, blocking=True) + else: + return await self.send_input(key.DOWN, blocking=False) - async def key_press(self, key: str, release: bool = True, blocking: bool = True) -> None: - raise NotImplementedError() + # 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]: ... + # fmt: on + async def key_press( + self, key: Union[str, Key], *, release: bool = True, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + if blocking: + d = await self.key_down(key, blocking=True) + if release: + return await self.key_up(key, blocking=True) + else: + return d + else: + await self.key_down(key, blocking=False) + if release: + await self.key_up(key, blocking=False) + return None - async def key_release(self, key: str, blocking: bool = True) -> None: - raise NotImplementedError() + # 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]: ... + # fmt: on + async def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + if blocking: + return await self.key_up(key=key, blocking=True) + else: + return await self.key_up(key=key, blocking=False) async def key_state(self, key_name: str, mode: Optional[Union[Literal['P'], Literal['T']]] = None) -> bool: raise NotImplementedError() - async def key_up(self, key: str, blocking: bool = True) -> None: - raise NotImplementedError() + # 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]: ... + # fmt: on + async def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + if isinstance(key, str): + key = Key(key_name=key) + if blocking: + return await self.send_input(key.UP, blocking=True) + else: + return await self.send_input(key.UP, blocking=False) async def key_wait( self, key_name: str, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False @@ -197,14 +269,40 @@ async def mouse_wheel( async def run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() - async def send(self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True) -> None: - raise NotImplementedError() + # fmt: off + @overload + async def send(self, s: str) -> None: ... + @overload + async def send(self, s: str, *, blocking: Literal[True]) -> None: ... + @overload + async def send(self, s: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + # fmt: on + async def send( + self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + args = [s] + if raw: + raw_resp = await self._transport.function_call('SendRaw', args=args, blocking=blocking) + return raw_resp + else: + resp = await self._transport.function_call('Send', args=args, blocking=blocking) + return resp async def send_event(self, s: str, delay: Optional[int] = None) -> None: raise NotImplementedError() - async def send_input(self, s: str, blocking: bool = True) -> None: - raise NotImplementedError() + # 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]: ... + # fmt: on + async def send_input(self, s: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + args = [s] + resp = await self._transport.function_call('SendInput', args, blocking=blocking) + return resp async def send_play(self, s: str) -> None: raise NotImplementedError() diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 73e20082..9c3ded66 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -237,6 +237,12 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._hotkey_transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + def start_hotkeys(self) -> None: + return self._hotkey_transport.start() + + def stop_hotkeys(self) -> None: + return self._hotkey_transport.stop() + async def init(self) -> None: self._started = True return None diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index e4c02010..117168eb 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3,6 +3,7 @@ import asyncio from typing import Any from typing import Callable +from typing import Dict from typing import Iterable from typing import List from typing import Literal @@ -14,6 +15,7 @@ from typing import TYPE_CHECKING from typing import Union +from ..keys import Key from .transport import DaemonProcessTransport from .transport import SyncFutureResult from .transport import Transport @@ -47,11 +49,18 @@ class FutureResult: class AHK: - def __init__(self, *, TransportClass: Optional[Type[Transport]] = None, **transport_kwargs: Any): + def __init__( + self, + *, + TransportClass: Optional[Type[Transport]] = None, + transport_options: Optional[Dict[str, Any]] = None, + ): + if transport_options is None: + transport_options = {} if TransportClass is None: TransportClass = DaemonProcessTransport assert TransportClass is not None - transport = TransportClass(**transport_kwargs) + transport = TransportClass(**transport_options) self._transport: Transport = transport def add_hotkey( @@ -62,6 +71,12 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + def start_hotkeys(self) -> None: + return self._transport.start_hotkeys() + + def stop_hotkeys(self) -> None: + return self._transport.stop_hotkeys() + # fmt: off @overload def list_windows(self) -> List[Window]: ... @@ -70,7 +85,9 @@ def list_windows(self, *, blocking: Literal[False]) -> Union[List[Window], SyncF @overload def list_windows(self, *, blocking: Literal[True]) -> List[Window]: ... # fmt: on - def list_windows(self, blocking: bool = True) -> Union[List[Window], SyncFutureResult[List[Window]]]: + def list_windows( + self, blocking: bool = True + ) -> Union[List[Window], SyncFutureResult[List[Window]]]: resp = self._transport.function_call('WindowList', engine=self, blocking=blocking) return resp @@ -144,20 +161,77 @@ def find_windows_by_title(self, title: str, exact: bool = False) -> Iterable[Win def get_volume(self, device_number: int = 1) -> float: raise NotImplementedError() - def key_down(self, key: str, blocking: bool = True) -> None: - raise NotImplementedError() + # 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]) -> SyncFutureResult[None]: ... + # fmt: on + def key_down(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + if isinstance(key, str): + key = Key(key_name=key) + if blocking: + return self.send_input(key.DOWN, blocking=True) + else: + return self.send_input(key.DOWN, blocking=False) - def key_press(self, key: str, release: bool = True, blocking: bool = True) -> None: - raise NotImplementedError() + # 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) -> SyncFutureResult[None]: ... + # fmt: on + def key_press( + self, key: Union[str, Key], *, release: bool = True, blocking: bool = True + ) -> Union[None, SyncFutureResult[None]]: + if blocking: + d = self.key_down(key, blocking=True) + if release: + return self.key_up(key, blocking=True) + else: + return d + else: + self.key_down(key, blocking=False) + if release: + self.key_up(key, blocking=False) + return None - def key_release(self, key: str, blocking: bool = True) -> None: - raise NotImplementedError() + # 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]) -> SyncFutureResult[None]: ... + # fmt: on + def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + if blocking: + return self.key_up(key=key, blocking=True) + else: + return self.key_up(key=key, blocking=False) def key_state(self, key_name: str, mode: Optional[Union[Literal['P'], Literal['T']]] = None) -> bool: raise NotImplementedError() - def key_up(self, key: str, blocking: bool = True) -> None: - raise NotImplementedError() + # 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]) -> SyncFutureResult[None]: ... + # fmt: on + def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + if isinstance(key, str): + key = Key(key_name=key) + if blocking: + return self.send_input(key.UP, blocking=True) + else: + return self.send_input(key.UP, blocking=False) def key_wait( self, key_name: str, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False @@ -195,14 +269,40 @@ def mouse_wheel( def run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() - def send(self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True) -> None: - raise NotImplementedError() + # fmt: off + @overload + def send(self, s: str) -> None: ... + @overload + def send(self, s: str, *, blocking: Literal[True]) -> None: ... + @overload + def send(self, s: str, *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + # fmt: on + def send( + self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True + ) -> Union[None, SyncFutureResult[None]]: + args = [s] + if raw: + raw_resp = self._transport.function_call('SendRaw', args=args, blocking=blocking) + return raw_resp + else: + resp = self._transport.function_call('Send', args=args, blocking=blocking) + return resp def send_event(self, s: str, delay: Optional[int] = None) -> None: raise NotImplementedError() - def send_input(self, s: str, blocking: bool = True) -> None: - raise NotImplementedError() + # 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]) -> SyncFutureResult[None]: ... + # fmt: on + def send_input(self, s: str, *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + args = [s] + resp = self._transport.function_call('SendInput', args, blocking=blocking) + return resp def send_play(self, s: str) -> None: raise NotImplementedError() @@ -550,13 +650,14 @@ def show_traytip( ) -> None: raise NotImplementedError() + # fmt: off @overload def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '') -> None: ... @overload def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', 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 = '', blocking: Literal[False]) -> SyncFutureResult[None]: ... - + # fmt: on def win_close( self, title: str = '', @@ -565,7 +666,7 @@ def win_close( seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', - blocking: bool = True + blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 01b269f7..1a9d65bf 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -120,7 +120,9 @@ def kill(proc: Killable) -> None: def async_assert_send_nonblocking_type_correct( obj: Any, -) -> TypeGuard[Future[Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]]]]: +) -> TypeGuard[ + Future[Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]]] +]: return True @@ -224,6 +226,12 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._hotkey_transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + def start_hotkeys(self) -> None: + return self._hotkey_transport.start() + + def stop_hotkeys(self) -> None: + return self._hotkey_transport.stop() + def init(self) -> None: self._started = True return None diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 8df3fc8c..9b76c5ee 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -464,6 +464,7 @@ Send(ByRef command) { s := Join(",", command*) str := Unescape(s) Send,% str + return FormatNoValueResponse() } SendRaw(ByRef command) { @@ -471,6 +472,7 @@ SendRaw(ByRef command) { s := Join(",", command*) str := Unescape(s) SendRaw,% str + return FormatNoValueResponse() } SendInput(ByRef command) { @@ -478,6 +480,7 @@ SendInput(ByRef command) { s := Join(",", command*) str := Unescape(s) SendInput,% str + return FormatNoValueResponse() } @@ -486,6 +489,7 @@ SendEvent(ByRef command) { s := Join(",", command*) str := Unescape(s) SendEvent,% str + return FormatNoValueResponse() } SendPlay(ByRef command) { @@ -493,6 +497,7 @@ SendPlay(ByRef command) { s := Join(",", command*) str := Unescape(s) SendPlay,% str + return FormatNoValueResponse() } SetCapsLockState(ByRef command) { @@ -502,6 +507,7 @@ SetCapsLockState(ByRef command) { state := command[2] SetCapsLockState, %state% } + return FormatNoValueResponse() } HideTrayTip(ByRef command) { diff --git a/ahk/hotkey.py b/ahk/hotkey.py index 8208f3bc..f6fdcf65 100644 --- a/ahk/hotkey.py +++ b/ahk/hotkey.py @@ -1,5 +1,6 @@ from __future__ import annotations +import atexit import os import subprocess import sys @@ -13,6 +14,8 @@ from typing import Dict from typing import List from typing import Optional +from typing import Protocol +from typing import runtime_checkable from typing import Tuple from typing import Type from typing import TypeVar @@ -206,6 +209,7 @@ def listener(self) -> None: stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) + atexit.register(kill, self._proc) while self._running: assert self._proc.stdout is not None line = self._proc.stdout.readline() @@ -214,3 +218,16 @@ def listener(self) -> None: continue logging.debug(f'Received {line!r}') self._callback_queue.put_nowait(line.decode('UTF-8').strip()) + + +@runtime_checkable +class Killable(Protocol): + def kill(self) -> None: + ... + + +def kill(proc: Killable) -> None: + try: + proc.kill() + except: + pass diff --git a/ahk/keys.py b/ahk/keys.py new file mode 100644 index 00000000..752a715d --- /dev/null +++ b/ahk/keys.py @@ -0,0 +1,247 @@ +""" +The ahk.keys module contains some useful constants and classes for working with keys. +""" +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: bool = False + symbol: str = '' + + def __init__(self, key_name: str): + self._key_name: str = key_name + + @property + def name(self) -> str: + return self._key_name + + @property + def DOWN(self) -> str: + return '{' + f'{self.name} down' + '}' + + @property + def UP(self) -> str: + return '{' + f'{self.name} up' + '}' + + def __str__(self) -> str: + return '{' + self.name + '}' + + def __hash__(self) -> int: + return hash(str(self)) + + def __mul__(self, n: int) -> str: + if not isinstance(n, int): + return NotImplemented + return '{' + f'{self.name} {n}' + '}' + + 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: str) -> str: + return str(self) + s + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(key_name={self.name!r})' + + def __format__(self, format_spec: Any) -> str: + return str(self) + + +SYMBOLS: Dict[str, str] +SYMBOLS = { + 'Win': '#', + 'LWin': '<#', + 'RWin': '>#', + 'Shift': '+', + 'LShift': '<+', + 'RShift': '>+', + 'Alt': '!', + 'LAlt': '!', + 'Control': '^', + 'LControl': '<^', + 'RControl': '>^', +} + + +class KeyCombo: + 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) -> 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: 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): + combo = KeyCombo(*[*self.modifiers, *other.modifiers]) + if other._s: + combo = combo + other._s + return combo + if isinstance(other, KeyModifier): + self.modifiers.append(other) # XXX: ???? + elif isinstance(other, Key) or isinstance(other, str): + self._s = str(other) + return 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) -> str: # type: ignore[override] + return SYMBOLS.get(self.name, str(self)) + + 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) + + +class KEYS: + """ + KEYS constants + REF: https://autohotkey.com/docs/KeyList.htm + """ + + 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 + key_name = f'Numpad{i}' + key = Key(key_name) + setattr(KEYS, key_name, key) + setattr(KEYS, key_name.upper(), key) + + for i in range(1, 25): + # set function keys + key_name = f'F{i}' + setattr(KEYS, key_name, Key(key_name)) + + for i in range(1, 33): + # set joystick keys + key_name = f'Joy{i}' + setattr(KEYS, key_name, Key(key_name)) + setattr(KEYS, key_name.upper(), Key(key_name)) + + +_init_keys() + +__all__ = [name for name in dir(KEYS) if not name.startswith('_')] + + +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__} has no attribute {name!r}') + return obj diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py new file mode 100644 index 00000000..95f7b60e --- /dev/null +++ b/tests/_async/test_hotkeys.py @@ -0,0 +1,43 @@ +import asyncio +import os +import subprocess +import sys +import time +from unittest import IsolatedAsyncioTestCase +from unittest import mock + +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: + self.ahk.stop_hotkeys() + self.ahk._transport._proc.kill() + + 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') + 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') + mock_ex_handler.assert_called() From 0eb45e5dac5fb8c97f612b23bc3050ba568ebe30 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Jul 2022 21:35:29 -0700 Subject: [PATCH 249/588] fix pre-commit fail --- tests/_async/test_hotkeys.py | 6 ++---- tests/_sync/test_hotkeys.py | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 tests/_sync/test_hotkeys.py diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index 95f7b60e..e96b5b60 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -1,10 +1,8 @@ import asyncio -import os -import subprocess -import sys import time -from unittest import IsolatedAsyncioTestCase +from unittest import IsolatedAsyncioTestCase # unasync: remove from unittest import mock +from unittest import TestCase from ahk import AsyncAHK from ahk import AsyncWindow diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py new file mode 100644 index 00000000..0f760eab --- /dev/null +++ b/tests/_sync/test_hotkeys.py @@ -0,0 +1,39 @@ +import asyncio +import time +from unittest import mock +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: + self.ahk.stop_hotkeys() + self.ahk._transport._proc.kill() + + 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') + 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') + mock_ex_handler.assert_called() From 091b90b348d655abfdc76bfc12f8cffa110cdcde Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Jul 2022 22:31:22 -0700 Subject: [PATCH 250/588] pin mypy --- ahk/hotkey.py | 14 ++++++++++---- requirements-dev.txt | 2 +- tests/_async/test_hotkeys.py | 2 ++ tests/_sync/test_hotkeys.py | 2 ++ tox.ini | 6 ++++-- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/ahk/hotkey.py b/ahk/hotkey.py index f6fdcf65..33a54578 100644 --- a/ahk/hotkey.py +++ b/ahk/hotkey.py @@ -126,19 +126,25 @@ def start(self) -> None: def stop(self) -> None: assert self._proc is not None self._running = False - self._proc.kill() self._callback_queue.empty() self._callback_queue.put_nowait(STOP) print('Waiting for stop...') if self._dispatcher_thread is not None: - self._dispatcher_thread.join() + try: + self._dispatcher_thread.join(timeout=3) + except TimeoutError: + print('DISPATCHER JOIN TIMED OUT!') self._dispatcher_thread = None - + print('Waiting for callback stop...') self._callback_queue.join() if self._listener_thread is not None: - self._listener_thread.join() + try: + self._listener_thread.join(timeout=3) + except TimeoutError: + print('LISTENER JOIN TIMED OUT!') self._listener_thread = None + self._proc.kill() def restart(self) -> None: self.stop() diff --git a/requirements-dev.txt b/requirements-dev.txt index a3c736c8..9028a011 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,6 +4,6 @@ unasync black tokenize-rt coverage -mypy +mypy==0.961 typing_extensions jinja2 diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index e96b5b60..b4e5818c 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -27,6 +27,7 @@ async def test_hotkey(self): self.ahk.add_hotkey('a', callback=m) self.ahk.start_hotkeys() await self.ahk.key_down('a') + await async_sleep(1) m.assert_called() async def test_hotkey_ex_handler(self): @@ -38,4 +39,5 @@ def 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 async_sleep(1) mock_ex_handler.assert_called() diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py index 0f760eab..b30743a8 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -25,6 +25,7 @@ def test_hotkey(self): self.ahk.add_hotkey('a', callback=m) self.ahk.start_hotkeys() self.ahk.key_down('a') + sleep(1) m.assert_called() def test_hotkey_ex_handler(self): @@ -36,4 +37,5 @@ def side_effect(): self.ahk.add_hotkey('a', callback=mock_cb, ex_handler=mock_ex_handler) self.ahk.start_hotkeys() self.ahk.key_down('a') + sleep(1) mock_ex_handler.assert_called() diff --git a/tox.ini b/tox.ini index de39331f..61af55aa 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,9 @@ envlist = py38,py39,py310 [testenv] deps = -rrequirements-dev.txt -passenv = CI +passenv = + CI + PYTHONUNBUFFERED commands = - coverage run -m pytest -s + coverage run -m pytest -s -vvv mypy --strict ahk From 697f8560c41e1f7be92fe1b50aa84e51fca067e6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Jul 2022 23:58:02 -0700 Subject: [PATCH 251/588] retry --- ahk/hotkey.py | 3 +++ requirements-dev.txt | 1 + tests/_async/test_hotkeys.py | 4 ++++ tests/_sync/test_hotkeys.py | 4 ++++ tox.ini | 2 +- 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ahk/hotkey.py b/ahk/hotkey.py index 33a54578..3f48abc3 100644 --- a/ahk/hotkey.py +++ b/ahk/hotkey.py @@ -222,6 +222,9 @@ def listener(self) -> None: if line.rstrip(b'\n') == _KEEPALIVE_SENTINEL: logging.debug('keepalive received') continue + if not line.strip(): + print('Listener: Process probably died, exiting') + break logging.debug(f'Received {line!r}') self._callback_queue.put_nowait(line.decode('UTF-8').strip()) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9028a011..1a0a331c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,3 +7,4 @@ coverage mypy==0.961 typing_extensions jinja2 +pytest-rerunfailures diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index b4e5818c..0c4841f6 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -1,4 +1,5 @@ import asyncio +import subprocess import time from unittest import IsolatedAsyncioTestCase # unasync: remove from unittest import mock @@ -21,12 +22,14 @@ async def asyncSetUp(self) -> None: async def asyncTearDown(self) -> None: self.ahk.stop_hotkeys() self.ahk._transport._proc.kill() + subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey.exe']) 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() @@ -39,5 +42,6 @@ def 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() diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py index b30743a8..470513be 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -1,4 +1,5 @@ import asyncio +import subprocess import time from unittest import mock from unittest import TestCase @@ -19,12 +20,14 @@ def setUp(self) -> None: def tearDown(self) -> None: self.ahk.stop_hotkeys() self.ahk._transport._proc.kill() + subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey.exe']) 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() @@ -37,5 +40,6 @@ def 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() diff --git a/tox.ini b/tox.ini index 61af55aa..89990e7c 100644 --- a/tox.ini +++ b/tox.ini @@ -7,5 +7,5 @@ passenv = CI PYTHONUNBUFFERED commands = - coverage run -m pytest -s -vvv + coverage run -m pytest -s -vvv --reruns 5 --only-rerun AssertionError mypy --strict ahk From 0d9555ff5e5fabdb268beef39871090615919515 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 21 Jul 2022 17:46:04 -0700 Subject: [PATCH 252/588] add key_wait --- ahk/_async/engine.py | 53 ++++++++++++++++++++++++++++++++++---------- ahk/_sync/engine.py | 46 ++++++++++++++++++++++++++++---------- ahk/daemon.ahk | 3 ++- buildunasync.py | 3 ++- 4 files changed, 79 insertions(+), 26 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 1cce59ef..6fb60b1d 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1,18 +1,19 @@ from __future__ import annotations import asyncio +import time from typing import Any from typing import Callable from typing import Dict from typing import Iterable from typing import List from typing import Literal +from typing import NoReturn from typing import Optional from typing import overload from typing import Sequence from typing import Tuple from typing import Type -from typing import TYPE_CHECKING from typing import Union from ..keys import Key @@ -23,9 +24,8 @@ from .window import AsyncWindow -class FutureResult: - ... - +async_sleep = asyncio.sleep # unasync: remove +sleep = time.sleep CoordModeTargets = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] CoordModeRelativeTo = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] @@ -189,16 +189,15 @@ async def key_press( self, key: Union[str, Key], *, release: bool = True, blocking: bool = True ) -> Union[None, AsyncFutureResult[None]]: if blocking: - d = await self.key_down(key, blocking=True) if release: return await self.key_up(key, blocking=True) else: - return d + return None else: - await self.key_down(key, blocking=False) + d = await self.key_down(key, blocking=False) if release: - await self.key_up(key, blocking=False) - return None + return await self.key_up(key, blocking=False) + return d # fmt: off @overload @@ -233,10 +232,36 @@ async def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[Non else: return await self.send_input(key.UP, blocking=False) + # fmt: off + @overload + async def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... + @overload + async def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... + @overload + async def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> AsyncFutureResult[int]: ... + # fmt: on async def key_wait( - self, key_name: str, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False - ) -> None: - raise NotImplementedError() + self, + key_name: str, + *, + timeout: Optional[int] = None, + logical_state: bool = False, + released: bool = False, + blocking: bool = True, + ) -> Union[int, AsyncFutureResult[int]]: + options = '' + if not released: + options += 'D' + if logical_state: + options += 'L' + if timeout: + options += f'T{timeout}' + args = [key_name] + if options: + args.append(options) + + resp = await self._transport.function_call('KeyWait', args) + return resp # async def mouse_position(self): # raise NotImplementedError() @@ -672,3 +697,7 @@ async def win_close( args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] resp = await self._transport.function_call('AHKWinClose', args=args, blocking=blocking) return resp + + async def block_forever(self) -> NoReturn: + while True: + await async_sleep(1) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 117168eb..4b615c6b 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1,18 +1,19 @@ from __future__ import annotations import asyncio +import time from typing import Any from typing import Callable from typing import Dict from typing import Iterable from typing import List from typing import Literal +from typing import NoReturn from typing import Optional from typing import overload from typing import Sequence from typing import Tuple from typing import Type -from typing import TYPE_CHECKING from typing import Union from ..keys import Key @@ -23,9 +24,7 @@ from .window import Window -class FutureResult: - ... - +sleep = time.sleep CoordModeTargets = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] CoordModeRelativeTo = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] @@ -189,16 +188,15 @@ def key_press( self, key: Union[str, Key], *, release: bool = True, blocking: bool = True ) -> Union[None, SyncFutureResult[None]]: if blocking: - d = self.key_down(key, blocking=True) if release: return self.key_up(key, blocking=True) else: - return d + return None else: - self.key_down(key, blocking=False) + d = self.key_down(key, blocking=False) if release: - self.key_up(key, blocking=False) - return None + return self.key_up(key, blocking=False) + return d # fmt: off @overload @@ -233,10 +231,30 @@ def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, Syn else: return self.send_input(key.UP, blocking=False) + # fmt: off + @overload + def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... + @overload + def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... + @overload + def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> SyncFutureResult[int]: ... + # fmt: on def key_wait( - self, key_name: str, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False - ) -> None: - raise NotImplementedError() + self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False, blocking: bool = True + ) -> Union[int, SyncFutureResult[int]]: + options = '' + if not released: + options += 'D' + if logical_state: + options += 'L' + if timeout: + options += f'T{timeout}' + args = [key_name] + if options: + args.append(options) + + resp = self._transport.function_call('KeyWait', args) + return resp # async def mouse_position(self): # raise NotImplementedError() @@ -672,3 +690,7 @@ def win_close( args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] resp = self._transport.function_call('AHKWinClose', args=args, blocking=blocking) return resp + + def block_forever(self) -> NoReturn: + while True: + sleep(1) diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 9b76c5ee..6503f01c 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -434,6 +434,7 @@ RegDelete(ByRef command) { } KeyWait(ByRef command) { + global INTEGERRESPONSEMESSAGE keyname := command[2] if (command.Length() = 2) { KeyWait,% keyname @@ -441,7 +442,7 @@ KeyWait(ByRef command) { options := command[3] KeyWait,% keyname,% options } - return ErrorLevel + return FormatResponse(INTEGERRESPONSEMESSAGE, ErrorLevel) } SetKeyDelay(ByRef command) { diff --git a/buildunasync.py b/buildunasync.py index 9ee951b1..fcdd56bd 100644 --- a/buildunasync.py +++ b/buildunasync.py @@ -13,7 +13,8 @@ '_AIOP': '_SIOP', 'async_create_process': 'sync_create_process', 'adrain_stdin': 'drain_stdin', - 'a_send_nonblocking': 'send_nonblocking' + 'a_send_nonblocking': 'send_nonblocking', + 'async_sleep': 'sleep', # "__aenter__": "__aenter__", }, ), From d6ff3fac8ff9b623e697f6bd577ebe63e9e75272 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 23 Jul 2022 14:53:59 -0700 Subject: [PATCH 253/588] winset methods --- ahk/_async/engine.py | 242 +++++++++++++++++++++++++++++++++- ahk/_async/transport.py | 35 ++++- ahk/_async/window.py | 43 +++++++ ahk/_sync/engine.py | 250 +++++++++++++++++++++++++++++++++++- ahk/_sync/transport.py | 35 ++++- ahk/_sync/window.py | 35 ++++- ahk/daemon.ahk | 191 ++++++++++++++++++++++----- tests/_async/test_window.py | 5 + tests/_sync/test_window.py | 5 + 9 files changed, 797 insertions(+), 44 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 6fb60b1d..68e5d437 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -531,6 +531,19 @@ async def win_get_control_list( resp = await self._transport.function_call('AHKWinGetControlList', args, blocking=blocking) 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]: ... + # fmt: on + async def win_get_from_mouse_position( + self, *, blocking: bool = True + ) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: + raise NotImplementedError() + # fmt: off @overload async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> bool: ... @@ -540,19 +553,240 @@ async def win_exists(self, title: str = '', text: str = '', exclude_title: str = async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... # fmt: on async def win_exists( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True ) -> Union[bool, AsyncFutureResult[bool]]: args = [title, text, exclude_title, exclude_text] resp = await self._transport.function_call('AHKWinExist', args, blocking=blocking) return resp - async def win_set(self, subcommand: str, *args: Any, blocking: bool = True) -> None: - # TODO: type hint subcommand literals + # fmt: off + @overload + async def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> None: ... + @overload + async def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + @overload + async def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + # fmt: on + async def win_set_title( + self, + new_title: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: raise NotImplementedError() - async def windows(self) -> Sequence[AsyncWindow]: + # 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 = '') -> 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 = '', *, 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 = '', *, blocking: Literal[True]) -> 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 = '', + *, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + args = [str(toggle), title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + async def win_set_bottom( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + args = [title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + async def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + async def win_set_top( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + args = [title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + async def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + async def win_set_disable( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + args = [title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + async def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + async def win_set_enable( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + args = [title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + async def win_set_redraw( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + args = [title, text, exclude_title, exclude_text] + 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 = '') -> bool: ... + @overload + async def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + @overload + async def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + # fmt: on + async def win_set_style( + self, + style: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[bool, AsyncFutureResult[bool]]: + args = [style, title, text, exclude_title, exclude_text] + 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 = '') -> bool: ... + @overload + async def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> bool: ... + # fmt: on + async def win_set_ex_style( + self, + style: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[bool, AsyncFutureResult[bool]]: + args = [style, title, text, exclude_title, exclude_text] + 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 = '') -> bool: ... + @overload + async def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + @overload + async def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + # fmt: on + async def win_set_region( + self, + options: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[bool, AsyncFutureResult[bool]]: raise NotImplementedError() + # fmt: off + @overload + async def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> None: ... + @overload + async def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + async def win_set_transparent( + self, + transparency: Union[int, Literal['Off']], + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + args = [str(transparency), title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + async def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + async def win_set_trans_color( + self, + color: Union[int, str], + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + args = [str(color), title, text, exclude_title, exclude_text] + resp = await self._transport.function_call('AHKWinSetTransColor', args, blocking=blocking) + return resp + + # alias for backwards compatibility + windows = list_windows + async def click( self, x: Optional[int] = None, diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 9c3ded66..d1e38b22 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -85,7 +85,18 @@ Literal['FromMouse'], Literal['WinGet'], Literal['WinSet'], + Literal['AHKWinSetAlwaysOnTop'], + Literal['AHKWinIsAlwaysOnTop'], + Literal['AHKWinSetTop'], + Literal['AHKWinSetBottom'], + Literal['AHKWinSetDisable'], + Literal['AHKWinSetEnable'], + Literal['AHKWinSetRedraw'], Literal['WinSetTitle'], + Literal['AHKWinSetTransparent'], + Literal['AHKWinSetTransColor'], + Literal['AHKWinSetStyle'], + Literal['AHKWinSetExStyle'], Literal['WinIsAlwaysOnTop'], Literal['WinClick'], Literal['AHKWinMove'], @@ -325,7 +336,7 @@ async def function_call(self, function_name: Literal['WinSet'], args: Optional[L @overload async def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + async def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Optional[bool], AsyncFutureResult[Optional[bool]]]: ... @overload async def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload @@ -361,6 +372,28 @@ async def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Op @overload async def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = 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] = 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] = 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] = 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] = 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] = 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] = 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] = 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] = 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index df1f4f47..1b816456 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -1,12 +1,15 @@ from __future__ import annotations from typing import Literal +from typing import Optional +from typing import overload from typing import Sequence from typing import TYPE_CHECKING from typing import Union if TYPE_CHECKING: from .engine import AsyncAHK + from .transport import AsyncFutureResult class WindowNotFoundException(Exception): @@ -78,6 +81,46 @@ async def list_controls(self) -> Sequence['AsyncControl']: ) return controls + # 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: ... + # fmt: on + async def set_always_on_top( + self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + if blocking: + resp = await self._engine.win_set_always_on_top( + toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True + ) + else: + resp = await self._engine.win_set_always_on_top( + toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=False + ) + return resp + + # 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: ... + # 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 + class AsyncControl: def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 4b615c6b..b5f00120 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -240,7 +240,13 @@ def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[ def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> SyncFutureResult[int]: ... # fmt: on def key_wait( - self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False, blocking: bool = True + self, + key_name: str, + *, + timeout: Optional[int] = None, + logical_state: bool = False, + released: bool = False, + blocking: bool = True, ) -> Union[int, SyncFutureResult[int]]: options = '' if not released: @@ -524,6 +530,19 @@ def win_get_control_list( resp = self._transport.function_call('AHKWinGetControlList', args, blocking=blocking) 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]) -> SyncFutureResult[Union[Window, None]]: ... + @overload + def win_get_from_mouse_position(self, *, blocking: Literal[True]) -> Union[Window, None]: ... + # fmt: on + def win_get_from_mouse_position( + self, *, blocking: bool = True + ) -> Union[Optional[Window], SyncFutureResult[Optional[Window]]]: + raise NotImplementedError() + # fmt: off @overload def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> bool: ... @@ -533,19 +552,240 @@ def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', e def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... # fmt: on def win_exists( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True ) -> Union[bool, SyncFutureResult[bool]]: args = [title, text, exclude_title, exclude_text] resp = self._transport.function_call('AHKWinExist', args, blocking=blocking) return resp - def win_set(self, subcommand: str, *args: Any, blocking: bool = True) -> None: - # TODO: type hint subcommand literals + # fmt: off + @overload + def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> None: ... + @overload + def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + @overload + def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + # fmt: on + def win_set_title( + self, + new_title: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[None, SyncFutureResult[None]]: raise NotImplementedError() - def windows(self) -> Sequence[Window]: + # 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 = '') -> 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[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 = '', *, blocking: Literal[True]) -> 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 = '', + *, + blocking: bool = True, + ) -> Union[None, SyncFutureResult[None]]: + args = [str(toggle), title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + @overload + def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + def win_set_bottom( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[None, SyncFutureResult[None]]: + args = [title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + @overload + def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + def win_set_top( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[None, SyncFutureResult[None]]: + args = [title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + @overload + def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + def win_set_disable( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[None, SyncFutureResult[None]]: + args = [title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + @overload + def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + def win_set_enable( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[None, SyncFutureResult[None]]: + args = [title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + @overload + def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + def win_set_redraw( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[None, SyncFutureResult[None]]: + args = [title, text, exclude_title, exclude_text] + 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 = '') -> bool: ... + @overload + def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + @overload + def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + # fmt: on + def win_set_style( + self, + style: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[bool, SyncFutureResult[bool]]: + args = [style, title, text, exclude_title, exclude_text] + 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 = '') -> bool: ... + @overload + def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + @overload + def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + # fmt: on + def win_set_ex_style( + self, + style: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[bool, SyncFutureResult[bool]]: + args = [style, title, text, exclude_title, exclude_text] + 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 = '') -> bool: ... + @overload + def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + @overload + def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + # fmt: on + def win_set_region( + self, + options: str, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[bool, SyncFutureResult[bool]]: raise NotImplementedError() + # fmt: off + @overload + def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> None: ... + @overload + def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + @overload + def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + def win_set_transparent( + self, + transparency: Union[int, Literal['Off']], + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[None, SyncFutureResult[None]]: + args = [str(transparency), title, text, exclude_title, exclude_text] + 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 = '') -> None: ... + @overload + def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + @overload + def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + # fmt: on + def win_set_trans_color( + self, + color: Union[int, str], + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + blocking: bool = True, + ) -> Union[None, SyncFutureResult[None]]: + args = [str(color), title, text, exclude_title, exclude_text] + resp = self._transport.function_call('AHKWinSetTransColor', args, blocking=blocking) + return resp + + # alias for backwards compatibility + windows = list_windows + def click( self, x: Optional[int] = None, diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 1a9d65bf..acba074e 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -83,7 +83,18 @@ Literal['FromMouse'], Literal['WinGet'], Literal['WinSet'], + Literal['AHKWinSetAlwaysOnTop'], + Literal['AHKWinIsAlwaysOnTop'], + Literal['AHKWinSetTop'], + Literal['AHKWinSetBottom'], + Literal['AHKWinSetDisable'], + Literal['AHKWinSetEnable'], + Literal['AHKWinSetRedraw'], Literal['WinSetTitle'], + Literal['AHKWinSetTransparent'], + Literal['AHKWinSetTransColor'], + Literal['AHKWinSetStyle'], + Literal['AHKWinSetExStyle'], Literal['WinIsAlwaysOnTop'], Literal['WinClick'], Literal['AHKWinMove'], @@ -314,7 +325,7 @@ def function_call(self, function_name: Literal['WinSet'], args: Optional[List[st @overload def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... + def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Optional[bool], SyncFutureResult[Optional[bool]]]: ... @overload def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload @@ -350,6 +361,28 @@ def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional @overload def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetBottom'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetDisable'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetEnable'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetRedraw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... + + @overload + def function_call(self, function_name: Literal['AHKWinSetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 11c249d7..f062b977 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -1,12 +1,14 @@ from __future__ import annotations from typing import Literal +from typing import Optional +from typing import overload from typing import Sequence from typing import TYPE_CHECKING from typing import Union - if TYPE_CHECKING: from .engine import AHK + from .transport import SyncFutureResult class WindowNotFoundException(Exception): @@ -78,6 +80,37 @@ def list_controls(self) -> Sequence['SyncControl']: ) return controls + # 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]) -> SyncFutureResult[None]: ... + @overload + def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: Literal[True]) -> None: ... + # fmt: on + def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + if blocking: + resp = self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True) + else: + resp = self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=False) + return resp + + # fmt: off + @overload + def is_always_on_top(self) -> bool: ... + @overload + def is_always_on_top(self, *, blocking: Literal[False]) -> SyncFutureResult[Optional[bool]]: ... + @overload + def is_always_on_top(self, *, blocking: Literal[True]) -> bool: ... + # fmt: on + def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, SyncFutureResult[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 class SyncControl: def __init__(self, window: Window, hwnd: str, control_class: str): diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 6503f01c..df628a6c 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -274,6 +274,127 @@ AHKWinGetExStyle(ByRef command) { return response } +AHKWinSetAlwaysOnTop(ByRef command) { + toggle := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + WinSet, AlwaysOntop, %toggle%, %title%, %text%, %extitle%, %extext% + return FormatNoValueResponse() +} + +AHKWinSetBottom(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinSet, Bottom,, %title%, %text%, %extitle%, %extext% + return FormatNoValueResponse() +} + +AHKWinSetTop(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinSet, Top,, %title%, %text%, %extitle%, %extext% + return FormatNoValueResponse() +} + +AHKWinSetEnable(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinSet, Enable,, %title%, %text%, %extitle%, %extext% + return FormatNoValueResponse() +} + +AHKWinSetDisable(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinSet, Disable,, %title%, %text%, %extitle%, %extext% + return FormatNoValueResponse() +} + +AHKWinSetRedraw(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinSet, Redraw,, %title%, %text%, %extitle%, %extext% + return FormatNoValueResponse() +} + +AHKWinSetStyle(ByRef command) { + global BOOLEANRESPONSEMESSAGE + style := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + WinSet, Style, %style%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + } else { + return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + } +} + +AHKWinSetExStyle(ByRef command) { + global BOOLEANRESPONSEMESSAGE + style := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + WinSet, ExStyle, %style%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + } else { + return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + } +} + +AHKWinSetRegion(ByRef command) { + global BOOLEANRESPONSEMESSAGE + options := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + WinSet, Region, %options%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + } else { + return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + } +} + +AHKWinSetTransparent(ByRef command) { + global BOOLEANRESPONSEMESSAGE + transparency := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + WinSet, Transparent, %transparency%, %title%, %text%, %extitle%, %extext% + return FormatNoValueResponse() +} + +AHKWinSetTransColor(ByRef command) { + global BOOLEANRESPONSEMESSAGE + color := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% + return FormatNoValueResponse() +} ImageSearch(ByRef command) { global COORDINATERESPONSEMESSAGE @@ -707,51 +828,57 @@ ControlSend(ByRef command) { } -BaseCheck(ByRef command) { - kommand := command[2] - title := command[3] - if %kommand%(title) { - return 1 - } - else { - return 0 - } -} + +; +;BaseCheck(ByRef command) { +; kommand := command[2] +; title := command[3] +; if %kommand%(title) { +; return 1 +; } +; else { +; return 0 +; } +;} FromMouse(ByRef command) { MouseGetPos,,, MouseWin return MouseWin } -WinGet(ByRef command) { - title := command[4] - text := command[5] - extitle := command[6] - extext := command[7] - WinGet, output,% command[3], %title%, %text%, %extitle%, %extext% - return output -} - -WinSet(ByRef command) { - subcommand := command[2] - title := command[4] - value := command[3] +;WinGet(ByRef command) { +; title := command[4] +; text := command[5] +; extitle := command[6] +; extext := command[7] +; WinGet, output,% command[3], %title%, %text%, %extitle%, %extext% +; return output +;} - WinSet,%subcommand%,%value%,%title% -} +;WinSet(ByRef command) { +; subcommand := command[2] +; title := command[4] +; value := command[3] +; +; WinSet,%subcommand%,%value%,%title% +;} -WinSetTitle(ByRef command) { - newtitle := command[4] - WinSetTitle,% command[2],, %newtitle% -} +;WinSetTitle(ByRef command) { +; newtitle := command[4] +; WinSetTitle,% command[2],, %newtitle% +;} -WinIsAlwaysOnTop(ByRef command) { +AHKWinIsAlwaysOnTop(ByRef command) { + global BOOLEANRESPONSEMESSAGE title := command[2] WinGet, ExStyle, ExStyle, %title% + if (ExStyle = "") + return FormatNoValueResponse() + if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. - return 1 + return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) else - return 0 + return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } WinClick(ByRef command) { diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 5580dd12..1ec399f9 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -57,3 +57,8 @@ async def test_win_process_path(self): 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 diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 04545bf3..3cf4335e 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -57,3 +57,8 @@ def test_win_process_path(self): 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 From 43138e4d42d41750115fe2965630aa853a3d0b1e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Jul 2022 21:10:10 +0000 Subject: [PATCH 254/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.1 → v3.8.2](https://github.com/asottile/reorder_python_imports/compare/v3.8.1...v3.8.2) - [github.com/pre-commit/mirrors-mypy: v0.961 → v0.971](https://github.com/pre-commit/mirrors-mypy/compare/v0.961...v0.971) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19cb613c..8bbe9576 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,12 +33,12 @@ repos: - "120" exclude: ^(ahk/_sync/.*\.py) - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.1 + rev: v3.8.2 hooks: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.961' + rev: 'v0.971' hooks: - id: mypy args: From 3a0bd4ee69a176f5427c22ca6ca398e22d5be75a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 25 Jul 2022 17:25:37 -0700 Subject: [PATCH 255/588] more changes --- ahk/__init__.py | 4 +-- ahk/_async/engine.py | 51 +++++++++++++++++-------------- ahk/_async/transport.py | 7 +++-- ahk/_async/window.py | 4 +++ ahk/_sync/__init__.py | 4 +-- ahk/_sync/engine.py | 61 ++++++++++++++++++++----------------- ahk/_sync/transport.py | 23 ++++++++------ ahk/_sync/window.py | 26 ++++++++++++---- ahk/daemon.ahk | 29 +++++++++++++----- ahk/message.py | 17 +++++++---- buildunasync.py | 1 + tests/_async/test_window.py | 5 +++ tests/_sync/test_window.py | 5 +++ 13 files changed, 151 insertions(+), 86 deletions(-) diff --git a/ahk/__init__.py b/ahk/__init__.py index e7365480..bb7e7bef 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -2,7 +2,7 @@ from ._async import AsyncControl from ._async import AsyncWindow from ._sync import AHK -from ._sync import SyncControl +from ._sync import Control from ._sync import Window -__all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK', 'SyncControl', 'AsyncControl'] +__all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK', 'Control', 'AsyncControl'] diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 68e5d437..45d6b26f 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -29,22 +29,7 @@ CoordModeTargets = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] CoordModeRelativeTo = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] -WinGetFunctions = Literal[ - Literal['AHKWinGetID'], - Literal['AHKWinGetIDLast'], - Literal['AHKWinGetPID'], - Literal['AHKWinGetProcessName'], - Literal['AHKWinGetProcessPath'], - Literal['AHKWinGetCount'], - Literal['AHKWinGetList'], - Literal['AHKWinGetMinMax'], - Literal['AHKWinGetControlList'], - Literal['AHKWinGetControlListHwnd'], - Literal['AHKWinGetTransparent'], - Literal['AHKWinGetTransColor'], - Literal['AHKWinGetStyle'], - Literal['AHKWinGetExStyle'], -] + CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] @@ -79,16 +64,19 @@ def stop_hotkeys(self) -> None: # fmt: off @overload - async def list_windows(self) -> List[AsyncWindow]: ... + async def list_windows(self, *, detect_hidden_windows: bool = False) -> List[AsyncWindow]: ... @overload - async def list_windows(self, *, blocking: Literal[False]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + async def list_windows(self, *, detect_hidden_windows: bool = False, blocking: Literal[False]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... @overload - async def list_windows(self, *, blocking: Literal[True]) -> List[AsyncWindow]: ... + async def list_windows(self, *, detect_hidden_windows: bool = False, blocking: Literal[True]) -> List[AsyncWindow]: ... # fmt: on async def list_windows( - self, blocking: bool = True + self, *, detect_hidden_windows: bool = False, blocking: bool = True ) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: - resp = await self._transport.function_call('WindowList', engine=self, blocking=blocking) + args = [] + if detect_hidden_windows: + args.append('On') + resp = await self._transport.function_call('WindowList', args, engine=self, blocking=blocking) return resp # fmt: off @@ -426,6 +414,21 @@ async def win_get( resp = await self._transport.function_call('AHKWinGetID', args, blocking=blocking, engine=self) return resp + # fmt: off + @overload + async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> str: ... + @overload + async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + @overload + async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> str: ... + # fmt: on + async def win_get_title( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[str, AsyncFutureResult[str]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = await self._transport.function_call('AHKWinGetTitle', args, blocking=blocking) + return resp + # fmt: off @overload async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[AsyncWindow, None]: ... @@ -528,7 +531,7 @@ async def win_get_control_list( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True ) -> Union[List[AsyncControl], None, AsyncFutureResult[Optional[List[AsyncControl]]]]: args = [title, text, exclude_title, exclude_title, exclude_text] - resp = await self._transport.function_call('AHKWinGetControlList', args, blocking=blocking) + resp = await self._transport.function_call('AHKWinGetControlList', args, blocking=blocking, engine=self) return resp # fmt: off @@ -738,7 +741,9 @@ async def win_set_region( *, blocking: bool = True, ) -> Union[bool, AsyncFutureResult[bool]]: - raise NotImplementedError() + args = [options, title, text, exclude_title, exclude_text] + resp = await self._transport.function_call('AHKWinSetRegion', args, blocking=blocking) + return resp # fmt: off @overload diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index d1e38b22..ab297a64 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -66,7 +66,7 @@ Literal['SendEvent'], Literal['SendPlay'], Literal['SetCapsLockState'], - Literal['WinGetTitle'], + Literal['AHKWinGetTitle'], Literal['WinGetClass'], Literal['WinGetText'], Literal['WinActivate'], @@ -97,6 +97,7 @@ Literal['AHKWinSetTransColor'], Literal['AHKWinSetStyle'], Literal['AHKWinSetExStyle'], + Literal['AHKWinSetRegion'], Literal['WinIsAlwaysOnTop'], Literal['WinClick'], Literal['AHKWinMove'], @@ -296,7 +297,7 @@ async def function_call(self, function_name: Literal['SendPlay'], args: Optional @overload async def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... + async def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload async def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload @@ -388,6 +389,8 @@ async def function_call(self, function_name: Literal['AHKWinSetRedraw'], args: O async def function_call(self, function_name: Literal['AHKWinSetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = 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] = 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] = None) -> Union[None, AsyncFutureResult[None]]: ... diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 1b816456..424876b6 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -73,6 +73,10 @@ async def get_minmax(self) -> int: ) return minmax + async def get_title(self) -> str: + title = await self._engine.win_get_title(title=f'ahk_id {self._ahk_id}') + return title + async def list_controls(self) -> Sequence['AsyncControl']: controls = await self._engine.win_get_control_list(title=f'ahk_id {self._ahk_id}') if controls is None: diff --git a/ahk/_sync/__init__.py b/ahk/_sync/__init__.py index 33cc49dc..6c55eeac 100644 --- a/ahk/_sync/__init__.py +++ b/ahk/_sync/__init__.py @@ -1,4 +1,4 @@ from .engine import AHK -from .window import SyncControl +from .window import Control from .window import Window -__all__ =['AHK', 'Window', 'SyncControl'] +__all__ =['AHK', 'Window', 'Control'] diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index b5f00120..55ee349e 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -20,7 +20,7 @@ from .transport import DaemonProcessTransport from .transport import SyncFutureResult from .transport import Transport -from .window import SyncControl +from .window import Control from .window import Window @@ -28,22 +28,7 @@ CoordModeTargets = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] CoordModeRelativeTo = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] -WinGetFunctions = Literal[ - Literal['AHKWinGetID'], - Literal['AHKWinGetIDLast'], - Literal['AHKWinGetPID'], - Literal['AHKWinGetProcessName'], - Literal['AHKWinGetProcessPath'], - Literal['AHKWinGetCount'], - Literal['AHKWinGetList'], - Literal['AHKWinGetMinMax'], - Literal['AHKWinGetControlList'], - Literal['AHKWinGetControlListHwnd'], - Literal['AHKWinGetTransparent'], - Literal['AHKWinGetTransColor'], - Literal['AHKWinGetStyle'], - Literal['AHKWinGetExStyle'], -] + CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] @@ -78,16 +63,19 @@ def stop_hotkeys(self) -> None: # fmt: off @overload - def list_windows(self) -> List[Window]: ... + def list_windows(self, *, detect_hidden_windows: bool = False) -> List[Window]: ... @overload - def list_windows(self, *, blocking: Literal[False]) -> Union[List[Window], SyncFutureResult[List[Window]]]: ... + def list_windows(self, *, detect_hidden_windows: bool = False, blocking: Literal[False]) -> Union[List[Window], SyncFutureResult[List[Window]]]: ... @overload - def list_windows(self, *, blocking: Literal[True]) -> List[Window]: ... + def list_windows(self, *, detect_hidden_windows: bool = False, blocking: Literal[True]) -> List[Window]: ... # fmt: on def list_windows( - self, blocking: bool = True + self, *, detect_hidden_windows: bool = False, blocking: bool = True ) -> Union[List[Window], SyncFutureResult[List[Window]]]: - resp = self._transport.function_call('WindowList', engine=self, blocking=blocking) + args = [] + if detect_hidden_windows: + args.append('On') + resp = self._transport.function_call('WindowList', args, engine=self, blocking=blocking) return resp # fmt: off @@ -425,6 +413,21 @@ def win_get( resp = self._transport.function_call('AHKWinGetID', args, blocking=blocking, engine=self) return resp + # fmt: off + @overload + def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> str: ... + @overload + def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[str]: ... + @overload + def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> str: ... + # fmt: on + def win_get_title( + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + ) -> Union[str, SyncFutureResult[str]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + resp = self._transport.function_call('AHKWinGetTitle', args, blocking=blocking) + return resp + # fmt: off @overload def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[Window, None]: ... @@ -517,17 +520,17 @@ def win_get_minmax( # fmt: off @overload - def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[List[SyncControl], None]: ... + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[List[Control], None]: ... @overload - def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[List[SyncControl], None]]: ... + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[List[Control], None]]: ... @overload - def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[List[SyncControl], None]: ... + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[List[Control], None]: ... # fmt: on def win_get_control_list( self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True - ) -> Union[List[SyncControl], None, SyncFutureResult[Optional[List[SyncControl]]]]: + ) -> Union[List[Control], None, SyncFutureResult[Optional[List[Control]]]]: args = [title, text, exclude_title, exclude_title, exclude_text] - resp = self._transport.function_call('AHKWinGetControlList', args, blocking=blocking) + resp = self._transport.function_call('AHKWinGetControlList', args, blocking=blocking, engine=self) return resp # fmt: off @@ -737,7 +740,9 @@ def win_set_region( *, blocking: bool = True, ) -> Union[bool, SyncFutureResult[bool]]: - raise NotImplementedError() + args = [options, title, text, exclude_title, exclude_text] + resp = self._transport.function_call('AHKWinSetRegion', args, blocking=blocking) + return resp # fmt: off @overload diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index acba074e..a532768b 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -25,7 +25,7 @@ from typing import Union if TYPE_CHECKING: - from ahk import SyncControl + from ahk import Control from ahk import Window if sys.version_info < (3, 10): @@ -64,7 +64,7 @@ Literal['SendEvent'], Literal['SendPlay'], Literal['SetCapsLockState'], - Literal['WinGetTitle'], + Literal['AHKWinGetTitle'], Literal['WinGetClass'], Literal['WinGetText'], Literal['WinActivate'], @@ -95,6 +95,7 @@ Literal['AHKWinSetTransColor'], Literal['AHKWinSetStyle'], Literal['AHKWinSetExStyle'], + Literal['AHKWinSetRegion'], Literal['WinIsAlwaysOnTop'], Literal['WinClick'], Literal['AHKWinMove'], @@ -132,7 +133,7 @@ def kill(proc: Killable) -> None: def async_assert_send_nonblocking_type_correct( obj: Any, ) -> TypeGuard[ - Future[Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]]] + Future[Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]] ]: return True @@ -285,7 +286,7 @@ def function_call(self, function_name: Literal['SendPlay'], args: Optional[List[ @overload def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... + def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload @@ -349,7 +350,7 @@ def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[ @overload def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, int], SyncFutureResult[Union[None, int]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[SyncControl], None, SyncFutureResult[Union[List[SyncControl], None]]]: ... + def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[Control], None, SyncFutureResult[Union[List[Control], None]]]: ... # @overload # async def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncControl], AsyncFutureResult[List[AsyncControl]]]: ... @overload @@ -377,6 +378,8 @@ def function_call(self, function_name: Literal['AHKWinSetRedraw'], args: Optiona def function_call(self, function_name: Literal['AHKWinSetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... @overload def function_call(self, function_name: Literal['AHKWinSetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinSetRegion'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... @overload def function_call(self, function_name: Literal['AHKWinSetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @@ -424,7 +427,7 @@ def function_call( @abstractmethod def send( self, request: RequestMessage, engine: Optional[AHK] = None - ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]]: + ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: return NotImplemented @@ -432,7 +435,7 @@ def send( def send_nonblocking( self, request: RequestMessage, engine: Optional[AHK] = None ) -> SyncFutureResult[ - Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]] + Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]] ]: return NotImplemented @@ -464,7 +467,7 @@ def _create_process(self) -> SyncAHKProcess: def _send_nonblocking( self, request: RequestMessage, engine: Optional[AHK] = None - ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]]: + ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: newline = '\n' msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( @@ -497,7 +500,7 @@ def _send_nonblocking( def send_nonblocking( self, request: RequestMessage, engine: Optional[AHK] = None ) -> SyncFutureResult[ - Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]] + 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) @@ -510,7 +513,7 @@ def send_nonblocking( def send( self, request: RequestMessage, engine: Optional[AHK] = None - ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[SyncControl]]: + ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: newline = '\n' msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index f062b977..93a5f796 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -6,6 +6,7 @@ from typing import Sequence from typing import TYPE_CHECKING from typing import Union + if TYPE_CHECKING: from .engine import AHK from .transport import SyncFutureResult @@ -72,7 +73,11 @@ def get_minmax(self) -> int: ) return minmax - def list_controls(self) -> Sequence['SyncControl']: + def get_title(self) -> str: + title = self._engine.win_get_title(title=f'ahk_id {self._ahk_id}') + return title + + def list_controls(self) -> Sequence['Control']: controls = self._engine.win_get_control_list(title=f'ahk_id {self._ahk_id}') if controls is None: raise WindowNotFoundException( @@ -88,11 +93,17 @@ def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, @overload def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: Literal[True]) -> None: ... # fmt: on - def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + def set_always_on_top( + self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True + ) -> Union[None, SyncFutureResult[None]]: if blocking: - resp = self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True) + resp = self._engine.win_set_always_on_top( + toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True + ) else: - resp = self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=False) + resp = self._engine.win_set_always_on_top( + toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=False + ) return resp # fmt: off @@ -105,14 +116,17 @@ def is_always_on_top(self, *, blocking: Literal[True]) -> bool: ... # fmt: on def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, SyncFutureResult[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? + 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 -class SyncControl: + +class Control: def __init__(self, window: Window, hwnd: str, control_class: str): self.window: Window = window self.hwnd: str = hwnd diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index df628a6c..5fff695a 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -67,6 +67,16 @@ AHKWinGetID(ByRef command) { return response } +AHKWinGetTitle(ByRef command) { + global STRINGRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + WinGetTitle, text, %title%, %text%, %extitle%, %extext% + return FormatResponse(STRINGRESPONSEMESSAGE, text) +} + AHKWinGetIDLast(ByRef command) { global WINDOWRESPONSEMESSAGE title := command[2] @@ -208,9 +218,7 @@ AHKWinGetControlList(ByRef command) { output .= Format("('{}', '{}'), ", hwnd, classname) } - output .= "])" - MsgBox,% output response := FormatResponse(WINDOWCONTROLLISTRESPONSEMESSAGE, output) return response } @@ -641,11 +649,9 @@ HideTrayTip(ByRef command) { } } -WinGetTitle(ByRef command) { - title := command[3] - WinGetTitle, text, %title% - return text -} + + + WinGetClass(ByRef command) { title := command[3] WinGetClass, text, %title% @@ -781,6 +787,14 @@ WinWaitClose(ByRef command) { WindowList(ByRef command) { global WINDOWIDLISTRESPONSEMESSAGE + + previous_setting := Format("{}", A_DetectHiddenWindows) + + detect_hidden_windows := command[2] + if (detect_hidden_windows) { + DetectHiddenWindows, %detect_hidden_windows% + } + WinGet windows, List r := "" Loop %windows% @@ -789,6 +803,7 @@ WindowList(ByRef command) { r .= id . "`," } resp := FormatResponse(WINDOWIDLISTRESPONSEMESSAGE, r) + DetectHiddenWindows, %previous_setting% return resp } diff --git a/ahk/message.py b/ahk/message.py index 28086213..17a87991 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -195,7 +195,7 @@ class WindowListResponseMessage(ResponseMessage): def unpack(self) -> Union[List[Window], List[AsyncWindow]]: from ._async.engine import AsyncAHK from ._async.window import AsyncWindow, AsyncControl - from ._sync.window import Window, SyncControl + from ._sync.window import Window, Control from ._sync.engine import AHK s = self._raw_content.decode(encoding='utf-8') @@ -234,7 +234,12 @@ def unpack(self) -> NoReturn: class WindowControlListResponseMessage(ResponseMessage): type = 'windowcontrollist' - def unpack(self) -> Union[List[AsyncControl], List[SyncControl]]: + 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) @@ -250,11 +255,11 @@ def unpack(self) -> Union[List[AsyncControl], List[SyncControl]]: ret_async.append(async_ctrl) return ret_async elif isinstance(self._engine, AHK): - ret_sync: List[SyncControl] = [] + ret_sync: List[Control] = [] window = Window(engine=self._engine, ahk_id=ahkid) for control in controls: hwnd, classname = control - ctrl = SyncControl(window=window, hwnd=hwnd, control_class=classname) + ctrl = Control(window=window, hwnd=hwnd, control_class=classname) ret_sync.append(ctrl) return ret_sync else: @@ -267,7 +272,7 @@ class WindowResponseMessage(ResponseMessage): def unpack(self) -> Union[Window, AsyncWindow]: from ._async.engine import AsyncAHK from ._async.window import AsyncWindow, AsyncControl - from ._sync.window import Window, SyncControl + from ._sync.window import Window, Control from ._sync.engine import AHK s = self._raw_content.decode(encoding='utf-8') @@ -318,5 +323,5 @@ def __init__(self, function_name: str, args: Optional[List[str]] = None): if TYPE_CHECKING: from ._async.engine import AsyncAHK from ._async.window import AsyncWindow, AsyncControl - from ._sync.window import Window, SyncControl + from ._sync.window import Window, Control from ._sync.engine import AHK diff --git a/buildunasync.py b/buildunasync.py index fcdd56bd..7ffe9f47 100644 --- a/buildunasync.py +++ b/buildunasync.py @@ -9,6 +9,7 @@ 'AsyncAHK': 'AHK', 'AsyncTransport': 'Transport', 'AsyncWindow': 'Window', + 'AsyncControl': 'Control', 'AsyncDaemonProcessTransport': 'DaemonProcessTransport', '_AIOP': '_SIOP', 'async_create_process': 'sync_create_process', diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 1ec399f9..1e54a50b 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -62,3 +62,8 @@ 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 diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 3cf4335e..038ad640 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -62,3 +62,8 @@ 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 From 7a9a2f766a76c00c880592456269a53e9ab03d79 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 26 Jul 2022 10:01:23 -0700 Subject: [PATCH 256/588] make mypy happy on 0.971 --- ahk/_async/engine.py | 15 +++++++++------ ahk/_async/window.py | 7 +++---- ahk/_sync/engine.py | 15 +++++++++------ ahk/_sync/window.py | 5 +++-- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 45d6b26f..17146675 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -161,7 +161,8 @@ async def key_down(self, key: Union[str, Key], *, blocking: bool = True) -> Unio if isinstance(key, str): key = Key(key_name=key) if blocking: - return await self.send_input(key.DOWN, blocking=True) + await self.send_input(key.DOWN, blocking=True) + return None else: return await self.send_input(key.DOWN, blocking=False) @@ -177,10 +178,10 @@ async def key_press( self, key: Union[str, Key], *, release: bool = True, blocking: bool = True ) -> Union[None, AsyncFutureResult[None]]: if blocking: + await self.key_down(key, blocking=True) if release: - return await self.key_up(key, blocking=True) - else: - return None + await self.key_up(key, blocking=True) + return None else: d = await self.key_down(key, blocking=False) if release: @@ -197,7 +198,8 @@ async def key_release(self, key: Union[str, Key], *, blocking: Literal[False]) - # fmt: on async def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: if blocking: - return await self.key_up(key=key, blocking=True) + await self.key_up(key=key, blocking=True) + return None else: return await self.key_up(key=key, blocking=False) @@ -216,7 +218,8 @@ async def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[Non if isinstance(key, str): key = Key(key_name=key) if blocking: - return await self.send_input(key.UP, blocking=True) + await self.send_input(key.UP, blocking=True) + return None else: return await self.send_input(key.UP, blocking=False) diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 424876b6..49097b2f 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -97,14 +97,13 @@ async def set_always_on_top( self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True ) -> Union[None, AsyncFutureResult[None]]: if blocking: - resp = await self._engine.win_set_always_on_top( - toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True - ) + await self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True) + return None else: resp = await self._engine.win_set_always_on_top( toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=False ) - return resp + return resp # fmt: off @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 55ee349e..53ac6253 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -160,7 +160,8 @@ def key_down(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None if isinstance(key, str): key = Key(key_name=key) if blocking: - return self.send_input(key.DOWN, blocking=True) + self.send_input(key.DOWN, blocking=True) + return None else: return self.send_input(key.DOWN, blocking=False) @@ -176,10 +177,10 @@ def key_press( self, key: Union[str, Key], *, release: bool = True, blocking: bool = True ) -> Union[None, SyncFutureResult[None]]: if blocking: + self.key_down(key, blocking=True) if release: - return self.key_up(key, blocking=True) - else: - return None + self.key_up(key, blocking=True) + return None else: d = self.key_down(key, blocking=False) if release: @@ -196,7 +197,8 @@ def key_release(self, key: Union[str, Key], *, blocking: Literal[False]) -> Sync # fmt: on def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: if blocking: - return self.key_up(key=key, blocking=True) + self.key_up(key=key, blocking=True) + return None else: return self.key_up(key=key, blocking=False) @@ -215,7 +217,8 @@ def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, Syn if isinstance(key, str): key = Key(key_name=key) if blocking: - return self.send_input(key.UP, blocking=True) + self.send_input(key.UP, blocking=True) + return None else: return self.send_input(key.UP, blocking=False) diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 93a5f796..0fc26957 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -97,14 +97,15 @@ def set_always_on_top( self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True ) -> Union[None, SyncFutureResult[None]]: if blocking: - resp = self._engine.win_set_always_on_top( + self._engine.win_set_always_on_top( toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True ) + return None else: resp = self._engine.win_set_always_on_top( toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=False ) - return resp + return resp # fmt: off @overload From 96d4217a5597c4a99d59e649028c30dab31ac3df Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 29 Jul 2022 02:35:20 -0700 Subject: [PATCH 257/588] detect hidden windows --- ahk/_async/engine.py | 493 +++++++++++++++++++++++++++++------- ahk/_async/transport.py | 4 + ahk/_sync/engine.py | 353 +++++++++++++++++++------- ahk/_sync/transport.py | 4 + ahk/daemon.ahk | 267 ++++++++++++++++++- tests/_async/test_window.py | 11 + tests/_sync/test_window.py | 11 + 7 files changed, 949 insertions(+), 194 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 17146675..f4dea1dd 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -62,20 +62,38 @@ def start_hotkeys(self) -> None: def stop_hotkeys(self) -> None: return self._transport.stop_hotkeys() + async def set_detect_hidden_windows(self, value: bool) -> None: + 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('On') + else: + args.append('Off') + await self._transport.function_call('AHKSetDetectHiddenWindows', args=args) + return None + # fmt: off @overload - async def list_windows(self, *, detect_hidden_windows: bool = False) -> List[AsyncWindow]: ... + async def list_windows(self, *, detect_hidden_windows: Optional[bool] = None) -> List[AsyncWindow]: ... @overload - async def list_windows(self, *, detect_hidden_windows: bool = False, blocking: Literal[False]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + async def list_windows(self, *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... @overload - async def list_windows(self, *, detect_hidden_windows: bool = False, blocking: Literal[True]) -> List[AsyncWindow]: ... + async def list_windows(self, *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[AsyncWindow]: ... # fmt: on async def list_windows( - self, *, detect_hidden_windows: bool = False, blocking: bool = True + self, *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: args = [] - if detect_hidden_windows: - args.append('On') + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = await self._transport.function_call('WindowList', args, engine=self, blocking=blocking) return resp @@ -404,136 +422,280 @@ async def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[AsyncWindow, None]: ... + async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '',*, detect_hidden_windows: Optional[bool] = None) -> Union[AsyncWindow, None]: ... @overload - async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... + async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... @overload - async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... + async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... # fmt: on async def win_get( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[None, AsyncWindow]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = await self._transport.function_call('AHKWinGetID', args, blocking=blocking, engine=self) return resp # fmt: off @overload - async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> str: ... + async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> str: ... @overload - async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> str: ... + async def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... # fmt: on async def win_get_title( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[str, AsyncFutureResult[str]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = await self._transport.function_call('AHKWinGetTitle', args, blocking=blocking) return resp # fmt: off @overload - async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[AsyncWindow, None]: ... + async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... + async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... + async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... # fmt: on async def win_get_idlast( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[AsyncWindow, None]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = await self._transport.function_call('AHKWinGetIDLast', args, blocking=blocking) return resp # fmt: off @overload - async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[int, None]: ... + async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[int, None]]: ... + async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> Union[int, None]: ... + async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... # fmt: on async def win_get_pid( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[int, None, AsyncFutureResult[Union[int, None]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> Union[str, None]: ... + async def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[str, None]]: ... + async def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> Union[str, None]: ... + async def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... # fmt: on async def win_get_process_name( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, str, AsyncFutureResult[Optional[str]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> Union[str, None]: ... + async def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[str, None]]: ... + async def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> Union[str, None]: ... + async def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... # fmt: on async def win_get_process_path( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[str, None, Union[None, str, AsyncFutureResult[Optional[str]]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> int: ... + async def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> int: ... @overload - async def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[int]: ... + async def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> int: ... + async def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> int: ... # fmt: on async def win_get_count( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[int, AsyncFutureResult[int]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> Union[int, None]: ... + async def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[int, None]]: ... + async def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> Union[int, None]: ... + async def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... # fmt: on async def win_get_minmax( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, int, AsyncFutureResult[Optional[int]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> Union[List[AsyncControl], None]: ... + async def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[Union[List[AsyncControl], None]]: ... + async def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> Union[List[AsyncControl], None]: ... + async def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[List[AsyncControl], None]: ... # fmt: on async def win_get_control_list( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[List[AsyncControl], None, AsyncFutureResult[Optional[List[AsyncControl]]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = await self._transport.function_call('AHKWinGetControlList', args, blocking=blocking, engine=self) return resp @@ -552,26 +714,42 @@ async def win_get_from_mouse_position( # fmt: off @overload - async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> bool: ... + async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> bool: ... @overload - async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> bool: ... + async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... # fmt: on async def win_exists( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[bool, AsyncFutureResult[bool]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = await self._transport.function_call('AHKWinExist', 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 = '') -> None: ... + async def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + async def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... # fmt: on async def win_set_title( self, @@ -581,17 +759,18 @@ async def win_set_title( exclude_title: str = '', exclude_text: str = '', *, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: raise NotImplementedError() # 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 = '') -> None: ... + 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 = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on async def win_set_always_on_top( self, @@ -601,94 +780,184 @@ async def win_set_always_on_top( exclude_title: str = '', exclude_text: str = '', *, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: args = [str(toggle), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> None: ... + async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... @overload - async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on async def win_set_bottom( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> None: ... + async def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... @overload - async def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + async def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on async def win_set_top( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> None: ... + async def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... @overload - async def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + async def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on async def win_set_disable( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> None: ... + async def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... @overload - async def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + async def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on async def win_set_enable( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> None: ... + async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... @overload - async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on async def win_set_redraw( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> bool: ... + async def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + async def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> bool: ... + async def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... # fmt: on async def win_set_style( self, @@ -698,19 +967,29 @@ async def win_set_style( exclude_title: str = '', exclude_text: str = '', *, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[bool, AsyncFutureResult[bool]]: args = [style, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> bool: ... + async def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + async def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> bool: ... + async def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... # fmt: on async def win_set_ex_style( self, @@ -720,19 +999,29 @@ async def win_set_ex_style( exclude_title: str = '', exclude_text: str = '', *, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[bool, AsyncFutureResult[bool]]: args = [style, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> bool: ... + async def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + async def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> bool: ... + async def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... # fmt: on async def win_set_region( self, @@ -742,19 +1031,29 @@ async def win_set_region( exclude_title: str = '', exclude_text: str = '', *, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[bool, AsyncFutureResult[bool]]: args = [options, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> None: ... + async def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + async def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on async def win_set_transparent( self, @@ -764,19 +1063,29 @@ async def win_set_transparent( exclude_title: str = '', exclude_text: str = '', *, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: args = [str(transparency), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) 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 = '') -> None: ... + async def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + async def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on async def win_set_trans_color( self, @@ -786,9 +1095,19 @@ async def win_set_trans_color( exclude_title: str = '', exclude_text: str = '', *, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: args = [str(color), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = await self._transport.function_call('AHKWinSetTransColor', args, blocking=blocking) return resp diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index ab297a64..c459cfb7 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -48,6 +48,7 @@ SyncFutureResult: TypeAlias = Future FunctionName = Literal[ + Literal['AHKSetDetectHiddenWindows'], Literal['AHKWinExist'], Literal['ImageSearch'], Literal['PixelGetColor'], @@ -397,6 +398,9 @@ async def function_call(self, function_name: Literal['AHKWinSetTransparent'], ar @overload async def function_call(self, function_name: Literal['AHKWinSetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 53ac6253..ab4fbc62 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -61,20 +61,38 @@ def start_hotkeys(self) -> None: def stop_hotkeys(self) -> None: return self._transport.stop_hotkeys() + + def set_detect_hidden_windows(self, value: bool) -> None: + 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('On') + else: + args.append('Off') + self._transport.function_call('AHKSetDetectHiddenWindows', args=args) + return None + + # fmt: off @overload - def list_windows(self, *, detect_hidden_windows: bool = False) -> List[Window]: ... + def list_windows(self, *, detect_hidden_windows: Optional[bool] = None) -> List[Window]: ... @overload - def list_windows(self, *, detect_hidden_windows: bool = False, blocking: Literal[False]) -> Union[List[Window], SyncFutureResult[List[Window]]]: ... + def list_windows(self, *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[List[Window], SyncFutureResult[List[Window]]]: ... @overload - def list_windows(self, *, detect_hidden_windows: bool = False, blocking: Literal[True]) -> List[Window]: ... + def list_windows(self, *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[Window]: ... # fmt: on def list_windows( - self, *, detect_hidden_windows: bool = False, blocking: bool = True + self, *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[List[Window], SyncFutureResult[List[Window]]]: args = [] - if detect_hidden_windows: - args.append('On') + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') resp = self._transport.function_call('WindowList', args, engine=self, blocking=blocking) return resp @@ -403,136 +421,199 @@ def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[Window, None]: ... + def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '',*, detect_hidden_windows: Optional[bool] = None) -> Union[Window, None]: ... @overload - def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[Window, None]]: ... + def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[Window, None]]: ... @overload - def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[Window, None]: ... + def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[Window, None]: ... # fmt: on def win_get( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[Window, None, SyncFutureResult[Union[None, Window]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') resp = self._transport.function_call('AHKWinGetID', args, blocking=blocking, engine=self) return resp # fmt: off @overload - def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> str: ... + def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> str: ... @overload - def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[str]: ... + def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[str]: ... @overload - def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> str: ... + def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... # fmt: on def win_get_title( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[str, SyncFutureResult[str]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') resp = self._transport.function_call('AHKWinGetTitle', args, blocking=blocking) return resp # fmt: off @overload - def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[Window, None]: ... + def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[Window, None]]: ... + def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[Window, None]]: ... @overload - def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[Window, None]: ... + def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[Window, None]: ... # fmt: on def win_get_idlast( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[Window, None, SyncFutureResult[Union[Window, None]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') resp = self._transport.function_call('AHKWinGetIDLast', args, blocking=blocking) return resp # fmt: off @overload - def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> Union[int, None]: ... + def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[int, None]]: ... + def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[int, None]]: ... @overload - def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[int, None]: ... + def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... # fmt: on def win_get_pid( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[int, None, SyncFutureResult[Union[int, None]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> Union[str, None]: ... + def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[str, None]]: ... + def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[str, None]]: ... @overload - def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[str, None]: ... + def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... # fmt: on def win_get_process_name( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[None, str, SyncFutureResult[Optional[str]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> Union[str, None]: ... + def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[str, None]]: ... + def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[str, None]]: ... @overload - def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[str, None]: ... + def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... # fmt: on def win_get_process_path( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[str, None, Union[None, str, SyncFutureResult[Optional[str]]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> int: ... + def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> int: ... @overload - def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[int]: ... + def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[int]: ... @overload - def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> int: ... + def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> int: ... # fmt: on def win_get_count( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[int, SyncFutureResult[int]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> Union[int, None]: ... + def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[int, None]]: ... + def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[int, None]]: ... @overload - def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[int, None]: ... + def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... # fmt: on def win_get_minmax( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[None, int, SyncFutureResult[Optional[int]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> Union[List[Control], None]: ... + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[Union[List[Control], None]]: ... + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[List[Control], None]]: ... @overload - def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> Union[List[Control], None]: ... + def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[List[Control], None]: ... # fmt: on def win_get_control_list( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[List[Control], None, SyncFutureResult[Optional[List[Control]]]]: args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') resp = self._transport.function_call('AHKWinGetControlList', args, blocking=blocking, engine=self) return resp @@ -551,26 +632,33 @@ def win_get_from_mouse_position( # fmt: off @overload - def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '') -> bool: ... + def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> bool: ... @overload - def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[bool]: ... @overload - def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... # fmt: on def win_exists( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[bool, SyncFutureResult[bool]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') resp = self._transport.function_call('AHKWinExist', 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 = '') -> None: ... + def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[True]) -> None: ... + def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... # fmt: on def win_set_title( self, @@ -580,17 +668,17 @@ def win_set_title( exclude_title: str = '', exclude_text: str = '', *, - blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: raise NotImplementedError() # 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 = '') -> None: ... + 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 = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[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 = '', *, blocking: Literal[True]) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_always_on_top( self, @@ -600,94 +688,136 @@ def win_set_always_on_top( exclude_title: str = '', exclude_text: str = '', *, - blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [str(toggle), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> None: ... + def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... @overload - def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... @overload - def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_bottom( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[None, SyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> None: ... + def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... @overload - def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... @overload - def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_top( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[None, SyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> None: ... + def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... @overload - def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... @overload - def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_disable( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[None, SyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> None: ... + def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... @overload - def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... @overload - def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_enable( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[None, SyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> None: ... + def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... @overload - def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... @overload - def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_redraw( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: bool = True + self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True ) -> Union[None, SyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> bool: ... + def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[bool]: ... @overload - def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + def win_set_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... # fmt: on def win_set_style( self, @@ -697,19 +827,26 @@ def win_set_style( exclude_title: str = '', exclude_text: str = '', *, - blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[bool, SyncFutureResult[bool]]: args = [style, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> bool: ... + def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[bool]: ... @overload - def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... # fmt: on def win_set_ex_style( self, @@ -719,19 +856,26 @@ def win_set_ex_style( exclude_title: str = '', exclude_text: str = '', *, - blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[bool, SyncFutureResult[bool]]: args = [style, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> bool: ... + def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[bool]: ... @overload - def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> bool: ... + def win_set_region(self, options: str, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... # fmt: on def win_set_region( self, @@ -741,19 +885,26 @@ def win_set_region( exclude_title: str = '', exclude_text: str = '', *, - blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[bool, SyncFutureResult[bool]]: args = [options, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> None: ... + def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... @overload - def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_transparent( self, @@ -763,19 +914,26 @@ def win_set_transparent( exclude_title: str = '', exclude_text: str = '', *, - blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [str(transparency), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') 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 = '') -> None: ... + def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... @overload - def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, blocking: Literal[True]) -> None: ... + def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_trans_color( self, @@ -785,9 +943,16 @@ def win_set_trans_color( exclude_title: str = '', exclude_text: str = '', *, - blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [str(color), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') resp = self._transport.function_call('AHKWinSetTransColor', args, blocking=blocking) return resp diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index a532768b..54018b5b 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -46,6 +46,7 @@ SyncFutureResult: TypeAlias = Future FunctionName = Literal[ + Literal['AHKSetDetectHiddenWindows'], Literal['AHKWinExist'], Literal['ImageSearch'], Literal['PixelGetColor'], @@ -386,6 +387,9 @@ def function_call(self, function_name: Literal['AHKWinSetTransparent'], args: Op @overload def function_call(self, function_name: Literal['AHKWinSetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + @overload + def function_call(self, function_name: Literal['AHKSetDetectHiddenWindows'], args: Optional[List[str]] = None) -> None: ... + # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 5fff695a..e639fd7b 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -27,12 +27,26 @@ FormatNoValueResponse() { return FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) } +AHKSetDetectHiddenWindows(ByRef command) { + value := command[2] + DetectHiddenWindows, %value% + return FormatNoValueResponse() +} + AHKWinExist(ByRef command) { global BOOLEANRESPONSEMESSAGE title := command[2] text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + if WinExist(title, text, extitle, extext) { resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) } else { @@ -47,6 +61,14 @@ AHKWinClose(ByRef command) { secondstowait := command[4] extitle := command[5] extext := command[6] + detect_hw := command[7] + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% return FormatNoValueResponse() } @@ -57,13 +79,21 @@ AHKWinGetID(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + 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(WINDOWRESPONSEMESSAGE, output) } - + DetectHiddenWindows, %current_detect_hw% return response } @@ -73,7 +103,17 @@ AHKWinGetTitle(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinGetTitle, text, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + return FormatResponse(STRINGRESPONSEMESSAGE, text) } @@ -83,12 +123,21 @@ AHKWinGetIDLast(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + 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(WINDOWRESPONSEMESSAGE, output) } + DetectHiddenWindows, %current_detect_hw% return response } @@ -99,12 +148,21 @@ AHKWinGetPID(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + 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(INTEGERRESPONSEMESSAGE, output) } + DetectHiddenWindows, %current_detect_hw% return response } @@ -115,12 +173,21 @@ AHKWinGetProcessName(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + 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(STRINGRESPONSEMESSAGE, output) } + DetectHiddenWindows, %current_detect_hw% return response } @@ -130,12 +197,21 @@ AHKWinGetProcessPath(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + 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(STRINGRESPONSEMESSAGE, output) } + DetectHiddenWindows, %current_detect_hw% return response } @@ -146,27 +222,24 @@ AHKWinGetCount(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinGet, output, Count, %title%, %text%, %extitle%, %extext% if (output = 0) { response := FormatResponse(INTEGERRESPONSEMESSAGE, output) } else { response := FormatResponse(INTEGERRESPONSEMESSAGE, output) } + DetectHiddenWindows, %current_detect_hw% return response } -;WinGetList(ByRef command) { -; global STRINGRESPONSEMESSAGE -; global INTEGERRESPONSEMESSAGE -; global NOVALUERESPONSEMESSAGE -; title := command[2] -; text := command[3] -; extitle := command[4] -; extext := command[5] -; WinGet, output, List, %title%, %text%, %extitle%, %extext% -; response := FormatResponse(NOVALUERESPONSEMESSAGE, output) -; return response -;} AHKWinGetMinMax(ByRef command) { @@ -175,12 +248,21 @@ AHKWinGetMinMax(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + 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(INTEGERRESPONSEMESSAGE, output) } + DetectHiddenWindows, %current_detect_hw% return response } @@ -191,6 +273,14 @@ AHKWinGetControlList(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinGet, ahkid, ID, %title%, %text%, %extitle%, %extext% @@ -220,6 +310,7 @@ AHKWinGetControlList(ByRef command) { } output .= "])" response := FormatResponse(WINDOWCONTROLLISTRESPONSEMESSAGE, output) + DetectHiddenWindows, %current_detect_hw% return response } ;AHKWinGetControlListHwnd(ByRef command) { @@ -230,8 +321,16 @@ AHKWinGetControlList(ByRef command) { ; text := command[3] ; extitle := command[4] ; extext := command[5] +; detect_hw := command[6] +; +; current_detect_hw := Format("{}", A_DetectHiddenWindows) +; +;if (detect_hw != "") { +; DetectHiddenWindows, %detect_hw% +;} ; WinGet, output, ControlListHwnd, %title%, %text%, %extitle%, %extext% ; response := FormatResponse(NOVALUERESPONSEMESSAGE, output) +; DetectHiddenWindows, %current_detect_hw% ; return response ;} @@ -241,8 +340,17 @@ AHKWinGetTransparent(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinGet, output, Transparent, %title%, %text%, %extitle%, %extext% response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + DetectHiddenWindows, %current_detect_hw% return response } AHKWinGetTransColor(ByRef command) { @@ -253,8 +361,17 @@ AHKWinGetTransColor(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinGet, output, TransColor, %title%, %text%, %extitle%, %extext% response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + DetectHiddenWindows, %current_detect_hw% return response } AHKWinGetStyle(ByRef command) { @@ -265,8 +382,17 @@ AHKWinGetStyle(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinGet, output, Style, %title%, %text%, %extitle%, %extext% response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + DetectHiddenWindows, %current_detect_hw% return response } AHKWinGetExStyle(ByRef command) { @@ -277,8 +403,17 @@ AHKWinGetExStyle(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinGet, output, ExStyle, %title%, %text%, %extitle%, %extext% response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + DetectHiddenWindows, %current_detect_hw% return response } @@ -288,7 +423,15 @@ AHKWinSetAlwaysOnTop(ByRef command) { text := command[4] extitle := command[5] extext := command[6] + detect_hw := command[7] + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinSet, AlwaysOntop, %toggle%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% return FormatNoValueResponse() } @@ -297,7 +440,16 @@ AHKWinSetBottom(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinSet, Bottom,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% return FormatNoValueResponse() } @@ -306,7 +458,16 @@ AHKWinSetTop(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinSet, Top,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% return FormatNoValueResponse() } @@ -315,7 +476,16 @@ AHKWinSetEnable(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinSet, Enable,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% return FormatNoValueResponse() } @@ -324,7 +494,16 @@ AHKWinSetDisable(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinSet, Disable,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% return FormatNoValueResponse() } @@ -333,7 +512,16 @@ AHKWinSetRedraw(ByRef command) { text := command[3] extitle := command[4] extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinSet, Redraw,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% return FormatNoValueResponse() } @@ -344,7 +532,16 @@ AHKWinSetStyle(ByRef command) { text := command[4] extitle := command[5] extext := command[6] + detect_hw := command[7] + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Style, %style%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% if (ErrorLevel = 1) { return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } else { @@ -359,7 +556,16 @@ AHKWinSetExStyle(ByRef command) { text := command[4] extitle := command[5] extext := command[6] + detect_hw := command[7] + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, ExStyle, %style%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% if (ErrorLevel = 1) { return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } else { @@ -374,7 +580,16 @@ AHKWinSetRegion(ByRef command) { text := command[4] extitle := command[5] extext := command[6] + detect_hw := command[7] + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Region, %options%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% if (ErrorLevel = 1) { return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } else { @@ -389,7 +604,16 @@ AHKWinSetTransparent(ByRef command) { text := command[4] extitle := command[5] extext := command[6] + detect_hw := command[7] + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, Transparent, %transparency%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% return FormatNoValueResponse() } @@ -400,6 +624,14 @@ AHKWinSetTransColor(ByRef command) { text := command[4] extitle := command[5] extext := command[6] + detect_hw := command[7] + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% return FormatNoValueResponse() } @@ -831,6 +1063,14 @@ ControlSend(ByRef command) { text := command[4] extitle := command[5] extext := command[6] + detect_hw := command[7] + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + command.RemoveAt(1) command.RemoveAt(1) command.RemoveAt(1) @@ -839,6 +1079,7 @@ ControlSend(ByRef command) { command.RemoveAt(1) str := Join(",", command*) keys := Unescape(str) + DetectHiddenWindows, %current_detect_hw% ControlSend, %ctrl%,% keys, %title%, %text%, %extitle%, %extext% } diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 1e54a50b..4a88b8da 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -67,3 +67,14 @@ 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 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) diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 038ad640..2a2bf4bb 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -67,3 +67,14 @@ 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 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) From 6242cd21b85070aa4035ca829941257ef1ac8a12 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 29 Jul 2022 03:11:44 -0700 Subject: [PATCH 258/588] more tests --- ahk/_async/engine.py | 2 +- ahk/_async/window.py | 6 + ahk/_sync/engine.py | 248 +++++++++++++++++++++++++++++------- ahk/_sync/window.py | 9 +- tests/_async/test_window.py | 29 ++++- tests/_sync/test_window.py | 29 ++++- 6 files changed, 270 insertions(+), 53 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index f4dea1dd..f871e322 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -510,7 +510,7 @@ async def win_get_idlast( raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' ) - resp = await self._transport.function_call('AHKWinGetIDLast', args, blocking=blocking) + resp = await self._transport.function_call('AHKWinGetIDLast', args, blocking=blocking, engine=self) return resp # fmt: off diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 49097b2f..35ef8884 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -85,6 +85,12 @@ async def list_controls(self) -> Sequence['AsyncControl']: ) return controls + 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 + ) + return None + # fmt: off @overload async def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> None: ... diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index ab4fbc62..eca26d2f 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -61,7 +61,6 @@ def start_hotkeys(self) -> None: def stop_hotkeys(self) -> None: return self._transport.stop_hotkeys() - def set_detect_hidden_windows(self, value: bool) -> None: if value not in (True, False): raise TypeError(f'detect hidden windows must be a boolean, got object of type {type(value)}') @@ -73,7 +72,6 @@ def set_detect_hidden_windows(self, value: bool) -> None: self._transport.function_call('AHKSetDetectHiddenWindows', args=args) return None - # fmt: off @overload def list_windows(self, *, detect_hidden_windows: Optional[bool] = None) -> List[Window]: ... @@ -92,7 +90,9 @@ def list_windows( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('WindowList', args, engine=self, blocking=blocking) return resp @@ -428,7 +428,14 @@ def win_get(self, title: str = '', text: str = '', exclude_title: str = '', excl def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[Window, None]: ... # fmt: on def win_get( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[Window, None, SyncFutureResult[Union[None, Window]]]: args = [title, text, exclude_title, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -437,7 +444,9 @@ def win_get( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinGetID', args, blocking=blocking, engine=self) return resp @@ -450,7 +459,14 @@ def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '' def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... # fmt: on def win_get_title( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[str, SyncFutureResult[str]]: args = [title, text, exclude_title, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -459,7 +475,9 @@ def win_get_title( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinGetTitle', args, blocking=blocking) return resp @@ -472,7 +490,14 @@ def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = ' def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[Window, None]: ... # fmt: on def win_get_idlast( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[Window, None, SyncFutureResult[Union[Window, None]]]: args = [title, text, exclude_title, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -481,8 +506,10 @@ def win_get_idlast( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') - resp = self._transport.function_call('AHKWinGetIDLast', args, blocking=blocking) + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + resp = self._transport.function_call('AHKWinGetIDLast', args, blocking=blocking, engine=self) return resp # fmt: off @@ -494,7 +521,14 @@ def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... # fmt: on def win_get_pid( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[int, None, SyncFutureResult[Union[int, None]]]: args = [title, text, exclude_title, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -503,7 +537,9 @@ def win_get_pid( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinGetPID', args, blocking=blocking) return resp @@ -516,7 +552,14 @@ def win_get_process_name(self, title: str = '', text: str = '', exclude_title: s def win_get_process_name(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... # fmt: on def win_get_process_name( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, str, SyncFutureResult[Optional[str]]]: args = [title, text, exclude_title, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -525,7 +568,9 @@ def win_get_process_name( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinGetProcessName', args, blocking=blocking) return resp @@ -538,7 +583,14 @@ def win_get_process_path(self, title: str = '', text: str = '', exclude_title: s def win_get_process_path(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... # fmt: on def win_get_process_path( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[str, None, Union[None, str, SyncFutureResult[Optional[str]]]]: args = [title, text, exclude_title, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -547,7 +599,9 @@ def win_get_process_path( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinGetProcessPath', args, blocking=blocking) return resp @@ -560,7 +614,14 @@ def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '' def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> int: ... # fmt: on def win_get_count( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[int, SyncFutureResult[int]]: args = [title, text, exclude_title, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -569,7 +630,9 @@ def win_get_count( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinGetCount', args, blocking=blocking) return resp @@ -582,7 +645,14 @@ def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = ' def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... # fmt: on def win_get_minmax( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, int, SyncFutureResult[Optional[int]]]: args = [title, text, exclude_title, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -591,7 +661,9 @@ def win_get_minmax( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinGetMinMax', args, blocking=blocking) return resp @@ -604,7 +676,14 @@ def win_get_control_list(self, title: str = '', text: str = '', exclude_title: s def win_get_control_list(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[List[Control], None]: ... # fmt: on def win_get_control_list( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[List[Control], None, SyncFutureResult[Optional[List[Control]]]]: args = [title, text, exclude_title, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -613,7 +692,9 @@ def win_get_control_list( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinGetControlList', args, blocking=blocking, engine=self) return resp @@ -639,7 +720,14 @@ def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', e def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... # fmt: on def win_exists( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[bool, SyncFutureResult[bool]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -648,7 +736,9 @@ def win_exists( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinExist', args, blocking=blocking) return resp @@ -668,7 +758,8 @@ def win_set_title( exclude_title: str = '', exclude_text: str = '', *, - detect_hidden_windows: Optional[bool] = None, blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: raise NotImplementedError() @@ -688,7 +779,8 @@ def win_set_always_on_top( exclude_title: str = '', exclude_text: str = '', *, - detect_hidden_windows: Optional[bool] = None, blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [str(toggle), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -697,7 +789,9 @@ def win_set_always_on_top( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetAlwaysOnTop', args, blocking=blocking) return resp @@ -710,7 +804,14 @@ def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = ' def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_bottom( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -719,7 +820,9 @@ def win_set_bottom( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetBottom', args, blocking=blocking) return resp @@ -732,7 +835,14 @@ def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_top( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -741,7 +851,9 @@ def win_set_top( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetTop', args, blocking=blocking) return resp @@ -754,7 +866,14 @@ def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_disable( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -763,7 +882,9 @@ def win_set_disable( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetDisable', args, blocking=blocking) return resp @@ -776,7 +897,14 @@ def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = ' def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_enable( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -785,7 +913,9 @@ def win_set_enable( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetEnable', args, blocking=blocking) return resp @@ -798,7 +928,14 @@ def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = ' def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... # fmt: on def win_set_redraw( - self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -807,7 +944,9 @@ def win_set_redraw( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetRedraw', args, blocking=blocking) return resp @@ -827,7 +966,8 @@ def win_set_style( exclude_title: str = '', exclude_text: str = '', *, - detect_hidden_windows: Optional[bool] = None, blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[bool, SyncFutureResult[bool]]: args = [style, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -836,7 +976,9 @@ def win_set_style( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetStyle', args, blocking=blocking) return resp @@ -856,7 +998,8 @@ def win_set_ex_style( exclude_title: str = '', exclude_text: str = '', *, - detect_hidden_windows: Optional[bool] = None, blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[bool, SyncFutureResult[bool]]: args = [style, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -865,7 +1008,9 @@ def win_set_ex_style( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetExStyle', args, blocking=blocking) return resp @@ -885,7 +1030,8 @@ def win_set_region( exclude_title: str = '', exclude_text: str = '', *, - detect_hidden_windows: Optional[bool] = None, blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[bool, SyncFutureResult[bool]]: args = [options, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -894,7 +1040,9 @@ def win_set_region( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetRegion', args, blocking=blocking) return resp @@ -914,7 +1062,8 @@ def win_set_transparent( exclude_title: str = '', exclude_text: str = '', *, - detect_hidden_windows: Optional[bool] = None, blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [str(transparency), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -923,7 +1072,9 @@ def win_set_transparent( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetTransparent', args, blocking=blocking) return resp @@ -943,7 +1094,8 @@ def win_set_trans_color( exclude_title: str = '', exclude_text: str = '', *, - detect_hidden_windows: Optional[bool] = None, blocking: bool = True, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: args = [str(color), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -952,7 +1104,9 @@ def win_set_trans_color( elif detect_hidden_windows is False: args.append('Off') else: - raise TypeError(f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}') + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) resp = self._transport.function_call('AHKWinSetTransColor', args, blocking=blocking) return resp diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 0fc26957..667c4412 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -85,6 +85,11 @@ def list_controls(self) -> Sequence['Control']: ) return controls + 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) + return None + + # fmt: off @overload def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> None: ... @@ -97,9 +102,7 @@ def set_always_on_top( self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True ) -> Union[None, SyncFutureResult[None]]: if blocking: - self._engine.win_set_always_on_top( - toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True - ) + self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True) return None else: resp = self._engine.win_set_always_on_top( diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 4a88b8da..607062a8 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -74,7 +74,34 @@ async def test_set_detect_hidden_windows(self): all_windows = await self.ahk.list_windows() assert len(all_windows) > len(non_hidden) - async def list_windows_hidden(self): + 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 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' diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 2a2bf4bb..58d2d103 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -74,7 +74,34 @@ def test_set_detect_hidden_windows(self): all_windows = self.ahk.list_windows() assert len(all_windows) > len(non_hidden) - def list_windows_hidden(self): + 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' From 545ed7ec57ca85414eea097c625ff19bd5dee298 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 29 Jul 2022 03:23:57 -0700 Subject: [PATCH 259/588] win set title --- ahk/_async/engine.py | 13 ++++++++++++- ahk/_async/transport.py | 4 +++- ahk/_sync/engine.py | 13 ++++++++++++- ahk/_sync/transport.py | 4 +++- ahk/daemon.ahk | 17 +++++++++++++++++ 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index f871e322..386835f8 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -762,7 +762,18 @@ async def win_set_title( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: - raise NotImplementedError() + args = [new_title, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + resp = await self._transport.function_call('AHKWinSetTitle', args, blocking=blocking) + return resp # fmt: off @overload diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index c459cfb7..cfee2283 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -49,6 +49,7 @@ FunctionName = Literal[ Literal['AHKSetDetectHiddenWindows'], + Literal['AHKWinSetTitle'], Literal['AHKWinExist'], Literal['ImageSearch'], Literal['PixelGetColor'], @@ -400,7 +401,8 @@ async def function_call(self, function_name: Literal['AHKWinSetTransColor'], arg @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['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index eca26d2f..fc31d558 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -761,7 +761,18 @@ def win_set_title( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: - raise NotImplementedError() + args = [new_title, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + resp = self._transport.function_call('AHKWinSetTitle', args, blocking=blocking) + return resp # fmt: off @overload diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 54018b5b..038087b1 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -47,6 +47,7 @@ FunctionName = Literal[ Literal['AHKSetDetectHiddenWindows'], + Literal['AHKWinSetTitle'], Literal['AHKWinExist'], Literal['ImageSearch'], Literal['PixelGetColor'], @@ -389,7 +390,8 @@ def function_call(self, function_name: Literal['AHKWinSetTransColor'], args: Opt @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, SyncFutureResult[None]]: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index e639fd7b..abeeacae 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -417,6 +417,23 @@ AHKWinGetExStyle(ByRef command) { return response } +AHKWinSetTitle(ByRef command) { + new_title := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + WinSetTitle, %title%, %text%, %new_title%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + return FormatNoValueResponse() +} + AHKWinSetAlwaysOnTop(ByRef command) { toggle := command[2] title := command[3] From 7c0170a88b1854346f64b7e52fe94765e8282cdc Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 29 Jul 2022 13:44:41 -0700 Subject: [PATCH 260/588] control send get text --- ahk/_async/engine.py | 94 +++++++++++++++++++++++++++++++++++-- ahk/_async/transport.py | 28 +++++------ ahk/_async/window.py | 32 +++++++++++++ ahk/_sync/engine.py | 94 +++++++++++++++++++++++++++++++++++-- ahk/_sync/transport.py | 28 +++++------ ahk/_sync/window.py | 37 ++++++++++++++- ahk/daemon.ahk | 51 +++++++++++++++----- tests/_async/test_window.py | 5 ++ tests/_sync/test_window.py | 5 ++ 9 files changed, 325 insertions(+), 49 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 386835f8..4d8ac6c2 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1,7 +1,9 @@ from __future__ import annotations import asyncio +import sys import time +import warnings from typing import Any from typing import Callable from typing import Dict @@ -11,11 +13,15 @@ from typing import NoReturn from typing import Optional from typing import overload -from typing import Sequence from typing import Tuple from typing import Type from typing import Union +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + from ..keys import Key from .transport import AsyncDaemonProcessTransport from .transport import AsyncFutureResult @@ -32,6 +38,11 @@ CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] +MatchModes: TypeAlias = Literal[1, 2, 3, 'RegEx'] +MatchSpeeds: TypeAlias = Literal['Fast', 'Slow'] + +TitleMatchMode: TypeAlias = Optional[Union[MatchModes, MatchSpeeds, Tuple[MatchModes, MatchSpeeds]]] + class AsyncAHK: def __init__( @@ -48,6 +59,16 @@ def __init__( transport = TransportClass(**transport_options) self._transport: AsyncTransport = transport + def __getattr__(self, item: Any) -> Any: + deprecation_replacements: Dict[str, Any] = {'type': self.send_input} + if item in deprecation_replacements: + warnings.warn( + 'type is deprecated and will be removed in a future version. Use `send_input` instead.', + DeprecationWarning, + stacklevel=2, + ) + return deprecation_replacements[item] + def add_hotkey( self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None ) -> None: @@ -56,6 +77,40 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + # fmt: off + @overload + async def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + # fmt: on + async def control_send( + self, + keys: str, + control: str = '', + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + args = [control, title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + args.append(keys) + resp = await self._transport.function_call('AHKControlSend', args, blocking=blocking) + return resp + def start_hotkeys(self) -> None: return self._transport.start_hotkeys() @@ -316,10 +371,10 @@ async def send( ) -> Union[None, AsyncFutureResult[None]]: args = [s] if raw: - raw_resp = await self._transport.function_call('SendRaw', args=args, blocking=blocking) + raw_resp = await self._transport.function_call('AHKSendRaw', args=args, blocking=blocking) return raw_resp else: - resp = await self._transport.function_call('Send', args=args, blocking=blocking) + resp = await self._transport.function_call('AHKSend', args=args, blocking=blocking) return resp async def send_event(self, s: str, delay: Optional[int] = None) -> None: @@ -335,7 +390,7 @@ async def send_input(self, s: str, *, blocking: Literal[False]) -> AsyncFutureRe # fmt: on async def send_input(self, s: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: args = [s] - resp = await self._transport.function_call('SendInput', args, blocking=blocking) + resp = await self._transport.function_call('AHKSendInput', args, blocking=blocking) return resp async def send_play(self, s: str) -> None: @@ -451,6 +506,37 @@ async def win_get( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Optional[str]: ... + @overload + async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Optional[str]]: ... + @overload + async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Optional[str]: ... + # fmt: on + async def win_get_text( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[Optional[str], AsyncFutureResult[Optional[str]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> str: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index cfee2283..7ed4089f 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -62,15 +62,15 @@ Literal['MouseClickDrag'], Literal['KeyWait'], Literal['SetKeyDelay'], - Literal['Send'], - Literal['SendRaw'], - Literal['SendInput'], - Literal['SendEvent'], - Literal['SendPlay'], + Literal['AHKSend'], + Literal['AHKSendRaw'], + Literal['AHKSendInput'], + Literal['AHKSendEvent'], + Literal['AHKSendPlay'], Literal['SetCapsLockState'], Literal['AHKWinGetTitle'], Literal['WinGetClass'], - Literal['WinGetText'], + Literal['AHKWinGetText'], Literal['WinActivate'], Literal['WinActivateBottom'], Literal['AHKWinClose'], @@ -83,7 +83,7 @@ Literal['WindowList'], Literal['WinSend'], Literal['WinSendRaw'], - Literal['ControlSend'], + Literal['AHKControlSend'], Literal['FromMouse'], Literal['WinGet'], Literal['WinSet'], @@ -287,15 +287,15 @@ async def function_call(self, function_name: Literal['KeyWait'], args: Optional[ @overload async def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['Send'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SendInput'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKSendInput'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SendEvent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKSendEvent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKSendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload async def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload @@ -303,7 +303,7 @@ async def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Op @overload async def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload - async def function_call(self, function_name: Literal['WinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... + async def function_call(self, function_name: Literal['AHKWinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Optional[str], AsyncFutureResult[Optional[str]]]: ... @overload async def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload @@ -329,7 +329,7 @@ async def function_call(self, function_name: Literal['WinSend'], args: Optional[ @overload async def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['ControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload async def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 35ef8884..cf298541 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -130,6 +130,38 @@ async def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, AsyncF ) return resp + # fmt: off + @overload + async def send(self, keys: str) -> None: ... + @overload + async def send(self, keys: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + # fmt: on + async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + if blocking: + await self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=True) + return None + else: + resp = await self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=False) + return resp + + # fmt: off + @overload + async def get_text(self) -> Optional[str]: ... + @overload + async def get_text(self, *, blocking: Literal[False]) -> AsyncFutureResult[Optional[str]]: ... + @overload + async def get_text(self, *, blocking: Literal[True]) -> Optional[str]: ... + # fmt: on + async def get_text(self, *, blocking: bool = True) -> Union[Optional[str], AsyncFutureResult[Optional[str]]]: + if blocking: + resp = await self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=True) + return resp + else: + nonblocking_resp = await self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=False) + return nonblocking_resp + class AsyncControl: def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index fc31d558..1ded9f1a 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1,7 +1,9 @@ from __future__ import annotations import asyncio +import sys import time +import warnings from typing import Any from typing import Callable from typing import Dict @@ -11,11 +13,15 @@ from typing import NoReturn from typing import Optional from typing import overload -from typing import Sequence from typing import Tuple from typing import Type from typing import Union +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + from ..keys import Key from .transport import DaemonProcessTransport from .transport import SyncFutureResult @@ -31,6 +37,11 @@ CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] +MatchModes: TypeAlias = Literal[1, 2, 3, 'RegEx'] +MatchSpeeds: TypeAlias = Literal['Fast', 'Slow'] + +TitleMatchMode: TypeAlias = Optional[Union[MatchModes, MatchSpeeds, Tuple[MatchModes, MatchSpeeds]]] + class AHK: def __init__( @@ -47,6 +58,16 @@ def __init__( transport = TransportClass(**transport_options) self._transport: Transport = transport + def __getattr__(self, item: Any) -> Any: + deprecation_replacements: Dict[str, Any] = {'type': self.send_input} + if item in deprecation_replacements: + warnings.warn( + 'type is deprecated and will be removed in a future version. Use `send_input` instead.', + DeprecationWarning, + stacklevel=2, + ) + return deprecation_replacements[item] + def add_hotkey( self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None ) -> None: @@ -55,6 +76,40 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + # fmt: off + @overload + def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + @overload + def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + # fmt: on + def control_send( + self, + keys: str, + control: str = '', + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[None, SyncFutureResult[None]]: + args = [control, title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + args.append(keys) + resp = self._transport.function_call('AHKControlSend', args, blocking=blocking) + return resp + def start_hotkeys(self) -> None: return self._transport.start_hotkeys() @@ -315,10 +370,10 @@ def send( ) -> Union[None, SyncFutureResult[None]]: args = [s] if raw: - raw_resp = self._transport.function_call('SendRaw', args=args, blocking=blocking) + raw_resp = self._transport.function_call('AHKSendRaw', args=args, blocking=blocking) return raw_resp else: - resp = self._transport.function_call('Send', args=args, blocking=blocking) + resp = self._transport.function_call('AHKSend', args=args, blocking=blocking) return resp def send_event(self, s: str, delay: Optional[int] = None) -> None: @@ -334,7 +389,7 @@ def send_input(self, s: str, *, blocking: Literal[False]) -> SyncFutureResult[No # fmt: on def send_input(self, s: str, *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: args = [s] - resp = self._transport.function_call('SendInput', args, blocking=blocking) + resp = self._transport.function_call('AHKSendInput', args, blocking=blocking) return resp def send_play(self, s: str) -> None: @@ -450,6 +505,37 @@ def win_get( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Optional[str]: ... + @overload + def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Optional[str]]: ... + @overload + def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Optional[str]: ... + # fmt: on + def win_get_text( + self, + title: str = '', + text: str = '', + exclude_title: str = '', + exclude_text: str = '', + *, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[Optional[str], SyncFutureResult[Optional[str]]]: + args = [title, text, exclude_title, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + else: + raise TypeError( + f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' + ) + 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> str: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 038087b1..3c23a7f1 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -60,15 +60,15 @@ Literal['MouseClickDrag'], Literal['KeyWait'], Literal['SetKeyDelay'], - Literal['Send'], - Literal['SendRaw'], - Literal['SendInput'], - Literal['SendEvent'], - Literal['SendPlay'], + Literal['AHKSend'], + Literal['AHKSendRaw'], + Literal['AHKSendInput'], + Literal['AHKSendEvent'], + Literal['AHKSendPlay'], Literal['SetCapsLockState'], Literal['AHKWinGetTitle'], Literal['WinGetClass'], - Literal['WinGetText'], + Literal['AHKWinGetText'], Literal['WinActivate'], Literal['WinActivateBottom'], Literal['AHKWinClose'], @@ -81,7 +81,7 @@ Literal['WindowList'], Literal['WinSend'], Literal['WinSendRaw'], - Literal['ControlSend'], + Literal['AHKControlSend'], Literal['FromMouse'], Literal['WinGet'], Literal['WinSet'], @@ -276,15 +276,15 @@ def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[s @overload def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['Send'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SendInput'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSendInput'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SendEvent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSendEvent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload @@ -292,7 +292,7 @@ def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Optional @overload def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... + def function_call(self, function_name: Literal['AHKWinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Optional[str], SyncFutureResult[Optional[str]]]: ... @overload def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload @@ -318,7 +318,7 @@ def function_call(self, function_name: Literal['WinSend'], args: Optional[List[s @overload def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['ControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 667c4412..1661be5c 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -86,10 +86,11 @@ def list_controls(self) -> Sequence['Control']: return controls 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) + self._engine.win_set_title( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, new_title=new_title + ) return None - # fmt: off @overload def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> None: ... @@ -129,6 +130,38 @@ def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, SyncFutureRe ) return resp + # fmt: off + @overload + def send(self, keys: str) -> None: ... + @overload + def send(self, keys: str, *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + @overload + def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + # fmt: on + def send(self, keys: str, *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + if blocking: + self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=True) + return None + else: + resp = self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=False) + return resp + + # fmt: off + @overload + def get_text(self) -> Optional[str]: ... + @overload + def get_text(self, *, blocking: Literal[False]) -> SyncFutureResult[Optional[str]]: ... + @overload + def get_text(self, *, blocking: Literal[True]) -> Optional[str]: ... + # fmt: on + def get_text(self, *, blocking: bool = True) -> Union[Optional[str], SyncFutureResult[Optional[str]]]: + if blocking: + resp = self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=True) + return resp + else: + nonblocking_resp = self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=False) + return nonblocking_resp + class Control: def __init__(self, window: Window, hwnd: str, control_class: str): diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index abeeacae..d4607b43 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -417,6 +417,38 @@ AHKWinGetExStyle(ByRef command) { return response } +AHKWinGetText(ByRef command) { + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + if (detect_hw != "") { + DetectHiddenWindows, %detect_hw% + } + + WinGetText, output, %title%, %text%, %extitle%, %extext% + + if (ErrorLevel = 1) { + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window text") + } + + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + DetectHiddenWindows, %current_detect_hw% + return response +} + + + AHKWinSetTitle(ByRef command) { new_title := command[2] title := command[3] @@ -838,7 +870,7 @@ Unescape(HayStack) { return ReplacedStr } -Send(ByRef command) { +AHKSend(ByRef command) { command.RemoveAt(1) s := Join(",", command*) str := Unescape(s) @@ -846,17 +878,17 @@ Send(ByRef command) { return FormatNoValueResponse() } -SendRaw(ByRef command) { +AHKSendRaw(ByRef command) { command.RemoveAt(1) - s := Join(",", command*) + s := Join(",", command*) ; TODO: remove after better input handling is implemented str := Unescape(s) SendRaw,% str return FormatNoValueResponse() } -SendInput(ByRef command) { +AHKSendInput(ByRef command) { command.RemoveAt(1) - s := Join(",", command*) + s := Join(",", command*) ; TODO: remove after better input handling is implemented str := Unescape(s) SendInput,% str return FormatNoValueResponse() @@ -906,11 +938,6 @@ WinGetClass(ByRef command) { WinGetClass, text, %title% return text } -WinGetText(ByRef command) { - title := command[3] - WinGetText, text, %title% - return text -} WinActivate(ByRef command) { title := command[2] @@ -1074,7 +1101,7 @@ WinSendRaw(ByRef command) { ControlSendRaw,,% keys, %title% } -ControlSend(ByRef command) { +AHKControlSend(ByRef command) { ctrl := command[2] title := command[3] text := command[4] @@ -1094,10 +1121,12 @@ ControlSend(ByRef command) { command.RemoveAt(1) command.RemoveAt(1) command.RemoveAt(1) + command.RemoveAt(1) str := Join(",", command*) keys := Unescape(str) DetectHiddenWindows, %current_detect_hw% ControlSend, %ctrl%,% keys, %title%, %text%, %extitle%, %extext% + return FormatNoValueResponse() } diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 607062a8..d9db1075 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -105,3 +105,8 @@ async def test_win_exists(self): 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') + text = await self.win.get_text() + assert 'Hello World' in text diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 58d2d103..a399463d 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -105,3 +105,8 @@ def test_win_exists(self): 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') + text = self.win.get_text() + assert 'Hello World' in text From e0e33536b0567b91848443ab8ab7b5945e3ed1f6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 29 Jul 2022 19:19:47 -0700 Subject: [PATCH 261/588] Add git add intent so pre-commit adds new files Co-authored-by: Anthony Sottile --- .unasync-rewrite.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.unasync-rewrite.py b/.unasync-rewrite.py index dc1f32d6..22378572 100644 --- a/.unasync-rewrite.py +++ b/.unasync-rewrite.py @@ -10,6 +10,8 @@ from tokenize_rt import src_to_tokens from tokenize_rt import tokens_to_src +GIT_EXECUTABLE = shutil.which('git') + changes = 0 @@ -57,6 +59,10 @@ def _copyfunc(src, dst, *, follow_symlinks=True): 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 From 65f373d716c8c01f38b60be60adf77b903878a91 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 29 Jul 2022 19:22:47 -0700 Subject: [PATCH 262/588] base64 message parsing in AHK Co-authored-by: Cloaker Smoker <43710474+CloakerSmoker@users.noreply.github.com> --- ahk/daemon.ahk | 79 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index d4607b43..a5551da5 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -425,14 +425,14 @@ AHKWinGetText(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] - + MsgBox, %title%, %text%, %extitle%, %extext%, %detect_hw% current_detect_hw := Format("{}", A_DetectHiddenWindows) if (detect_hw != "") { DetectHiddenWindows, %detect_hw% } - WinGetText, output, %title%, %text%, %extitle%, %extext% + WinGetText, output,% title if (ErrorLevel = 1) { return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window text") @@ -887,9 +887,8 @@ AHKSendRaw(ByRef command) { } AHKSendInput(ByRef command) { - command.RemoveAt(1) - s := Join(",", command*) ; TODO: remove after better input handling is implemented - str := Unescape(s) + + str := command[2] SendInput,% str return FormatNoValueResponse() } @@ -1114,16 +1113,7 @@ AHKControlSend(ByRef command) { DetectHiddenWindows, %detect_hw% } - - command.RemoveAt(1) - command.RemoveAt(1) - command.RemoveAt(1) - command.RemoveAt(1) - command.RemoveAt(1) - command.RemoveAt(1) - command.RemoveAt(1) - str := Join(",", command*) - keys := Unescape(str) + keys := command[8] DetectHiddenWindows, %current_detect_hw% ControlSend, %ctrl%,% keys, %title%, %text%, %extitle%, %extext% return FormatNoValueResponse() @@ -1238,14 +1228,69 @@ CountNewlines(ByRef s) { return count } +AHKEcho(ByRef command) { + global STRINGRESPONSEMESSAGE + return FormatResponse(STRINGRESPONSEMESSAGE, command) +} + + +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. + + 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") +} + +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 +} stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] response := "" Loop { query := RTrim(stdin.ReadLine(), "`n") - commandArray := StrSplit(query, ",") - + commandArray := CommandArrayFromQuery(query) try { func := commandArray[1] response := %func%(commandArray) From c281a09ed7444103d7a2551ec99c2c93a32c5ec4 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 29 Jul 2022 19:51:30 -0700 Subject: [PATCH 263/588] new message encoding --- ahk/_async/engine.py | 30 +++++++++++++++--------------- ahk/_async/transport.py | 15 ++++----------- ahk/_async/window.py | 8 ++++---- ahk/_sync/engine.py | 30 +++++++++++++++--------------- ahk/_sync/transport.py | 15 ++++----------- ahk/_sync/window.py | 8 ++++---- ahk/daemon.ahk | 36 +++++++++++++++++------------------- ahk/message.py | 7 ++++++- tests/_async/test_window.py | 16 +++++++++++++++- tests/_sync/test_window.py | 16 +++++++++++++++- tox.ini | 2 +- 11 files changed, 100 insertions(+), 83 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 4d8ac6c2..40e70b7a 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -97,7 +97,7 @@ async def control_send( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: - args = [control, title, text, exclude_title, exclude_title, exclude_text] + args = [control, keys, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -493,7 +493,7 @@ async def win_get( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[None, AsyncWindow]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -508,11 +508,11 @@ async def win_get( # fmt: off @overload - async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> Optional[str]: ... + async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> str: ... @overload - async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Optional[str]]: ... + async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Optional[str]: ... + async def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... # fmt: on async def win_get_text( self, @@ -523,8 +523,8 @@ async def win_get_text( *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[Optional[str], AsyncFutureResult[Optional[str]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + ) -> Union[str, AsyncFutureResult[str]]: + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -555,7 +555,7 @@ async def win_get_title( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, AsyncFutureResult[str]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -586,7 +586,7 @@ async def win_get_idlast( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[AsyncWindow, None]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -617,7 +617,7 @@ async def win_get_pid( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[int, None, AsyncFutureResult[Union[int, None]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -648,7 +648,7 @@ async def win_get_process_name( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, str, AsyncFutureResult[Optional[str]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -679,7 +679,7 @@ async def win_get_process_path( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, None, Union[None, str, AsyncFutureResult[Optional[str]]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -710,7 +710,7 @@ async def win_get_count( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[int, AsyncFutureResult[int]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -741,7 +741,7 @@ async def win_get_minmax( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, int, AsyncFutureResult[Optional[int]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -772,7 +772,7 @@ async def win_get_control_list( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[List[AsyncControl], None, AsyncFutureResult[Optional[List[AsyncControl]]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 7ed4089f..e14bbde4 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -3,6 +3,7 @@ import asyncio.subprocess import atexit import os +import re import subprocess import sys import warnings @@ -303,7 +304,7 @@ async def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Op @overload async def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = None) -> Union[Optional[str], AsyncFutureResult[Optional[str]]]: ... + async def function_call(self, function_name: Literal['AHKWinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload async def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload @@ -492,11 +493,7 @@ async def _create_process(self) -> AsyncAHKProcess: async def _send_nonblocking( self, request: RequestMessage, engine: Optional[AsyncAHK] = None ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: - newline = '\n' - - msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( - 'utf-8' - ) + msg = request.format() proc = await self._create_process() try: proc.write(msg) @@ -545,11 +542,7 @@ def send_nonblocking( async def send( self, request: RequestMessage, engine: Optional[AsyncAHK] = None ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: - newline = '\n' - - msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( - 'utf-8' - ) + msg = request.format() assert self._proc is not None self._proc.write(msg) await self._proc.adrain_stdin() diff --git a/ahk/_async/window.py b/ahk/_async/window.py index cf298541..91beaf23 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -148,13 +148,13 @@ async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFu # fmt: off @overload - async def get_text(self) -> Optional[str]: ... + async def get_text(self) -> str: ... @overload - async def get_text(self, *, blocking: Literal[False]) -> AsyncFutureResult[Optional[str]]: ... + async def get_text(self, *, blocking: Literal[False]) -> AsyncFutureResult[str]: ... @overload - async def get_text(self, *, blocking: Literal[True]) -> Optional[str]: ... + async def get_text(self, *, blocking: Literal[True]) -> str: ... # fmt: on - async def get_text(self, *, blocking: bool = True) -> Union[Optional[str], AsyncFutureResult[Optional[str]]]: + async def get_text(self, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: if blocking: resp = await self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=True) return resp diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 1ded9f1a..2843aade 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -96,7 +96,7 @@ def control_send( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, SyncFutureResult[None]]: - args = [control, title, text, exclude_title, exclude_title, exclude_text] + args = [control, keys, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -492,7 +492,7 @@ def win_get( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Window, None, SyncFutureResult[Union[None, Window]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -507,11 +507,11 @@ def win_get( # fmt: off @overload - def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> Optional[str]: ... + def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> str: ... @overload - def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Optional[str]]: ... + def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[str]: ... @overload - def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Optional[str]: ... + def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... # fmt: on def win_get_text( self, @@ -522,8 +522,8 @@ def win_get_text( *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[Optional[str], SyncFutureResult[Optional[str]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + ) -> Union[str, SyncFutureResult[str]]: + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -554,7 +554,7 @@ def win_get_title( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, SyncFutureResult[str]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -585,7 +585,7 @@ def win_get_idlast( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Window, None, SyncFutureResult[Union[Window, None]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -616,7 +616,7 @@ def win_get_pid( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[int, None, SyncFutureResult[Union[int, None]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -647,7 +647,7 @@ def win_get_process_name( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, str, SyncFutureResult[Optional[str]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -678,7 +678,7 @@ def win_get_process_path( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, None, Union[None, str, SyncFutureResult[Optional[str]]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -709,7 +709,7 @@ def win_get_count( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[int, SyncFutureResult[int]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -740,7 +740,7 @@ def win_get_minmax( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, int, SyncFutureResult[Optional[int]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -771,7 +771,7 @@ def win_get_control_list( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[List[Control], None, SyncFutureResult[Optional[List[Control]]]]: - args = [title, text, exclude_title, exclude_title, exclude_text] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 3c23a7f1..9d73f323 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -3,6 +3,7 @@ import asyncio.subprocess import atexit import os +import re import subprocess import sys import warnings @@ -292,7 +293,7 @@ def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Optional @overload def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Optional[str], SyncFutureResult[Optional[str]]]: ... + def function_call(self, function_name: Literal['AHKWinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... @overload def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... @overload @@ -474,11 +475,7 @@ def _create_process(self) -> SyncAHKProcess: def _send_nonblocking( self, request: RequestMessage, engine: Optional[AHK] = None ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: - newline = '\n' - - msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( - 'utf-8' - ) + msg = request.format() proc = self._create_process() try: proc.write(msg) @@ -520,11 +517,7 @@ def send_nonblocking( def send( self, request: RequestMessage, engine: Optional[AHK] = None ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: - newline = '\n' - - msg = f"{request.function_name}{',' if request.args else ''}{','.join(arg.replace(newline, '`n') for arg in request.args)}\n".encode( - 'utf-8' - ) + msg = request.format() assert self._proc is not None self._proc.write(msg) self._proc.drain_stdin() diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 1661be5c..a7c40070 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -148,13 +148,13 @@ def send(self, keys: str, *, blocking: bool = True) -> Union[None, SyncFutureRes # fmt: off @overload - def get_text(self) -> Optional[str]: ... + def get_text(self) -> str: ... @overload - def get_text(self, *, blocking: Literal[False]) -> SyncFutureResult[Optional[str]]: ... + def get_text(self, *, blocking: Literal[False]) -> SyncFutureResult[str]: ... @overload - def get_text(self, *, blocking: Literal[True]) -> Optional[str]: ... + def get_text(self, *, blocking: Literal[True]) -> str: ... # fmt: on - def get_text(self, *, blocking: bool = True) -> Union[Optional[str], SyncFutureResult[Optional[str]]]: + def get_text(self, *, blocking: bool = True) -> Union[str, SyncFutureResult[str]]: if blocking: resp = self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=True) return resp diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index a5551da5..df878ae1 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -425,24 +425,19 @@ AHKWinGetText(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] - MsgBox, %title%, %text%, %extitle%, %extext%, %detect_hw% current_detect_hw := Format("{}", A_DetectHiddenWindows) if (detect_hw != "") { DetectHiddenWindows, %detect_hw% } - - WinGetText, output,% title + DetectHiddenWindows, On + WinGetText, output,%title%,%text%,%extitle%,%extext% if (ErrorLevel = 1) { return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window text") } - if (output = 0 || output = "") { - response := FormatNoValueResponse() - } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) - } + response := FormatResponse(STRINGRESPONSEMESSAGE, output) DetectHiddenWindows, %current_detect_hw% return response } @@ -771,10 +766,14 @@ AHKKeyState(ByRef command) { } MouseMove(ByRef command) { - if (command.Length() = 5) { - MouseMove, command[2], command[3], command[4], R + x := command[2] + y := command[3] + speed := command[4] + relative := command[5] + if (relative != "") { + MouseMove, %x%, %y%, %speed%, R } else { - MouseMove, command[2], command[3], command[4] + MouseMove, %x%, %y%, %speed% } resp := FormatNoValueResponse() return resp @@ -1102,20 +1101,19 @@ WinSendRaw(ByRef command) { AHKControlSend(ByRef command) { ctrl := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] + keys := command[3] + title := command[4] + text := command[5] + extitle := command[6] + extext := command[7] + detect_hw := command[8] current_detect_hw := Format("{}", A_DetectHiddenWindows) if (detect_hw != "") { DetectHiddenWindows, %detect_hw% } - - keys := command[8] + ControlSend, %ctrl%, %keys%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% - ControlSend, %ctrl%,% keys, %title%, %text%, %extitle%, %extext% return FormatNoValueResponse() } diff --git a/ahk/message.py b/ahk/message.py index 17a87991..93da3420 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -5,6 +5,7 @@ import string import sys from abc import abstractmethod +from base64 import b64encode from typing import Any from typing import cast from typing import Generator @@ -17,7 +18,6 @@ from typing import Type from typing import TYPE_CHECKING - if sys.version_info >= (3, 10): from typing import TypeGuard else: @@ -295,6 +295,11 @@ 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, diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index d9db1075..46a0eb08 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -21,7 +21,7 @@ async def asyncSetUp(self) -> None: async def asyncTearDown(self) -> None: try: - await self.win.close() + self.p.kill() except Exception: pass self.ahk._transport._proc.kill() @@ -110,3 +110,17 @@ async def test_control_send_window(self): await self.win.send('Hello World') text = await self.win.get_text() assert 'Hello World' in text + + async def test_send_literal_comma(self): + await self.win.send('hello, world') + print(self.win) + text = await self.win.get_text() + assert 'hello, world' in text + + async def test_send_literal_tilde_n(self): + expected_text = '```nim\nimport std/strformat\n```' + await self.win.send(expected_text) + text = await self.win.get_text() + assert '```nim' in text + assert '\nimport std/strformat' in text + assert '\n```' in text diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index a399463d..53d98c86 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -21,7 +21,7 @@ def setUp(self) -> None: def tearDown(self) -> None: try: - self.win.close() + self.p.kill() except Exception: pass self.ahk._transport._proc.kill() @@ -110,3 +110,17 @@ def test_control_send_window(self): self.win.send('Hello World') text = self.win.get_text() assert 'Hello World' in text + + def test_send_literal_comma(self): + self.win.send('hello, world') + print(self.win) + text = self.win.get_text() + assert 'hello, world' in text + + def test_send_literal_tilde_n(self): + expected_text = '```nim\nimport std/strformat\n```' + self.win.send(expected_text) + text = self.win.get_text() + assert '```nim' in text + assert '\nimport std/strformat' in text + assert '\n```' in text diff --git a/tox.ini b/tox.ini index 89990e7c..ca9046d7 100644 --- a/tox.ini +++ b/tox.ini @@ -7,5 +7,5 @@ passenv = CI PYTHONUNBUFFERED commands = - coverage run -m pytest -s -vvv --reruns 5 --only-rerun AssertionError + coverage run -m pytest -s -vvv --reruns 8 --only-rerun AssertionError mypy --strict ahk From 6941e7d6715157c2c4098fd9d0041c061a5d787e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 29 Jul 2022 23:31:41 -0700 Subject: [PATCH 264/588] title match mode and AsyncFuture --- ahk/_async/engine.py | 715 +++++++++++++++++++++++++++---- ahk/_async/transport.py | 42 +- ahk/_sync/engine.py | 815 ++++++++++++++++++++++++++++++------ ahk/_sync/transport.py | 159 +++---- ahk/_sync/window.py | 18 +- ahk/daemon.ahk | 424 +++++++++++++++++-- buildunasync.py | 1 + tests/_async/test_mouse.py | 4 +- tests/_async/test_window.py | 2 +- tests/_sync/test_mouse.py | 2 +- 10 files changed, 1838 insertions(+), 344 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 40e70b7a..3a226480 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -38,10 +38,12 @@ CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] -MatchModes: TypeAlias = Literal[1, 2, 3, 'RegEx'] -MatchSpeeds: TypeAlias = Literal['Fast', 'Slow'] +MatchModes: TypeAlias = Literal[1, 2, 3, 'RegEx', ''] +MatchSpeeds: TypeAlias = Literal['Fast', 'Slow', ''] -TitleMatchMode: TypeAlias = Optional[Union[MatchModes, MatchSpeeds, Tuple[MatchModes, MatchSpeeds]]] +TitleMatchMode: TypeAlias = Optional[ + Union[MatchModes, MatchSpeeds, Tuple[Union[MatchModes, MatchSpeeds], Union[MatchSpeeds, MatchModes]]] +] class AsyncAHK: @@ -77,13 +79,32 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + async def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> Union[None, AsyncFutureResult[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)) + resp = await self._transport.function_call('AHKSetTitleMatchMode', args) + return resp + # fmt: off @overload - async def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on async def control_send( self, @@ -94,6 +115,7 @@ async def control_send( 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]]: @@ -107,7 +129,26 @@ async def control_send( raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' ) - args.append(keys) + 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 @@ -130,14 +171,18 @@ async def set_detect_hidden_windows(self, value: bool) -> None: # fmt: off @overload - async def list_windows(self, *, detect_hidden_windows: Optional[bool] = None) -> List[AsyncWindow]: ... + async def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> List[AsyncWindow]: ... @overload - async def list_windows(self, *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + async def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... @overload - async def list_windows(self, *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[AsyncWindow]: ... + async def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[AsyncWindow]: ... # fmt: on async def list_windows( - self, *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True + self, + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, ) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: args = [] if detect_hidden_windows is not None: @@ -149,6 +194,26 @@ async def list_windows( 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('WindowList', args, engine=self, blocking=blocking) return resp @@ -477,11 +542,11 @@ async def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '',*, detect_hidden_windows: Optional[bool] = None) -> Union[AsyncWindow, None]: ... + 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) -> Union[AsyncWindow, None]: ... @overload - async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... + 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: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... @overload - async def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... + 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: Literal[True]) -> Union[AsyncWindow, None]: ... # fmt: on async def win_get( self, @@ -490,6 +555,7 @@ async def win_get( 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]]]: @@ -503,16 +569,36 @@ async def win_get( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> str: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + 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: ... # fmt: on async def win_get_text( self, @@ -521,6 +607,7 @@ async def win_get_text( 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]]: @@ -534,16 +621,36 @@ async def win_get_text( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> str: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[str]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + 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: ... # fmt: on async def win_get_title( self, @@ -552,6 +659,7 @@ async def win_get_title( 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]]: @@ -565,16 +673,36 @@ async def win_get_title( 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('AHKWinGetTitle', args, blocking=blocking) return resp # fmt: off @overload - async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[AsyncWindow, None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[AsyncWindow, None]: ... + 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]: ... # fmt: on async def win_get_idlast( self, @@ -583,6 +711,7 @@ async def win_get_idlast( 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]]]: @@ -596,16 +725,36 @@ async def win_get_idlast( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[int, None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[int, None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... + 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]: ... # fmt: on async def win_get_pid( self, @@ -614,6 +763,7 @@ async def win_get_pid( 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]]]: @@ -627,16 +777,36 @@ async def win_get_pid( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[str, None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[str, None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... + 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]: ... # fmt: on async def win_get_process_name( self, @@ -645,6 +815,7 @@ async def win_get_process_name( 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]]]: @@ -658,16 +829,36 @@ async def win_get_process_name( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[str, None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[str, None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... + 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]: ... # fmt: on async def win_get_process_path( self, @@ -676,6 +867,7 @@ async def win_get_process_path( 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]]]]: @@ -689,16 +881,36 @@ async def win_get_process_path( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> int: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[int]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> int: ... + 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: ... # fmt: on async def win_get_count( self, @@ -707,6 +919,7 @@ async def win_get_count( 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]]: @@ -720,16 +933,36 @@ async def win_get_count( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[int, None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[int, None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... + 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]: ... # fmt: on async def win_get_minmax( self, @@ -738,6 +971,7 @@ async def win_get_minmax( 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]]]: @@ -751,16 +985,36 @@ async def win_get_minmax( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[List[AsyncControl], None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Union[List[AsyncControl], None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[List[AsyncControl], None]: ... + 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]: ... # fmt: on async def win_get_control_list( self, @@ -769,6 +1023,7 @@ async def win_get_control_list( 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]]]]: @@ -782,6 +1037,26 @@ async def win_get_control_list( 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('AHKWinGetControlList', args, blocking=blocking, engine=self) return resp @@ -800,11 +1075,11 @@ async def win_get_from_mouse_position( # fmt: off @overload - async def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> bool: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + 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: ... # fmt: on async def win_exists( self, @@ -813,6 +1088,7 @@ async def win_exists( 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]]: @@ -826,16 +1102,36 @@ async def win_exists( 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('AHKWinExist', 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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]: ... # fmt: on async def win_set_title( self, @@ -845,6 +1141,7 @@ async def win_set_title( 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]]: @@ -858,16 +1155,36 @@ async def win_set_title( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on async def win_set_always_on_top( self, @@ -877,6 +1194,7 @@ async def win_set_always_on_top( 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]]: @@ -890,16 +1208,36 @@ async def win_set_always_on_top( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on async def win_set_bottom( self, @@ -908,6 +1246,7 @@ async def win_set_bottom( 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]]: @@ -921,16 +1260,36 @@ async def win_set_bottom( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on async def win_set_top( self, @@ -939,6 +1298,7 @@ async def win_set_top( 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]]: @@ -952,16 +1312,36 @@ async def win_set_top( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on async def win_set_disable( self, @@ -970,6 +1350,7 @@ async def win_set_disable( 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]]: @@ -983,16 +1364,36 @@ async def win_set_disable( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on async def win_set_enable( self, @@ -1001,6 +1402,7 @@ async def win_set_enable( 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]]: @@ -1014,16 +1416,36 @@ async def win_set_enable( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on async def win_set_redraw( self, @@ -1032,6 +1454,7 @@ async def win_set_redraw( 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]]: @@ -1045,16 +1468,36 @@ async def win_set_redraw( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> bool: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + 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: ... # fmt: on async def win_set_style( self, @@ -1064,6 +1507,7 @@ async def win_set_style( 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]]: @@ -1077,16 +1521,36 @@ async def win_set_style( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> bool: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + 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: ... # fmt: on async def win_set_ex_style( self, @@ -1096,6 +1560,7 @@ async def win_set_ex_style( 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]]: @@ -1109,16 +1574,36 @@ async def win_set_ex_style( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> bool: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + 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: ... # fmt: on async def win_set_region( self, @@ -1128,6 +1613,7 @@ async def win_set_region( 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]]: @@ -1141,16 +1627,36 @@ async def win_set_region( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on async def win_set_transparent( self, @@ -1160,6 +1666,7 @@ async def win_set_transparent( 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]]: @@ -1173,16 +1680,36 @@ async def win_set_transparent( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on async def win_set_trans_color( self, @@ -1192,6 +1719,7 @@ async def win_set_trans_color( 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]]: @@ -1205,6 +1733,26 @@ async def win_set_trans_color( 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 @@ -1273,12 +1821,7 @@ async def image_search( else: x2, y2 = ('A_ScreenWidth', 'A_ScreenHeight') - args = [ - str(x1), - str(y1), - str(x2), - str(y2), - ] + args = [str(x1), str(y1), str(x2), str(y2)] if options: s = '' for opt in options: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index e14bbde4..255636ee 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -13,8 +13,10 @@ from shutil import which from typing import Any from typing import AnyStr +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 Optional @@ -23,6 +25,7 @@ from typing import runtime_checkable from typing import Tuple from typing import TYPE_CHECKING +from typing import TypeVar from typing import Union if TYPE_CHECKING: @@ -39,17 +42,34 @@ DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' +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) -> Awaitable[T_AsyncFuture]: + return 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]' -AsyncFutureResult: TypeAlias = asyncio.Task # unasync: remove - -SyncFutureResult: TypeAlias = Future FunctionName = Literal[ Literal['AHKSetDetectHiddenWindows'], + Literal['AHKSetTitleMatchMode'], Literal['AHKWinSetTitle'], Literal['AHKWinExist'], Literal['ImageSearch'], @@ -404,6 +424,9 @@ async def function_call(self, function_name: Literal['AHKWinSetTransColor'], arg 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) -> Union[None, AsyncFutureResult[None]]: ... + # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload @@ -459,9 +482,7 @@ async def a_send_nonblocking( # unasync: remove @abstractmethod def send_nonblocking( self, request: RequestMessage, engine: Optional[AsyncAHK] = None - ) -> SyncFutureResult[ - Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] - ]: + ) -> FutureResult[Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]]: return NotImplemented @@ -523,13 +544,12 @@ async def a_send_nonblocking( # unasync: remove Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] ]: loop = asyncio.get_running_loop() - return loop.create_task(self._send_nonblocking(request=request, engine=engine)) + task = loop.create_task(self._send_nonblocking(request=request, engine=engine)) + return AsyncFutureResult(task) def send_nonblocking( self, request: RequestMessage, engine: Optional[AsyncAHK] = None - ) -> SyncFutureResult[ - Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] - ]: + ) -> 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) @@ -537,7 +557,7 @@ def send_nonblocking( assert async_assert_send_nonblocking_type_correct( fut ) # workaround to get mypy correctness in sync and async implementation - return fut + return FutureResult(fut) async def send( self, request: RequestMessage, engine: Optional[AsyncAHK] = None diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 2843aade..3b7a62b3 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -24,7 +24,7 @@ from ..keys import Key from .transport import DaemonProcessTransport -from .transport import SyncFutureResult +from .transport import FutureResult from .transport import Transport from .window import Control from .window import Window @@ -37,10 +37,12 @@ CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] -MatchModes: TypeAlias = Literal[1, 2, 3, 'RegEx'] -MatchSpeeds: TypeAlias = Literal['Fast', 'Slow'] +MatchModes: TypeAlias = Literal[1, 2, 3, 'RegEx', ''] +MatchSpeeds: TypeAlias = Literal['Fast', 'Slow', ''] -TitleMatchMode: TypeAlias = Optional[Union[MatchModes, MatchSpeeds, Tuple[MatchModes, MatchSpeeds]]] +TitleMatchMode: TypeAlias = Optional[ + Union[MatchModes, MatchSpeeds, Tuple[Union[MatchModes, MatchSpeeds], Union[MatchSpeeds, MatchModes]]] +] class AHK: @@ -76,13 +78,32 @@ def add_hotkey( def add_hotstring(self, trigger_string: str, replacement: str) -> None: return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> Union[None, FutureResult[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)) + resp = self._transport.function_call('AHKSetTitleMatchMode', args) + return resp + # fmt: off @overload - def control_send(self, keys: str, control: str = '', title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on def control_send( self, @@ -93,9 +114,10 @@ def control_send( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [control, keys, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -106,7 +128,26 @@ def control_send( raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' ) - args.append(keys) + 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 @@ -129,15 +170,19 @@ def set_detect_hidden_windows(self, value: bool) -> None: # fmt: off @overload - def list_windows(self, *, detect_hidden_windows: Optional[bool] = None) -> List[Window]: ... + def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> List[Window]: ... @overload - def list_windows(self, *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[List[Window], SyncFutureResult[List[Window]]]: ... + def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[List[Window], FutureResult[List[Window]]]: ... @overload - def list_windows(self, *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[Window]: ... + def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[Window]: ... # fmt: on def list_windows( - self, *, detect_hidden_windows: Optional[bool] = None, blocking: bool = True - ) -> Union[List[Window], SyncFutureResult[List[Window]]]: + self, + *, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, + blocking: bool = True, + ) -> Union[List[Window], FutureResult[List[Window]]]: args = [] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -148,6 +193,26 @@ def list_windows( 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('WindowList', args, engine=self, blocking=blocking) return resp @@ -155,13 +220,13 @@ def list_windows( @overload def get_mouse_position(self, *, blocking: Literal[True]) -> Tuple[int, int]: ... @overload - def get_mouse_position(self, *, blocking: Literal[False]) -> SyncFutureResult[Tuple[int, int]]: ... + def get_mouse_position(self, *, blocking: Literal[False]) -> FutureResult[Tuple[int, int]]: ... @overload def get_mouse_position(self) -> Tuple[int, int]: ... # fmt: on def get_mouse_position( self, *, blocking: bool = True - ) -> Union[Tuple[int, int], SyncFutureResult[Tuple[int, int]]]: + ) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: resp = self._transport.function_call('MouseGetPos', blocking=blocking) return resp @@ -171,7 +236,7 @@ def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Union[str, @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) -> 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, ) -> SyncFutureResult[None]: ... + 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, ) -> FutureResult[None]: ... # fmt: on def mouse_move( self, @@ -181,7 +246,7 @@ def mouse_move( speed: Optional[int] = None, relative: bool = False, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: if relative and (x is None or y is None): x = x or 0 y = y or 0 @@ -227,9 +292,9 @@ 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]) -> SyncFutureResult[None]: ... + def key_down(self, key: Union[str, Key], *, blocking: Literal[False]) -> FutureResult[None]: ... # fmt: on - def key_down(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + def key_down(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, FutureResult[None]]: if isinstance(key, str): key = Key(key_name=key) if blocking: @@ -244,11 +309,11 @@ 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) -> SyncFutureResult[None]: ... + def key_press(self, key: Union[str, Key], *, blocking: Literal[False], release: bool = True) -> FutureResult[None]: ... # fmt: on def key_press( self, key: Union[str, Key], *, release: bool = True, blocking: bool = True - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: if blocking: self.key_down(key, blocking=True) if release: @@ -266,9 +331,9 @@ 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]) -> SyncFutureResult[None]: ... + def key_release(self, key: Union[str, Key], *, blocking: Literal[False]) -> FutureResult[None]: ... # fmt: on - def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[None, FutureResult[None]]: if blocking: self.key_up(key=key, blocking=True) return None @@ -284,9 +349,9 @@ 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]) -> SyncFutureResult[None]: ... + def key_up(self, key: Union[str, Key], *, blocking: Literal[False]) -> FutureResult[None]: ... # fmt: on - def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, FutureResult[None]]: if isinstance(key, str): key = Key(key_name=key) if blocking: @@ -301,7 +366,7 @@ def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_stat @overload def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... @overload - def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> SyncFutureResult[int]: ... + def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> FutureResult[int]: ... # fmt: on def key_wait( self, @@ -311,7 +376,7 @@ def key_wait( logical_state: bool = False, released: bool = False, blocking: bool = True, - ) -> Union[int, SyncFutureResult[int]]: + ) -> Union[int, FutureResult[int]]: options = '' if not released: options += 'D' @@ -363,11 +428,11 @@ def send(self, s: str) -> None: ... @overload def send(self, s: str, *, blocking: Literal[True]) -> None: ... @overload - def send(self, s: str, *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + def send(self, s: str, *, blocking: Literal[False]) -> FutureResult[None]: ... # fmt: on def send( self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [s] if raw: raw_resp = self._transport.function_call('AHKSendRaw', args=args, blocking=blocking) @@ -385,9 +450,9 @@ 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]) -> SyncFutureResult[None]: ... + def send_input(self, s: str, *, blocking: Literal[False]) -> FutureResult[None]: ... # fmt: on - def send_input(self, s: str, *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + def send_input(self, s: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: args = [s] resp = self._transport.function_call('AHKSendInput', args, blocking=blocking) return resp @@ -476,11 +541,11 @@ def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '',*, detect_hidden_windows: Optional[bool] = None) -> Union[Window, None]: ... + 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) -> Union[Window, None]: ... @overload - def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[Window, None]]: ... + 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: Literal[False]) -> FutureResult[Union[Window, None]]: ... @overload - def win_get(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[Window, None]: ... + 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: Literal[True]) -> Union[Window, None]: ... # fmt: on def win_get( self, @@ -489,9 +554,10 @@ def win_get( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[Window, None, SyncFutureResult[Union[None, Window]]]: + ) -> Union[Window, None, FutureResult[Union[None, Window]]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -502,16 +568,36 @@ def win_get( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> str: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[str]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + 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: ... # fmt: on def win_get_text( self, @@ -520,9 +606,10 @@ def win_get_text( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[str, SyncFutureResult[str]]: + ) -> Union[str, FutureResult[str]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -533,16 +620,36 @@ def win_get_text( 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('AHKWinGetText', args, blocking=blocking) return resp # fmt: off @overload - def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> str: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[str]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> str: ... + 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: ... # fmt: on def win_get_title( self, @@ -551,9 +658,10 @@ def win_get_title( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[str, SyncFutureResult[str]]: + ) -> Union[str, FutureResult[str]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -564,16 +672,36 @@ def win_get_title( 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('AHKWinGetTitle', args, blocking=blocking) return resp # fmt: off @overload - def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[Window, None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[Window, None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[Window, None]: ... + 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]: ... # fmt: on def win_get_idlast( self, @@ -582,9 +710,10 @@ def win_get_idlast( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[Window, None, SyncFutureResult[Union[Window, None]]]: + ) -> Union[Window, None, FutureResult[Union[Window, None]]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -595,16 +724,36 @@ def win_get_idlast( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[int, None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[int, None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... + 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]: ... # fmt: on def win_get_pid( self, @@ -613,9 +762,10 @@ def win_get_pid( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[int, None, SyncFutureResult[Union[int, None]]]: + ) -> Union[int, None, FutureResult[Union[int, None]]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -626,16 +776,36 @@ def win_get_pid( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[str, None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[str, None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... + 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]: ... # fmt: on def win_get_process_name( self, @@ -644,9 +814,10 @@ def win_get_process_name( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, str, SyncFutureResult[Optional[str]]]: + ) -> Union[None, str, FutureResult[Optional[str]]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -657,16 +828,36 @@ def win_get_process_name( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[str, None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[str, None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[str, None]: ... + 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]: ... # fmt: on def win_get_process_path( self, @@ -675,9 +866,10 @@ def win_get_process_path( 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, SyncFutureResult[Optional[str]]]]: + ) -> Union[str, None, Union[None, str, FutureResult[Optional[str]]]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -688,16 +880,36 @@ def win_get_process_path( 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('AHKWinGetProcessPath', args, blocking=blocking) return resp # fmt: off @overload - def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> int: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[int]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> int: ... + 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: ... # fmt: on def win_get_count( self, @@ -706,9 +918,10 @@ def win_get_count( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[int, SyncFutureResult[int]]: + ) -> Union[int, FutureResult[int]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -719,16 +932,36 @@ def win_get_count( 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('AHKWinGetCount', args, blocking=blocking) return resp # fmt: off @overload - def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[int, None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[int, None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[int, None]: ... + 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]: ... # fmt: on def win_get_minmax( self, @@ -737,9 +970,10 @@ def win_get_minmax( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, int, SyncFutureResult[Optional[int]]]: + ) -> Union[None, int, FutureResult[Optional[int]]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -750,16 +984,36 @@ def win_get_minmax( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> Union[List[Control], None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[Union[List[Control], None]]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> Union[List[Control], None]: ... + 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]: ... # fmt: on def win_get_control_list( self, @@ -768,9 +1022,10 @@ def win_get_control_list( 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, SyncFutureResult[Optional[List[Control]]]]: + ) -> Union[List[Control], None, FutureResult[Optional[List[Control]]]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -781,6 +1036,26 @@ def win_get_control_list( 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('AHKWinGetControlList', args, blocking=blocking, engine=self) return resp @@ -788,22 +1063,22 @@ def win_get_control_list( @overload def win_get_from_mouse_position(self) -> Union[Window, None]: ... @overload - def win_get_from_mouse_position(self, *, blocking: Literal[False]) -> SyncFutureResult[Union[Window, None]]: ... + 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]: ... # fmt: on def win_get_from_mouse_position( self, *, blocking: bool = True - ) -> Union[Optional[Window], SyncFutureResult[Optional[Window]]]: + ) -> Union[Optional[Window], FutureResult[Optional[Window]]]: raise NotImplementedError() # fmt: off @overload - def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> bool: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + 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: ... # fmt: on def win_exists( self, @@ -812,9 +1087,10 @@ def win_exists( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[bool, SyncFutureResult[bool]]: + ) -> Union[bool, FutureResult[bool]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -825,16 +1101,36 @@ def win_exists( 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('AHKWinExist', 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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]: ... # fmt: on def win_set_title( self, @@ -844,9 +1140,10 @@ def win_set_title( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [new_title, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -857,16 +1154,36 @@ def win_set_title( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on def win_set_always_on_top( self, @@ -876,9 +1193,10 @@ def win_set_always_on_top( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [str(toggle), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -889,16 +1207,36 @@ def win_set_always_on_top( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on def win_set_bottom( self, @@ -907,9 +1245,10 @@ def win_set_bottom( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -920,16 +1259,36 @@ def win_set_bottom( 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('AHKWinSetBottom', args, blocking=blocking) return resp # fmt: off @overload - def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on def win_set_top( self, @@ -938,9 +1297,10 @@ def win_set_top( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -951,16 +1311,36 @@ def win_set_top( 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('AHKWinSetTop', args, blocking=blocking) return resp # fmt: off @overload - def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on def win_set_disable( self, @@ -969,9 +1349,10 @@ def win_set_disable( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -982,16 +1363,36 @@ def win_set_disable( 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('AHKWinSetDisable', args, blocking=blocking) return resp # fmt: off @overload - def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on def win_set_enable( self, @@ -1000,9 +1401,10 @@ def win_set_enable( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -1013,16 +1415,36 @@ def win_set_enable( 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('AHKWinSetEnable', args, blocking=blocking) return resp # fmt: off @overload - def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on def win_set_redraw( self, @@ -1031,9 +1453,10 @@ def win_set_redraw( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -1044,16 +1467,36 @@ def win_set_redraw( 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('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 = '', *, detect_hidden_windows: Optional[bool] = None) -> bool: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + 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: ... # fmt: on def win_set_style( self, @@ -1063,9 +1506,10 @@ def win_set_style( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[bool, SyncFutureResult[bool]]: + ) -> Union[bool, FutureResult[bool]]: args = [style, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -1076,16 +1520,36 @@ def win_set_style( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> bool: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + 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: ... # fmt: on def win_set_ex_style( self, @@ -1095,9 +1559,10 @@ def win_set_ex_style( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[bool, SyncFutureResult[bool]]: + ) -> Union[bool, FutureResult[bool]]: args = [style, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -1108,16 +1573,36 @@ def win_set_ex_style( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> bool: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[bool]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> bool: ... + 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: ... # fmt: on def win_set_region( self, @@ -1127,9 +1612,10 @@ def win_set_region( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[bool, SyncFutureResult[bool]]: + ) -> Union[bool, FutureResult[bool]]: args = [options, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -1140,16 +1626,36 @@ def win_set_region( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on def win_set_transparent( self, @@ -1159,9 +1665,10 @@ def win_set_transparent( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [str(transparency), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -1172,16 +1679,36 @@ def win_set_transparent( 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 = '', *, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> SyncFutureResult[None]: ... + 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 = '', *, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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: ... # fmt: on def win_set_trans_color( self, @@ -1191,9 +1718,10 @@ def win_set_trans_color( exclude_title: str = '', exclude_text: str = '', *, + title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args = [str(color), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -1204,6 +1732,26 @@ def win_set_trans_color( 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 @@ -1228,7 +1776,7 @@ def click( @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None) -> Optional[Tuple[int, int]]: ... @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[False]) -> SyncFutureResult[Optional[Tuple[int, int]]]: ... + 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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[False]) -> FutureResult[Optional[Tuple[int, int]]]: ... @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[True]) -> Optional[Tuple[int, int]]: ... # fmt: on @@ -1245,7 +1793,7 @@ def image_search( transparent: Optional[str] = None, icon: Optional[int] = None, blocking: bool = True, - ) -> Union[Tuple[int, int], None, SyncFutureResult[Optional[Tuple[int, int]]]]: + ) -> Union[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: """ https://www.autohotkey.com/docs/commands/ImageSearch.htm """ @@ -1272,12 +1820,7 @@ def image_search( else: x2, y2 = ('A_ScreenWidth', 'A_ScreenHeight') - args = [ - str(x1), - str(y1), - str(x2), - str(y2), - ] + args = [str(x1), str(y1), str(x2), str(y2)] if options: s = '' for opt in options: @@ -1338,7 +1881,7 @@ def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optiona @overload def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', 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 = '', blocking: Literal[False]) -> SyncFutureResult[None]: ... + def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', blocking: Literal[False]) -> FutureResult[None]: ... # fmt: on def win_close( self, @@ -1349,7 +1892,7 @@ def win_close( exclude_title: str = '', exclude_text: str = '', blocking: bool = True, - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] resp = self._transport.function_call('AHKWinClose', args=args, blocking=blocking) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 9d73f323..d6413cd4 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -13,8 +13,10 @@ from shutil import which from typing import Any from typing import AnyStr +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 Optional @@ -23,6 +25,7 @@ from typing import runtime_checkable from typing import Tuple from typing import TYPE_CHECKING +from typing import TypeVar from typing import Union if TYPE_CHECKING: @@ -39,15 +42,26 @@ DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' +T_SyncFuture = TypeVar('T_SyncFuture') -SyncIOProcess: TypeAlias = 'subprocess.Popen[bytes]' -SyncFutureResult: TypeAlias = Future +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]' + FunctionName = Literal[ Literal['AHKSetDetectHiddenWindows'], + Literal['AHKSetTitleMatchMode'], Literal['AHKWinSetTitle'], Literal['AHKWinExist'], Literal['ImageSearch'], @@ -253,146 +267,149 @@ def init(self) -> None: # fmt: off @overload - def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... + def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, FutureResult[bool]]: ... @overload - def function_call(self, function_name: Literal['ImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], None, SyncFutureResult[Union[Tuple[int, int], None]]]: ... + def function_call(self, function_name: Literal['ImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], None, FutureResult[Union[Tuple[int, int], None]]]: ... @overload - def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... + def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], SyncFutureResult[Tuple[int, int]]]: ... + def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... @overload - def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], SyncFutureResult[Tuple[int, int]]]: ... + def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... @overload - def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, SyncFutureResult[bool]]: ... + def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, FutureResult[bool]]: ... @overload - def function_call(self, function_name: Literal['MouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['MouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['Click'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['Click'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[int, SyncFutureResult[int]]: ... + def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[int, FutureResult[int]]: ... @overload - def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSendInput'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSendEvent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[str, SyncFutureResult[str]]: ... + def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... + def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[str, SyncFutureResult[str]]: ... + def function_call(self, function_name: Literal['AHKWinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinActivateBottom'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinMinimize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinMinimize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[Window], SyncFutureResult[List[Window]]]: ... + def function_call(self, function_name: Literal['WindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[Window], FutureResult[List[Window]]]: ... @overload - def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['AHKControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... + def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, SyncFutureResult[str]]: ... + def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinSet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinSet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Optional[bool], SyncFutureResult[Optional[bool]]]: ... + def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Optional[bool], FutureResult[Optional[bool]]]: ... @overload - def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... # @overload # async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK) = None -> Union[TupleResponseMessage, AsyncFutureResult[TupleResponseMessage]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, Window], SyncFutureResult[Union[None, Window]]]: ... + def function_call(self, function_name: Literal['AHKWinGetID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Union[None, Window], SyncFutureResult[Union[None, Window]]]: ... + def function_call(self, function_name: Literal['AHKWinGetIDLast'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Union[int, None], SyncFutureResult[Union[int, None]]]: ... + def function_call(self, function_name: Literal['AHKWinGetPID'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... + def function_call(self, function_name: Literal['AHKWinGetProcessName'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... + def function_call(self, function_name: Literal['AHKWinGetProcessPath'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[int, SyncFutureResult[int]]: ... + def function_call(self, function_name: Literal['AHKWinGetCount'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[List[Window], SyncFutureResult[List[Window]]]: ... + def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Union[None, int], SyncFutureResult[Union[None, int]]]: ... + def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[List[Control], None, SyncFutureResult[Union[List[Control], None]]]: ... + def function_call(self, function_name: Literal['AHKWinGetControlList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[Control], None, FutureResult[Union[List[Control], None]]]: ... # @overload # async def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncControl], AsyncFutureResult[List[AsyncControl]]]: ... @overload - def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, int], SyncFutureResult[Union[None, int]]]: ... + def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... + def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... + def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Union[None, str], SyncFutureResult[Union[None, str]]]: ... + def function_call(self, function_name: Literal['AHKWinGetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinSetAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinSetBottom'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinSetTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinSetDisable'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinSetEnable'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinSetRedraw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[bool, SyncFutureResult[bool]]: ... + def function_call(self, function_name: Literal['AHKWinSetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[bool, SyncFutureResult[bool]]: ... + def function_call(self, function_name: Literal['AHKWinSetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[bool, SyncFutureResult[bool]]: ... + def function_call(self, function_name: Literal['AHKWinSetRegion'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinSetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, SyncFutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinSetTransColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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, SyncFutureResult[None]]: ... + 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) -> Union[None, FutureResult[None]]: ... + # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload @@ -441,7 +458,7 @@ def send( @abstractmethod def send_nonblocking( self, request: RequestMessage, engine: Optional[AHK] = None - ) -> SyncFutureResult[ + ) -> FutureResult[ Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]] ]: return NotImplemented @@ -502,7 +519,7 @@ def _send_nonblocking( def send_nonblocking( self, request: RequestMessage, engine: Optional[AHK] = None - ) -> SyncFutureResult[ + ) -> FutureResult[ Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]] ]: # this is only used by the sync implementation @@ -512,7 +529,7 @@ def send_nonblocking( assert async_assert_send_nonblocking_type_correct( fut ) # workaround to get mypy correctness in sync and async implementation - return fut + return FutureResult(fut) def send( self, request: RequestMessage, engine: Optional[AHK] = None diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index a7c40070..edc61d20 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from .engine import AHK - from .transport import SyncFutureResult + from .transport import FutureResult class WindowNotFoundException(Exception): @@ -95,13 +95,13 @@ def set_title(self, new_title: str) -> None: @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]) -> SyncFutureResult[None]: ... + 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: ... # fmt: on def set_always_on_top( self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True - ) -> Union[None, SyncFutureResult[None]]: + ) -> Union[None, FutureResult[None]]: if blocking: self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True) return None @@ -115,11 +115,11 @@ def set_always_on_top( @overload def is_always_on_top(self) -> bool: ... @overload - def is_always_on_top(self, *, blocking: Literal[False]) -> SyncFutureResult[Optional[bool]]: ... + def is_always_on_top(self, *, blocking: Literal[False]) -> FutureResult[Optional[bool]]: ... @overload def is_always_on_top(self, *, blocking: Literal[True]) -> bool: ... # fmt: on - def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, SyncFutureResult[Optional[bool]]]: + 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 @@ -134,11 +134,11 @@ def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, SyncFutureRe @overload def send(self, keys: str) -> None: ... @overload - def send(self, keys: str, *, blocking: Literal[False]) -> SyncFutureResult[None]: ... + def send(self, keys: str, *, blocking: Literal[False]) -> FutureResult[None]: ... @overload def send(self, keys: str, *, blocking: Literal[True]) -> None: ... # fmt: on - def send(self, keys: str, *, blocking: bool = True) -> Union[None, SyncFutureResult[None]]: + def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: if blocking: self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=True) return None @@ -150,11 +150,11 @@ def send(self, keys: str, *, blocking: bool = True) -> Union[None, SyncFutureRes @overload def get_text(self) -> str: ... @overload - def get_text(self, *, blocking: Literal[False]) -> SyncFutureResult[str]: ... + def get_text(self, *, blocking: Literal[False]) -> FutureResult[str]: ... @overload def get_text(self, *, blocking: Literal[True]) -> str: ... # fmt: on - def get_text(self, *, blocking: bool = True) -> Union[str, SyncFutureResult[str]]: + def get_text(self, *, blocking: bool = True) -> Union[str, FutureResult[str]]: if blocking: resp = self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=True) return resp diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index df878ae1..9b122bd1 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -33,6 +33,14 @@ AHKSetDetectHiddenWindows(ByRef command) { return FormatNoValueResponse() } +AHKSetTitleMatchMode(ByRef command) { + val1 := command[2] + val2 := command[3] + if (val1 != "") { + + } +} + AHKWinExist(ByRef command) { global BOOLEANRESPONSEMESSAGE title := command[2] @@ -40,6 +48,17 @@ AHKWinExist(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -62,6 +81,17 @@ AHKWinClose(ByRef command) { extitle := command[5] extext := command[6] detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 != "") { @@ -80,6 +110,17 @@ AHKWinGetID(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -94,6 +135,8 @@ AHKWinGetID(ByRef command) { response := FormatResponse(WINDOWRESPONSEMESSAGE, output) } DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } @@ -104,6 +147,17 @@ AHKWinGetTitle(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -113,6 +167,8 @@ AHKWinGetTitle(ByRef command) { WinGetTitle, text, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return FormatResponse(STRINGRESPONSEMESSAGE, text) } @@ -124,6 +180,17 @@ AHKWinGetIDLast(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -138,6 +205,8 @@ AHKWinGetIDLast(ByRef command) { response := FormatResponse(WINDOWRESPONSEMESSAGE, output) } DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } @@ -149,6 +218,17 @@ AHKWinGetPID(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -163,6 +243,8 @@ AHKWinGetPID(ByRef command) { response := FormatResponse(INTEGERRESPONSEMESSAGE, output) } DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } @@ -174,6 +256,17 @@ AHKWinGetProcessName(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -188,6 +281,8 @@ AHKWinGetProcessName(ByRef command) { response := FormatResponse(STRINGRESPONSEMESSAGE, output) } DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } @@ -198,6 +293,17 @@ AHKWinGetProcessPath(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -212,6 +318,8 @@ AHKWinGetProcessPath(ByRef command) { response := FormatResponse(STRINGRESPONSEMESSAGE, output) } DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } @@ -223,6 +331,17 @@ AHKWinGetCount(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -237,6 +356,8 @@ AHKWinGetCount(ByRef command) { response := FormatResponse(INTEGERRESPONSEMESSAGE, output) } DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } @@ -249,6 +370,17 @@ AHKWinGetMinMax(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -263,6 +395,8 @@ AHKWinGetMinMax(ByRef command) { response := FormatResponse(INTEGERRESPONSEMESSAGE, output) } DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } @@ -274,6 +408,17 @@ AHKWinGetControlList(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -311,28 +456,10 @@ AHKWinGetControlList(ByRef command) { output .= "])" response := FormatResponse(WINDOWCONTROLLISTRESPONSEMESSAGE, output) DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } -;AHKWinGetControlListHwnd(ByRef command) { -; global STRINGRESPONSEMESSAGE -; global INTEGERRESPONSEMESSAGE -; global NOVALUERESPONSEMESSAGE -; title := command[2] -; text := command[3] -; extitle := command[4] -; extext := command[5] -; detect_hw := command[6] -; -; current_detect_hw := Format("{}", A_DetectHiddenWindows) -; -;if (detect_hw != "") { -; DetectHiddenWindows, %detect_hw% -;} -; WinGet, output, ControlListHwnd, %title%, %text%, %extitle%, %extext% -; response := FormatResponse(NOVALUERESPONSEMESSAGE, output) -; DetectHiddenWindows, %current_detect_hw% -; return response -;} AHKWinGetTransparent(ByRef command) { global INTEGERRESPONSEMESSAGE @@ -341,6 +468,17 @@ AHKWinGetTransparent(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -351,6 +489,8 @@ AHKWinGetTransparent(ByRef command) { WinGet, output, Transparent, %title%, %text%, %extitle%, %extext% response := FormatResponse(INTEGERRESPONSEMESSAGE, output) DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } AHKWinGetTransColor(ByRef command) { @@ -362,6 +502,17 @@ AHKWinGetTransColor(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -372,6 +523,8 @@ AHKWinGetTransColor(ByRef command) { WinGet, output, TransColor, %title%, %text%, %extitle%, %extext% response := FormatResponse(NOVALUERESPONSEMESSAGE, output) DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } AHKWinGetStyle(ByRef command) { @@ -383,6 +536,17 @@ AHKWinGetStyle(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -393,6 +557,8 @@ AHKWinGetStyle(ByRef command) { WinGet, output, Style, %title%, %text%, %extitle%, %extext% response := FormatResponse(NOVALUERESPONSEMESSAGE, output) DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } AHKWinGetExStyle(ByRef command) { @@ -404,6 +570,17 @@ AHKWinGetExStyle(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -414,6 +591,8 @@ AHKWinGetExStyle(ByRef command) { WinGet, output, ExStyle, %title%, %text%, %extitle%, %extext% response := FormatResponse(NOVALUERESPONSEMESSAGE, output) DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } @@ -425,12 +604,23 @@ AHKWinGetText(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% } - DetectHiddenWindows, On + WinGetText, output,%title%,%text%,%extitle%,%extext% if (ErrorLevel = 1) { @@ -439,6 +629,8 @@ AHKWinGetText(ByRef command) { response := FormatResponse(STRINGRESPONSEMESSAGE, output) DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return response } @@ -451,6 +643,17 @@ AHKWinSetTitle(ByRef command) { extitle := command[5] extext := command[6] detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 != "") { @@ -458,6 +661,8 @@ AHKWinSetTitle(ByRef command) { } WinSetTitle, %title%, %text%, %new_title%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() } @@ -468,6 +673,17 @@ AHKWinSetAlwaysOnTop(ByRef command) { extitle := command[5] extext := command[6] detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 != "") { @@ -476,6 +692,8 @@ AHKWinSetAlwaysOnTop(ByRef command) { WinSet, AlwaysOntop, %toggle%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() } @@ -485,6 +703,17 @@ AHKWinSetBottom(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -494,6 +723,8 @@ AHKWinSetBottom(ByRef command) { WinSet, Bottom,, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() } @@ -503,6 +734,17 @@ AHKWinSetTop(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -512,6 +754,8 @@ AHKWinSetTop(ByRef command) { WinSet, Top,, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() } @@ -521,6 +765,17 @@ AHKWinSetEnable(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -530,6 +785,8 @@ AHKWinSetEnable(ByRef command) { WinSet, Enable,, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() } @@ -539,6 +796,17 @@ AHKWinSetDisable(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -548,6 +816,8 @@ AHKWinSetDisable(ByRef command) { WinSet, Disable,, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() } @@ -557,6 +827,17 @@ AHKWinSetRedraw(ByRef command) { extitle := command[4] extext := command[5] detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) @@ -566,6 +847,8 @@ AHKWinSetRedraw(ByRef command) { WinSet, Redraw,, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() } @@ -577,6 +860,17 @@ AHKWinSetStyle(ByRef command) { extitle := command[5] extext := command[6] detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 != "") { @@ -586,6 +880,8 @@ AHKWinSetStyle(ByRef command) { WinSet, Style, %style%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% if (ErrorLevel = 1) { return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } else { @@ -601,6 +897,17 @@ AHKWinSetExStyle(ByRef command) { extitle := command[5] extext := command[6] detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 != "") { @@ -610,6 +917,8 @@ AHKWinSetExStyle(ByRef command) { WinSet, ExStyle, %style%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% if (ErrorLevel = 1) { return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } else { @@ -625,6 +934,17 @@ AHKWinSetRegion(ByRef command) { extitle := command[5] extext := command[6] detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 != "") { @@ -634,6 +954,8 @@ AHKWinSetRegion(ByRef command) { WinSet, Region, %options%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% if (ErrorLevel = 1) { return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } else { @@ -649,6 +971,17 @@ AHKWinSetTransparent(ByRef command) { extitle := command[5] extext := command[6] detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 != "") { @@ -658,6 +991,8 @@ AHKWinSetTransparent(ByRef command) { WinSet, Transparent, %transparency%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() } @@ -669,6 +1004,17 @@ AHKWinSetTransColor(ByRef command) { extitle := command[5] extext := command[6] detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 != "") { @@ -1062,11 +1408,22 @@ WinWaitClose(ByRef command) { WindowList(ByRef command) { global WINDOWIDLISTRESPONSEMESSAGE - previous_setting := Format("{}", A_DetectHiddenWindows) + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + detect_hw := command[2] + match_mode := command[3] + match_speed := command[4] - detect_hidden_windows := command[2] - if (detect_hidden_windows) { - DetectHiddenWindows, %detect_hidden_windows% + 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 @@ -1077,7 +1434,9 @@ WindowList(ByRef command) { r .= id . "`," } resp := FormatResponse(WINDOWIDLISTRESPONSEMESSAGE, r) - DetectHiddenWindows, %previous_setting% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return resp } @@ -1107,6 +1466,17 @@ AHKControlSend(ByRef command) { extitle := command[6] extext := command[7] detect_hw := command[8] + match_mode := command[9] + match_speed := command[10] + + 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 != "") { @@ -1114,6 +1484,8 @@ AHKControlSend(ByRef command) { } ControlSend, %ctrl%, %keys%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() } diff --git a/buildunasync.py b/buildunasync.py index 7ffe9f47..77a6c9c6 100644 --- a/buildunasync.py +++ b/buildunasync.py @@ -16,6 +16,7 @@ 'adrain_stdin': 'drain_stdin', 'a_send_nonblocking': 'send_nonblocking', 'async_sleep': 'sleep', + 'AsyncFutureResult': 'FutureResult', # "__aenter__": "__aenter__", }, ), diff --git a/tests/_async/test_mouse.py b/tests/_async/test_mouse.py index 6bdc818e..1049fca1 100644 --- a/tests/_async/test_mouse.py +++ b/tests/_async/test_mouse.py @@ -56,6 +56,4 @@ async def test_mouse_move_nonblocking(self): pos = await self.ahk.get_mouse_position() assert pos != current_pos assert pos != (500, 500) - await res # unasync: remove - return # unasync: remove - sleep(1) + await res.result() diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 46a0eb08..8e4ef4e8 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -98,7 +98,7 @@ async def test_win_get_count(self): # assert all_count > count async def test_win_exists(self): - assert self.win.exists() + assert await self.win.exists() await self.win.close() assert not await self.win.exists() diff --git a/tests/_sync/test_mouse.py b/tests/_sync/test_mouse.py index 40cc7400..913b9bf1 100644 --- a/tests/_sync/test_mouse.py +++ b/tests/_sync/test_mouse.py @@ -55,4 +55,4 @@ def test_mouse_move_nonblocking(self): pos = self.ahk.get_mouse_position() assert pos != current_pos assert pos != (500, 500) - sleep(1) + res.result() From c64ec809d09ce272d560d8dfd964b416a3d4361d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 30 Jul 2022 21:44:36 -0700 Subject: [PATCH 265/588] return the actual result --- ahk/_async/transport.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 255636ee..e8b82a88 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -50,8 +50,8 @@ 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) -> Awaitable[T_AsyncFuture]: - return self._task + async def result(self) -> T_AsyncFuture: + return await self._task class FutureResult(Generic[T_SyncFuture]): From fae9622c0deb2f594298cf94c06f6b319e445014 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Aug 2022 12:25:03 -0700 Subject: [PATCH 266/588] title match for windows --- ahk/_async/engine.py | 133 ++++++++++++++++++++++++++++++++++-- ahk/_async/transport.py | 9 ++- ahk/_async/window.py | 75 ++++++++++++++++---- ahk/_sync/engine.py | 133 ++++++++++++++++++++++++++++++++++-- ahk/_sync/transport.py | 17 ++--- ahk/_sync/window.py | 47 +++++++++---- ahk/daemon.ahk | 18 ++++- requirements-dev.txt | 2 +- tests/_async/test_window.py | 17 +++++ tests/_sync/test_window.py | 17 +++++ 10 files changed, 414 insertions(+), 54 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 3a226480..7d5bdc9f 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -74,12 +74,52 @@ def __getattr__(self, item: Any) -> Any: def add_hotkey( self, hotkey: 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 hotkey: the hotkey that should trigger the callback. For example the string '#n' for Win+n + :param callback: A callable (e.g., a function) to run when the hotkey is triggered. + :param ex_handler: An exception handler callable that runs when your callback fails. The exception handler must accept two positional arguments. + The first argument is a string representing the hotkey that failed and the second is the exception instance that was raised during the execution of your callback. + If you do not provide an exception handler, a default handler is used. + """ return self._transport.add_hotkey(hotkey=hotkey, callback=callback, ex_handler=ex_handler) def add_hotstring(self, trigger_string: str, replacement: 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 + + :param trigger_string: The 'abbreviation' part of the hotstring. e.g., `btw` + :param replacement: The text to replace when the trigger fires. e.g., `by the way` + """ return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) - async def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> Union[None, AsyncFutureResult[None]]: + async def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> None: + """ + Sets the default title match mode + + Has no effect for `Window`/`Control` instance methods (these always use hwnd) + + 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 @@ -95,7 +135,27 @@ async def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> Uni ) args.append(str(match_mode)) args.append(str(match_speed)) - resp = await self._transport.function_call('AHKSetTitleMatchMode', args) + 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 # fmt: off @@ -119,6 +179,22 @@ async def control_send( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for ControlSend + + Reference: https://www.autohotkey.com/docs/commands/ControlSend.htm + + :param keys: + :param control: + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ args = [control, keys, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -153,12 +229,27 @@ async def control_send( return resp 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 + + """ return self._transport.stop_hotkeys() async def set_detect_hidden_windows(self, value: bool) -> None: + """ + Analog for AutoHotkey's `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 = [] @@ -1878,11 +1969,11 @@ async def show_traytip( # fmt: off @overload - async def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '') -> None: ... + 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 = '', blocking: Literal[True]) -> None: ... + 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 = '', blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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]: ... # fmt: on async def win_close( self, @@ -1893,9 +1984,41 @@ async def win_close( 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]]: args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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('AHKWinClose', args=args, blocking=blocking) return resp diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index e8b82a88..95aae47d 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -68,6 +68,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ + Literal['AHKGetTitleMatchMode'], + Literal['AHKGetTitleMatchSpeed'], Literal['AHKSetDetectHiddenWindows'], Literal['AHKSetTitleMatchMode'], Literal['AHKWinSetTitle'], @@ -425,8 +427,11 @@ async def function_call(self, function_name: Literal['AHKSetDetectHiddenWindows' @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) -> Union[None, AsyncFutureResult[None]]: ... - + 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['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 91beaf23..e857592e 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -35,14 +35,20 @@ def __hash__(self) -> int: return hash(self._ahk_id) async def close(self) -> None: - await self._engine.win_close(title=f'ahk_id {self._ahk_id}') + 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 exists(self) -> bool: - return await self._engine.win_exists(title=f'ahk_id {self._ahk_id}') + return await self._engine.win_exists( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) async def get_pid(self) -> int: - pid = await self._engine.win_get_pid(title=f'ahk_id {self._ahk_id}') + 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' @@ -50,7 +56,9 @@ async def get_pid(self) -> int: return pid async def get_process_name(self) -> str: - name = await self._engine.win_get_process_name(title=f'ahk_id {self._ahk_id}') + 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' @@ -58,7 +66,9 @@ async def get_process_name(self) -> str: return name async def get_process_path(self) -> str: - path = await self._engine.win_get_process_path(title=f'ahk_id {self._ahk_id}') + 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' @@ -66,7 +76,9 @@ async def get_process_path(self) -> str: return path async def get_minmax(self) -> int: - minmax = await self._engine.win_get_minmax(title=f'ahk_id {self._ahk_id}') + 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' @@ -74,11 +86,15 @@ async def get_minmax(self) -> int: return minmax async def get_title(self) -> str: - title = await self._engine.win_get_title(title=f'ahk_id {self._ahk_id}') + 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 async def list_controls(self) -> Sequence['AsyncControl']: - controls = await self._engine.win_get_control_list(title=f'ahk_id {self._ahk_id}') + 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' @@ -87,7 +103,10 @@ async def list_controls(self) -> Sequence['AsyncControl']: 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=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + new_title=new_title, + title_match_mode=(1, 'Fast'), ) return None @@ -103,11 +122,21 @@ async def set_always_on_top( self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True ) -> Union[None, AsyncFutureResult[None]]: if blocking: - await self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True) + await self._engine.win_set_always_on_top( + toggle=toggle, + title=f'ahk_id {self._ahk_id}', + blocking=True, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) return None else: resp = await self._engine.win_set_always_on_top( - toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=False + toggle=toggle, + title=f'ahk_id {self._ahk_id}', + blocking=False, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), ) return resp @@ -140,10 +169,22 @@ async def send(self, keys: str, *, blocking: Literal[True]) -> None: ... # fmt: on async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: if blocking: - await self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=True) + await self._engine.control_send( + keys=keys, + title=f'ahk_id {self._ahk_id}', + blocking=True, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) return None else: - resp = await self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=False) + resp = await self._engine.control_send( + keys=keys, + title=f'ahk_id {self._ahk_id}', + blocking=False, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) return resp # fmt: off @@ -156,10 +197,14 @@ async def get_text(self, *, blocking: Literal[True]) -> str: ... # fmt: on async def get_text(self, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: if blocking: - resp = await self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=True) + resp = await self._engine.win_get_text( + title=f'ahk_id {self._ahk_id}', blocking=True, detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) return resp else: - nonblocking_resp = await self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=False) + nonblocking_resp = await self._engine.win_get_text( + title=f'ahk_id {self._ahk_id}', blocking=False, detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) return nonblocking_resp diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 3b7a62b3..d9522850 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -73,12 +73,52 @@ def __getattr__(self, item: Any) -> Any: def add_hotkey( self, hotkey: 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 hotkey: the hotkey that should trigger the callback. For example the string '#n' for Win+n + :param callback: A callable (e.g., a function) to run when the hotkey is triggered. + :param ex_handler: An exception handler callable that runs when your callback fails. The exception handler must accept two positional arguments. + The first argument is a string representing the hotkey that failed and the second is the exception instance that was raised during the execution of your callback. + If you do not provide an exception handler, a default handler is used. + """ return self._transport.add_hotkey(hotkey=hotkey, callback=callback, ex_handler=ex_handler) def add_hotstring(self, trigger_string: str, replacement: 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 + + :param trigger_string: The 'abbreviation' part of the hotstring. e.g., `btw` + :param replacement: The text to replace when the trigger fires. e.g., `by the way` + """ return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) - def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> Union[None, FutureResult[None]]: + def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> None: + """ + Sets the default title match mode + + Has no effect for `Window`/`Control` instance methods (these always use hwnd) + + 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 @@ -94,7 +134,27 @@ def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> Union[Non ) args.append(str(match_mode)) args.append(str(match_speed)) - resp = self._transport.function_call('AHKSetTitleMatchMode', args) + 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 # fmt: off @@ -118,6 +178,22 @@ def control_send( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: + """ + Analog for ControlSend + + Reference: https://www.autohotkey.com/docs/commands/ControlSend.htm + + :param keys: + :param control: + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ args = [control, keys, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -152,12 +228,27 @@ def control_send( return resp 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 + + """ return self._transport.stop_hotkeys() def set_detect_hidden_windows(self, value: bool) -> None: + """ + Analog for AutoHotkey's `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 = [] @@ -1877,11 +1968,11 @@ def show_traytip( # fmt: off @overload - def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '') -> None: ... + 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 = '', blocking: Literal[True]) -> None: ... + 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 = '', blocking: Literal[False]) -> FutureResult[None]: ... + 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]: ... # fmt: on def win_close( self, @@ -1891,10 +1982,40 @@ def win_close( seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', - blocking: bool = True, + blocking: bool = True, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None ) -> Union[None, FutureResult[None]]: args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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('AHKWinClose', args=args, blocking=blocking) return resp diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index d6413cd4..5175656f 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -60,6 +60,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ +Literal['AHKGetTitleMatchMode'], +Literal['AHKGetTitleMatchSpeed'], Literal['AHKSetDetectHiddenWindows'], Literal['AHKSetTitleMatchMode'], Literal['AHKWinSetTitle'], @@ -408,8 +410,11 @@ def function_call(self, function_name: Literal['AHKSetDetectHiddenWindows'], arg @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) -> Union[None, FutureResult[None]]: ... - + 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 # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload @@ -458,9 +463,7 @@ def send( @abstractmethod def send_nonblocking( self, request: RequestMessage, engine: Optional[AHK] = None - ) -> FutureResult[ - Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]] - ]: + ) -> FutureResult[Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]]: return NotImplemented @@ -519,9 +522,7 @@ def _send_nonblocking( def send_nonblocking( self, request: RequestMessage, engine: Optional[AHK] = None - ) -> FutureResult[ - Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]] - ]: + ) -> 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) diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index edc61d20..a18ce70e 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -35,14 +35,18 @@ def __hash__(self) -> int: return hash(self._ahk_id) def close(self) -> None: - self._engine.win_close(title=f'ahk_id {self._ahk_id}') + self._engine.win_close(title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast')) return None def exists(self) -> bool: - return self._engine.win_exists(title=f'ahk_id {self._ahk_id}') + return self._engine.win_exists( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) def get_pid(self) -> int: - pid = self._engine.win_get_pid(title=f'ahk_id {self._ahk_id}') + 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' @@ -50,7 +54,9 @@ def get_pid(self) -> int: return pid def get_process_name(self) -> str: - name = self._engine.win_get_process_name(title=f'ahk_id {self._ahk_id}') + 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' @@ -58,7 +64,9 @@ def get_process_name(self) -> str: return name def get_process_path(self) -> str: - path = self._engine.win_get_process_path(title=f'ahk_id {self._ahk_id}') + 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' @@ -66,7 +74,9 @@ def get_process_path(self) -> str: return path def get_minmax(self) -> int: - minmax = self._engine.win_get_minmax(title=f'ahk_id {self._ahk_id}') + 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' @@ -74,11 +84,15 @@ def get_minmax(self) -> int: return minmax def get_title(self) -> str: - title = self._engine.win_get_title(title=f'ahk_id {self._ahk_id}') + title = self._engine.win_get_title( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) return title def list_controls(self) -> Sequence['Control']: - controls = self._engine.win_get_control_list(title=f'ahk_id {self._ahk_id}') + 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' @@ -87,7 +101,10 @@ def list_controls(self) -> Sequence['Control']: 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=f'ahk_id {self._ahk_id}', + detect_hidden_windows=True, + new_title=new_title, + title_match_mode=(1, 'Fast'), ) return None @@ -103,11 +120,11 @@ def set_always_on_top( self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True ) -> Union[None, FutureResult[None]]: if blocking: - self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True) + self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True, detect_hidden_windows=True, title_match_mode=(1, 'Fast')) return None else: resp = self._engine.win_set_always_on_top( - toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=False + toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=False, detect_hidden_windows=True, title_match_mode=(1, 'Fast') ) return resp @@ -140,10 +157,10 @@ def send(self, keys: str, *, blocking: Literal[True]) -> None: ... # fmt: on def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: if blocking: - self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=True) + self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=True, detect_hidden_windows=True, title_match_mode=(1, 'Fast')) return None else: - resp = self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=False) + resp = self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=False, detect_hidden_windows=True, title_match_mode=(1, 'Fast')) return resp # fmt: off @@ -156,10 +173,10 @@ def get_text(self, *, blocking: Literal[True]) -> str: ... # fmt: on def get_text(self, *, blocking: bool = True) -> Union[str, FutureResult[str]]: if blocking: - resp = self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=True) + resp = self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=True, detect_hidden_windows=True, title_match_mode=(1, 'Fast')) return resp else: - nonblocking_resp = self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=False) + nonblocking_resp = self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=False, detect_hidden_windows=True, title_match_mode=(1, 'Fast')) return nonblocking_resp diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 9b122bd1..125c5074 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -37,8 +37,22 @@ AHKSetTitleMatchMode(ByRef command) { val1 := command[2] val2 := command[3] if (val1 != "") { - + SetTitleMatchMode, %val1% + } + if (val2 != "") { + SetTitleMatchMode, %val2% } + return FormatNoValueResponse() +} + +AHKGetTitleMatchMode(ByRef command) { + global STRINGRESPONSEMESSAGE + return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchMode) +} + +AHKGetTitleMatchSpeed(ByRef command) { + global STRINGRESPONSEMESSAGE + return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchModeSpeed) } AHKWinExist(ByRef command) { @@ -1605,7 +1619,7 @@ AHKEcho(ByRef command) { 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. diff --git a/requirements-dev.txt b/requirements-dev.txt index 1a0a331c..f321754b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ unasync black tokenize-rt coverage -mypy==0.961 +mypy typing_extensions jinja2 pytest-rerunfailures diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 8e4ef4e8..687efb5c 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -124,3 +124,20 @@ async def test_send_literal_tilde_n(self): 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' diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 53d98c86..9f8d2364 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -124,3 +124,20 @@ def test_send_literal_tilde_n(self): 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' From f8abcdcb11b0971f038375eff3fe65047038bcc8 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Aug 2022 13:52:32 -0700 Subject: [PATCH 267/588] control send --- ahk/_async/engine.py | 10 +++-- ahk/_async/window.py | 31 ++++++++++++++++ ahk/_sync/engine.py | 12 +++--- ahk/_sync/window.py | 73 +++++++++++++++++++++++++++++++++---- tests/_async/test_window.py | 7 ++++ tests/_sync/test_window.py | 7 ++++ 6 files changed, 124 insertions(+), 16 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 7d5bdc9f..d633d084 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -33,10 +33,12 @@ async_sleep = asyncio.sleep # unasync: remove sleep = time.sleep -CoordModeTargets = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] -CoordModeRelativeTo = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] +CoordModeTargets: TypeAlias = Union[ + Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu'] +] +CoordModeRelativeTo: TypeAlias = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] -CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] +CoordMode: TypeAlias = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] MatchModes: TypeAlias = Literal[1, 2, 3, 'RegEx', ''] MatchSpeeds: TypeAlias = Literal['Fast', 'Slow', ''] @@ -1921,7 +1923,7 @@ async def image_search( args.append(s) else: args.append(image_path) - resp = await self._transport.function_call('ImageSearch', args) + resp = await self._transport.function_call('ImageSearch', args, blocking=blocking) return resp async def mouse_drag( diff --git a/ahk/_async/window.py b/ahk/_async/window.py index e857592e..1883913b 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -213,6 +213,37 @@ 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 + + # fmt: off + @overload + async def send(self, keys: str) -> None: ... + @overload + async def send(self, keys: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + # fmt: on + async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + if blocking: + await self._engine.control_send( + keys=keys, + control=self.control_class, + title=f'ahk_id {self.window._ahk_id}', + blocking=True, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + return None + else: + resp = await self._engine.control_send( + keys=keys, + control=self.control_class, + title=f'ahk_id {self.window._ahk_id}', + blocking=False, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + return resp 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/_sync/engine.py b/ahk/_sync/engine.py index d9522850..9f0acba3 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -32,10 +32,10 @@ sleep = time.sleep -CoordModeTargets = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] -CoordModeRelativeTo = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] +CoordModeTargets: TypeAlias = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] +CoordModeRelativeTo: TypeAlias = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] -CoordMode = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] +CoordMode: TypeAlias = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] MatchModes: TypeAlias = Literal[1, 2, 3, 'RegEx', ''] MatchSpeeds: TypeAlias = Literal['Fast', 'Slow', ''] @@ -1920,7 +1920,7 @@ def image_search( args.append(s) else: args.append(image_path) - resp = self._transport.function_call('ImageSearch', args) + resp = self._transport.function_call('ImageSearch', args, blocking=blocking) return resp def mouse_drag( @@ -1982,7 +1982,9 @@ def win_close( 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 + blocking: bool = True, + title_match_mode: Optional[TitleMatchMode] = None, + detect_hidden_windows: Optional[bool] = None, ) -> Union[None, FutureResult[None]]: args: List[str] args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index a18ce70e..aeb064c7 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -35,7 +35,9 @@ def __hash__(self) -> int: return hash(self._ahk_id) def close(self) -> None: - self._engine.win_close(title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast')) + self._engine.win_close( + title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) return None def exists(self) -> bool: @@ -120,11 +122,21 @@ def set_always_on_top( self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0], *, blocking: bool = True ) -> Union[None, FutureResult[None]]: if blocking: - self._engine.win_set_always_on_top(toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=True, detect_hidden_windows=True, title_match_mode=(1, 'Fast')) + self._engine.win_set_always_on_top( + toggle=toggle, + title=f'ahk_id {self._ahk_id}', + blocking=True, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) return None else: resp = self._engine.win_set_always_on_top( - toggle=toggle, title=f'ahk_id {self._ahk_id}', blocking=False, detect_hidden_windows=True, title_match_mode=(1, 'Fast') + toggle=toggle, + title=f'ahk_id {self._ahk_id}', + blocking=False, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), ) return resp @@ -157,10 +169,22 @@ def send(self, keys: str, *, blocking: Literal[True]) -> None: ... # fmt: on def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: if blocking: - self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=True, detect_hidden_windows=True, title_match_mode=(1, 'Fast')) + self._engine.control_send( + keys=keys, + title=f'ahk_id {self._ahk_id}', + blocking=True, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) return None else: - resp = self._engine.control_send(keys=keys, title=f'ahk_id {self._ahk_id}', blocking=False, detect_hidden_windows=True, title_match_mode=(1, 'Fast')) + resp = self._engine.control_send( + keys=keys, + title=f'ahk_id {self._ahk_id}', + blocking=False, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) return resp # fmt: off @@ -173,10 +197,14 @@ def get_text(self, *, blocking: Literal[True]) -> str: ... # fmt: on def get_text(self, *, blocking: bool = True) -> Union[str, FutureResult[str]]: if blocking: - resp = self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=True, detect_hidden_windows=True, title_match_mode=(1, 'Fast')) + resp = self._engine.win_get_text( + title=f'ahk_id {self._ahk_id}', blocking=True, detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) return resp else: - nonblocking_resp = self._engine.win_get_text(title=f'ahk_id {self._ahk_id}', blocking=False, detect_hidden_windows=True, title_match_mode=(1, 'Fast')) + nonblocking_resp = self._engine.win_get_text( + title=f'ahk_id {self._ahk_id}', blocking=False, detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) return nonblocking_resp @@ -185,6 +213,37 @@ 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 + + # fmt: off + @overload + def send(self, keys: str) -> None: ... + @overload + def send(self, keys: str, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + # fmt: on + def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + if blocking: + self._engine.control_send( + keys=keys, + control=self.control_class, + title=f'ahk_id {self.window._ahk_id}', + blocking=True, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + return None + else: + resp = self._engine.control_send( + keys=keys, + control=self.control_class, + title=f'ahk_id {self.window._ahk_id}', + blocking=False, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + return resp 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/tests/_async/test_window.py b/tests/_async/test_window.py index 687efb5c..683c0222 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -141,3 +141,10 @@ 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 diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 9f8d2364..e5a0de82 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -141,3 +141,10 @@ 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 From 3eb04f4a8c7bcec398cf88a89441ee6a75bf73cc Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Aug 2022 15:46:00 -0700 Subject: [PATCH 268/588] allow seamless use of sync API --- ahk/__init__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ahk/__init__.py b/ahk/__init__.py index bb7e7bef..5c681140 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -1,3 +1,6 @@ +from typing import Any +from typing import Optional + from ._async import AsyncAHK from ._async import AsyncControl from ._async import AsyncWindow @@ -6,3 +9,19 @@ from ._sync import Window __all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK', 'Control', 'AsyncControl'] + +_global_instance: Optional[AHK] = 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}') From 31c821ab5df4bd172db70c0df822545402476f4a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Aug 2022 15:58:50 -0700 Subject: [PATCH 269/588] minor cleanups --- ahk/_async/engine.py | 2 +- ahk/_async/transport.py | 4 ---- ahk/_sync/__init__.py | 3 ++- ahk/_sync/transport.py | 8 ++------ ahk/hotkey.py | 4 ---- ahk/message.py | 4 ++-- 6 files changed, 7 insertions(+), 18 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index d633d084..462a5c49 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -635,7 +635,7 @@ async def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - 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) -> Union[AsyncWindow, None]: ... + 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) -> Union[AsyncWindow, None]: ... @overload 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: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... @overload diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 95aae47d..b4976555 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -3,7 +3,6 @@ import asyncio.subprocess import atexit import os -import re import subprocess import sys import warnings @@ -13,9 +12,7 @@ from shutil import which from typing import Any from typing import AnyStr -from typing import Awaitable from typing import Callable -from typing import Coroutine from typing import Generic from typing import List from typing import Literal @@ -590,4 +587,3 @@ async def send( if TYPE_CHECKING: from .engine import AsyncAHK - from ahk import AHK diff --git a/ahk/_sync/__init__.py b/ahk/_sync/__init__.py index 6c55eeac..b2a72267 100644 --- a/ahk/_sync/__init__.py +++ b/ahk/_sync/__init__.py @@ -1,4 +1,5 @@ from .engine import AHK from .window import Control from .window import Window -__all__ =['AHK', 'Window', 'Control'] + +__all__ = ['AHK', 'Window', 'Control'] diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 5175656f..f6060e48 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -3,7 +3,6 @@ import asyncio.subprocess import atexit import os -import re import subprocess import sys import warnings @@ -13,9 +12,7 @@ from shutil import which from typing import Any from typing import AnyStr -from typing import Awaitable from typing import Callable -from typing import Coroutine from typing import Generic from typing import List from typing import Literal @@ -60,8 +57,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ -Literal['AHKGetTitleMatchMode'], -Literal['AHKGetTitleMatchSpeed'], + Literal['AHKGetTitleMatchMode'], + Literal['AHKGetTitleMatchSpeed'], Literal['AHKSetDetectHiddenWindows'], Literal['AHKSetTitleMatchMode'], Literal['AHKWinSetTitle'], @@ -558,4 +555,3 @@ def send( if TYPE_CHECKING: from .engine import AHK - from ahk import AHK diff --git a/ahk/hotkey.py b/ahk/hotkey.py index 3f48abc3..925d2558 100644 --- a/ahk/hotkey.py +++ b/ahk/hotkey.py @@ -10,7 +10,6 @@ from textwrap import dedent from typing import Any from typing import Callable -from typing import Deque from typing import Dict from typing import List from typing import Optional @@ -25,7 +24,6 @@ from typing import ParamSpec else: from typing_extensions import ParamSpec -import traceback import logging import tempfile from jinja2 import Environment, BaseLoader @@ -201,8 +199,6 @@ def _render_hotkey_tempate(self) -> str: return ret def listener(self) -> None: - last_keepalive_received: Optional[float] = None - hotkey_script_contents = self._render_hotkey_tempate() logging.debug('hotkey script contents:\n%s', hotkey_script_contents) with tempfile.TemporaryDirectory(prefix='python-ahk') as tmpdirname: diff --git a/ahk/message.py b/ahk/message.py index 93da3420..e1f09c63 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -271,8 +271,8 @@ class WindowResponseMessage(ResponseMessage): def unpack(self) -> Union[Window, AsyncWindow]: from ._async.engine import AsyncAHK - from ._async.window import AsyncWindow, AsyncControl - from ._sync.window import Window, Control + from ._async.window import AsyncWindow + from ._sync.window import Window from ._sync.engine import AHK s = self._raw_content.decode(encoding='utf-8') From 81eb08971cbe0a2bd48b101fdbda92f4f13d6786 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 16 Aug 2022 16:40:27 -0700 Subject: [PATCH 270/588] new hotkey class --- ahk/__init__.py | 4 +- ahk/_async/engine.py | 54 +++++++--- ahk/_async/transport.py | 22 ++-- ahk/_sync/engine.py | 60 ++++++++--- ahk/_sync/transport.py | 22 ++-- ahk/hotkey.py | 198 +++++++++++++++++++++++++---------- ahk/hotkeys.ahk | 76 ++++++++++++++ ahk/keys.py | 2 +- tests/_async/test_hotkeys.py | 7 +- tests/_sync/test_hotkeys.py | 6 +- 10 files changed, 346 insertions(+), 105 deletions(-) create mode 100644 ahk/hotkeys.ahk diff --git a/ahk/__init__.py b/ahk/__init__.py index 5c681140..59754690 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -7,8 +7,10 @@ from ._sync import AHK from ._sync import Control from ._sync import Window +from .hotkey import Hotkey +from .hotkey import Hotstring -__all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK', 'Control', 'AsyncControl'] +__all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK', 'Control', 'AsyncControl', 'Hotkey', 'Hotstring'] _global_instance: Optional[AHK] = None diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 462a5c49..281ca176 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -17,6 +17,9 @@ from typing import Type from typing import Union +from ..hotkey import Hotkey +from ..hotkey import Hotstring + if sys.version_info < (3, 10): from typing_extensions import TypeAlias else: @@ -73,9 +76,7 @@ def __getattr__(self, item: Any) -> Any: ) return deprecation_replacements[item] - def add_hotkey( - self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None - ) -> None: + def add_hotkey(self, hotkey: Hotkey) -> None: """ Register a function to be called when a hotkey is pressed. @@ -86,27 +87,32 @@ def add_hotkey( - 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 hotkey: the hotkey that should trigger the callback. For example the string '#n' for Win+n - :param callback: A callable (e.g., a function) to run when the hotkey is triggered. - :param ex_handler: An exception handler callable that runs when your callback fails. The exception handler must accept two positional arguments. - The first argument is a string representing the hotkey that failed and the second is the exception instance that was raised during the execution of your callback. - If you do not provide an exception handler, a default handler is used. + :param hotkey: an instance of ahk.hotkey.Hotkey """ - return self._transport.add_hotkey(hotkey=hotkey, callback=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 add_hotstring(self, trigger_string: str, replacement: str) -> None: + def add_hotstring(self, hotstring: Hotstring) -> 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 + - All hotstrings (and hotkeys) run in a single AHK process instance separate from other AHK processes. - :param trigger_string: The 'abbreviation' part of the hotstring. e.g., `btw` - :param replacement: The text to replace when the trigger fires. e.g., `by the way` + :param hotstring: an instance of ahk.hotkey.Hotstring """ - return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + 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 async def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> None: """ @@ -377,6 +383,20 @@ async def find_windows_by_text(self, text: str, exact: bool = False) -> Iterable async def find_windows_by_title(self, title: str, exact: bool = False) -> Iterable[AsyncWindow]: raise NotImplementedError() + async def find_window( + self, func: Optional[Callable[[AsyncWindow], bool]] = None, **kwargs: Any + ) -> Iterable[AsyncWindow]: + raise NotImplementedError() + + async def find_window_by_class(self, class_name: str, exact: bool = False) -> Iterable[AsyncWindow]: + raise NotImplementedError() + + async def find_window_by_text(self, text: str, exact: bool = False) -> Iterable[AsyncWindow]: + raise NotImplementedError() + + async def find_window_by_title(self, title: str, exact: bool = False) -> Iterable[AsyncWindow]: + raise NotImplementedError() + async def get_volume(self, device_number: int = 1) -> float: raise NotImplementedError() @@ -1852,6 +1872,10 @@ async def win_set_trans_color( # alias for backwards compatibility windows = list_windows + async def right_click(self, *args: Any, **kwargs: Any) -> Union[None, AsyncFutureResult[None]]: + kwargs['button'] = 2 + return await self.click(*args, **kwargs) + async def click( self, x: Optional[int] = None, @@ -1863,7 +1887,7 @@ async def click( relative: Optional[bool] = None, blocking: bool = True, mode: Optional[CoordMode] = None, - ) -> None: + ) -> Union[None, AsyncFutureResult[None]]: raise NotImplementedError() # fmt: off diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index b4976555..c6e10528 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -34,7 +34,7 @@ else: from typing import TypeAlias, TypeGuard -from ahk.hotkey import ThreadedHotkeyTransport +from ahk.hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from concurrent.futures import Future, ThreadPoolExecutor DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' @@ -263,13 +263,21 @@ def __init__(self, /, executable_path: Union[str, os.PathLike[AnyStr]] = '', **k self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) pass - def add_hotkey( - self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None - ) -> None: - return self._hotkey_transport.add_hotkey(hotkey=hotkey, callback=callback, ex_handler=ex_handler) + def add_hotkey(self, hotkey: Hotkey) -> None: + 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, trigger_string: str, replacement: str) -> None: - return self._hotkey_transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + def add_hotstring(self, hotstring: Hotstring) -> None: + 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 start_hotkeys(self) -> None: return self._hotkey_transport.start() diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 9f0acba3..c5a9d051 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -17,6 +17,9 @@ from typing import Type from typing import Union +from ..hotkey import Hotkey +from ..hotkey import Hotstring + if sys.version_info < (3, 10): from typing_extensions import TypeAlias else: @@ -32,7 +35,9 @@ sleep = time.sleep -CoordModeTargets: TypeAlias = Union[Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu']] +CoordModeTargets: TypeAlias = Union[ + Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu'] +] CoordModeRelativeTo: TypeAlias = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] CoordMode: TypeAlias = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] @@ -71,8 +76,7 @@ def __getattr__(self, item: Any) -> Any: return deprecation_replacements[item] def add_hotkey( - self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None - ) -> None: + self, hotkey: Hotkey) -> None: """ Register a function to be called when a hotkey is pressed. @@ -83,27 +87,32 @@ def add_hotkey( - 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 hotkey: the hotkey that should trigger the callback. For example the string '#n' for Win+n - :param callback: A callable (e.g., a function) to run when the hotkey is triggered. - :param ex_handler: An exception handler callable that runs when your callback fails. The exception handler must accept two positional arguments. - The first argument is a string representing the hotkey that failed and the second is the exception instance that was raised during the execution of your callback. - If you do not provide an exception handler, a default handler is used. + :param hotkey: an instance of ahk.hotkey.Hotkey """ - return self._transport.add_hotkey(hotkey=hotkey, callback=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 add_hotstring(self, trigger_string: str, replacement: str) -> None: + def add_hotstring(self, hotstring: Hotstring) -> 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 + - All hotstrings (and hotkeys) run in a single AHK process instance separate from other AHK processes. - :param trigger_string: The 'abbreviation' part of the hotstring. e.g., `btw` - :param replacement: The text to replace when the trigger fires. e.g., `by the way` + :param hotstring: an instance of ahk.hotkey.Hotstring """ - return self._transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + 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 set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> None: """ @@ -374,6 +383,21 @@ def find_windows_by_text(self, text: str, exact: bool = False) -> Iterable[Windo def find_windows_by_title(self, title: str, exact: bool = False) -> Iterable[Window]: raise NotImplementedError() + + def find_window( + self, func: Optional[Callable[[Window], bool]] = None, **kwargs: Any + ) -> Iterable[Window]: + raise NotImplementedError() + + def find_window_by_class(self, class_name: str, exact: bool = False) -> Iterable[Window]: + raise NotImplementedError() + + def find_window_by_text(self, text: str, exact: bool = False) -> Iterable[Window]: + raise NotImplementedError() + + def find_window_by_title(self, title: str, exact: bool = False) -> Iterable[Window]: + raise NotImplementedError() + def get_volume(self, device_number: int = 1) -> float: raise NotImplementedError() @@ -632,7 +656,7 @@ def type(self, s: str, blocking: bool = True) -> None: # fmt: off @overload - 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) -> Union[Window, None]: ... + 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) -> Union[Window, None]: ... @overload 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: Literal[False]) -> FutureResult[Union[Window, None]]: ... @overload @@ -1849,6 +1873,10 @@ def win_set_trans_color( # alias for backwards compatibility windows = list_windows + def right_click(self, *args: Any, **kwargs: Any) -> Union[None, FutureResult[None]]: + kwargs['button'] = 2 + return self.click(*args, **kwargs) + def click( self, x: Optional[int] = None, @@ -1860,7 +1888,7 @@ def click( relative: Optional[bool] = None, blocking: bool = True, mode: Optional[CoordMode] = None, - ) -> None: + ) -> Union[None, FutureResult[None]]: raise NotImplementedError() # fmt: off diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index f6060e48..57fe9d8f 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -34,7 +34,7 @@ else: from typing import TypeAlias, TypeGuard -from ahk.hotkey import ThreadedHotkeyTransport +from ahk.hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from concurrent.futures import Future, ThreadPoolExecutor DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' @@ -246,13 +246,21 @@ def __init__(self, /, executable_path: Union[str, os.PathLike[AnyStr]] = '', **k self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) pass - def add_hotkey( - self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None - ) -> None: - return self._hotkey_transport.add_hotkey(hotkey=hotkey, callback=callback, ex_handler=ex_handler) + def add_hotkey(self, hotkey: Hotkey) -> None: + 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, trigger_string: str, replacement: str) -> None: - return self._hotkey_transport.add_hotstring(trigger_string=trigger_string, replacement=replacement) + def add_hotstring(self, hotstring: Hotstring) -> None: + 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 start_hotkeys(self) -> None: return self._hotkey_transport.start() diff --git a/ahk/hotkey.py b/ahk/hotkey.py index 925d2558..6b1c3c04 100644 --- a/ahk/hotkey.py +++ b/ahk/hotkey.py @@ -1,12 +1,17 @@ from __future__ import annotations import atexit +import functools import os +import pathlib +import re import subprocess import sys import threading +import warnings from abc import ABC from abc import abstractmethod +from base64 import b64encode from textwrap import dedent from typing import Any from typing import Callable @@ -24,6 +29,12 @@ from typing import ParamSpec else: from typing_extensions import ParamSpec + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + import logging import tempfile from jinja2 import Environment, BaseLoader @@ -42,11 +53,18 @@ def _default_ex_handler(hotkey: str, ex: Exception) -> None: class HotkeyTransportBase(ABC): def __init__(self, executable_path: str, default_ex_handler: Optional[Callable[[str, Exception], Any]] = None): self._executable_path = executable_path - self._hotkeys: Dict[str, Tuple[Callable[[], Any], Optional[Callable[[str, Exception], Any]]]] = {} + self._hotkeys: Dict[str, Hotkey] = {} self._default_ex_handler: Callable[[str, Exception], Any] = default_ex_handler or _default_ex_handler - # self._transport_options: Dict[Any, Any] = transport_options or {} - self._hotstrings: Dict[str, str] = {} + self._hotstrings: Dict[str, Hotstring] = {} self._running: bool = False + self._get_callback_registry = functools.lru_cache(maxsize=None)(self._callback_registry_uncached) + + @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]]: + return self._hotkeys | self._hotstrings @abstractmethod def restart(self) -> Any: @@ -56,28 +74,20 @@ def restart(self) -> Any: def start(self) -> Any: return NotImplemented - @staticmethod - def _validate_hotkey(hotkey: str) -> None: - assert '\n' not in hotkey, 'Newlines not allowed in hotkeys' # TODO: perform better validation - - @staticmethod - def _validate_hotstring(trigger: str, replacement: str) -> None: - assert '\n' not in trigger, 'newlines not allowed in hotstrings' # TODO: perform better validation - assert '\n' not in replacement, 'newlines not allowed in hotstrings' - - def add_hotkey( - self, hotkey: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None - ) -> None: - self._validate_hotkey(hotkey) - self._hotkeys[hotkey] = (callback, ex_handler) + 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, trigger_string: str, replacement: str) -> None: - replacement = replacement.replace('\n', '`n').replace('\r', '`n') - self._validate_hotstring(trigger_string, replacement) - self._hotstrings[trigger_string] = replacement + 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 @@ -127,20 +137,20 @@ def stop(self) -> None: self._callback_queue.empty() self._callback_queue.put_nowait(STOP) - print('Waiting for stop...') + logging.debug('Waiting for stop...') if self._dispatcher_thread is not None: try: self._dispatcher_thread.join(timeout=3) except TimeoutError: - print('DISPATCHER JOIN TIMED OUT!') + logging.debug('DISPATCHER JOIN TIMED OUT!') self._dispatcher_thread = None - print('Waiting for callback stop...') + 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: - print('LISTENER JOIN TIMED OUT!') + logging.debug('LISTENER JOIN TIMED OUT!') self._listener_thread = None self._proc.kill() @@ -156,12 +166,16 @@ def dispatcher(self) -> None: break assert isinstance(job, str) - if job not in self._hotkeys: + if job not in self._callback_registry: logging.warning(f'Received request to dispatch unregistered hotkey: {job!r}. Ignoring.') self._callback_queue.task_done() continue - cb, ex_handler = self._hotkeys[job] + hot_thing: Union[Hotstring, Hotkey] = self._hotkeys[job] + cb = hot_thing.callback + assert cb is not None + ex_handler = hot_thing.ex_handler + 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() @@ -169,32 +183,11 @@ def dispatcher(self) -> None: def _render_hotkey_tempate(self) -> str: env = Environment(loader=BaseLoader()) - template = env.from_string( - dedent( - """\ - KEEPALIVE := Chr(57344) - SetTimer, keepalive, 1000 - - {% for hotkey in hotkeys %} - - {{ hotkey }}:: - FileAppend, %A_ThisHotkey%`n, *, UTF-8 - return - - {% endfor %} - - {% for trigger, replacement in hotstrings %} - - ::{{ trigger }}::{{replacement}} - - {% endfor %} - keepalive: - global KEEPALIVE - FileAppend, %KEEPALIVE%`n, *, UTF-8 - """ - ) - ) - ret = template.render(hotkeys=list(self._hotkeys), hotstrings=self._hotstrings.items()) + # TODO: make string constant for template + fname = pathlib.Path(__file__).parent / 'hotkeys.ahk' + template_string = open(fname).read() + template = env.from_string(template_string) + ret = template.render(hotkeys=list(self._hotkeys.values()), hotstrings=self._hotstrings.values()) assert isinstance(ret, str) return ret @@ -219,12 +212,109 @@ def listener(self) -> None: logging.debug('keepalive received') continue if not line.strip(): - print('Listener: Process probably died, exiting') + logging.debug('Listener: Process probably died, exiting') break logging.debug(f'Received {line!r}') self._callback_queue.put_nowait(line.decode('UTF-8').strip()) +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]], + options: str = '', + ): + self.replacement: Optional[str] + self.callback: Optional[Callable[[], Any]] + self.ex_handler: Optional[Callable[[str, Exception], Any]] + self._trigger: str = 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)) + + @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: + assert '\n' not in self.trigger, 'Newlines not allowed in trigger' + 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\n+|S[IPE]|T|Z)+', self.options.upper() + ), f'Invalid options: {self.options!r}' + return None + + @runtime_checkable class Killable(Protocol): def kill(self) -> None: diff --git a/ahk/hotkeys.ahk b/ahk/hotkeys.ahk new file mode 100644 index 00000000..816ab4d6 --- /dev/null +++ b/ahk/hotkeys.ahk @@ -0,0 +1,76 @@ +KEEPALIVE := Chr(57344) +SetTimer, keepalive, 1000 + + +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 %} + + +keepalive: +global KEEPALIVE +FileAppend, %KEEPALIVE%`n, *, UTF-8 diff --git a/ahk/keys.py b/ahk/keys.py index 752a715d..c978ce73 100644 --- a/ahk/keys.py +++ b/ahk/keys.py @@ -243,5 +243,5 @@ def _init_keys() -> None: 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__} has no attribute {name!r}') + raise AttributeError(f'module {__name__!r} has no attribute {name!r}') return obj diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index 0c4841f6..eb971508 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -7,6 +7,8 @@ from ahk import AsyncAHK from ahk import AsyncWindow +from ahk.hotkey import Hotkey + async_sleep = asyncio.sleep # unasync: remove @@ -25,8 +27,9 @@ async def asyncTearDown(self) -> None: subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey.exe']) async def test_hotkey(self): + with mock.MagicMock(return_value=None) as m: - self.ahk.add_hotkey('a', callback=m) + self.ahk.add_hotkey(Hotkey('a', callback=m)) self.ahk.start_hotkeys() await self.ahk.key_down('a') await self.ahk.key_press('a') @@ -39,7 +42,7 @@ def side_effect(): 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.add_hotkey(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') diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py index 470513be..af653462 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -6,6 +6,7 @@ from ahk import AHK from ahk import Window +from ahk.hotkey import Hotkey sleep = time.sleep @@ -23,8 +24,9 @@ def tearDown(self) -> None: subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey.exe']) def test_hotkey(self): + with mock.MagicMock(return_value=None) as m: - self.ahk.add_hotkey('a', callback=m) + self.ahk.add_hotkey(Hotkey('a', callback=m)) self.ahk.start_hotkeys() self.ahk.key_down('a') self.ahk.key_press('a') @@ -37,7 +39,7 @@ def side_effect(): 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.add_hotkey(Hotkey('a', callback=mock_cb, ex_handler=mock_ex_handler)) self.ahk.start_hotkeys() self.ahk.key_down('a') self.ahk.key_press('a') From fad08b10da11aa46928434a9eec433748033f88c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 16 Aug 2022 16:43:04 -0700 Subject: [PATCH 271/588] update requirements --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e6b291dc..b9face06 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,7 +37,7 @@ packages = ahk._async ahk._sync install_requires = - typing_extensions; python_version < "3.10" + typing_extensions; python_version < "3.11" jinja2>=3.0 cmdclass = build_py = buildunasync.build_py From eda183f710a330a57d317ff0a98dafeb39fdc655 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 16 Aug 2022 16:44:30 -0700 Subject: [PATCH 272/588] add hotkey script includes --- MANIFEST.in | 1 + setup.cfg | 1 + 2 files changed, 2 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 98730c44..3ab9b44e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include ahk/daemon.ahk +include ahk/hotkeys.ahk include docs/README.md include buildunasync.py diff --git a/setup.cfg b/setup.cfg index b9face06..03402423 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,6 +46,7 @@ cmdclass = ahk = py.typed daemon.ahk + hotkeys.ahk [build-system] requires = ["setuptools", "unasync", "tokenize-rt"] From 128e611b6aad2f5078900537feb26460084c25a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Sep 2022 22:13:35 +0000 Subject: [PATCH 273/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.6.0 → 22.8.0](https://github.com/psf/black/compare/22.6.0...22.8.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8b55ccb..2df28ac0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '22.6.0' + rev: '22.8.0' hooks: - id: black args: From 261ec9d87136d88c4637121fd5d2bc57f61610c4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 23:08:21 +0000 Subject: [PATCH 274/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.8.0 → 22.10.0](https://github.com/psf/black/compare/22.8.0...22.10.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2df28ac0..685a186e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '22.8.0' + rev: '22.10.0' hooks: - id: black args: From d3cb59db58de7e58e245f221f1cc11962202e033 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 11 Nov 2022 11:23:30 -0800 Subject: [PATCH 275/588] controlclick/controlgettext --- ahk/_async/engine.py | 190 +++++++++++++++++++++++++++++++++++++++ ahk/_async/transport.py | 9 ++ ahk/_async/window.py | 125 ++++++++++++-------------- ahk/_sync/engine.py | 194 +++++++++++++++++++++++++++++++++++++++- ahk/_sync/transport.py | 9 ++ ahk/_sync/window.py | 111 +++++++++++------------ ahk/daemon.ahk | 116 ++++++++++++++++++++---- 7 files changed, 608 insertions(+), 146 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 281ca176..1e6a62c6 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -75,6 +75,7 @@ def __getattr__(self, item: Any) -> Any: stacklevel=2, ) return deprecation_replacements[item] + raise AttributeError(f'{self.__class__.__qualname__!r} object has no attribute {item!r}') def add_hotkey(self, hotkey: Hotkey) -> None: """ @@ -166,6 +167,121 @@ async def get_title_match_speed(self) -> str: resp = await self._transport.function_call('AHKGetTitleMatchSpeed') return resp + # fmt: off + @overload + async def control_click(self, *, button: Union[int, str] = 1, 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: Union[int, str] = 1, 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: Union[int, str] = 1, 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: Union[int, str] = 1, 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: Union[int, str] = 1, + 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]]: + raise NotImplementedError + args = [control, title, text, button, click_count, options, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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]]: + args = [control, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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_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: ... @@ -173,6 +289,8 @@ async def control_send(self, keys: str, control: str = '', title: str = '', text 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, @@ -275,6 +393,8 @@ async def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = Non async def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... @overload async def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[AsyncWindow]: ... + @overload + async def list_windows(self, *, 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, @@ -323,6 +443,8 @@ async def get_mouse_position(self, *, blocking: Literal[True]) -> Tuple[int, int async def get_mouse_position(self, *, blocking: Literal[False]) -> AsyncFutureResult[Tuple[int, int]]: ... @overload async def get_mouse_position(self) -> Tuple[int, int]: ... + @overload + async def get_mouse_position(self, *, blocking: bool = True) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... # fmt: on async def get_mouse_position( self, *, blocking: bool = True @@ -337,6 +459,8 @@ async def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Unio 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) -> 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, ) -> 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) -> Union[None, AsyncFutureResult[None]]: ... # fmt: on async def mouse_move( self, @@ -407,6 +531,8 @@ async def key_down(self, key: Union[str, Key]) -> None: ... 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]]: if isinstance(key, str): @@ -424,6 +550,8 @@ async def key_press(self, key: Union[str, Key], *, release: bool = True) -> None 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 @@ -446,6 +574,8 @@ async def key_release(self, key: Union[str, Key]) -> None: ... 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]]: if blocking: @@ -464,6 +594,8 @@ async def key_up(self, key: Union[str, Key]) -> None: ... 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]]: if isinstance(key, str): @@ -481,6 +613,8 @@ async def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logica async def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... @overload async def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> AsyncFutureResult[int]: ... + @overload + async def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[int, AsyncFutureResult[int]]: ... # fmt: on async def key_wait( self, @@ -543,6 +677,8 @@ async def send(self, s: str) -> None: ... async def send(self, s: str, *, blocking: Literal[True]) -> None: ... @overload async def send(self, s: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def send(self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... # fmt: on async def send( self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True @@ -565,6 +701,8 @@ async def send_input(self, s: str) -> None: ... 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]]: args = [s] @@ -660,6 +798,8 @@ async def win_get(self, title: str = '', text: str = '', exclude_title: str = '' 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: Literal[False]) -> AsyncFutureResult[Union[AsyncWindow, None]]: ... @overload 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: Literal[True]) -> Union[AsyncWindow, None]: ... + @overload + 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]]]: ... # fmt: on async def win_get( self, @@ -712,6 +852,8 @@ async def win_get_text(self, title: str = '', text: str = '', exclude_title: str 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, @@ -764,6 +906,8 @@ async def win_get_title(self, title: str = '', text: str = '', exclude_title: st 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, @@ -816,6 +960,8 @@ async def win_get_idlast(self, title: str = '', text: str = '', exclude_title: s 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, @@ -868,6 +1014,8 @@ async def win_get_pid(self, title: str = '', text: str = '', exclude_title: str 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, @@ -920,6 +1068,8 @@ async def win_get_process_name(self, title: str = '', text: str = '', exclude_ti 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, @@ -972,6 +1122,8 @@ async def win_get_process_path(self, title: str = '', text: str = '', exclude_ti 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, @@ -1024,6 +1176,8 @@ async def win_get_count(self, title: str = '', text: str = '', exclude_title: st 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, @@ -1076,6 +1230,8 @@ async def win_get_minmax(self, title: str = '', text: str = '', exclude_title: s 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, @@ -1128,6 +1284,8 @@ async def win_get_control_list(self, title: str = '', text: str = '', exclude_ti 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, @@ -1180,6 +1338,8 @@ async def win_get_from_mouse_position(self) -> Union[AsyncWindow, None]: ... 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 @@ -1193,6 +1353,8 @@ async def win_exists(self, title: str = '', text: str = '', exclude_title: str = 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, @@ -1245,6 +1407,8 @@ async def win_set_title(self, new_title: str, title: str = '', text: str = '', e 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, @@ -1298,6 +1462,8 @@ async def win_set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, 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, @@ -1351,6 +1517,8 @@ async def win_set_bottom(self, title: str = '', text: str = '', exclude_title: s 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, @@ -1403,6 +1571,8 @@ async def win_set_top(self, title: str = '', text: str = '', exclude_title: str 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, @@ -1455,6 +1625,8 @@ async def win_set_disable(self, title: str = '', text: str = '', exclude_title: 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, @@ -1507,6 +1679,8 @@ async def win_set_enable(self, title: str = '', text: str = '', exclude_title: s 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, @@ -1559,6 +1733,8 @@ async def win_set_redraw(self, title: str = '', text: str = '', exclude_title: s 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, @@ -1611,6 +1787,8 @@ async def win_set_style(self, style: str, title: str = '', text: str = '', exclu 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, @@ -1664,6 +1842,8 @@ async def win_set_ex_style(self, style: str, title: str = '', text: str = '', ex 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, @@ -1717,6 +1897,8 @@ async def win_set_region(self, options: str, title: str = '', text: str = '', ex 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, @@ -1770,6 +1952,8 @@ async def win_set_transparent(self, transparency: Union[int, Literal['Off']], ti 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, @@ -1823,6 +2007,8 @@ async def win_set_trans_color(self, color: Union[int, str], title: str = '', tex 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, @@ -1897,6 +2083,8 @@ async def image_search(self, image_path: str, upper_bound: Tuple[Union[int, str] 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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[Optional[Tuple[int, int]]]: ... @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[True]) -> Optional[Tuple[int, int]]: ... + @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: bool = True) -> Union[Tuple[int, int], None, AsyncFutureResult[Optional[Tuple[int, int]]]]: ... # fmt: on async def image_search( self, @@ -2000,6 +2188,8 @@ async def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: O 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, diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index c6e10528..93c81452 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -65,6 +65,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ + Literal['AHKControlClick'], + Literal['AHKControlGetText'], Literal['AHKGetTitleMatchMode'], Literal['AHKGetTitleMatchSpeed'], Literal['AHKSetDetectHiddenWindows'], @@ -437,6 +439,13 @@ async def function_call(self, function_name: Literal['AHKSetTitleMatchMode'], ar 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] = 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['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 1883913b..f9d48475 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -117,28 +117,19 @@ async def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 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]]: - if blocking: - await self._engine.win_set_always_on_top( - toggle=toggle, - title=f'ahk_id {self._ahk_id}', - blocking=True, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return None - else: - resp = await self._engine.win_set_always_on_top( - toggle=toggle, - title=f'ahk_id {self._ahk_id}', - blocking=False, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return resp + 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 @@ -147,6 +138,8 @@ async def is_always_on_top(self) -> bool: ... 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}'] @@ -166,26 +159,17 @@ async def send(self, keys: str) -> None: ... async def send(self, keys: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... @overload async def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + @overload + async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... # fmt: on async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: - if blocking: - await self._engine.control_send( - keys=keys, - title=f'ahk_id {self._ahk_id}', - blocking=True, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return None - else: - resp = await self._engine.control_send( - keys=keys, - title=f'ahk_id {self._ahk_id}', - blocking=False, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return resp + return await self._engine.control_send( + keys=keys, + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) # fmt: off @overload @@ -194,18 +178,13 @@ async def get_text(self) -> str: ... 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]]: - if blocking: - resp = await self._engine.win_get_text( - title=f'ahk_id {self._ahk_id}', blocking=True, detect_hidden_windows=True, title_match_mode=(1, 'Fast') - ) - return resp - else: - nonblocking_resp = await self._engine.win_get_text( - title=f'ahk_id {self._ahk_id}', blocking=False, detect_hidden_windows=True, title_match_mode=(1, 'Fast') - ) - return nonblocking_resp + 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') + ) class AsyncControl: @@ -215,6 +194,21 @@ def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): self.control_class: str = control_class self._engine = window._engine + # fmt: off + @overload + async def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '') -> None: ... + @overload + async def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: Literal[True]) -> None: ... + @overload + async def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on + async def click( + self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + raise NotImplementedError + # fmt: off @overload async def send(self, keys: str) -> None: ... @@ -222,28 +216,27 @@ async def send(self, keys: str) -> None: ... async def send(self, keys: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... @overload async def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + @overload + async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... # fmt: on async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: - if blocking: - await self._engine.control_send( - keys=keys, - control=self.control_class, - title=f'ahk_id {self.window._ahk_id}', - blocking=True, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return None - else: - resp = await self._engine.control_send( - keys=keys, - control=self.control_class, - title=f'ahk_id {self.window._ahk_id}', - blocking=False, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return resp + return await self._engine.control_send( + keys=keys, + control=self.control_class, + title=f'ahk_id {self.window._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + + async def get_text(self, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: + return await self._engine.control_get_text( + control=self.control_class, + title=f'ahk_id {self.window._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) 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/_sync/engine.py b/ahk/_sync/engine.py index c5a9d051..dbfec0b7 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -74,9 +74,9 @@ def __getattr__(self, item: Any) -> Any: stacklevel=2, ) return deprecation_replacements[item] + raise AttributeError(f'{self.__class__.__qualname__!r} object has no attribute {item!r}') - def add_hotkey( - self, hotkey: Hotkey) -> None: + def add_hotkey(self, hotkey: Hotkey) -> None: """ Register a function to be called when a hotkey is pressed. @@ -166,6 +166,121 @@ def get_title_match_speed(self) -> str: resp = self._transport.function_call('AHKGetTitleMatchSpeed') return resp + # fmt: off + @overload + def control_click(self, *, button: Union[int, str] = 1, 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: Union[int, str] = 1, 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: Union[int, str] = 1, 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: Union[int, str] = 1, 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: Union[int, str] = 1, + 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]]: + raise NotImplementedError + args = [control, title, text, button, click_count, options, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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]]: + args = [control, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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_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: ... @@ -173,6 +288,8 @@ def control_send(self, keys: str, control: str = '', title: str = '', text: str 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, @@ -275,6 +392,8 @@ def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, det def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[List[Window], FutureResult[List[Window]]]: ... @overload def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[Window]: ... + @overload + def list_windows(self, *, 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, @@ -323,6 +442,8 @@ def get_mouse_position(self, *, blocking: Literal[True]) -> Tuple[int, int]: ... def get_mouse_position(self, *, blocking: Literal[False]) -> FutureResult[Tuple[int, int]]: ... @overload def get_mouse_position(self) -> Tuple[int, int]: ... + @overload + def get_mouse_position(self, *, blocking: bool = True) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... # fmt: on def get_mouse_position( self, *, blocking: bool = True @@ -337,6 +458,8 @@ def mouse_move(self, x: Optional[Union[str, int]] = None, y: Optional[Union[str, 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) -> 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, ) -> 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) -> Union[None, FutureResult[None]]: ... # fmt: on def mouse_move( self, @@ -383,7 +506,6 @@ def find_windows_by_text(self, text: str, exact: bool = False) -> Iterable[Windo def find_windows_by_title(self, title: str, exact: bool = False) -> Iterable[Window]: raise NotImplementedError() - def find_window( self, func: Optional[Callable[[Window], bool]] = None, **kwargs: Any ) -> Iterable[Window]: @@ -408,6 +530,8 @@ def key_down(self, key: Union[str, Key]) -> None: ... 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]]: if isinstance(key, str): @@ -425,6 +549,8 @@ def key_press(self, key: Union[str, Key], *, release: bool = True) -> None: ... 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 @@ -447,6 +573,8 @@ def key_release(self, key: Union[str, Key]) -> None: ... 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]]: if blocking: @@ -465,6 +593,8 @@ def key_up(self, key: Union[str, Key]) -> None: ... 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]]: if isinstance(key, str): @@ -482,6 +612,8 @@ def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_stat def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... @overload def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> FutureResult[int]: ... + @overload + def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[int, FutureResult[int]]: ... # fmt: on def key_wait( self, @@ -544,6 +676,8 @@ def send(self, s: str) -> None: ... def send(self, s: str, *, blocking: Literal[True]) -> None: ... @overload def send(self, s: str, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def send(self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... # fmt: on def send( self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True @@ -566,6 +700,8 @@ def send_input(self, s: str) -> None: ... 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]]: args = [s] @@ -661,6 +797,8 @@ def win_get(self, title: str = '', text: str = '', exclude_title: str = '', excl 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: Literal[False]) -> FutureResult[Union[Window, None]]: ... @overload 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: Literal[True]) -> Union[Window, None]: ... + @overload + 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]]]: ... # fmt: on def win_get( self, @@ -713,6 +851,8 @@ def win_get_text(self, title: str = '', text: str = '', exclude_title: str = '', 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, @@ -765,6 +905,8 @@ def win_get_title(self, title: str = '', text: str = '', exclude_title: str = '' 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, @@ -817,6 +959,8 @@ def win_get_idlast(self, title: str = '', text: str = '', exclude_title: str = ' 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, @@ -869,6 +1013,8 @@ def win_get_pid(self, title: str = '', text: str = '', exclude_title: str = '', 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, @@ -921,6 +1067,8 @@ def win_get_process_name(self, title: str = '', text: str = '', exclude_title: s 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, @@ -973,6 +1121,8 @@ def win_get_process_path(self, title: str = '', text: str = '', exclude_title: s 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, @@ -1025,6 +1175,8 @@ def win_get_count(self, title: str = '', text: str = '', exclude_title: str = '' 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, @@ -1077,6 +1229,8 @@ def win_get_minmax(self, title: str = '', text: str = '', exclude_title: str = ' 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, @@ -1129,6 +1283,8 @@ def win_get_control_list(self, title: str = '', text: str = '', exclude_title: s 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, @@ -1181,6 +1337,8 @@ def win_get_from_mouse_position(self) -> Union[Window, None]: ... 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 @@ -1194,6 +1352,8 @@ def win_exists(self, title: str = '', text: str = '', exclude_title: str = '', e 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, @@ -1246,6 +1406,8 @@ def win_set_title(self, new_title: str, title: str = '', text: str = '', exclude 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, @@ -1299,6 +1461,8 @@ def win_set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0] 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, @@ -1352,6 +1516,8 @@ def win_set_bottom(self, title: str = '', text: str = '', exclude_title: str = ' 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, @@ -1404,6 +1570,8 @@ def win_set_top(self, title: str = '', text: str = '', exclude_title: str = '', 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, @@ -1456,6 +1624,8 @@ def win_set_disable(self, title: str = '', text: str = '', exclude_title: str = 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, @@ -1508,6 +1678,8 @@ def win_set_enable(self, title: str = '', text: str = '', exclude_title: str = ' 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, @@ -1560,6 +1732,8 @@ def win_set_redraw(self, title: str = '', text: str = '', exclude_title: str = ' 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, @@ -1612,6 +1786,8 @@ def win_set_style(self, style: str, title: str = '', text: str = '', exclude_tit 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, @@ -1665,6 +1841,8 @@ def win_set_ex_style(self, style: str, title: str = '', text: str = '', exclude_ 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, @@ -1718,6 +1896,8 @@ def win_set_region(self, options: str, title: str = '', text: str = '', exclude_ 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, @@ -1771,6 +1951,8 @@ def win_set_transparent(self, transparency: Union[int, Literal['Off']], title: s 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, @@ -1824,6 +2006,8 @@ def win_set_trans_color(self, color: Union[int, str], title: str = '', text: str 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, @@ -1898,6 +2082,8 @@ def image_search(self, image_path: str, upper_bound: Tuple[Union[int, str], Unio 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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[False]) -> FutureResult[Optional[Tuple[int, int]]]: ... @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[True]) -> Optional[Tuple[int, int]]: ... + @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: bool = True) -> Union[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: ... # fmt: on def image_search( self, @@ -2001,6 +2187,8 @@ def win_close(self, title: str = '', *, text: str = '', seconds_to_wait: Optiona 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, diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 57fe9d8f..d880ccfb 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -57,6 +57,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ + Literal['AHKControlClick'], + Literal['AHKControlGetText'], Literal['AHKGetTitleMatchMode'], Literal['AHKGetTitleMatchSpeed'], Literal['AHKSetDetectHiddenWindows'], @@ -420,6 +422,13 @@ def function_call(self, function_name: Literal['AHKSetTitleMatchMode'], args: Op 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] = 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 # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index aeb064c7..321f465f 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -117,28 +117,19 @@ def set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> 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]]: - if blocking: - self._engine.win_set_always_on_top( - toggle=toggle, - title=f'ahk_id {self._ahk_id}', - blocking=True, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return None - else: - resp = self._engine.win_set_always_on_top( - toggle=toggle, - title=f'ahk_id {self._ahk_id}', - blocking=False, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return resp + 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 @@ -147,6 +138,8 @@ def is_always_on_top(self) -> bool: ... 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}'] @@ -166,26 +159,17 @@ def send(self, keys: str) -> None: ... def send(self, keys: str, *, blocking: Literal[False]) -> FutureResult[None]: ... @overload def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + @overload + def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... # fmt: on def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: - if blocking: - self._engine.control_send( - keys=keys, - title=f'ahk_id {self._ahk_id}', - blocking=True, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return None - else: - resp = self._engine.control_send( - keys=keys, - title=f'ahk_id {self._ahk_id}', - blocking=False, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return resp + return self._engine.control_send( + keys=keys, + title=f'ahk_id {self._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) # fmt: off @overload @@ -194,18 +178,13 @@ def get_text(self) -> str: ... 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]]: - if blocking: - resp = self._engine.win_get_text( - title=f'ahk_id {self._ahk_id}', blocking=True, detect_hidden_windows=True, title_match_mode=(1, 'Fast') + return self._engine.win_get_text( + title=f'ahk_id {self._ahk_id}', blocking=blocking, detect_hidden_windows=True, title_match_mode=(1, 'Fast') ) - return resp - else: - nonblocking_resp = self._engine.win_get_text( - title=f'ahk_id {self._ahk_id}', blocking=False, detect_hidden_windows=True, title_match_mode=(1, 'Fast') - ) - return nonblocking_resp class Control: @@ -215,6 +194,21 @@ def __init__(self, window: Window, hwnd: str, control_class: str): self.control_class: str = control_class self._engine = window._engine + # fmt: off + @overload + def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '') -> None: ... + @overload + def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: Literal[True]) -> None: ... + @overload + def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def click( + self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: bool = True + ) -> Union[None, FutureResult[None]]: + raise NotImplementedError + # fmt: off @overload def send(self, keys: str) -> None: ... @@ -222,28 +216,27 @@ def send(self, keys: str) -> None: ... def send(self, keys: str, *, blocking: Literal[False]) -> FutureResult[None]: ... @overload def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + @overload + def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... # fmt: on def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: - if blocking: - self._engine.control_send( + return self._engine.control_send( keys=keys, control=self.control_class, title=f'ahk_id {self.window._ahk_id}', - blocking=True, + blocking=blocking, detect_hidden_windows=True, title_match_mode=(1, 'Fast'), ) - return None - else: - resp = self._engine.control_send( - keys=keys, - control=self.control_class, - title=f'ahk_id {self.window._ahk_id}', - blocking=False, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) - return resp + + def get_text(self, blocking: bool = True) -> Union[str, FutureResult[str]]: + return self._engine.control_get_text( + control=self.control_class, + title=f'ahk_id {self.window._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) 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/daemon.ahk b/ahk/daemon.ahk index 125c5074..335bfbba 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -638,10 +638,11 @@ AHKWinGetText(ByRef command) { WinGetText, output,%title%,%text%,%extitle%,%extext% if (ErrorLevel = 1) { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window text") + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window text") + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) } - response := FormatResponse(STRINGRESPONSEMESSAGE, output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -893,14 +894,15 @@ AHKWinSetStyle(ByRef command) { WinSet, Style, %style%, %title%, %text%, %extitle%, %extext% - DetectHiddenWindows, %current_detect_hw% - SetTitleMatchMode, %current_match_mode% - SetTitleMatchMode, %current_match_speed% if (ErrorLevel = 1) { - return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } else { - return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp } AHKWinSetExStyle(ByRef command) { @@ -930,14 +932,15 @@ AHKWinSetExStyle(ByRef command) { WinSet, ExStyle, %style%, %title%, %text%, %extitle%, %extext% - DetectHiddenWindows, %current_detect_hw% - SetTitleMatchMode, %current_match_mode% - SetTitleMatchMode, %current_match_speed% if (ErrorLevel = 1) { - return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } else { - return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp } AHKWinSetRegion(ByRef command) { @@ -967,14 +970,15 @@ AHKWinSetRegion(ByRef command) { WinSet, Region, %options%, %title%, %text%, %extitle%, %extext% - DetectHiddenWindows, %current_detect_hw% - SetTitleMatchMode, %current_match_mode% - SetTitleMatchMode, %current_match_speed% if (ErrorLevel = 1) { - return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } else { - return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp } AHKWinSetTransparent(ByRef command) { @@ -1472,6 +1476,82 @@ WinSendRaw(ByRef command) { ControlSendRaw,,% keys, %title% } +AHKControlClick(ByRef command) { + ctrl := command[2] + title := command[3] + text := command[4] + button := command[5] + click_count := command[6] + options := command[7] + exclude_title := command[8] + exclude_text := command[9] + detect_hw := command[10] + match_mode := command[11] + match_speed := command[12] + + 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% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKControlGetText(ByRef command) { + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + ControlGetText, result, %ctrl%, %title%, %text%, %extitle%, %extext% + + if (ErrorLevel = 1) { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the text") + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, result) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response + + +} + AHKControlSend(ByRef command) { ctrl := command[2] keys := command[3] @@ -1679,7 +1759,7 @@ Loop { func := commandArray[1] response := %func%(commandArray) } catch e { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, %e%) + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, e) } if (response) { From 2d0c47db817ecb0d961f89dfb83b24abd5f13d63 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 11 Nov 2022 11:38:48 -0800 Subject: [PATCH 276/588] use py38 compatible dict union --- ahk/hotkey.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ahk/hotkey.py b/ahk/hotkey.py index 6b1c3c04..87f83937 100644 --- a/ahk/hotkey.py +++ b/ahk/hotkey.py @@ -64,7 +64,9 @@ def _callback_registry(self) -> Dict[str, Union[Hotkey, Hotstring]]: return self._get_callback_registry() def _callback_registry_uncached(self) -> Dict[str, Union[Hotkey, Hotstring]]: - return self._hotkeys | self._hotstrings + registry: Dict[str, Union[Hotkey, Hotstring]] = dict(self._hotkeys) + registry.update(self._hotstrings) + return registry @abstractmethod def restart(self) -> Any: From f631b5520bb9434b68a71d8ee4fe57d91177b816 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 11 Nov 2022 11:46:41 -0800 Subject: [PATCH 277/588] control click --- ahk/_async/engine.py | 3 +-- ahk/_async/window.py | 10 +++++++++- ahk/_sync/engine.py | 3 +-- ahk/_sync/window.py | 28 ++++++++++++++++++---------- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 1e6a62c6..209dc46e 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -192,8 +192,7 @@ async def control_click( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: - raise NotImplementedError - args = [control, title, text, button, click_count, options, exclude_title, exclude_text] + 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('On') diff --git a/ahk/_async/window.py b/ahk/_async/window.py index f9d48475..ac7bdfe1 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -207,7 +207,15 @@ async def click(self, *, button: Union[int, str] = 1, click_count: int = 1, opti async def click( self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: bool = True ) -> Union[None, AsyncFutureResult[None]]: - raise NotImplementedError + return await self._engine.control_click( + button=button, + control=self.control_class, + options=options, + title=f'ahk_id {self.window._ahk_id}', + title_match_mode=(1, 'Fast'), + detect_hidden_windows=True, + blocking=blocking, + ) # fmt: off @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index dbfec0b7..23df2581 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -191,8 +191,7 @@ def control_click( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: - raise NotImplementedError - args = [control, title, text, button, click_count, options, exclude_title, exclude_text] + 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('On') diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 321f465f..87ad474d 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -183,8 +183,8 @@ 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') - ) + title=f'ahk_id {self._ahk_id}', blocking=blocking, detect_hidden_windows=True, title_match_mode=(1, 'Fast') + ) class Control: @@ -207,7 +207,15 @@ def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: s def click( self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: bool = True ) -> Union[None, FutureResult[None]]: - raise NotImplementedError + return self._engine.control_click( + button=button, + control=self.control_class, + options=options, + title=f'ahk_id {self.window._ahk_id}', + title_match_mode=(1, 'Fast'), + detect_hidden_windows=True, + blocking=blocking + ) # fmt: off @overload @@ -221,13 +229,13 @@ def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[ # fmt: on def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: return self._engine.control_send( - keys=keys, - control=self.control_class, - title=f'ahk_id {self.window._ahk_id}', - blocking=blocking, - detect_hidden_windows=True, - title_match_mode=(1, 'Fast'), - ) + keys=keys, + control=self.control_class, + title=f'ahk_id {self.window._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) def get_text(self, blocking: bool = True) -> Union[str, FutureResult[str]]: return self._engine.control_get_text( From a1b108645f14ac5f6d84f8069919e8f8ca478202 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 11 Nov 2022 15:43:53 -0800 Subject: [PATCH 278/588] fix controlclick button parameter --- ahk/_async/engine.py | 10 +++++----- ahk/_async/window.py | 16 +++++++++++----- ahk/_sync/engine.py | 10 +++++----- ahk/_sync/window.py | 13 +++++++------ ahk/daemon.ahk | 15 ++++++++++----- 5 files changed, 38 insertions(+), 26 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 209dc46e..8742c27c 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -169,18 +169,18 @@ async def get_title_match_speed(self) -> str: # fmt: off @overload - async def control_click(self, *, button: Union[int, str] = 1, 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: ... + 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: Union[int, str] = 1, 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]: ... + 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: Union[int, str] = 1, 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: ... + 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: Union[int, str] = 1, 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]]: ... + 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: Union[int, str] = 1, + button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', control: str = '', diff --git a/ahk/_async/window.py b/ahk/_async/window.py index ac7bdfe1..8d3fd94a 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -196,20 +196,26 @@ def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): # fmt: off @overload - async def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '') -> None: ... + async def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '') -> None: ... @overload - async def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def click(self, *, 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, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: Literal[True]) -> None: ... + async def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: Literal[True]) -> None: ... @overload - async def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + async def click(self, *, 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, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: bool = True + self, + *, + button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', + click_count: int = 1, + options: str = '', + blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: return await self._engine.control_click( button=button, control=self.control_class, + click_count=click_count, options=options, title=f'ahk_id {self.window._ahk_id}', title_match_mode=(1, 'Fast'), diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 23df2581..49963979 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -168,18 +168,18 @@ def get_title_match_speed(self) -> str: # fmt: off @overload - def control_click(self, *, button: Union[int, str] = 1, 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: ... + 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: Union[int, str] = 1, 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]: ... + 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: Union[int, str] = 1, 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: ... + 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: Union[int, str] = 1, 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]]: ... + 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: Union[int, str] = 1, + button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', control: str = '', diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 87ad474d..ca320750 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -196,25 +196,26 @@ def __init__(self, window: Window, hwnd: str, control_class: str): # fmt: off @overload - def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '') -> None: ... + def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '') -> None: ... @overload - def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: Literal[False]) -> FutureResult[None]: ... + def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: Literal[False]) -> FutureResult[None]: ... @overload - def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: Literal[True]) -> None: ... + def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: Literal[True]) -> None: ... @overload - def click(self, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: bool = True) -> Union[None, FutureResult[None]]: ... + def click(self, *, 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, *, button: Union[int, str] = 1, click_count: int = 1, options: str = '', blocking: bool = True + self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: bool = True ) -> Union[None, FutureResult[None]]: return self._engine.control_click( button=button, control=self.control_class, + click_count=click_count, options=options, title=f'ahk_id {self.window._ahk_id}', title_match_mode=(1, 'Fast'), detect_hidden_windows=True, - blocking=blocking + blocking=blocking, ) # fmt: off diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 335bfbba..a713c1ed 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1504,6 +1504,11 @@ AHKControlClick(ByRef command) { } ControlClick, %ctrl%, %title%, %text%, %button%, %click_count%, %options%, %exclude_title%, %exclude_text% + + if (ErrorLevel != 0) { + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Failed to click control") + } + DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -1750,20 +1755,20 @@ CommandArrayFromQuery(ByRef text) { } stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] -response := "" +pyresp := "" Loop { query := RTrim(stdin.ReadLine(), "`n") commandArray := CommandArrayFromQuery(query) try { func := commandArray[1] - response := %func%(commandArray) + pyresp := %func%(commandArray) } catch e { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, e) + pyresp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, e) } - if (response) { - FileAppend, %response%, *, UTF-8 + if (pyresp) { + FileAppend, %pyresp%, *, UTF-8 } else { msg := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("Unknown Error when calling {}", func)) FileAppend, %msg%, *, UTF-8 From a28a4fd62948d81e163a670509034b73378b069b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 11 Nov 2022 15:44:48 -0800 Subject: [PATCH 279/588] fix exception response for control click failures --- ahk/daemon.ahk | 1 + 1 file changed, 1 insertion(+) diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index a713c1ed..6af6059e 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1477,6 +1477,7 @@ WinSendRaw(ByRef command) { } AHKControlClick(ByRef command) { + global EXCEPTIONRESPONSEMESSAGE ctrl := command[2] title := command[3] text := command[4] From 8476f5f9fb61f02d6b4a112a209c14c19dcceedd Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 12 Nov 2022 13:24:03 -0800 Subject: [PATCH 280/588] remove unused imports in utils Co-authored-by: JoShMiQueL <26714939+JoShMiQueL@users.noreply.github.com> --- ahk/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ahk/utils.py b/ahk/utils.py index f2d34a8b..f7bf24d1 100644 --- a/ahk/utils.py +++ b/ahk/utils.py @@ -1,6 +1,4 @@ import logging -import functools -from asyncio import coroutine ESCAPE_SEQUENCE_MAP = { '\n': '`n', From 7988c130c5dde312a7d2953313f74e53cb6244bb Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 12 Nov 2022 13:28:57 -0800 Subject: [PATCH 281/588] 0.14.2 :package: --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 85b56a95..167fc75b 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='ahk', - version='0.14.1', + version='0.14.2', url='https://github.com/spyoungtech/ahk', description='A Python wrapper for AHK', long_description=long_description, From 3a9cc43f7758f5b6fee171d2f255b64170c4a661 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 12 Nov 2022 16:47:17 -0800 Subject: [PATCH 282/588] get control position --- ahk/_async/engine.py | 56 +++++++++++++++++++++++++++++++++++++++++ ahk/_async/transport.py | 3 +++ ahk/_async/window.py | 22 ++++++++++++++++ ahk/_sync/engine.py | 56 +++++++++++++++++++++++++++++++++++++++++ ahk/_sync/transport.py | 3 +++ ahk/_sync/window.py | 27 +++++++++++++++++++- ahk/daemon.ahk | 43 +++++++++++++++++++++++++++++++ 7 files changed, 209 insertions(+), 1 deletion(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 8742c27c..8e0ca038 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -281,6 +281,62 @@ async def control_get_text( 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) -> Tuple[int, int, int, int]: ... + @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[Tuple[int, int, int, int]]: ... + @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]) -> Tuple[int, int, int, int]: ... + @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[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: ... + # 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[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: + args = [control, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 93c81452..c3134ca7 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -65,6 +65,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ + Literal['AHKControlGetPos'], Literal['AHKControlClick'], Literal['AHKControlGetText'], Literal['AHKGetTitleMatchMode'], @@ -446,6 +447,8 @@ async def function_call(self, function_name: Literal['AHKControlGetText'], args: @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[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 8d3fd94a..dbef9f5e 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -4,6 +4,7 @@ from typing import Optional from typing import overload from typing import Sequence +from typing import Tuple from typing import TYPE_CHECKING from typing import Union @@ -252,5 +253,26 @@ async def get_text(self, blocking: bool = True) -> Union[str, AsyncFutureResult[ title_match_mode=(1, 'Fast'), ) + # fmt: off + @overload + async def get_position(self) -> Tuple[int, int, int, int]: ... + @overload + async def get_position(self, blocking: Literal[False]) -> AsyncFutureResult[Tuple[int, int, int, int]]: ... + @overload + async def get_position(self, blocking: Literal[True]) -> Tuple[int, int, int, int]: ... + @overload + async def get_position(self, blocking: bool = True) -> Union[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: ... + # fmt: on + async def get_position( + self, blocking: bool = True + ) -> Union[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: + return await self._engine.control_get_position( + control=self.control_class, + title=f'ahk_id {self.window._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast'), + ) + 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/_sync/engine.py b/ahk/_sync/engine.py index 49963979..eb41ce29 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -280,6 +280,62 @@ def control_get_text( 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) -> Tuple[int, int, int, int]: ... + @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[Tuple[int, int, int, int]]: ... + @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]) -> Tuple[int, int, int, int]: ... + @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[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: ... + # 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[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: + args = [control, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index d880ccfb..525db440 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -57,6 +57,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ + Literal['AHKControlGetPos'], Literal['AHKControlClick'], Literal['AHKControlGetText'], Literal['AHKGetTitleMatchMode'], @@ -429,6 +430,8 @@ def function_call(self, function_name: Literal['AHKControlGetText'], args: Optio @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[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index ca320750..decd170f 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -4,6 +4,7 @@ from typing import Optional from typing import overload from typing import Sequence +from typing import Tuple from typing import TYPE_CHECKING from typing import Union @@ -205,7 +206,12 @@ def click(self, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = ' def click(self, *, 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, *, button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', click_count: int = 1, options: str = '', blocking: bool = True + self, + *, + button: Literal['L', 'R', 'M', 'LEFT', 'RIGHT', 'MIDDLE'] = 'L', + click_count: int = 1, + options: str = '', + blocking: bool = True, ) -> Union[None, FutureResult[None]]: return self._engine.control_click( button=button, @@ -247,5 +253,24 @@ def get_text(self, blocking: bool = True) -> Union[str, FutureResult[str]]: title_match_mode=(1, 'Fast'), ) + # fmt: off + @overload + def get_position(self) -> Tuple[int, int, int, int]: ... + @overload + def get_position(self, blocking: Literal[False]) -> FutureResult[Tuple[int, int, int, int]]: ... + @overload + def get_position(self, blocking: Literal[True]) -> Tuple[int, int, int, int]: ... + @overload + def get_position(self, blocking: bool = True) -> Union[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: ... + # fmt: on + def get_position(self, blocking: bool = True) -> Union[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: + return self._engine.control_get_position( + control=self.control_class, + title=f'ahk_id {self.window._ahk_id}', + blocking=blocking, + detect_hidden_windows=True, + title_match_mode=(1, 'Fast') + ) + 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/daemon.ahk b/ahk/daemon.ahk index 6af6059e..098b70c5 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1554,6 +1554,49 @@ AHKControlGetText(ByRef command) { SetTitleMatchMode, %current_match_speed% return response +} + + +AHKControlGetPos(ByRef command) { + global TUPLERESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + ControlGetPos, x, y, w, h, %ctrl%, %title%, %text%, %extitle%, %extext% + + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + + if (ErrorLevel = 1) { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the text") + } else { + response := FormatResponse(TUPLERESPONSEMESSAGE, result) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response } From 11dcc0c03614f760a48a005261bb60ea07dc7562 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 14 Nov 2022 13:48:16 -0800 Subject: [PATCH 283/588] get active window --- ahk/_async/engine.py | 18 ++++++++++++++++-- ahk/_sync/engine.py | 12 ++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 8e0ca038..b3d26b9b 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -545,8 +545,22 @@ async def mouse_move( async def a_run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() - async def get_active_window(self) -> Union[AsyncWindow, None]: - raise NotImplementedError() + # 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, blocking: bool = True + ) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: + return await self.win_get( + title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking + ) async def find_windows( self, func: Optional[Callable[[AsyncWindow], bool]] = None, **kwargs: Any diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index eb41ce29..5918c523 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -544,8 +544,16 @@ def mouse_move( def a_run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() - def get_active_window(self) -> Union[Window, None]: - raise NotImplementedError() + @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]]]: ... + def get_active_window(self, blocking: bool = True) -> Union[Optional[Window], FutureResult[Optional[Window]]]: + return self.win_get(title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking) def find_windows( self, func: Optional[Callable[[Window], bool]] = None, **kwargs: Any From ee5c18540c543977d5f44e84668df047375c61d0 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 14 Nov 2022 14:10:23 -0800 Subject: [PATCH 284/588] cleanup function names --- ahk/_async/engine.py | 10 +-- ahk/_async/transport.py | 161 ++++++++++++++++++++-------------------- ahk/_sync/engine.py | 20 +++-- ahk/_sync/transport.py | 161 ++++++++++++++++++++-------------------- ahk/daemon.ahk | 10 +-- 5 files changed, 183 insertions(+), 179 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index b3d26b9b..2467c82a 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -488,7 +488,7 @@ async def list_windows( else: args.append('') args.append('') - resp = await self._transport.function_call('WindowList', args, engine=self, blocking=blocking) + resp = await self._transport.function_call('AHKWindowList', args, engine=self, blocking=blocking) return resp # fmt: off @@ -504,7 +504,7 @@ async def get_mouse_position(self, *, blocking: bool = True) -> Union[Tuple[int, async def get_mouse_position( self, *, blocking: bool = True ) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: - resp = await self._transport.function_call('MouseGetPos', blocking=blocking) + resp = await self._transport.function_call('AHKMouseGetPos', blocking=blocking) return resp # fmt: off @@ -539,7 +539,7 @@ async def mouse_move( args = [str(x), str(y), str(speed)] if relative: args.append('R') - resp = await self._transport.function_call('MouseMove', args, blocking=blocking) + resp = await self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp async def a_run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: @@ -705,7 +705,7 @@ async def key_wait( if options: args.append(options) - resp = await self._transport.function_call('KeyWait', args) + resp = await self._transport.function_call('AHKKeyWait', args) return resp # async def mouse_position(self): @@ -2204,7 +2204,7 @@ async def image_search( args.append(s) else: args.append(image_path) - resp = await self._transport.function_call('ImageSearch', args, blocking=blocking) + resp = await self._transport.function_call('AHKImageSearch', args, blocking=blocking) return resp async def mouse_drag( diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index c3134ca7..6cb58b8e 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -65,82 +65,81 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ - Literal['AHKControlGetPos'], - Literal['AHKControlClick'], - Literal['AHKControlGetText'], - Literal['AHKGetTitleMatchMode'], - Literal['AHKGetTitleMatchSpeed'], - Literal['AHKSetDetectHiddenWindows'], - Literal['AHKSetTitleMatchMode'], - Literal['AHKWinSetTitle'], - Literal['AHKWinExist'], - Literal['ImageSearch'], - Literal['PixelGetColor'], - Literal['PixelSearch'], - Literal['MouseGetPos'], - Literal['AHKKeyState'], - Literal['MouseMove'], - Literal['CoordMode'], - Literal['Click'], - Literal['MouseClickDrag'], - Literal['KeyWait'], - Literal['SetKeyDelay'], - Literal['AHKSend'], - Literal['AHKSendRaw'], - Literal['AHKSendInput'], - Literal['AHKSendEvent'], - Literal['AHKSendPlay'], - Literal['SetCapsLockState'], - Literal['AHKWinGetTitle'], - Literal['WinGetClass'], - Literal['AHKWinGetText'], - Literal['WinActivate'], - Literal['WinActivateBottom'], - Literal['AHKWinClose'], - Literal['WinHide'], - Literal['WinKill'], - Literal['WinMaximize'], - Literal['WinMinimize'], - Literal['WinRestore'], - Literal['WinShow'], - Literal['WindowList'], - Literal['WinSend'], - Literal['WinSendRaw'], - Literal['AHKControlSend'], - Literal['FromMouse'], - Literal['WinGet'], - Literal['WinSet'], - Literal['AHKWinSetAlwaysOnTop'], - Literal['AHKWinIsAlwaysOnTop'], - Literal['AHKWinSetTop'], - Literal['AHKWinSetBottom'], - Literal['AHKWinSetDisable'], - Literal['AHKWinSetEnable'], - Literal['AHKWinSetRedraw'], - Literal['WinSetTitle'], - Literal['AHKWinSetTransparent'], - Literal['AHKWinSetTransColor'], - Literal['AHKWinSetStyle'], - Literal['AHKWinSetExStyle'], - Literal['AHKWinSetRegion'], - Literal['WinIsAlwaysOnTop'], - Literal['WinClick'], - Literal['AHKWinMove'], - Literal['AHKWinGetPos'], - Literal['AHKWinGetID'], - Literal['AHKWinGetIDLast'], - Literal['AHKWinGetPID'], - Literal['AHKWinGetProcessName'], - Literal['AHKWinGetProcessPath'], - Literal['AHKWinGetCount'], - Literal['AHKWinGetList'], - Literal['AHKWinGetMinMax'], - Literal['AHKWinGetControlList'], - Literal['AHKWinGetControlListHwnd'], - Literal['AHKWinGetTransparent'], - Literal['AHKWinGetTransColor'], - Literal['AHKWinGetStyle'], - Literal['AHKWinGetExStyle'], + 'AHKControlClick', + 'AHKControlGetPos', + 'AHKControlGetText', + 'AHKControlSend', + 'AHKGetTitleMatchMode', + 'AHKGetTitleMatchSpeed', + 'AHKImageSearch', + 'AHKKeyState', + 'AHKKeyWait', + 'AHKMouseGetPos', + 'AHKMouseMove', + 'AHKSend', + 'AHKSendEvent', + 'AHKSendInput', + 'AHKSendPlay', + 'AHKSendRaw', + 'AHKSetDetectHiddenWindows', + 'AHKSetTitleMatchMode', + 'AHKWinClose', + 'AHKWinExist', + 'AHKWinGetControlList', + 'AHKWinGetControlListHwnd', + 'AHKWinGetCount', + 'AHKWinGetExStyle', + 'AHKWinGetID', + 'AHKWinGetIDLast', + 'AHKWinGetList', + 'AHKWinGetMinMax', + 'AHKWinGetPID', + 'AHKWinGetPos', + 'AHKWinGetProcessName', + 'AHKWinGetProcessPath', + 'AHKWinGetStyle', + 'AHKWinGetText', + 'AHKWinGetTitle', + 'AHKWinGetTransColor', + 'AHKWinGetTransparent', + 'AHKWinIsAlwaysOnTop', + 'AHKWinMove', + 'AHKWinSetAlwaysOnTop', + 'AHKWinSetBottom', + 'AHKWinSetDisable', + 'AHKWinSetEnable', + 'AHKWinSetExStyle', + 'AHKWinSetRedraw', + 'AHKWinSetRegion', + 'AHKWinSetStyle', + 'AHKWinSetTitle', + 'AHKWinSetTop', + 'AHKWinSetTransColor', + 'AHKWinSetTransparent', + 'AHKWindowList', + 'Click', + 'CoordMode', + 'FromMouse', + 'MouseClickDrag', + 'PixelGetColor', + 'PixelSearch', + 'SetCapsLockState', + 'SetKeyDelay', + 'WinActivate', + 'WinActivateBottom', + 'WinClick', + 'WinGet', + 'WinGetClass', + 'WinHide', + 'WinKill', + 'WinMaximize', + 'WinMinimize', + 'WinRestore', + 'WinSend', + 'WinSendRaw', + 'WinSet', + 'WinSetTitle', + 'WinShow', ] @@ -296,17 +295,17 @@ async def init(self) -> None: @overload async def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[bool, AsyncFutureResult[bool]]: ... @overload - async def function_call(self, function_name: Literal['ImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], None, AsyncFutureResult[Union[Tuple[int, int], None]]]: ... + async def function_call(self, function_name: Literal['AHKImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], None, AsyncFutureResult[Union[Tuple[int, int], None]]]: ... @overload async def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload async def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... @overload - async def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... + async def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... @overload async def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[bool, AsyncFutureResult[bool]]: ... @overload - async def function_call(self, function_name: Literal['MouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKMouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload async def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload @@ -314,7 +313,7 @@ async def function_call(self, function_name: Literal['Click'], args: Optional[Li @overload async def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[int, AsyncFutureResult[int]]: ... + async def function_call(self, function_name: Literal['AHKKeyWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[int, AsyncFutureResult[int]]: ... @overload async def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload @@ -354,7 +353,7 @@ async def function_call(self, function_name: Literal['WinRestore'], args: Option @overload async def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + async def function_call(self, function_name: Literal['AHKWindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... @overload async def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 5918c523..f60a2a6e 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -487,7 +487,7 @@ def list_windows( else: args.append('') args.append('') - resp = self._transport.function_call('WindowList', args, engine=self, blocking=blocking) + resp = self._transport.function_call('AHKWindowList', args, engine=self, blocking=blocking) return resp # fmt: off @@ -503,7 +503,7 @@ def get_mouse_position(self, *, blocking: bool = True) -> Union[Tuple[int, int], def get_mouse_position( self, *, blocking: bool = True ) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: - resp = self._transport.function_call('MouseGetPos', blocking=blocking) + resp = self._transport.function_call('AHKMouseGetPos', blocking=blocking) return resp # fmt: off @@ -538,12 +538,13 @@ def mouse_move( args = [str(x), str(y), str(speed)] if relative: args.append('R') - resp = self._transport.function_call('MouseMove', args, blocking=blocking) + resp = self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp def a_run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() + # fmt: off @overload def get_active_window(self) -> Optional[Window]: ... @overload @@ -552,8 +553,13 @@ def get_active_window(self, blocking: Literal[True]) -> Optional[Window]: ... 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]]]: ... - def get_active_window(self, blocking: bool = True) -> Union[Optional[Window], FutureResult[Optional[Window]]]: - return self.win_get(title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking) + # fmt: on + def get_active_window( + self, blocking: bool = True + ) -> Union[Optional[Window], FutureResult[Optional[Window]]]: + return self.win_get( + title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking + ) def find_windows( self, func: Optional[Callable[[Window], bool]] = None, **kwargs: Any @@ -698,7 +704,7 @@ def key_wait( if options: args.append(options) - resp = self._transport.function_call('KeyWait', args) + resp = self._transport.function_call('AHKKeyWait', args) return resp # async def mouse_position(self): @@ -2197,7 +2203,7 @@ def image_search( args.append(s) else: args.append(image_path) - resp = self._transport.function_call('ImageSearch', args, blocking=blocking) + resp = self._transport.function_call('AHKImageSearch', args, blocking=blocking) return resp def mouse_drag( diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 525db440..4ff12d10 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -57,82 +57,81 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ - Literal['AHKControlGetPos'], - Literal['AHKControlClick'], - Literal['AHKControlGetText'], - Literal['AHKGetTitleMatchMode'], - Literal['AHKGetTitleMatchSpeed'], - Literal['AHKSetDetectHiddenWindows'], - Literal['AHKSetTitleMatchMode'], - Literal['AHKWinSetTitle'], - Literal['AHKWinExist'], - Literal['ImageSearch'], - Literal['PixelGetColor'], - Literal['PixelSearch'], - Literal['MouseGetPos'], - Literal['AHKKeyState'], - Literal['MouseMove'], - Literal['CoordMode'], - Literal['Click'], - Literal['MouseClickDrag'], - Literal['KeyWait'], - Literal['SetKeyDelay'], - Literal['AHKSend'], - Literal['AHKSendRaw'], - Literal['AHKSendInput'], - Literal['AHKSendEvent'], - Literal['AHKSendPlay'], - Literal['SetCapsLockState'], - Literal['AHKWinGetTitle'], - Literal['WinGetClass'], - Literal['AHKWinGetText'], - Literal['WinActivate'], - Literal['WinActivateBottom'], - Literal['AHKWinClose'], - Literal['WinHide'], - Literal['WinKill'], - Literal['WinMaximize'], - Literal['WinMinimize'], - Literal['WinRestore'], - Literal['WinShow'], - Literal['WindowList'], - Literal['WinSend'], - Literal['WinSendRaw'], - Literal['AHKControlSend'], - Literal['FromMouse'], - Literal['WinGet'], - Literal['WinSet'], - Literal['AHKWinSetAlwaysOnTop'], - Literal['AHKWinIsAlwaysOnTop'], - Literal['AHKWinSetTop'], - Literal['AHKWinSetBottom'], - Literal['AHKWinSetDisable'], - Literal['AHKWinSetEnable'], - Literal['AHKWinSetRedraw'], - Literal['WinSetTitle'], - Literal['AHKWinSetTransparent'], - Literal['AHKWinSetTransColor'], - Literal['AHKWinSetStyle'], - Literal['AHKWinSetExStyle'], - Literal['AHKWinSetRegion'], - Literal['WinIsAlwaysOnTop'], - Literal['WinClick'], - Literal['AHKWinMove'], - Literal['AHKWinGetPos'], - Literal['AHKWinGetID'], - Literal['AHKWinGetIDLast'], - Literal['AHKWinGetPID'], - Literal['AHKWinGetProcessName'], - Literal['AHKWinGetProcessPath'], - Literal['AHKWinGetCount'], - Literal['AHKWinGetList'], - Literal['AHKWinGetMinMax'], - Literal['AHKWinGetControlList'], - Literal['AHKWinGetControlListHwnd'], - Literal['AHKWinGetTransparent'], - Literal['AHKWinGetTransColor'], - Literal['AHKWinGetStyle'], - Literal['AHKWinGetExStyle'], + 'AHKControlClick', + 'AHKControlGetPos', + 'AHKControlGetText', + 'AHKControlSend', + 'AHKGetTitleMatchMode', + 'AHKGetTitleMatchSpeed', + 'AHKImageSearch', + 'AHKKeyState', + 'AHKKeyWait', + 'AHKMouseGetPos', + 'AHKMouseMove', + 'AHKSend', + 'AHKSendEvent', + 'AHKSendInput', + 'AHKSendPlay', + 'AHKSendRaw', + 'AHKSetDetectHiddenWindows', + 'AHKSetTitleMatchMode', + 'AHKWinClose', + 'AHKWinExist', + 'AHKWinGetControlList', + 'AHKWinGetControlListHwnd', + 'AHKWinGetCount', + 'AHKWinGetExStyle', + 'AHKWinGetID', + 'AHKWinGetIDLast', + 'AHKWinGetList', + 'AHKWinGetMinMax', + 'AHKWinGetPID', + 'AHKWinGetPos', + 'AHKWinGetProcessName', + 'AHKWinGetProcessPath', + 'AHKWinGetStyle', + 'AHKWinGetText', + 'AHKWinGetTitle', + 'AHKWinGetTransColor', + 'AHKWinGetTransparent', + 'AHKWinIsAlwaysOnTop', + 'AHKWinMove', + 'AHKWinSetAlwaysOnTop', + 'AHKWinSetBottom', + 'AHKWinSetDisable', + 'AHKWinSetEnable', + 'AHKWinSetExStyle', + 'AHKWinSetRedraw', + 'AHKWinSetRegion', + 'AHKWinSetStyle', + 'AHKWinSetTitle', + 'AHKWinSetTop', + 'AHKWinSetTransColor', + 'AHKWinSetTransparent', + 'AHKWindowList', + 'Click', + 'CoordMode', + 'FromMouse', + 'MouseClickDrag', + 'PixelGetColor', + 'PixelSearch', + 'SetCapsLockState', + 'SetKeyDelay', + 'WinActivate', + 'WinActivateBottom', + 'WinClick', + 'WinGet', + 'WinGetClass', + 'WinHide', + 'WinKill', + 'WinMaximize', + 'WinMinimize', + 'WinRestore', + 'WinSend', + 'WinSendRaw', + 'WinSet', + 'WinSetTitle', + 'WinShow', ] @@ -279,17 +278,17 @@ def init(self) -> None: @overload def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, FutureResult[bool]]: ... @overload - def function_call(self, function_name: Literal['ImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], None, FutureResult[Union[Tuple[int, int], None]]]: ... + def function_call(self, function_name: Literal['AHKImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], None, FutureResult[Union[Tuple[int, int], None]]]: ... @overload def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... @overload def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... @overload - def function_call(self, function_name: Literal['MouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... + def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... @overload def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, FutureResult[bool]]: ... @overload - def function_call(self, function_name: Literal['MouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKMouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload @@ -297,7 +296,7 @@ def function_call(self, function_name: Literal['Click'], args: Optional[List[str @overload def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['KeyWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[int, FutureResult[int]]: ... + def function_call(self, function_name: Literal['AHKKeyWait'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[int, FutureResult[int]]: ... @overload def function_call(self, function_name: Literal['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload @@ -337,7 +336,7 @@ def function_call(self, function_name: Literal['WinRestore'], args: Optional[Lis @overload def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[Window], FutureResult[List[Window]]]: ... + def function_call(self, function_name: Literal['AHKWindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[Window], FutureResult[List[Window]]]: ... @overload def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 098b70c5..f82a638f 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1044,7 +1044,7 @@ AHKWinSetTransColor(ByRef command) { return FormatNoValueResponse() } -ImageSearch(ByRef command) { +AHKImageSearch(ByRef command) { global COORDINATERESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE imagepath := command[6] @@ -1105,7 +1105,7 @@ PixelSearch(ByRef command) { } -MouseGetPos(ByRef command) { +AHKMouseGetPos(ByRef command) { global COORDINATERESPONSEMESSAGE MouseGetPos, xpos, ypos payload := Format("({}, {})", xpos, ypos) @@ -1129,7 +1129,7 @@ AHKKeyState(ByRef command) { } } -MouseMove(ByRef command) { +AHKMouseMove(ByRef command) { x := command[2] y := command[3] speed := command[4] @@ -1206,7 +1206,7 @@ RegDelete(ByRef command) { RegDelete, %keyname%, command[3] } -KeyWait(ByRef command) { +AHKKeyWait(ByRef command) { global INTEGERRESPONSEMESSAGE keyname := command[2] if (command.Length() = 2) { @@ -1423,7 +1423,7 @@ WinWaitClose(ByRef command) { } -WindowList(ByRef command) { +AHKWindowList(ByRef command) { global WINDOWIDLISTRESPONSEMESSAGE current_detect_hw := Format("{}", A_DetectHiddenWindows) From 7b97d1e5bdd4547b1821c8ad5180b10a09485ac5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 22:54:51 +0000 Subject: [PATCH 285/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.6.0 → 22.10.0](https://github.com/psf/black/compare/22.6.0...22.10.0) - [github.com/asottile/reorder_python_imports: v3.8.2 → v3.9.0](https://github.com/asottile/reorder_python_imports/compare/v3.8.2...v3.9.0) - [github.com/pre-commit/mirrors-mypy: v0.971 → v0.990](https://github.com/pre-commit/mirrors-mypy/compare/v0.971...v0.990) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8bbe9576..762401f4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '22.6.0' + rev: '22.10.0' hooks: - id: black args: @@ -33,12 +33,12 @@ repos: - "120" exclude: ^(ahk/_sync/.*\.py) - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.2 + rev: v3.9.0 hooks: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.971' + rev: 'v0.990' hooks: - id: mypy args: From 1af0da04465efb43cb729ff6336076611385a399 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 14 Nov 2022 14:56:28 -0800 Subject: [PATCH 286/588] add click --- ahk/_async/engine.py | 85 +++++++++++++++++++++++++++++++++++++---- ahk/_async/transport.py | 4 +- ahk/_sync/engine.py | 85 +++++++++++++++++++++++++++++++++++++---- ahk/_sync/transport.py | 4 +- ahk/daemon.ahk | 35 +++++++++-------- 5 files changed, 179 insertions(+), 34 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 2467c82a..43e9e4b1 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -39,7 +39,7 @@ CoordModeTargets: TypeAlias = Union[ Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu'] ] -CoordModeRelativeTo: TypeAlias = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] +CoordModeRelativeTo: TypeAlias = Union[Literal['Screen', 'Relative', 'Window', 'Client', '']] CoordMode: TypeAlias = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] @@ -50,6 +50,60 @@ 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', + ], +] + + +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 + class AsyncAHK: def __init__( @@ -2133,17 +2187,34 @@ async def right_click(self, *args: Any, **kwargs: Any) -> Union[None, AsyncFutur async def click( self, - x: Optional[int] = None, + x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, *, - button: Optional[str] = None, - n: Optional[int] = None, - direction: Optional[str] = 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, - mode: Optional[CoordMode] = None, + coord_mode: Optional[CoordModeRelativeTo] = None, ) -> Union[None, AsyncFutureResult[None]]: - raise NotImplementedError() + 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 = '' + args = [str(x), str(y), button, str(click_count), direction or '', r, coord_mode] + + return await self._transport.function_call('AHKClick', args=args, blocking=blocking) # fmt: off @overload diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 6cb58b8e..bb8b1ded 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -117,7 +117,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinSetTransColor', 'AHKWinSetTransparent', 'AHKWindowList', - 'Click', + 'AHKClick', 'CoordMode', 'FromMouse', 'MouseClickDrag', @@ -309,7 +309,7 @@ async def function_call(self, function_name: Literal['AHKMouseMove'], args: Opti @overload async def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['Click'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload async def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index f60a2a6e..d566218e 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -38,7 +38,7 @@ CoordModeTargets: TypeAlias = Union[ Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu'] ] -CoordModeRelativeTo: TypeAlias = Union[Literal['Screen'], Literal['Relative'], Literal['Window'], Literal['Client']] +CoordModeRelativeTo: TypeAlias = Union[Literal['Screen', 'Relative', 'Window', 'Client', '']] CoordMode: TypeAlias = Union[CoordModeTargets, Tuple[CoordModeTargets, CoordModeRelativeTo]] @@ -49,6 +49,60 @@ 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', + ], +] + + +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 + class AHK: def __init__( @@ -2132,17 +2186,34 @@ def right_click(self, *args: Any, **kwargs: Any) -> Union[None, FutureResult[Non def click( self, - x: Optional[int] = None, + x: Optional[Union[int, Tuple[int, int]]] = None, y: Optional[int] = None, *, - button: Optional[str] = None, - n: Optional[int] = None, - direction: Optional[str] = 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, - mode: Optional[CoordMode] = None, + coord_mode: Optional[CoordModeRelativeTo] = None, ) -> Union[None, FutureResult[None]]: - raise NotImplementedError() + 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 = '' + args = [str(x), str(y), button, str(click_count), direction or '', r, coord_mode] + + return self._transport.function_call('AHKClick', args=args, blocking=blocking) # fmt: off @overload diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 4ff12d10..ac174509 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -109,7 +109,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinSetTransColor', 'AHKWinSetTransparent', 'AHKWindowList', - 'Click', + 'AHKClick', 'CoordMode', 'FromMouse', 'MouseClickDrag', @@ -292,7 +292,7 @@ def function_call(self, function_name: Literal['AHKMouseMove'], args: Optional[L @overload def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['Click'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index f82a638f..f3687b38 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1152,23 +1152,26 @@ CoordMode(ByRef command) { } -Click(ByRef command) { - if (command.Length() = 1) { - Click - } else if (command.Length() = 2) { - Click, command[2] - } else if (command.Length() = 3) { - Click, command[2], command[3] - } else if (command.Length() = 4) { - Click, command[2], command[3], command[4] - } else if (command.Length() = 5) { - Click, command[2], command[3], command[4], command[5] - } else if (command.Length() = 6) { - Click, command[2], command[3], command[4], command[5], command[6] - } else if (command.Length() = 7) { - Click, command[2], command[3], command[4], command[5], command[6], command[7] +AHKClick(ByRef command) { + x := command[2] + y := command[3] + button := command[4] + direction := command[5] + r := command[6] + relative_to := command[7] + current_coord_rel := Format("{}", A_CoordModeMouse) + + if (relative_to != current_coord_rel) { + CoordMode, Mouse, %relative_to% + } + + Click, %x%, %y%, %button%, %direction%, %r% + + if (relative_to != current_coord_rel) { + CoordMode, Mouse, %current_coord_rel% } - return + + return FormatNoValueResponse() } From a43ccad9c98655c2323dda11e7b27246e064424e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 14 Nov 2022 15:01:28 -0800 Subject: [PATCH 287/588] fix coord mode for click --- ahk/daemon.ahk | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index f3687b38..d936ea43 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1156,18 +1156,19 @@ AHKClick(ByRef command) { x := command[2] y := command[3] button := command[4] - direction := command[5] - r := command[6] - relative_to := command[7] + click_count := command[5] + direction := command[6] + r := command[7] + relative_to := command[8] current_coord_rel := Format("{}", A_CoordModeMouse) - if (relative_to != current_coord_rel) { + if (relative_to != "") { CoordMode, Mouse, %relative_to% } Click, %x%, %y%, %button%, %direction%, %r% - if (relative_to != current_coord_rel) { + if (relative_to != "") { CoordMode, Mouse, %current_coord_rel% } From f930975978fced39c40476ecaac29756c6e922c4 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 14 Nov 2022 15:19:27 -0800 Subject: [PATCH 288/588] add right_click --- ahk/_async/engine.py | 49 +++++++++++++++++++++++++++++++++++++++----- ahk/_sync/engine.py | 30 ++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 43e9e4b1..d5b4d396 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2181,10 +2181,49 @@ async def win_set_trans_color( # alias for backwards compatibility windows = list_windows - async def right_click(self, *args: Any, **kwargs: Any) -> Union[None, AsyncFutureResult[None]]: - kwargs['button'] = 2 - return await self.click(*args, **kwargs) + # 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) -> 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) -> 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) -> 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) -> 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, + ) -> 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, + ) + # 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) -> 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) -> 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) -> 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) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on async def click( self, x: Optional[Union[int, Tuple[int, int]]] = None, @@ -2213,8 +2252,8 @@ async def click( if coord_mode is None: coord_mode = '' args = [str(x), str(y), button, str(click_count), direction or '', r, coord_mode] - - return await self._transport.function_call('AHKClick', args=args, blocking=blocking) + resp = await self._transport.function_call('AHKClick', args, blocking=blocking) + return resp # fmt: off @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index d566218e..ec1d9ba8 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2180,10 +2180,30 @@ def win_set_trans_color( # alias for backwards compatibility windows = list_windows - def right_click(self, *args: Any, **kwargs: Any) -> Union[None, FutureResult[None]]: - kwargs['button'] = 2 - return self.click(*args, **kwargs) + # 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) -> 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) -> 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) -> 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) -> 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) -> 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) + # 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) -> 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) -> 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) -> 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) -> Union[None, FutureResult[None]]: ... + # fmt: on def click( self, x: Optional[Union[int, Tuple[int, int]]] = None, @@ -2212,8 +2232,8 @@ def click( if coord_mode is None: coord_mode = '' args = [str(x), str(y), button, str(click_count), direction or '', r, coord_mode] - - return self._transport.function_call('AHKClick', args=args, blocking=blocking) + resp = self._transport.function_call('AHKClick', args, blocking=blocking) + return resp # fmt: off @overload From d75dcdc8b5e00bad1949d43c908ae5c77e31340f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 14 Nov 2022 16:46:02 -0800 Subject: [PATCH 289/588] set/get coord mode --- ahk/_async/engine.py | 38 +++++++------------------ ahk/_async/transport.py | 9 ++++++ ahk/_sync/engine.py | 61 +++++++++++++++++++++-------------------- ahk/_sync/transport.py | 9 ++++++ ahk/daemon.ahk | 31 +++++++++++++++++++++ 5 files changed, 90 insertions(+), 58 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index d5b4d396..3281bf26 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -221,6 +221,16 @@ async def get_title_match_speed(self) -> str: resp = await self._transport.function_call('AHKGetTitleMatchSpeed') return resp + async def set_coord_mode(self, target: CoordModeTargets, relative_to: CoordModeRelativeTo = 'Screen') -> None: + args = [str(target), str(relative_to)] + await self._transport.function_call('AHKSetCoordMode', args) + return None + + async def get_coord_mode(self, target: CoordModeTargets) -> str: + args = [str(target)] + resp = await self._transport.function_call('AHKGetCoordMode', args) + 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: ... @@ -762,34 +772,6 @@ async def key_wait( resp = await self._transport.function_call('AHKKeyWait', args) return resp - # async def mouse_position(self): - # raise NotImplementedError() - - async def mouse_wheel( - self, - direction: Union[ - Literal['up'], Literal['down'], Literal['UP'], Literal['DOWN'], Literal['Up'], Literal['Down'] - ], - *args: Any, - **kwargs: Any, - ) -> None: - raise NotImplementedError() - - # async def reg_delete(self, key_name: str, value_name: str = '') -> None: - # raise NotImplementedError() - # - # async def reg_loop(self, reg: str, key_name: str, mode=''): - # raise NotImplementedError() - # - # async def reg_read(self, key_name: str, value_name='') -> str: - # raise NotImplementedError() - # - # async def reg_set_view(self, reg_view: int) -> None: - # raise NotImplementedError() - # - # async def reg_write(self, value_type: str, key_name: str, value_name='') -> None: - # raise NotImplementedError() - async def run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index bb8b1ded..a20a8209 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -69,6 +69,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKControlGetPos', 'AHKControlGetText', 'AHKControlSend', + 'AHKGetCoordMode', 'AHKGetTitleMatchMode', 'AHKGetTitleMatchSpeed', 'AHKImageSearch', @@ -82,6 +83,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSendPlay', 'AHKSendRaw', 'AHKSetDetectHiddenWindows', + 'AHKSetCoordMode', 'AHKSetTitleMatchMode', 'AHKWinClose', 'AHKWinExist', @@ -448,6 +450,13 @@ async def function_call(self, function_name: Literal['AHKControlClick'], args: O @overload async def function_call(self, function_name: Literal['AHKControlGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: ... + + @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['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index ec1d9ba8..dcb13fe9 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -220,6 +220,16 @@ def get_title_match_speed(self) -> str: resp = self._transport.function_call('AHKGetTitleMatchSpeed') return resp + def set_coord_mode(self, target: CoordModeTargets, relative_to: CoordModeRelativeTo = 'Screen') -> None: + args = [str(target), str(relative_to)] + self._transport.function_call('AHKSetCoordMode', args) + return None + + def get_coord_mode(self, target: CoordModeTargets) -> str: + args = [str(target)] + resp = self._transport.function_call('AHKGetCoordMode', args) + 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: ... @@ -761,34 +771,6 @@ def key_wait( resp = self._transport.function_call('AHKKeyWait', args) return resp - # async def mouse_position(self): - # raise NotImplementedError() - - def mouse_wheel( - self, - direction: Union[ - Literal['up'], Literal['down'], Literal['UP'], Literal['DOWN'], Literal['Up'], Literal['Down'] - ], - *args: Any, - **kwargs: Any, - ) -> None: - raise NotImplementedError() - - # async def reg_delete(self, key_name: str, value_name: str = '') -> None: - # raise NotImplementedError() - # - # async def reg_loop(self, reg: str, key_name: str, mode=''): - # raise NotImplementedError() - # - # async def reg_read(self, key_name: str, value_name='') -> str: - # raise NotImplementedError() - # - # async def reg_set_view(self, reg_view: int) -> None: - # raise NotImplementedError() - # - # async def reg_write(self, value_type: str, key_name: str, value_name='') -> None: - # raise NotImplementedError() - def run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() @@ -2190,9 +2172,28 @@ def right_click(self, x: Optional[Union[int, Tuple[int, int]]] = None, y: Option @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) -> 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) -> Union[None, FutureResult[None]]: + 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, + ) -> 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) + return self.click( + x, + y, + button=button, + click_count=click_count, + direction=direction, + relative=relative, + blocking=blocking, + coord_mode=coord_mode, + ) # fmt: off @overload diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index ac174509..64afb4df 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -61,6 +61,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKControlGetPos', 'AHKControlGetText', 'AHKControlSend', + 'AHKGetCoordMode', 'AHKGetTitleMatchMode', 'AHKGetTitleMatchSpeed', 'AHKImageSearch', @@ -74,6 +75,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSendPlay', 'AHKSendRaw', 'AHKSetDetectHiddenWindows', + 'AHKSetCoordMode', 'AHKSetTitleMatchMode', 'AHKWinClose', 'AHKWinExist', @@ -431,6 +433,13 @@ def function_call(self, function_name: Literal['AHKControlClick'], args: Optiona @overload def function_call(self, function_name: Literal['AHKControlGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: ... + + @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 # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index d936ea43..8dfdab43 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1176,6 +1176,37 @@ AHKClick(ByRef command) { } +AHKGetCoordMode(ByRef command) { + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + target := command[2] + + if (target = "ToolTip") { + return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeToolTip) + } + if (target = "Pixel") { + return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModePixel) + } + if (target = "Mouse") { + return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeMouse) + } + if (target = "Caret") { + return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeCaret) + } + if (target = "Menu") { + return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeMenu) + } + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Invalid coord mode") +} + +AHKSetCoordMode(ByRef command) { + target := command[2] + relative_to := command[3] + CoordMode, %target%, %relative_to% + + return FormatNoValueResponse() +} + MouseClickDrag(ByRef command) { button := command[2] if (command.Length() = 6) { From a468f275ecfc63c58cbf3b957f68233b33b411f2 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 14 Nov 2022 16:46:35 -0800 Subject: [PATCH 290/588] add py311 to tox config --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ca9046d7..0bbe7be9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py38,py39,py310 +envlist = py38,py39,py310,py311 [testenv] deps = -rrequirements-dev.txt From caa70b1b714cbf3cafefbe521a572e08e790fe36 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 14 Nov 2022 16:49:00 -0800 Subject: [PATCH 291/588] add python3.11 to GHA tests --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d5757916..fbfe3524 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,7 +5,7 @@ jobs: strategy: fail-fast: false matrix: - python_version: ["3.10", "3.9", "3.8"] + python_version: ["3.10", "3.9", "3.8", "3.11"] runs-on: windows-latest timeout-minutes: 5 steps: From 872f1b561fe7b8a8211ac2ad5a5fe7f9e0466791 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 14 Nov 2022 20:43:38 -0800 Subject: [PATCH 292/588] send_input send_play --- ahk/_async/engine.py | 82 ++++++++++++++++++++++++++++++++++++------ ahk/_sync/engine.py | 83 ++++++++++++++++++++++++++++++++++++------ ahk/daemon.ahk | 86 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 214 insertions(+), 37 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 3281bf26..66f9a250 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -777,18 +777,33 @@ async def run_script(self, script_text: str, decode: bool = True, blocking: bool # fmt: off @overload - async def send(self, s: str) -> None: ... + async def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None) -> None: ... @overload - async def send(self, s: str, *, blocking: Literal[True]) -> None: ... + async def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[True]) -> None: ... @overload - async def send(self, s: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... @overload - async def send(self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + async def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... # fmt: on async def send( - self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True + self, + s: str, + *, + raw: bool = False, + key_delay: Optional[int] = None, + key_press_duration: Optional[int] = None, + blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: 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 raw: raw_resp = await self._transport.function_call('AHKSendRaw', args=args, blocking=blocking) return raw_resp @@ -796,8 +811,28 @@ async def send( resp = await self._transport.function_call('AHKSend', args=args, blocking=blocking) return resp - async def send_event(self, s: str, delay: Optional[int] = None) -> None: - raise NotImplementedError() + # 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]]: + resp = await self.send( + s, raw=True, key_delay=key_delay, key_press_duration=key_press_duration, blocking=blocking + ) + return resp # fmt: off @overload @@ -814,11 +849,36 @@ async def send_input(self, s: str, *, blocking: bool = True) -> Union[None, Asyn resp = await self._transport.function_call('AHKSendInput', args, blocking=blocking) return resp - async def send_play(self, s: str) -> None: - raise NotImplementedError() + # 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]]: + 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('') - async def send_raw(self, s: str, delay: Optional[int] = None) -> None: - raise NotImplementedError() + resp = await self._transport.function_call('AHKSendPlay', args=args, blocking=blocking) + return resp async def set_capslock_state( self, state: Optional[Union[Literal['On'], Literal['Off'], Literal['AlwaysOn'], Literal['AlwaysOff']]] = None diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index dcb13fe9..8c5f22bd 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -776,18 +776,33 @@ def run_script(self, script_text: str, decode: bool = True, blocking: bool = Tru # fmt: off @overload - def send(self, s: str) -> None: ... + def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None) -> None: ... @overload - def send(self, s: str, *, blocking: Literal[True]) -> None: ... + def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[True]) -> None: ... @overload - def send(self, s: str, *, blocking: Literal[False]) -> FutureResult[None]: ... + def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: Literal[False]) -> FutureResult[None]: ... @overload - def send(self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... # fmt: on def send( - self, s: str, raw: bool = False, delay: Optional[int] = None, blocking: bool = True + self, + s: str, + *, + raw: bool = False, + key_delay: Optional[int] = None, + key_press_duration: Optional[int] = None, + blocking: bool = True, ) -> Union[None, FutureResult[None]]: 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 raw: raw_resp = self._transport.function_call('AHKSendRaw', args=args, blocking=blocking) return raw_resp @@ -795,8 +810,28 @@ def send( resp = self._transport.function_call('AHKSend', args=args, blocking=blocking) return resp - def send_event(self, s: str, delay: Optional[int] = None) -> None: - raise NotImplementedError() + # 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]]: + resp = self.send( + s, raw=True, key_delay=key_delay, key_press_duration=key_press_duration, blocking=blocking + ) + return resp # fmt: off @overload @@ -813,11 +848,37 @@ def send_input(self, s: str, *, blocking: bool = True) -> Union[None, FutureResu resp = self._transport.function_call('AHKSendInput', args, blocking=blocking) return resp - def send_play(self, s: str) -> None: - raise NotImplementedError() - def send_raw(self, s: str, delay: Optional[int] = None) -> None: - raise NotImplementedError() + # 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]]: + 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 def set_capslock_state( self, state: Optional[Union[Literal['On'], Literal['Off'], Literal['AlwaysOn'], Literal['AlwaysOff']]] = None diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 8dfdab43..81147ac6 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1269,42 +1269,98 @@ Unescape(HayStack) { } AHKSend(ByRef command) { - command.RemoveAt(1) - s := Join(",", command*) - str := Unescape(s) + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + Send,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } return FormatNoValueResponse() } AHKSendRaw(ByRef command) { - command.RemoveAt(1) - s := Join(",", command*) ; TODO: remove after better input handling is implemented - str := Unescape(s) + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + SendRaw,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } return FormatNoValueResponse() } AHKSendInput(ByRef command) { - str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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() } -SendEvent(ByRef command) { - command.RemoveAt(1) - s := Join(",", command*) - str := Unescape(s) +AHKSendEvent(ByRef command) { + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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() } -SendPlay(ByRef command) { - command.RemoveAt(1) - s := Join(",", command*) - str := Unescape(s) +AHKSendPlay(ByRef command) { + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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() } From 7438e53a84b348cfdf241e4a46f08f01eadd0ece Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 22:32:34 +0000 Subject: [PATCH 293/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v0.990 → v0.991](https://github.com/pre-commit/mirrors-mypy/compare/v0.990...v0.991) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 762401f4..4cd7516d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.990' + rev: 'v0.991' hooks: - id: mypy args: From 6b2eabc25faed94768b9dcb127743f810b268c25 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 23 Nov 2022 12:24:14 -0800 Subject: [PATCH 294/588] add flake8 --- .pre-commit-config.yaml | 9 +++++++++ ahk/_async/engine.py | 4 ++-- ahk/_async/transport.py | 12 +++++------- ahk/_sync/engine.py | 6 +++--- ahk/_sync/transport.py | 20 ++++++++++---------- ahk/hotkey.py | 8 +------- ahk/message.py | 4 ++-- 7 files changed, 32 insertions(+), 31 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4cd7516d..72bf54c9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,3 +46,12 @@ repos: exclude: ^(tests/.*|setup\.py|\.build\.py|\.unasync-rewrite\.py|_tests_setup\.py|buildunasync\.py) additional_dependencies: - jinja2 + +- repo: https://github.com/pycqa/flake8 + rev: '6.0.0' # pick a git hash / tag to point to + hooks: + - id: flake8 + args: + - "--ignore" + - "E501,E704,E301,W503" + files: ahk\/(?!_sync).* diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 66f9a250..fe451e79 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -893,7 +893,7 @@ async def show_error_traytip( title: str, text: str, second: float = 1.0, - slient: bool = False, + silent: bool = False, large_icon: bool = False, blocking: bool = True, ) -> None: @@ -904,7 +904,7 @@ async def show_info_traytip( title: str, text: str, second: float = 1.0, - slient: bool = False, + silent: bool = False, large_icon: bool = False, blocking: bool = True, ) -> None: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index a20a8209..2ac4bab6 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -12,7 +12,6 @@ from shutil import which from typing import Any from typing import AnyStr -from typing import Callable from typing import Generic from typing import List from typing import Literal @@ -35,6 +34,9 @@ from typing import TypeAlias, TypeGuard from ahk.hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring +from ahk.message import RequestMessage +from ahk.message import ResponseMessage + from concurrent.futures import Future, ThreadPoolExecutor DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' @@ -154,7 +156,7 @@ def kill(self) -> None: def kill(proc: Killable) -> None: try: proc.kill() - except: + except: # noqa pass @@ -563,7 +565,7 @@ async def _send_nonblocking( finally: try: proc.kill() - except: + except: # noqa pass response = ResponseMessage.from_bytes(content, engine=engine) return response.unpack() # type: ignore @@ -609,9 +611,5 @@ async def send( return response.unpack() # type: ignore -from ahk.message import RequestMessage -from ahk.message import ResponseMessage - - if TYPE_CHECKING: from .engine import AsyncAHK diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 8c5f22bd..7c9386f1 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -311,6 +311,7 @@ def control_get_text( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, FutureResult[str]]: + args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -848,7 +849,6 @@ def send_input(self, s: str, *, blocking: bool = True) -> Union[None, FutureResu resp = self._transport.function_call('AHKSendInput', args, 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: ... @@ -893,7 +893,7 @@ def show_error_traytip( title: str, text: str, second: float = 1.0, - slient: bool = False, + silent: bool = False, large_icon: bool = False, blocking: bool = True, ) -> None: @@ -904,7 +904,7 @@ def show_info_traytip( title: str, text: str, second: float = 1.0, - slient: bool = False, + silent: bool = False, large_icon: bool = False, blocking: bool = True, ) -> None: diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 64afb4df..de890e4b 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -12,7 +12,6 @@ from shutil import which from typing import Any from typing import AnyStr -from typing import Callable from typing import Generic from typing import List from typing import Literal @@ -35,6 +34,9 @@ from typing import TypeAlias, TypeGuard from ahk.hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring +from ahk.message import RequestMessage +from ahk.message import ResponseMessage + from concurrent.futures import Future, ThreadPoolExecutor DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' @@ -146,7 +148,7 @@ def kill(self) -> None: def kill(proc: Killable) -> None: try: proc.kill() - except: + except: # noqa pass @@ -206,11 +208,11 @@ def _resolve_executable_path(executable_path: Union[str, os.PathLike[AnyStr]] = if not executable_path: executable_path = ( os.environ.get('AHK_PATH', '') - or which('AutoHotkey.exe') - or which('AutoHotkeyU64.exe') - or which('AutoHotkeyU32.exe') - or which('AutoHotkeyA32.exe') - or '' + or which('AutoHotkey.exe') # noqa + or which('AutoHotkeyU64.exe') # noqa + or which('AutoHotkeyU32.exe') # noqa + or which('AutoHotkeyA32.exe') # noqa + or '' # noqa ) if not executable_path: @@ -539,7 +541,7 @@ def _send_nonblocking( finally: try: proc.kill() - except: + except: # noqa pass response = ResponseMessage.from_bytes(content, engine=engine) return response.unpack() # type: ignore @@ -577,8 +579,6 @@ def send( return response.unpack() # type: ignore -from ahk.message import RequestMessage -from ahk.message import ResponseMessage if TYPE_CHECKING: diff --git a/ahk/hotkey.py b/ahk/hotkey.py index 87f83937..f372379d 100644 --- a/ahk/hotkey.py +++ b/ahk/hotkey.py @@ -12,7 +12,6 @@ from abc import ABC from abc import abstractmethod from base64 import b64encode -from textwrap import dedent from typing import Any from typing import Callable from typing import Dict @@ -20,7 +19,6 @@ from typing import Optional from typing import Protocol from typing import runtime_checkable -from typing import Tuple from typing import Type from typing import TypeVar from typing import Union @@ -30,10 +28,6 @@ else: from typing_extensions import ParamSpec -if sys.version_info >= (3, 11): - from typing import Self -else: - from typing_extensions import Self import logging import tempfile @@ -326,5 +320,5 @@ def kill(self) -> None: def kill(proc: Killable) -> None: try: proc.kill() - except: + except: # noqa pass diff --git a/ahk/message.py b/ahk/message.py index e1f09c63..8c3e5253 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -194,8 +194,8 @@ class WindowListResponseMessage(ResponseMessage): def unpack(self) -> Union[List[Window], List[AsyncWindow]]: from ._async.engine import AsyncAHK - from ._async.window import AsyncWindow, AsyncControl - from ._sync.window import Window, Control + from ._async.window import AsyncWindow + from ._sync.window import Window from ._sync.engine import AHK s = self._raw_content.decode(encoding='utf-8') From bdbf94cc8b1d9570c572413286858c9486ae6d3e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 5 Dec 2022 15:07:06 -0800 Subject: [PATCH 295/588] window properties, find_window functions --- ahk/_async/engine.py | 250 +++++++++++++++++++++++++++++++++++----- ahk/_async/transport.py | 7 +- ahk/_async/window.py | 137 +++++++++++++++++++--- ahk/_sync/engine.py | 243 ++++++++++++++++++++++++++++++++------ ahk/_sync/transport.py | 19 ++- ahk/_sync/window.py | 118 ++++++++++++++++--- ahk/daemon.ahk | 71 ++++++++---- ahk/message.py | 21 +++- 8 files changed, 735 insertions(+), 131 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index fe451e79..bf662116 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -5,9 +5,10 @@ import time import warnings from typing import Any +from typing import Awaitable from typing import Callable +from typing import Coroutine from typing import Dict -from typing import Iterable from typing import List from typing import Literal from typing import NoReturn @@ -31,11 +32,14 @@ from .transport import AsyncTransport from .window import AsyncControl from .window import AsyncWindow - +from ahk.message import Position async_sleep = asyncio.sleep # unasync: remove sleep = time.sleep +AsyncFilterFunc: TypeAlias = Callable[[AsyncWindow], Awaitable[bool]] # unasync: remove +SyncFilterFunc: TypeAlias = Callable[[AsyncWindow], bool] + CoordModeTargets: TypeAlias = Union[ Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu'] ] @@ -83,6 +87,14 @@ ], ] +AsyncPropertyReturnTupleIntInt: TypeAlias = Coroutine[None, None, Tuple[int, int]] # unasync: remove +SyncPropertyReturnTupleIntInt: TypeAlias = Tuple[int, int] + +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: """ @@ -123,8 +135,9 @@ def __init__( def __getattr__(self, item: Any) -> Any: deprecation_replacements: Dict[str, Any] = {'type': self.send_input} if item in deprecation_replacements: + func = deprecation_replacements[item] warnings.warn( - 'type is deprecated and will be removed in a future version. Use `send_input` instead.', + f'{item!r} is deprecated and will be removed in a future version. Use {func.__name__!r} instead.', DeprecationWarning, stacklevel=2, ) @@ -347,13 +360,13 @@ async def control_get_text( # 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) -> Tuple[int, int, int, int]: ... + 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[Tuple[int, int, int, int]]: ... + 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]) -> Tuple[int, int, int, int]: ... + 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[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: ... + 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, @@ -366,7 +379,7 @@ async def control_get_position( title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: + ) -> Union[Position, AsyncFutureResult[Position]]: args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -507,22 +520,26 @@ async def set_detect_hidden_windows(self, value: bool) -> None: # fmt: off @overload - async def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> List[AsyncWindow]: ... + 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_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + 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]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... @overload - async def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[AsyncWindow]: ... + 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_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + 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]]]: - args = [] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -571,6 +588,13 @@ async def get_mouse_position( resp = await self._transport.function_call('AHKMouseGetPos', blocking=blocking) return resp + @property + def mouse_position(self) -> AsyncPropertyReturnTupleIntInt: + warnings.warn( # unasync: remove + _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('mouse_position'), category=DeprecationWarning, stacklevel=2 + ) + return self.get_mouse_position() + # 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) -> None: ... @@ -626,33 +650,141 @@ async def get_active_window( title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking ) - async def find_windows( - self, func: Optional[Callable[[AsyncWindow], bool]] = None, **kwargs: Any - ) -> Iterable[AsyncWindow]: - raise NotImplementedError() + @property + def active_window(self) -> AsyncPropertyReturnOptionalAsyncWindow: + warnings.warn( # unasync: remove + _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('active_window'), category=DeprecationWarning, stacklevel=2 + ) + return self.get_active_window() - async def find_windows_by_class(self, class_name: str, exact: bool = False) -> Iterable[AsyncWindow]: - raise NotImplementedError() + 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 match_mode=3 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: bool = False) -> Iterable[AsyncWindow]: - raise NotImplementedError() + 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: bool = False) -> Iterable[AsyncWindow]: - raise NotImplementedError() + 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[Callable[[AsyncWindow], bool]] = None, **kwargs: Any - ) -> Iterable[AsyncWindow]: - raise NotImplementedError() + 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: bool = False) -> Iterable[AsyncWindow]: - raise NotImplementedError() + 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: bool = False) -> Iterable[AsyncWindow]: - raise NotImplementedError() + 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: bool = False) -> Iterable[AsyncWindow]: - raise NotImplementedError() + 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: raise NotImplementedError() @@ -1118,6 +1250,60 @@ async def win_get_title( resp = await self._transport.function_call('AHKWinGetTitle', args, blocking=blocking) return resp + # fmt: off + @overload + 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) -> Union[Position, None]: ... + @overload + 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: Literal[False]) -> AsyncFutureResult[Union[Position, None]]: ... + @overload + 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: Literal[True]) -> Union[Position, None]: ... + @overload + 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]]]: ... + # 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]]]: + args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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('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]: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 2ac4bab6..280f3f34 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -36,6 +36,7 @@ from ahk.hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from ahk.message import RequestMessage from ahk.message import ResponseMessage +from ahk.message import Position from concurrent.futures import Future, ThreadPoolExecutor @@ -378,8 +379,8 @@ async def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], arg async def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload async def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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) = None -> Union[TupleResponseMessage, AsyncFutureResult[TupleResponseMessage]]: ... + @overload + async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = None) -> Union[Union[None, AsyncWindow], AsyncFutureResult[Union[None, AsyncWindow]]]: ... @overload @@ -451,7 +452,7 @@ async def function_call(self, function_name: Literal['AHKControlGetText'], args: 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[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: ... + 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: ... diff --git a/ahk/_async/window.py b/ahk/_async/window.py index dbef9f5e..2d8f7256 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -1,5 +1,9 @@ from __future__ import annotations +import sys +import warnings +from typing import Any +from typing import Coroutine from typing import Literal from typing import Optional from typing import overload @@ -8,6 +12,13 @@ from typing import TYPE_CHECKING from typing import Union +from ahk.message import Position + +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + if TYPE_CHECKING: from .engine import AsyncAHK from .transport import AsyncFutureResult @@ -17,6 +28,24 @@ class WindowNotFoundException(Exception): ... +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.' +) + + class AsyncWindow: def __init__(self, engine: AsyncAHK, ahk_id: str): self._engine: AsyncAHK = engine @@ -46,6 +75,17 @@ async def exists(self) -> bool: 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') @@ -56,6 +96,13 @@ async def get_pid(self) -> int: ) 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') @@ -66,6 +113,13 @@ async def get_process_name(self) -> str: ) 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') @@ -76,6 +130,13 @@ async def get_process_path(self) -> str: ) 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') @@ -92,15 +153,17 @@ async def get_title(self) -> str: ) return title - 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') + @property + def title(self) -> AsyncPropertyReturnStr: + warnings.warn( # unasync: remove + _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('title'), category=DeprecationWarning, stacklevel=2 ) - 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 + 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( @@ -111,6 +174,16 @@ async def set_title(self, new_title: str) -> None: ) 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 set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> None: ... @@ -153,6 +226,15 @@ async def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, AsyncF ) 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) -> None: ... @@ -187,6 +269,33 @@ async def get_text(self, *, blocking: bool = True) -> Union[str, AsyncFutureResu 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]]]: ... + # fmt: on + async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutureResult[Optional[Position]]]: + resp = await self._engine.win_get_position( + 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 + class AsyncControl: def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): @@ -255,17 +364,15 @@ async def get_text(self, blocking: bool = True) -> Union[str, AsyncFutureResult[ # fmt: off @overload - async def get_position(self) -> Tuple[int, int, int, int]: ... + async def get_position(self) -> Position: ... @overload - async def get_position(self, blocking: Literal[False]) -> AsyncFutureResult[Tuple[int, int, int, int]]: ... + async def get_position(self, blocking: Literal[False]) -> AsyncFutureResult[Position]: ... @overload - async def get_position(self, blocking: Literal[True]) -> Tuple[int, int, int, int]: ... + async def get_position(self, blocking: Literal[True]) -> Position: ... @overload - async def get_position(self, blocking: bool = True) -> Union[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: ... + async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutureResult[Position]]: ... # fmt: on - async def get_position( - self, blocking: bool = True - ) -> Union[Tuple[int, int, int, int], AsyncFutureResult[Tuple[int, int, int, int]]]: + async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutureResult[Position]]: return await self._engine.control_get_position( control=self.control_class, title=f'ahk_id {self.window._ahk_id}', diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 7c9386f1..34693a24 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -5,9 +5,10 @@ import time import warnings from typing import Any +from typing import Awaitable from typing import Callable +from typing import Coroutine from typing import Dict -from typing import Iterable from typing import List from typing import Literal from typing import NoReturn @@ -31,10 +32,12 @@ from .transport import Transport from .window import Control from .window import Window - +from ahk.message import Position sleep = time.sleep +SyncFilterFunc: TypeAlias = Callable[[Window], bool] + CoordModeTargets: TypeAlias = Union[ Literal['ToolTip'], Literal['Pixel'], Literal['Mouse'], Literal['Caret'], Literal['Menu'] ] @@ -82,6 +85,12 @@ ], ] +SyncPropertyReturnTupleIntInt: TypeAlias = Tuple[int, int] + +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: """ @@ -122,8 +131,9 @@ def __init__( def __getattr__(self, item: Any) -> Any: deprecation_replacements: Dict[str, Any] = {'type': self.send_input} if item in deprecation_replacements: + func = deprecation_replacements[item] warnings.warn( - 'type is deprecated and will be removed in a future version. Use `send_input` instead.', + f'{item!r} is deprecated and will be removed in a future version. Use {func.__name__!r} instead.', DeprecationWarning, stacklevel=2, ) @@ -311,7 +321,6 @@ def control_get_text( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, FutureResult[str]]: - args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -347,13 +356,13 @@ def control_get_text( # 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) -> Tuple[int, int, int, int]: ... + 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[Tuple[int, int, int, int]]: ... + 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]) -> Tuple[int, int, int, int]: ... + 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[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: ... + 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, @@ -366,7 +375,7 @@ def control_get_position( title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: + ) -> Union[Position, FutureResult[Position]]: args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -507,22 +516,26 @@ def set_detect_hidden_windows(self, value: bool) -> None: # fmt: off @overload - def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> List[Window]: ... + 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_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[False]) -> Union[List[Window], FutureResult[List[Window]]]: ... + 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]) -> Union[List[Window], FutureResult[List[Window]]]: ... @overload - def list_windows(self, *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> List[Window]: ... + 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_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True,) -> Union[List[Window], FutureResult[List[Window]]]: ... + 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]]]: - args = [] + args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: args.append('On') @@ -571,6 +584,10 @@ def get_mouse_position( resp = self._transport.function_call('AHKMouseGetPos', blocking=blocking) return resp + @property + def mouse_position(self) -> SyncPropertyReturnTupleIntInt: + return self.get_mouse_position() + # 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) -> None: ... @@ -626,33 +643,138 @@ def get_active_window( title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking ) - def find_windows( - self, func: Optional[Callable[[Window], bool]] = None, **kwargs: Any - ) -> Iterable[Window]: - raise NotImplementedError() + @property + def active_window(self) -> SyncPropertyReturnOptionalAsyncWindow: + return self.get_active_window() - def find_windows_by_class(self, class_name: str, exact: bool = False) -> Iterable[Window]: - raise NotImplementedError() + 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 match_mode=3 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: bool = False) -> Iterable[Window]: - raise NotImplementedError() + 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: bool = False) -> Iterable[Window]: - raise NotImplementedError() + 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[Callable[[Window], bool]] = None, **kwargs: Any - ) -> Iterable[Window]: - raise NotImplementedError() + 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: bool = False) -> Iterable[Window]: - raise NotImplementedError() + 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: bool = False) -> Iterable[Window]: - raise NotImplementedError() + 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: bool = False) -> Iterable[Window]: - raise NotImplementedError() + 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: raise NotImplementedError() @@ -1118,6 +1240,61 @@ def win_get_title( resp = self._transport.function_call('AHKWinGetTitle', args, blocking=blocking) return resp + # fmt: off + @overload + 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) -> Union[Position, None]: ... + @overload + 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: Literal[False]) -> FutureResult[Union[Position, None]]: ... + @overload + 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: Literal[True]) -> Union[Position, None]: ... + @overload + 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]]]: ... + # 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]]]: + args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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('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]: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index de890e4b..cb8dc2c5 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -36,6 +36,7 @@ from ahk.hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from ahk.message import RequestMessage from ahk.message import ResponseMessage +from ahk.message import Position from concurrent.futures import Future, ThreadPoolExecutor @@ -208,11 +209,11 @@ def _resolve_executable_path(executable_path: Union[str, os.PathLike[AnyStr]] = if not executable_path: executable_path = ( os.environ.get('AHK_PATH', '') - or which('AutoHotkey.exe') # noqa - or which('AutoHotkeyU64.exe') # noqa - or which('AutoHotkeyU32.exe') # noqa - or which('AutoHotkeyA32.exe') # noqa - or '' # noqa + or which('AutoHotkey.exe') + or which('AutoHotkeyU64.exe') + or which('AutoHotkeyU32.exe') + or which('AutoHotkeyA32.exe') + or '' ) if not executable_path: @@ -361,8 +362,8 @@ def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Opt def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... - # @overload - # async def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK) = None -> Union[TupleResponseMessage, AsyncFutureResult[TupleResponseMessage]]: ... + @overload + def function_call(self, function_name: Literal['AHKWinGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Union[None, Window], FutureResult[Union[None, Window]]]: ... @overload @@ -434,7 +435,7 @@ def function_call(self, function_name: Literal['AHKControlGetText'], args: Optio 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[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: ... + 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: ... @@ -579,7 +580,5 @@ def send( return response.unpack() # type: ignore - - if TYPE_CHECKING: from .engine import AHK diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index decd170f..00d64d72 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -1,5 +1,9 @@ from __future__ import annotations +import sys +import warnings +from typing import Any +from typing import Coroutine from typing import Literal from typing import Optional from typing import overload @@ -8,6 +12,13 @@ from typing import TYPE_CHECKING from typing import Union +from ahk.message import Position + +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias + if TYPE_CHECKING: from .engine import AHK from .transport import FutureResult @@ -17,6 +28,20 @@ class WindowNotFoundException(Exception): ... +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.' +) + + class Window: def __init__(self, engine: AHK, ahk_id: str): self._engine: AHK = engine @@ -46,6 +71,14 @@ def exists(self) -> bool: 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') @@ -56,6 +89,10 @@ def get_pid(self) -> int: ) 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') @@ -66,6 +103,10 @@ def get_process_name(self) -> str: ) 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') @@ -76,6 +117,10 @@ def get_process_path(self) -> str: ) 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') @@ -92,15 +137,13 @@ def get_title(self) -> str: ) return title - 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 + @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( @@ -111,6 +154,16 @@ def set_title(self, new_title: str) -> None: ) 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 set_always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> None: ... @@ -153,6 +206,14 @@ def is_always_on_top(self, *, blocking: bool = True) -> Union[bool, FutureResult ) 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) -> None: ... @@ -187,6 +248,33 @@ def get_text(self, *, blocking: bool = True) -> Union[str, FutureResult[str]]: 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]]]: ... + # fmt: on + def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Optional[Position]]]: + resp = self._engine.win_get_position( + 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 + class Control: def __init__(self, window: Window, hwnd: str, control_class: str): @@ -255,21 +343,21 @@ def get_text(self, blocking: bool = True) -> Union[str, FutureResult[str]]: # fmt: off @overload - def get_position(self) -> Tuple[int, int, int, int]: ... + def get_position(self) -> Position: ... @overload - def get_position(self, blocking: Literal[False]) -> FutureResult[Tuple[int, int, int, int]]: ... + def get_position(self, blocking: Literal[False]) -> FutureResult[Position]: ... @overload - def get_position(self, blocking: Literal[True]) -> Tuple[int, int, int, int]: ... + def get_position(self, blocking: Literal[True]) -> Position: ... @overload - def get_position(self, blocking: bool = True) -> Union[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: ... + def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Position]]: ... # fmt: on - def get_position(self, blocking: bool = True) -> Union[Tuple[int, int, int, int], FutureResult[Tuple[int, int, int, int]]]: + def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Position]]: return self._engine.control_get_position( control=self.control_class, title=f'ahk_id {self.window._ahk_id}', blocking=blocking, detect_hidden_windows=True, - title_match_mode=(1, 'Fast') + title_match_mode=(1, 'Fast'), ) def __repr__(self) -> str: diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 81147ac6..58582029 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -13,6 +13,8 @@ NOVALUERESPONSEMESSAGE := "007" ; NoValueResponseMessage EXCEPTIONRESPONSEMESSAGE := "008" ; ExceptionResponseMessage WINDOWCONTROLLISTRESPONSEMESSAGE := "009" ; WindowControlListResponseMessage WINDOWRESPONSEMESSAGE := "00a" ; WindowResponseMessage +POSITIONRESPONSEMESSAGE := "00b" ; PositionResponseMessage + NOVALUE_SENTINEL := Chr(57344) FormatResponse(MessageType, payload) { @@ -1519,9 +1521,13 @@ AHKWindowList(ByRef command) { current_detect_hw := Format("{}", A_DetectHiddenWindows) - detect_hw := command[2] - match_mode := command[3] - match_speed := command[4] + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) @@ -1535,7 +1541,7 @@ AHKWindowList(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinGet windows, List + WinGet windows, List, %title%, %text%, %extitle%, %extext% r := "" Loop %windows% { @@ -1649,7 +1655,7 @@ AHKControlGetText(ByRef command) { AHKControlGetPos(ByRef command) { - global TUPLERESPONSEMESSAGE + global POSITIONRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE ctrl := command[2] title := command[3] @@ -1675,14 +1681,13 @@ AHKControlGetPos(ByRef command) { } ControlGetPos, x, y, w, h, %ctrl%, %title%, %text%, %extitle%, %extext% - - result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) - if (ErrorLevel = 1) { response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the text") } else { - response := FormatResponse(TUPLERESPONSEMESSAGE, result) + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse(PositionResponseMessage, result) } + DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -1808,21 +1813,45 @@ AHKWinMove(ByRef command) { } AHKWinGetPos(ByRef command) { + global POSITIONRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + title := command[2] - WinGetPos, x, y, width, height, %title% - if (command.Length() = 3) { - pos_info := command[3] - if (pos_info = "position") { - s := Format("({}, {})", x, y) - } else if (pos_info = "height") { - s := Format("({})", height) - } else if (pos_info = "width") { - s := Format("({})", width) - } + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGetPos, x, y, w, h, %title%, %text%, %extitle%, %extext% + + if (ErrorLevel = 1) { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the position") } else { - s := Format("({}, {}, {}, {})", x, y, width, height) + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse(PositionResponseMessage, result) } - return s + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response } CountNewlines(ByRef s) { diff --git a/ahk/message.py b/ahk/message.py index 8c3e5253..142fd2ea 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -6,6 +6,7 @@ import sys from abc import abstractmethod from base64 import b64encode +from collections import namedtuple from typing import Any from typing import cast from typing import Generator @@ -30,6 +31,9 @@ class OutOfMessageTypes(Exception): ... +Position = namedtuple('Position', ('x', 'y', 'width', 'height')) + + @runtime_checkable class BytesLineReadable(Protocol): def readline(self) -> bytes: @@ -202,10 +206,10 @@ def unpack(self) -> Union[List[Window], List[AsyncWindow]]: 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] + 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] + 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}') @@ -287,6 +291,17 @@ def unpack(self) -> Union[Window, AsyncWindow]: raise ValueError(f'Invalid engine: {self._engine!r}') +class PositionResponseMessage(TupleResponseMessage): + type = 'position' + + 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 + + T_RequestMessageType = TypeVar('T_RequestMessageType', bound='RequestMessage') @@ -312,8 +327,10 @@ def format(self) -> bytes: NoValueResponseMessage, WindowControlListResponseMessage, ExceptionResponseMessage, + PositionResponseMessage, ] ResponseMessageClassTypes = Union[ + Type[PositionResponseMessage], Type[TupleResponseMessage], Type[CoordinateResponseMessage], Type[IntegerResponseMessage], From b8ac60cd4eb4ddc0e81bf319f544c7962ed53c4a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 5 Dec 2022 15:58:28 -0800 Subject: [PATCH 296/588] add KeyState --- ahk/_async/engine.py | 21 +++++++++++++++++++-- ahk/_sync/engine.py | 20 +++++++++++++++++--- ahk/daemon.ahk | 9 +++++---- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index bf662116..701047d5 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -849,8 +849,25 @@ async def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> U else: return await self.key_up(key=key, blocking=False) - async def key_state(self, key_name: str, mode: Optional[Union[Literal['P'], Literal['T']]] = None) -> bool: - raise NotImplementedError() + # fmt: off + @overload + async def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None) -> bool: ... + @overload + async def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: Literal[True]) -> bool: ... + @overload + async def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: Literal[False]) -> AsyncFutureResult[bool]: ... + @overload + async def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + # fmt: on + async def key_state( + self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: bool = True + ) -> Union[bool, AsyncFutureResult[bool]]: + args = [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) + return await self._transport.function_call('AHKKeyState', args, blocking=blocking) # fmt: off @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 34693a24..01ef9a54 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -839,8 +839,23 @@ def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[N else: return self.key_up(key=key, blocking=False) - def key_state(self, key_name: str, mode: Optional[Union[Literal['P'], Literal['T']]] = None) -> bool: - raise NotImplementedError() + # fmt: off + @overload + def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None) -> bool: ... + @overload + def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: Literal[True]) -> bool: ... + @overload + def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: Literal[False]) -> FutureResult[bool]: ... + @overload + def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + # fmt: on + def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: + args = [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) + return self._transport.function_call('AHKKeyState', args, blocking=blocking) # fmt: off @overload @@ -1294,7 +1309,6 @@ def win_get_position( 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]: ... diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 58582029..8f45c66b 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1116,17 +1116,18 @@ AHKMouseGetPos(ByRef command) { } AHKKeyState(ByRef command) { + global BOOLEANRESPONSEMESSAGE if (command.Length() = 3) { if (GetKeyState(command[2], command[3])) { - return 1 + return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) } else { - return 0 + return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } } else{ if (GetKeyState(command[2])) { - return 1 + return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) } else { - return 0 + return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } } } From 053e3980c2f9ab48176bec6466335b479c4fa0cb Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 5 Dec 2022 16:18:28 -0800 Subject: [PATCH 297/588] add SetCapsLockState --- ahk/_async/engine.py | 23 ++++++++++++++++++++--- ahk/_async/transport.py | 4 ++-- ahk/_sync/engine.py | 27 +++++++++++++++++++++++---- ahk/_sync/transport.py | 4 ++-- ahk/daemon.ahk | 6 +++--- 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 701047d5..a47c4197 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1029,10 +1029,27 @@ async def send_play( 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['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + @overload + async def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + @overload + async def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on async def set_capslock_state( - self, state: Optional[Union[Literal['On'], Literal['Off'], Literal['AlwaysOn'], Literal['AlwaysOff']]] = None - ) -> None: - raise NotImplementedError() + self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + args: List[str] = [] + if state is not None: + if state.lower() not in ('on', 'off', 'alwayson', 'alwaysoff'): + raise ValueError( + f'Invalid value for state. Must be one of On, Off, AlwaysOn, AlwaysOff or None. Got {state!r}' + ) + args.append(state) + return await self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) async def set_volume(self, value: int, device_number: int = 1) -> None: raise NotImplementedError() diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 280f3f34..b58d17f5 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -128,7 +128,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'MouseClickDrag', 'PixelGetColor', 'PixelSearch', - 'SetCapsLockState', + 'AHKSetCapsLockState', 'SetKeyDelay', 'WinActivate', 'WinActivateBottom', @@ -332,7 +332,7 @@ async def function_call(self, function_name: Literal['AHKSendEvent'], args: Opti @overload async def function_call(self, function_name: Literal['AHKSendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKSetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 01ef9a54..d8c6d925 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -849,7 +849,9 @@ def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, @overload def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... # fmt: on - def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: + def key_state( + self, key_name: str, mode: Optional[Literal['T', 'P']] = None, *, blocking: bool = True + ) -> Union[bool, FutureResult[bool]]: args = [key_name] if mode is not None: if mode not in ('T', 'P'): @@ -1017,10 +1019,27 @@ def send_play( resp = self._transport.function_call('AHKSendPlay', args=args, blocking=blocking) return resp + # fmt: off + @overload + def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + @overload + def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + @overload + def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on def set_capslock_state( - self, state: Optional[Union[Literal['On'], Literal['Off'], Literal['AlwaysOn'], Literal['AlwaysOff']]] = None - ) -> None: - raise NotImplementedError() + self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + args: List[str] = [] + if state is not None: + if state.lower() not in ('on', 'off', 'alwayson', 'alwaysoff'): + raise ValueError( + f'Invalid value for state. Must be one of On, Off, AlwaysOn, AlwaysOff or None. Got {state!r}' + ) + args.append(state) + return self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) def set_volume(self, value: int, device_number: int = 1) -> None: raise NotImplementedError() diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index cb8dc2c5..a471f62c 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -120,7 +120,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'MouseClickDrag', 'PixelGetColor', 'PixelSearch', - 'SetCapsLockState', + 'AHKSetCapsLockState', 'SetKeyDelay', 'WinActivate', 'WinActivateBottom', @@ -315,7 +315,7 @@ def function_call(self, function_name: Literal['AHKSendEvent'], args: Optional[L @overload def function_call(self, function_name: Literal['AHKSendPlay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['SetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKSetCapsLockState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[str, FutureResult[str]]: ... @overload diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 8f45c66b..56f5dbc6 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1367,11 +1367,11 @@ AHKSendPlay(ByRef command) { return FormatNoValueResponse() } -SetCapsLockState(ByRef command) { - if (command.Length() = 1) { +AHKSetCapsLockState(ByRef command) { + state := command[2] + if (state = "") { SetCapsLockState % !GetKeyState("CapsLock", "T") } else { - state := command[2] SetCapsLockState, %state% } return FormatNoValueResponse() From 9039161c0d28e2bec0ae67c6558fb41c6a24841e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 5 Dec 2022 18:18:27 -0800 Subject: [PATCH 298/588] account for other possible return values of GetKeyState --- ahk/_async/engine.py | 23 ++++++++++++++++------- ahk/_async/transport.py | 2 +- ahk/_sync/engine.py | 23 ++++++++++++++++------- ahk/_sync/transport.py | 2 +- ahk/daemon.ahk | 39 ++++++++++++++++++++++++++------------- ahk/message.py | 10 ++++++++++ 6 files changed, 70 insertions(+), 29 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index a47c4197..2c5991c6 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -851,18 +851,27 @@ async def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> U # fmt: off @overload - async def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None) -> bool: ... + 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]) -> bool: ... + 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]) -> AsyncFutureResult[bool]: ... + 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[bool, AsyncFutureResult[bool]]: ... + 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[bool, AsyncFutureResult[bool]]: - args = [key_name] + 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], + ]: + 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}') diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index b58d17f5..b2d01eee 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -308,7 +308,7 @@ async def function_call(self, function_name: Literal['PixelSearch'], args: Optio @overload async def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... @overload - async def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + async def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index d8c6d925..202ac0ea 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -841,18 +841,27 @@ def key_release(self, key: Union[str, Key], *, blocking: bool = True) -> Union[N # fmt: off @overload - def key_state(self, key_name: str, mode: Optional[Literal['T', 'P']] = None) -> bool: ... + 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]) -> bool: ... + 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]) -> FutureResult[bool]: ... + 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[bool, FutureResult[bool]]: ... + 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[bool, FutureResult[bool]]: - args = [key_name] + 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], + ]: + 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}') diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index a471f62c..89729578 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -291,7 +291,7 @@ def function_call(self, function_name: Literal['PixelSearch'], args: Optional[Li @overload def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... @overload - def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, FutureResult[bool]]: ... + def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, FutureResult[None]]: ... @overload diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 56f5dbc6..d02f1476 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -14,7 +14,7 @@ EXCEPTIONRESPONSEMESSAGE := "008" ; ExceptionResponseMessage WINDOWCONTROLLISTRESPONSEMESSAGE := "009" ; WindowControlListResponseMessage WINDOWRESPONSEMESSAGE := "00a" ; WindowResponseMessage POSITIONRESPONSEMESSAGE := "00b" ; PositionResponseMessage - +FLOATRESPONSEMESSAGE := "00c" ; FloatResponseMessage NOVALUE_SENTINEL := Chr(57344) FormatResponse(MessageType, payload) { @@ -1116,20 +1116,33 @@ AHKMouseGetPos(ByRef command) { } AHKKeyState(ByRef command) { - global BOOLEANRESPONSEMESSAGE - if (command.Length() = 3) { - if (GetKeyState(command[2], command[3])) { - return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) - } else { - return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) - } + global INTEGERRESPONSEMESSAGE + global FLOATRESPONSEMESSAGE + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + + keyname := command[2] + mode := command[3] + if (mode != "") { + state := GetKeyState(keyname, mode) } else{ - if (GetKeyState(command[2])) { - return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) - } else { - return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) - } + state := GetKeyState(keyname) } + + if (state = "") { + return FormatNoValueResponse() + } + + if state is integer + return FormatResponse(INTEGERRESPONSEMESSAGE, state) + + if state is float + return FormatResponse(FLOATRESPONSEMESSAGE, state) + + if state is alnum + return FormatResponse(STRINGRESPONSEMESSAGE, state) + + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, state) } AHKMouseMove(ByRef command) { diff --git a/ahk/message.py b/ahk/message.py index 142fd2ea..856f9679 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -302,6 +302,16 @@ def unpack(self) -> Position: return pos +class FloatResponseMessage(ResponseMessage): + type = 'float' + + def unpack(self) -> float: + s = self._raw_content.decode(encoding='utf-8') + val = ast.literal_eval(s) + assert isinstance(val, float) + return val + + T_RequestMessageType = TypeVar('T_RequestMessageType', bound='RequestMessage') From 5349a9ab5819d79f098a3166699c798e0d7a1679 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 5 Dec 2022 18:37:19 -0800 Subject: [PATCH 299/588] support 0 and 1 for set_capslock_state --- ahk/_async/engine.py | 14 +++++++------- ahk/_sync/engine.py | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 2c5991c6..0a2c9c77 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1040,24 +1040,24 @@ async def send_play( # fmt: off @overload - async def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + async def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... @overload - async def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + async def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... @overload - async def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... @overload - async def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + async def set_capslock_state(self, state: Optional[Literal[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['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True + self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True ) -> Union[None, AsyncFutureResult[None]]: args: List[str] = [] if state is not None: - if state.lower() not in ('on', 'off', 'alwayson', 'alwaysoff'): + 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}' ) - args.append(state) + args.append(str(state)) return await self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) async def set_volume(self, value: int, device_number: int = 1) -> None: diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 202ac0ea..241bcc0f 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1030,24 +1030,24 @@ def send_play( # fmt: off @overload - def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... @overload - def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... @overload - def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> FutureResult[None]: ... + def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> FutureResult[None]: ... @overload - def set_capslock_state(self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... # fmt: on def set_capslock_state( - self, state: Optional[Literal['On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True + self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True ) -> Union[None, FutureResult[None]]: args: List[str] = [] if state is not None: - if state.lower() not in ('on', 'off', 'alwayson', 'alwaysoff'): + 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}' ) - args.append(state) + args.append(str(state)) return self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) def set_volume(self, value: int, device_number: int = 1) -> None: From baa298debcb0a5d099340c667eb7f27f34e06e36 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 5 Dec 2022 18:37:53 -0800 Subject: [PATCH 300/588] some more tests --- tests/_async/test_keys.py | 33 +++++++++++++++++++++++++++++++++ tests/_async/test_window.py | 10 ++++++++++ tests/_sync/test_keys.py | 33 +++++++++++++++++++++++++++++++++ tests/_sync/test_window.py | 10 ++++++++++ 4 files changed, 86 insertions(+) create mode 100644 tests/_async/test_keys.py create mode 100644 tests/_sync/test_keys.py diff --git a/tests/_async/test_keys.py b/tests/_async/test_keys.py new file mode 100644 index 00000000..7972cfba --- /dev/null +++ b/tests/_async/test_keys.py @@ -0,0 +1,33 @@ +import asyncio +import os +import subprocess +import sys +import time +from unittest import IsolatedAsyncioTestCase + +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) + 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.ahk._transport._proc.kill() + + async def test_set_capslock(self): + await self.ahk.set_capslock_state('On') + assert await self.ahk.key_state('CapsLock', mode='T') == 1 diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 683c0222..500bad07 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -148,3 +148,13 @@ async def test_control_send_from_control(self): 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 diff --git a/tests/_sync/test_keys.py b/tests/_sync/test_keys.py new file mode 100644 index 00000000..9973930f --- /dev/null +++ b/tests/_sync/test_keys.py @@ -0,0 +1,33 @@ +import asyncio +import os +import subprocess +import sys +import time +from unittest import TestCase + +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) + self.ahk.set_capslock_state('Off') + + def tearDown(self) -> None: + self.ahk.set_capslock_state('Off') + try: + self.p.kill() + except Exception: + pass + self.ahk._transport._proc.kill() + + def test_set_capslock(self): + self.ahk.set_capslock_state('On') + assert self.ahk.key_state('CapsLock', mode='T') == 1 diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index e5a0de82..7d1d3726 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -148,3 +148,13 @@ def test_control_send_from_control(self): 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 From aab4cdccd60bc06e1d709f129357821baf46ed2e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 5 Dec 2022 19:15:47 -0800 Subject: [PATCH 301/588] add coveragerc --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..b4c3abd9 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +source = ahk From 15d220acafde1f8b79e6123f7ca3548d783cacdb Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Dec 2022 14:02:03 -0800 Subject: [PATCH 302/588] change hotkey/hotstring interface. --- ahk/_async/engine.py | 15 +++++++++++++-- ahk/_sync/engine.py | 11 +++++++++-- ahk/hotkey.py | 3 ++- tests/_async/test_hotkeys.py | 4 ++-- tests/_sync/test_hotkeys.py | 4 ++-- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 0a2c9c77..c0a14104 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -144,7 +144,9 @@ def __getattr__(self, item: Any) -> Any: return deprecation_replacements[item] raise AttributeError(f'{self.__class__.__qualname__!r} object has no attribute {item!r}') - def add_hotkey(self, hotkey: Hotkey) -> 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. @@ -157,6 +159,7 @@ def add_hotkey(self, hotkey: Hotkey) -> None: :param hotkey: an instance of ahk.hotkey.Hotkey """ + 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: @@ -164,7 +167,14 @@ def add_hotkey(self, hotkey: Hotkey) -> None: warnings.warn(warning.message, warning.category, stacklevel=2) return None - def add_hotstring(self, hotstring: Hotstring) -> None: + def add_hotstring( + self, + trigger: str, + replacement_or_callback: Union[str, Callable[[], Any]], + *, + ex_handler: Optional[Callable[[str, Exception], Any]], + options: str = '', + ) -> None: """ Register a hotstring, e.g., `::btw::by the way` @@ -175,6 +185,7 @@ def add_hotstring(self, hotstring: Hotstring) -> None: :param hotstring: an instance of ahk.hotkey.Hotstring """ + 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: diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 241bcc0f..7fb00354 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -140,7 +140,7 @@ def __getattr__(self, item: Any) -> Any: return deprecation_replacements[item] raise AttributeError(f'{self.__class__.__qualname__!r} object has no attribute {item!r}') - def add_hotkey(self, hotkey: Hotkey) -> 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. @@ -153,6 +153,7 @@ def add_hotkey(self, hotkey: Hotkey) -> None: :param hotkey: an instance of ahk.hotkey.Hotkey """ + 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: @@ -160,7 +161,12 @@ def add_hotkey(self, hotkey: Hotkey) -> None: warnings.warn(warning.message, warning.category, stacklevel=2) return None - def add_hotstring(self, hotstring: Hotstring) -> None: + def add_hotstring(self, trigger: str, + replacement_or_callback: Union[str, Callable[[], Any]], + *, + ex_handler: Optional[Callable[[str, Exception], Any]], + options: str = '', +) -> None: """ Register a hotstring, e.g., `::btw::by the way` @@ -171,6 +177,7 @@ def add_hotstring(self, hotstring: Hotstring) -> None: :param hotstring: an instance of ahk.hotkey.Hotstring """ + 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: diff --git a/ahk/hotkey.py b/ahk/hotkey.py index f372379d..4f242ef4 100644 --- a/ahk/hotkey.py +++ b/ahk/hotkey.py @@ -216,7 +216,7 @@ def listener(self) -> None: class Hotkey: def __init__( - self, keyname: str, *, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None + self, keyname: str, callback: Callable[[], Any], *, ex_handler: Optional[Callable[[str, Exception], Any]] = None ): self._keyname: str = keyname self.callback: Callable[[], Any] = callback @@ -249,6 +249,7 @@ def __init__( self, trigger: str, replacement_or_callback: Union[str, Callable[[], Any]], + *, ex_handler: Optional[Callable[[str, Exception], Any]], options: str = '', ): diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index eb971508..21d021a9 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -29,7 +29,7 @@ async def asyncTearDown(self) -> None: async def test_hotkey(self): with mock.MagicMock(return_value=None) as m: - self.ahk.add_hotkey(Hotkey('a', callback=m)) + self.ahk.add_hotkey('a', callback=m) self.ahk.start_hotkeys() await self.ahk.key_down('a') await self.ahk.key_press('a') @@ -42,7 +42,7 @@ def side_effect(): with mock.MagicMock() as mock_cb, mock.MagicMock() as mock_ex_handler: mock_cb.side_effect = side_effect - self.ahk.add_hotkey(Hotkey('a', callback=mock_cb, ex_handler=mock_ex_handler)) + 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') diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py index af653462..ff96b33a 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -26,7 +26,7 @@ def tearDown(self) -> None: def test_hotkey(self): with mock.MagicMock(return_value=None) as m: - self.ahk.add_hotkey(Hotkey('a', callback=m)) + self.ahk.add_hotkey('a', callback=m) self.ahk.start_hotkeys() self.ahk.key_down('a') self.ahk.key_press('a') @@ -39,7 +39,7 @@ def side_effect(): with mock.MagicMock() as mock_cb, mock.MagicMock() as mock_ex_handler: mock_cb.side_effect = side_effect - self.ahk.add_hotkey(Hotkey('a', callback=mock_cb, ex_handler=mock_ex_handler)) + 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') From eb971c2ef0a79fcb0254b2fac9548a59ea710a91 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Dec 2022 14:04:10 -0800 Subject: [PATCH 303/588] more explicit message on unhandled errors --- ahk/_async/transport.py | 12 +++++++++++- ahk/_sync/transport.py | 8 +++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index b2d01eee..2101d7a2 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -46,6 +46,10 @@ T_SyncFuture = TypeVar('T_SyncFuture') +class AHKProtocolError(Exception): + ... + + class AsyncFutureResult(Generic[T_AsyncFuture]): # unasync: remove def __init__(self, task: asyncio.Task[T_AsyncFuture]): self._task: asyncio.Task[T_AsyncFuture] = task @@ -604,7 +608,13 @@ async def send( content_buffer = BytesIO() content_buffer.write(tom) content_buffer.write(num_lines) - for _ in range(int(num_lines) + 1): + try: + lines_to_read = int(num_lines) + 1 + except ValueError: + raise AHKProtocolError( + 'Unexpected data received. This is usually the result of an unhandled error in the AHK process.' + ) + for _ in range(lines_to_read): part = await self._proc.readline() content_buffer.write(part) content = content_buffer.getvalue()[:-1] diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 89729578..77129fa0 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -44,6 +44,8 @@ T_SyncFuture = TypeVar('T_SyncFuture') +class AHKProtocolError(Exception): + ... @@ -572,7 +574,11 @@ def send( content_buffer = BytesIO() content_buffer.write(tom) content_buffer.write(num_lines) - for _ in range(int(num_lines) + 1): + try: + lines_to_read = int(num_lines) + 1 + except ValueError: + raise AHKProtocolError('Unexpected data received. This is usually the result of an unhandled error in the AHK process.') + for _ in range(lines_to_read): part = self._proc.readline() content_buffer.write(part) content = content_buffer.getvalue()[:-1] From 462c0c8e19021b05c9b9cc12f7f7545c39b751fe Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Dec 2022 14:11:43 -0800 Subject: [PATCH 304/588] fix priority option for hotstrings --- ahk/hotkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/hotkey.py b/ahk/hotkey.py index 4f242ef4..2c9d0e61 100644 --- a/ahk/hotkey.py +++ b/ahk/hotkey.py @@ -307,7 +307,7 @@ def _validate(self) -> None: 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\n+|S[IPE]|T|Z)+', self.options.upper() + r'(\?|C|C1|K\d+|O|P\d+|S[IPE]|T|Z)+', self.options.upper() ), f'Invalid options: {self.options!r}' return None From 5275d9382b2a030307c65ed60c1f6823192d3386 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Dec 2022 15:58:37 -0800 Subject: [PATCH 305/588] use tox 3 until we fix tests with tox 4 --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fbfe3524..213b5594 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,7 +21,7 @@ jobs: python -m pip install --upgrade pip python -m pip install -r requirements-dev.txt python -m pip install . - python -m pip install tox + python -m pip install "tox<4" python -m pip install ahk-binary - name: Test with coverage/pytest env: From 5bcbcb0f6cff9ed381666d641843297a936716de Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Dec 2022 16:28:23 -0800 Subject: [PATCH 306/588] win_activate, hotstring bug fixes, set send level --- ahk/__init__.py | 4 +- ahk/_async/engine.py | 72 ++++++++++++++++++++++++++++-- ahk/_async/transport.py | 13 ++++-- ahk/_async/window.py | 19 ++++++++ ahk/{hotkey.py => _hotkey.py} | 11 +++-- ahk/_sync/engine.py | 82 ++++++++++++++++++++++++++++++++--- ahk/_sync/transport.py | 19 ++++++-- ahk/_sync/window.py | 18 ++++++++ ahk/_utils.py | 28 ++++++++++++ ahk/daemon.ahk | 51 +++++++++++++++++++--- tests/_async/test_hotkeys.py | 1 - tests/_async/test_keys.py | 10 +++++ tests/_async/test_window.py | 5 +++ tests/_sync/test_hotkeys.py | 1 - tests/_sync/test_keys.py | 10 +++++ tests/_sync/test_window.py | 5 +++ 16 files changed, 318 insertions(+), 31 deletions(-) rename ahk/{hotkey.py => _hotkey.py} (96%) create mode 100644 ahk/_utils.py diff --git a/ahk/__init__.py b/ahk/__init__.py index 59754690..5c681140 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -7,10 +7,8 @@ from ._sync import AHK from ._sync import Control from ._sync import Window -from .hotkey import Hotkey -from .hotkey import Hotstring -__all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK', 'Control', 'AsyncControl', 'Hotkey', 'Hotstring'] +__all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK', 'Control', 'AsyncControl'] _global_instance: Optional[AHK] = None diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index c0a14104..19b6fa21 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -18,8 +18,8 @@ from typing import Type from typing import Union -from ..hotkey import Hotkey -from ..hotkey import Hotstring +from .._hotkey import Hotkey +from .._hotkey import Hotstring if sys.version_info < (3, 10): from typing_extensions import TypeAlias @@ -172,7 +172,7 @@ def add_hotstring( trigger: str, replacement_or_callback: Union[str, Callable[[], Any]], *, - ex_handler: Optional[Callable[[str, Exception], Any]], + ex_handler: Optional[Callable[[str, Exception], Any]] = None, options: str = '', ) -> None: """ @@ -944,6 +944,18 @@ async def key_wait( async def run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() + async def set_send_level(self, level: int) -> None: + 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: + 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) -> None: ... @@ -1805,6 +1817,60 @@ async def win_exists( 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]]: + args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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('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: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 2101d7a2..ee0c102e 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -33,7 +33,7 @@ else: from typing import TypeAlias, TypeGuard -from ahk.hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring +from ahk._hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from ahk.message import RequestMessage from ahk.message import ResponseMessage from ahk.message import Position @@ -77,6 +77,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKControlGetText', 'AHKControlSend', 'AHKGetCoordMode', + 'AHKGetSendLevel', 'AHKGetTitleMatchMode', 'AHKGetTitleMatchSpeed', 'AHKImageSearch', @@ -91,7 +92,9 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSendRaw', 'AHKSetDetectHiddenWindows', 'AHKSetCoordMode', + 'AHKSetSendLevel', 'AHKSetTitleMatchMode', + 'AHKWinActivate', 'AHKWinClose', 'AHKWinExist', 'AHKWinGetControlList', @@ -134,7 +137,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'PixelSearch', 'AHKSetCapsLockState', 'SetKeyDelay', - 'WinActivate', 'WinActivateBottom', 'WinClick', 'WinGet', @@ -344,7 +346,7 @@ async def function_call(self, function_name: Literal['WinGetClass'], args: Optio @overload async def function_call(self, function_name: Literal['AHKWinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload - async def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKWinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload @@ -464,6 +466,11 @@ async def function_call(self, function_name: Literal['AHKGetCoordMode'], args: L @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['AHKSetSendLevel'], args: List[str]) -> None: ... + # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 2d8f7256..fb98a328 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -296,6 +296,25 @@ async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutu ) 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 + class AsyncControl: def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): diff --git a/ahk/hotkey.py b/ahk/_hotkey.py similarity index 96% rename from ahk/hotkey.py rename to ahk/_hotkey.py index 2c9d0e61..4e41ba3c 100644 --- a/ahk/hotkey.py +++ b/ahk/_hotkey.py @@ -34,6 +34,8 @@ from jinja2 import Environment, BaseLoader from queue import Queue +from ahk._utils import escape_sequence_replace + P_HotkeyCallbackParam = ParamSpec('P_HotkeyCallbackParam') T_HotkeyCallbackReturn = TypeVar('T_HotkeyCallbackReturn') @@ -250,13 +252,13 @@ def __init__( trigger: str, replacement_or_callback: Union[str, Callable[[], Any]], *, - ex_handler: Optional[Callable[[str, Exception], 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 = trigger + self._trigger: str = escape_sequence_replace(trigger) self._options: str = options if callable(replacement_or_callback): self.replacement = None @@ -293,7 +295,7 @@ def __eq__(self, other: Any) -> bool: @property def _id(self) -> str: - return str(hash(self)) + return str(hash(self)).replace('-', '0') @property def _replacement_as_b64(self) -> str: @@ -302,7 +304,8 @@ def _replacement_as_b64(self) -> str: return str(b64encode(data), 'UTF-8') def _validate(self) -> None: - assert '\n' not in self.trigger, 'Newlines not allowed in trigger' + 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.' diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 7fb00354..0bc1ccb1 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -18,8 +18,8 @@ from typing import Type from typing import Union -from ..hotkey import Hotkey -from ..hotkey import Hotstring +from .._hotkey import Hotkey +from .._hotkey import Hotstring if sys.version_info < (3, 10): from typing_extensions import TypeAlias @@ -140,7 +140,9 @@ def __getattr__(self, item: Any) -> Any: return deprecation_replacements[item] raise AttributeError(f'{self.__class__.__qualname__!r} object has no attribute {item!r}') - def add_hotkey(self, keyname: str, callback: Callable[[], Any], *, ex_handler: Optional[Callable[[str, Exception], Any]] = None) -> 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. @@ -161,12 +163,14 @@ def add_hotkey(self, keyname: str, callback: Callable[[], Any], *, ex_handler: O warnings.warn(warning.message, warning.category, stacklevel=2) return None - def add_hotstring(self, trigger: str, + def add_hotstring( + self, + trigger: str, replacement_or_callback: Union[str, Callable[[], Any]], *, - ex_handler: Optional[Callable[[str, Exception], Any]], + ex_handler: Optional[Callable[[str, Exception], Any]] = None, options: str = '', -) -> None: + ) -> None: """ Register a hotstring, e.g., `::btw::by the way` @@ -930,6 +934,18 @@ def key_wait( def run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: raise NotImplementedError() + def set_send_level(self, level: int) -> None: + 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: + 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) -> None: ... @@ -1791,6 +1807,60 @@ def win_exists( 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]]: + args = [title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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('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: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 77129fa0..66977357 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -33,7 +33,7 @@ else: from typing import TypeAlias, TypeGuard -from ahk.hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring +from ahk._hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from ahk.message import RequestMessage from ahk.message import ResponseMessage from ahk.message import Position @@ -44,11 +44,13 @@ T_SyncFuture = TypeVar('T_SyncFuture') + class AHKProtocolError(Exception): ... + class FutureResult(Generic[T_SyncFuture]): def __init__(self, future: Future[T_SyncFuture]): self._fut: Future[T_SyncFuture] = future @@ -67,6 +69,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKControlGetText', 'AHKControlSend', 'AHKGetCoordMode', + 'AHKGetSendLevel', 'AHKGetTitleMatchMode', 'AHKGetTitleMatchSpeed', 'AHKImageSearch', @@ -81,7 +84,9 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSendRaw', 'AHKSetDetectHiddenWindows', 'AHKSetCoordMode', + 'AHKSetSendLevel', 'AHKSetTitleMatchMode', + 'AHKWinActivate', 'AHKWinClose', 'AHKWinExist', 'AHKWinGetControlList', @@ -124,7 +129,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'PixelSearch', 'AHKSetCapsLockState', 'SetKeyDelay', - 'WinActivate', 'WinActivateBottom', 'WinClick', 'WinGet', @@ -325,7 +329,7 @@ def function_call(self, function_name: Literal['WinGetClass'], args: Optional[Li @overload def function_call(self, function_name: Literal['AHKWinGetText'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinActivate'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[None, FutureResult[None]]: ... @overload @@ -445,6 +449,11 @@ def function_call(self, function_name: Literal['AHKGetCoordMode'], args: List[st @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['AHKSetSendLevel'], args: List[str]) -> None: ... + # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload @@ -577,7 +586,9 @@ def send( try: lines_to_read = int(num_lines) + 1 except ValueError: - raise AHKProtocolError('Unexpected data received. This is usually the result of an unhandled error in the AHK process.') + raise AHKProtocolError( + 'Unexpected data received. This is usually the result of an unhandled error in the AHK process.' + ) for _ in range(lines_to_read): part = self._proc.readline() content_buffer.write(part) diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 00d64d72..477c6034 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -275,6 +275,24 @@ def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Op ) 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 class Control: def __init__(self, window: Window, hwnd: str, control_class: str): diff --git a/ahk/_utils.py b/ahk/_utils.py new file mode 100644 index 00000000..8f0ae281 --- /dev/null +++ b/ahk/_utils.py @@ -0,0 +1,28 @@ +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 escape_sequence_replace(s: str) -> str: + """ + 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/daemon.ahk b/ahk/daemon.ahk index d02f1476..cb5014bf 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -57,6 +57,17 @@ AHKGetTitleMatchSpeed(ByRef command) { return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchModeSpeed) } +AHKSetSendLevel(ByRef command) { + level := command[2] + SendLevel, %level% + return FormatNoValueResponse() +} + +AHKGetSendLevel(ByRef command) { + global INTEGERRESPONSEMESSAGE + return FormatResponse(INTEGERRESPONSEMESSAGE, A_SendLevel) +} + AHKWinExist(ByRef command) { global BOOLEANRESPONSEMESSAGE title := command[2] @@ -87,6 +98,11 @@ AHKWinExist(ByRef command) { } else { resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp } @@ -1408,14 +1424,37 @@ WinGetClass(ByRef command) { return text } -WinActivate(ByRef command) { +AHKWinActivate(ByRef command) { title := command[2] - if (command.Length() = 2) { - WinActivate, %title% - } else { - secondstowait := command[3] - WinActivate, %title%, %secondstowait% + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% } + + WinActivate, %title%, %text%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() } WinActivateBottom(ByRef command) { diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index 21d021a9..9ca18644 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -7,7 +7,6 @@ from ahk import AsyncAHK from ahk import AsyncWindow -from ahk.hotkey import Hotkey async_sleep = asyncio.sleep # unasync: remove diff --git a/tests/_async/test_keys.py b/tests/_async/test_keys.py index 7972cfba..efd0155c 100644 --- a/tests/_async/test_keys.py +++ b/tests/_async/test_keys.py @@ -31,3 +31,13 @@ async def asyncTearDown(self) -> None: 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() diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 500bad07..bde7d465 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -158,3 +158,8 @@ async def test_control_position(self): 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 diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py index ff96b33a..e49279e2 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -6,7 +6,6 @@ from ahk import AHK from ahk import Window -from ahk.hotkey import Hotkey sleep = time.sleep diff --git a/tests/_sync/test_keys.py b/tests/_sync/test_keys.py index 9973930f..26f2d71f 100644 --- a/tests/_sync/test_keys.py +++ b/tests/_sync/test_keys.py @@ -31,3 +31,13 @@ def tearDown(self) -> None: 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() diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 7d1d3726..c965af80 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -158,3 +158,8 @@ def test_control_position(self): 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 From 6e7fe64a5a0210688f17bb9f481826bee73fa5af Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Dec 2022 17:22:39 -0800 Subject: [PATCH 307/588] fix build-system, use tox4 --- .github/workflows/test.yaml | 2 +- pyproject.toml | 2 ++ setup.cfg | 3 --- 3 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 pyproject.toml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 213b5594..fbfe3524 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,7 +21,7 @@ jobs: python -m pip install --upgrade pip python -m pip install -r requirements-dev.txt python -m pip install . - python -m pip install "tox<4" + python -m pip install tox python -m pip install ahk-binary - name: Test with coverage/pytest env: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..209e9dcb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires = ["setuptools", "unasync", "tokenize-rt"] diff --git a/setup.cfg b/setup.cfg index 03402423..1b2e6bbf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,6 +47,3 @@ ahk = py.typed daemon.ahk hotkeys.ahk - -[build-system] -requires = ["setuptools", "unasync", "tokenize-rt"] From d4e1d38121e8437934d82b888d4621d4ac9fd30e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Dec 2022 18:02:56 -0800 Subject: [PATCH 308/588] win_get_from_mouse_position --- ahk/_async/engine.py | 3 ++- ahk/_async/transport.py | 4 ++-- ahk/_constants.py | 0 ahk/_sync/engine.py | 3 ++- ahk/_sync/transport.py | 4 ++-- ahk/daemon.ahk | 10 ++++++++-- 6 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 ahk/_constants.py diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 19b6fa21..84151d72 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1761,7 +1761,8 @@ async def win_get_from_mouse_position(self, *, blocking: bool = True) -> Union[O async def win_get_from_mouse_position( self, *, blocking: bool = True ) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: - raise NotImplementedError() + resp = await self._transport.function_call('AHKWinFromMouse', blocking=blocking, engine=self) + return resp # fmt: off @overload diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index ee0c102e..3f1484f3 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -97,6 +97,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinActivate', 'AHKWinClose', 'AHKWinExist', + 'AHKWinFromMouse', 'AHKWinGetControlList', 'AHKWinGetControlListHwnd', 'AHKWinGetCount', @@ -131,7 +132,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWindowList', 'AHKClick', 'CoordMode', - 'FromMouse', 'MouseClickDrag', 'PixelGetColor', 'PixelSearch', @@ -372,7 +372,7 @@ async def function_call(self, function_name: Literal['WinSendRaw'], args: Option @overload async def function_call(self, function_name: Literal['AHKControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... + async def function_call(self, function_name: Literal['AHKWinFromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: ... @overload async def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload diff --git a/ahk/_constants.py b/ahk/_constants.py new file mode 100644 index 00000000..e69de29b diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 0bc1ccb1..bc8e35c5 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1751,7 +1751,8 @@ def win_get_from_mouse_position(self, *, blocking: bool = True) -> Union[Optiona def win_get_from_mouse_position( self, *, blocking: bool = True ) -> Union[Optional[Window], FutureResult[Optional[Window]]]: - raise NotImplementedError() + resp = self._transport.function_call('AHKWinFromMouse', blocking=blocking, engine=self) + return resp # fmt: off @overload diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 66977357..484562fb 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -89,6 +89,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinActivate', 'AHKWinClose', 'AHKWinExist', + 'AHKWinFromMouse', 'AHKWinGetControlList', 'AHKWinGetControlListHwnd', 'AHKWinGetCount', @@ -123,7 +124,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWindowList', 'AHKClick', 'CoordMode', - 'FromMouse', 'MouseClickDrag', 'PixelGetColor', 'PixelSearch', @@ -355,7 +355,7 @@ def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[Lis @overload def function_call(self, function_name: Literal['AHKControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['FromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... + def function_call(self, function_name: Literal['AHKWinFromMouse'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Optional[Window], FutureResult[Optional[Window]]]: ... @overload def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... @overload diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index cb5014bf..c2bef5b2 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1795,9 +1795,15 @@ AHKControlSend(ByRef command) { ; } ;} -FromMouse(ByRef command) { +AHKWinFromMouse(ByRef command) { + global WINDOWRESPONSEMESSAGE MouseGetPos,,, MouseWin - return MouseWin + + if (MouseWin = "") { + return FormatNoValueResponse() + } + + return FormatResponse(WINDOWRESPONSEMESSAGE, MouseWin) } ;WinGet(ByRef command) { From edc52cb5bfd4b8bbfa47c8f4637f8bb5e860d9cc Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Dec 2022 20:53:49 -0800 Subject: [PATCH 309/588] mouse_drag --- ahk/_async/engine.py | 27 +++++++++++++++++++++++---- ahk/_async/transport.py | 4 ++-- ahk/_sync/engine.py | 28 ++++++++++++++++++++++++---- ahk/_sync/transport.py | 4 ++-- ahk/daemon.ahk | 29 ++++++++++++++++++++++------- 5 files changed, 73 insertions(+), 19 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 84151d72..3c9553c5 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2669,16 +2669,35 @@ async def image_search( async def mouse_drag( self, x: int, - y: Optional[int] = None, + y: int, *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, - button: Union[str, int] = 1, + button: MouseButton = 1, relative: Optional[bool] = None, blocking: bool = True, - mode: Optional[CoordMode] = None, + coord_mode: Optional[CoordModeRelativeTo] = None, ) -> None: - raise NotImplementedError() + 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) + + await self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) async def pixel_get_color( self, x: int, y: int, coord_mode: str = 'Screen', alt: bool = False, slow: bool = False, rgb: bool = True diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 3f1484f3..ae385635 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -83,6 +83,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKImageSearch', 'AHKKeyState', 'AHKKeyWait', + 'AHKMouseClickDrag', 'AHKMouseGetPos', 'AHKMouseMove', 'AHKSend', @@ -132,7 +133,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWindowList', 'AHKClick', 'CoordMode', - 'MouseClickDrag', 'PixelGetColor', 'PixelSearch', 'AHKSetCapsLockState', @@ -322,7 +322,7 @@ async def function_call(self, function_name: Literal['CoordMode'], args: Optiona @overload async def function_call(self, function_name: Literal['AHKClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKMouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = None) -> Union[int, AsyncFutureResult[int]]: ... @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index bc8e35c5..bbe9fb62 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2659,16 +2659,36 @@ def image_search( def mouse_drag( self, x: int, - y: Optional[int] = None, + y: int, *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, - button: Union[str, int] = 1, + button: MouseButton = 1, relative: Optional[bool] = None, blocking: bool = True, - mode: Optional[CoordMode] = None, + coord_mode: Optional[CoordModeRelativeTo] = None, ) -> None: - raise NotImplementedError() + 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) + + self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) + def pixel_get_color( self, x: int, y: int, coord_mode: str = 'Screen', alt: bool = False, slow: bool = False, rgb: bool = True diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 484562fb..a00887d6 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -75,6 +75,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKImageSearch', 'AHKKeyState', 'AHKKeyWait', + 'AHKMouseClickDrag', 'AHKMouseGetPos', 'AHKMouseMove', 'AHKSend', @@ -124,7 +125,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWindowList', 'AHKClick', 'CoordMode', - 'MouseClickDrag', 'PixelGetColor', 'PixelSearch', 'AHKSetCapsLockState', @@ -305,7 +305,7 @@ def function_call(self, function_name: Literal['CoordMode'], args: Optional[List @overload def function_call(self, function_name: Literal['AHKClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['MouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKMouseClickDrag'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[int, FutureResult[int]]: ... @overload diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index c2bef5b2..0ca52298 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1239,15 +1239,30 @@ AHKSetCoordMode(ByRef command) { return FormatNoValueResponse() } -MouseClickDrag(ByRef command) { +AHKMouseClickDrag(ByRef command) { button := command[2] - if (command.Length() = 6) { - MouseClickDrag,%button%,command[3],command[4],command[5],command[6] - } else if (command.Length() = 7) { - MouseClickDrag,%button%,command[3],command[4],command[5],command[6],command[7] - } else if (command.Length() = 8) { - MouseClickDrag,%button%,command[3],command[4],command[5],command[6],command[7],R + x1 := command[3] + y1 := command[4] + x2 := command[5] + y2 := command[6] + speed := command[7] + relative := command[8] + relative_to := command[9] + + 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% } + + return FormatNoValueResponse() + } RegRead(ByRef command) { From 8d8af45b9302446ff89cead1b0456f789852174c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 15 Dec 2022 14:14:56 -0800 Subject: [PATCH 310/588] pixelgetcolor/pixelsearch --- ahk/_async/engine.py | 76 +++++++++++++++++++++++++++--------- ahk/_async/transport.py | 8 ++-- ahk/_sync/engine.py | 77 ++++++++++++++++++++++++++++--------- ahk/_sync/transport.py | 8 ++-- ahk/daemon.ahk | 75 ++++++++++++++++++++++++------------ tests/_async/test_screen.py | 27 +++++++++++-- tests/_sync/test_screen.py | 27 +++++++++++-- 7 files changed, 221 insertions(+), 77 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 3c9553c5..f1c9981c 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2606,13 +2606,13 @@ async def click( # 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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None) -> Optional[Tuple[int, int]]: ... + 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[Tuple[int, int]]: ... @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[False]) -> AsyncFutureResult[Optional[Tuple[int, int]]]: ... + 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[Tuple[int, int]]]: ... @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[True]) -> Optional[Tuple[int, int]]: ... + 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[Tuple[int, int]]: ... @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: bool = True) -> Union[Tuple[int, int], None, AsyncFutureResult[Optional[Tuple[int, int]]]]: ... + 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[Tuple[int, int], None, AsyncFutureResult[Optional[Tuple[int, int]]]]: ... # fmt: on async def image_search( self, @@ -2621,7 +2621,7 @@ async def image_search( lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, *, color_variation: Optional[int] = None, - coord_mode: str = 'Screen', + coord_mode: Optional[CoordModeRelativeTo] = None, scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, @@ -2656,11 +2656,8 @@ async def image_search( args = [str(x1), str(y1), str(x2), str(y2)] if options: - s = '' - for opt in options: - s += f'*{opt} ' - s += image_path - args.append(s) + opts = ' '.join(f'*{opt}' for opt in options) + args.append(opts) else: args.append(image_path) resp = await self._transport.function_call('AHKImageSearch', args, blocking=blocking) @@ -2699,22 +2696,65 @@ async def mouse_drag( await self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) + # 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: str = 'Screen', alt: bool = False, slow: bool = False, rgb: bool = True - ) -> str: - raise NotImplementedError() + 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]]: + 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[Tuple[int, int]]: ... + @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[Tuple[int, int]]: ... + @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[Tuple[int, int]]]: ... + @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[Tuple[int, int]], AsyncFutureResult[Optional[Tuple[int, int]]]]: ... + # 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, - upper_bound: Tuple[int, int] = (0, 0), - lower_bound: Optional[Tuple[int, int]] = None, - coord_mode: str = 'Screen', + *, + coord_mode: Optional[CoordModeRelativeTo] = None, fast: bool = True, rgb: bool = True, - ) -> Union[Tuple[int, int], None]: - raise NotImplementedError() + blocking: bool = True, + ) -> Union[Optional[Tuple[int, int]], AsyncFutureResult[Optional[Tuple[int, int]]]]: + 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 async def show_traytip( self, diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index ae385635..f00016e4 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -86,6 +86,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKMouseClickDrag', 'AHKMouseGetPos', 'AHKMouseMove', + 'AHKPixelGetColor', + 'AHKPixelSearch', 'AHKSend', 'AHKSendEvent', 'AHKSendInput', @@ -133,8 +135,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWindowList', 'AHKClick', 'CoordMode', - 'PixelGetColor', - 'PixelSearch', 'AHKSetCapsLockState', 'SetKeyDelay', 'WinActivateBottom', @@ -308,9 +308,9 @@ async def function_call(self, function_name: Literal['AHKWinExist'], args: Optio @overload async def function_call(self, function_name: Literal['AHKImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], None, AsyncFutureResult[Union[Tuple[int, int], None]]]: ... @overload - async def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... + async def function_call(self, function_name: Literal['AHKPixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload - async def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... + async def function_call(self, function_name: Literal['AHKPixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Optional[Tuple[int, int]], AsyncFutureResult[Optional[Tuple[int, int]]]]: ... @overload async def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... @overload diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index bbe9fb62..8c172a07 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2596,13 +2596,13 @@ def click( # 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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None) -> Optional[Tuple[int, int]]: ... + 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[Tuple[int, int]]: ... @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[False]) -> FutureResult[Optional[Tuple[int, int]]]: ... + 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[Tuple[int, int]]]: ... @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: Literal[True]) -> Optional[Tuple[int, int]]: ... + 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[Tuple[int, int]]: ... @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: str = 'Screen', scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, icon: Optional[int] = None, blocking: bool = True) -> Union[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: ... + 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[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: ... # fmt: on def image_search( self, @@ -2611,7 +2611,7 @@ def image_search( lower_bound: Optional[Tuple[Union[int, str], Union[int, str]]] = None, *, color_variation: Optional[int] = None, - coord_mode: str = 'Screen', + coord_mode: Optional[CoordModeRelativeTo] = None, scale_height: Optional[int] = None, scale_width: Optional[int] = None, transparent: Optional[str] = None, @@ -2646,11 +2646,8 @@ def image_search( args = [str(x1), str(y1), str(x2), str(y2)] if options: - s = '' - for opt in options: - s += f'*{opt} ' - s += image_path - args.append(s) + opts = ' '.join(f'*{opt}' for opt in options) + args.append(opts) else: args.append(image_path) resp = self._transport.function_call('AHKImageSearch', args, blocking=blocking) @@ -2689,23 +2686,65 @@ def mouse_drag( self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) - + # 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: str = 'Screen', alt: bool = False, slow: bool = False, rgb: bool = True - ) -> str: - raise NotImplementedError() + 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]]: + 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[Tuple[int, int]]: ... + @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[Tuple[int, int]]: ... + @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[Tuple[int, int]]]: ... + @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[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: ... + # 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, - upper_bound: Tuple[int, int] = (0, 0), - lower_bound: Optional[Tuple[int, int]] = None, - coord_mode: str = 'Screen', + *, + coord_mode: Optional[CoordModeRelativeTo] = None, fast: bool = True, rgb: bool = True, - ) -> Union[Tuple[int, int], None]: - raise NotImplementedError() + blocking: bool = True, + ) -> Union[Optional[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: + 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 def show_traytip( self, diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index a00887d6..1d39c182 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -78,6 +78,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKMouseClickDrag', 'AHKMouseGetPos', 'AHKMouseMove', + 'AHKPixelGetColor', + 'AHKPixelSearch', 'AHKSend', 'AHKSendEvent', 'AHKSendInput', @@ -125,8 +127,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWindowList', 'AHKClick', 'CoordMode', - 'PixelGetColor', - 'PixelSearch', 'AHKSetCapsLockState', 'SetKeyDelay', 'WinActivateBottom', @@ -291,9 +291,9 @@ def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[Li @overload def function_call(self, function_name: Literal['AHKImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], None, FutureResult[Union[Tuple[int, int], None]]]: ... @overload - def function_call(self, function_name: Literal['PixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... + def function_call(self, function_name: Literal['AHKPixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['PixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... + def function_call(self, function_name: Literal['AHKPixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Optional[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: ... @overload def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... @overload diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 0ca52298..20797c53 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1089,37 +1089,64 @@ AHKImageSearch(ByRef command) { return s } -PixelGetColor(ByRef command) { - x := command[3] - y := command[4] - if (command.Length() = 4) { - PixelGetColor,color,% x,% y - } else { - options := command[5] - PixelGetColor,color,% x,% y, %options% +AHKPixelGetColor(ByRef command) { + global STRINGRESPONSEMESSAGE + x := command[2] + y := command[3] + coord_mode := command[4] + options := command[5] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode, Pixel, %coord_mode% } - return color + + PixelGetColor, color, %x%, %y%, %options% + ; TODO: check errorlevel + + if (coord_mode != "") { + CoordMode, Pixel, %current_mode% + } + + return FormatResponse(STRINGRESPONSEMESSAGE, color) } -PixelSearch(ByRef command) { - x1 := command[4] - y1 := command[5] - x2 := command[6] - y2 := command[7] - if (x2 = "A_ScreenWidth") { - x2 := A_ScreenWidth +AHKPixelSearch(ByRef command) { + global COORDINATERESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + x1 := command[2] + y1 := command[3] + x2 := command[4] + y2 := command[5] + color := command[6] + variation := command[7] + options := command[8] + coord_mode := command[9] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode, Pixel, %coord_mode% } - if (y2 = "A_ScreenHeight") { - y2 := A_ScreenHeight + + PixelSearch, resultx, resulty, %x1%, %y1%, %x2%, %y2%, %color%, %variation%, %options% + + if (coord_mode != "") { + CoordMode, Pixel, %current_mode% } - if (command.Length() = 9) { - PixelSearch, xpos, ypos,% x1,% y1,% x2,% y2, command[8], command[9] + + if (ErrorLevel = 1) { + return FormatNoValueResponse() + } else if (ErrorLevel = 0) { + payload := Format("({}, {})", resultx, resulty) + return FormatResponse(COORDINATERESPONSEMESSAGE, payload) + } else if (ErrorLevel = 2) { + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem conducting the pixel search (ErrorLevel 2)") } else { - options := command[10] - PixelSearch, xpos, ypos,% x1,% y1,% x2,% y2, command[8], command[9], %options% + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Unexpected error. This is probably a bug. Please report this at https://github.com/spyoungtech/ahk/issues") } - s := Format("({}, {})", xpos, ypos) - return s + } diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 63859c44..082eeb9b 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -1,5 +1,7 @@ import asyncio import os +import threading +import time from itertools import product from unittest import IsolatedAsyncioTestCase @@ -32,15 +34,32 @@ async def asyncTearDown(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('Does not work in GitHub Actions') - return - self.im.show() + self._show_in_thread() + time.sleep(2) self.im.save('testimage.png') position = await self.ahk.image_search('testimage.png') assert isinstance(position, tuple) + async def test_pixel_search(self): + self._show_in_thread() + time.sleep(2) + self.im.save('testimage.png') + position = await self.ahk.image_search('testimage.png') + 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_pixel_get_color(self): # x, y = await self.ahk.pixel_search(0xFF0000) # result = await self.ahk.pixel_get_color(x, y) diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index 3c554436..0a10813e 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -1,5 +1,7 @@ import asyncio import os +import threading +import time from itertools import product from unittest import TestCase @@ -32,15 +34,32 @@ def tearDown(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('Does not work in GitHub Actions') - return - self.im.show() + self._show_in_thread() + time.sleep(2) self.im.save('testimage.png') position = self.ahk.image_search('testimage.png') assert isinstance(position, tuple) + def test_pixel_search(self): + self._show_in_thread() + time.sleep(2) + self.im.save('testimage.png') + position = self.ahk.image_search('testimage.png') + 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 + # async def test_pixel_get_color(self): # x, y = await self.ahk.pixel_search(0xFF0000) # result = await self.ahk.pixel_get_color(x, y) From 46c8a339f0b92cf7a7f6554646f7055b31a3e02a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 15 Dec 2022 14:24:24 -0800 Subject: [PATCH 311/588] remove tests that don't work in CI --- tests/_async/test_screen.py | 6 ++++++ tests/_sync/test_screen.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 082eeb9b..da66a96b 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -40,6 +40,9 @@ def _show_in_thread(self): 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(2) self.im.save('testimage.png') @@ -47,6 +50,9 @@ async def test_image_search(self): 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(2) self.im.save('testimage.png') diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index 0a10813e..c60c40c6 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -40,6 +40,9 @@ def _show_in_thread(self): 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(2) self.im.save('testimage.png') @@ -47,6 +50,9 @@ def test_image_search(self): 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(2) self.im.save('testimage.png') From fd0a8cb0422765fb5e05c397780eeaf95fada302 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 15 Dec 2022 14:44:35 -0800 Subject: [PATCH 312/588] refactor win args --- ahk/_async/engine.py | 744 +++++++++++-------------------------------- ahk/_sync/engine.py | 744 +++++++++++-------------------------------- 2 files changed, 364 insertions(+), 1124 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index f1c9981c..74483362 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -529,27 +529,15 @@ async def set_detect_hidden_windows(self, value: bool) -> None: await self._transport.function_call('AHKSetDetectHiddenWindows', args=args) return None - # 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]) -> Union[List[AsyncWindow], 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 = '', + @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, - blocking: bool = True, - ) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: + ) -> List[str]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -580,6 +568,37 @@ async def list_windows( 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]) -> Union[List[AsyncWindow], 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]]]: + 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 @@ -1151,9 +1170,6 @@ async def sound_set( ) -> None: raise NotImplementedError() - async def type(self, s: str, blocking: bool = True) -> None: - raise NotImplementedError() - # fmt: off @overload 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) -> Union[AsyncWindow, None]: ... @@ -1175,36 +1191,14 @@ async def win_get( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[None, AsyncWindow]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1229,36 +1223,14 @@ async def win_get_text( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, AsyncFutureResult[str]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1283,36 +1255,14 @@ async def win_get_title( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, AsyncFutureResult[str]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1337,36 +1287,14 @@ async def win_get_position( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Position, None, AsyncFutureResult[Union[Position, None]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1391,36 +1319,14 @@ async def win_get_idlast( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[AsyncWindow, None]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1445,36 +1351,14 @@ async def win_get_pid( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[int, None, AsyncFutureResult[Union[int, None]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1499,36 +1383,14 @@ async def win_get_process_name( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, str, AsyncFutureResult[Optional[str]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1553,36 +1415,14 @@ async def win_get_process_path( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, None, Union[None, str, AsyncFutureResult[Optional[str]]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1607,36 +1447,14 @@ async def win_get_count( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[int, AsyncFutureResult[int]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1661,36 +1479,14 @@ async def win_get_minmax( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, int, AsyncFutureResult[Optional[int]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1715,36 +1511,14 @@ async def win_get_control_list( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[List[AsyncControl], None, AsyncFutureResult[Optional[List[AsyncControl]]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1785,36 +1559,14 @@ async def win_exists( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[bool, AsyncFutureResult[bool]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1839,36 +1591,14 @@ async def win_activate( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -2003,36 +1733,14 @@ async def win_set_bottom( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -2057,36 +1765,14 @@ async def win_set_top( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -2111,36 +1797,14 @@ async def win_set_disable( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -2165,36 +1829,14 @@ async def win_set_enable( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -2219,36 +1861,14 @@ async def win_set_redraw( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 8c172a07..eecceec4 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -525,27 +525,15 @@ def set_detect_hidden_windows(self, value: bool) -> None: self._transport.function_call('AHKSetDetectHiddenWindows', args=args) return None - # 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]) -> Union[List[Window], 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 = '', + @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, - blocking: bool = True, - ) -> Union[List[Window], FutureResult[List[Window]]]: + ) -> List[str]: args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -576,6 +564,37 @@ def list_windows( 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]) -> Union[List[Window], 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]]]: + 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 @@ -1141,9 +1160,6 @@ def sound_set( ) -> None: raise NotImplementedError() - def type(self, s: str, blocking: bool = True) -> None: - raise NotImplementedError() - # fmt: off @overload 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) -> Union[Window, None]: ... @@ -1165,36 +1181,14 @@ def win_get( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Window, None, FutureResult[Union[None, Window]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1219,36 +1213,14 @@ def win_get_text( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, FutureResult[str]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1273,36 +1245,14 @@ def win_get_title( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, FutureResult[str]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1327,36 +1277,14 @@ def win_get_position( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Position, None, FutureResult[Union[Position, None]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1381,36 +1309,14 @@ def win_get_idlast( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Window, None, FutureResult[Union[Window, None]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1435,36 +1341,14 @@ def win_get_pid( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[int, None, FutureResult[Union[int, None]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1489,36 +1373,14 @@ def win_get_process_name( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, str, FutureResult[Optional[str]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1543,36 +1405,14 @@ def win_get_process_path( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, None, Union[None, str, FutureResult[Optional[str]]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1597,36 +1437,14 @@ def win_get_count( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[int, FutureResult[int]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1651,36 +1469,14 @@ def win_get_minmax( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, int, FutureResult[Optional[int]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1705,36 +1501,14 @@ def win_get_control_list( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[List[Control], None, FutureResult[Optional[List[Control]]]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1775,36 +1549,14 @@ def win_exists( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[bool, FutureResult[bool]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1829,36 +1581,14 @@ def win_activate( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -1993,36 +1723,14 @@ def win_set_bottom( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -2047,36 +1755,14 @@ def win_set_top( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -2101,36 +1787,14 @@ def win_set_disable( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -2155,36 +1819,14 @@ def win_set_enable( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 @@ -2209,36 +1851,14 @@ def win_set_redraw( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: - args = [title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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 From 199ea72e7b8b74c903dd521ca7999fb50f947cb5 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 15 Dec 2022 16:09:18 -0800 Subject: [PATCH 313/588] add win_get_class --- ahk/_async/engine.py | 32 +++++++++++++++++++++++++++++ ahk/_async/transport.py | 6 +++--- ahk/_async/window.py | 15 ++++++++++++++ ahk/_sync/engine.py | 32 +++++++++++++++++++++++++++++ ahk/_sync/transport.py | 6 +++--- ahk/_sync/window.py | 15 ++++++++++++++ ahk/daemon.ahk | 41 +++++++++++++++++++++++++++++++++---- tests/_async/test_keys.py | 2 ++ tests/_async/test_window.py | 10 ++++++++- tests/_sync/test_keys.py | 2 ++ tests/_sync/test_window.py | 10 ++++++++- 11 files changed, 159 insertions(+), 12 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 74483362..42cc1d9e 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1266,6 +1266,38 @@ async def win_get_title( 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]]: + 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, 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]: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index f00016e4..1dc6cfa5 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -140,7 +140,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'WinActivateBottom', 'WinClick', 'WinGet', - 'WinGetClass', + 'AHKWinGetClass', 'WinHide', 'WinKill', 'WinMaximize', @@ -210,7 +210,7 @@ async def readline(self) -> bytes: return line def kill(self) -> None: - assert self._proc is not None + assert self._proc is not None, 'no process to kill' self._proc.kill() @@ -342,7 +342,7 @@ async def function_call(self, function_name: Literal['AHKSetCapsLockState'], arg @overload async def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload - async def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... + async def function_call(self, function_name: Literal['AHKWinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = None) -> Union[str, AsyncFutureResult[str]]: ... @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index fb98a328..1e988c20 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -184,6 +184,21 @@ async def list_controls(self) -> Sequence['AsyncControl']: ) return controls + # 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: ... diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index eecceec4..2a31d55a 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1256,6 +1256,38 @@ def win_get_title( 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]]: + 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, 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]: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 1d39c182..f30a527d 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -132,7 +132,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'WinActivateBottom', 'WinClick', 'WinGet', - 'WinGetClass', + 'AHKWinGetClass', 'WinHide', 'WinKill', 'WinMaximize', @@ -197,7 +197,7 @@ def readline(self) -> bytes: return line def kill(self) -> None: - assert self._proc is not None + assert self._proc is not None, 'no process to kill' self._proc.kill() @@ -325,7 +325,7 @@ def function_call(self, function_name: Literal['AHKSetCapsLockState'], args: Opt @overload def function_call(self, function_name: Literal['AHKWinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... @overload - def function_call(self, function_name: Literal['WinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... + def function_call(self, function_name: Literal['AHKWinGetClass'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[str, FutureResult[str]]: ... @overload diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 477c6034..4703b4ca 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -164,6 +164,20 @@ def list_controls(self) -> Sequence['Control']: ) return controls + # 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: ... @@ -294,6 +308,7 @@ def activate(self, blocking: bool = True) -> Union[None, FutureResult[None]]: ) return resp + class Control: def __init__(self, window: Window, hwnd: str, control_class: str): self.window: Window = window diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 20797c53..43b63207 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1460,10 +1460,43 @@ HideTrayTip(ByRef command) { -WinGetClass(ByRef command) { - title := command[3] - WinGetClass, text, %title% - return text +AHKWinGetClass(ByRef command) { + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGetClass, output,%title%,%text%,%extitle%,%extext% + + if (ErrorLevel = 1) { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window class") + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response } AHKWinActivate(ByRef command) { diff --git a/tests/_async/test_keys.py b/tests/_async/test_keys.py index efd0155c..51f9abd9 100644 --- a/tests/_async/test_keys.py +++ b/tests/_async/test_keys.py @@ -26,7 +26,9 @@ async def asyncTearDown(self) -> None: 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') diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index bde7d465..36be3134 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -3,8 +3,11 @@ import subprocess import sys import time +import tracemalloc from unittest import IsolatedAsyncioTestCase +tracemalloc.start() + from ahk import AsyncAHK from ahk import AsyncWindow @@ -22,9 +25,11 @@ async def asyncSetUp(self) -> None: async def asyncTearDown(self) -> None: try: self.p.kill() - except Exception: + except: pass + self.p.communicate() self.ahk._transport._proc.kill() + time.sleep(0.2) async def test_exists(self): self.assertTrue(await self.ahk.win_exists(title='Untitled - Notepad')) @@ -163,3 +168,6 @@ 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' diff --git a/tests/_sync/test_keys.py b/tests/_sync/test_keys.py index 26f2d71f..d3348cba 100644 --- a/tests/_sync/test_keys.py +++ b/tests/_sync/test_keys.py @@ -26,7 +26,9 @@ def tearDown(self) -> None: 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') diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index c965af80..b2f3d23b 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -3,8 +3,11 @@ import subprocess import sys import time +import tracemalloc from unittest import TestCase +tracemalloc.start() + from ahk import AHK from ahk import Window @@ -22,9 +25,11 @@ def setUp(self) -> None: def tearDown(self) -> None: try: self.p.kill() - except Exception: + except: pass + self.p.communicate() self.ahk._transport._proc.kill() + time.sleep(0.2) def test_exists(self): self.assertTrue(self.ahk.win_exists(title='Untitled - Notepad')) @@ -163,3 +168,6 @@ 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' From 8687e7a2cbc156e74a5266ae009d79042233a96e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 15 Dec 2022 16:52:00 -0800 Subject: [PATCH 314/588] win_kill --- ahk/_async/engine.py | 32 +++++++++++++++++++++++++ ahk/_async/transport.py | 4 ++-- ahk/_async/window.py | 5 ++++ ahk/_sync/engine.py | 32 +++++++++++++++++++++++++ ahk/_sync/transport.py | 4 ++-- ahk/_sync/window.py | 10 ++++++-- ahk/daemon.ahk | 47 ++++++++++++++++++++++++++++++------- tests/_async/test_mouse.py | 7 ++++++ tests/_async/test_screen.py | 6 +++-- tests/_async/test_window.py | 2 +- tests/_sync/test_mouse.py | 7 ++++++ tests/_sync/test_screen.py | 6 +++-- tests/_sync/test_window.py | 2 +- 13 files changed, 143 insertions(+), 21 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 42cc1d9e..847b15d5 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2477,6 +2477,38 @@ async def win_close( resp = await self._transport.function_call('AHKWinClose', args=args, blocking=blocking) return resp + # fmt: off + @overload + async def win_kill(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_kill(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_kill(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_kill(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_kill( + 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]]: + 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('AHKWinKill', args, engine=self, blocking=blocking) + return resp + async def block_forever(self) -> NoReturn: while True: await async_sleep(1) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 1dc6cfa5..903668c5 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -142,7 +142,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'WinGet', 'AHKWinGetClass', 'WinHide', - 'WinKill', + 'AHKWinKill', 'WinMaximize', 'WinMinimize', 'WinRestore', @@ -354,7 +354,7 @@ async def function_call(self, function_name: Literal['AHKWinClose'], args: Optio @overload async def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKWinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload async def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 1e988c20..4ab9de91 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -70,6 +70,11 @@ async def close(self) -> None: ) 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') diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 2a31d55a..8f3bbda3 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2467,6 +2467,38 @@ def win_close( resp = self._transport.function_call('AHKWinClose', args=args, blocking=blocking) return resp + # fmt: off + @overload + def win_kill(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_kill(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_kill(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_kill(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_kill( + 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]]: + 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('AHKWinKill', args, engine=self, blocking=blocking) + return resp + def block_forever(self) -> NoReturn: while True: sleep(1) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index f30a527d..1e27ec9e 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -134,7 +134,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'WinGet', 'AHKWinGetClass', 'WinHide', - 'WinKill', + 'AHKWinKill', 'WinMaximize', 'WinMinimize', 'WinRestore', @@ -337,7 +337,7 @@ def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[Li @overload def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 4703b4ca..af8c7473 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -66,6 +66,11 @@ def close(self) -> None: ) 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') @@ -175,8 +180,9 @@ def get_class(self, blocking: Literal[False]) -> FutureResult[str]: ... 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) - + 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 diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 43b63207..d85b0d39 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -130,8 +130,46 @@ AHKWinClose(ByRef command) { DetectHiddenWindows, %detect_hw% } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% + + return FormatNoValueResponse() +} + +AHKWinKill(ByRef command) { + title := command[2] + text := command[3] + secondstowait := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + + WinKill, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() } @@ -1553,15 +1591,6 @@ WinHide(ByRef command) { } } -WinKill(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinKill, %title% - } else { - secondstowait := command[3] - WinKill, %title%, %secondstowait% - } -} WinMaximize(ByRef command) { title := command[2] diff --git a/tests/_async/test_mouse.py b/tests/_async/test_mouse.py index 1049fca1..da78fb88 100644 --- a/tests/_async/test_mouse.py +++ b/tests/_async/test_mouse.py @@ -19,6 +19,13 @@ class TestMouseAsync(IsolatedAsyncioTestCase): 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) diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index da66a96b..f2d15b84 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -18,16 +18,17 @@ async def asyncSetUp(self) -> None: self.im = Image.new('RGB', (20, 20)) for coord in product(range(20), range(20)): self.im.putpixel(coord, (255, 0, 0)) - await asyncio.sleep(2) + time.sleep(1) async def asyncTearDown(self): print('tearing down') for win in await self.ahk.list_windows(): if win not in self.before_windows: print('closing', win) - await win.close() + await win.kill() print('killing proc') self.ahk._transport._proc.kill() + time.sleep(0.2) # # async def test_pixel_search(self): @@ -57,6 +58,7 @@ async def test_pixel_search(self): time.sleep(2) self.im.save('testimage.png') position = await self.ahk.image_search('testimage.png') + assert position is not None x, y = position color = await self.ahk.pixel_get_color(x, y) region_start = (x - 1, y - 1) diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 36be3134..8e12cceb 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -37,7 +37,7 @@ async def test_exists(self): async def test_close(self): await self.win.close() - await asyncio.sleep(0.2) + time.sleep(0.2) self.assertFalse(await self.win.exists()) async def test_win_get_returns_none_nonexistent(self): diff --git a/tests/_sync/test_mouse.py b/tests/_sync/test_mouse.py index 913b9bf1..97f1faf3 100644 --- a/tests/_sync/test_mouse.py +++ b/tests/_sync/test_mouse.py @@ -18,6 +18,13 @@ class TestMouseAsync(TestCase): 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) diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index c60c40c6..410c55c3 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -18,16 +18,17 @@ def setUp(self) -> None: self.im = Image.new('RGB', (20, 20)) for coord in product(range(20), range(20)): self.im.putpixel(coord, (255, 0, 0)) - asyncio.sleep(2) + time.sleep(1) def tearDown(self): print('tearing down') for win in self.ahk.list_windows(): if win not in self.before_windows: print('closing', win) - win.close() + win.kill() print('killing proc') self.ahk._transport._proc.kill() + time.sleep(0.2) # # async def test_pixel_search(self): @@ -57,6 +58,7 @@ def test_pixel_search(self): time.sleep(2) self.im.save('testimage.png') position = self.ahk.image_search('testimage.png') + assert position is not None x, y = position color = self.ahk.pixel_get_color(x, y) region_start = (x - 1, y - 1) diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index b2f3d23b..5af62d76 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -37,7 +37,7 @@ def test_exists(self): def test_close(self): self.win.close() - asyncio.sleep(0.2) + time.sleep(0.2) self.assertFalse(self.win.exists()) def test_win_get_returns_none_nonexistent(self): From 35e855ebeafa50e48c4eec45e076e1151f9dbafb Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 15 Dec 2022 16:54:12 -0800 Subject: [PATCH 315/588] move timeout minutes to tests only --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fbfe3524..5356b57c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,7 +7,6 @@ jobs: matrix: python_version: ["3.10", "3.9", "3.8", "3.11"] runs-on: windows-latest - timeout-minutes: 5 steps: - name: Checkout uses: actions/checkout@v2 @@ -24,6 +23,7 @@ jobs: python -m pip install tox python -m pip install ahk-binary - name: Test with coverage/pytest + timeout-minutes: 5 env: PYTHONUNBUFFERED: "1" run: | From b7626edf7c4a88db7628f4f22043bc8ea24a35d1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 15 Dec 2022 23:50:09 -0800 Subject: [PATCH 316/588] win_minimize/maximize/restore --- ahk/_async/engine.py | 96 +++++++++++++++++++++++++++++++++++++ ahk/_async/transport.py | 12 ++--- ahk/_async/window.py | 45 ++++++++++++++++++ ahk/_sync/engine.py | 98 ++++++++++++++++++++++++++++++++++++++ ahk/_sync/transport.py | 12 ++--- ahk/_sync/window.py | 45 ++++++++++++++++++ ahk/daemon.ahk | 103 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 399 insertions(+), 12 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 847b15d5..7bf3510b 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2509,6 +2509,102 @@ async def win_kill( 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]]: + 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]]: + 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]]: + 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 + async def block_forever(self) -> NoReturn: while True: await async_sleep(1) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 903668c5..61da949e 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -143,9 +143,9 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinGetClass', 'WinHide', 'AHKWinKill', - 'WinMaximize', - 'WinMinimize', - 'WinRestore', + 'AHKWinMaximize', + 'AHKWinMinimize', + 'AHKWinRestore', 'WinSend', 'WinSendRaw', 'WinSet', @@ -356,11 +356,11 @@ async def function_call(self, function_name: Literal['WinHide'], args: Optional[ @overload async def function_call(self, function_name: Literal['AHKWinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKWinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinMinimize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKWinMinimize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + async def function_call(self, function_name: Literal['AHKWinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload async def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 4ab9de91..3b1baf5b 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -189,6 +189,51 @@ async def list_controls(self) -> Sequence['AsyncControl']: ) 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: ... diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 8f3bbda3..e518eeb2 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2499,6 +2499,104 @@ def win_kill( 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]]: + 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]]: + 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]]: + 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 + + def block_forever(self) -> NoReturn: while True: sleep(1) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 1e27ec9e..74296b80 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -135,9 +135,9 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinGetClass', 'WinHide', 'AHKWinKill', - 'WinMaximize', - 'WinMinimize', - 'WinRestore', + 'AHKWinMaximize', + 'AHKWinMinimize', + 'AHKWinRestore', 'WinSend', 'WinSendRaw', 'WinSet', @@ -339,11 +339,11 @@ def function_call(self, function_name: Literal['WinHide'], args: Optional[List[s @overload def function_call(self, function_name: Literal['AHKWinKill'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinMaximize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinMinimize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinMinimize'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... + def function_call(self, function_name: Literal['AHKWinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index af8c7473..1e3d343c 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -169,6 +169,51 @@ def list_controls(self) -> Sequence['Control']: ) 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: ... diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index d85b0d39..000986fa 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -173,6 +173,109 @@ AHKWinKill(ByRef command) { return FormatNoValueResponse() } + +AHKWinMinimize(ByRef command) { + title := command[2] + text := command[3] + secondstowait := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + + WinMinimize, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() +} + +AHKWinMaximize(ByRef command) { + title := command[2] + text := command[3] + secondstowait := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + + WinMaximize, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() +} + +AHKWinRestore(ByRef command) { + title := command[2] + text := command[3] + secondstowait := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + + WinRestore, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() +} + AHKWinGetID(ByRef command) { global WINDOWRESPONSEMESSAGE title := command[2] From e3be8c1a3fae2a761cc70739a2e5422a89a1a64a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 28 Dec 2022 16:05:10 -0800 Subject: [PATCH 317/588] win_set methods, win_wait --- ahk/_async/engine.py | 24 ++++++ ahk/_async/transport.py | 3 + ahk/_async/window.py | 179 ++++++++++++++++++++++++++++++++++++++-- ahk/_sync/engine.py | 14 +++- ahk/_sync/transport.py | 3 + ahk/_sync/window.py | 179 ++++++++++++++++++++++++++++++++++++++-- ahk/daemon.ahk | 45 ++++++++++ ahk/message.py | 8 +- 8 files changed, 437 insertions(+), 18 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 7bf3510b..71bb6579 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2605,6 +2605,30 @@ async def win_restore( resp = await self._transport.function_call('AHKWinRestore', args, engine=self, blocking=blocking) return resp + 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]]: + 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 + async def block_forever(self) -> NoReturn: while True: await async_sleep(1) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 61da949e..d2d5ba18 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -133,6 +133,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinSetTransColor', 'AHKWinSetTransparent', 'AHKWindowList', + 'AHKWinWait', 'AHKClick', 'CoordMode', 'AHKSetCapsLockState', @@ -470,6 +471,8 @@ async def function_call(self, function_name: Literal['AHKSetCoordMode'], args: L async def function_call(self, function_name: Literal['AHKGetSendLevel']) -> int: ... @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] = None) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 3b1baf5b..ab89a7de 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -342,13 +342,13 @@ def text(self) -> AsyncPropertyReturnStr: @overload async def get_position(self) -> Position: ... @overload - async def get_position(self, blocking: Literal[False]) -> AsyncFutureResult[Optional[Position]]: ... + async def get_position(self, *, blocking: Literal[False]) -> AsyncFutureResult[Optional[Position]]: ... @overload - async def get_position(self, blocking: Literal[True]) -> Position: ... + async def get_position(self, *, blocking: Literal[True]) -> Position: ... @overload - async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutureResult[Optional[Position]]]: ... + async def get_position(self, *, blocking: bool = True) -> Union[Position, AsyncFutureResult[Optional[Position]]]: ... # fmt: on - async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutureResult[Optional[Position]]]: + async def get_position(self, *, blocking: bool = True) -> Union[Position, AsyncFutureResult[Optional[Position]]]: resp = await self._engine.win_get_position( title=f'ahk_id {self._ahk_id}', blocking=blocking, @@ -365,13 +365,13 @@ async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutu @overload async def activate(self) -> None: ... @overload - async def activate(self, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def activate(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... @overload - async def activate(self, blocking: Literal[True]) -> None: ... + async def activate(self, *, blocking: Literal[True]) -> None: ... @overload - async def activate(self, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + async def activate(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... # fmt: on - async def activate(self, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + 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, @@ -380,6 +380,169 @@ async def activate(self, blocking: bool = True) -> Union[None, AsyncFutureResult ) return resp + # fmt: off + @overload + async def bottom(self, *, blocking: Literal[True]) -> None: ... + @overload + async def bottom(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def bottom(self) -> None: ... + # fmt: on + async def 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 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, + ) + class AsyncControl: def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index e518eeb2..58a8f53b 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2563,7 +2563,6 @@ def win_maximize( 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: ... @@ -2597,6 +2596,19 @@ def win_restore( return resp + 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]]: + 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 + def block_forever(self) -> NoReturn: while True: sleep(1) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 74296b80..e5421a17 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -125,6 +125,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinSetTransColor', 'AHKWinSetTransparent', 'AHKWindowList', + 'AHKWinWait', 'AHKClick', 'CoordMode', 'AHKSetCapsLockState', @@ -453,6 +454,8 @@ def function_call(self, function_name: Literal['AHKSetCoordMode'], args: List[st def function_call(self, function_name: Literal['AHKGetSendLevel']) -> int: ... @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] = None) -> Union[Window, FutureResult[Window]]: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 1e3d343c..1c737308 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -321,13 +321,13 @@ def text(self) -> SyncPropertyReturnStr: @overload def get_position(self) -> Position: ... @overload - def get_position(self, blocking: Literal[False]) -> FutureResult[Optional[Position]]: ... + def get_position(self, *, blocking: Literal[False]) -> FutureResult[Optional[Position]]: ... @overload - def get_position(self, blocking: Literal[True]) -> Position: ... + def get_position(self, *, blocking: Literal[True]) -> Position: ... @overload - def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Optional[Position]]]: ... + def get_position(self, *, blocking: bool = True) -> Union[Position, FutureResult[Optional[Position]]]: ... # fmt: on - def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Optional[Position]]]: + def get_position(self, *, blocking: bool = True) -> Union[Position, FutureResult[Optional[Position]]]: resp = self._engine.win_get_position( title=f'ahk_id {self._ahk_id}', blocking=blocking, @@ -344,13 +344,13 @@ def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Op @overload def activate(self) -> None: ... @overload - def activate(self, blocking: Literal[False]) -> FutureResult[None]: ... + def activate(self, *, blocking: Literal[False]) -> FutureResult[None]: ... @overload - def activate(self, blocking: Literal[True]) -> None: ... + def activate(self, *, blocking: Literal[True]) -> None: ... @overload - def activate(self, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + def activate(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... # fmt: on - def activate(self, blocking: bool = True) -> Union[None, FutureResult[None]]: + def activate(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: resp = self._engine.win_activate( title=f'ahk_id {self._ahk_id}', blocking=blocking, @@ -359,6 +359,169 @@ def activate(self, blocking: bool = True) -> Union[None, FutureResult[None]]: ) return resp + # fmt: off + @overload + def bottom(self, *, blocking: Literal[True]) -> None: ... + @overload + def bottom(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def bottom(self) -> None: ... + # fmt: on + def 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 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, + ) + class Control: def __init__(self, window: Window, hwnd: str, control_class: str): diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 000986fa..9d0d756c 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -15,6 +15,8 @@ WINDOWCONTROLLISTRESPONSEMESSAGE := "009" ; WindowControlListResponseMessage WINDOWRESPONSEMESSAGE := "00a" ; WindowResponseMessage POSITIONRESPONSEMESSAGE := "00b" ; PositionResponseMessage FLOATRESPONSEMESSAGE := "00c" ; FloatResponseMessage +TIMEOUTRESPONSEMESSAGE := "00d" ; TimeoutResponseMessage + NOVALUE_SENTINEL := Chr(57344) FormatResponse(MessageType, payload) { @@ -173,6 +175,49 @@ AHKWinKill(ByRef command) { return FormatNoValueResponse() } +AHKWinWait(ByRef command) { + global WINDOWRESPONSEMESSAGE + global TIMEOUTRESPONSEMESSAGE + + title := command[2] + text := command[3] + secondstowait := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[9] + timeout := command[10] + 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% + } + + + WinWait, %title%, %text%, %timeout%, %extitle%, %extext% + + if (ErrorLevel = 1) { + resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp +} AHKWinMinimize(ByRef command) { title := command[2] diff --git a/ahk/message.py b/ahk/message.py index 856f9679..5ec3fb19 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -229,10 +229,11 @@ class AHKExecutionException(Exception): class ExceptionResponseMessage(ResponseMessage): type = 'exception' + _exception_type: Type[Exception] = AHKExecutionException def unpack(self) -> NoReturn: s = self._raw_content.decode(encoding='utf-8') - raise AHKExecutionException(s) + raise self._exception_type(s) class WindowControlListResponseMessage(ResponseMessage): @@ -312,6 +313,11 @@ def unpack(self) -> float: return val +class TimeoutResponseMessage(ExceptionResponseMessage): + type = 'timeoutexception' + _exception_type = TimeoutError + + T_RequestMessageType = TypeVar('T_RequestMessageType', bound='RequestMessage') From 360be9f925d195236789f8904507dd08e08b48aa Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 28 Dec 2022 16:19:55 -0800 Subject: [PATCH 318/588] fix win_ methods --- ahk/_async/engine.py | 45 +++++++----------------- ahk/_sync/engine.py | 59 ++++++++++++++------------------ ahk/daemon.ahk | 81 ++++++++++++++++++++++---------------------- 3 files changed, 78 insertions(+), 107 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 71bb6579..d6e532b8 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2442,39 +2442,17 @@ async def win_close( title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, ) -> Union[None, AsyncFutureResult[None]]: - args: List[str] - args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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=args, blocking=blocking) + resp = await self._transport.function_call('AHKWinClose', args, engine=self, blocking=blocking) return resp # fmt: off @@ -2492,6 +2470,7 @@ async def win_kill( *, title: str = '', text: str = '', + seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, @@ -2506,6 +2485,8 @@ async def win_kill( 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 diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 58a8f53b..1d2bcaf7 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2432,39 +2432,17 @@ def win_close( title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, ) -> Union[None, FutureResult[None]]: - args: List[str] - args = [title, text, str(seconds_to_wait) if seconds_to_wait is not None else '', exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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('') + 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=args, blocking=blocking) + resp = self._transport.function_call('AHKWinClose', args, engine=self, blocking=blocking) return resp # fmt: off @@ -2482,6 +2460,7 @@ def win_kill( *, title: str = '', text: str = '', + seconds_to_wait: Optional[int] = None, exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, @@ -2496,6 +2475,8 @@ def win_kill( 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 @@ -2595,8 +2576,18 @@ def win_restore( resp = self._transport.function_call('AHKWinRestore', args, engine=self, blocking=blocking) return resp - - 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]]: + 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]]: args = self._format_win_args( title=title, text=text, diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 9d0d756c..80c35f8f 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -111,12 +111,12 @@ AHKWinExist(ByRef command) { AHKWinClose(ByRef command) { title := command[2] text := command[3] - secondstowait := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + secondstowait := command[9] current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) @@ -144,12 +144,12 @@ AHKWinClose(ByRef command) { AHKWinKill(ByRef command) { title := command[2] text := command[3] - secondstowait := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + secondstowait := command[9] current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) @@ -181,13 +181,12 @@ AHKWinWait(ByRef command) { title := command[2] text := command[3] - secondstowait := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] - timeout := command[10] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[9] current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) if (match_mode != "") { @@ -201,10 +200,11 @@ AHKWinWait(ByRef command) { if (detect_hw != "") { DetectHiddenWindows, %detect_hw% } - - - WinWait, %title%, %text%, %timeout%, %extitle%, %extext% - + if (timeout != "") { + WinWait, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWait, %title%, %text%,, %extitle%, %extext% + } if (ErrorLevel = 1) { resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") } else { @@ -222,12 +222,12 @@ AHKWinWait(ByRef command) { AHKWinMinimize(ByRef command) { title := command[2] text := command[3] - secondstowait := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) @@ -256,12 +256,11 @@ AHKWinMinimize(ByRef command) { AHKWinMaximize(ByRef command) { title := command[2] text := command[3] - secondstowait := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) @@ -290,12 +289,12 @@ AHKWinMaximize(ByRef command) { AHKWinRestore(ByRef command) { title := command[2] text := command[3] - secondstowait := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) From 278f114607f98c763352485375b33c7cef3ae0bf Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 28 Dec 2022 16:52:24 -0800 Subject: [PATCH 319/588] win_wait_active/not_active win_hide/show; cleanup --- ahk/_async/engine.py | 114 ++++++++++++++ ahk/_async/transport.py | 38 ++--- ahk/_sync/engine.py | 114 ++++++++++++++ ahk/_sync/transport.py | 38 ++--- ahk/daemon.ahk | 325 ++++++++++++++++++++-------------------- 5 files changed, 415 insertions(+), 214 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index d6e532b8..ebbf7a21 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -497,6 +497,8 @@ async def control_send( 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 @@ -2610,6 +2612,118 @@ async def win_wait( resp = await self._transport.function_call('AHKWinWait', args, blocking=blocking, engine=self) return resp + 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]]: + 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 + + 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]]: + 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_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]]: + 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]]: + 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 + async def block_forever(self) -> NoReturn: while True: await async_sleep(1) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index d2d5ba18..b8da0bda 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -118,6 +118,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinGetTitle', 'AHKWinGetTransColor', 'AHKWinGetTransparent', + 'AHKWinHide', 'AHKWinIsAlwaysOnTop', 'AHKWinMove', 'AHKWinSetAlwaysOnTop', @@ -132,8 +133,11 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinSetTop', 'AHKWinSetTransColor', 'AHKWinSetTransparent', + 'AHKWinShow', 'AHKWindowList', 'AHKWinWait', + 'AHKWinWaitActive', + 'AHKWinWaitNotActive', 'AHKClick', 'CoordMode', 'AHKSetCapsLockState', @@ -147,10 +151,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinMaximize', 'AHKWinMinimize', 'AHKWinRestore', - 'WinSend', - 'WinSendRaw', - 'WinSet', - 'WinSetTitle', 'WinShow', ] @@ -367,20 +367,10 @@ async def function_call(self, function_name: Literal['WinShow'], args: Optional[ @overload async def function_call(self, function_name: Literal['AHKWindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... @overload - async def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... - @overload - async def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... - @overload async def function_call(self, function_name: Literal['AHKControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = None) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: ... @overload - async def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... - @overload - async def function_call(self, function_name: Literal['WinSet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... - @overload - async def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... - @overload async def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Optional[bool], AsyncFutureResult[Optional[bool]]]: ... @overload async def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @@ -406,8 +396,6 @@ async def function_call(self, function_name: Literal['AHKWinGetList'], args: Opt async def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = None) -> Union[List[AsyncControl], None, AsyncFutureResult[Union[List[AsyncControl], None]]]: ... - # @overload - # async def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncControl], AsyncFutureResult[List[AsyncControl]]]: ... @overload async def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Union[None, int], AsyncFutureResult[Union[None, int]]]: ... @overload @@ -416,7 +404,6 @@ async def function_call(self, function_name: Literal['AHKWinGetTransColor'], arg async def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = 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] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload @@ -435,12 +422,10 @@ async def function_call(self, function_name: Literal['AHKWinSetStyle'], args: Op async def function_call(self, function_name: Literal['AHKWinSetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = 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] = 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] = None) -> Union[None, AsyncFutureResult[None]]: ... - @overload async def function_call(self, function_name: Literal['AHKSetDetectHiddenWindows'], args: Optional[List[str]] = None) -> None: ... @overload @@ -451,28 +436,31 @@ async def function_call(self, function_name: Literal['AHKSetTitleMatchMode'], ar 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] = 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['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] = 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] = 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] = 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] = 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] = None) -> Union[None, AsyncFutureResult[None]]: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 1d2bcaf7..14e2ce2a 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -493,6 +493,8 @@ def control_send( 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 @@ -2600,6 +2602,118 @@ def win_wait( resp = self._transport.function_call('AHKWinWait', args, blocking=blocking, engine=self) return resp + 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]]: + 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 + + 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]]: + 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_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]]: + 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]]: + 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 + def block_forever(self) -> NoReturn: while True: sleep(1) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index e5421a17..7b37bf5b 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -110,6 +110,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinGetTitle', 'AHKWinGetTransColor', 'AHKWinGetTransparent', + 'AHKWinHide', 'AHKWinIsAlwaysOnTop', 'AHKWinMove', 'AHKWinSetAlwaysOnTop', @@ -124,8 +125,11 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinSetTop', 'AHKWinSetTransColor', 'AHKWinSetTransparent', + 'AHKWinShow', 'AHKWindowList', 'AHKWinWait', + 'AHKWinWaitActive', + 'AHKWinWaitNotActive', 'AHKClick', 'CoordMode', 'AHKSetCapsLockState', @@ -139,10 +143,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinMaximize', 'AHKWinMinimize', 'AHKWinRestore', - 'WinSend', - 'WinSendRaw', - 'WinSet', - 'WinSetTitle', 'WinShow', ] @@ -350,20 +350,10 @@ def function_call(self, function_name: Literal['WinShow'], args: Optional[List[s @overload def function_call(self, function_name: Literal['AHKWindowList'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[List[Window], FutureResult[List[Window]]]: ... @overload - def function_call(self, function_name: Literal['WinSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... - @overload - def function_call(self, function_name: Literal['WinSendRaw'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... - @overload def function_call(self, function_name: Literal['AHKControlSend'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Optional[Window], FutureResult[Optional[Window]]]: ... @overload - def function_call(self, function_name: Literal['WinGet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... - @overload - def function_call(self, function_name: Literal['WinSet'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... - @overload - def function_call(self, function_name: Literal['WinSetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... - @overload def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Optional[bool], FutureResult[Optional[bool]]]: ... @overload def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @@ -389,8 +379,6 @@ def function_call(self, function_name: Literal['AHKWinGetList'], args: Optional[ def function_call(self, function_name: Literal['AHKWinGetMinMax'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[List[Control], None, FutureResult[Union[List[Control], None]]]: ... - # @overload - # async def function_call(self, function_name: Literal['AHKWinGetControlListHwnd'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[List[AsyncControl], AsyncFutureResult[List[AsyncControl]]]: ... @overload def function_call(self, function_name: Literal['AHKWinGetTransparent'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Union[None, int], FutureResult[Union[None, int]]]: ... @overload @@ -399,7 +387,6 @@ def function_call(self, function_name: Literal['AHKWinGetTransColor'], args: Opt def function_call(self, function_name: Literal['AHKWinGetStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = 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] = None) -> Union[None, FutureResult[None]]: ... @overload @@ -418,12 +405,10 @@ def function_call(self, function_name: Literal['AHKWinSetStyle'], args: Optional def function_call(self, function_name: Literal['AHKWinSetExStyle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = 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] = 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] = None) -> Union[None, FutureResult[None]]: ... - @overload def function_call(self, function_name: Literal['AHKSetDetectHiddenWindows'], args: Optional[List[str]] = None) -> None: ... @overload @@ -434,28 +419,31 @@ def function_call(self, function_name: Literal['AHKSetTitleMatchMode'], args: Op 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] = 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['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] = 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] = 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] = 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] = 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] = None) -> Union[None, FutureResult[None]]: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 80c35f8f..12eacbc6 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -219,6 +219,98 @@ AHKWinWait(ByRef command) { return resp } + +AHKWinWaitActive(ByRef command) { + global WINDOWRESPONSEMESSAGE + global TIMEOUTRESPONSEMESSAGE + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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% + } + if (timeout != "") { + WinWaitActive, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitActive, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp +} + + +AHKWinWaitNotActive(ByRef command) { + global WINDOWRESPONSEMESSAGE + global TIMEOUTRESPONSEMESSAGE + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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% + } + if (timeout != "") { + WinWaitNotActive, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitNotActive, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp +} + + + AHKWinMinimize(ByRef command) { title := command[2] text := command[3] @@ -440,12 +532,12 @@ AHKWinGetPID(ByRef command) { 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 (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } current_detect_hw := Format("{}", A_DetectHiddenWindows) @@ -946,6 +1038,69 @@ AHKWinSetBottom(ByRef command) { return FormatNoValueResponse() } +AHKWinShow(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinShow, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKWinHide(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinHide, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + + AHKWinSetTop(ByRef command) { title := command[2] text := command[3] @@ -1387,14 +1542,6 @@ AHKMouseMove(ByRef command) { return resp } -CoordMode(ByRef command) { - if (command.Length() = 2) { - CoordMode,% command[2] - } else { - CoordMode,% command[2],% command[3] - } -} - AHKClick(ByRef command) { x := command[2] @@ -1717,108 +1864,8 @@ AHKWinActivate(ByRef command) { return FormatNoValueResponse() } -WinActivateBottom(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinActivateBottom, %title% - } else { - secondstowait := command[3] - WinActivateBottom, %title%, %secondstowait% - } -} - - -WinHide(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinHide, %title% - } else { - secondstowait := command[3] - WinHide, %title%, %secondstowait% - } -} -WinMaximize(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinMaximize, %title% - } else { - secondstowait := command[3] - WinMaximize, %title%, %secondstowait% - } -} - -WinMinimize(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinMinimize, %title% - } else { - secondstowait := command[3] - WinMinimize, %title%, %secondstowait% - } -} - -WinRestore(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinRestore, %title% - } else { - secondstowait := command[3] - WinRestore, %title%, %secondstowait% - } -} - -WinShow(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinShow, %title% - } else { - secondstowait := command[3] - WinShow, %title%, %secondstowait% - } -} - -WinWait(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinWait, %title% - } else { - secondstowait = command[3] - WinWait, %title%, %secondstowait% - } -} - -WinWaitActive(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinWaitActive, %title% - } else { - secondstowait := command[3] - WinWaitActive, %title%, %secondstowait% - } -} - -WinWaitNotActive(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinWaitNotActive, %title% - } else { - secondstowait = command[3] - WinWaitNotActive, %title%, %secondstowait% - } -} - -WinWaitClose(ByRef command) { - title := command[2] - if (command.Length() = 2) { - WinWaitClose, %title% - } else { - secondstowait := command[3] - WinWaitClose, %title%, %secondstowait% - } -} - AHKWindowList(ByRef command) { global WINDOWIDLISTRESPONSEMESSAGE @@ -1859,23 +1906,7 @@ AHKWindowList(ByRef command) { return resp } -WinSend(ByRef command) { - title := command[2] - command.RemoveAt(1) - command.RemoveAt(1) - str := Join(",", command*) - keys := Unescape(str) - ControlSend,,% keys, %title% -} -WinSendRaw(ByRef command) { - title := command[2] - command.RemoveAt(1) - command.RemoveAt(1) - str := Join(",", command*) - keys := Unescape(str) - ControlSendRaw,,% keys, %title% -} AHKControlClick(ByRef command) { global EXCEPTIONRESPONSEMESSAGE @@ -2057,27 +2088,6 @@ AHKWinFromMouse(ByRef command) { return FormatResponse(WINDOWRESPONSEMESSAGE, MouseWin) } -;WinGet(ByRef command) { -; title := command[4] -; text := command[5] -; extitle := command[6] -; extext := command[7] -; WinGet, output,% command[3], %title%, %text%, %extitle%, %extext% -; return output -;} - -;WinSet(ByRef command) { -; subcommand := command[2] -; title := command[4] -; value := command[3] -; -; WinSet,%subcommand%,%value%,%title% -;} - -;WinSetTitle(ByRef command) { -; newtitle := command[4] -; WinSetTitle,% command[2],, %newtitle% -;} AHKWinIsAlwaysOnTop(ByRef command) { global BOOLEANRESPONSEMESSAGE @@ -2092,19 +2102,6 @@ AHKWinIsAlwaysOnTop(ByRef command) { return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) } -WinClick(ByRef command) { - x := command[2] - y := command[3] - hwnd := command[4] - button := command[5] - n := command[6] - if (command.Length() = 6) { - ControlClick,x%x% y%y%,%hwnd%,,%button%,%n% - } else { - options := command[6] - ControlClick, x%x% y%y%, %hwnd%,,%button%, %n%, options - } -} AHKWinMove(ByRef command) { title := command [2] From 6c558ce34154a4f534413fb18e61ff0780655ca7 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 28 Dec 2022 17:09:07 -0800 Subject: [PATCH 320/588] window click --- ahk/_async/window.py | 48 ++++++++++++++++++++++++++++++++++++++++++++ ahk/_sync/window.py | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/ahk/_async/window.py b/ahk/_async/window.py index ab89a7de..240d20d6 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -319,6 +319,38 @@ async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFu 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: ... @@ -396,6 +428,22 @@ async def bottom(self, *, blocking: bool = True) -> Union[None, AsyncFutureResul title_match_mode=(1, 'Fast'), ) + # fmt: off + @overload + async def top(self, *, blocking: Literal[True]) -> None: ... + @overload + async def top(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def top(self) -> None: ... + # fmt: on + async def 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 disable(self, *, blocking: Literal[True]) -> None: ... diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 1c737308..50b9b315 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -298,6 +298,29 @@ def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[ 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: ... @@ -375,6 +398,22 @@ def bottom(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: title_match_mode=(1, 'Fast'), ) + # fmt: off + @overload + def top(self, *, blocking: Literal[True]) -> None: ... + @overload + def top(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def top(self) -> None: ... + # fmt: on + def 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 disable(self, *, blocking: Literal[True]) -> None: ... From c254643264db31b80bdc465a63418ea244ef4dfb Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 28 Dec 2022 20:50:11 -0800 Subject: [PATCH 321/588] more methods --- ahk/_async/engine.py | 78 +++++++++++++++++++++++++++++++ ahk/_async/transport.py | 3 ++ ahk/_async/window.py | 73 +++++++++++++++++++++++++---- ahk/_sync/engine.py | 78 +++++++++++++++++++++++++++++++ ahk/_sync/transport.py | 3 ++ ahk/_sync/window.py | 80 ++++++++++++++++++++++++++++---- ahk/daemon.ahk | 91 +++++++++++++++++++++++++++---------- tests/_async/test_window.py | 10 ++++ tests/_sync/test_window.py | 10 ++++ 9 files changed, 386 insertions(+), 40 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index ebbf7a21..eeba864f 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -627,6 +627,12 @@ def mouse_position(self) -> AsyncPropertyReturnTupleIntInt: ) return self.get_mouse_position() + @mouse_position.setter + def mouse_position(self, new_position: Tuple[int, int]) -> None: + 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) -> None: ... @@ -2724,6 +2730,78 @@ async def win_hide( 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]]: + 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]]: + 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 + async def block_forever(self) -> NoReturn: while True: await async_sleep(1) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index b8da0bda..3c64053a 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -119,6 +119,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinGetTransColor', 'AHKWinGetTransparent', 'AHKWinHide', + 'AHKWinIsActive', 'AHKWinIsAlwaysOnTop', 'AHKWinMove', 'AHKWinSetAlwaysOnTop', @@ -461,6 +462,8 @@ async def function_call(self, function_name: Literal['AHKWinWaitNotActive'], arg async def function_call(self, function_name: Literal['AHKWinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = 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] = None) -> Union[bool, AsyncFutureResult[bool]]: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 240d20d6..5b04df1d 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -414,13 +414,13 @@ async def activate(self, *, blocking: bool = True) -> Union[None, AsyncFutureRes # fmt: off @overload - async def bottom(self, *, blocking: Literal[True]) -> None: ... + async def to_bottom(self, *, blocking: Literal[True]) -> None: ... @overload - async def bottom(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def to_bottom(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... @overload - async def bottom(self) -> None: ... + async def to_bottom(self) -> None: ... # fmt: on - async def bottom(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + 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, @@ -430,13 +430,13 @@ async def bottom(self, *, blocking: bool = True) -> Union[None, AsyncFutureResul # fmt: off @overload - async def top(self, *, blocking: Literal[True]) -> None: ... + async def to_top(self, *, blocking: Literal[True]) -> None: ... @overload - async def top(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def to_top(self, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... @overload - async def top(self) -> None: ... + async def to_top(self) -> None: ... # fmt: on - async def top(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + 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, @@ -444,6 +444,38 @@ async def top(self, *, blocking: bool = True) -> Union[None, AsyncFutureResult[N 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: ... @@ -591,6 +623,31 @@ async def set_trans_color( 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, + ) + class AsyncControl: def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 14e2ce2a..bde9ecac 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -620,6 +620,11 @@ def get_mouse_position( def mouse_position(self) -> SyncPropertyReturnTupleIntInt: return self.get_mouse_position() + @mouse_position.setter + def mouse_position(self, new_position: Tuple[int, int]) -> None: + 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) -> None: ... @@ -2714,6 +2719,79 @@ def win_hide( 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]]: + 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]]: + 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 + def block_forever(self) -> NoReturn: while True: sleep(1) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 7b37bf5b..5d0b49f4 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -111,6 +111,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinGetTransColor', 'AHKWinGetTransparent', 'AHKWinHide', + 'AHKWinIsActive', 'AHKWinIsAlwaysOnTop', 'AHKWinMove', 'AHKWinSetAlwaysOnTop', @@ -444,6 +445,8 @@ def function_call(self, function_name: Literal['AHKWinWaitNotActive'], args: Opt def function_call(self, function_name: Literal['AHKWinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = 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] = None) -> Union[bool, FutureResult[bool]]: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 50b9b315..e13ff72e 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -308,7 +308,16 @@ def click(self, x: int = 0, y: int = 0, *, button: Literal['L', 'R', 'M', 'LEFT' @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]]: + 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, @@ -384,13 +393,13 @@ def activate(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: # fmt: off @overload - def bottom(self, *, blocking: Literal[True]) -> None: ... + def to_bottom(self, *, blocking: Literal[True]) -> None: ... @overload - def bottom(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + def to_bottom(self, *, blocking: Literal[False]) -> FutureResult[None]: ... @overload - def bottom(self) -> None: ... + def to_bottom(self) -> None: ... # fmt: on - def bottom(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + 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, @@ -400,13 +409,13 @@ def bottom(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: # fmt: off @overload - def top(self, *, blocking: Literal[True]) -> None: ... + def to_top(self, *, blocking: Literal[True]) -> None: ... @overload - def top(self, *, blocking: Literal[False]) -> FutureResult[None]: ... + def to_top(self, *, blocking: Literal[False]) -> FutureResult[None]: ... @overload - def top(self) -> None: ... + def to_top(self) -> None: ... # fmt: on - def top(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + 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, @@ -414,6 +423,38 @@ def top(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: 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: ... @@ -561,6 +602,27 @@ def set_trans_color( 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, + ) + class Control: def __init__(self, window: Window, hwnd: str, control_class: str): diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 12eacbc6..36d73837 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -412,6 +412,41 @@ AHKWinRestore(ByRef command) { return FormatNoValueResponse() } +AHKWinIsActive(ByRef command) { + global BOOLEANRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 WinActive(title, text, extitle, extext) { + response := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + } else { + response := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + AHKWinGetID(ByRef command) { global WINDOWRESPONSEMESSAGE title := command[2] @@ -2065,17 +2100,6 @@ AHKControlSend(ByRef command) { -; -;BaseCheck(ByRef command) { -; kommand := command[2] -; title := command[3] -; if %kommand%(title) { -; return 1 -; } -; else { -; return 0 -; } -;} AHKWinFromMouse(ByRef command) { global WINDOWRESPONSEMESSAGE @@ -2104,19 +2128,40 @@ AHKWinIsAlwaysOnTop(ByRef command) { AHKWinMove(ByRef command) { - title := command [2] - x := command[3] - y := command[4] - if (command.Length()) = 4 { - WinMove,%title%,,%x%,%y% - } else if (command.Length() = 5) { - a := command[5] - WinMove,%title%,,%x%,%y%,%a% - } else if (command.Length() = 6) { - a := command[5] - b := command[6] - WinMove,%title%,,%x%,%y%,%a%,%b% + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + x := command[9] + y := command[10] + width := command[11] + height := command[12] + + 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() + } AHKWinGetPos(ByRef command) { diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 8e12cceb..472c8526 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -171,3 +171,13 @@ async def test_win_activate(self): 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 diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 5af62d76..a395fb05 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -171,3 +171,13 @@ def test_win_activate(self): 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 From 7fcda4058de858565f10b820f50f30d7e71de4d5 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 11 Jan 2023 12:05:19 -0800 Subject: [PATCH 322/588] fix case where global state is not reset when calling AHKControlClick --- ahk/daemon.ahk | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 36d73837..113219c6 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1974,13 +1974,16 @@ AHKControlClick(ByRef command) { ControlClick, %ctrl%, %title%, %text%, %button%, %click_count%, %options%, %exclude_title%, %exclude_text% if (ErrorLevel != 0) { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Failed to click control") + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Failed to click control") + } else { + response := FormatNoValueResponse() } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% - return FormatNoValueResponse() + + return response } AHKControlGetText(ByRef command) { From bc5895b432b46d81bc1027fdb3b82210bb3d4342 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 11 Jan 2023 12:11:01 -0800 Subject: [PATCH 323/588] remove unused functions --- ahk/_async/transport.py | 13 ------------- ahk/_sync/transport.py | 13 ------------- 2 files changed, 26 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 3c64053a..f53e703f 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -140,19 +140,14 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinWaitActive', 'AHKWinWaitNotActive', 'AHKClick', - 'CoordMode', 'AHKSetCapsLockState', 'SetKeyDelay', 'WinActivateBottom', - 'WinClick', - 'WinGet', 'AHKWinGetClass', - 'WinHide', 'AHKWinKill', 'AHKWinMaximize', 'AHKWinMinimize', 'AHKWinRestore', - 'WinShow', ] @@ -320,8 +315,6 @@ async def function_call(self, function_name: Literal['AHKKeyState'], args: Optio @overload async def function_call(self, function_name: Literal['AHKMouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = 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] = None) -> Union[None, AsyncFutureResult[None]]: ... @@ -354,8 +347,6 @@ async def function_call(self, function_name: Literal['WinActivateBottom'], args: @overload async def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = 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] = None) -> Union[None, AsyncFutureResult[None]]: ... @@ -364,8 +355,6 @@ async def function_call(self, function_name: Literal['AHKWinMinimize'], args: Op @overload async def function_call(self, function_name: Literal['AHKWinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... @overload - async def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = 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] = None) -> Union[None, AsyncFutureResult[None]]: ... @@ -374,8 +363,6 @@ async def function_call(self, function_name: Literal['AHKWinFromMouse'], args: O @overload async def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Optional[bool], AsyncFutureResult[Optional[bool]]]: ... @overload - async def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... - @overload async def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = None) -> Union[Union[Position, None], AsyncFutureResult[Union[None, Position]]]: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 5d0b49f4..399e475a 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -132,19 +132,14 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWinWaitActive', 'AHKWinWaitNotActive', 'AHKClick', - 'CoordMode', 'AHKSetCapsLockState', 'SetKeyDelay', 'WinActivateBottom', - 'WinClick', - 'WinGet', 'AHKWinGetClass', - 'WinHide', 'AHKWinKill', 'AHKWinMaximize', 'AHKWinMinimize', 'AHKWinRestore', - 'WinShow', ] @@ -303,8 +298,6 @@ def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[Li @overload def function_call(self, function_name: Literal['AHKMouseMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['CoordMode'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = 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] = None) -> Union[None, FutureResult[None]]: ... @@ -337,8 +330,6 @@ def function_call(self, function_name: Literal['WinActivateBottom'], args: Optio @overload def function_call(self, function_name: Literal['AHKWinClose'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = 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] = None) -> Union[None, FutureResult[None]]: ... @@ -347,8 +338,6 @@ def function_call(self, function_name: Literal['AHKWinMinimize'], args: Optional @overload def function_call(self, function_name: Literal['AHKWinRestore'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... @overload - def function_call(self, function_name: Literal['WinShow'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = 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] = None) -> Union[None, FutureResult[None]]: ... @@ -357,8 +346,6 @@ def function_call(self, function_name: Literal['AHKWinFromMouse'], args: Optiona @overload def function_call(self, function_name: Literal['AHKWinIsAlwaysOnTop'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Optional[bool], FutureResult[Optional[bool]]]: ... @overload - def function_call(self, function_name: Literal['WinClick'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... - @overload def function_call(self, function_name: Literal['AHKWinMove'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[Union[Position, None], FutureResult[Union[None, Position]]]: ... From 1da260996ec790d9e3312c8948e62385b9ee3a65 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 11 Jan 2023 16:28:51 -0800 Subject: [PATCH 324/588] add better support for freezing --- .pre-commit-config.yaml | 7 + _set_constants.py | 31 + ahk/_async/engine.py | 7 +- ahk/_async/transport.py | 39 +- ahk/_constants.py | 2300 ++++++++++++++++++++++++++++++++++ ahk/_sync/engine.py | 16 +- ahk/_sync/transport.py | 39 +- tests/_async/test_scripts.py | 38 + 8 files changed, 2458 insertions(+), 19 deletions(-) create mode 100644 _set_constants.py create mode 100644 tests/_async/test_scripts.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 72bf54c9..f56449b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,13 @@ repos: - unasync - 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: v4.3.0 diff --git a/_set_constants.py b/_set_constants.py new file mode 100644 index 00000000..4d0ca5be --- /dev/null +++ b/_set_constants.py @@ -0,0 +1,31 @@ +import shutil +import subprocess +import sys + +with open('ahk/daemon.ahk') as f: + daemon_script = f.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 = r"""{daemon_script} +""" +''' + +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/ahk/_async/engine.py b/ahk/_async/engine.py index eeba864f..58d23086 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -914,7 +914,8 @@ async def key_state( 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) - return await self._transport.function_call('AHKKeyState', args, blocking=blocking) + resp = await self._transport.function_call('AHKKeyState', args, blocking=blocking) + return resp # fmt: off @overload @@ -1108,7 +1109,9 @@ async def set_capslock_state( f'Invalid value for state. Must be one of On, Off, AlwaysOn, AlwaysOff or None. Got {state!r}' ) args.append(str(state)) - return await self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) + + resp = await self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) + return resp async def set_volume(self, value: int, device_number: int = 1) -> None: raise NotImplementedError() diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index f53e703f..19c38714 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -5,6 +5,7 @@ import os import subprocess import sys +import tempfile import warnings from abc import ABC from abc import abstractmethod @@ -37,6 +38,7 @@ from ahk.message import RequestMessage from ahk.message import ResponseMessage from ahk.message import Position +from ahk._constants import DAEMON_SCRIPT as _DAEMON_SCRIPT from concurrent.futures import Future, ThreadPoolExecutor @@ -483,7 +485,11 @@ async def function_call( engine: Optional[AsyncAHK] = None, ) -> Any: if not self._started: - await self.init() + 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) @@ -515,6 +521,7 @@ class AsyncDaemonProcessTransport(AsyncTransport): def __init__(self, *, executable_path: Union[str, os.PathLike[AnyStr]] = ''): self._proc: Optional[AsyncAHKProcess] self._proc = None + self._temp_script: Optional[str] = None super().__init__(executable_path=executable_path) async def init(self) -> None: @@ -524,13 +531,35 @@ async def init(self) -> None: async def start(self) -> None: assert self._proc is None, 'cannot start a process twice' - daemon_script = os.path.abspath(os.path.join(os.path.dirname(__file__), '../daemon.ahk')) - runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] - self._proc = AsyncAHKProcess(runargs=runargs) - await self._proc.start() + with warnings.catch_warnings(record=True) as caught_warnings: + self._proc = await self._create_process() + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) async def _create_process(self) -> AsyncAHKProcess: daemon_script = os.path.abspath(os.path.join(os.path.dirname(__file__), '../daemon.ahk')) + if not os.path.exists(daemon_script): + if self._temp_script is None or not os.path.exists(self._temp_script): + warnings.warn( + 'daemon script not found. This is typically the result of a error in attempting to ' + 'repackage/redistribute `ahk` without including its package data. Will attempt to run ' + 'daemon script from tempfile, but this action may be blocked by some security tools. ' + 'To fix this warning, make sure to include package data when bundling applications that ' + 'depend on `ahk`', + category=UserWarning, + stacklevel=2, + ) + + with tempfile.NamedTemporaryFile( + mode='w', prefix='python-ahk-', suffix='.ahk', delete=False + ) as tempscriptfile: + tempscriptfile.write(_DAEMON_SCRIPT) # XXX: can we make this async? + self._temp_script = tempscriptfile.name + daemon_script = self._temp_script + atexit.register(os.remove, tempscriptfile.name) + else: + daemon_script = self._temp_script runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] proc = AsyncAHKProcess(runargs=runargs) await proc.start() diff --git a/ahk/_constants.py b/ahk/_constants.py index e69de29b..f181a176 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -0,0 +1,2300 @@ +# THIS FILE IS AUTOGENERATED BY _set_constants.py +# DO NOT EDIT BY HAND + +DAEMON_SCRIPT = r"""#NoEnv +#Persistent +#SingleInstance Off + +RESPONSEMESSAGE := "000" ; ResponseMessage +TUPLERESPONSEMESSAGE := "001" ; TupleResponseMessage +COORDINATERESPONSEMESSAGE := "002" ; CoordinateResponseMessage +INTEGERRESPONSEMESSAGE := "003" ; IntegerResponseMessage +BOOLEANRESPONSEMESSAGE := "004" ; BooleanResponseMessage +STRINGRESPONSEMESSAGE := "005" ; StringResponseMessage +WINDOWIDLISTRESPONSEMESSAGE := "006" ; WindowIDListResponseMessage +NOVALUERESPONSEMESSAGE := "007" ; NoValueResponseMessage +EXCEPTIONRESPONSEMESSAGE := "008" ; ExceptionResponseMessage +WINDOWCONTROLLISTRESPONSEMESSAGE := "009" ; WindowControlListResponseMessage +WINDOWRESPONSEMESSAGE := "00a" ; WindowResponseMessage +POSITIONRESPONSEMESSAGE := "00b" ; PositionResponseMessage +FLOATRESPONSEMESSAGE := "00c" ; FloatResponseMessage +TIMEOUTRESPONSEMESSAGE := "00d" ; TimeoutResponseMessage + +NOVALUE_SENTINEL := Chr(57344) + +FormatResponse(MessageType, payload) { + newline_count := CountNewlines(payload) + response := Format("{}`n{}`n{}`n", MessageType, newline_count, payload) + return response +} + +FormatNoValueResponse() { + global NOVALUE_SENTINEL + global NOVALUERESPONSEMESSAGE + return FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) +} + +AHKSetDetectHiddenWindows(ByRef command) { + value := command[2] + DetectHiddenWindows, %value% + return FormatNoValueResponse() +} + +AHKSetTitleMatchMode(ByRef command) { + val1 := command[2] + val2 := command[3] + if (val1 != "") { + SetTitleMatchMode, %val1% + } + if (val2 != "") { + SetTitleMatchMode, %val2% + } + return FormatNoValueResponse() +} + +AHKGetTitleMatchMode(ByRef command) { + global STRINGRESPONSEMESSAGE + return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchMode) +} + +AHKGetTitleMatchSpeed(ByRef command) { + global STRINGRESPONSEMESSAGE + return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchModeSpeed) +} + +AHKSetSendLevel(ByRef command) { + level := command[2] + SendLevel, %level% + return FormatNoValueResponse() +} + +AHKGetSendLevel(ByRef command) { + global INTEGERRESPONSEMESSAGE + return FormatResponse(INTEGERRESPONSEMESSAGE, A_SendLevel) +} + +AHKWinExist(ByRef command) { + global BOOLEANRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 WinExist(title, text, extitle, extext) { + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + } else { + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp +} + +AHKWinClose(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + secondstowait := command[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% + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% + + return FormatNoValueResponse() +} + +AHKWinKill(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + secondstowait := command[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% + } + + + WinKill, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() +} + +AHKWinWait(ByRef command) { + global WINDOWRESPONSEMESSAGE + global TIMEOUTRESPONSEMESSAGE + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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% + } + if (timeout != "") { + WinWait, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWait, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp +} + + +AHKWinWaitActive(ByRef command) { + global WINDOWRESPONSEMESSAGE + global TIMEOUTRESPONSEMESSAGE + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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% + } + if (timeout != "") { + WinWaitActive, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitActive, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp +} + + +AHKWinWaitNotActive(ByRef command) { + global WINDOWRESPONSEMESSAGE + global TIMEOUTRESPONSEMESSAGE + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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% + } + if (timeout != "") { + WinWaitNotActive, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitNotActive, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + } else { + WinGet, output, ID + resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return resp +} + + + +AHKWinMinimize(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + + WinMinimize, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() +} + +AHKWinMaximize(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + + WinMaximize, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() +} + +AHKWinRestore(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + + WinRestore, %title%, %text%, %secondstowait%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() +} + +AHKWinIsActive(ByRef command) { + global BOOLEANRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 WinActive(title, text, extitle, extext) { + response := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + } else { + response := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + +AHKWinGetID(ByRef command) { + global WINDOWRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, ID, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(WINDOWRESPONSEMESSAGE, output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + +AHKWinGetTitle(ByRef command) { + global STRINGRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGetTitle, text, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatResponse(STRINGRESPONSEMESSAGE, text) +} + +AHKWinGetIDLast(ByRef command) { + global WINDOWRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, IDLast, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(WINDOWRESPONSEMESSAGE, output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + + +AHKWinGetPID(ByRef command) { + global INTEGERRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, PID, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + + +AHKWinGetProcessName(ByRef command) { + global STRINGRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, ProcessName, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + +AHKWinGetProcessPath(ByRef command) { + global STRINGRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, ProcessPath, %title%, %text%, %extitle%, %extext% + if (output = 0 || output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + + +AHKWinGetCount(ByRef command) { + global INTEGERRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, Count, %title%, %text%, %extitle%, %extext% + if (output = 0) { + response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + } else { + response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + + + +AHKWinGetMinMax(ByRef command) { + global INTEGERRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, MinMax, %title%, %text%, %extitle%, %extext% + if (output = "") { + response := FormatNoValueResponse() + } else { + response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + +AHKWinGetControlList(ByRef command) { + global EXCEPTIONRESPONSEMESSAGE + global WINDOWCONTROLLISTRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + + 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(WINDOWCONTROLLISTRESPONSEMESSAGE, Format("('{}', [])", ahkid)) + } + + ctrListArr := StrSplit(ctrList, "`n") + ctrListIDArr := StrSplit(ctrListID, "`n") + if (ctrListArr.Length() != ctrListIDArr.Length()) { + return FormatResponse(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(WINDOWCONTROLLISTRESPONSEMESSAGE, output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + +AHKWinGetTransparent(ByRef command) { + global INTEGERRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, Transparent, %title%, %text%, %extitle%, %extext% + response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} +AHKWinGetTransColor(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, TransColor, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} +AHKWinGetStyle(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, Style, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} +AHKWinGetExStyle(ByRef command) { + global STRINGRESPONSEMESSAGE + global INTEGERRESPONSEMESSAGE + global NOVALUERESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGet, output, ExStyle, %title%, %text%, %extitle%, %extext% + response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + +AHKWinGetText(ByRef command) { + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGetText, output,%title%,%text%,%extitle%,%extext% + + if (ErrorLevel = 1) { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window text") + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + + + +AHKWinSetTitle(ByRef command) { + new_title := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + WinSetTitle, %title%, %text%, %new_title%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKWinSetAlwaysOnTop(ByRef command) { + toggle := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + WinSet, AlwaysOntop, %toggle%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKWinSetBottom(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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, Bottom,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKWinShow(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinShow, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKWinHide(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinHide, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + + +AHKWinSetTop(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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, Top,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKWinSetEnable(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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, Enable,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKWinSetDisable(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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, Disable,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKWinSetRedraw(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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, Redraw,, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKWinSetStyle(ByRef command) { + global BOOLEANRESPONSEMESSAGE + style := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + + WinSet, Style, %style%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + } else { + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp +} + +AHKWinSetExStyle(ByRef command) { + global BOOLEANRESPONSEMESSAGE + style := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + + WinSet, ExStyle, %style%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + } else { + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp +} + +AHKWinSetRegion(ByRef command) { + global BOOLEANRESPONSEMESSAGE + options := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + + WinSet, Region, %options%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + } else { + resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp +} + +AHKWinSetTransparent(ByRef command) { + global BOOLEANRESPONSEMESSAGE + transparency := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + + WinSet, Transparent, %transparency%, %title%, %text%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() +} + +AHKWinSetTransColor(ByRef command) { + global BOOLEANRESPONSEMESSAGE + color := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + + WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% + return FormatNoValueResponse() +} + +AHKImageSearch(ByRef command) { + global COORDINATERESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + imagepath := command[6] + x1 := command[2] + y1 := command[3] + x2 := command[4] + y2 := command[5] + + if (x2 = "A_ScreenWidth") { + x2 := A_ScreenWidth + } + if (y2 = "A_ScreenHeight") { + y2 := A_ScreenHeight + } + ImageSearch, xpos, ypos,% x1,% y1,% x2,% y2, %imagepath% + if (ErrorLevel = 2) { + s := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "there was a problem that prevented the command 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(COORDINATERESPONSEMESSAGE, Format("({}, {})", xpos, ypos)) + } + + return s +} + +AHKPixelGetColor(ByRef command) { + global STRINGRESPONSEMESSAGE + x := command[2] + y := command[3] + coord_mode := command[4] + options := command[5] + + 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(STRINGRESPONSEMESSAGE, color) +} + +AHKPixelSearch(ByRef command) { + global COORDINATERESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + x1 := command[2] + y1 := command[3] + x2 := command[4] + y2 := command[5] + color := command[6] + variation := command[7] + options := command[8] + coord_mode := command[9] + + 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(COORDINATERESPONSEMESSAGE, payload) + } else if (ErrorLevel = 2) { + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem conducting the pixel search (ErrorLevel 2)") + } else { + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Unexpected error. This is probably a bug. Please report this at https://github.com/spyoungtech/ahk/issues") + } + +} + + +AHKMouseGetPos(ByRef command) { + global COORDINATERESPONSEMESSAGE + MouseGetPos, xpos, ypos + payload := Format("({}, {})", xpos, ypos) + resp := FormatResponse(COORDINATERESPONSEMESSAGE, payload) + return resp +} + +AHKKeyState(ByRef command) { + global INTEGERRESPONSEMESSAGE + global FLOATRESPONSEMESSAGE + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + + keyname := command[2] + mode := command[3] + if (mode != "") { + state := GetKeyState(keyname, mode) + } else{ + state := GetKeyState(keyname) + } + + if (state = "") { + return FormatNoValueResponse() + } + + if state is integer + return FormatResponse(INTEGERRESPONSEMESSAGE, state) + + if state is float + return FormatResponse(FLOATRESPONSEMESSAGE, state) + + if state is alnum + return FormatResponse(STRINGRESPONSEMESSAGE, state) + + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, state) +} + +AHKMouseMove(ByRef command) { + x := command[2] + y := command[3] + speed := command[4] + relative := command[5] + if (relative != "") { + MouseMove, %x%, %y%, %speed%, R + } else { + MouseMove, %x%, %y%, %speed% + } + resp := FormatNoValueResponse() + return resp +} + + +AHKClick(ByRef command) { + x := command[2] + y := command[3] + button := command[4] + click_count := command[5] + direction := command[6] + r := command[7] + relative_to := command[8] + current_coord_rel := Format("{}", A_CoordModeMouse) + + if (relative_to != "") { + CoordMode, Mouse, %relative_to% + } + + Click, %x%, %y%, %button%, %direction%, %r% + + if (relative_to != "") { + CoordMode, Mouse, %current_coord_rel% + } + + return FormatNoValueResponse() + +} + +AHKGetCoordMode(ByRef command) { + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + target := command[2] + + if (target = "ToolTip") { + return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeToolTip) + } + if (target = "Pixel") { + return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModePixel) + } + if (target = "Mouse") { + return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeMouse) + } + if (target = "Caret") { + return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeCaret) + } + if (target = "Menu") { + return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeMenu) + } + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Invalid coord mode") +} + +AHKSetCoordMode(ByRef command) { + target := command[2] + relative_to := command[3] + CoordMode, %target%, %relative_to% + + return FormatNoValueResponse() +} + +AHKMouseClickDrag(ByRef command) { + button := command[2] + x1 := command[3] + y1 := command[4] + x2 := command[5] + y2 := command[6] + speed := command[7] + relative := command[8] + relative_to := command[9] + + 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% + } + + return FormatNoValueResponse() + +} + +RegRead(ByRef command) { + keyname := command[3] + RegRead, output, %keyname%, command[4] + return output +} + +SetRegView(ByRef command) { + view := command[2] + SetRegView, %view% +} + +RegWrite(ByRef command) { + valuetype := command[2] + keyname := command[3] + + RegWrite, %valuetype%, %keyname%, command[4] +} + +RegDelete(ByRef command) { + keyname := command[2] + RegDelete, %keyname%, command[3] +} + +AHKKeyWait(ByRef command) { + global INTEGERRESPONSEMESSAGE + keyname := command[2] + if (command.Length() = 2) { + KeyWait,% keyname + } else { + options := command[3] + KeyWait,% keyname,% options + } + return FormatResponse(INTEGERRESPONSEMESSAGE, ErrorLevel) +} + +SetKeyDelay(ByRef command) { + SetKeyDelay, command[2], command[3] +} + +Join(sep, params*) { + for index,param in params + str := param . sep + return SubStr(str, 1, -StrLen(sep)) +} + +Unescape(HayStack) { + ReplacedStr := StrReplace(Haystack, "``n" , "`n") + return ReplacedStr +} + +AHKSend(ByRef command) { + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + + Send,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + return FormatNoValueResponse() +} + +AHKSendRaw(ByRef command) { + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %key_delay%, %key_press_duration% + } + + SendRaw,% str + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay, %current_delay%, %current_key_duration% + } + return FormatNoValueResponse() +} + +AHKSendInput(ByRef command) { + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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() +} + + +AHKSendEvent(ByRef command) { + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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() +} + +AHKSendPlay(ByRef command) { + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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() +} + +AHKSetCapsLockState(ByRef command) { + state := command[2] + if (state = "") { + SetCapsLockState % !GetKeyState("CapsLock", "T") + } else { + SetCapsLockState, %state% + } + return FormatNoValueResponse() +} + +HideTrayTip(ByRef command) { + 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 + } +} + + + + +AHKWinGetClass(ByRef command) { + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGetClass, output,%title%,%text%,%extitle%,%extext% + + if (ErrorLevel = 1) { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window class") + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return response +} + +AHKWinActivate(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinActivate, %title%, %text%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return FormatNoValueResponse() +} + + + + +AHKWindowList(ByRef command) { + global WINDOWIDLISTRESPONSEMESSAGE + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + if (detect_hw) { + DetectHiddenWindows, %detect_hw% + } + + WinGet windows, List, %title%, %text%, %extitle%, %extext% + r := "" + Loop %windows% + { + id := windows%A_Index% + r .= id . "`," + } + resp := FormatResponse(WINDOWIDLISTRESPONSEMESSAGE, r) + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return resp +} + + + +AHKControlClick(ByRef command) { + global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] + title := command[3] + text := command[4] + button := command[5] + click_count := command[6] + options := command[7] + exclude_title := command[8] + exclude_text := command[9] + detect_hw := command[10] + match_mode := command[11] + match_speed := command[12] + + 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(EXCEPTIONRESPONSEMESSAGE, "Failed to click control") + } else { + response := FormatNoValueResponse() + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response +} + +AHKControlGetText(ByRef command) { + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + ControlGetText, result, %ctrl%, %title%, %text%, %extitle%, %extext% + + if (ErrorLevel = 1) { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the text") + } else { + response := FormatResponse(STRINGRESPONSEMESSAGE, result) + } + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response +} + + +AHKControlGetPos(ByRef command) { + global POSITIONRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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% + } + + ControlGetPos, x, y, w, h, %ctrl%, %title%, %text%, %extitle%, %extext% + if (ErrorLevel = 1) { + response := FormatResponse(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(PositionResponseMessage, result) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response + + +} + +AHKControlSend(ByRef command) { + ctrl := command[2] + keys := command[3] + title := command[4] + text := command[5] + extitle := command[6] + extext := command[7] + detect_hw := command[8] + match_mode := command[9] + match_speed := command[10] + + 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() +} + + + + +AHKWinFromMouse(ByRef command) { + global WINDOWRESPONSEMESSAGE + MouseGetPos,,, MouseWin + + if (MouseWin = "") { + return FormatNoValueResponse() + } + + return FormatResponse(WINDOWRESPONSEMESSAGE, MouseWin) +} + + +AHKWinIsAlwaysOnTop(ByRef command) { + global BOOLEANRESPONSEMESSAGE + title := command[2] + WinGet, ExStyle, ExStyle, %title% + if (ExStyle = "") + return FormatNoValueResponse() + + if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. + return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + else + return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) +} + + +AHKWinMove(ByRef command) { + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + x := command[9] + y := command[10] + width := command[11] + height := command[12] + + 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() + +} + +AHKWinGetPos(ByRef command) { + global POSITIONRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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% + } + + WinGetPos, x, y, w, h, %title%, %text%, %extitle%, %extext% + + if (ErrorLevel = 1) { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the position") + } else { + result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) + response := FormatResponse(PositionResponseMessage, result) + } + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + + return response +} + +CountNewlines(ByRef s) { + newline := "`n" + StringReplace, s, s, %newline%, %newline%, UseErrorLevel + count := ErrorLevel + return count +} + +AHKEcho(ByRef command) { + global STRINGRESPONSEMESSAGE + return FormatResponse(STRINGRESPONSEMESSAGE, command) +} + + +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. + + 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") +} + +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 +} + +stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] +pyresp := "" + +Loop { + query := RTrim(stdin.ReadLine(), "`n") + commandArray := CommandArrayFromQuery(query) + try { + func := commandArray[1] + pyresp := %func%(commandArray) + } catch e { + pyresp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, e) + } + + if (pyresp) { + FileAppend, %pyresp%, *, UTF-8 + } else { + msg := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("Unknown Error when calling {}", func)) + FileAppend, %msg%, *, UTF-8 + } +} + +""" diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index bde9ecac..7957c4d3 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -903,7 +903,8 @@ def key_state( 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) - return self._transport.function_call('AHKKeyState', args, blocking=blocking) + resp = self._transport.function_call('AHKKeyState', args, blocking=blocking) + return resp # fmt: off @overload @@ -1097,7 +1098,9 @@ def set_capslock_state( f'Invalid value for state. Must be one of On, Off, AlwaysOn, AlwaysOff or None. Got {state!r}' ) args.append(str(state)) - return self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) + + resp = self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) + return resp def set_volume(self, value: int, device_number: int = 1) -> None: raise NotImplementedError() @@ -2751,16 +2754,15 @@ def win_is_active( 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: ... + 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: ... + 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]: ... + 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]]: ... + 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, diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 399e475a..12092f69 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -5,6 +5,7 @@ import os import subprocess import sys +import tempfile import warnings from abc import ABC from abc import abstractmethod @@ -37,6 +38,7 @@ from ahk.message import RequestMessage from ahk.message import ResponseMessage from ahk.message import Position +from ahk._constants import DAEMON_SCRIPT as _DAEMON_SCRIPT from concurrent.futures import Future, ThreadPoolExecutor @@ -466,7 +468,11 @@ def function_call( engine: Optional[AHK] = None, ) -> Any: if not self._started: - self.init() + 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) @@ -491,6 +497,7 @@ class DaemonProcessTransport(Transport): def __init__(self, *, executable_path: Union[str, os.PathLike[AnyStr]] = ''): self._proc: Optional[SyncAHKProcess] self._proc = None + self._temp_script: Optional[str] = None super().__init__(executable_path=executable_path) def init(self) -> None: @@ -500,13 +507,35 @@ def init(self) -> None: def start(self) -> None: assert self._proc is None, 'cannot start a process twice' - daemon_script = os.path.abspath(os.path.join(os.path.dirname(__file__), '../daemon.ahk')) - runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] - self._proc = SyncAHKProcess(runargs=runargs) - self._proc.start() + with warnings.catch_warnings(record=True) as caught_warnings: + self._proc = self._create_process() + if caught_warnings: + for warning in caught_warnings: + warnings.warn(warning.message, warning.category, stacklevel=2) def _create_process(self) -> SyncAHKProcess: daemon_script = os.path.abspath(os.path.join(os.path.dirname(__file__), '../daemon.ahk')) + if not os.path.exists(daemon_script): + if self._temp_script is None or not os.path.exists(self._temp_script): + warnings.warn( + 'daemon script not found. This is typically the result of a error in attempting to ' + 'repackage/redistribute `ahk` without including its package data. Will attempt to run ' + 'daemon script from tempfile, but this action may be blocked by some security tools. ' + 'To fix this warning, make sure to include package data when bundling applications that ' + 'depend on `ahk`', + category=UserWarning, + stacklevel=2, + ) + + with tempfile.NamedTemporaryFile( + mode='w', prefix='python-ahk-', suffix='.ahk', delete=False + ) as tempscriptfile: + tempscriptfile.write(_DAEMON_SCRIPT) # XXX: can we make this async? + self._temp_script = tempscriptfile.name + daemon_script = self._temp_script + atexit.register(os.remove, tempscriptfile.name) + else: + daemon_script = self._temp_script runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] proc = SyncAHKProcess(runargs=runargs) proc.start() diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py new file mode 100644 index 00000000..835fc780 --- /dev/null +++ b/tests/_async/test_scripts.py @@ -0,0 +1,38 @@ +import asyncio +import os +import pathlib +import subprocess +import sys +import time +from unittest import IsolatedAsyncioTestCase +from unittest import mock + +from ahk import AsyncAHK +from ahk import AsyncWindow + +async_sleep = asyncio.sleep # unasync: remove + +sleep = time.sleep + + +class TestScripts(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_script_missing_makes_tempfile(self): + with mock.patch('os.path.exists', new=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) From c02949dac0eb17d935c991032c5c365d594a5422 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 11 Jan 2023 17:28:36 -0800 Subject: [PATCH 325/588] add test script --- tests/_async/test_scripts.py | 15 +++------------ tests/_sync/test_scripts.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 tests/_sync/test_scripts.py diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index 835fc780..f819ba54 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -1,21 +1,12 @@ -import asyncio -import os import pathlib -import subprocess -import sys import time -from unittest import IsolatedAsyncioTestCase -from unittest import mock +import unittest.mock from ahk import AsyncAHK from ahk import AsyncWindow -async_sleep = asyncio.sleep # unasync: remove -sleep = time.sleep - - -class TestScripts(IsolatedAsyncioTestCase): +class TestScripts(unittest.IsolatedAsyncioTestCase): win: AsyncWindow async def asyncSetUp(self) -> None: @@ -29,7 +20,7 @@ async def asyncTearDown(self) -> None: time.sleep(0.2) async def test_script_missing_makes_tempfile(self): - with mock.patch('os.path.exists', new=mock.Mock(return_value=False)): + with 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 diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py new file mode 100644 index 00000000..cf3d4cac --- /dev/null +++ b/tests/_sync/test_scripts.py @@ -0,0 +1,29 @@ +import pathlib +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 + time.sleep(0.2) + + def test_script_missing_makes_tempfile(self): + with 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) From e83caf879f8c1c4411dd0832698b08432f9bce4e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 11 Jan 2023 17:33:05 -0800 Subject: [PATCH 326/588] support coord mode for mouse position --- ahk/_async/engine.py | 16 ++++++++++------ ahk/_constants.py | 11 +++++++++++ ahk/_sync/engine.py | 16 ++++++++++------ ahk/daemon.ahk | 11 +++++++++++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 58d23086..960c8080 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -606,18 +606,22 @@ async def list_windows( # fmt: off @overload - async def get_mouse_position(self, *, blocking: Literal[True]) -> Tuple[int, int]: ... + async def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: Literal[True]) -> Tuple[int, int]: ... @overload - async def get_mouse_position(self, *, blocking: Literal[False]) -> AsyncFutureResult[Tuple[int, int]]: ... + async def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: Literal[False]) -> AsyncFutureResult[Tuple[int, int]]: ... @overload - async def get_mouse_position(self) -> Tuple[int, int]: ... + async def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None) -> Tuple[int, int]: ... @overload - async def get_mouse_position(self, *, blocking: bool = True) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... + async def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: bool = True) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... # fmt: on async def get_mouse_position( - self, *, blocking: bool = True + self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: bool = True ) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: - resp = await self._transport.function_call('AHKMouseGetPos', blocking=blocking) + if coord_mode: + args = [str(coord_mode)] + else: + args = [] + resp = await self._transport.function_call('AHKMouseGetPos', args, blocking=blocking) return resp @property diff --git a/ahk/_constants.py b/ahk/_constants.py index f181a176..5b84f068 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -1530,9 +1530,20 @@ AHKMouseGetPos(ByRef command) { global COORDINATERESPONSEMESSAGE + coord_mode := command[2] + current_coord_mode := Format("{}", A_CoordModeMouse) + if (coord_mode != "") { + CoordMode, Mouse, %coord_mode% + } MouseGetPos, xpos, ypos + payload := Format("({}, {})", xpos, ypos) resp := FormatResponse(COORDINATERESPONSEMESSAGE, payload) + + if (coord_mode != "") { + CoordMode, Mouse, %current_coord_mode% + } + return resp } diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 7957c4d3..33399d57 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -602,18 +602,22 @@ def list_windows( # fmt: off @overload - def get_mouse_position(self, *, blocking: Literal[True]) -> Tuple[int, int]: ... + def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: Literal[True]) -> Tuple[int, int]: ... @overload - def get_mouse_position(self, *, blocking: Literal[False]) -> FutureResult[Tuple[int, int]]: ... + def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: Literal[False]) -> FutureResult[Tuple[int, int]]: ... @overload - def get_mouse_position(self) -> Tuple[int, int]: ... + def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None) -> Tuple[int, int]: ... @overload - def get_mouse_position(self, *, blocking: bool = True) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... + def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: bool = True) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... # fmt: on def get_mouse_position( - self, *, blocking: bool = True + self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: bool = True ) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: - resp = self._transport.function_call('AHKMouseGetPos', blocking=blocking) + if coord_mode: + args = [str(coord_mode)] + else: + args = [] + resp = self._transport.function_call('AHKMouseGetPos', args, blocking=blocking) return resp @property diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index 113219c6..d20dbef6 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -1527,9 +1527,20 @@ AHKPixelSearch(ByRef command) { AHKMouseGetPos(ByRef command) { global COORDINATERESPONSEMESSAGE + coord_mode := command[2] + current_coord_mode := Format("{}", A_CoordModeMouse) + if (coord_mode != "") { + CoordMode, Mouse, %coord_mode% + } MouseGetPos, xpos, ypos + payload := Format("({}, {})", xpos, ypos) resp := FormatResponse(COORDINATERESPONSEMESSAGE, payload) + + if (coord_mode != "") { + CoordMode, Mouse, %current_coord_mode% + } + return resp } From 4dbd5840a21b12e954daec39621c00b0947f4bda Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 11 Jan 2023 17:43:52 -0800 Subject: [PATCH 327/588] fix tests --- tests/_async/test_scripts.py | 2 +- tests/_sync/test_scripts.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index f819ba54..d30e299f 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -20,7 +20,7 @@ async def asyncTearDown(self) -> None: time.sleep(0.2) async def test_script_missing_makes_tempfile(self): - with mock.patch('os.path.exists', new=unittest.mock.Mock(return_value=False)): + 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 diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py index cf3d4cac..9b80adba 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -20,7 +20,7 @@ def tearDown(self) -> None: time.sleep(0.2) def test_script_missing_makes_tempfile(self): - with mock.patch('os.path.exists', new=unittest.mock.Mock(return_value=False)): + 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 From 31773fd35a659967fabc662efc4cfa218bc46eb2 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 19 Jan 2023 21:04:23 -0800 Subject: [PATCH 328/588] templating, directives --- _set_constants.py | 2 +- ahk/_async/engine.py | 8 +- ahk/_async/transport.py | 69 +++++++++------- ahk/_constants.py | 34 ++++---- ahk/_sync/engine.py | 8 +- ahk/_sync/transport.py | 69 +++++++++------- ahk/daemon.ahk | 32 ++++---- ahk/directives.py | 155 ++++++++++++++++++++++++++++++++++++ tests/_async/test_screen.py | 4 +- tests/_sync/test_screen.py | 4 +- 10 files changed, 281 insertions(+), 104 deletions(-) create mode 100644 ahk/directives.py diff --git a/_set_constants.py b/_set_constants.py index 4d0ca5be..97e3acca 100644 --- a/_set_constants.py +++ b/_set_constants.py @@ -14,7 +14,7 @@ # THIS FILE IS AUTOGENERATED BY _set_constants.py # DO NOT EDIT BY HAND -DAEMON_SCRIPT = r"""{daemon_script} +DAEMON_SCRIPT_TEMPLATE = r"""{daemon_script} """ ''' diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 960c8080..4be14fc7 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -20,6 +20,7 @@ from .._hotkey import Hotkey from .._hotkey import Hotstring +from ..directives import Directive if sys.version_info < (3, 10): from typing_extensions import TypeAlias @@ -122,14 +123,13 @@ def __init__( self, *, TransportClass: Optional[Type[AsyncTransport]] = None, - transport_options: Optional[Dict[str, Any]] = None, + directives: Optional[list[Directive | Type[Directive]]] = None, + executable_path: str = '', ): - if transport_options is None: - transport_options = {} if TransportClass is None: TransportClass = AsyncDaemonProcessTransport assert TransportClass is not None - transport = TransportClass(**transport_options) + transport = TransportClass(executable_path=executable_path, directives=directives) self._transport: AsyncTransport = transport def __getattr__(self, item: Any) -> Any: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 19c38714..25a97225 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -12,7 +12,6 @@ from io import BytesIO from shutil import which from typing import Any -from typing import AnyStr from typing import Generic from typing import List from typing import Literal @@ -21,6 +20,7 @@ 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 @@ -34,11 +34,15 @@ else: from typing import TypeAlias, TypeGuard +import jinja2 + from ahk._hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from ahk.message import RequestMessage from ahk.message import ResponseMessage from ahk.message import Position -from ahk._constants import DAEMON_SCRIPT as _DAEMON_SCRIPT +from ahk.message import _message_registry +from ahk._constants import DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE +from ahk.directives import Directive from concurrent.futures import Future, ThreadPoolExecutor @@ -227,7 +231,7 @@ class AhkExecutableNotFoundError(EnvironmentError): pass -def _resolve_executable_path(executable_path: Union[str, os.PathLike[AnyStr]] = '') -> str: +def _resolve_executable_path(executable_path: str = '') -> str: if not executable_path: executable_path = ( os.environ.get('AHK_PATH', '') @@ -270,10 +274,16 @@ def _resolve_executable_path(executable_path: Union[str, os.PathLike[AnyStr]] = class AsyncTransport(ABC): _started: bool = False - def __init__(self, /, executable_path: Union[str, os.PathLike[AnyStr]] = '', **kwargs: Any): + def __init__( + self, + /, + executable_path: str = '', + directives: Optional[list[Union[Directive, Type[Directive]]]] = None, + **kwargs: Any, + ): self._executable_path: str = _resolve_executable_path(executable_path=executable_path) self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) - pass + self._directives: list[Union[Directive, Type[Directive]]] = directives or [] def add_hotkey(self, hotkey: Hotkey) -> None: with warnings.catch_warnings(record=True) as caught_warnings: @@ -518,11 +528,16 @@ def send_nonblocking( class AsyncDaemonProcessTransport(AsyncTransport): - def __init__(self, *, executable_path: Union[str, os.PathLike[AnyStr]] = ''): + def __init__( + self, + *, + executable_path: str = '', + directives: Optional[list[Directive | Type[Directive]]] = None, + ): self._proc: Optional[AsyncAHKProcess] self._proc = None self._temp_script: Optional[str] = None - super().__init__(executable_path=executable_path) + super().__init__(executable_path=executable_path, directives=directives) async def init(self) -> None: await self.start() @@ -537,29 +552,25 @@ async def start(self) -> None: for warning in caught_warnings: warnings.warn(warning.message, warning.category, stacklevel=2) + def _render_script(self) -> str: + template = jinja2.Environment(loader=jinja2.BaseLoader(), trim_blocks=True, autoescape=False).from_string( + _DAEMON_SCRIPT_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) + async def _create_process(self) -> AsyncAHKProcess: - daemon_script = os.path.abspath(os.path.join(os.path.dirname(__file__), '../daemon.ahk')) - if not os.path.exists(daemon_script): - if self._temp_script is None or not os.path.exists(self._temp_script): - warnings.warn( - 'daemon script not found. This is typically the result of a error in attempting to ' - 'repackage/redistribute `ahk` without including its package data. Will attempt to run ' - 'daemon script from tempfile, but this action may be blocked by some security tools. ' - 'To fix this warning, make sure to include package data when bundling applications that ' - 'depend on `ahk`', - category=UserWarning, - stacklevel=2, - ) - - with tempfile.NamedTemporaryFile( - mode='w', prefix='python-ahk-', suffix='.ahk', delete=False - ) as tempscriptfile: - tempscriptfile.write(_DAEMON_SCRIPT) # XXX: can we make this async? - self._temp_script = tempscriptfile.name - daemon_script = self._temp_script - atexit.register(os.remove, tempscriptfile.name) - else: - daemon_script = self._temp_script + 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(os.remove, tempscriptfile.name) + else: + daemon_script = self._temp_script runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] proc = AsyncAHKProcess(runargs=runargs) await proc.start() diff --git a/ahk/_constants.py b/ahk/_constants.py index 5b84f068..40e7757b 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -1,24 +1,24 @@ # THIS FILE IS AUTOGENERATED BY _set_constants.py # DO NOT EDIT BY HAND -DAEMON_SCRIPT = r"""#NoEnv +DAEMON_SCRIPT_TEMPLATE = r"""#NoEnv #Persistent #SingleInstance Off -RESPONSEMESSAGE := "000" ; ResponseMessage -TUPLERESPONSEMESSAGE := "001" ; TupleResponseMessage -COORDINATERESPONSEMESSAGE := "002" ; CoordinateResponseMessage -INTEGERRESPONSEMESSAGE := "003" ; IntegerResponseMessage -BOOLEANRESPONSEMESSAGE := "004" ; BooleanResponseMessage -STRINGRESPONSEMESSAGE := "005" ; StringResponseMessage -WINDOWIDLISTRESPONSEMESSAGE := "006" ; WindowIDListResponseMessage -NOVALUERESPONSEMESSAGE := "007" ; NoValueResponseMessage -EXCEPTIONRESPONSEMESSAGE := "008" ; ExceptionResponseMessage -WINDOWCONTROLLISTRESPONSEMESSAGE := "009" ; WindowControlListResponseMessage -WINDOWRESPONSEMESSAGE := "00a" ; WindowResponseMessage -POSITIONRESPONSEMESSAGE := "00b" ; PositionResponseMessage -FLOATRESPONSEMESSAGE := "00c" ; FloatResponseMessage -TIMEOUTRESPONSEMESSAGE := "00d" ; TimeoutResponseMessage +; BEGIN user-defined directives + +{% for directive in directives %} +{{ directive }} + +{% endfor %} + +; END user-defined directives + + +{% for tom, name in message_types.items() %} +{{ name }} := "{{ tom }}" +{% endfor %} + NOVALUE_SENTINEL := Chr(57344) @@ -1917,7 +1917,7 @@ AHKWindowList(ByRef command) { - global WINDOWIDLISTRESPONSEMESSAGE + global WINDOWLISTRESPONSEMESSAGE current_detect_hw := Format("{}", A_DetectHiddenWindows) @@ -1948,7 +1948,7 @@ id := windows%A_Index% r .= id . "`," } - resp := FormatResponse(WINDOWIDLISTRESPONSEMESSAGE, r) + resp := FormatResponse(WINDOWLISTRESPONSEMESSAGE, r) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 33399d57..b3887fbf 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -20,6 +20,7 @@ from .._hotkey import Hotkey from .._hotkey import Hotstring +from ..directives import Directive if sys.version_info < (3, 10): from typing_extensions import TypeAlias @@ -118,14 +119,13 @@ def __init__( self, *, TransportClass: Optional[Type[Transport]] = None, - transport_options: Optional[Dict[str, Any]] = None, + directives: Optional[list[Directive | Type[Directive]]] = None, + executable_path: str = '', ): - if transport_options is None: - transport_options = {} if TransportClass is None: TransportClass = DaemonProcessTransport assert TransportClass is not None - transport = TransportClass(**transport_options) + transport = TransportClass(executable_path=executable_path, directives=directives) self._transport: Transport = transport def __getattr__(self, item: Any) -> Any: diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 12092f69..471a6ba7 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -12,7 +12,6 @@ from io import BytesIO from shutil import which from typing import Any -from typing import AnyStr from typing import Generic from typing import List from typing import Literal @@ -21,6 +20,7 @@ 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 @@ -34,11 +34,15 @@ else: from typing import TypeAlias, TypeGuard +import jinja2 + from ahk._hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from ahk.message import RequestMessage from ahk.message import ResponseMessage from ahk.message import Position -from ahk._constants import DAEMON_SCRIPT as _DAEMON_SCRIPT +from ahk.message import _message_registry +from ahk._constants import DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE +from ahk.directives import Directive from concurrent.futures import Future, ThreadPoolExecutor @@ -210,7 +214,7 @@ class AhkExecutableNotFoundError(EnvironmentError): pass -def _resolve_executable_path(executable_path: Union[str, os.PathLike[AnyStr]] = '') -> str: +def _resolve_executable_path(executable_path: str = '') -> str: if not executable_path: executable_path = ( os.environ.get('AHK_PATH', '') @@ -253,10 +257,16 @@ def _resolve_executable_path(executable_path: Union[str, os.PathLike[AnyStr]] = class Transport(ABC): _started: bool = False - def __init__(self, /, executable_path: Union[str, os.PathLike[AnyStr]] = '', **kwargs: Any): + def __init__( + self, + /, + executable_path: str = '', + directives: Optional[list[Union[Directive, Type[Directive]]]] = None, + **kwargs: Any, + ): self._executable_path: str = _resolve_executable_path(executable_path=executable_path) self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) - pass + self._directives: list[Union[Directive, Type[Directive]]] = directives or [] def add_hotkey(self, hotkey: Hotkey) -> None: with warnings.catch_warnings(record=True) as caught_warnings: @@ -494,11 +504,16 @@ def send_nonblocking( class DaemonProcessTransport(Transport): - def __init__(self, *, executable_path: Union[str, os.PathLike[AnyStr]] = ''): + def __init__( + self, + *, + executable_path: str = '', + directives: Optional[list[Directive | Type[Directive]]] = None, + ): self._proc: Optional[SyncAHKProcess] self._proc = None self._temp_script: Optional[str] = None - super().__init__(executable_path=executable_path) + super().__init__(executable_path=executable_path, directives=directives) def init(self) -> None: self.start() @@ -513,29 +528,25 @@ def start(self) -> None: for warning in caught_warnings: warnings.warn(warning.message, warning.category, stacklevel=2) + def _render_script(self) -> str: + template = jinja2.Environment(loader=jinja2.BaseLoader(), trim_blocks=True, autoescape=False).from_string( + _DAEMON_SCRIPT_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) + def _create_process(self) -> SyncAHKProcess: - daemon_script = os.path.abspath(os.path.join(os.path.dirname(__file__), '../daemon.ahk')) - if not os.path.exists(daemon_script): - if self._temp_script is None or not os.path.exists(self._temp_script): - warnings.warn( - 'daemon script not found. This is typically the result of a error in attempting to ' - 'repackage/redistribute `ahk` without including its package data. Will attempt to run ' - 'daemon script from tempfile, but this action may be blocked by some security tools. ' - 'To fix this warning, make sure to include package data when bundling applications that ' - 'depend on `ahk`', - category=UserWarning, - stacklevel=2, - ) - - with tempfile.NamedTemporaryFile( - mode='w', prefix='python-ahk-', suffix='.ahk', delete=False - ) as tempscriptfile: - tempscriptfile.write(_DAEMON_SCRIPT) # XXX: can we make this async? - self._temp_script = tempscriptfile.name - daemon_script = self._temp_script - atexit.register(os.remove, tempscriptfile.name) - else: - daemon_script = self._temp_script + 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(os.remove, tempscriptfile.name) + else: + daemon_script = self._temp_script runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] proc = SyncAHKProcess(runargs=runargs) proc.start() diff --git a/ahk/daemon.ahk b/ahk/daemon.ahk index d20dbef6..3dd8d7a7 100644 --- a/ahk/daemon.ahk +++ b/ahk/daemon.ahk @@ -2,20 +2,20 @@ #Persistent #SingleInstance Off -RESPONSEMESSAGE := "000" ; ResponseMessage -TUPLERESPONSEMESSAGE := "001" ; TupleResponseMessage -COORDINATERESPONSEMESSAGE := "002" ; CoordinateResponseMessage -INTEGERRESPONSEMESSAGE := "003" ; IntegerResponseMessage -BOOLEANRESPONSEMESSAGE := "004" ; BooleanResponseMessage -STRINGRESPONSEMESSAGE := "005" ; StringResponseMessage -WINDOWIDLISTRESPONSEMESSAGE := "006" ; WindowIDListResponseMessage -NOVALUERESPONSEMESSAGE := "007" ; NoValueResponseMessage -EXCEPTIONRESPONSEMESSAGE := "008" ; ExceptionResponseMessage -WINDOWCONTROLLISTRESPONSEMESSAGE := "009" ; WindowControlListResponseMessage -WINDOWRESPONSEMESSAGE := "00a" ; WindowResponseMessage -POSITIONRESPONSEMESSAGE := "00b" ; PositionResponseMessage -FLOATRESPONSEMESSAGE := "00c" ; FloatResponseMessage -TIMEOUTRESPONSEMESSAGE := "00d" ; TimeoutResponseMessage +; BEGIN user-defined directives + +{% for directive in directives %} +{{ directive }} + +{% endfor %} + +; END user-defined directives + + +{% for tom, name in message_types.items() %} +{{ name }} := "{{ tom }}" +{% endfor %} + NOVALUE_SENTINEL := Chr(57344) @@ -1914,7 +1914,7 @@ AHKWinActivate(ByRef command) { AHKWindowList(ByRef command) { - global WINDOWIDLISTRESPONSEMESSAGE + global WINDOWLISTRESPONSEMESSAGE current_detect_hw := Format("{}", A_DetectHiddenWindows) @@ -1945,7 +1945,7 @@ AHKWindowList(ByRef command) { id := windows%A_Index% r .= id . "`," } - resp := FormatResponse(WINDOWIDLISTRESPONSEMESSAGE, r) + resp := FormatResponse(WINDOWLISTRESPONSEMESSAGE, r) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% diff --git a/ahk/directives.py b/ahk/directives.py new file mode 100644 index 00000000..259de495 --- /dev/null +++ b/ahk/directives.py @@ -0,0 +1,155 @@ +""" +Contains directive classes +""" +from types import SimpleNamespace +from typing import Any +from typing import NoReturn + + +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) -> str: + return f'#{cls.__name__}' + + def __hash__(self) -> int: + return hash(str(self)) + + def __eq__(cls, other: Any) -> bool: + return bool(str(cls) == other) + + +class Directive(SimpleNamespace, metaclass=DirectiveMeta): + """ + Simple directive class + 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: Any): + super().__init__(name=self.name, **kwargs) + self._kwargs = kwargs + + @property + def name(self) -> str: + return self.__class__.__name__ + + 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() + + def __eq__(self, other: Any) -> bool: + return bool(str(self) == other) + + def __hash__(self) -> int: # type: ignore[override] + return hash(str(self)) + + +class AllowSameLineComments(Directive): + pass + + +class ClipboardTimeout(Directive): + def __init__(self, milliseconds: int = 0, **kwargs: Any): + kwargs['milliseconds'] = milliseconds + super().__init__(**kwargs) + + +class ErrorStdOut(Directive): + pass + + +class HotKeyInterval(ClipboardTimeout): + pass + + +class HotKeyModifierTimeout(HotKeyInterval): + pass + + +class Include(Directive): + def __init__(self, include_name: str, **kwargs: Any): + kwargs['include_name'] = include_name + super().__init__(**kwargs) + + +class IncludeAgain(Include): + pass + + +class InputLevel(Directive): + def __init__(self, level: int, **kwargs: Any): + kwargs['level'] = level + super().__init__(**kwargs) + + +class InstallKeybdHook(Directive): + pass + + +class InstallMouseHook(Directive): + pass + + +class KeyHistory(Directive): + def __init__(self, limit: int = 40, **kwargs: Any): + kwargs['limit'] = limit + super().__init__(**kwargs) + + +class MaxHotkeysPerInterval(Directive): + def __init__(self, value: int, **kwargs: Any): + kwargs['value'] = value + super().__init__(**kwargs) + + +class MaxMem(Directive): + def __init__(self, megabytes: int, **kwargs: Any): + if megabytes < 1: + raise ValueError('megabytes cannot be less than 1') + if megabytes > 4095: + raise ValueError('megabytes cannot exceed 4095') + kwargs['megabytes'] = megabytes + super().__init__(**kwargs) + + +class MaxThreads(Directive): + def __init__(self) -> NoReturn: + raise NotImplementedError() + + +class MaxThreadsBuffer(Directive): + def __init__(self) -> NoReturn: + raise NotImplementedError() + + +class MaxThreadsPerHotkey(Directive): + def __init__(self) -> NoReturn: + raise NotImplementedError() + + +class MenuMaskKey(Directive): + def __init__(self) -> NoReturn: + raise NotImplementedError() + + +class NoTrayIcon(Directive): + pass + + +class UseHook(Directive): + pass + + +class Warn(Directive): + pass + + +class WinActivateForce(Directive): + pass diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index f2d15b84..56239d49 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -45,7 +45,7 @@ async def test_image_search(self): self.skipTest('This test does not work in GitHub Actions') return self._show_in_thread() - time.sleep(2) + time.sleep(3) self.im.save('testimage.png') position = await self.ahk.image_search('testimage.png') assert isinstance(position, tuple) @@ -55,7 +55,7 @@ async def test_pixel_search(self): self.skipTest('This test does not work in GitHub Actions') return self._show_in_thread() - time.sleep(2) + time.sleep(3) self.im.save('testimage.png') position = await self.ahk.image_search('testimage.png') assert position is not None diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index 410c55c3..3d911be9 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -45,7 +45,7 @@ def test_image_search(self): self.skipTest('This test does not work in GitHub Actions') return self._show_in_thread() - time.sleep(2) + time.sleep(3) self.im.save('testimage.png') position = self.ahk.image_search('testimage.png') assert isinstance(position, tuple) @@ -55,7 +55,7 @@ def test_pixel_search(self): self.skipTest('This test does not work in GitHub Actions') return self._show_in_thread() - time.sleep(2) + time.sleep(3) self.im.save('testimage.png') position = self.ahk.image_search('testimage.png') assert position is not None From 297b5149e3eb9a63ee22f08eca805fe0455e8579 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 1 Feb 2023 16:19:24 -0800 Subject: [PATCH 329/588] implement `type` --- ahk/_async/engine.py | 28 +++++++++++++++------------- ahk/_hotkey.py | 4 ++-- ahk/_sync/engine.py | 28 +++++++++++++++------------- ahk/_utils.py | 28 ++++++++++++++++++---------- tests/_async/test_window.py | 8 +++++++- tests/_sync/test_window.py | 8 +++++++- 6 files changed, 64 insertions(+), 40 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 4be14fc7..cf4da74e 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -8,7 +8,6 @@ from typing import Awaitable from typing import Callable from typing import Coroutine -from typing import Dict from typing import List from typing import Literal from typing import NoReturn @@ -20,6 +19,7 @@ from .._hotkey import Hotkey from .._hotkey import Hotstring +from .._utils import type_escape from ..directives import Directive if sys.version_info < (3, 10): @@ -132,18 +132,6 @@ def __init__( transport = TransportClass(executable_path=executable_path, directives=directives) self._transport: AsyncTransport = transport - def __getattr__(self, item: Any) -> Any: - deprecation_replacements: Dict[str, Any] = {'type': self.send_input} - if item in deprecation_replacements: - func = deprecation_replacements[item] - warnings.warn( - f'{item!r} is deprecated and will be removed in a future version. Use {func.__name__!r} instead.', - DeprecationWarning, - stacklevel=2, - ) - return deprecation_replacements[item] - raise AttributeError(f'{self.__class__.__qualname__!r} object has no attribute {item!r}') - def add_hotkey( self, keyname: str, callback: Callable[[], Any], *, ex_handler: Optional[Callable[[str, Exception], Any]] = None ) -> None: @@ -1062,6 +1050,20 @@ async def send_input(self, s: str, *, blocking: bool = True) -> Union[None, Asyn 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]]: + 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: ... diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index 4e41ba3c..3bf5a69e 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -34,7 +34,7 @@ from jinja2 import Environment, BaseLoader from queue import Queue -from ahk._utils import escape_sequence_replace +from ahk._utils import hotkey_escape P_HotkeyCallbackParam = ParamSpec('P_HotkeyCallbackParam') T_HotkeyCallbackReturn = TypeVar('T_HotkeyCallbackReturn') @@ -258,7 +258,7 @@ def __init__( self.replacement: Optional[str] self.callback: Optional[Callable[[], Any]] self.ex_handler: Optional[Callable[[str, Exception], Any]] - self._trigger: str = escape_sequence_replace(trigger) + self._trigger: str = hotkey_escape(trigger) self._options: str = options if callable(replacement_or_callback): self.replacement = None diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index b3887fbf..7e59a62b 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -8,7 +8,6 @@ from typing import Awaitable from typing import Callable from typing import Coroutine -from typing import Dict from typing import List from typing import Literal from typing import NoReturn @@ -20,6 +19,7 @@ from .._hotkey import Hotkey from .._hotkey import Hotstring +from .._utils import type_escape from ..directives import Directive if sys.version_info < (3, 10): @@ -128,18 +128,6 @@ def __init__( transport = TransportClass(executable_path=executable_path, directives=directives) self._transport: Transport = transport - def __getattr__(self, item: Any) -> Any: - deprecation_replacements: Dict[str, Any] = {'type': self.send_input} - if item in deprecation_replacements: - func = deprecation_replacements[item] - warnings.warn( - f'{item!r} is deprecated and will be removed in a future version. Use {func.__name__!r} instead.', - DeprecationWarning, - stacklevel=2, - ) - return deprecation_replacements[item] - raise AttributeError(f'{self.__class__.__qualname__!r} object has no attribute {item!r}') - def add_hotkey( self, keyname: str, callback: Callable[[], Any], *, ex_handler: Optional[Callable[[str, Exception], Any]] = None ) -> None: @@ -1051,6 +1039,20 @@ def send_input(self, s: str, *, blocking: bool = True) -> Union[None, FutureResu 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]]: + 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: ... diff --git a/ahk/_utils.py b/ahk/_utils.py index 8f0ae281..7f58368b 100644 --- a/ahk/_utils.py +++ b/ahk/_utils.py @@ -1,4 +1,4 @@ -ESCAPE_SEQUENCE_MAP = { +HOTKEY_ESCAPE_SEQUENCE_MAP = { '\n': '`n', '\t': '`t', '\r': '`r', @@ -13,16 +13,24 @@ ':': '`:', } +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 escape_sequence_replace(s: str) -> str: - """ - 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{!}' - """ +def type_escape(s: str) -> str: return s.translate(_TRANSLATION_TABLE) diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 472c8526..befbb2bc 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -118,10 +118,16 @@ async def test_control_send_window(self): async def test_send_literal_comma(self): await self.win.send('hello, world') - print(self.win) 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_literal_tilde_n(self): expected_text = '```nim\nimport std/strformat\n```' await self.win.send(expected_text) diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index a395fb05..bbb3f024 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -118,10 +118,16 @@ def test_control_send_window(self): def test_send_literal_comma(self): self.win.send('hello, world') - print(self.win) 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_literal_tilde_n(self): expected_text = '```nim\nimport std/strformat\n```' self.win.send(expected_text) From 85fc7d790b294cc52c010a818294569bac8c3e94 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 1 Feb 2023 20:28:41 -0800 Subject: [PATCH 330/588] better templating with fallback --- MANIFEST.in | 2 +- _set_constants.py | 8 +++- ahk/_async/transport.py | 34 +++++++++++--- ahk/_constants.py | 79 +++++++++++++++++++++++++++++++++ ahk/_hotkey.py | 22 +++++---- ahk/_sync/transport.py | 34 +++++++++++--- ahk/{ => templates}/daemon.ahk | 0 ahk/{ => templates}/hotkeys.ahk | 0 setup.cfg | 7 ++- 9 files changed, 159 insertions(+), 27 deletions(-) rename ahk/{ => templates}/daemon.ahk (100%) rename ahk/{ => templates}/hotkeys.ahk (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 3ab9b44e..e086d46b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include ahk/daemon.ahk +include ahk/templates/daemon.ahk include ahk/hotkeys.ahk include docs/README.md include buildunasync.py diff --git a/_set_constants.py b/_set_constants.py index 97e3acca..b6ecb297 100644 --- a/_set_constants.py +++ b/_set_constants.py @@ -2,9 +2,12 @@ import subprocess import sys -with open('ahk/daemon.ahk') as f: +with open('ahk/templates/daemon.ahk') as f: daemon_script = f.read() +with open('ahk/templates/hotkeys.ahk') as hotkeyfile: + hotkey_script = hotkeyfile.read() + GIT_EXECUTABLE = shutil.which('git') if not GIT_EXECUTABLE: @@ -16,6 +19,9 @@ DAEMON_SCRIPT_TEMPLATE = r"""{daemon_script} """ + +HOTKEYS_SCRIPT_TEMPLATE = r"""{hotkey_script} +""" ''' with open('ahk/_constants.py', encoding='utf-8') as f: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 25a97225..9eaea0f8 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -533,12 +533,34 @@ def __init__( *, executable_path: str = '', directives: Optional[list[Directive | Type[Directive]]] = None, + jinja_loader: Optional[jinja2.BaseLoader] = None, + template: Optional[jinja2.Template] = None, ): self._proc: Optional[AsyncAHKProcess] self._proc = None self._temp_script: Optional[str] = None + self.__template: jinja2.Template + self._jinja_env: jinja2.Environment + if jinja_loader is None: + self._jinja_env = jinja2.Environment( + loader=jinja2.PackageLoader('ahk', 'templates'), 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('daemon.ahk') + except jinja2.TemplateNotFound: + warnings.warn('daemon template missing. Falling back to constant', category=UserWarning) + self.__template = self._jinja_env.from_string(_DAEMON_SCRIPT_TEMPLATE) + if template is None: + template = self.__template + self._template: jinja2.Template = template super().__init__(executable_path=executable_path, directives=directives) + @property + def template(self) -> jinja2.Template: + return self._template + async def init(self) -> None: await self.start() await super().init() @@ -552,12 +574,12 @@ async def start(self) -> None: for warning in caught_warnings: warnings.warn(warning.message, warning.category, stacklevel=2) - def _render_script(self) -> str: - template = jinja2.Environment(loader=jinja2.BaseLoader(), trim_blocks=True, autoescape=False).from_string( - _DAEMON_SCRIPT_TEMPLATE - ) + 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) + return template.render(directives=self._directives, message_types=message_types, **kwargs) async def _create_process(self) -> AsyncAHKProcess: if self._temp_script is None or not os.path.exists(self._temp_script): @@ -593,8 +615,6 @@ async def _send_nonblocking( part = await proc.readline() content_buffer.write(part) content = content_buffer.getvalue()[:-1] - except Exception: - raise finally: try: proc.kill() diff --git a/ahk/_constants.py b/ahk/_constants.py index 40e7757b..ba600ec8 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2309,3 +2309,82 @@ } """ + +HOTKEYS_SCRIPT_TEMPLATE = r"""KEEPALIVE := Chr(57344) +SetTimer, keepalive, 1000 + + +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 %} + + +keepalive: +global KEEPALIVE +FileAppend, %KEEPALIVE%`n, *, UTF-8 + +""" diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index 3bf5a69e..d25cc65d 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -3,7 +3,6 @@ import atexit import functools import os -import pathlib import re import subprocess import sys @@ -23,6 +22,8 @@ from typing import TypeVar from typing import Union +import jinja2 + if sys.version_info >= (3, 10): from typing import ParamSpec else: @@ -31,10 +32,10 @@ import logging import tempfile -from jinja2 import Environment, BaseLoader from queue import Queue from ahk._utils import hotkey_escape +from ._constants import HOTKEYS_SCRIPT_TEMPLATE as _HOTKEY_SCRIPT P_HotkeyCallbackParam = ParamSpec('P_HotkeyCallbackParam') T_HotkeyCallbackReturn = TypeVar('T_HotkeyCallbackReturn') @@ -104,6 +105,15 @@ def __init__(self, executable_path: str, default_ex_handler: Optional[Callable[[ self._callback_queue: Queue[Union[str, Type[STOP]]] = Queue() self._listener_thread: Optional[threading.Thread] = None self._dispatcher_thread: Optional[threading.Thread] = None + self._jinja_env: jinja2.Environment = jinja2.Environment( + loader=jinja2.PackageLoader('ahk', 'templates'), autoescape=False + ) + self._template: jinja2.Template + try: + self._template = self._jinja_env.get_template('hotkeys.ahk') + except jinja2.TemplateNotFound: + warnings.warn('hotkey template not found, falling back to constant', category=UserWarning) + self._template = self._jinja_env.from_string(_HOTKEY_SCRIPT) def _do_callback( self, hotkey: str, cb: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None @@ -180,13 +190,7 @@ def dispatcher(self) -> None: self._callback_queue.task_done() # maybe _do_callback should handle this? def _render_hotkey_tempate(self) -> str: - env = Environment(loader=BaseLoader()) - # TODO: make string constant for template - fname = pathlib.Path(__file__).parent / 'hotkeys.ahk' - template_string = open(fname).read() - template = env.from_string(template_string) - ret = template.render(hotkeys=list(self._hotkeys.values()), hotstrings=self._hotstrings.values()) - assert isinstance(ret, str) + ret = self._template.render(hotkeys=list(self._hotkeys.values()), hotstrings=self._hotstrings.values()) return ret def listener(self) -> None: diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 471a6ba7..4b9b90bb 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -509,12 +509,34 @@ def __init__( *, executable_path: str = '', directives: Optional[list[Directive | Type[Directive]]] = None, + jinja_loader: Optional[jinja2.BaseLoader] = None, + template: Optional[jinja2.Template] = None, ): self._proc: Optional[SyncAHKProcess] self._proc = None self._temp_script: Optional[str] = None + self.__template: jinja2.Template + self._jinja_env: jinja2.Environment + if jinja_loader is None: + self._jinja_env = jinja2.Environment( + loader=jinja2.PackageLoader('ahk', 'templates'), 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('daemon.ahk') + except jinja2.TemplateNotFound: + warnings.warn('daemon template missing. Falling back to constant', category=UserWarning) + self.__template = self._jinja_env.from_string(_DAEMON_SCRIPT_TEMPLATE) + if template is None: + template = self.__template + self._template: jinja2.Template = template super().__init__(executable_path=executable_path, directives=directives) + @property + def template(self) -> jinja2.Template: + return self._template + def init(self) -> None: self.start() super().init() @@ -528,12 +550,12 @@ def start(self) -> None: for warning in caught_warnings: warnings.warn(warning.message, warning.category, stacklevel=2) - def _render_script(self) -> str: - template = jinja2.Environment(loader=jinja2.BaseLoader(), trim_blocks=True, autoescape=False).from_string( - _DAEMON_SCRIPT_TEMPLATE - ) + 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) + return template.render(directives=self._directives, message_types=message_types, **kwargs) def _create_process(self) -> SyncAHKProcess: if self._temp_script is None or not os.path.exists(self._temp_script): @@ -569,8 +591,6 @@ def _send_nonblocking( part = proc.readline() content_buffer.write(part) content = content_buffer.getvalue()[:-1] - except Exception: - raise finally: try: proc.kill() diff --git a/ahk/daemon.ahk b/ahk/templates/daemon.ahk similarity index 100% rename from ahk/daemon.ahk rename to ahk/templates/daemon.ahk diff --git a/ahk/hotkeys.ahk b/ahk/templates/hotkeys.ahk similarity index 100% rename from ahk/hotkeys.ahk rename to ahk/templates/hotkeys.ahk diff --git a/setup.cfg b/setup.cfg index 1b2e6bbf..408d1004 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ classifiers = Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 [options] include_package_data = True @@ -45,5 +46,7 @@ cmdclass = [options.package_data] ahk = py.typed - daemon.ahk - hotkeys.ahk + templates/*.ahk + +[bdist_wheel] +universal = True From 45a6ca0296a8de88980eff1d061ce6bc5dd497e6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 2 Feb 2023 00:54:26 -0800 Subject: [PATCH 331/588] add get volume --- ahk/_async/engine.py | 4 +++- ahk/_async/transport.py | 4 +++- ahk/_constants.py | 22 ++++++++++++++++++++++ ahk/_sync/engine.py | 4 +++- ahk/_sync/transport.py | 4 +++- ahk/templates/daemon.ahk | 22 ++++++++++++++++++++++ 6 files changed, 56 insertions(+), 4 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index cf4da74e..4b352eef 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -817,7 +817,9 @@ async def find_window_by_title( return windows[0] if windows else None async def get_volume(self, device_number: int = 1) -> float: - raise NotImplementedError() + args = [str(device_number)] + response = await self._transport.function_call('AHKGetVolume', args) + return response # fmt: off @overload diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 9eaea0f8..a0dad662 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -86,6 +86,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKGetSendLevel', 'AHKGetTitleMatchMode', 'AHKGetTitleMatchSpeed', + 'AHKGetVolume', 'AHKImageSearch', 'AHKKeyState', 'AHKKeyWait', @@ -463,7 +464,8 @@ async def function_call(self, function_name: Literal['AHKWinShow'], args: Option async def function_call(self, function_name: Literal['AHKWinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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] = 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['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_constants.py b/ahk/_constants.py index ba600ec8..4d4268b4 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2223,6 +2223,28 @@ return response } + +AHKGetVolume(ByRef command) { + global EXCEPTIONRESPONSEMESSAGE + global FLOATRESPONSEMESSAGE + device_number := command[2] + + try { + SoundGetWaveVolume, retval, %device_number% + } catch e { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {}", device_number)) + return response + } + if (ErrorLevel = 1) { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {}", device_number)) + } else { + response := FormatResponse(FLOATRESPONSEMESSAGE, Format("{}", retval)) + } + return response +} + + + CountNewlines(ByRef s) { newline := "`n" StringReplace, s, s, %newline%, %newline%, UseErrorLevel diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 7e59a62b..bd34cd20 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -806,7 +806,9 @@ def find_window_by_title( return windows[0] if windows else None def get_volume(self, device_number: int = 1) -> float: - raise NotImplementedError() + args = [str(device_number)] + response = self._transport.function_call('AHKGetVolume', args) + return response # fmt: off @overload diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 4b9b90bb..2e79175d 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -78,6 +78,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKGetSendLevel', 'AHKGetTitleMatchMode', 'AHKGetTitleMatchSpeed', + 'AHKGetVolume', 'AHKImageSearch', 'AHKKeyState', 'AHKKeyWait', @@ -446,7 +447,8 @@ def function_call(self, function_name: Literal['AHKWinShow'], args: Optional[Lis def function_call(self, function_name: Literal['AHKWinHide'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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] = None) -> Union[bool, FutureResult[bool]]: ... - + @overload + def function_call(self, function_name: Literal['AHKGetVolume'], args: Optional[List[str]] = None) -> float: ... # @overload # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 3dd8d7a7..7ecafa17 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2220,6 +2220,28 @@ AHKWinGetPos(ByRef command) { return response } + +AHKGetVolume(ByRef command) { + global EXCEPTIONRESPONSEMESSAGE + global FLOATRESPONSEMESSAGE + device_number := command[2] + + try { + SoundGetWaveVolume, retval, %device_number% + } catch e { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {}", device_number)) + return response + } + if (ErrorLevel = 1) { + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {}", device_number)) + } else { + response := FormatResponse(FLOATRESPONSEMESSAGE, Format("{}", retval)) + } + return response +} + + + CountNewlines(ByRef s) { newline := "`n" StringReplace, s, s, %newline%, %newline%, UseErrorLevel From 67012ef521ff4e44145db44f32060638ef9a79d3 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 2 Feb 2023 01:28:53 -0800 Subject: [PATCH 332/588] add sound methods --- ahk/_async/engine.py | 36 +++++++++++++++++++++++++----------- ahk/_async/transport.py | 16 ++++++++++++++++ ahk/_constants.py | 38 ++++++++++++++++++++++++++++++++++++++ ahk/_sync/engine.py | 35 ++++++++++++++++++++++++----------- ahk/_sync/transport.py | 16 ++++++++++++++++ ahk/templates/daemon.ahk | 38 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 157 insertions(+), 22 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 4b352eef..e4a3fb55 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1121,8 +1121,11 @@ async def set_capslock_state( resp = await self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) return resp - async def set_volume(self, value: int, device_number: int = 1) -> None: - raise NotImplementedError() + async def set_volume( + self, value: int, device_number: int = 1, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + args = [str(device_number), str(value)] + return await self._transport.function_call('AHKSetVolume', args, blocking=blocking) async def show_error_traytip( self, @@ -1169,16 +1172,25 @@ async def show_warning_traytip( ) -> None: raise NotImplementedError() - async def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: - raise NotImplementedError() + async def sound_beep( + self, frequency: int = 523, duration: int = 150, *, blocking: bool = True + ) -> Optional[AsyncFutureResult[None]]: + args = [str(frequency), str(duration)] + await self._transport.function_call('AHKSoundBeep', args, blocking=blocking) + return None async def sound_get( - self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME' - ) -> None: - raise NotImplementedError() + self, + device_number: int = 1, + component_type: str = 'MASTER', + control_type: str = 'VOLUME', + blocking: bool = True, + ) -> Union[str, AsyncFutureResult[str]]: + args = [str(device_number), component_type, control_type] + return await self._transport.function_call('AHKSoundGet', args, blocking=blocking) - async def sound_play(self, filename: str, blocking: bool = True) -> None: - raise NotImplementedError() + async def sound_play(self, filename: str, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + return await self._transport.function_call('AHKSoundPlay', [filename], blocking=blocking) async def sound_set( self, @@ -1186,8 +1198,10 @@ async def sound_set( device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME', - ) -> None: - raise NotImplementedError() + blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + args = [str(device_number), component_type, control_type, str(value)] + return await self._transport.function_call('AHKSoundSet', args, blocking=blocking) # fmt: off @overload diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index a0dad662..737fbb77 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -104,6 +104,11 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSetCoordMode', 'AHKSetSendLevel', 'AHKSetTitleMatchMode', + 'AHKSetVolume', + 'AHKSoundBeep', + 'AHKSoundGet', + 'AHKSoundPlay', + 'AHKSoundSet', 'AHKWinActivate', 'AHKWinClose', 'AHKWinExist', @@ -466,6 +471,17 @@ async def function_call(self, function_name: Literal['AHKWinHide'], args: Option async def function_call(self, function_name: Literal['AHKWinIsActive'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = 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['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/_constants.py b/ahk/_constants.py index 4d4268b4..ffd425a1 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2243,7 +2243,45 @@ return response } +AHKSoundBeep(ByRef command) { + freq := command[2] + duration := command[3] + SoundBeep , %freq%, %duration% + return FormatNoValueResponse() +} + +AHKSoundGet(ByRef command) { + global STRINGRESPONSEMESSAGE + device_number := command[2] + component_type := command[3] + control_type := command[4] + SoundGet, retval, %component_type%, %control_type%, %device_number% + ; TODO interpret return type + return FormatResponse(STRINGRESPONSEMESSAGE, Format("{}", retval)) +} + +AHKSoundSet(ByRef command) { + device_number := command[2] + component_type := command[3] + control_type := command[4] + value := command[5] + SoundSet, %value%, %component_type%, %control_type%, %device_number% + return FormatNoValueResponse() +} + +AHKSoundPlay(ByRef command) { + filename := command[2] + SoundPlay, %filename% + return FormatNoValueResponse() +} + +AHKSetVolume(ByRef command) { + device_number := command[2] + value := command[3] + SoundSetWaveVolume, %value%, %device_number% + return FormatNoValueResponse() +} CountNewlines(ByRef s) { newline := "`n" diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index bd34cd20..56dbc05e 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1110,8 +1110,10 @@ def set_capslock_state( resp = self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) return resp - def set_volume(self, value: int, device_number: int = 1) -> None: - raise NotImplementedError() + def set_volume(self, value: int, device_number: int = 1, blocking: bool = True) -> Union[None, FutureResult[None]]: + args = [str(device_number), str(value)] + return self._transport.function_call('AHKSetVolume', args, blocking=blocking) + def show_error_traytip( self, @@ -1158,16 +1160,25 @@ def show_warning_traytip( ) -> None: raise NotImplementedError() - def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: - raise NotImplementedError() + def sound_beep( + self, frequency: int = 523, duration: int = 150, *, blocking: bool = True + ) -> Optional[FutureResult[None]]: + args = [str(frequency), str(duration)] + self._transport.function_call('AHKSoundBeep', args, blocking=blocking) + return None def sound_get( - self, device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME' - ) -> None: - raise NotImplementedError() + self, + device_number: int = 1, + component_type: str = 'MASTER', + control_type: str = 'VOLUME', + blocking: bool = True, + ) -> Union[str, FutureResult[str]]: + args = [str(device_number), component_type, control_type] + return self._transport.function_call('AHKSoundGet', args, blocking=blocking) - def sound_play(self, filename: str, blocking: bool = True) -> None: - raise NotImplementedError() + def sound_play(self, filename: str, blocking: bool = True) -> Union[None, FutureResult[None]]: + return self._transport.function_call('AHKSoundPlay', [filename], blocking=blocking) def sound_set( self, @@ -1175,8 +1186,10 @@ def sound_set( device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME', - ) -> None: - raise NotImplementedError() + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + args = [str(device_number), component_type, control_type, str(value)] + return self._transport.function_call('AHKSoundSet', args, blocking=blocking) # fmt: off @overload diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 2e79175d..9a19d6de 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -96,6 +96,11 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSetCoordMode', 'AHKSetSendLevel', 'AHKSetTitleMatchMode', + 'AHKSetVolume', + 'AHKSoundBeep', + 'AHKSoundGet', + 'AHKSoundPlay', + 'AHKSoundSet', 'AHKWinActivate', 'AHKWinClose', 'AHKWinExist', @@ -449,6 +454,17 @@ def function_call(self, function_name: Literal['AHKWinHide'], args: Optional[Lis def function_call(self, function_name: Literal['AHKWinIsActive'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = 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 # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... # @overload diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 7ecafa17..a1111210 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2240,7 +2240,45 @@ AHKGetVolume(ByRef command) { return response } +AHKSoundBeep(ByRef command) { + freq := command[2] + duration := command[3] + SoundBeep , %freq%, %duration% + return FormatNoValueResponse() +} + +AHKSoundGet(ByRef command) { + global STRINGRESPONSEMESSAGE + device_number := command[2] + component_type := command[3] + control_type := command[4] + SoundGet, retval, %component_type%, %control_type%, %device_number% + ; TODO interpret return type + return FormatResponse(STRINGRESPONSEMESSAGE, Format("{}", retval)) +} + +AHKSoundSet(ByRef command) { + device_number := command[2] + component_type := command[3] + control_type := command[4] + value := command[5] + SoundSet, %value%, %component_type%, %control_type%, %device_number% + return FormatNoValueResponse() +} + +AHKSoundPlay(ByRef command) { + filename := command[2] + SoundPlay, %filename% + return FormatNoValueResponse() +} + +AHKSetVolume(ByRef command) { + device_number := command[2] + value := command[3] + SoundSetWaveVolume, %value%, %device_number% + return FormatNoValueResponse() +} CountNewlines(ByRef s) { newline := "`n" From bd3c2cd9d301b73e00453bb33f33010cdb58b300 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 2 Feb 2023 01:45:21 -0800 Subject: [PATCH 333/588] add traytip --- ahk/_async/engine.py | 64 ++++++++++++++++++++++--------------- ahk/_async/transport.py | 3 ++ ahk/_constants.py | 10 ++++++ ahk/_sync/engine.py | 69 ++++++++++++++++++++++++---------------- ahk/_sync/transport.py | 3 ++ ahk/templates/daemon.ahk | 10 ++++++ 6 files changed, 105 insertions(+), 54 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index e4a3fb55..ecf919a6 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1127,48 +1127,72 @@ async def set_volume( args = [str(device_number), str(value)] return await self._transport.function_call('AHKSetVolume', args, blocking=blocking) - async def show_error_traytip( + async def show_traytip( self, title: str, text: str, second: float = 1.0, + type_id: int = 1, + *, silent: bool = False, large_icon: bool = False, blocking: bool = True, - ) -> None: - raise NotImplementedError() + ) -> Union[None, AsyncFutureResult[None]]: + 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) - async def show_info_traytip( + async def show_error_traytip( self, title: str, text: str, second: float = 1.0, + *, silent: bool = False, large_icon: bool = False, blocking: bool = True, - ) -> None: - raise NotImplementedError() + ) -> Union[None, AsyncFutureResult[None]]: + return await self.show_traytip( + title=title, text=text, second=second, type_id=3, silent=silent, large_icon=large_icon, blocking=blocking + ) - async def show_tooltip( + async def show_info_traytip( self, + title: str, text: str, - x: Optional[int] = None, - y: Optional[int] = None, - *, second: float = 1.0, - id: Optional[str] = None, + *, + silent: bool = False, + large_icon: bool = False, blocking: bool = True, - ) -> None: - raise NotImplementedError() + ) -> Union[None, AsyncFutureResult[None]]: + return await self.show_traytip( + title=title, text=text, second=second, type_id=1, silent=silent, large_icon=large_icon, blocking=blocking + ) async def show_warning_traytip( self, title: str, text: str, second: float = 1.0, - slient: bool = False, + *, + silent: bool = False, large_icon: bool = False, blocking: bool = True, + ) -> Union[None, AsyncFutureResult[None]]: + 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, + *, + second: float = 1.0, + id: Optional[str] = None, + blocking: bool = True, ) -> None: raise NotImplementedError() @@ -2441,18 +2465,6 @@ async def pixel_search( resp = await self._transport.function_call('AHKPixelSearch', args, blocking=blocking) return resp - async def show_traytip( - self, - title: str, - text: str, - second: float = 1.0, - type_id: int = 1, - slient: bool = False, - large_icon: bool = False, - blocking: bool = True, - ) -> None: - raise NotImplementedError() - # 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: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 737fbb77..72d90bd0 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -109,6 +109,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSoundGet', 'AHKSoundPlay', 'AHKSoundSet', + 'AHKTrayTip', 'AHKWinActivate', 'AHKWinClose', 'AHKWinExist', @@ -481,6 +482,8 @@ async def function_call(self, function_name: Literal['AHKSoundPlay'], args: Opti 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['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/_constants.py b/ahk/_constants.py index ffd425a1..21c222b9 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2295,6 +2295,16 @@ return FormatResponse(STRINGRESPONSEMESSAGE, command) } +AHKTraytip(ByRef command) { + title := command[2] + text := command[3] + second := command[4] + option := command[5] + + TrayTip, %title%, %text%, %second%, %option% + return FormatNoValueResponse() +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 56dbc05e..c2f4fcba 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1110,53 +1110,78 @@ def set_capslock_state( resp = self._transport.function_call('AHKSetCapsLockState', args, blocking=blocking) return resp - def set_volume(self, value: int, device_number: int = 1, blocking: bool = True) -> Union[None, FutureResult[None]]: + def set_volume( + self, value: int, device_number: int = 1, blocking: bool = True + ) -> Union[None, FutureResult[None]]: args = [str(device_number), str(value)] return self._transport.function_call('AHKSetVolume', args, blocking=blocking) - - def show_error_traytip( + def show_traytip( self, title: str, text: str, second: float = 1.0, + type_id: int = 1, + *, silent: bool = False, large_icon: bool = False, blocking: bool = True, - ) -> None: - raise NotImplementedError() + ) -> Union[None, FutureResult[None]]: + 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) - def show_info_traytip( + def show_error_traytip( self, title: str, text: str, second: float = 1.0, + *, silent: bool = False, large_icon: bool = False, blocking: bool = True, - ) -> None: - raise NotImplementedError() + ) -> Union[None, FutureResult[None]]: + return self.show_traytip( + title=title, text=text, second=second, type_id=3, silent=silent, large_icon=large_icon, blocking=blocking + ) - def show_tooltip( + def show_info_traytip( self, + title: str, text: str, - x: Optional[int] = None, - y: Optional[int] = None, - *, second: float = 1.0, - id: Optional[str] = None, + *, + silent: bool = False, + large_icon: bool = False, blocking: bool = True, - ) -> None: - raise NotImplementedError() + ) -> Union[None, FutureResult[None]]: + return self.show_traytip( + title=title, text=text, second=second, type_id=1, silent=silent, large_icon=large_icon, blocking=blocking + ) def show_warning_traytip( self, title: str, text: str, second: float = 1.0, - slient: bool = False, + *, + silent: bool = False, large_icon: bool = False, blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + 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, + *, + second: float = 1.0, + id: Optional[str] = None, + blocking: bool = True, ) -> None: raise NotImplementedError() @@ -2429,18 +2454,6 @@ def pixel_search( resp = self._transport.function_call('AHKPixelSearch', args, blocking=blocking) return resp - def show_traytip( - self, - title: str, - text: str, - second: float = 1.0, - type_id: int = 1, - slient: bool = False, - large_icon: bool = False, - blocking: bool = True, - ) -> None: - raise NotImplementedError() - # 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: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 9a19d6de..e18484da 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -101,6 +101,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSoundGet', 'AHKSoundPlay', 'AHKSoundSet', + 'AHKTrayTip', 'AHKWinActivate', 'AHKWinClose', 'AHKWinExist', @@ -464,6 +465,8 @@ def function_call(self, function_name: Literal['AHKSoundPlay'], args: Optional[L 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 # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index a1111210..6f06b9a7 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2292,6 +2292,16 @@ AHKEcho(ByRef command) { return FormatResponse(STRINGRESPONSEMESSAGE, command) } +AHKTraytip(ByRef command) { + title := command[2] + text := command[3] + second := command[4] + option := command[5] + + TrayTip, %title%, %text%, %second%, %option% + return FormatNoValueResponse() +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance From 1cd52c65ea3f6692fac0347c5a5b1f88006b4191 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 2 Feb 2023 15:57:33 -0800 Subject: [PATCH 334/588] format response byref --- ahk/_constants.py | 2 +- ahk/templates/daemon.ahk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 21c222b9..d1ae348b 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -22,7 +22,7 @@ NOVALUE_SENTINEL := Chr(57344) -FormatResponse(MessageType, payload) { +FormatResponse(ByRef MessageType, ByRef payload) { newline_count := CountNewlines(payload) response := Format("{}`n{}`n{}`n", MessageType, newline_count, payload) return response diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 6f06b9a7..b0aea5e9 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -19,7 +19,7 @@ NOVALUE_SENTINEL := Chr(57344) -FormatResponse(MessageType, payload) { +FormatResponse(ByRef MessageType, ByRef payload) { newline_count := CountNewlines(payload) response := Format("{}`n{}`n{}`n", MessageType, newline_count, payload) return response From f42b55880d986fcface0d6c1cddda71f685f72db Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 2 Feb 2023 21:21:51 -0800 Subject: [PATCH 335/588] add clipboard functions --- MANIFEST.in | 2 +- ahk/_async/engine.py | 31 ++++++++++++++ ahk/_async/transport.py | 14 ++++++- ahk/_constants.py | 76 +++++++++++++++++++++++++++++++++- ahk/_sync/engine.py | 31 ++++++++++++++ ahk/_sync/transport.py | 14 ++++++- ahk/message.py | 10 +++++ ahk/templates/daemon.ahk | 76 +++++++++++++++++++++++++++++++++- setup.cfg | 1 + tests/_async/test_clipboard.py | 27 ++++++++++++ tests/_sync/test_clipboard.py | 27 ++++++++++++ 11 files changed, 302 insertions(+), 7 deletions(-) create mode 100644 tests/_async/test_clipboard.py create mode 100644 tests/_sync/test_clipboard.py diff --git a/MANIFEST.in b/MANIFEST.in index e086d46b..d7c30eeb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ include ahk/templates/daemon.ahk -include ahk/hotkeys.ahk +include ahk/templates/hotkeys.ahk include docs/README.md include buildunasync.py diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index ecf919a6..dea45bff 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1,7 +1,9 @@ from __future__ import annotations import asyncio +import os import sys +import tempfile import time import warnings from typing import Any @@ -2839,6 +2841,35 @@ async def win_move( resp = await self._transport.function_call('AHKWinMove', args, blocking=blocking) return resp + async def get_clipboard(self, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: + return await self._transport.function_call('AHKGetClipboard', blocking=blocking) + + async def set_clipboard(self, s: str, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + args = [s] + return await self._transport.function_call('AHKSetClipboard', args, blocking=blocking) + + async def get_clipboard_all(self) -> Union[bytes, AsyncFutureResult[bytes]]: + return await self._transport.function_call('AHKGetClipboardAll') + + async def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + # 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}'] + try: + resp = await self._transport.function_call('AHKSetClipboardAll', args, blocking=blocking) + return resp + finally: + try: + os.remove(f.name) + except Exception: + pass + async def block_forever(self) -> NoReturn: while True: await async_sleep(1) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 72d90bd0..1a432767 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -82,6 +82,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKControlGetPos', 'AHKControlGetText', 'AHKControlSend', + 'AHKGetClipboard', + 'AHKGetClipboardAll', 'AHKGetCoordMode', 'AHKGetSendLevel', 'AHKGetTitleMatchMode', @@ -100,8 +102,10 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSendInput', 'AHKSendPlay', 'AHKSendRaw', - 'AHKSetDetectHiddenWindows', + 'AHKSetClipboard', + 'AHKSetClipboardAll', 'AHKSetCoordMode', + 'AHKSetDetectHiddenWindows', 'AHKSetSendLevel', 'AHKSetTitleMatchMode', 'AHKSetVolume', @@ -484,6 +488,14 @@ async def function_call(self, function_name: Literal['AHKSoundSet'], args: Optio 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['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/_constants.py b/ahk/_constants.py index d1ae348b..beba9def 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -34,6 +34,12 @@ return FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) } +FormatBinaryResponse(ByRef bin) { + global B64BINARYRESPONSEMESSAGE + b64 := b64encode(bin) + return FormatResponse(B64BINARYRESPONSEMESSAGE, b64) +} + AHKSetDetectHiddenWindows(ByRef command) { value := command[2] DetectHiddenWindows, %value% @@ -2232,7 +2238,7 @@ try { SoundGetWaveVolume, retval, %device_number% } catch e { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {}", device_number)) + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {} ({})", device_number, e.message)) return response } if (ErrorLevel = 1) { @@ -2305,6 +2311,28 @@ return FormatNoValueResponse() } +AHKGetClipboard(ByRef command) { + global STRINGRESPONSEMESSAGE + return FormatResponse(STRINGRESPONSEMESSAGE, Clipboard) +} + +AHKGetClipboardAll(ByRef command) { + data := ClipboardAll + return FormatBinaryResponse(data) +} + +AHKSetClipboard(ByRef command) { + text := command[2] + Clipboard := text + return FormatNoValueResponse() +} + +AHKSetClipboardAll(ByRef command) { + ; TODO there should be a way for us to accept a base64 string instead + filename := command[2] + FileRead, Clipboard, %filename% + return FormatNoValueResponse() +} b64decode(ByRef pszString) { ; TODO load DLL globally for performance @@ -2317,7 +2345,12 @@ ; [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 @@ -2344,6 +2377,44 @@ 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.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 Exception(msg, -1) + } + return ret +} + + +; End of included content + CommandArrayFromQuery(ByRef text) { decoded_commands := [] encoded_array := StrSplit(text, "|") @@ -2367,7 +2438,8 @@ func := commandArray[1] pyresp := %func%(commandArray) } catch e { - pyresp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, e) + message := Format("Error occurred in {}. The error message was: {}", e.What, e.message) + pyresp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, message) } if (pyresp) { diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index c2f4fcba..85d1770d 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1,7 +1,9 @@ from __future__ import annotations import asyncio +import os import sys +import tempfile import time import warnings from typing import Any @@ -2828,6 +2830,35 @@ def win_move( resp = self._transport.function_call('AHKWinMove', args, blocking=blocking) return resp + def get_clipboard(self, blocking: bool = True) -> Union[str, FutureResult[str]]: + return self._transport.function_call('AHKGetClipboard', blocking=blocking) + + def set_clipboard(self, s: str, blocking: bool = True) -> Union[None, FutureResult[None]]: + args = [s] + return self._transport.function_call('AHKSetClipboard', args, blocking=blocking) + + def get_clipboard_all(self) -> Union[bytes, FutureResult[bytes]]: + return self._transport.function_call('AHKGetClipboardAll') + + def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Union[None, FutureResult[None]]: + # 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}'] + try: + resp = self._transport.function_call('AHKSetClipboardAll', args, blocking=blocking) + return resp + finally: + try: + os.remove(f.name) + except Exception: + pass + def block_forever(self) -> NoReturn: while True: sleep(1) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index e18484da..d1175a7b 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -74,6 +74,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKControlGetPos', 'AHKControlGetText', 'AHKControlSend', + 'AHKGetClipboard', + 'AHKGetClipboardAll', 'AHKGetCoordMode', 'AHKGetSendLevel', 'AHKGetTitleMatchMode', @@ -92,8 +94,10 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSendInput', 'AHKSendPlay', 'AHKSendRaw', - 'AHKSetDetectHiddenWindows', + 'AHKSetClipboard', + 'AHKSetClipboardAll', 'AHKSetCoordMode', + 'AHKSetDetectHiddenWindows', 'AHKSetSendLevel', 'AHKSetTitleMatchMode', 'AHKSetVolume', @@ -467,6 +471,14 @@ def function_call(self, function_name: Literal['AHKSoundSet'], args: Optional[Li 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 # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/message.py b/ahk/message.py index 5ec3fb19..b6a59df4 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -1,6 +1,7 @@ from __future__ import annotations import ast +import base64 import itertools import string import sys @@ -318,6 +319,15 @@ class TimeoutResponseMessage(ExceptionResponseMessage): _exception_type = TimeoutError +class B64BinaryResponseMessage(ResponseMessage): + type = 'binary' + + def unpack(self) -> bytes: + b64_content = self._raw_content + b = base64.b64decode(b64_content) + return b + + T_RequestMessageType = TypeVar('T_RequestMessageType', bound='RequestMessage') diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index b0aea5e9..93047a96 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -31,6 +31,12 @@ FormatNoValueResponse() { return FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) } +FormatBinaryResponse(ByRef bin) { + global B64BINARYRESPONSEMESSAGE + b64 := b64encode(bin) + return FormatResponse(B64BINARYRESPONSEMESSAGE, b64) +} + AHKSetDetectHiddenWindows(ByRef command) { value := command[2] DetectHiddenWindows, %value% @@ -2229,7 +2235,7 @@ AHKGetVolume(ByRef command) { try { SoundGetWaveVolume, retval, %device_number% } catch e { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {}", device_number)) + response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {} ({})", device_number, e.message)) return response } if (ErrorLevel = 1) { @@ -2302,6 +2308,28 @@ AHKTraytip(ByRef command) { return FormatNoValueResponse() } +AHKGetClipboard(ByRef command) { + global STRINGRESPONSEMESSAGE + return FormatResponse(STRINGRESPONSEMESSAGE, Clipboard) +} + +AHKGetClipboardAll(ByRef command) { + data := ClipboardAll + return FormatBinaryResponse(data) +} + +AHKSetClipboard(ByRef command) { + text := command[2] + Clipboard := text + return FormatNoValueResponse() +} + +AHKSetClipboardAll(ByRef command) { + ; TODO there should be a way for us to accept a base64 string instead + filename := command[2] + FileRead, Clipboard, %filename% + return FormatNoValueResponse() +} b64decode(ByRef pszString) { ; TODO load DLL globally for performance @@ -2314,7 +2342,12 @@ b64decode(ByRef pszString) { ; [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 @@ -2341,6 +2374,44 @@ b64decode(ByRef pszString) { 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.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 Exception(msg, -1) + } + return ret +} + + +; End of included content + CommandArrayFromQuery(ByRef text) { decoded_commands := [] encoded_array := StrSplit(text, "|") @@ -2364,7 +2435,8 @@ Loop { func := commandArray[1] pyresp := %func%(commandArray) } catch e { - pyresp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, e) + message := Format("Error occurred in {}. The error message was: {}", e.What, e.message) + pyresp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, message) } if (pyresp) { diff --git a/setup.cfg b/setup.cfg index 408d1004..cc2276eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,6 +35,7 @@ include_package_data = True python_requires = >=3.8.0 packages = ahk + ahk.templates ahk._async ahk._sync install_requires = diff --git a/tests/_async/test_clipboard.py b/tests/_async/test_clipboard.py new file mode 100644 index 00000000..602b1ee4 --- /dev/null +++ b/tests/_async/test_clipboard.py @@ -0,0 +1,27 @@ +import time +from unittest import IsolatedAsyncioTestCase + +from ahk import AsyncAHK + + +class TestWindowAsync(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}' diff --git a/tests/_sync/test_clipboard.py b/tests/_sync/test_clipboard.py new file mode 100644 index 00000000..4abb865b --- /dev/null +++ b/tests/_sync/test_clipboard.py @@ -0,0 +1,27 @@ +import time +from unittest import TestCase + +from ahk import AHK + + +class TestWindowAsync(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}' From 3ed2dc5dab1dc970467ea8269bbcedea9a7752c7 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 4 Feb 2023 00:34:40 -0800 Subject: [PATCH 336/588] run_script --- ahk/_async/engine.py | 8 +- ahk/_async/transport.py | 126 +++++++++++++++++-- ahk/_constants.py | 228 ++++++++++++++++++++++++++++++++--- ahk/_sync/engine.py | 6 +- ahk/_sync/transport.py | 109 +++++++++++++++-- ahk/templates/daemon.ahk | 226 +++++++++++++++++++++++++++++++--- buildunasync.py | 1 + tests/_async/test_hotkeys.py | 3 +- tests/_async/test_screen.py | 4 - tests/_async/test_scripts.py | 34 ++++++ tests/_async/test_window.py | 5 +- tests/_sync/test_hotkeys.py | 3 +- tests/_sync/test_screen.py | 4 - tests/_sync/test_scripts.py | 34 ++++++ tests/_sync/test_window.py | 5 +- 15 files changed, 717 insertions(+), 79 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index dea45bff..69d88228 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -662,7 +662,7 @@ async def mouse_move( resp = await self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp - async def a_run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: + async def a_run_script(self, script_text: str, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: raise NotImplementedError() # fmt: off @@ -965,8 +965,10 @@ async def key_wait( resp = await self._transport.function_call('AHKKeyWait', args) return resp - async def run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: - raise NotImplementedError() + async def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, AsyncFutureResult[str]]: + return await self._transport.run_script(script_text_or_path, blocking=blocking, timeout=timeout) async def set_send_level(self, level: int) -> None: if not isinstance(level, int): diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 1a432767..9f81d91d 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -189,11 +189,30 @@ def async_assert_send_nonblocking_type_correct( return True +class Communicable(Protocol): + runargs: List[str] + + def communicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: + ... + + async def acommunicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: + ... + + @property + def returncode(self) -> Optional[int]: + ... + + 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 + async def start(self) -> None: self._proc = await async_create_process(self.runargs) atexit.register(kill, self._proc) @@ -227,6 +246,19 @@ def kill(self) -> None: assert self._proc is not None, 'no process to kill' self._proc.kill() + async def acommunicate( + 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 async_create_process(runargs: List[str]) -> asyncio.subprocess.Process: # unasync: remove return await asyncio.subprocess.create_subprocess_exec( @@ -322,6 +354,12 @@ async def init(self) -> None: self._started = True return None + @abstractmethod + async def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, AsyncFutureResult[str]]: + return NotImplemented + # fmt: off @overload async def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[bool, AsyncFutureResult[bool]]: ... @@ -614,18 +652,29 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A 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, **kwargs) - async def _create_process(self) -> AsyncAHKProcess: - 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(os.remove, tempscriptfile.name) + async 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(os.remove, tempscriptfile.name) + else: + daemon_script = self._temp_script else: - daemon_script = self._temp_script + 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(os.remove, tempscript.name) runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] proc = AsyncAHKProcess(runargs=runargs) await proc.start() @@ -702,6 +751,61 @@ async def send( 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: + stdout, stderr = await proc.acommunicate(script_bytes, 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') + + 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]: + pool = ThreadPoolExecutor(max_workers=1) + + def f() -> str: + stdout, stderr = proc.communicate(script_bytes, 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') + + fut = pool.submit(f) + pool.shutdown(wait=False) + return FutureResult(fut) + + 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) + await proc.start() + if blocking: + 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/_constants.py b/ahk/_constants.py index beba9def..de1ec89b 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -1,23 +1,28 @@ # THIS FILE IS AUTOGENERATED BY _set_constants.py # DO NOT EDIT BY HAND -DAEMON_SCRIPT_TEMPLATE = r"""#NoEnv +DAEMON_SCRIPT_TEMPLATE = r"""{% block daemon_script %} +{% block directives %} +#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 %} - +{% block message_types %} {% for tom, name in message_types.items() %} {{ name }} := "{{ tom }}" {% endfor %} +{% endblock message_types %} NOVALUE_SENTINEL := Chr(57344) @@ -41,12 +46,15 @@ } AHKSetDetectHiddenWindows(ByRef command) { + {% block AHKSetDetectHiddenWindows %} value := command[2] DetectHiddenWindows, %value% return FormatNoValueResponse() + {% endblock AHKSetDetectHiddenWindows %} } AHKSetTitleMatchMode(ByRef command) { + {% block AHKSetTitleMatchMode %} val1 := command[2] val2 := command[3] if (val1 != "") { @@ -56,30 +64,40 @@ SetTitleMatchMode, %val2% } return FormatNoValueResponse() + {% endblock AHKSetTitleMatchMode %} } AHKGetTitleMatchMode(ByRef command) { + {% block AHKGetTitleMatchMode %} global STRINGRESPONSEMESSAGE return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchMode) + {% endblock AHKGetTitleMatchMode %} } AHKGetTitleMatchSpeed(ByRef command) { + {% block AHKGetTitleMatchSpeed %} global STRINGRESPONSEMESSAGE return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchModeSpeed) + {% endblock AHKGetTitleMatchSpeed %} } AHKSetSendLevel(ByRef command) { + {% block AHKSetSendLevel %} level := command[2] SendLevel, %level% return FormatNoValueResponse() + {% endblock AHKSetSendLevel %} } AHKGetSendLevel(ByRef command) { + {% block AHKGetSendLevel %} global INTEGERRESPONSEMESSAGE return FormatResponse(INTEGERRESPONSEMESSAGE, A_SendLevel) + {% endblock AHKGetSendLevel %} } AHKWinExist(ByRef command) { + {% block AHKWinExist %} global BOOLEANRESPONSEMESSAGE title := command[2] text := command[3] @@ -115,9 +133,11 @@ SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinExist %} } AHKWinClose(ByRef command) { + {% block AHKWinClose %} title := command[2] text := command[3] extitle := command[4] @@ -148,9 +168,11 @@ WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% return FormatNoValueResponse() + {% endblock AHKWinClose %} } AHKWinKill(ByRef command) { + {% block AHKWinKill %} title := command[2] text := command[3] extitle := command[4] @@ -182,9 +204,11 @@ SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinKill %} } AHKWinWait(ByRef command) { + {% block AHKWinWait %} global WINDOWRESPONSEMESSAGE global TIMEOUTRESPONSEMESSAGE @@ -226,10 +250,12 @@ SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinWait %} } AHKWinWaitActive(ByRef command) { + {% block AHKWinWaitActive %} global WINDOWRESPONSEMESSAGE global TIMEOUTRESPONSEMESSAGE @@ -271,10 +297,12 @@ SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinWaitActive %} } AHKWinWaitNotActive(ByRef command) { + {% block AHKWinWaitNotActive %} global WINDOWRESPONSEMESSAGE global TIMEOUTRESPONSEMESSAGE @@ -316,11 +344,13 @@ SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinWaitNotActive %} } AHKWinMinimize(ByRef command) { + {% block AHKWinMinimize %} title := command[2] text := command[3] extitle := command[4] @@ -352,9 +382,11 @@ SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinMinimize %} } AHKWinMaximize(ByRef command) { + {% block AHKWinMaximize %} title := command[2] text := command[3] extitle := command[4] @@ -385,9 +417,11 @@ SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinMaximize %} } AHKWinRestore(ByRef command) { + {% block AHKWinRestore %} title := command[2] text := command[3] extitle := command[4] @@ -419,9 +453,11 @@ SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinRestore %} } AHKWinIsActive(ByRef command) { + {% block AHKWinIsActive %} global BOOLEANRESPONSEMESSAGE title := command[2] text := command[3] @@ -454,9 +490,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinIsActive %} } AHKWinGetID(ByRef command) { + {% block AHKWinGetID %} global WINDOWRESPONSEMESSAGE title := command[2] text := command[3] @@ -491,9 +529,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetID %} } AHKWinGetTitle(ByRef command) { + {% block AHKWinGetTitle %} global STRINGRESPONSEMESSAGE title := command[2] text := command[3] @@ -524,9 +564,11 @@ SetTitleMatchMode, %current_match_speed% return FormatResponse(STRINGRESPONSEMESSAGE, text) + {% endblock AHKWinGetTitle %} } AHKWinGetIDLast(ByRef command) { + {% block AHKWinGetIDLast %} global WINDOWRESPONSEMESSAGE title := command[2] text := command[3] @@ -561,10 +603,12 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetIDLast %} } AHKWinGetPID(ByRef command) { + {% block AHKWinGetPID %} global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -599,10 +643,12 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetPID %} } AHKWinGetProcessName(ByRef command) { + {% block AHKWinGetProcessName %} global STRINGRESPONSEMESSAGE title := command[2] text := command[3] @@ -614,12 +660,12 @@ 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 (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } current_detect_hw := Format("{}", A_DetectHiddenWindows) @@ -637,9 +683,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetProcessName %} } AHKWinGetProcessPath(ByRef command) { + {% block AHKWinGetProcessPath %} global STRINGRESPONSEMESSAGE title := command[2] text := command[3] @@ -674,10 +722,12 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetProcessPath %} } AHKWinGetCount(ByRef command) { + {% block AHKWinGetCount %} global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -712,11 +762,13 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetCount %} } AHKWinGetMinMax(ByRef command) { + {% block AHKWinGetMinMax %} global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -751,9 +803,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetMinMax %} } AHKWinGetControlList(ByRef command) { + {% block AHKWinGetControlList %} global EXCEPTIONRESPONSEMESSAGE global WINDOWCONTROLLISTRESPONSEMESSAGE title := command[2] @@ -812,9 +866,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetControlList %} } AHKWinGetTransparent(ByRef command) { + {% block AHKWinGetTransparent %} global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -845,8 +901,10 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetTransparent %} } AHKWinGetTransColor(ByRef command) { + {% block AHKWinGetTransColor %} global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE global NOVALUERESPONSEMESSAGE @@ -879,8 +937,10 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetTransColor %} } AHKWinGetStyle(ByRef command) { + {% block AHKWinGetStyle %} global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE global NOVALUERESPONSEMESSAGE @@ -913,8 +973,10 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetStyle %} } AHKWinGetExStyle(ByRef command) { + {% block AHKWinGetExStyle %} global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE global NOVALUERESPONSEMESSAGE @@ -947,9 +1009,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetExStyle %} } AHKWinGetText(ByRef command) { + {% block AHKWinGetText %} global STRINGRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE title := command[2] @@ -986,11 +1050,13 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetText %} } AHKWinSetTitle(ByRef command) { + {% block AHKWinSetTitle %} new_title := command[2] title := command[3] text := command[4] @@ -1018,9 +1084,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetTitle %} } AHKWinSetAlwaysOnTop(ByRef command) { + {% block AHKWinSetAlwaysOnTop %} toggle := command[2] title := command[3] text := command[4] @@ -1049,9 +1117,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetAlwaysOnTop %} } AHKWinSetBottom(ByRef command) { + {% block AHKWinSetBottom %} title := command[2] text := command[3] extitle := command[4] @@ -1080,9 +1150,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetBottom %} } AHKWinShow(ByRef command) { + {% block AHKWinShow %} title := command[2] text := command[3] extitle := command[4] @@ -1111,9 +1183,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinShow %} } AHKWinHide(ByRef command) { + {% block AHKWinHide %} title := command[2] text := command[3] extitle := command[4] @@ -1142,10 +1216,12 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinHide %} } AHKWinSetTop(ByRef command) { + {% block AHKWinSetTop %} title := command[2] text := command[3] extitle := command[4] @@ -1174,9 +1250,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetTop %} } AHKWinSetEnable(ByRef command) { + {% block AHKWinSetEnable %} title := command[2] text := command[3] extitle := command[4] @@ -1205,9 +1283,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetEnable %} } AHKWinSetDisable(ByRef command) { + {% block AHKWinSetDisable %} title := command[2] text := command[3] extitle := command[4] @@ -1236,9 +1316,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetDisable %} } AHKWinSetRedraw(ByRef command) { + {% block AHKWinSetRedraw %} title := command[2] text := command[3] extitle := command[4] @@ -1267,9 +1349,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetRedraw %} } AHKWinSetStyle(ByRef command) { + {% block AHKWinSetStyle %} global BOOLEANRESPONSEMESSAGE style := command[2] title := command[3] @@ -1305,9 +1389,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinSetStyle %} } AHKWinSetExStyle(ByRef command) { + {% block AHKWinSetExStyle %} global BOOLEANRESPONSEMESSAGE style := command[2] title := command[3] @@ -1343,9 +1429,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinSetExStyle %} } AHKWinSetRegion(ByRef command) { + {% block AHKWinSetRegion %} global BOOLEANRESPONSEMESSAGE options := command[2] title := command[3] @@ -1381,9 +1469,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinSetRegion %} } AHKWinSetTransparent(ByRef command) { + {% block AHKWinSetTransparent %} global BOOLEANRESPONSEMESSAGE transparency := command[2] title := command[3] @@ -1414,9 +1504,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetTransparent %} } AHKWinSetTransColor(ByRef command) { + {% block AHKWinSetTransColor %} global BOOLEANRESPONSEMESSAGE color := command[2] title := command[3] @@ -1444,9 +1536,11 @@ WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% return FormatNoValueResponse() + {% endblock AHKWinSetTransColor %} } AHKImageSearch(ByRef command) { + {% block AHKImageSearch %} global COORDINATERESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE imagepath := command[6] @@ -1471,9 +1565,11 @@ } return s + {% endblock AHKImageSearch %} } AHKPixelGetColor(ByRef command) { + {% block AHKPixelGetColor %} global STRINGRESPONSEMESSAGE x := command[2] y := command[3] @@ -1494,9 +1590,11 @@ } return FormatResponse(STRINGRESPONSEMESSAGE, color) + {% endblock AHKPixelGetColor %} } AHKPixelSearch(ByRef command) { + {% block AHKPixelSearch %} global COORDINATERESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE x1 := command[2] @@ -1531,10 +1629,12 @@ return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Unexpected error. This is probably a bug. Please report this at https://github.com/spyoungtech/ahk/issues") } + {% endblock AHKPixelSearch %} } AHKMouseGetPos(ByRef command) { + {% block AHKMouseGetPos %} global COORDINATERESPONSEMESSAGE coord_mode := command[2] current_coord_mode := Format("{}", A_CoordModeMouse) @@ -1551,9 +1651,11 @@ } return resp + {% endblock AHKMouseGetPos %} } AHKKeyState(ByRef command) { + {% block AHKKeyState %} global INTEGERRESPONSEMESSAGE global FLOATRESPONSEMESSAGE global STRINGRESPONSEMESSAGE @@ -1581,9 +1683,11 @@ return FormatResponse(STRINGRESPONSEMESSAGE, state) return FormatResponse(EXCEPTIONRESPONSEMESSAGE, state) + {% endblock AHKKeyState %} } AHKMouseMove(ByRef command) { + {% block AHKMouseMove %} x := command[2] y := command[3] speed := command[4] @@ -1595,10 +1699,12 @@ } resp := FormatNoValueResponse() return resp + {% endblock AHKMouseMove %} } AHKClick(ByRef command) { + {% block AHKClick %} x := command[2] y := command[3] button := command[4] @@ -1620,9 +1726,11 @@ return FormatNoValueResponse() + {% endblock AHKClick %} } AHKGetCoordMode(ByRef command) { + {% block AHKGetCoordMode %} global STRINGRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE target := command[2] @@ -1643,17 +1751,21 @@ return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeMenu) } return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Invalid coord mode") + {% endblock AHKGetCoordMode %} } AHKSetCoordMode(ByRef command) { + {% block AHKSetCoordMode %} target := command[2] relative_to := command[3] CoordMode, %target%, %relative_to% return FormatNoValueResponse() + {% endblock AHKSetCoordMode %} } AHKMouseClickDrag(ByRef command) { + {% block AHKMouseClickDrag %} button := command[2] x1 := command[3] y1 := command[4] @@ -1677,32 +1789,42 @@ return FormatNoValueResponse() + {% endblock AHKMouseClickDrag %} } RegRead(ByRef command) { + {% block RegRead %} keyname := command[3] RegRead, output, %keyname%, command[4] return output + {% endblock RegRead %} } SetRegView(ByRef command) { + {% block SetRegView %} view := command[2] SetRegView, %view% + {% endblock SetRegView %} } RegWrite(ByRef command) { + {% block RegWrite %} valuetype := command[2] keyname := command[3] RegWrite, %valuetype%, %keyname%, command[4] + {% endblock RegWrite %} } RegDelete(ByRef command) { + {% block RegDelete %} keyname := command[2] RegDelete, %keyname%, command[3] + {% endblock RegDelete %} } AHKKeyWait(ByRef command) { + {% block AHKKeyWait %} global INTEGERRESPONSEMESSAGE keyname := command[2] if (command.Length() = 2) { @@ -1712,24 +1834,19 @@ KeyWait,% keyname,% options } return FormatResponse(INTEGERRESPONSEMESSAGE, ErrorLevel) + {% endblock AHKKeyWait %} } SetKeyDelay(ByRef command) { + {% block SetKeyDelay %} SetKeyDelay, command[2], command[3] + {% endblock SetKeyDelay %} } -Join(sep, params*) { - for index,param in params - str := param . sep - return SubStr(str, 1, -StrLen(sep)) -} -Unescape(HayStack) { - ReplacedStr := StrReplace(Haystack, "``n" , "`n") - return ReplacedStr -} AHKSend(ByRef command) { + {% block AHKSend %} str := command[2] key_delay := command[3] key_press_duration := command[4] @@ -1746,9 +1863,11 @@ SetKeyDelay, %current_delay%, %current_key_duration% } return FormatNoValueResponse() + {% endblock AHKSend %} } AHKSendRaw(ByRef command) { + {% block AHKSendRaw %} str := command[2] key_delay := command[3] key_press_duration := command[4] @@ -1765,9 +1884,11 @@ SetKeyDelay, %current_delay%, %current_key_duration% } return FormatNoValueResponse() + {% endblock AHKSendRaw %} } AHKSendInput(ByRef command) { + {% block AHKSendInput %} str := command[2] key_delay := command[3] key_press_duration := command[4] @@ -1784,10 +1905,12 @@ SetKeyDelay, %current_delay%, %current_key_duration% } return FormatNoValueResponse() + {% endblock AHKSendInput %} } AHKSendEvent(ByRef command) { + {% block AHKSendEvent %} str := command[2] key_delay := command[3] key_press_duration := command[4] @@ -1804,9 +1927,11 @@ SetKeyDelay, %current_delay%, %current_key_duration% } return FormatNoValueResponse() + {% endblock AHKSendEvent %} } AHKSendPlay(ByRef command) { + {% block AHKSendPlay %} str := command[2] key_delay := command[3] key_press_duration := command[4] @@ -1823,9 +1948,11 @@ SetKeyDelay, %current_delay%, %current_key_duration% } return FormatNoValueResponse() + {% endblock AHKSendPlay %} } AHKSetCapsLockState(ByRef command) { + {% block AHKSetCapsLockState %} state := command[2] if (state = "") { SetCapsLockState % !GetKeyState("CapsLock", "T") @@ -1833,21 +1960,25 @@ SetCapsLockState, %state% } return FormatNoValueResponse() + {% endblock AHKSetCapsLockState %} } HideTrayTip(ByRef command) { + {% 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(ByRef command) { + {% block AHKWinGetClass %} global STRINGRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE title := command[2] @@ -1884,9 +2015,11 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetClass %} } AHKWinActivate(ByRef command) { + {% block AHKWinActivate %} title := command[2] text := command[3] extitle := command[4] @@ -1917,12 +2050,14 @@ SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinActivate %} } AHKWindowList(ByRef command) { + {% block AHKWindowList %} global WINDOWLISTRESPONSEMESSAGE current_detect_hw := Format("{}", A_DetectHiddenWindows) @@ -1959,11 +2094,13 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWindowList %} } AHKControlClick(ByRef command) { + {% block AHKControlClick %} global EXCEPTIONRESPONSEMESSAGE ctrl := command[2] title := command[3] @@ -2004,9 +2141,11 @@ SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKControlClick %} } AHKControlGetText(ByRef command) { + {% block AHKControlGetText %} global STRINGRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE ctrl := command[2] @@ -2044,10 +2183,12 @@ SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKControlGetText %} } AHKControlGetPos(ByRef command) { + {% block AHKControlGetPos %} global POSITIONRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE ctrl := command[2] @@ -2088,9 +2229,11 @@ return response + {% endblock AHKControlGetPos %} } AHKControlSend(ByRef command) { + {% block AHKControlSend %} ctrl := command[2] keys := command[3] title := command[4] @@ -2119,12 +2262,14 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKControlSend %} } AHKWinFromMouse(ByRef command) { + {% block AHKWinFromMouse %} global WINDOWRESPONSEMESSAGE MouseGetPos,,, MouseWin @@ -2133,10 +2278,12 @@ } return FormatResponse(WINDOWRESPONSEMESSAGE, MouseWin) + {% endblock AHKWinFromMouse %} } AHKWinIsAlwaysOnTop(ByRef command) { + {% block AHKWinIsAlwaysOnTop %} global BOOLEANRESPONSEMESSAGE title := command[2] WinGet, ExStyle, ExStyle, %title% @@ -2147,10 +2294,12 @@ return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) else return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + {% endblock AHKWinIsAlwaysOnTop %} } AHKWinMove(ByRef command) { + {% block AHKWinMove %} title := command[2] text := command[3] extitle := command[4] @@ -2185,9 +2334,11 @@ return FormatNoValueResponse() + {% endblock AHKWinMove %} } AHKWinGetPos(ByRef command) { + {% block AHKWinGetPos %} global POSITIONRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE @@ -2227,10 +2378,12 @@ SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetPos %} } AHKGetVolume(ByRef command) { + {% block AHKGetVolume %} global EXCEPTIONRESPONSEMESSAGE global FLOATRESPONSEMESSAGE device_number := command[2] @@ -2247,16 +2400,20 @@ response := FormatResponse(FLOATRESPONSEMESSAGE, Format("{}", retval)) } return response + {% endblock AHKGetVolume %} } AHKSoundBeep(ByRef command) { + {% block AHKSoundBeep %} freq := command[2] duration := command[3] SoundBeep , %freq%, %duration% return FormatNoValueResponse() + {% endblock AHKSoundBeep %} } AHKSoundGet(ByRef command) { + {% block AHKSoundGet %} global STRINGRESPONSEMESSAGE device_number := command[2] component_type := command[3] @@ -2265,28 +2422,35 @@ SoundGet, retval, %component_type%, %control_type%, %device_number% ; TODO interpret return type return FormatResponse(STRINGRESPONSEMESSAGE, Format("{}", retval)) + {% endblock AHKSoundGet %} } AHKSoundSet(ByRef command) { + {% block AHKSoundSet %} device_number := command[2] component_type := command[3] control_type := command[4] value := command[5] SoundSet, %value%, %component_type%, %control_type%, %device_number% return FormatNoValueResponse() + {% endblock AHKSoundSet %} } AHKSoundPlay(ByRef command) { + {% block AHKSoundPlay %} filename := command[2] SoundPlay, %filename% return FormatNoValueResponse() + {% endblock AHKSoundPlay %} } AHKSetVolume(ByRef command) { + {% block AHKSetVolume %} device_number := command[2] value := command[3] SoundSetWaveVolume, %value%, %device_number% return FormatNoValueResponse() + {% endblock AHKSetVolume %} } CountNewlines(ByRef s) { @@ -2297,11 +2461,14 @@ } AHKEcho(ByRef command) { + {% block AHKEcho %} global STRINGRESPONSEMESSAGE return FormatResponse(STRINGRESPONSEMESSAGE, command) + {% endblock AHKEcho %} } AHKTraytip(ByRef command) { + {% block AHKTraytip %} title := command[2] text := command[3] second := command[4] @@ -2309,29 +2476,38 @@ TrayTip, %title%, %text%, %second%, %option% return FormatNoValueResponse() + {% endblock AHKTraytip %} } AHKGetClipboard(ByRef command) { + {% block AHKGetClipboard %} global STRINGRESPONSEMESSAGE return FormatResponse(STRINGRESPONSEMESSAGE, Clipboard) + {% endblock AHKGetClipboard %} } AHKGetClipboardAll(ByRef command) { + {% block AHKGetClipboardAll %} data := ClipboardAll return FormatBinaryResponse(data) + {% endblock AHKGetClipboardAll %} } AHKSetClipboard(ByRef command) { + {% block AHKSetClipboard %} text := command[2] Clipboard := text return FormatNoValueResponse() + {% endblock AHKSetClipboard %} } AHKSetClipboardAll(ByRef command) { + {% block AHKSetClipboardAll %} ; TODO there should be a way for us to accept a base64 string instead filename := command[2] FileRead, Clipboard, %filename% return FormatNoValueResponse() + {% endblock AHKSetClipboardAll %} } b64decode(ByRef pszString) { @@ -2428,6 +2604,11 @@ return decoded_commands } + +{% block before_autoexecute %} +{% endblock before_autoexecute %} + +{% block autoexecute %} stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] pyresp := "" @@ -2436,19 +2617,28 @@ commandArray := CommandArrayFromQuery(query) try { func := commandArray[1] + {% block before_function %} + {% endblock before_function %} pyresp := %func%(commandArray) + {% 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(EXCEPTIONRESPONSEMESSAGE, message) + {% endblock function_error_handle %} } - + {% block send_response %} if (pyresp) { FileAppend, %pyresp%, *, UTF-8 } else { msg := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("Unknown Error when calling {}", func)) FileAppend, %msg%, *, UTF-8 } + {% endblock send_response %} } +{% endblock autoexecute %} +{% endblock daemon_script %} """ diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 85d1770d..73cb0839 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -654,7 +654,7 @@ def mouse_move( resp = self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp - def a_run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: + def a_run_script(self, script_text: str, blocking: bool = True) -> Union[str, FutureResult[str]]: raise NotImplementedError() # fmt: off @@ -954,8 +954,8 @@ def key_wait( resp = self._transport.function_call('AHKKeyWait', args) return resp - def run_script(self, script_text: str, decode: bool = True, blocking: bool = True, **runkwargs: Any) -> str: - raise NotImplementedError() + def run_script(self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None) -> Union[str, FutureResult[str]]: + return self._transport.run_script(script_text_or_path, blocking=blocking, timeout=timeout) def set_send_level(self, level: int) -> None: if not isinstance(level, int): diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index d1175a7b..10c6dc75 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -181,11 +181,30 @@ def async_assert_send_nonblocking_type_correct( return True +class Communicable(Protocol): + runargs: List[str] + + def communicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: + ... + + def acommunicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: + ... + + @property + def returncode(self) -> Optional[int]: + ... + + 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) -> None: self._proc = sync_create_process(self.runargs) atexit.register(kill, self._proc) @@ -214,6 +233,17 @@ def kill(self) -> None: assert self._proc is not None, 'no process to kill' self._proc.kill() + def acommunicate( + self, input_bytes: Optional[bytes] = None, timeout: Optional[int] = None + ) -> Tuple[bytes, bytes]: + assert self._proc is not None + return 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) + @@ -305,6 +335,12 @@ def init(self) -> None: self._started = True return None + @abstractmethod + def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, FutureResult[str]]: + return NotImplemented + # fmt: off @overload def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, FutureResult[bool]]: ... @@ -590,18 +626,29 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A 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, **kwargs) - def _create_process(self) -> SyncAHKProcess: - 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(os.remove, tempscriptfile.name) + 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(os.remove, tempscriptfile.name) + else: + daemon_script = self._temp_script else: - daemon_script = self._temp_script + 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(os.remove, tempscript.name) runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] proc = SyncAHKProcess(runargs=runargs) proc.start() @@ -671,5 +718,45 @@ def send( return response.unpack() # type: ignore + def _sync_run_nonblocking( + self, + proc: Communicable, + script_bytes: Optional[bytes], + timeout: Optional[int] = None, + ) -> FutureResult[str]: + pool = ThreadPoolExecutor(max_workers=1) + + def f() -> str: + stdout, stderr = proc.communicate(script_bytes, 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') + + fut = pool.submit(f) + pool.shutdown(wait=False) + return FutureResult(fut) + + 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) + proc.start() + if blocking: + stdout, stderr = 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 self._sync_run_nonblocking(proc, script_bytes, timeout=timeout) + + if TYPE_CHECKING: from .engine import AHK diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 93047a96..c7293a0b 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1,20 +1,25 @@ +{% block daemon_script %} +{% block directives %} #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 %} - +{% block message_types %} {% for tom, name in message_types.items() %} {{ name }} := "{{ tom }}" {% endfor %} +{% endblock message_types %} NOVALUE_SENTINEL := Chr(57344) @@ -38,12 +43,15 @@ FormatBinaryResponse(ByRef bin) { } AHKSetDetectHiddenWindows(ByRef command) { + {% block AHKSetDetectHiddenWindows %} value := command[2] DetectHiddenWindows, %value% return FormatNoValueResponse() + {% endblock AHKSetDetectHiddenWindows %} } AHKSetTitleMatchMode(ByRef command) { + {% block AHKSetTitleMatchMode %} val1 := command[2] val2 := command[3] if (val1 != "") { @@ -53,30 +61,40 @@ AHKSetTitleMatchMode(ByRef command) { SetTitleMatchMode, %val2% } return FormatNoValueResponse() + {% endblock AHKSetTitleMatchMode %} } AHKGetTitleMatchMode(ByRef command) { + {% block AHKGetTitleMatchMode %} global STRINGRESPONSEMESSAGE return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchMode) + {% endblock AHKGetTitleMatchMode %} } AHKGetTitleMatchSpeed(ByRef command) { + {% block AHKGetTitleMatchSpeed %} global STRINGRESPONSEMESSAGE return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchModeSpeed) + {% endblock AHKGetTitleMatchSpeed %} } AHKSetSendLevel(ByRef command) { + {% block AHKSetSendLevel %} level := command[2] SendLevel, %level% return FormatNoValueResponse() + {% endblock AHKSetSendLevel %} } AHKGetSendLevel(ByRef command) { + {% block AHKGetSendLevel %} global INTEGERRESPONSEMESSAGE return FormatResponse(INTEGERRESPONSEMESSAGE, A_SendLevel) + {% endblock AHKGetSendLevel %} } AHKWinExist(ByRef command) { + {% block AHKWinExist %} global BOOLEANRESPONSEMESSAGE title := command[2] text := command[3] @@ -112,9 +130,11 @@ AHKWinExist(ByRef command) { SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinExist %} } AHKWinClose(ByRef command) { + {% block AHKWinClose %} title := command[2] text := command[3] extitle := command[4] @@ -145,9 +165,11 @@ AHKWinClose(ByRef command) { WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% return FormatNoValueResponse() + {% endblock AHKWinClose %} } AHKWinKill(ByRef command) { + {% block AHKWinKill %} title := command[2] text := command[3] extitle := command[4] @@ -179,9 +201,11 @@ AHKWinKill(ByRef command) { SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinKill %} } AHKWinWait(ByRef command) { + {% block AHKWinWait %} global WINDOWRESPONSEMESSAGE global TIMEOUTRESPONSEMESSAGE @@ -223,10 +247,12 @@ AHKWinWait(ByRef command) { SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinWait %} } AHKWinWaitActive(ByRef command) { + {% block AHKWinWaitActive %} global WINDOWRESPONSEMESSAGE global TIMEOUTRESPONSEMESSAGE @@ -268,10 +294,12 @@ AHKWinWaitActive(ByRef command) { SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinWaitActive %} } AHKWinWaitNotActive(ByRef command) { + {% block AHKWinWaitNotActive %} global WINDOWRESPONSEMESSAGE global TIMEOUTRESPONSEMESSAGE @@ -313,11 +341,13 @@ AHKWinWaitNotActive(ByRef command) { SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinWaitNotActive %} } AHKWinMinimize(ByRef command) { + {% block AHKWinMinimize %} title := command[2] text := command[3] extitle := command[4] @@ -349,9 +379,11 @@ AHKWinMinimize(ByRef command) { SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinMinimize %} } AHKWinMaximize(ByRef command) { + {% block AHKWinMaximize %} title := command[2] text := command[3] extitle := command[4] @@ -382,9 +414,11 @@ AHKWinMaximize(ByRef command) { SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinMaximize %} } AHKWinRestore(ByRef command) { + {% block AHKWinRestore %} title := command[2] text := command[3] extitle := command[4] @@ -416,9 +450,11 @@ AHKWinRestore(ByRef command) { SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinRestore %} } AHKWinIsActive(ByRef command) { + {% block AHKWinIsActive %} global BOOLEANRESPONSEMESSAGE title := command[2] text := command[3] @@ -451,9 +487,11 @@ AHKWinIsActive(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinIsActive %} } AHKWinGetID(ByRef command) { + {% block AHKWinGetID %} global WINDOWRESPONSEMESSAGE title := command[2] text := command[3] @@ -488,9 +526,11 @@ AHKWinGetID(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetID %} } AHKWinGetTitle(ByRef command) { + {% block AHKWinGetTitle %} global STRINGRESPONSEMESSAGE title := command[2] text := command[3] @@ -521,9 +561,11 @@ AHKWinGetTitle(ByRef command) { SetTitleMatchMode, %current_match_speed% return FormatResponse(STRINGRESPONSEMESSAGE, text) + {% endblock AHKWinGetTitle %} } AHKWinGetIDLast(ByRef command) { + {% block AHKWinGetIDLast %} global WINDOWRESPONSEMESSAGE title := command[2] text := command[3] @@ -558,10 +600,12 @@ AHKWinGetIDLast(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetIDLast %} } AHKWinGetPID(ByRef command) { + {% block AHKWinGetPID %} global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -596,10 +640,12 @@ AHKWinGetPID(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetPID %} } AHKWinGetProcessName(ByRef command) { + {% block AHKWinGetProcessName %} global STRINGRESPONSEMESSAGE title := command[2] text := command[3] @@ -611,12 +657,12 @@ AHKWinGetProcessName(ByRef command) { 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 (match_mode != "") { + SetTitleMatchMode, %match_mode% + } + if (match_speed != "") { + SetTitleMatchMode, %match_speed% + } current_detect_hw := Format("{}", A_DetectHiddenWindows) @@ -634,9 +680,11 @@ if (match_speed != "") { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetProcessName %} } AHKWinGetProcessPath(ByRef command) { + {% block AHKWinGetProcessPath %} global STRINGRESPONSEMESSAGE title := command[2] text := command[3] @@ -671,10 +719,12 @@ AHKWinGetProcessPath(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetProcessPath %} } AHKWinGetCount(ByRef command) { + {% block AHKWinGetCount %} global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -709,11 +759,13 @@ AHKWinGetCount(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetCount %} } AHKWinGetMinMax(ByRef command) { + {% block AHKWinGetMinMax %} global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -748,9 +800,11 @@ AHKWinGetMinMax(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetMinMax %} } AHKWinGetControlList(ByRef command) { + {% block AHKWinGetControlList %} global EXCEPTIONRESPONSEMESSAGE global WINDOWCONTROLLISTRESPONSEMESSAGE title := command[2] @@ -809,9 +863,11 @@ AHKWinGetControlList(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetControlList %} } AHKWinGetTransparent(ByRef command) { + {% block AHKWinGetTransparent %} global INTEGERRESPONSEMESSAGE title := command[2] text := command[3] @@ -842,8 +898,10 @@ AHKWinGetTransparent(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetTransparent %} } AHKWinGetTransColor(ByRef command) { + {% block AHKWinGetTransColor %} global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE global NOVALUERESPONSEMESSAGE @@ -876,8 +934,10 @@ AHKWinGetTransColor(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetTransColor %} } AHKWinGetStyle(ByRef command) { + {% block AHKWinGetStyle %} global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE global NOVALUERESPONSEMESSAGE @@ -910,8 +970,10 @@ AHKWinGetStyle(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetStyle %} } AHKWinGetExStyle(ByRef command) { + {% block AHKWinGetExStyle %} global STRINGRESPONSEMESSAGE global INTEGERRESPONSEMESSAGE global NOVALUERESPONSEMESSAGE @@ -944,9 +1006,11 @@ AHKWinGetExStyle(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetExStyle %} } AHKWinGetText(ByRef command) { + {% block AHKWinGetText %} global STRINGRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE title := command[2] @@ -983,11 +1047,13 @@ AHKWinGetText(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetText %} } AHKWinSetTitle(ByRef command) { + {% block AHKWinSetTitle %} new_title := command[2] title := command[3] text := command[4] @@ -1015,9 +1081,11 @@ AHKWinSetTitle(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetTitle %} } AHKWinSetAlwaysOnTop(ByRef command) { + {% block AHKWinSetAlwaysOnTop %} toggle := command[2] title := command[3] text := command[4] @@ -1046,9 +1114,11 @@ AHKWinSetAlwaysOnTop(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetAlwaysOnTop %} } AHKWinSetBottom(ByRef command) { + {% block AHKWinSetBottom %} title := command[2] text := command[3] extitle := command[4] @@ -1077,9 +1147,11 @@ AHKWinSetBottom(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetBottom %} } AHKWinShow(ByRef command) { + {% block AHKWinShow %} title := command[2] text := command[3] extitle := command[4] @@ -1108,9 +1180,11 @@ AHKWinShow(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinShow %} } AHKWinHide(ByRef command) { + {% block AHKWinHide %} title := command[2] text := command[3] extitle := command[4] @@ -1139,10 +1213,12 @@ AHKWinHide(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinHide %} } AHKWinSetTop(ByRef command) { + {% block AHKWinSetTop %} title := command[2] text := command[3] extitle := command[4] @@ -1171,9 +1247,11 @@ AHKWinSetTop(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetTop %} } AHKWinSetEnable(ByRef command) { + {% block AHKWinSetEnable %} title := command[2] text := command[3] extitle := command[4] @@ -1202,9 +1280,11 @@ AHKWinSetEnable(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetEnable %} } AHKWinSetDisable(ByRef command) { + {% block AHKWinSetDisable %} title := command[2] text := command[3] extitle := command[4] @@ -1233,9 +1313,11 @@ AHKWinSetDisable(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetDisable %} } AHKWinSetRedraw(ByRef command) { + {% block AHKWinSetRedraw %} title := command[2] text := command[3] extitle := command[4] @@ -1264,9 +1346,11 @@ AHKWinSetRedraw(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetRedraw %} } AHKWinSetStyle(ByRef command) { + {% block AHKWinSetStyle %} global BOOLEANRESPONSEMESSAGE style := command[2] title := command[3] @@ -1302,9 +1386,11 @@ AHKWinSetStyle(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinSetStyle %} } AHKWinSetExStyle(ByRef command) { + {% block AHKWinSetExStyle %} global BOOLEANRESPONSEMESSAGE style := command[2] title := command[3] @@ -1340,9 +1426,11 @@ AHKWinSetExStyle(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinSetExStyle %} } AHKWinSetRegion(ByRef command) { + {% block AHKWinSetRegion %} global BOOLEANRESPONSEMESSAGE options := command[2] title := command[3] @@ -1378,9 +1466,11 @@ AHKWinSetRegion(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWinSetRegion %} } AHKWinSetTransparent(ByRef command) { + {% block AHKWinSetTransparent %} global BOOLEANRESPONSEMESSAGE transparency := command[2] title := command[3] @@ -1411,9 +1501,11 @@ AHKWinSetTransparent(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinSetTransparent %} } AHKWinSetTransColor(ByRef command) { + {% block AHKWinSetTransColor %} global BOOLEANRESPONSEMESSAGE color := command[2] title := command[3] @@ -1441,9 +1533,11 @@ AHKWinSetTransColor(ByRef command) { WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% return FormatNoValueResponse() + {% endblock AHKWinSetTransColor %} } AHKImageSearch(ByRef command) { + {% block AHKImageSearch %} global COORDINATERESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE imagepath := command[6] @@ -1468,9 +1562,11 @@ AHKImageSearch(ByRef command) { } return s + {% endblock AHKImageSearch %} } AHKPixelGetColor(ByRef command) { + {% block AHKPixelGetColor %} global STRINGRESPONSEMESSAGE x := command[2] y := command[3] @@ -1491,9 +1587,11 @@ AHKPixelGetColor(ByRef command) { } return FormatResponse(STRINGRESPONSEMESSAGE, color) + {% endblock AHKPixelGetColor %} } AHKPixelSearch(ByRef command) { + {% block AHKPixelSearch %} global COORDINATERESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE x1 := command[2] @@ -1528,10 +1626,12 @@ AHKPixelSearch(ByRef command) { return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Unexpected error. This is probably a bug. Please report this at https://github.com/spyoungtech/ahk/issues") } + {% endblock AHKPixelSearch %} } AHKMouseGetPos(ByRef command) { + {% block AHKMouseGetPos %} global COORDINATERESPONSEMESSAGE coord_mode := command[2] current_coord_mode := Format("{}", A_CoordModeMouse) @@ -1548,9 +1648,11 @@ AHKMouseGetPos(ByRef command) { } return resp + {% endblock AHKMouseGetPos %} } AHKKeyState(ByRef command) { + {% block AHKKeyState %} global INTEGERRESPONSEMESSAGE global FLOATRESPONSEMESSAGE global STRINGRESPONSEMESSAGE @@ -1578,9 +1680,11 @@ AHKKeyState(ByRef command) { return FormatResponse(STRINGRESPONSEMESSAGE, state) return FormatResponse(EXCEPTIONRESPONSEMESSAGE, state) + {% endblock AHKKeyState %} } AHKMouseMove(ByRef command) { + {% block AHKMouseMove %} x := command[2] y := command[3] speed := command[4] @@ -1592,10 +1696,12 @@ AHKMouseMove(ByRef command) { } resp := FormatNoValueResponse() return resp + {% endblock AHKMouseMove %} } AHKClick(ByRef command) { + {% block AHKClick %} x := command[2] y := command[3] button := command[4] @@ -1617,9 +1723,11 @@ AHKClick(ByRef command) { return FormatNoValueResponse() + {% endblock AHKClick %} } AHKGetCoordMode(ByRef command) { + {% block AHKGetCoordMode %} global STRINGRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE target := command[2] @@ -1640,17 +1748,21 @@ AHKGetCoordMode(ByRef command) { return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeMenu) } return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Invalid coord mode") + {% endblock AHKGetCoordMode %} } AHKSetCoordMode(ByRef command) { + {% block AHKSetCoordMode %} target := command[2] relative_to := command[3] CoordMode, %target%, %relative_to% return FormatNoValueResponse() + {% endblock AHKSetCoordMode %} } AHKMouseClickDrag(ByRef command) { + {% block AHKMouseClickDrag %} button := command[2] x1 := command[3] y1 := command[4] @@ -1674,32 +1786,42 @@ AHKMouseClickDrag(ByRef command) { return FormatNoValueResponse() + {% endblock AHKMouseClickDrag %} } RegRead(ByRef command) { + {% block RegRead %} keyname := command[3] RegRead, output, %keyname%, command[4] return output + {% endblock RegRead %} } SetRegView(ByRef command) { + {% block SetRegView %} view := command[2] SetRegView, %view% + {% endblock SetRegView %} } RegWrite(ByRef command) { + {% block RegWrite %} valuetype := command[2] keyname := command[3] RegWrite, %valuetype%, %keyname%, command[4] + {% endblock RegWrite %} } RegDelete(ByRef command) { + {% block RegDelete %} keyname := command[2] RegDelete, %keyname%, command[3] + {% endblock RegDelete %} } AHKKeyWait(ByRef command) { + {% block AHKKeyWait %} global INTEGERRESPONSEMESSAGE keyname := command[2] if (command.Length() = 2) { @@ -1709,24 +1831,19 @@ AHKKeyWait(ByRef command) { KeyWait,% keyname,% options } return FormatResponse(INTEGERRESPONSEMESSAGE, ErrorLevel) + {% endblock AHKKeyWait %} } SetKeyDelay(ByRef command) { + {% block SetKeyDelay %} SetKeyDelay, command[2], command[3] + {% endblock SetKeyDelay %} } -Join(sep, params*) { - for index,param in params - str := param . sep - return SubStr(str, 1, -StrLen(sep)) -} -Unescape(HayStack) { - ReplacedStr := StrReplace(Haystack, "``n" , "`n") - return ReplacedStr -} AHKSend(ByRef command) { + {% block AHKSend %} str := command[2] key_delay := command[3] key_press_duration := command[4] @@ -1743,9 +1860,11 @@ AHKSend(ByRef command) { SetKeyDelay, %current_delay%, %current_key_duration% } return FormatNoValueResponse() + {% endblock AHKSend %} } AHKSendRaw(ByRef command) { + {% block AHKSendRaw %} str := command[2] key_delay := command[3] key_press_duration := command[4] @@ -1762,9 +1881,11 @@ AHKSendRaw(ByRef command) { SetKeyDelay, %current_delay%, %current_key_duration% } return FormatNoValueResponse() + {% endblock AHKSendRaw %} } AHKSendInput(ByRef command) { + {% block AHKSendInput %} str := command[2] key_delay := command[3] key_press_duration := command[4] @@ -1781,10 +1902,12 @@ AHKSendInput(ByRef command) { SetKeyDelay, %current_delay%, %current_key_duration% } return FormatNoValueResponse() + {% endblock AHKSendInput %} } AHKSendEvent(ByRef command) { + {% block AHKSendEvent %} str := command[2] key_delay := command[3] key_press_duration := command[4] @@ -1801,9 +1924,11 @@ AHKSendEvent(ByRef command) { SetKeyDelay, %current_delay%, %current_key_duration% } return FormatNoValueResponse() + {% endblock AHKSendEvent %} } AHKSendPlay(ByRef command) { + {% block AHKSendPlay %} str := command[2] key_delay := command[3] key_press_duration := command[4] @@ -1820,9 +1945,11 @@ AHKSendPlay(ByRef command) { SetKeyDelay, %current_delay%, %current_key_duration% } return FormatNoValueResponse() + {% endblock AHKSendPlay %} } AHKSetCapsLockState(ByRef command) { + {% block AHKSetCapsLockState %} state := command[2] if (state = "") { SetCapsLockState % !GetKeyState("CapsLock", "T") @@ -1830,21 +1957,25 @@ AHKSetCapsLockState(ByRef command) { SetCapsLockState, %state% } return FormatNoValueResponse() + {% endblock AHKSetCapsLockState %} } HideTrayTip(ByRef command) { + {% 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(ByRef command) { + {% block AHKWinGetClass %} global STRINGRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE title := command[2] @@ -1881,9 +2012,11 @@ AHKWinGetClass(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetClass %} } AHKWinActivate(ByRef command) { + {% block AHKWinActivate %} title := command[2] text := command[3] extitle := command[4] @@ -1914,12 +2047,14 @@ AHKWinActivate(ByRef command) { SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKWinActivate %} } AHKWindowList(ByRef command) { + {% block AHKWindowList %} global WINDOWLISTRESPONSEMESSAGE current_detect_hw := Format("{}", A_DetectHiddenWindows) @@ -1956,11 +2091,13 @@ AHKWindowList(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return resp + {% endblock AHKWindowList %} } AHKControlClick(ByRef command) { + {% block AHKControlClick %} global EXCEPTIONRESPONSEMESSAGE ctrl := command[2] title := command[3] @@ -2001,9 +2138,11 @@ AHKControlClick(ByRef command) { SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKControlClick %} } AHKControlGetText(ByRef command) { + {% block AHKControlGetText %} global STRINGRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE ctrl := command[2] @@ -2041,10 +2180,12 @@ AHKControlGetText(ByRef command) { SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKControlGetText %} } AHKControlGetPos(ByRef command) { + {% block AHKControlGetPos %} global POSITIONRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE ctrl := command[2] @@ -2085,9 +2226,11 @@ AHKControlGetPos(ByRef command) { return response + {% endblock AHKControlGetPos %} } AHKControlSend(ByRef command) { + {% block AHKControlSend %} ctrl := command[2] keys := command[3] title := command[4] @@ -2116,12 +2259,14 @@ AHKControlSend(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% return FormatNoValueResponse() + {% endblock AHKControlSend %} } AHKWinFromMouse(ByRef command) { + {% block AHKWinFromMouse %} global WINDOWRESPONSEMESSAGE MouseGetPos,,, MouseWin @@ -2130,10 +2275,12 @@ AHKWinFromMouse(ByRef command) { } return FormatResponse(WINDOWRESPONSEMESSAGE, MouseWin) + {% endblock AHKWinFromMouse %} } AHKWinIsAlwaysOnTop(ByRef command) { + {% block AHKWinIsAlwaysOnTop %} global BOOLEANRESPONSEMESSAGE title := command[2] WinGet, ExStyle, ExStyle, %title% @@ -2144,10 +2291,12 @@ AHKWinIsAlwaysOnTop(ByRef command) { return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) else return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + {% endblock AHKWinIsAlwaysOnTop %} } AHKWinMove(ByRef command) { + {% block AHKWinMove %} title := command[2] text := command[3] extitle := command[4] @@ -2182,9 +2331,11 @@ AHKWinMove(ByRef command) { return FormatNoValueResponse() + {% endblock AHKWinMove %} } AHKWinGetPos(ByRef command) { + {% block AHKWinGetPos %} global POSITIONRESPONSEMESSAGE global EXCEPTIONRESPONSEMESSAGE @@ -2224,10 +2375,12 @@ AHKWinGetPos(ByRef command) { SetTitleMatchMode, %current_match_speed% return response + {% endblock AHKWinGetPos %} } AHKGetVolume(ByRef command) { + {% block AHKGetVolume %} global EXCEPTIONRESPONSEMESSAGE global FLOATRESPONSEMESSAGE device_number := command[2] @@ -2244,16 +2397,20 @@ AHKGetVolume(ByRef command) { response := FormatResponse(FLOATRESPONSEMESSAGE, Format("{}", retval)) } return response + {% endblock AHKGetVolume %} } AHKSoundBeep(ByRef command) { + {% block AHKSoundBeep %} freq := command[2] duration := command[3] SoundBeep , %freq%, %duration% return FormatNoValueResponse() + {% endblock AHKSoundBeep %} } AHKSoundGet(ByRef command) { + {% block AHKSoundGet %} global STRINGRESPONSEMESSAGE device_number := command[2] component_type := command[3] @@ -2262,28 +2419,35 @@ AHKSoundGet(ByRef command) { SoundGet, retval, %component_type%, %control_type%, %device_number% ; TODO interpret return type return FormatResponse(STRINGRESPONSEMESSAGE, Format("{}", retval)) + {% endblock AHKSoundGet %} } AHKSoundSet(ByRef command) { + {% block AHKSoundSet %} device_number := command[2] component_type := command[3] control_type := command[4] value := command[5] SoundSet, %value%, %component_type%, %control_type%, %device_number% return FormatNoValueResponse() + {% endblock AHKSoundSet %} } AHKSoundPlay(ByRef command) { + {% block AHKSoundPlay %} filename := command[2] SoundPlay, %filename% return FormatNoValueResponse() + {% endblock AHKSoundPlay %} } AHKSetVolume(ByRef command) { + {% block AHKSetVolume %} device_number := command[2] value := command[3] SoundSetWaveVolume, %value%, %device_number% return FormatNoValueResponse() + {% endblock AHKSetVolume %} } CountNewlines(ByRef s) { @@ -2294,11 +2458,14 @@ CountNewlines(ByRef s) { } AHKEcho(ByRef command) { + {% block AHKEcho %} global STRINGRESPONSEMESSAGE return FormatResponse(STRINGRESPONSEMESSAGE, command) + {% endblock AHKEcho %} } AHKTraytip(ByRef command) { + {% block AHKTraytip %} title := command[2] text := command[3] second := command[4] @@ -2306,29 +2473,38 @@ AHKTraytip(ByRef command) { TrayTip, %title%, %text%, %second%, %option% return FormatNoValueResponse() + {% endblock AHKTraytip %} } AHKGetClipboard(ByRef command) { + {% block AHKGetClipboard %} global STRINGRESPONSEMESSAGE return FormatResponse(STRINGRESPONSEMESSAGE, Clipboard) + {% endblock AHKGetClipboard %} } AHKGetClipboardAll(ByRef command) { + {% block AHKGetClipboardAll %} data := ClipboardAll return FormatBinaryResponse(data) + {% endblock AHKGetClipboardAll %} } AHKSetClipboard(ByRef command) { + {% block AHKSetClipboard %} text := command[2] Clipboard := text return FormatNoValueResponse() + {% endblock AHKSetClipboard %} } AHKSetClipboardAll(ByRef command) { + {% block AHKSetClipboardAll %} ; TODO there should be a way for us to accept a base64 string instead filename := command[2] FileRead, Clipboard, %filename% return FormatNoValueResponse() + {% endblock AHKSetClipboardAll %} } b64decode(ByRef pszString) { @@ -2425,6 +2601,11 @@ CommandArrayFromQuery(ByRef text) { return decoded_commands } + +{% block before_autoexecute %} +{% endblock before_autoexecute %} + +{% block autoexecute %} stdin := FileOpen("*", "r `n", "UTF-8") ; Requires [v1.1.17+] pyresp := "" @@ -2433,16 +2614,25 @@ Loop { commandArray := CommandArrayFromQuery(query) try { func := commandArray[1] + {% block before_function %} + {% endblock before_function %} pyresp := %func%(commandArray) + {% 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(EXCEPTIONRESPONSEMESSAGE, message) + {% endblock function_error_handle %} } - + {% block send_response %} if (pyresp) { FileAppend, %pyresp%, *, UTF-8 } else { msg := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("Unknown Error when calling {}", func)) FileAppend, %msg%, *, UTF-8 } + {% endblock send_response %} } +{% endblock autoexecute %} +{% endblock daemon_script %} diff --git a/buildunasync.py b/buildunasync.py index 77a6c9c6..15a2bba2 100644 --- a/buildunasync.py +++ b/buildunasync.py @@ -17,6 +17,7 @@ 'a_send_nonblocking': 'send_nonblocking', 'async_sleep': 'sleep', 'AsyncFutureResult': 'FutureResult', + '_async_run_nonblocking': '_sync_run_nonblocking' # "__aenter__": "__aenter__", }, ), diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index 9ca18644..6f835f3a 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -23,7 +23,8 @@ async def asyncSetUp(self) -> None: async def asyncTearDown(self) -> None: self.ahk.stop_hotkeys() self.ahk._transport._proc.kill() - subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey.exe']) + subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey.exe'], capture_output=True) + time.sleep(0.2) async def test_hotkey(self): diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 56239d49..709f8f7d 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -12,7 +12,6 @@ class TestScreen(IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: - print('setting up') self.ahk = AsyncAHK() self.before_windows = await self.ahk.list_windows() self.im = Image.new('RGB', (20, 20)) @@ -21,12 +20,9 @@ async def asyncSetUp(self) -> None: time.sleep(1) async def asyncTearDown(self): - print('tearing down') for win in await self.ahk.list_windows(): if win not in self.before_windows: - print('closing', win) await win.kill() - print('killing proc') self.ahk._transport._proc.kill() time.sleep(0.2) diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index d30e299f..a6b71786 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -1,4 +1,6 @@ import pathlib +import subprocess +import tempfile import time import unittest.mock @@ -17,6 +19,7 @@ async def asyncTearDown(self) -> None: 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): @@ -27,3 +30,34 @@ async def test_script_missing_makes_tempfile(self): 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 = 'Run Notepad' + await self.ahk.run_script(script) + notepad = await self.ahk.win_get(title='Untitled - Notepad') + assert notepad is not None + + 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('Run Notepad') + await self.ahk.run_script(f.name) + notepad = await self.ahk.win_get(title='Untitled - Notepad') + assert notepad is not None + + async def test_run_script_file_unicode(self): + assert await self.ahk.win_get(title='Untitled - Notepad') is None + subprocess.Popen('Notepad') + 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_get(title='*Untitled - Notepad') + 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' diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index befbb2bc..ade7374e 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -29,6 +29,7 @@ async def asyncTearDown(self) -> None: 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): @@ -112,9 +113,9 @@ async def test_win_set_title(self): assert await self.win.get_title() == 'Foo' async def test_control_send_window(self): - await self.win.send('Hello World') + await self.win.send('hello world') text = await self.win.get_text() - assert 'Hello World' in text + assert 'hello world' in text async def test_send_literal_comma(self): await self.win.send('hello, world') diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py index e49279e2..b53f7ae7 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -20,7 +20,8 @@ def setUp(self) -> None: def tearDown(self) -> None: self.ahk.stop_hotkeys() self.ahk._transport._proc.kill() - subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey.exe']) + subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey.exe'], capture_output=True) + time.sleep(0.2) def test_hotkey(self): diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index 3d911be9..10d6c18e 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -12,7 +12,6 @@ class TestScreen(TestCase): def setUp(self) -> None: - print('setting up') self.ahk = AHK() self.before_windows = self.ahk.list_windows() self.im = Image.new('RGB', (20, 20)) @@ -21,12 +20,9 @@ def setUp(self) -> None: time.sleep(1) def tearDown(self): - print('tearing down') for win in self.ahk.list_windows(): if win not in self.before_windows: - print('closing', win) win.kill() - print('killing proc') self.ahk._transport._proc.kill() time.sleep(0.2) diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py index 9b80adba..8068008e 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -1,4 +1,6 @@ import pathlib +import subprocess +import tempfile import time import unittest.mock @@ -17,6 +19,7 @@ def tearDown(self) -> None: 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): @@ -27,3 +30,34 @@ def test_script_missing_makes_tempfile(self): 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 = 'Run Notepad' + self.ahk.run_script(script) + notepad = self.ahk.win_get(title='Untitled - Notepad') + assert notepad is not None + + 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('Run Notepad') + self.ahk.run_script(f.name) + notepad = self.ahk.win_get(title='Untitled - Notepad') + assert notepad is not None + + def test_run_script_file_unicode(self): + assert self.ahk.win_get(title='Untitled - Notepad') is None + subprocess.Popen('Notepad') + 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_get(title='*Untitled - Notepad') + 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' diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index bbb3f024..bdb5b0dc 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -29,6 +29,7 @@ def tearDown(self) -> None: 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): @@ -112,9 +113,9 @@ def test_win_set_title(self): assert self.win.get_title() == 'Foo' def test_control_send_window(self): - self.win.send('Hello World') + self.win.send('hello world') text = self.win.get_text() - assert 'Hello World' in text + assert 'hello world' in text def test_send_literal_comma(self): self.win.send('hello, world') From df43a94222422610aa11f8c12abef4b559714050 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 4 Feb 2023 02:28:30 -0800 Subject: [PATCH 337/588] block_input ; try to make tests reliable --- .pre-commit-config.yaml | 4 ++-- ahk/_async/engine.py | 11 +++++++++-- ahk/_async/transport.py | 3 +++ ahk/_constants.py | 6 ++++++ ahk/_sync/engine.py | 13 ++++++++++--- ahk/_sync/transport.py | 3 +++ ahk/templates/daemon.ahk | 6 ++++++ tests/_async/test_hotkeys.py | 1 - tests/_async/test_scripts.py | 4 ++++ tests/_sync/test_hotkeys.py | 1 - tests/_sync/test_scripts.py | 4 ++++ 11 files changed, 47 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f56449b2..d9a4d051 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: files: ^(ahk/daemon\.ahk|ahk/_constants\.py) - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: mixed-line-ending args: ["-f", "lf"] @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '22.10.0' + rev: '23.1.0' hooks: - id: black args: diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 69d88228..c4a5c07d 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2850,8 +2850,8 @@ async def set_clipboard(self, s: str, blocking: bool = True) -> Union[None, Asyn args = [s] return await self._transport.function_call('AHKSetClipboard', args, blocking=blocking) - async def get_clipboard_all(self) -> Union[bytes, AsyncFutureResult[bytes]]: - return await self._transport.function_call('AHKGetClipboardAll') + async def get_clipboard_all(self, blocking: bool = True) -> Union[bytes, AsyncFutureResult[bytes]]: + return await self._transport.function_call('AHKGetClipboardAll', blocking=blocking) async def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: # TODO: figure out how to do this without a tempfile @@ -2872,6 +2872,13 @@ async def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Uni except Exception: pass + async def block_input( + self, + value: Literal['On', 'Off', 'Default', 'Send', 'Mouse', 'MouseMove', 'MouseMoveOff', 'SendAndMouse'], + /, # flake8: noqa + ) -> None: + await self._transport.function_call('AHKBlockInput', args=[value]) + async def block_forever(self) -> NoReturn: while True: await async_sleep(1) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 9f81d91d..e4eb78db 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -78,6 +78,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ + 'AHKBlockInput', 'AHKControlClick', 'AHKControlGetPos', 'AHKControlGetText', @@ -534,6 +535,8 @@ async def function_call(self, function_name: Literal['AHKGetClipboardAll'], args 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['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/_constants.py b/ahk/_constants.py index de1ec89b..f9b85ca3 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2510,6 +2510,12 @@ {% endblock AHKSetClipboardAll %} } +AHKBlockInput(ByRef command) { + value := command[2] + BlockInput, %value% + return FormatNoValueResponse() +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 73cb0839..a136da63 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -954,7 +954,9 @@ def key_wait( resp = self._transport.function_call('AHKKeyWait', args) return resp - def run_script(self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None) -> Union[str, FutureResult[str]]: + def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, FutureResult[str]]: return self._transport.run_script(script_text_or_path, blocking=blocking, timeout=timeout) def set_send_level(self, level: int) -> None: @@ -2837,8 +2839,8 @@ def set_clipboard(self, s: str, blocking: bool = True) -> Union[None, FutureResu args = [s] return self._transport.function_call('AHKSetClipboard', args, blocking=blocking) - def get_clipboard_all(self) -> Union[bytes, FutureResult[bytes]]: - return self._transport.function_call('AHKGetClipboardAll') + def get_clipboard_all(self, blocking: bool = True) -> Union[bytes, FutureResult[bytes]]: + return self._transport.function_call('AHKGetClipboardAll', blocking=blocking) def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Union[None, FutureResult[None]]: # TODO: figure out how to do this without a tempfile @@ -2859,6 +2861,11 @@ def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Union[Non except Exception: pass + def block_input( + self, value: Literal['On', 'Off', 'Default', 'Send', 'Mouse', 'MouseMove', 'MouseMoveOff', 'SendAndMouse'], / # flake8: noqa + ) -> None: + self._transport.function_call('AHKBlockInput', args=[value]) + def block_forever(self) -> NoReturn: while True: sleep(1) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 10c6dc75..41f03820 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -70,6 +70,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ + 'AHKBlockInput', 'AHKControlClick', 'AHKControlGetPos', 'AHKControlGetText', @@ -515,6 +516,8 @@ def function_call(self, function_name: Literal['AHKGetClipboardAll'], args: Opti 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 # async def function_call(self, function_name: Literal['HideTrayTip'], args: Optional[List[str]] = None) -> None: ... diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index c7293a0b..0d144152 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2507,6 +2507,12 @@ AHKSetClipboardAll(ByRef command) { {% endblock AHKSetClipboardAll %} } +AHKBlockInput(ByRef command) { + value := command[2] + BlockInput, %value% + return FormatNoValueResponse() +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index 6f835f3a..6e2b618a 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -27,7 +27,6 @@ async def asyncTearDown(self) -> None: 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() diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index a6b71786..8e39fb2c 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -35,6 +35,7 @@ async def test_run_script_text(self): assert await self.ahk.win_get(title='Untitled - Notepad') is None script = 'Run Notepad' await self.ahk.run_script(script) + time.sleep(0.3) notepad = await self.ahk.win_get(title='Untitled - Notepad') assert notepad is not None @@ -42,6 +43,7 @@ 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('Run Notepad') + time.sleep(0.3) await self.ahk.run_script(f.name) notepad = await self.ahk.win_get(title='Untitled - Notepad') assert notepad is not None @@ -49,9 +51,11 @@ async def test_run_script_file(self): async def test_run_script_file_unicode(self): assert await self.ahk.win_get(title='Untitled - Notepad') is None subprocess.Popen('Notepad') + time.sleep(0.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) + time.sleep(0.3) notepad = await self.ahk.win_get(title='*Untitled - Notepad') assert notepad is not None text = await notepad.get_text() diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py index b53f7ae7..d0cece8f 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -24,7 +24,6 @@ def tearDown(self) -> None: 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() diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py index 8068008e..7f273858 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -35,6 +35,7 @@ def test_run_script_text(self): assert self.ahk.win_get(title='Untitled - Notepad') is None script = 'Run Notepad' self.ahk.run_script(script) + time.sleep(0.3) notepad = self.ahk.win_get(title='Untitled - Notepad') assert notepad is not None @@ -42,6 +43,7 @@ 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('Run Notepad') + time.sleep(0.3) self.ahk.run_script(f.name) notepad = self.ahk.win_get(title='Untitled - Notepad') assert notepad is not None @@ -49,9 +51,11 @@ def test_run_script_file(self): def test_run_script_file_unicode(self): assert self.ahk.win_get(title='Untitled - Notepad') is None subprocess.Popen('Notepad') + time.sleep(0.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) + time.sleep(0.3) notepad = self.ahk.win_get(title='*Untitled - Notepad') assert notepad is not None text = notepad.get_text() From 07b37b71efe0d09c953600621ada19ea1480cf6b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 4 Feb 2023 02:36:25 -0800 Subject: [PATCH 338/588] move wait to right place --- tests/_async/test_scripts.py | 2 +- tests/_sync/test_scripts.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index 8e39fb2c..ef718089 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -43,8 +43,8 @@ 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('Run Notepad') - time.sleep(0.3) await self.ahk.run_script(f.name) + time.sleep(0.3) notepad = await self.ahk.win_get(title='Untitled - Notepad') assert notepad is not None diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py index 7f273858..4b1e4a2e 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -43,8 +43,8 @@ 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('Run Notepad') - time.sleep(0.3) self.ahk.run_script(f.name) + time.sleep(0.3) notepad = self.ahk.win_get(title='Untitled - Notepad') assert notepad is not None From 7f6f46c05d419913328e74734b2740a9d654b536 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 4 Feb 2023 04:18:26 -0800 Subject: [PATCH 339/588] on_clipboard_change --- ahk/_async/engine.py | 5 +++ ahk/_async/transport.py | 7 ++++ ahk/_constants.py | 17 ++++++++- ahk/_hotkey.py | 66 ++++++++++++++++++++++++++++------ ahk/_sync/engine.py | 7 +++- ahk/_sync/transport.py | 5 +++ ahk/templates/hotkeys.ahk | 15 ++++++++ tests/_async/test_clipboard.py | 18 ++++++++-- tests/_async/test_keys.py | 18 ++++++++-- tests/_async/test_scripts.py | 1 + tests/_sync/test_clipboard.py | 17 +++++++-- tests/_sync/test_keys.py | 17 +++++++-- tests/_sync/test_scripts.py | 1 + 13 files changed, 173 insertions(+), 21 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index c4a5c07d..4abf5df7 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2872,6 +2872,11 @@ async def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Uni except Exception: pass + def on_clipboard_change( + self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None + ) -> None: + self._transport.on_clipboard_change(callback, ex_handler) + async def block_input( self, value: Literal['On', 'Off', 'Default', 'Send', 'Mouse', 'MouseMove', 'MouseMoveOff', 'SendAndMouse'], diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index e4eb78db..3d0b2ad6 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -12,6 +12,7 @@ from io import BytesIO from shutil import which from typing import Any +from typing import Callable from typing import Generic from typing import List from typing import Literal @@ -329,6 +330,12 @@ def __init__( self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) self._directives: list[Union[Directive, Type[Directive]]] = directives or [] + def on_clipboard_change( + self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None + ) -> None: + self._hotkey_transport.on_clipboard_change(callback, ex_handler) + return None + def add_hotkey(self, hotkey: Hotkey) -> None: with warnings.catch_warnings(record=True) as caught_warnings: self._hotkey_transport.add_hotkey(hotkey=hotkey) diff --git a/ahk/_constants.py b/ahk/_constants.py index f9b85ca3..22908051 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2648,7 +2648,11 @@ """ -HOTKEYS_SCRIPT_TEMPLATE = r"""KEEPALIVE := Chr(57344) +HOTKEYS_SCRIPT_TEMPLATE = r"""#Persistent +{% if on_clipboard %} +OnClipboardChange("ClipChanged") +{% endif %} +KEEPALIVE := Chr(57344) SetTimer, keepalive, 1000 @@ -2720,6 +2724,17 @@ {% 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 diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index d25cc65d..c09650c2 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -42,9 +42,17 @@ _KEEPALIVE_SENTINEL = b'\xee\x80\x80' +_CLIPBOARD_SENTINEL = '\ue001' -def _default_ex_handler(hotkey: str, ex: Exception) -> None: - logging.error(f'Failure in hotkey {hotkey!r}', exc_info=True) + +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): @@ -55,6 +63,8 @@ def __init__(self, executable_path: str, default_ex_handler: Optional[Callable[[ 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 @property def _callback_registry(self) -> Dict[str, Union[Hotkey, Hotstring]]: @@ -92,6 +102,15 @@ def add_hotstring(self, hotstring: Hotstring) -> None: # TODO: add support for adding IfWinActive/IfWinExist 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: ... @@ -116,14 +135,17 @@ def __init__(self, executable_path: str, default_ex_handler: Optional[Callable[[ self._template = self._jinja_env.from_string(_HOTKEY_SCRIPT) def _do_callback( - self, hotkey: str, cb: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None + 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, cb_exc) + ex_handler(hotkey_or_clip_change_type, cb_exc) # type: ignore[arg-type] return None def start(self) -> None: @@ -168,21 +190,37 @@ def restart(self) -> None: 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 not in self._callback_registry: + 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 - - hot_thing: Union[Hotstring, Hotkey] = self._hotkeys[job] - cb = hot_thing.callback + 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 - ex_handler = hot_thing.ex_handler 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) @@ -190,7 +228,13 @@ def dispatcher(self) -> None: self._callback_queue.task_done() # maybe _do_callback should handle this? def _render_hotkey_tempate(self) -> str: - ret = self._template.render(hotkeys=list(self._hotkeys.values()), hotstrings=self._hotstrings.values()) + 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 + ) return ret def listener(self) -> None: diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index a136da63..87e1a902 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2861,8 +2861,13 @@ def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Union[Non except Exception: pass + def on_clipboard_change(self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None) -> None: + self._transport.on_clipboard_change(callback, ex_handler) + def block_input( - self, value: Literal['On', 'Off', 'Default', 'Send', 'Mouse', 'MouseMove', 'MouseMoveOff', 'SendAndMouse'], / # flake8: noqa + self, + value: Literal['On', 'Off', 'Default', 'Send', 'Mouse', 'MouseMove', 'MouseMoveOff', 'SendAndMouse'], + /, # flake8: noqa ) -> None: self._transport.function_call('AHKBlockInput', args=[value]) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 41f03820..8b6eebb4 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -12,6 +12,7 @@ from io import BytesIO from shutil import which from typing import Any +from typing import Callable from typing import Generic from typing import List from typing import Literal @@ -310,6 +311,10 @@ def __init__( self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) self._directives: list[Union[Directive, Type[Directive]]] = directives or [] + def on_clipboard_change(self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None) -> None: + self._hotkey_transport.on_clipboard_change(callback, ex_handler) + return None + def add_hotkey(self, hotkey: Hotkey) -> None: with warnings.catch_warnings(record=True) as caught_warnings: self._hotkey_transport.add_hotkey(hotkey=hotkey) diff --git a/ahk/templates/hotkeys.ahk b/ahk/templates/hotkeys.ahk index 816ab4d6..af9e471d 100644 --- a/ahk/templates/hotkeys.ahk +++ b/ahk/templates/hotkeys.ahk @@ -1,3 +1,7 @@ +#Persistent +{% if on_clipboard %} +OnClipboardChange("ClipChanged") +{% endif %} KEEPALIVE := Chr(57344) SetTimer, keepalive, 1000 @@ -70,6 +74,17 @@ b64decode(ByRef pszString) { {% 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 diff --git a/tests/_async/test_clipboard.py b/tests/_async/test_clipboard.py index 602b1ee4..e3d526a3 100644 --- a/tests/_async/test_clipboard.py +++ b/tests/_async/test_clipboard.py @@ -1,10 +1,15 @@ +import asyncio import time -from unittest import IsolatedAsyncioTestCase +import unittest.mock from ahk import AsyncAHK +async_sleep = asyncio.sleep # unasync: remove -class TestWindowAsync(IsolatedAsyncioTestCase): +sleep = time.sleep + + +class TestWindowAsync(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: self.ahk = AsyncAHK() @@ -25,3 +30,12 @@ async def test_clipboard_all(self): 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() diff --git a/tests/_async/test_keys.py b/tests/_async/test_keys.py index 51f9abd9..87650e02 100644 --- a/tests/_async/test_keys.py +++ b/tests/_async/test_keys.py @@ -3,13 +3,17 @@ import subprocess import sys import time -from unittest import IsolatedAsyncioTestCase +import unittest.mock from ahk import AsyncAHK from ahk import AsyncWindow +async_sleep = asyncio.sleep # unasync: remove -class TestWindowAsync(IsolatedAsyncioTestCase): +sleep = time.sleep + + +class TestWindowAsync(unittest.IsolatedAsyncioTestCase): win: AsyncWindow async def asyncSetUp(self) -> None: @@ -43,3 +47,13 @@ async def test_hotstring(self): time.sleep(2) assert 'by the way' 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() diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index ef718089..f7d1b96e 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -19,6 +19,7 @@ async def asyncTearDown(self) -> None: self.ahk._transport._proc.kill() except: pass + subprocess.run([]) subprocess.run(['TASKKILL', '/F', '/IM', 'notepad.exe'], capture_output=True) time.sleep(0.2) diff --git a/tests/_sync/test_clipboard.py b/tests/_sync/test_clipboard.py index 4abb865b..46156e1f 100644 --- a/tests/_sync/test_clipboard.py +++ b/tests/_sync/test_clipboard.py @@ -1,10 +1,14 @@ +import asyncio import time -from unittest import TestCase +import unittest.mock from ahk import AHK -class TestWindowAsync(TestCase): +sleep = time.sleep + + +class TestWindowAsync(unittest.TestCase): def setUp(self) -> None: self.ahk = AHK() @@ -25,3 +29,12 @@ def test_clipboard_all(self): 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() diff --git a/tests/_sync/test_keys.py b/tests/_sync/test_keys.py index d3348cba..b4be92af 100644 --- a/tests/_sync/test_keys.py +++ b/tests/_sync/test_keys.py @@ -3,13 +3,16 @@ import subprocess import sys import time -from unittest import TestCase +import unittest.mock from ahk import AHK from ahk import Window -class TestWindowAsync(TestCase): +sleep = time.sleep + + +class TestWindowAsync(unittest.TestCase): win: Window def setUp(self) -> None: @@ -43,3 +46,13 @@ def test_hotstring(self): time.sleep(2) assert 'by the way' 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() diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py index 4b1e4a2e..b1c5f08f 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -19,6 +19,7 @@ def tearDown(self) -> None: self.ahk._transport._proc.kill() except: pass + subprocess.run([]) subprocess.run(['TASKKILL', '/F', '/IM', 'notepad.exe'], capture_output=True) time.sleep(0.2) From 2f63cb650b1f0e71cd8f8bfd4ada49d4b4cdb839 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 4 Feb 2023 15:47:40 -0800 Subject: [PATCH 340/588] fix typo --- tests/_async/test_scripts.py | 1 - tests/_sync/test_scripts.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index f7d1b96e..ef718089 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -19,7 +19,6 @@ async def asyncTearDown(self) -> None: self.ahk._transport._proc.kill() except: pass - subprocess.run([]) subprocess.run(['TASKKILL', '/F', '/IM', 'notepad.exe'], capture_output=True) time.sleep(0.2) diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py index b1c5f08f..4b1e4a2e 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -19,7 +19,6 @@ def tearDown(self) -> None: self.ahk._transport._proc.kill() except: pass - subprocess.run([]) subprocess.run(['TASKKILL', '/F', '/IM', 'notepad.exe'], capture_output=True) time.sleep(0.2) From ec480cc5e04504fc6a6c2437e2bb9e47f41fdac7 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 4 Feb 2023 16:02:07 -0800 Subject: [PATCH 341/588] try to make tests more reliable --- tests/_async/test_scripts.py | 21 ++++++++------------- tests/_sync/test_scripts.py | 21 ++++++++------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index ef718089..cbb1d223 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -33,30 +33,25 @@ async def test_script_missing_makes_tempfile(self): async def test_run_script_text(self): assert await self.ahk.win_get(title='Untitled - Notepad') is None - script = 'Run Notepad' - await self.ahk.run_script(script) - time.sleep(0.3) - notepad = await self.ahk.win_get(title='Untitled - Notepad') - assert notepad is not 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('Run Notepad') - await self.ahk.run_script(f.name) - time.sleep(0.3) - notepad = await self.ahk.win_get(title='Untitled - Notepad') - assert notepad is not None + 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') - time.sleep(0.3) + 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) - time.sleep(0.3) - notepad = await self.ahk.win_get(title='*Untitled - Notepad') + notepad = await self.ahk.win_wait(title='*Untitled - Notepad', timeout=3) assert notepad is not None text = await notepad.get_text() assert 'א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ' in text diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py index 4b1e4a2e..ccf8758d 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -33,30 +33,25 @@ def test_script_missing_makes_tempfile(self): def test_run_script_text(self): assert self.ahk.win_get(title='Untitled - Notepad') is None - script = 'Run Notepad' - self.ahk.run_script(script) - time.sleep(0.3) - notepad = self.ahk.win_get(title='Untitled - Notepad') - assert notepad is not 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('Run Notepad') - self.ahk.run_script(f.name) - time.sleep(0.3) - notepad = self.ahk.win_get(title='Untitled - Notepad') - assert notepad is not None + 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') - time.sleep(0.3) + 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) - time.sleep(0.3) - notepad = self.ahk.win_get(title='*Untitled - Notepad') + notepad = self.ahk.win_wait(title='*Untitled - Notepad', timeout=3) assert notepad is not None text = notepad.get_text() assert 'א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ' in text From 3a359863930bc7adb19f6a768f832332f56b6767 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 5 Feb 2023 03:04:17 -0800 Subject: [PATCH 342/588] make API more consistent --- ahk/_async/engine.py | 119 ++++++++++++++++++++--------------------- ahk/_sync/engine.py | 123 ++++++++++++++++++++++--------------------- 2 files changed, 123 insertions(+), 119 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 4abf5df7..cb6bbdbe 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -135,7 +135,7 @@ def __init__( self._transport: AsyncTransport = transport def add_hotkey( - self, keyname: str, callback: Callable[[], Any], *, ex_handler: Optional[Callable[[str, Exception], Any]] = None + 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. @@ -161,7 +161,6 @@ def add_hotstring( self, trigger: str, replacement_or_callback: Union[str, Callable[[], Any]], - *, ex_handler: Optional[Callable[[str, Exception], Any]] = None, options: str = '', ) -> None: @@ -247,17 +246,16 @@ async def get_coord_mode(self, target: CoordModeTargets) -> str: # 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: ... + 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]: ... + 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: ... + 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]]: ... + 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 = '', @@ -266,6 +264,7 @@ async def control_click( text: str = '', exclude_title: str = '', exclude_text: str = '', + *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, @@ -564,21 +563,21 @@ def _format_win_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]: ... + 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]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + 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]) -> Union[List[AsyncWindow], 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]: ... + 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]]]: ... + 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, @@ -596,16 +595,16 @@ async def list_windows( # fmt: off @overload - async def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: Literal[True]) -> Tuple[int, int]: ... + async def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[True]) -> Tuple[int, int]: ... @overload - async def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: Literal[False]) -> AsyncFutureResult[Tuple[int, int]]: ... + async def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[False]) -> AsyncFutureResult[Tuple[int, int]]: ... @overload - async def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None) -> Tuple[int, int]: ... + async def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None) -> Tuple[int, int]: ... @overload - async def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: bool = True) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... + async def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... # fmt: on async def get_mouse_position( - self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: bool = True + self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True ) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: if coord_mode: args = [str(coord_mode)] @@ -2245,21 +2244,21 @@ async def win_set_trans_color( # 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) -> None: ... + 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) -> 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) -> None: ... + 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) -> 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) -> AsyncFutureResult[None]: ... + 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) -> 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) -> Union[None, AsyncFutureResult[None]]: ... + 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) -> 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, @@ -2278,22 +2277,22 @@ async def right_click( # 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) -> None: ... + 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) -> 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) -> None: ... + 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) -> 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) -> AsyncFutureResult[None]: ... + 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) -> 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) -> Union[None, AsyncFutureResult[None]]: ... + 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) -> 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, @@ -2471,22 +2470,22 @@ async def pixel_search( # 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: ... + 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: ... + 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]: ... + 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]]: ... + 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, @@ -2506,22 +2505,22 @@ async def win_close( # fmt: off @overload - async def win_kill(self, *, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', 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]]: ... + 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 = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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 = '', 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]]: ... + 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, @@ -2541,21 +2540,21 @@ async def win_kill( # 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: ... + 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]]: ... + 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: ... + 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]]: ... + 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, @@ -2573,21 +2572,21 @@ async def win_minimize( # 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: ... + 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]]: ... + 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: ... + 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]]: ... + 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, @@ -2605,21 +2604,21 @@ async def win_maximize( # 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: ... + 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]]: ... + 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: ... + 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]]: ... + 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, @@ -2637,11 +2636,11 @@ async def win_restore( 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, @@ -2661,11 +2660,11 @@ async def win_wait( 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, @@ -2685,11 +2684,11 @@ async def win_wait_active( 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, @@ -2843,17 +2842,19 @@ async def win_move( resp = await self._transport.function_call('AHKWinMove', args, blocking=blocking) return resp - async def get_clipboard(self, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: + async def get_clipboard(self, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: return await self._transport.function_call('AHKGetClipboard', blocking=blocking) - async def set_clipboard(self, s: str, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + async def set_clipboard(self, s: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: 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]]: + async def get_clipboard_all(self, *, blocking: bool = True) -> Union[bytes, AsyncFutureResult[bytes]]: return await self._transport.function_call('AHKGetClipboardAll', blocking=blocking) - async def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + async def set_clipboard_all( + self, contents: bytes, *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: # 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') diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 87e1a902..64a09102 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -131,7 +131,7 @@ def __init__( self._transport: Transport = transport def add_hotkey( - self, keyname: str, callback: Callable[[], Any], *, ex_handler: Optional[Callable[[str, Exception], Any]] = None + 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. @@ -157,7 +157,6 @@ def add_hotstring( self, trigger: str, replacement_or_callback: Union[str, Callable[[], Any]], - *, ex_handler: Optional[Callable[[str, Exception], Any]] = None, options: str = '', ) -> None: @@ -243,17 +242,16 @@ def get_coord_mode(self, target: CoordModeTargets) -> str: # 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: ... + 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]: ... + 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: ... + 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]]: ... + 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 = '', @@ -262,6 +260,7 @@ def control_click( text: str = '', exclude_title: str = '', exclude_text: str = '', + *, title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, @@ -560,21 +559,21 @@ def _format_win_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]: ... + 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]) -> Union[List[Window], FutureResult[List[Window]]]: ... + 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]) -> Union[List[Window], 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]: ... + 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]]]: ... + 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, @@ -592,16 +591,16 @@ def list_windows( # fmt: off @overload - def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: Literal[True]) -> Tuple[int, int]: ... + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[True]) -> Tuple[int, int]: ... @overload - def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: Literal[False]) -> FutureResult[Tuple[int, int]]: ... + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[False]) -> FutureResult[Tuple[int, int]]: ... @overload - def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None) -> Tuple[int, int]: ... + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None) -> Tuple[int, int]: ... @overload - def get_mouse_position(self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: bool = True) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... # fmt: on def get_mouse_position( - self, *, coord_mode: Optional[CoordModeRelativeTo] = None, blocking: bool = True + self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True ) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: if coord_mode: args = [str(coord_mode)] @@ -2234,21 +2233,21 @@ def win_set_trans_color( # 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) -> None: ... + 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) -> 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) -> None: ... + 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) -> 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) -> FutureResult[None]: ... + 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) -> 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) -> Union[None, FutureResult[None]]: ... + 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) -> 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, @@ -2267,22 +2266,22 @@ def right_click( # 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) -> None: ... + 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) -> 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) -> None: ... + 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) -> 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) -> FutureResult[None]: ... + 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) -> 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) -> Union[None, FutureResult[None]]: ... + 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) -> 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, @@ -2460,22 +2459,22 @@ def pixel_search( # 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: ... + 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: ... + 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]: ... + 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]]: ... + 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, @@ -2495,22 +2494,22 @@ def win_close( # fmt: off @overload - def win_kill(self, *, title: str = '', text: str = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None) -> None: ... + 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 = '', 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]]: ... + 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 = '', exclude_title: str = '', exclude_text: str = '', title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: Literal[True]) -> None: ... + 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 = '', 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]]: ... + 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, @@ -2530,21 +2529,21 @@ def win_kill( # 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: ... + 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]]: ... + 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: ... + 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]]: ... + 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, @@ -2562,21 +2561,21 @@ def win_minimize( # 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: ... + 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]]: ... + 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: ... + 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]]: ... + 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, @@ -2594,21 +2593,21 @@ def win_maximize( # 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: ... + 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]]: ... + 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: ... + 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]]: ... + 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, @@ -2626,11 +2625,11 @@ def win_restore( 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, @@ -2650,11 +2649,11 @@ def win_wait( 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, @@ -2674,11 +2673,11 @@ def win_wait_active( 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, @@ -2832,17 +2831,19 @@ def win_move( resp = self._transport.function_call('AHKWinMove', args, blocking=blocking) return resp - def get_clipboard(self, blocking: bool = True) -> Union[str, FutureResult[str]]: + def get_clipboard(self, *, blocking: bool = True) -> Union[str, FutureResult[str]]: return self._transport.function_call('AHKGetClipboard', blocking=blocking) - def set_clipboard(self, s: str, blocking: bool = True) -> Union[None, FutureResult[None]]: + def set_clipboard(self, s: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: args = [s] return self._transport.function_call('AHKSetClipboard', args, blocking=blocking) - def get_clipboard_all(self, blocking: bool = True) -> Union[bytes, FutureResult[bytes]]: + def get_clipboard_all(self, *, blocking: bool = True) -> Union[bytes, FutureResult[bytes]]: return self._transport.function_call('AHKGetClipboardAll', blocking=blocking) - def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Union[None, FutureResult[None]]: + def set_clipboard_all( + self, contents: bytes, *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: # 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') @@ -2861,7 +2862,9 @@ def set_clipboard_all(self, contents: bytes, blocking: bool = True) -> Union[Non except Exception: pass - def on_clipboard_change(self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None) -> None: + def on_clipboard_change( + self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None + ) -> None: self._transport.on_clipboard_change(callback, ex_handler) def block_input( From ad68a2c66bcfb7501d46d93651fc357fe827f4c8 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 5 Feb 2023 03:51:20 -0800 Subject: [PATCH 343/588] implement tooltip --- ahk/_async/engine.py | 26 ++++++++++++++++++++------ ahk/_async/transport.py | 5 +++-- ahk/_constants.py | 11 +++++++++++ ahk/_sync/engine.py | 26 ++++++++++++++++++++------ ahk/_sync/transport.py | 9 ++++++--- ahk/templates/daemon.ahk | 11 +++++++++++ 6 files changed, 71 insertions(+), 17 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index cb6bbdbe..f9cee154 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -661,8 +661,9 @@ async def mouse_move( resp = await self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp - async def a_run_script(self, script_text: str, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: - raise NotImplementedError() + async def a_run_script(self, *args: Any, **kwargs: Any) -> Union[str, AsyncFutureResult[str]]: + warnings.warn('a_run_script is deprecated. Use run_script instead.', DeprecationWarning, stacklevel=2) + return await self.run_script(*args, **kwargs) # fmt: off @overload @@ -1189,15 +1190,28 @@ async def show_warning_traytip( async def show_tooltip( self, - text: str, + text: str = '', x: Optional[int] = None, y: Optional[int] = None, + which: int = 1, *, - second: float = 1.0, - id: Optional[str] = None, blocking: bool = True, ) -> None: - raise NotImplementedError() + 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('') + await self._transport.function_call('AHKShowToolTip', args, blocking=blocking) + + async def hide_tooltip(self, which: int = 1) -> None: + await self.show_tooltip(which=which) async def sound_beep( self, frequency: int = 523, duration: int = 150, *, blocking: bool = True diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 3d0b2ad6..73163061 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -111,6 +111,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSetSendLevel', 'AHKSetTitleMatchMode', 'AHKSetVolume', + 'AHKShowToolTip', 'AHKSoundBeep', 'AHKSoundGet', 'AHKSoundPlay', @@ -544,12 +545,12 @@ async def function_call(self, function_name: Literal['AHKSetClipboard'], args: O 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]], *, blocking: bool = True) -> 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['BaseCheck'], args: Optional[List[str]] = None) -> None: ... - # @overload # async def function_call(self, function_name: Literal['WinWait'], args: Optional[List[str]] = None) -> str: ... # @overload # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[List[str]] = None) -> str: ... diff --git a/ahk/_constants.py b/ahk/_constants.py index 22908051..f693cf7f 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2479,6 +2479,17 @@ {% endblock AHKTraytip %} } +AHKShowToolTip(ByRef command) { + {% block AHKShowToolTip %} + text := command[2] + x := command[3] + y := command[4] + which := command[5] + ToolTip, %text%, %x%, %y%, %which% + return FormatNoValueResponse() + {% endblock AHKShowToolTip %} +} + AHKGetClipboard(ByRef command) { {% block AHKGetClipboard %} global STRINGRESPONSEMESSAGE diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 64a09102..aeb9b72f 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -653,8 +653,9 @@ def mouse_move( resp = self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp - def a_run_script(self, script_text: str, blocking: bool = True) -> Union[str, FutureResult[str]]: - raise NotImplementedError() + def a_run_script(self, *args: Any, **kwargs: Any) -> Union[str, FutureResult[str]]: + warnings.warn('a_run_script is deprecated. Use run_script instead.', DeprecationWarning, stacklevel=2) + return self.run_script(*args, **kwargs) # fmt: off @overload @@ -1178,15 +1179,28 @@ def show_warning_traytip( def show_tooltip( self, - text: str, + text: str = '', x: Optional[int] = None, y: Optional[int] = None, + which: int = 1, *, - second: float = 1.0, - id: Optional[str] = None, blocking: bool = True, ) -> None: - raise NotImplementedError() + 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('') + self._transport.function_call('AHKShowToolTip', args, blocking=blocking) + + def hide_tooltip(self, which: int = 1) -> None: + self.show_tooltip(which=which) def sound_beep( self, frequency: int = 523, duration: int = 150, *, blocking: bool = True diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 8b6eebb4..f6a5d94b 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -103,6 +103,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSetSendLevel', 'AHKSetTitleMatchMode', 'AHKSetVolume', + 'AHKShowToolTip', 'AHKSoundBeep', 'AHKSoundGet', 'AHKSoundPlay', @@ -311,7 +312,9 @@ def __init__( self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) self._directives: list[Union[Directive, Type[Directive]]] = directives or [] - def on_clipboard_change(self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None) -> None: + def on_clipboard_change( + self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None + ) -> None: self._hotkey_transport.on_clipboard_change(callback, ex_handler) return None @@ -523,12 +526,12 @@ def function_call(self, function_name: Literal['AHKSetClipboard'], args: Optiona 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]], *, blocking: bool = True) -> 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['BaseCheck'], args: Optional[List[str]] = None) -> None: ... - # @overload # async def function_call(self, function_name: Literal['WinWait'], args: Optional[List[str]] = None) -> str: ... # @overload # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[List[str]] = None) -> str: ... diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 0d144152..87356f4d 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2476,6 +2476,17 @@ AHKTraytip(ByRef command) { {% endblock AHKTraytip %} } +AHKShowToolTip(ByRef command) { + {% block AHKShowToolTip %} + text := command[2] + x := command[3] + y := command[4] + which := command[5] + ToolTip, %text%, %x%, %y%, %which% + return FormatNoValueResponse() + {% endblock AHKShowToolTip %} +} + AHKGetClipboard(ByRef command) { {% block AHKGetClipboard %} global STRINGRESPONSEMESSAGE From 8efe4df217bef48bc67e8ba329d5b6dd4060bebd Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 7 Feb 2023 16:44:51 -0800 Subject: [PATCH 344/588] implement clip_wait --- ahk/_async/engine.py | 8 ++++++++ ahk/_async/transport.py | 3 +++ ahk/_constants.py | 14 ++++++++++++++ ahk/_sync/engine.py | 8 ++++++++ ahk/_sync/transport.py | 3 +++ ahk/templates/daemon.ahk | 14 ++++++++++++++ 6 files changed, 50 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index f9cee154..1e7b76b6 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2892,6 +2892,14 @@ def on_clipboard_change( ) -> None: self._transport.on_clipboard_change(callback, ex_handler) + async def clip_wait( + self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: bool = True + ) -> None: + args = [str(timeout) if timeout else ''] + if wait_for_any_data: + args.append('1') + await self._transport.function_call('AHKClipWait', args, blocking=blocking) + async def block_input( self, value: Literal['On', 'Off', 'Default', 'Send', 'Mouse', 'MouseMove', 'MouseMoveOff', 'SendAndMouse'], diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 73163061..b906c0b9 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -80,6 +80,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ 'AHKBlockInput', + 'AHKClipWait', 'AHKControlClick', 'AHKControlGetPos', 'AHKControlGetText', @@ -547,6 +548,8 @@ async def function_call(self, function_name: Literal['AHKSetClipboardAll'], args 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]], *, blocking: bool = True) -> 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: ... diff --git a/ahk/_constants.py b/ahk/_constants.py index f693cf7f..078db0d9 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2521,6 +2521,20 @@ {% endblock AHKSetClipboardAll %} } +AHKClipWait(ByRef command) { + global TIMEOUTRESPONSEMESSAGE + timeout := command[2] + wait_for_any_data := command[3] + + + ClipWait, %timeout%, %wait_for_any_data% + + if (ErrorLevel = 1) { + return FormatResponse(TIMEOUTRESPONSEMESSAGE, "timed out waiting for clipboard data") + } + return FormatNoValueResponse() +} + AHKBlockInput(ByRef command) { value := command[2] BlockInput, %value% diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index aeb9b72f..458348ac 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2881,6 +2881,14 @@ def on_clipboard_change( ) -> None: self._transport.on_clipboard_change(callback, ex_handler) + def clip_wait( + self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: bool = True + ) -> None: + args = [str(timeout) if timeout else ''] + if wait_for_any_data: + args.append('1') + self._transport.function_call('AHKClipWait', args, blocking=blocking) + def block_input( self, value: Literal['On', 'Off', 'Default', 'Send', 'Mouse', 'MouseMove', 'MouseMoveOff', 'SendAndMouse'], diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index f6a5d94b..a121b96c 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -72,6 +72,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: FunctionName = Literal[ 'AHKBlockInput', + 'AHKClipWait', 'AHKControlClick', 'AHKControlGetPos', 'AHKControlGetText', @@ -528,6 +529,8 @@ def function_call(self, function_name: Literal['AHKSetClipboardAll'], args: Opti 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]], *, blocking: bool = True) -> 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: ... diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 87356f4d..369aaca7 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2518,6 +2518,20 @@ AHKSetClipboardAll(ByRef command) { {% endblock AHKSetClipboardAll %} } +AHKClipWait(ByRef command) { + global TIMEOUTRESPONSEMESSAGE + timeout := command[2] + wait_for_any_data := command[3] + + + ClipWait, %timeout%, %wait_for_any_data% + + if (ErrorLevel = 1) { + return FormatResponse(TIMEOUTRESPONSEMESSAGE, "timed out waiting for clipboard data") + } + return FormatNoValueResponse() +} + AHKBlockInput(ByRef command) { value := command[2] BlockInput, %value% From d641d36d6b4ff02ef43b0774ac829e2d5e318746 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 7 Feb 2023 17:52:25 -0800 Subject: [PATCH 345/588] add reg read/write/delete --- ahk/_async/engine.py | 36 +++++++++++++++++++++-- ahk/_async/transport.py | 23 ++++++--------- ahk/_constants.py | 54 ++++++++++++++++++++++++----------- ahk/_sync/engine.py | 36 +++++++++++++++++++++-- ahk/_sync/transport.py | 23 ++++++--------- ahk/templates/daemon.ahk | 54 ++++++++++++++++++++++++----------- tests/_async/test_registry.py | 53 ++++++++++++++++++++++++++++++++++ tests/_sync/test_registry.py | 53 ++++++++++++++++++++++++++++++++++ 8 files changed, 266 insertions(+), 66 deletions(-) create mode 100644 tests/_async/test_registry.py create mode 100644 tests/_sync/test_registry.py diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 1e7b76b6..7e2e2e2f 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2894,11 +2894,11 @@ def on_clipboard_change( async def clip_wait( self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: bool = True - ) -> None: + ) -> Union[None, AsyncFutureResult[None]]: args = [str(timeout) if timeout else ''] if wait_for_any_data: args.append('1') - await self._transport.function_call('AHKClipWait', args, blocking=blocking) + return await self._transport.function_call('AHKClipWait', args, blocking=blocking) async def block_input( self, @@ -2907,6 +2907,38 @@ async def block_input( ) -> None: await self._transport.function_call('AHKBlockInput', args=[value]) + async def reg_delete( + self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True + ) -> Union[None, AsyncFutureResult[None]]: + args = [key_name, value_name if value_name is not None else ''] + return await self._transport.function_call('AHKRegDelete', args, blocking=blocking) + + 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]]: + 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) + return await self._transport.function_call('AHKRegWrite', args, blocking=blocking) + + async def reg_read( + self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True + ) -> Union[str, AsyncFutureResult[str]]: + args = [key_name] + if value_name is not None: + args.append(value_name) + return await self._transport.function_call('AHKRegRead', args, blocking=blocking) + async def block_forever(self) -> NoReturn: while True: await async_sleep(1) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index b906c0b9..2c166bc7 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -100,6 +100,9 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKMouseMove', 'AHKPixelGetColor', 'AHKPixelSearch', + 'AHKRegRead', + 'AHKRegWrite', + 'AHKRegDelete', 'AHKSend', 'AHKSendEvent', 'AHKSendInput', @@ -554,21 +557,13 @@ async def function_call(self, function_name: Literal['AHKClipWait'], args: Optio # @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['WinWait'], args: Optional[List[str]] = None) -> str: ... - # @overload - # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[List[str]] = None) -> str: ... - # @overload - # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[List[str]] = None) -> str: ... - # @overload # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[List[str]] = None) -> None: ... - # @overload - # async def function_call(self, function_name: Literal['RegRead'], args: Optional[List[str]] = None) -> None: ... - # @overload - # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[List[str]] = None) -> None: ... - # @overload - # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[List[str]] = None) -> None: ... - # @overload - # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[List[str]] = None) -> 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]]: ... # fmt: on diff --git a/ahk/_constants.py b/ahk/_constants.py index 078db0d9..9f5dec6d 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -1792,34 +1792,54 @@ {% endblock AHKMouseClickDrag %} } -RegRead(ByRef command) { +AHKRegRead(ByRef command) { {% block RegRead %} - keyname := command[3] - RegRead, output, %keyname%, command[4] - return output + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + key_name := command[2] + value_name := command[3] + + RegRead, output, %key_name%, %value_name% + + if (ErrorLevel = 1) { + resp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + } + else { + resp := FormatResponse(STRINGRESPONSEMESSAGE, Format("{}", output)) + } + return resp {% endblock RegRead %} } -SetRegView(ByRef command) { - {% block SetRegView %} - view := command[2] - SetRegView, %view% - {% endblock SetRegView %} -} -RegWrite(ByRef command) { + +AHKRegWrite(ByRef command) { {% block RegWrite %} - valuetype := command[2] - keyname := command[3] + global EXCEPTIONRESPONSEMESSAGE + value_type := command[2] + key_name := command[3] + value_name := command[4] + value := command[5] + RegWrite, %value_type%, %key_name%, %value_name%, %value% + if (ErrorLevel = 1) { + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + } - RegWrite, %valuetype%, %keyname%, command[4] + return FormatNoValueResponse() {% endblock RegWrite %} } -RegDelete(ByRef command) { +AHKRegDelete(ByRef command) { {% block RegDelete %} - keyname := command[2] - RegDelete, %keyname%, command[3] + global EXCEPTIONRESPONSEMESSAGE + key_name := command[2] + value_name := command[3] + RegDelete, %key_name%, %value_name% + if (ErrorLevel = 1) { + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + } + return FormatNoValueResponse() + {% endblock RegDelete %} } diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 458348ac..c3c9b644 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2883,11 +2883,11 @@ def on_clipboard_change( def clip_wait( self, timeout: Optional[float] = None, wait_for_any_data: bool = False, *, blocking: bool = True - ) -> None: + ) -> Union[None, FutureResult[None]]: args = [str(timeout) if timeout else ''] if wait_for_any_data: args.append('1') - self._transport.function_call('AHKClipWait', args, blocking=blocking) + return self._transport.function_call('AHKClipWait', args, blocking=blocking) def block_input( self, @@ -2896,6 +2896,38 @@ def block_input( ) -> None: self._transport.function_call('AHKBlockInput', args=[value]) + def reg_delete( + self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + args = [key_name, value_name if value_name is not None else ''] + return self._transport.function_call('AHKRegDelete', args, blocking=blocking) + + 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]]: + 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) + return self._transport.function_call('AHKRegWrite', args, blocking=blocking) + + def reg_read( + self, key_name: str, value_name: Optional[str] = None, *, blocking: bool = True + ) -> Union[str, FutureResult[str]]: + args = [key_name] + if value_name is not None: + args.append(value_name) + return self._transport.function_call('AHKRegRead', args, blocking=blocking) + def block_forever(self) -> NoReturn: while True: sleep(1) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index a121b96c..b1dbd474 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -92,6 +92,9 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKMouseMove', 'AHKPixelGetColor', 'AHKPixelSearch', + 'AHKRegRead', + 'AHKRegWrite', + 'AHKRegDelete', 'AHKSend', 'AHKSendEvent', 'AHKSendInput', @@ -535,21 +538,13 @@ def function_call(self, function_name: Literal['AHKClipWait'], args: Optional[Li # @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['WinWait'], args: Optional[List[str]] = None) -> str: ... - # @overload - # async def function_call(self, function_name: Literal['WinWaitActive'], args: Optional[List[str]] = None) -> str: ... - # @overload - # async def function_call(self, function_name: Literal['WinWaitNotActive'], args: Optional[List[str]] = None) -> str: ... - # @overload # async def function_call(self, function_name: Literal['WinWaitClose'], args: Optional[List[str]] = None) -> None: ... - # @overload - # async def function_call(self, function_name: Literal['RegRead'], args: Optional[List[str]] = None) -> None: ... - # @overload - # async def function_call(self, function_name: Literal['SetRegView'], args: Optional[List[str]] = None) -> None: ... - # @overload - # async def function_call(self, function_name: Literal['RegWrite'], args: Optional[List[str]] = None) -> None: ... - # @overload - # async def function_call(self, function_name: Literal['RegDelete'], args: Optional[List[str]] = None) -> 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]]: ... # fmt: on diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 369aaca7..1026fbf9 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1789,34 +1789,54 @@ AHKMouseClickDrag(ByRef command) { {% endblock AHKMouseClickDrag %} } -RegRead(ByRef command) { +AHKRegRead(ByRef command) { {% block RegRead %} - keyname := command[3] - RegRead, output, %keyname%, command[4] - return output + global STRINGRESPONSEMESSAGE + global EXCEPTIONRESPONSEMESSAGE + key_name := command[2] + value_name := command[3] + + RegRead, output, %key_name%, %value_name% + + if (ErrorLevel = 1) { + resp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + } + else { + resp := FormatResponse(STRINGRESPONSEMESSAGE, Format("{}", output)) + } + return resp {% endblock RegRead %} } -SetRegView(ByRef command) { - {% block SetRegView %} - view := command[2] - SetRegView, %view% - {% endblock SetRegView %} -} -RegWrite(ByRef command) { + +AHKRegWrite(ByRef command) { {% block RegWrite %} - valuetype := command[2] - keyname := command[3] + global EXCEPTIONRESPONSEMESSAGE + value_type := command[2] + key_name := command[3] + value_name := command[4] + value := command[5] + RegWrite, %value_type%, %key_name%, %value_name%, %value% + if (ErrorLevel = 1) { + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + } - RegWrite, %valuetype%, %keyname%, command[4] + return FormatNoValueResponse() {% endblock RegWrite %} } -RegDelete(ByRef command) { +AHKRegDelete(ByRef command) { {% block RegDelete %} - keyname := command[2] - RegDelete, %keyname%, command[3] + global EXCEPTIONRESPONSEMESSAGE + key_name := command[2] + value_name := command[3] + RegDelete, %key_name%, %value_name% + if (ErrorLevel = 1) { + return FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + } + return FormatNoValueResponse() + {% endblock RegDelete %} } diff --git a/tests/_async/test_registry.py b/tests/_async/test_registry.py new file mode 100644 index 00000000..e6f9e8ee --- /dev/null +++ b/tests/_async/test_registry.py @@ -0,0 +1,53 @@ +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') diff --git a/tests/_sync/test_registry.py b/tests/_sync/test_registry.py new file mode 100644 index 00000000..735b0aad --- /dev/null +++ b/tests/_sync/test_registry.py @@ -0,0 +1,53 @@ +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') From 8888688ce1a40b528d5408b06921c8702a46977c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 8 Feb 2023 00:59:53 -0800 Subject: [PATCH 346/588] add missing overloads --- ahk/_async/engine.py | 180 ++++++++++++++++++++++++++++++++++++++-- ahk/_async/transport.py | 2 +- ahk/_sync/engine.py | 180 ++++++++++++++++++++++++++++++++++++++-- ahk/_sync/transport.py | 2 +- 4 files changed, 352 insertions(+), 12 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 7e2e2e2f..b5b50863 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1125,12 +1125,32 @@ async def set_capslock_state( resp = await self._transport.function_call('AHKSetCapsLockState', 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 + self, value: int, device_number: int = 1, *, blocking: bool = True ) -> Union[None, AsyncFutureResult[None]]: args = [str(device_number), str(value)] return await self._transport.function_call('AHKSetVolume', args, blocking=blocking) + # fmt: off + @overload + async def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_traytip(self, title: str, text: str, second: float = 1.0, 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, @@ -1146,6 +1166,16 @@ async def show_traytip( 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on async def show_error_traytip( self, title: str, @@ -1160,6 +1190,16 @@ async def show_error_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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on async def show_info_traytip( self, title: str, @@ -1174,6 +1214,16 @@ async def show_info_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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + @overload + async def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + async def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + # fmt: on async def show_warning_traytip( self, title: str, @@ -1194,8 +1244,6 @@ async def show_tooltip( x: Optional[int] = None, y: Optional[int] = None, which: int = 1, - *, - blocking: bool = True, ) -> None: if which not in range(1, 21): raise ValueError('which must be an integer between 1 and 20') @@ -1208,11 +1256,21 @@ async def show_tooltip( args.append(str(y)) else: args.append('') - await self._transport.function_call('AHKShowToolTip', args, blocking=blocking) + await self._transport.function_call('AHKShowToolTip', args) async def hide_tooltip(self, which: int = 1) -> None: await self.show_tooltip(which=which) + # 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]]: @@ -1220,17 +1278,38 @@ async def sound_beep( 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]]: args = [str(device_number), component_type, control_type] return await self._transport.function_call('AHKSoundGet', args, blocking=blocking) - async def sound_play(self, filename: str, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + # 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]]: return await self._transport.function_call('AHKSoundPlay', [filename], blocking=blocking) async def sound_set( @@ -1239,6 +1318,7 @@ async def sound_set( device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME', + *, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: args = [str(device_number), component_type, control_type, str(value)] @@ -2648,6 +2728,16 @@ async def win_restore( 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 = '', @@ -2672,6 +2762,16 @@ async def win_wait( 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 = '', @@ -2696,6 +2796,16 @@ async def win_wait_active( 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 = '', @@ -2856,6 +2966,16 @@ async def win_move( 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]]: return await self._transport.function_call('AHKGetClipboard', blocking=blocking) @@ -2866,6 +2986,16 @@ async def set_clipboard(self, s: str, *, blocking: bool = True) -> Union[None, A async def get_clipboard_all(self, *, blocking: bool = True) -> Union[bytes, AsyncFutureResult[bytes]]: 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]]: @@ -2892,6 +3022,16 @@ def on_clipboard_change( ) -> None: 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]]: @@ -2907,12 +3047,32 @@ async def block_input( ) -> None: 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]]: 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'], @@ -2931,6 +3091,16 @@ async def reg_write( args.append(value) 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]]: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 2c166bc7..03ad6f36 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -550,7 +550,7 @@ async def function_call(self, function_name: Literal['AHKSetClipboardAll'], args @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]], *, blocking: bool = True) -> None: ... + 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]]: ... diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index c3c9b644..8ecd3466 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1114,12 +1114,32 @@ def set_capslock_state( resp = self._transport.function_call('AHKSetCapsLockState', 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 + self, value: int, device_number: int = 1, *, blocking: bool = True ) -> Union[None, FutureResult[None]]: args = [str(device_number), str(value)] return self._transport.function_call('AHKSetVolume', args, blocking=blocking) + # fmt: off + @overload + def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_traytip(self, title: str, text: str, second: float = 1.0, 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, @@ -1135,6 +1155,16 @@ def show_traytip( args = [title, text, str(second), str(option)] return self._transport.function_call('AHKTrayTip', args, blocking=blocking) + # fmt: off + @overload + def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on def show_error_traytip( self, title: str, @@ -1149,6 +1179,16 @@ def show_error_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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on def show_info_traytip( self, title: str, @@ -1163,6 +1203,16 @@ def show_info_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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on def show_warning_traytip( self, title: str, @@ -1183,8 +1233,6 @@ def show_tooltip( x: Optional[int] = None, y: Optional[int] = None, which: int = 1, - *, - blocking: bool = True, ) -> None: if which not in range(1, 21): raise ValueError('which must be an integer between 1 and 20') @@ -1197,11 +1245,21 @@ def show_tooltip( args.append(str(y)) else: args.append('') - self._transport.function_call('AHKShowToolTip', args, blocking=blocking) + self._transport.function_call('AHKShowToolTip', args) def hide_tooltip(self, which: int = 1) -> None: self.show_tooltip(which=which) + # 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]]: @@ -1209,17 +1267,38 @@ def sound_beep( 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]]: args = [str(device_number), component_type, control_type] return self._transport.function_call('AHKSoundGet', args, blocking=blocking) - def sound_play(self, filename: str, blocking: bool = True) -> Union[None, FutureResult[None]]: + # 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]]: return self._transport.function_call('AHKSoundPlay', [filename], blocking=blocking) def sound_set( @@ -1228,6 +1307,7 @@ def sound_set( device_number: int = 1, component_type: str = 'MASTER', control_type: str = 'VOLUME', + *, blocking: bool = True, ) -> Union[None, FutureResult[None]]: args = [str(device_number), component_type, control_type, str(value)] @@ -2637,6 +2717,16 @@ def win_restore( 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 = '', @@ -2661,6 +2751,16 @@ def win_wait( 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 = '', @@ -2685,6 +2785,16 @@ def win_wait_active( 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 = '', @@ -2845,6 +2955,16 @@ def win_move( 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]]: return self._transport.function_call('AHKGetClipboard', blocking=blocking) @@ -2855,6 +2975,16 @@ def set_clipboard(self, s: str, *, blocking: bool = True) -> Union[None, FutureR def get_clipboard_all(self, *, blocking: bool = True) -> Union[bytes, FutureResult[bytes]]: 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]]: @@ -2881,6 +3011,16 @@ def on_clipboard_change( ) -> None: 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]]: @@ -2896,12 +3036,32 @@ def block_input( ) -> None: 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]]: 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'], @@ -2920,6 +3080,16 @@ def reg_write( args.append(value) 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]]: diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index b1dbd474..a05a5e34 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -531,7 +531,7 @@ def function_call(self, function_name: Literal['AHKSetClipboardAll'], args: Opti @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]], *, blocking: bool = True) -> None: ... + 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]]: ... From ed35de12c127bc073c109f5c764c895e8666a825 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 8 Feb 2023 01:10:24 -0800 Subject: [PATCH 347/588] implement win_wait_close --- ahk/_async/engine.py | 34 +++++++++++++++++++++++ ahk/_async/transport.py | 5 ++-- ahk/_constants.py | 42 +++++++++++++++++++++++++++++ ahk/_sync/engine.py | 58 +++++++++++++++++++++++++++++++--------- ahk/_sync/transport.py | 5 ++-- ahk/templates/daemon.ahk | 42 +++++++++++++++++++++++++++++ 6 files changed, 170 insertions(+), 16 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index b5b50863..6c9a614a 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2830,6 +2830,40 @@ async def win_wait_not_active( 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]]: + 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: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 03ad6f36..424d757e 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -162,6 +162,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWindowList', 'AHKWinWait', 'AHKWinWaitActive', + 'AHKWinWaitClose', 'AHKWinWaitNotActive', 'AHKClick', 'AHKSetCapsLockState', @@ -556,8 +557,8 @@ async def function_call(self, function_name: Literal['AHKClipWait'], args: Optio # @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['WinWaitClose'], 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] = 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 diff --git a/ahk/_constants.py b/ahk/_constants.py index 9f5dec6d..0e4e00a9 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -347,7 +347,49 @@ {% endblock AHKWinWaitNotActive %} } +AHKWinWaitClose(ByRef command) { + {% block AHKWinWaitClose %} + global TIMEOUTRESPONSEMESSAGE + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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% + } + if (timeout != "") { + WinWaitClose, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitClose, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse(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(ByRef command) { {% block AHKWinMinimize %} diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 8ecd3466..f3e81ed8 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2719,13 +2719,13 @@ def win_restore( # 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: ... + 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]: ... + 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: ... + 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]]: ... + 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, @@ -2753,13 +2753,13 @@ def win_wait( # 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: ... + 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]: ... + 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: ... + 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]]: ... + 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, @@ -2787,13 +2787,13 @@ def win_wait_active( # 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: ... + 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]: ... + 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: ... + 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]]: ... + 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, @@ -2819,6 +2819,40 @@ def win_wait_not_active( 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]]: + 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: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index a05a5e34..ff280260 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -154,6 +154,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKWindowList', 'AHKWinWait', 'AHKWinWaitActive', + 'AHKWinWaitClose', 'AHKWinWaitNotActive', 'AHKClick', 'AHKSetCapsLockState', @@ -537,8 +538,8 @@ def function_call(self, function_name: Literal['AHKClipWait'], args: Optional[Li # @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['WinWaitClose'], 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] = 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 diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 1026fbf9..17af1821 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -344,7 +344,49 @@ AHKWinWaitNotActive(ByRef command) { {% endblock AHKWinWaitNotActive %} } +AHKWinWaitClose(ByRef command) { + {% block AHKWinWaitClose %} + global TIMEOUTRESPONSEMESSAGE + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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% + } + if (timeout != "") { + WinWaitClose, %title%, %text%, %timeout%, %extitle%, %extext% + } else { + WinWaitClose, %title%, %text%,, %extitle%, %extext% + } + if (ErrorLevel = 1) { + resp := FormatResponse(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(ByRef command) { {% block AHKWinMinimize %} From dca2bad7c03835d00e26214f0883d7460e010dee Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 13 Apr 2023 20:14:42 -0700 Subject: [PATCH 348/588] update docstrings --- ahk/_async/engine.py | 106 +++++++++++++++++++++++++++++++++++++++++-- ahk/_sync/engine.py | 106 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 206 insertions(+), 6 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 6c9a614a..e64cadac 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -147,7 +147,10 @@ def add_hotkey( - 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 hotkey: an instance of ahk.hotkey.Hotkey + :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: @@ -172,7 +175,11 @@ def add_hotstring( - 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 hotstring: an instance of ahk.hotkey.Hotstring + :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: @@ -269,6 +276,21 @@ async def control_click( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: + """ + + :param button: the mouse button to use + :param click_count: how many times to click + :param options: options -- same meaning as in AutoHotkey + :param control: the control to click + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ 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: @@ -325,6 +347,19 @@ async def control_get_text( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, AsyncFutureResult[str]]: + """ + Analog to ``ControlGetText`` + + :param control: + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -380,6 +415,19 @@ async def control_get_position( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Position, AsyncFutureResult[Position]]: + """ + Analog to ``ControlGetPos`` + + :param control: + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -438,7 +486,7 @@ async def control_send( blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: """ - Analog for ControlSend + Analog for ``ControlSend`` Reference: https://www.autohotkey.com/docs/commands/ControlSend.htm @@ -582,6 +630,19 @@ async def list_windows( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: + """ + Enumerate all windows matching the criteria. + + + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ args = self._format_win_args( title=title, text=text, @@ -606,6 +667,13 @@ async def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = N async def get_mouse_position( self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True ) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: + """ + Analog for ``MouseGetPos`` + + :param coord_mode: + :param blocking: + :return: + """ if coord_mode: args = [str(coord_mode)] else: @@ -615,6 +683,11 @@ async def get_mouse_position( @property def mouse_position(self) -> AsyncPropertyReturnTupleIntInt: + """ + Convenience property for ``get_mouse_position`` + + :return: + """ warnings.warn( # unasync: remove _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('mouse_position'), category=DeprecationWarning, stacklevel=2 ) @@ -622,6 +695,12 @@ def mouse_position(self) -> AsyncPropertyReturnTupleIntInt: @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 + :return: + """ 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) @@ -645,6 +724,16 @@ async def mouse_move( relative: bool = False, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for ``MouseMove`` + + :param x: + :param y: + :param speed: + :param relative: + :param blocking: + :return: + """ if relative and (x is None or y is None): x = x or 0 y = y or 0 @@ -678,12 +767,23 @@ async def get_active_window(self, blocking: bool = True) -> Union[Optional[Async async def get_active_window( self, blocking: bool = True ) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: + """ + Gets the currently active window. + + :param blocking: + :return: + """ return await self.win_get( title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking ) @property def active_window(self) -> AsyncPropertyReturnOptionalAsyncWindow: + """ + Gets the currently active window + + :return: + """ warnings.warn( # unasync: remove _PROPERTY_DEPRECATION_WARNING_MESSAGE.format('active_window'), category=DeprecationWarning, stacklevel=2 ) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index f3e81ed8..442e203e 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -143,7 +143,10 @@ def add_hotkey( - 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 hotkey: an instance of ahk.hotkey.Hotkey + :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: @@ -168,7 +171,11 @@ def add_hotstring( - 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 hotstring: an instance of ahk.hotkey.Hotstring + :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: @@ -265,6 +272,21 @@ def control_click( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: + """ + + :param button: the mouse button to use + :param click_count: how many times to click + :param options: options -- same meaning as in AutoHotkey + :param control: the control to click + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ 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: @@ -321,6 +343,19 @@ def control_get_text( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[str, FutureResult[str]]: + """ + Analog to ``ControlGetText`` + + :param control: + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -376,6 +411,19 @@ def control_get_position( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Position, FutureResult[Position]]: + """ + Analog to ``ControlGetPos`` + + :param control: + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: @@ -434,7 +482,7 @@ def control_send( blocking: bool = True, ) -> Union[None, FutureResult[None]]: """ - Analog for ControlSend + Analog for ``ControlSend`` Reference: https://www.autohotkey.com/docs/commands/ControlSend.htm @@ -578,6 +626,19 @@ def list_windows( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[List[Window], FutureResult[List[Window]]]: + """ + Enumerate all windows matching the criteria. + + + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ args = self._format_win_args( title=title, text=text, @@ -602,6 +663,13 @@ def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, * def get_mouse_position( self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True ) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: + """ + Analog for ``MouseGetPos`` + + :param coord_mode: + :param blocking: + :return: + """ if coord_mode: args = [str(coord_mode)] else: @@ -611,10 +679,21 @@ def get_mouse_position( @property def mouse_position(self) -> SyncPropertyReturnTupleIntInt: + """ + Convenience property for ``get_mouse_position`` + + :return: + """ 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 + :return: + """ x, y = new_position return self.mouse_move(x=x, y=y, speed=0, relative=False) @@ -637,6 +716,16 @@ def mouse_move( relative: bool = False, blocking: bool = True, ) -> Union[None, FutureResult[None]]: + """ + Analog for ``MouseMove`` + + :param x: + :param y: + :param speed: + :param relative: + :param blocking: + :return: + """ if relative and (x is None or y is None): x = x or 0 y = y or 0 @@ -670,12 +759,23 @@ def get_active_window(self, blocking: bool = True) -> Union[Optional[Window], Fu def get_active_window( self, blocking: bool = True ) -> Union[Optional[Window], FutureResult[Optional[Window]]]: + """ + Gets the currently active window. + + :param blocking: + :return: + """ return self.win_get( title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking ) @property def active_window(self) -> SyncPropertyReturnOptionalAsyncWindow: + """ + Gets the currently active window + + :return: + """ return self.get_active_window() def find_windows( From 3e592315397b69ad9bc2ad13bb5996d4004238c2 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 19:45:50 -0700 Subject: [PATCH 349/588] make resolve_button non-public --- ahk/_async/engine.py | 4 ++-- ahk/_sync/engine.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index e64cadac..25222685 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -99,7 +99,7 @@ _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: +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: @@ -2498,7 +2498,7 @@ async def click( 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) + button = _resolve_button(button) if relative: r = 'Rel' diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 442e203e..2dc5affe 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -95,7 +95,7 @@ _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: +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: @@ -2487,7 +2487,7 @@ def click( 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) + button = _resolve_button(button) if relative: r = 'Rel' From e37b7ba18318e1e364da2abacdc19cb74e525eef Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 19:47:27 -0700 Subject: [PATCH 350/588] add docs --- docs/Makefile | 20 ++ docs/README.md | 496 +++++++++++++++++++++++++++++++++++++++ docs/api/async.rst | 7 + docs/api/index.rst | 14 ++ docs/api/methods.md | 235 +++++++++++++++++++ docs/api/sync.rst | 10 + docs/conf.py | 35 +++ docs/docrequirements.txt | 6 + docs/index.rst | 29 +++ docs/make.bat | 35 +++ docs/quickstart.rst | 38 +++ 11 files changed, 925 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/api/async.rst create mode 100644 docs/api/index.rst create mode 100644 docs/api/methods.md create mode 100644 docs/api/sync.rst create mode 100644 docs/conf.py create mode 100644 docs/docrequirements.txt create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/quickstart.rst 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 index e69de29b..5ddca827 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,496 @@ +# ahk + +A fully typed Python wrapper around AHK. + +[![Docs](https://readthedocs.org/projects/ahk/badge/?version=latest)](https://ahk.readthedocs.io/en/latest/?badge=latest) +[![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) +[![Downloads](https://pepy.tech/badge/ahk)](https://pepy.tech/project/ahk) + +# Installation + +``` +pip install ahk +``` +Requires Python 3.8+ + +See also [Non-Python dependencies](#non-python-dependencies) + +# 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/master/docs/_static/ahk.gif) + +# Examples + +Non-exhaustive examples of some functions available with this package. Full documentation coming soon! + +## 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) +``` + +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 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 +``` + +## 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 +ahk.key_wait('a', timeout=3) # Wait up to 3 seconds for the "a" key to be pressed. NOTE: This throws + # a TimeoutError if the key isn't pressed within the timeout window +``` + +## 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 + +win.send('hello') # Send keys directly to the window (does not need focus!) +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 windwow +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.always_on_top = 'On' # Make the window always on top +# or +win.set_always_on_top('On') + +for window in ahk.list_windows(): + 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() + ... +``` + +## 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 🌎' +``` + +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 +``` + + +## 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 + +```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 +``` + +## 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 +``` + +## 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. + +## 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 + +TBD + + + + + +# 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. + +Note: this should be AutoHotkey V1. AutoHotkey V2 is not yet supported. + +A convenient way to do this is to install the `binary` extra + +``` +pip install "ahk[binary]" +``` + + +You can also use the `AHK_PATH` environment variable to specify the executable location. + +```console +set AHK_PATH=C:\Path\To\AutoHotkey.exe +``` + +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. + +[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/api/async.rst b/docs/api/async.rst new file mode 100644 index 00000000..0460f35f --- /dev/null +++ b/docs/api/async.rst @@ -0,0 +1,7 @@ +Async API +========= + + +.. autoclass:: ahk._async.engine.AsyncAHK + :members: + :undoc-members: diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 00000000..a0e547c6 --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,14 @@ +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: 3 + :caption: Contents: + :glob: + + * diff --git a/docs/api/methods.md b/docs/api/methods.md new file mode 100644 index 00000000..dc091a3f --- /dev/null +++ b/docs/api/methods.md @@ -0,0 +1,235 @@ +# Available Methods + +Most useful methods from autohotkey are implemented in this wrapper. However, not everything is implemented (yet). This +page can serve as a quick reference of AutoHotkey methods that are implemented in the wrapper. + +(Coming soon: links to equivalent Python method(s)) + +### Mouse and Keyboard + +| AutoHotkey Command | Status | Notes | +|----------------------------------------------------------------------------------------------|-----------------|-------------------------------------------------------------------------| +| [#KeyHistory](https://www.autohotkey.com/docs/commands/_KeyHistory.htm) | Not Implemented | | +| [BlockInput](https://www.autohotkey.com/docs/commands/BlockInput.htm) | Implemented | | +| [Click](https://www.autohotkey.com/docs/commands/Click.htm) | Implemented | | +| [ControlClick](https://www.autohotkey.com/docs/commands/ControlClick.htm) | Implemented | | +| [ControlSend[Raw]](https://www.autohotkey.com/docs/commands/ControlSend.htm) | Implemented | | +| [CoordMode](https://www.autohotkey.com/docs/commands/CoordMode.htm) | Implemented | | +| [GetKeyName()](https://www.autohotkey.com/docs/commands/GetKey.htm) | Not Implemented | | +| [GetKeySC()](https://www.autohotkey.com/docs/commands/GetKey.htm) | Not Implemented | | +| [GetKeyState](https://www.autohotkey.com/docs/commands/GetKeyState.htm#command) | Implemented | | +| [GetKeyVK()](https://www.autohotkey.com/docs/commands/GetKey.htm) | Not Implemented | | +| [KeyHistory](https://www.autohotkey.com/docs/commands/KeyHistory.htm) | Not Implemented | | +| [KeyWait](https://www.autohotkey.com/docs/commands/KeyWait.htm) | Implemented | | +| [Input](https://www.autohotkey.com/docs/commands/Input.htm) | Not Implemented | Use python `input()` instead | +| [InputHook()](https://www.autohotkey.com/docs/commands/InputHook.htm) | Not Implemented | | +| [MouseClick](https://www.autohotkey.com/docs/commands/MouseClick.htm) | Implemented | | +| [MouseClickDrag](https://www.autohotkey.com/docs/commands/MouseClickDrag.htm) | Implemented | | +| [MouseGetPos](https://www.autohotkey.com/docs/commands/MouseGetPos.htm) | Implemented | | +| [MouseMove](https://www.autohotkey.com/docs/commands/MouseMove.htm) | Implemented | | +| [SendLevel](https://www.autohotkey.com/docs/commands/SendLevel.htm) | Implemented | | +| [SendMode](https://www.autohotkey.com/docs/commands/SendMode.htm) | Not Implemented | | +| [SetCapsLockState](https://www.autohotkey.com/docs/commands/SetNumScrollCapsLockState.htm) | Implemented | | +| [SetDefaultMouseSpeed](https://www.autohotkey.com/docs/commands/SetDefaultMouseSpeed.htm) | Implemented | Speed is controlled by the `speed` keyword argument of relevant methods | +| [SetKeyDelay](https://www.autohotkey.com/docs/commands/SetKeyDelay.htm) | Implemented | Delay is controlled by the `delay` keyword argument of relevant methods | +| [SetMouseDelay](https://www.autohotkey.com/docs/commands/SetMouseDelay.htm) | Not Implemented | Delays between mouse movements can be controlled in Python code | +| [SetNumLockState](https://www.autohotkey.com/docs/commands/SetNumScrollCapsLockState.htm) | Implemented | | +| [SetScrollLockState](https://www.autohotkey.com/docs/commands/SetNumScrollCapsLockState.htm) | Implemented | | +| [SetStoreCapsLockMode](https://www.autohotkey.com/docs/commands/SetStoreCapslockMode.htm) | Not Implemented | note | + + +### Hotkeys + +| AutoHotkey Command | Status | Notes | +|-----------------------------------------------------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------| +| [Hotkeys](https://www.autohotkey.com/docs/Hotkeys.htm) | Implemented | Before 1.0, callbacks were only supported as Autohotkey Scripts
In 1.0 and later, callbacks are supported as Python functions | +| [Hotstrings](https://www.autohotkey.com/docs/Hotstrings.htm) | Implemented | Available in 1.0+ | +| [Suspend](https://www.autohotkey.com/docs/commands/Suspend.htm) | Implemented* | Use stop_hotkeys and start_hotkeys to enable/disable hotkeys | + + + +### ClipBoard + +| AutoHotkey Command | Status | Notes | +|------------------------------------------------------------------------------------------------|-------------|-------| +| [OnClipboardChange()](https://www.autohotkey.com/docs/commands/OnClipboardChange.htm#function) | Implemented | | +| [Clipboard/ClipboardAll](https://www.autohotkey.com/docs/misc/Clipboard.htm#ClipboardAll) | Implemented | | +| [ClipWAit](https://www.autohotkey.com/docs/v1/lib/ClipWait.htm) | Implemented | note | + + + +### Screen/Image + +| AutoHotkey Command | Status | Notes | +|----------------------------------------------------------------------------------------------|-------------|-------| +| [ImageSearch](https://www.autohotkey.com/docs/commands/ImageSearch.htm) | Implemented | | +| [PixelGetColor](https://www.autohotkey.com/docs/commands/PixelGetColor.htm) | Implemented | | +| [PixelSearch](https://www.autohotkey.com/docs/commands/PixelSearch.htm) | Implemented | note | + + +### Registry + + + +| AutoHotkey Command | Status | Notes | +|----------------------------------------------------------------------------------------------|-----------------|-------| +| [RegDelete](https://www.autohotkey.com/docs/commands/RegDelete.htm) | Implemented | | +| [RegRead](https://www.autohotkey.com/docs/commands/RegRead.htm) | Implemented | | +| [RegWrite](https://www.autohotkey.com/docs/commands/RegWrite.htm) | Implemented | | +| [SetRegView](https://www.autohotkey.com/docs/commands/SetRegView.htm) | Not Implemented | note | + + + +### Window + + +#### Window | Controls + +| AutoHotkey Command | Status | Notes | +|----------------------------------------------------------------------------------------------|-----------------|-------| +| [Control](https://www.autohotkey.com/docs/commands/Control.htm) | Implemented | | +| [ControlClick](https://www.autohotkey.com/docs/commands/ControlClick.htm) | Implemented | | +| [ControlFocus](https://www.autohotkey.com/docs/commands/ControlFocus.htm) | Not Implemented | | +| [ControlGet](https://www.autohotkey.com/docs/commands/ControlGet.htm) | Implemented | | +| [ControlGetFocus](https://www.autohotkey.com/docs/commands/ControlGetFocus.htm) | Not Implemented | | +| [ControlGetPos](https://www.autohotkey.com/docs/commands/ControlGetPos.htm) | Implemented | | +| [ControlGetText](https://www.autohotkey.com/docs/commands/ControlGetText.htm) | Implemented | | +| [ControlMove](https://www.autohotkey.com/docs/commands/ControlMove.htm) | Implemented | | +| [ControlSend[Raw]](https://www.autohotkey.com/docs/commands/ControlSend.htm) | Implemented | | +| [ControlSetText](https://www.autohotkey.com/docs/commands/ControlSetText.htm) | Implemented | | +| [Menu](https://www.autohotkey.com/docs/commands/Menu.htm) | Not Implemented | | +| [PostMessage/SendMessage](https://www.autohotkey.com/docs/commands/PostMessage.htm) | Not Implemented | | +| [SetControlDelay](https://www.autohotkey.com/docs/commands/SetControlDelay.htm) | Not Implemented | | +| [WinMenuSelectItem](https://www.autohotkey.com/docs/commands/WinMenuSelectItem.htm) | Not Implemented | note | + + +#### Window | Groups + +| AutoHotkey Command | Status | Notes | +|----------------------------------------------------------------------------------------------|-----------------|-------| +| [GroupActivate](https://www.autohotkey.com/docs/commands/GroupActivate.htm) | | | +| [GroupAdd](https://www.autohotkey.com/docs/commands/GroupAdd.htm) | | | +| [GroupClose](https://www.autohotkey.com/docs/commands/GroupClose.htm) | | | +| [GroupDeactivate](https://www.autohotkey.com/docs/commands/GroupDeactivate.htm) | | note | + +### Window functions + +| AutoHotkey Command | Status | Notes | +|-----------------------------------------------------------------------------------------|-----------------|---------------------------------------------------| +| [#WinActivateForce](https://www.autohotkey.com/docs/commands/_WinActivateForce.htm) | Implemented | Any directive can be added to the daemon | +| [DetectHiddenText](https://www.autohotkey.com/docs/commands/DetectHiddenText.htm) | Planned | | +| [DetectHiddenWindows](https://www.autohotkey.com/docs/commands/DetectHiddenWindows.htm) | Implemented | | +| [IfWin[Not]Active](https://www.autohotkey.com/docs/commands/IfWinActive.htm) | Not Implemented | Use Python `if` with `win_active`/`win.is_active` | +| [IfWin[Not]Exist](https://www.autohotkey.com/docs/commands/IfWinExist.htm) | Not Implemented | Use Python `if` with `win_exists`/`win.exists` | +| [SetTitleMatchMode](https://www.autohotkey.com/docs/commands/SetTitleMatchMode.htm) | Implemented | | +| [SetWinDelay](https://www.autohotkey.com/docs/commands/SetWinDelay.htm) | Not Implemented | Delays can be controlled in Python code | +| [StatusBarGetText](https://www.autohotkey.com/docs/commands/StatusBarGetText.htm) | Not Implemented | | +| [StatusBarWait](https://www.autohotkey.com/docs/commands/StatusBarWait.htm) | Not Implemented | | +| [WinActivate](https://www.autohotkey.com/docs/commands/WinActivate.htm) | Implemented | | +| [WinActivateBottom](https://www.autohotkey.com/docs/commands/WinActivateBottom.htm) | Implemented | | +| [WinActive()](https://www.autohotkey.com/docs/commands/WinActive.htm) | Implemented | | +| [WinClose](https://www.autohotkey.com/docs/commands/WinClose.htm) | Implemented | | +| [WinExist()](https://www.autohotkey.com/docs/commands/WinExist.htm) | Implemented | | +| [WinGet](https://www.autohotkey.com/docs/commands/WinGet.htm) | Implemented | | +| [WinGetActiveStats](https://www.autohotkey.com/docs/commands/WinGetActiveStats.htm) | Not Implemented | | +| [WinGetActiveTitle](https://www.autohotkey.com/docs/commands/WinGetActiveTitle.htm) | Not Implemented | | +| [WinGetClass](https://www.autohotkey.com/docs/commands/WinGetClass.htm) | Implemented | | +| [WinGetPos](https://www.autohotkey.com/docs/commands/WinGetPos.htm) | Implemented | | +| [WinGetText](https://www.autohotkey.com/docs/commands/WinGetText.htm) | Implemented | | +| [WinGetTitle](https://www.autohotkey.com/docs/commands/WinGetTitle.htm) | Implemented | | +| [WinHide](https://www.autohotkey.com/docs/commands/WinHide.htm) | Implemented | | +| [WinKill](https://www.autohotkey.com/docs/commands/WinKill.htm) | Implemented | | +| [WinMaximize](https://www.autohotkey.com/docs/commands/WinMaximize.htm) | Implemented | | +| [WinMinimize](https://www.autohotkey.com/docs/commands/WinMinimize.htm) | Implemented | | +| [WinMinimizeAll[Undo]](https://www.autohotkey.com/docs/commands/WinMinimizeAll.htm) | Not Implemented | | +| [WinMove](https://www.autohotkey.com/docs/commands/WinMove.htm) | Implemented | | +| [WinRestore](https://www.autohotkey.com/docs/commands/WinRestore.htm) | Implemented | | +| [WinSet](https://www.autohotkey.com/docs/commands/WinSet.htm) | Implemented | | +| [WinSetTitle](https://www.autohotkey.com/docs/commands/WinSetTitle.htm) | Implemented | | +| [WinShow](https://www.autohotkey.com/docs/commands/WinShow.htm) | Implemented | | +| [WinWait](https://www.autohotkey.com/docs/commands/WinWait.htm) | Implemented | | +| [WinWait[Not]Active](https://www.autohotkey.com/docs/commands/WinWaitActive.htm) | Implemented | | +| [WinWaitClose](https://www.autohotkey.com/docs/commands/WinWaitClose.htm) | Implemented | note | + + + + +### Sound + +| AutoHotkey Command | Status | Notes | +|----------------------------------------------------------------------------------------------|-----------------|-------| +| [SoundBeep](https://www.autohotkey.com/docs/commands/SoundBeep.htm) | Implemented | | +| [SoundGet](https://www.autohotkey.com/docs/commands/SoundGet.htm) | Implemented | | +| [SoundGetWaveVolume](https://www.autohotkey.com/docs/commands/SoundGetWaveVolume.htm) | Not Implemented | | +| [SoundPlay](https://www.autohotkey.com/docs/commands/SoundPlay.htm) | Implemented | | +| [SoundSet](https://www.autohotkey.com/docs/commands/SoundSet.htm) | Implemented | | +| [SoundSetWaveVolume](https://www.autohotkey.com/docs/commands/SoundSetWaveVolume.htm) | Not Implemented | note | + + + +### GUI + +GUI methods are largely unimplmented, except `ToolTip` and `TrayTip`. +We recommend using one of the many Python GUI libraries, such as [easygui](https://github.com/robertlugg/easygui), [pysimplegui](https://www.pysimplegui.org/en/latest/) or similar. + + +| AutoHotkey Command | Status | Notes | +|-------------------------------------------------------------------------------|-----------------|-------| +| [Gui](https://www.autohotkey.com/docs/commands/Gui.htm) | Not Implemented | | +| [Gui control types](https://www.autohotkey.com/docs/commands/GuiControls.htm) | Not Implemented | | +| [GuiControl](https://www.autohotkey.com/docs/commands/GuiControl.htm) | Not Implemented | | +| [GuiControlGet](https://www.autohotkey.com/docs/commands/GuiControlGet.htm) | Not Implemented | | +| [Gui ListView control](https://www.autohotkey.com/docs/commands/ListView.htm) | Not Implemented | | +| [Gui TreeView control](https://www.autohotkey.com/docs/commands/TreeView.htm) | Not Implemented | | +| [IfMsgBox](https://www.autohotkey.com/docs/commands/IfMsgBox.htm) | Not Implemented | | +| [InputBox](https://www.autohotkey.com/docs/commands/InputBox.htm) | Not Implemented | | +| [LoadPicture()](https://www.autohotkey.com/docs/commands/LoadPicture.htm) | Not Implemented | | +| [Menu](https://www.autohotkey.com/docs/commands/Menu.htm) | Not Implemented | | +| [MenuGetHandle()](https://www.autohotkey.com/docs/commands/MenuGetHandle.htm) | Not Implemented | | +| [MenuGetName()](https://www.autohotkey.com/docs/commands/MenuGetName.htm) | Not Implemented | | +| [MsgBox](https://www.autohotkey.com/docs/commands/MsgBox.htm) | Not Implemented | | +| [OnMessage()](https://www.autohotkey.com/docs/commands/OnMessage.htm) | Not Implemented | | +| [Progress](https://www.autohotkey.com/docs/commands/Progress.htm) | Not Implemented | | +| [SplashImage](https://www.autohotkey.com/docs/commands/Progress.htm) | Not Implemented | | +| [SplashTextOn/Off](https://www.autohotkey.com/docs/commands/SplashTextOn.htm) | Not Implemented | | +| [ToolTip](https://www.autohotkey.com/docs/commands/ToolTip.htm) | Implemented | | +| [TrayTip](https://www.autohotkey.com/docs/commands/TrayTip.htm) | Implemented | note | + + + +### Directives + +In general, all directives are technically usable, however many do not have applicable context in the Python library. + +Some directives are mentioned in tables above and are omitted from this table. + +| AutoHotkey Command | Notes | +|-----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------| +| [#HotkeyInterval](https://www.autohotkey.com/docs/commands/_HotkeyInterval.htm) | | +| [#HotkeyModifierTimeout](https://www.autohotkey.com/docs/commands/_HotkeyModifierTimeout.htm) | | +| [#Hotstring](https://www.autohotkey.com/docs/commands/_Hotstring.htm) | | +| [#Include[Again]](https://www.autohotkey.com/docs/commands/_Include.htm) | Using this directive is strongly discouraged as it is **very** likely to cause issues. Use with extreme caution. | +| [#InputLevel](https://www.autohotkey.com/docs/commands/_InputLevel.htm) | | +| [#KeyHistory](https://www.autohotkey.com/docs/commands/_KeyHistory.htm) | | +| [#MaxHotkeysPerInterval](https://www.autohotkey.com/docs/commands/_MaxHotkeysPerInterval.htm) | | +| [#MaxMem](https://www.autohotkey.com/docs/commands/_MaxMem.htm) | | +| [#MaxThreads](https://www.autohotkey.com/docs/commands/_MaxThreads.htm) | | +| [#MaxThreadsBuffer](https://www.autohotkey.com/docs/commands/_MaxThreadsBuffer.htm) | | +| [#MaxThreadsPerHotkey](https://www.autohotkey.com/docs/commands/_MaxThreadsPerHotkey.htm) | Hotkey callbacks are run in Python, so this largely won't have any significant effect | +| [#MenuMaskKey](https://www.autohotkey.com/docs/commands/_MenuMaskKey.htm) | | +| [#NoEnv](https://www.autohotkey.com/docs/commands/_NoEnv.htm) | | +| [#NoTrayIcon](https://www.autohotkey.com/docs/commands/_NoTrayIcon.htm) | If you use hotkeys or hotstrings, you probably also want to configure this as a hotkey transport option | +| [#Persistent](https://www.autohotkey.com/docs/commands/_Persistent.htm) | This is on by default in scripts run by this library | +| [#Requires](https://www.autohotkey.com/docs/commands/_Requires.htm) | | +| [#SingleInstance](https://www.autohotkey.com/docs/commands/_SingleInstance.htm) | This directive is provided by default (SingleInstance Off for the main thread) | +| [#UseHook](https://www.autohotkey.com/docs/commands/_UseHook.htm) | | +| [#Warn](https://www.autohotkey.com/docs/commands/_Warn.htm) | Not relevant for this library | +| [#AllowSameLineComments](https://www.autohotkey.com/docs/commands/_AllowSameLineComments.htm) | Not relevant for this library | +| [#ClipboardTimeout](https://www.autohotkey.com/docs/commands/_ClipboardTimeout.htm) | Not relevant for this library | +| [#CommentFlag](https://www.autohotkey.com/docs/commands/_CommentFlag.htm) | Not relevant for this library | +| [#ErrorStdOut](https://www.autohotkey.com/docs/commands/_ErrorStdOut.htm) | Not relevant for this library | +| [#EscapeChar](https://www.autohotkey.com/docs/commands/_EscapeChar.htm) | Not relevant for this library | +| [#InstallKeybdHook](https://www.autohotkey.com/docs/commands/_InstallKeybdHook.htm) | Not relevant for this library | +| [#InstallMouseHook](https://www.autohotkey.com/docs/commands/_InstallMouseHook.htm) | Not relevant for this library | +| [#If](https://www.autohotkey.com/docs/commands/_If.htm) | Not relevant for this library | +| [#IfTimeout](https://www.autohotkey.com/docs/commands/_IfTimeout.htm) | Not relevant for this library | diff --git a/docs/api/sync.rst b/docs/api/sync.rst new file mode 100644 index 00000000..384e5da6 --- /dev/null +++ b/docs/api/sync.rst @@ -0,0 +1,10 @@ +Sync API +======== + +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. + +.. autoclass:: ahk._sync.engine.AHK + :members: + :undoc-members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..fbd44731 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,35 @@ +# 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'] diff --git a/docs/docrequirements.txt b/docs/docrequirements.txt new file mode 100644 index 00000000..91cc7b7e --- /dev/null +++ b/docs/docrequirements.txt @@ -0,0 +1,6 @@ +sphinx<3 +mistune<2 +sphinx-rtd-theme +sphinx-autodoc-typehints<1.11 +m2r +jinja2 diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..736ce73f --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,29 @@ +.. 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 + + + + +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/ From cec7cfb1f3461b682c80723cefa80d9ab5a96554 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 19:54:31 -0700 Subject: [PATCH 351/588] add readthedocs config (boo hiss) --- docs/.readthedocs.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/.readthedocs.yaml 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 From a589e6a615c995dfb63f777e4e9511c18260c136 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 20:02:04 -0700 Subject: [PATCH 352/588] unpin requirements --- docs/docrequirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docrequirements.txt b/docs/docrequirements.txt index 91cc7b7e..bb4e7047 100644 --- a/docs/docrequirements.txt +++ b/docs/docrequirements.txt @@ -1,6 +1,6 @@ -sphinx<3 +sphinx mistune<2 sphinx-rtd-theme -sphinx-autodoc-typehints<1.11 +sphinx-autodoc-typehints m2r jinja2 From b3333c9babbb57a8fe4470ff01cecd1126ef2bd7 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 20:03:35 -0700 Subject: [PATCH 353/588] use sphinx 6 --- docs/docrequirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docrequirements.txt b/docs/docrequirements.txt index bb4e7047..eb4a375c 100644 --- a/docs/docrequirements.txt +++ b/docs/docrequirements.txt @@ -1,4 +1,4 @@ -sphinx +sphinx<7 mistune<2 sphinx-rtd-theme sphinx-autodoc-typehints From bdcb54e6d20d63532d5156a7910270adf3139057 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 20:06:51 -0700 Subject: [PATCH 354/588] docs --- docs/api/async.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/async.rst b/docs/api/async.rst index 0460f35f..d456e80b 100644 --- a/docs/api/async.rst +++ b/docs/api/async.rst @@ -1,6 +1,7 @@ Async API ========= +The async API is mostly identical to the sync API. .. autoclass:: ahk._async.engine.AsyncAHK :members: From aa4d6bbec309b1f8591be66da4f7d1a406cfea39 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 20:09:45 -0700 Subject: [PATCH 355/588] tidy index --- docs/api/index.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/api/index.rst b/docs/api/index.rst index a0e547c6..863ba819 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -7,8 +7,9 @@ about the programming interface. This is largely auto-generated documentation. ``ahk`` .. toctree:: - :maxdepth: 3 + :maxdepth: 1 :caption: Contents: - :glob: - * + sync + async + methods From 059e53cacc0dad881d717d441255557b6eff1736 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 20:35:00 -0700 Subject: [PATCH 356/588] add deprecation doc --- ahk/_async/engine.py | 3 +++ ahk/_sync/engine.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 25222685..09beca24 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -751,6 +751,9 @@ async def mouse_move( return resp async def a_run_script(self, *args: Any, **kwargs: Any) -> Union[str, AsyncFutureResult[str]]: + """ + Deprecated. Use ``run_script`` instead. + """ warnings.warn('a_run_script is deprecated. Use run_script instead.', DeprecationWarning, stacklevel=2) return await self.run_script(*args, **kwargs) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 2dc5affe..e5b43051 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -743,6 +743,9 @@ def mouse_move( return resp def a_run_script(self, *args: Any, **kwargs: Any) -> Union[str, FutureResult[str]]: + """ + Deprecated. Use ``run_script`` instead. + """ warnings.warn('a_run_script is deprecated. Use run_script instead.', DeprecationWarning, stacklevel=2) return self.run_script(*args, **kwargs) From 8d5688ecbdccb922d95ead30108439596f63c677 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 20:35:50 -0700 Subject: [PATCH 357/588] docs --- docs/api/async.rst | 4 ++++ docs/api/sync.rst | 4 ++++ docs/conf.py | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/docs/api/async.rst b/docs/api/async.rst index d456e80b..a307fdd6 100644 --- a/docs/api/async.rst +++ b/docs/api/async.rst @@ -6,3 +6,7 @@ The async API is mostly identical to the sync API. .. autoclass:: ahk._async.engine.AsyncAHK :members: :undoc-members: + +.. autoclass:: ahk._async.transport.AsyncFutureResult + :members: + :undoc-members: diff --git a/docs/api/sync.rst b/docs/api/sync.rst index 384e5da6..dee4cc36 100644 --- a/docs/api/sync.rst +++ b/docs/api/sync.rst @@ -8,3 +8,7 @@ The sync API is the default API described in most of the README. .. autoclass:: ahk._sync.engine.AHK :members: :undoc-members: + +.. autoclass:: ahk._sync.transport.FutureResult + :members: + :undoc-members: diff --git a/docs/conf.py b/docs/conf.py index fbd44731..354ab211 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,3 +33,9 @@ html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] + +autodoc_default_options = { + 'member-order': 'bysource', + 'undoc-members': True, + 'special-members': '__init__', +} From 779b0a9443e4fa773af0d4b0aab9673cea4a8776 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 21:24:45 -0700 Subject: [PATCH 358/588] add release workflow --- .github/workflows/release.yaml | 37 + ahk/_sync/engine.py | 3222 ++++++++++++++++++++++++++++++++ setup.cfg | 3 - 3 files changed, 3259 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/release.yaml 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/ahk/_sync/engine.py b/ahk/_sync/engine.py index e5b43051..8b4abf2c 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -24,3217 +24,6439 @@ from .._utils import type_escape from ..directives import Directive + + if sys.version_info < (3, 10): + from typing_extensions import TypeAlias + else: + from typing import TypeAlias + + from ..keys import Key + from .transport import DaemonProcessTransport + from .transport import FutureResult + from .transport import Transport + from .window import Control + from .window import Window + from ahk.message import Position + + + sleep = time.sleep + + + SyncFilterFunc: TypeAlias = Callable[[Window], bool] + + 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', + ], + ] + + + SyncPropertyReturnTupleIntInt: TypeAlias = Tuple[int, int] + + + 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 + + + class AHK: + def __init__( + self, + *, + TransportClass: Optional[Type[Transport]] = None, + directives: Optional[list[Directive | Type[Directive]]] = None, + executable_path: str = '', + ): + if TransportClass is None: + TransportClass = DaemonProcessTransport + assert TransportClass is not None + transport = TransportClass(executable_path=executable_path, directives=directives) + self._transport: Transport = transport + + 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 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 set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> None: + """ + Sets the default title match mode + + Has no effect for `Window`/`Control` instance methods (these always use hwnd) + + 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: + args = [str(target), str(relative_to)] + self._transport.function_call('AHKSetCoordMode', args) + return None + + def get_coord_mode(self, target: CoordModeTargets) -> str: + args = [str(target)] + resp = self._transport.function_call('AHKGetCoordMode', args) + 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]]: + """ + + :param button: the mouse button to use + :param click_count: how many times to click + :param options: options -- same meaning as in AutoHotkey + :param control: the control to click + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ + 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('On') + elif detect_hidden_windows is False: + args.append('Off') + 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 to ``ControlGetText`` + + :param control: + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ + args = [control, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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`` + + :param control: + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ + args = [control, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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`` + + Reference: https://www.autohotkey.com/docs/commands/ControlSend.htm + + :param keys: + :param control: + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ + args = [control, keys, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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 + + """ + return self._transport.stop_hotkeys() + + def set_detect_hidden_windows(self, value: bool) -> None: + """ + Analog for AutoHotkey's `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('On') + else: + args.append('Off') + 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('On') + elif detect_hidden_windows is False: + args.append('Off') + 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]) -> Union[List[Window], 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. + + + :param title: + :param text: + :param exclude_title: + :param exclude_text: + :param title_match_mode: + :param detect_hidden_windows: + :param blocking: + :return: + """ + 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]) -> Tuple[int, int]: ... + @overload + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[False]) -> FutureResult[Tuple[int, int]]: ... + @overload + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None) -> Tuple[int, int]: ... + @overload + def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... + # fmt: on + def get_mouse_position( + self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True + ) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: + """ + Analog for ``MouseGetPos`` + + :param coord_mode: + :param blocking: + :return: + """ + 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 ``get_mouse_position`` + + :return: + """ + + 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 + :return: + """ + + 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) -> 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) -> 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, ) -> 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) -> 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, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + """ + Analog for ``MouseMove`` + + :param x: + :param y: + :param speed: + :param relative: + :param blocking: + :return: + """ + 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') + 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 ``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, blocking: bool = True + ) -> Union[Optional[Window], FutureResult[Optional[Window]]]: + """ + Gets the currently active window. + + :param blocking: + :return: + """ + return self.win_get( + title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking + ) + + @property + def active_window(self) -> SyncPropertyReturnOptionalAsyncWindow: + """ + Gets the currently active window + + :return: + """ + + 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 match_mode=3 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: + 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]]: + 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]]: + 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]]: + 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], + ]: + 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) + 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]]: + 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] = None, logical_state: bool = False, released: bool = False) -> int: ... + @overload + def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... + @overload + def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> FutureResult[int]: ... + @overload + def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[int, FutureResult[int]]: ... + # fmt: on + def key_wait( + self, + key_name: str, + *, + timeout: Optional[int] = None, + logical_state: bool = False, + released: bool = False, + blocking: bool = True, + ) -> Union[int, FutureResult[int]]: + options = '' + if not released: + options += 'D' + if logical_state: + options += 'L' + if timeout: + options += f'T{timeout}' + args = [key_name] + if options: + args.append(options) + + resp = self._transport.function_call('AHKKeyWait', args) + return resp + + def run_script( + self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None + ) -> Union[str, FutureResult[str]]: + return self._transport.run_script(script_text_or_path, blocking=blocking, timeout=timeout) + + def set_send_level(self, level: int) -> None: + 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: + 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) -> None: ... + @overload + def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = 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, 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, 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, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + 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 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]]: + 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]]: + 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]]: + 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]]: + 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[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + @overload + def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + @overload + def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def set_capslock_state( + self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: + 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}' + ) + args.append(str(state)) + + resp = self._transport.function_call('AHKSetCapsLockState', 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]]: + args = [str(device_number), str(value)] + return self._transport.function_call('AHKSetVolume', args, blocking=blocking) + + # fmt: off + @overload + def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_traytip(self, title: str, text: str, second: float = 1.0, 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: float = 1.0, + type_id: int = 1, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def show_error_traytip( + self, + title: str, + text: str, + second: float = 1.0, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def show_info_traytip( + self, + title: str, + text: str, + second: float = 1.0, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + @overload + def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + @overload + def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + # fmt: on + def show_warning_traytip( + self, + title: str, + text: str, + second: float = 1.0, + *, + silent: bool = False, + large_icon: bool = False, + blocking: bool = True, + ) -> Union[None, FutureResult[None]]: + 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: + 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('') + self._transport.function_call('AHKShowToolTip', args) + + def hide_tooltip(self, which: int = 1) -> None: + self.show_tooltip(which=which) + + # 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]]: + 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]]: + 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]]: + 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]]: + 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, 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, 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, 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, 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]]]: ... + # 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]]]: + 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]]: + 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, 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, 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, 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, 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]]]: + 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]]]: + 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]]]: + 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]]]: + 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]]]]: + 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]]: + 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]]]: + 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]]]]: + 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]]: + 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]]: + args = [new_title, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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]]: + args = [str(toggle), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + args = [style, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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]]: + args = [style, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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]]: + args = [options, title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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]]: + args = [str(transparency), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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]]: + args = [str(color), title, text, exclude_title, exclude_text] + if detect_hidden_windows is not None: + if detect_hidden_windows is True: + args.append('On') + elif detect_hidden_windows is False: + args.append('Off') + 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) -> 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) -> 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) -> 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) -> 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, + ) -> 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, + ) + + # 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) -> 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) -> 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) -> 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) -> 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, + ) -> Union[None, FutureResult[None]]: + 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 = '' + args = [str(x), str(y), button, str(click_count), direction or '', r, coord_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[Tuple[int, int]]: ... + @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[Tuple[int, int]]]: ... + @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[Tuple[int, int]]: ... + @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[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: ... + # 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[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: + """ + https://www.autohotkey.com/docs/commands/ImageSearch.htm + """ + + 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) + else: + args.append(image_path) + resp = self._transport.function_call('AHKImageSearch', args, blocking=blocking) + return resp + + def mouse_drag( + self, + x: int, + y: int, + *, + from_position: Optional[Tuple[int, int]] = None, + speed: Optional[int] = None, + button: MouseButton = 1, + relative: Optional[bool] = None, + blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, + ) -> None: + 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) + + self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) + + # 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]]: + 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[Tuple[int, int]]: ... + @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[Tuple[int, int]]: ... + @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[Tuple[int, int]]]: ... + @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[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: ... + # 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[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + 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]]: + return self._transport.function_call('AHKGetClipboard', blocking=blocking) + + def set_clipboard(self, s: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + args = [s] + return self._transport.function_call('AHKSetClipboard', args, blocking=blocking) + + def get_clipboard_all(self, *, blocking: bool = True) -> Union[bytes, FutureResult[bytes]]: + 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]]: + # 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}'] + 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: + 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]]: + args = [str(timeout) if timeout else ''] + if wait_for_any_data: + args.append('1') + 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: + 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]]: + 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]]: + 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) + 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]]: + args = [key_name] + if value_name is not None: + args.append(value_name) + return self._transport.function_call('AHKRegRead', args, blocking=blocking) + + def block_forever(self) -> NoReturn: + while True: + sleep(1) diff --git a/setup.cfg b/setup.cfg index cc2276eb..070b0c79 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,6 +48,3 @@ cmdclass = ahk = py.typed templates/*.ahk - -[bdist_wheel] -universal = True From c34541ddcc752f374fbd02c10d630223b4fac1fc Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 21:26:51 -0700 Subject: [PATCH 359/588] le --- ahk/_sync/engine.py | 3224 +------------------------------------------ 1 file changed, 1 insertion(+), 3223 deletions(-) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 8b4abf2c..e5b43051 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -24,6439 +24,3217 @@ from .._utils import type_escape from ..directives import Directive - - if sys.version_info < (3, 10): - from typing_extensions import TypeAlias - else: - from typing import TypeAlias - - from ..keys import Key - from .transport import DaemonProcessTransport - from .transport import FutureResult - from .transport import Transport - from .window import Control - from .window import Window - from ahk.message import Position - - - sleep = time.sleep - - - SyncFilterFunc: TypeAlias = Callable[[Window], bool] - - 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', - ], - ] - - - SyncPropertyReturnTupleIntInt: TypeAlias = Tuple[int, int] - - - 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 - - - class AHK: - def __init__( - self, - *, - TransportClass: Optional[Type[Transport]] = None, - directives: Optional[list[Directive | Type[Directive]]] = None, - executable_path: str = '', - ): - if TransportClass is None: - TransportClass = DaemonProcessTransport - assert TransportClass is not None - transport = TransportClass(executable_path=executable_path, directives=directives) - self._transport: Transport = transport - - 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 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 set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> None: - """ - Sets the default title match mode - - Has no effect for `Window`/`Control` instance methods (these always use hwnd) - - 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: - args = [str(target), str(relative_to)] - self._transport.function_call('AHKSetCoordMode', args) - return None - - def get_coord_mode(self, target: CoordModeTargets) -> str: - args = [str(target)] - resp = self._transport.function_call('AHKGetCoordMode', args) - 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]]: - """ - - :param button: the mouse button to use - :param click_count: how many times to click - :param options: options -- same meaning as in AutoHotkey - :param control: the control to click - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: - """ - 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('On') - elif detect_hidden_windows is False: - args.append('Off') - 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 to ``ControlGetText`` - - :param control: - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: - """ - args = [control, title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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`` - - :param control: - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: - """ - args = [control, title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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`` - - Reference: https://www.autohotkey.com/docs/commands/ControlSend.htm - - :param keys: - :param control: - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: - """ - args = [control, keys, title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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 - - """ - return self._transport.stop_hotkeys() - - def set_detect_hidden_windows(self, value: bool) -> None: - """ - Analog for AutoHotkey's `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('On') - else: - args.append('Off') - 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('On') - elif detect_hidden_windows is False: - args.append('Off') - 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]) -> Union[List[Window], 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. - - - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: - """ - 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]) -> Tuple[int, int]: ... - @overload - def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[False]) -> FutureResult[Tuple[int, int]]: ... - @overload - def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None) -> Tuple[int, int]: ... - @overload - def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... - # fmt: on - def get_mouse_position( - self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True - ) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: - """ - Analog for ``MouseGetPos`` - - :param coord_mode: - :param blocking: - :return: - """ - 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 ``get_mouse_position`` - - :return: - """ - - 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 - :return: - """ - - 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) -> 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) -> 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, ) -> 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) -> 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, - blocking: bool = True, - ) -> Union[None, FutureResult[None]]: - """ - Analog for ``MouseMove`` - - :param x: - :param y: - :param speed: - :param relative: - :param blocking: - :return: - """ - 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') - 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 ``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, blocking: bool = True - ) -> Union[Optional[Window], FutureResult[Optional[Window]]]: - """ - Gets the currently active window. - - :param blocking: - :return: - """ - return self.win_get( - title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking - ) - - @property - def active_window(self) -> SyncPropertyReturnOptionalAsyncWindow: - """ - Gets the currently active window - - :return: - """ - - 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 match_mode=3 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: - 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]]: - 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]]: - 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]]: - 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], - ]: - 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) - 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]]: - 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] = None, logical_state: bool = False, released: bool = False) -> int: ... - @overload - def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... - @overload - def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> FutureResult[int]: ... - @overload - def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[int, FutureResult[int]]: ... - # fmt: on - def key_wait( - self, - key_name: str, - *, - timeout: Optional[int] = None, - logical_state: bool = False, - released: bool = False, - blocking: bool = True, - ) -> Union[int, FutureResult[int]]: - options = '' - if not released: - options += 'D' - if logical_state: - options += 'L' - if timeout: - options += f'T{timeout}' - args = [key_name] - if options: - args.append(options) - - resp = self._transport.function_call('AHKKeyWait', args) - return resp - - def run_script( - self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None - ) -> Union[str, FutureResult[str]]: - return self._transport.run_script(script_text_or_path, blocking=blocking, timeout=timeout) - - def set_send_level(self, level: int) -> None: - 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: - 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) -> None: ... - @overload - def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = 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, 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, 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, - blocking: bool = True, - ) -> Union[None, FutureResult[None]]: - 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 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]]: - 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]]: - 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]]: - 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]]: - 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('') - - + 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[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... - @overload - def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... - @overload - def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> FutureResult[None]: ... - @overload - def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... - # fmt: on - def set_capslock_state( - self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True - ) -> Union[None, FutureResult[None]]: - 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}' - ) - args.append(str(state)) - - resp = self._transport.function_call('AHKSetCapsLockState', 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]]: - args = [str(device_number), str(value)] - return self._transport.function_call('AHKSetVolume', args, blocking=blocking) - - # fmt: off - @overload - def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... - @overload - def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... - @overload - def show_traytip(self, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... - @overload - def show_traytip(self, title: str, text: str, second: float = 1.0, 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: float = 1.0, - type_id: int = 1, - *, - silent: bool = False, - large_icon: bool = False, - blocking: bool = True, - ) -> Union[None, FutureResult[None]]: - 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... - @overload - def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... - @overload - def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... - @overload - def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... - # fmt: on - def show_error_traytip( - self, - title: str, - text: str, - second: float = 1.0, - *, - silent: bool = False, - large_icon: bool = False, - blocking: bool = True, - ) -> Union[None, FutureResult[None]]: - 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... - @overload - def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... - @overload - def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... - @overload - def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... - # fmt: on - def show_info_traytip( - self, - title: str, - text: str, - second: float = 1.0, - *, - silent: bool = False, - large_icon: bool = False, - blocking: bool = True, - ) -> Union[None, FutureResult[None]]: - 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... - @overload - def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... - @overload - def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... - @overload - def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... - # fmt: on - def show_warning_traytip( - self, - title: str, - text: str, - second: float = 1.0, - *, - silent: bool = False, - large_icon: bool = False, - blocking: bool = True, - ) -> Union[None, FutureResult[None]]: - 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: - 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('') - self._transport.function_call('AHKShowToolTip', args) - - def hide_tooltip(self, which: int = 1) -> None: - self.show_tooltip(which=which) - - # 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]]: - 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]]: - 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]]: - 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]]: - 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, 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, 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, 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, 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]]]: ... - # 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]]]: - 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]]: - 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, 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, 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, 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, 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]]]: - 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]]]: - 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]]]: - 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]]]: - 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]]]]: - 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]]: - 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]]]: - 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]]]]: - 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]]: - 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]]: - args = [new_title, title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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]]: - args = [str(toggle), title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - args = [style, title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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]]: - args = [style, title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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]]: - args = [options, title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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]]: - args = [str(transparency), title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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]]: - args = [str(color), title, text, exclude_title, exclude_text] - if detect_hidden_windows is not None: - if detect_hidden_windows is True: - args.append('On') - elif detect_hidden_windows is False: - args.append('Off') - 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) -> 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) -> 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) -> 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) -> 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, - ) -> 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, - ) - - # 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) -> 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) -> 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) -> 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) -> 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, - ) -> Union[None, FutureResult[None]]: - 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 = '' - args = [str(x), str(y), button, str(click_count), direction or '', r, coord_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[Tuple[int, int]]: ... - @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[Tuple[int, int]]]: ... - @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[Tuple[int, int]]: ... - @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[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: ... - # 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[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: - """ - https://www.autohotkey.com/docs/commands/ImageSearch.htm - """ - - 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) - else: - args.append(image_path) - resp = self._transport.function_call('AHKImageSearch', args, blocking=blocking) - return resp - - def mouse_drag( - self, - x: int, - y: int, - *, - from_position: Optional[Tuple[int, int]] = None, - speed: Optional[int] = None, - button: MouseButton = 1, - relative: Optional[bool] = None, - blocking: bool = True, - coord_mode: Optional[CoordModeRelativeTo] = None, - ) -> None: - 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) - - self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) - - # 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]]: - 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[Tuple[int, int]]: ... - @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[Tuple[int, int]]: ... - @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[Tuple[int, int]]]: ... - @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[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: ... - # 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[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - 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]]: - return self._transport.function_call('AHKGetClipboard', blocking=blocking) - - def set_clipboard(self, s: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: - args = [s] - return self._transport.function_call('AHKSetClipboard', args, blocking=blocking) - - def get_clipboard_all(self, *, blocking: bool = True) -> Union[bytes, FutureResult[bytes]]: - 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]]: - # 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}'] - 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: - 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]]: - args = [str(timeout) if timeout else ''] - if wait_for_any_data: - args.append('1') - 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: - 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]]: - 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]]: - 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) - 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]]: - args = [key_name] - if value_name is not None: - args.append(value_name) - return self._transport.function_call('AHKRegRead', args, blocking=blocking) - - def block_forever(self) -> NoReturn: - while True: - sleep(1) From 23139b713a02e30df8a9048c1bd1618a6c2ff7fd Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 21:41:49 -0700 Subject: [PATCH 360/588] 1.0.0 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 070b0c79..38b819db 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.0.0b +version = 1.0.0 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From c7c20bc3db191a22758bb77b823629db2740e278 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 21:50:49 -0700 Subject: [PATCH 361/588] add run_script examples --- docs/README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 5ddca827..51eaf120 100644 --- a/docs/README.md +++ b/docs/README.md @@ -442,8 +442,25 @@ code is safe. ## Run arbitrary AutoHotkey scripts -TBD +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) +```
From 254673fea02dc4130e90812a6203e8f630c5a5af Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 21:55:43 -0700 Subject: [PATCH 362/588] fix anchor in readme --- docs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 51eaf120..6c098e64 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,7 +16,7 @@ pip install ahk ``` Requires Python 3.8+ -See also [Non-Python dependencies](#non-python-dependencies) +See also [Non-Python dependencies](#deps) # Usage @@ -463,7 +463,7 @@ ahk.run_script(script_path) ``` - + # Non-Python dependencies From daeba8bedcfbdf59d8784faa334900c316e09e2d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 1 May 2023 22:16:40 -0700 Subject: [PATCH 363/588] fix build badge --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 6c098e64..37f89a53 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,7 @@ A fully typed Python wrapper around AHK. [![Docs](https://readthedocs.org/projects/ahk/badge/?version=latest)](https://ahk.readthedocs.io/en/latest/?badge=latest) -[![Build](https://ci.appveyor.com/api/projects/status/2c53x6gglw9nxgj1/branch/master?svg=true)](https://ci.appveyor.com/project/spyoungtech/ahk/branch/master) +[![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) From 281f7b58877cb57079008017208f14d1af410a84 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 2 May 2023 01:06:30 -0700 Subject: [PATCH 364/588] post fixes --- docs/README.md | 2 +- setup.cfg | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 37f89a53..8324ed7b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -30,7 +30,7 @@ ahk.mouse_move(x=150, y=150, speed=10, blocking=True) # Moves the mouse to x, y print(ahk.mouse_position) # (150, 150) ``` -![ahk](https://raw.githubusercontent.com/spyoungtech/ahk/master/docs/_static/ahk.gif) +![ahk](https://raw.githubusercontent.com/spyoungtech/ahk/9d049a327c7a10c9f19dfef89fc63668695023fc/docs/_static/ahk.gif) # Examples diff --git a/setup.cfg b/setup.cfg index 38b819db..1584686a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.0.0 +version = 1.0.0.post1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK @@ -44,6 +44,9 @@ install_requires = cmdclass = build_py = buildunasync.build_py +[options.extras_require] +binary = ahk-binary==1.1.33.9 + [options.package_data] ahk = py.typed From 73c81519387482c88b15b44570334ccac9378ce1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 2 May 2023 10:46:57 -0700 Subject: [PATCH 365/588] minor doc improvements --- docs/api/async.rst | 20 ++++++++++++++++++-- docs/api/sync.rst | 22 ++++++++++++++++++++-- docs/conf.py | 2 ++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/docs/api/async.rst b/docs/api/async.rst index a307fdd6..871d0be2 100644 --- a/docs/api/async.rst +++ b/docs/api/async.rst @@ -1,12 +1,28 @@ Async API ========= +.. toctree:: + The async API is mostly identical to the sync API. -.. autoclass:: ahk._async.engine.AsyncAHK +AsyncFutureResult +----------------- + +.. autoclass:: ahk._async.transport.AsyncFutureResult :members: :undoc-members: -.. autoclass:: ahk._async.transport.AsyncFutureResult +AsyncWindow +----------- + +.. autoclass:: ahk._async.window.AsyncWindow + :members: + :undoc-members: + + +AsyncAHK +-------- + +.. autoclass:: ahk._async.engine.AsyncAHK :members: :undoc-members: diff --git a/docs/api/sync.rst b/docs/api/sync.rst index dee4cc36..8ec76647 100644 --- a/docs/api/sync.rst +++ b/docs/api/sync.rst @@ -1,14 +1,32 @@ 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. -.. autoclass:: ahk._sync.engine.AHK +FutureResult +------------ + +.. autoclass:: ahk._sync.transport.FutureResult :members: :undoc-members: -.. autoclass:: ahk._sync.transport.FutureResult + +Window +------ + +.. autoclass:: ahk._sync.window.Window + :members: + :undoc-members: + + +AHK +--- + +.. autoclass:: ahk._sync.engine.AHK :members: :undoc-members: diff --git a/docs/conf.py b/docs/conf.py index 354ab211..a8fb76ff 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -39,3 +39,5 @@ 'undoc-members': True, 'special-members': '__init__', } + +always_document_param_types = True From 66ee5c36ca929cb0164af6120dfb1dee4fc5880b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 2 May 2023 15:42:21 -0700 Subject: [PATCH 366/588] docstrings, minor bug fix for key_wait --- ahk/_async/engine.py | 365 +++++++++++++++++------ ahk/_sync/engine.py | 365 +++++++++++++++++------ docs/_static/css/custom.css | 7 + docs/api/async.rst | 6 + docs/api/directives.rst | 9 + docs/api/index.rst | 1 + docs/api/methods.md | 235 --------------- docs/api/methods.rst | 560 ++++++++++++++++++++++++++++++++++++ docs/api/sync.rst | 5 + docs/conf.py | 5 + 10 files changed, 1139 insertions(+), 419 deletions(-) create mode 100644 docs/_static/css/custom.css create mode 100644 docs/api/directives.rst delete mode 100644 docs/api/methods.md create mode 100644 docs/api/methods.rst diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 09beca24..7329e894 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -193,9 +193,7 @@ async def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> Non """ Sets the default title match mode - Has no effect for `Window`/`Control` instance methods (these always use hwnd) - - Does not affect methods called with `blocking=True` (because these run in a separate AHK process) + 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 @@ -225,7 +223,7 @@ async def get_title_match_mode(self) -> str: """ Get the title match mode. - I.E. the current value of `A_TitleMatchMode` + I.E. the current value of ``A_TitleMatchMode`` """ resp = await self._transport.function_call('AHKGetTitleMatchMode') @@ -235,18 +233,24 @@ async def get_title_match_speed(self) -> str: """ Get the title match mode speed. - I.E. the current value of `A_TitleMatchModeSpeed` + 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 @@ -277,19 +281,7 @@ async def control_click( blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: """ - - :param button: the mouse button to use - :param click_count: how many times to click - :param options: options -- same meaning as in AutoHotkey - :param control: the control to click - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: + Analog for `ControlClick `_ """ args = [control, title, text, str(button), str(click_count), options, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -348,17 +340,7 @@ async def control_get_text( blocking: bool = True, ) -> Union[str, AsyncFutureResult[str]]: """ - Analog to ``ControlGetText`` - - :param control: - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: + Analog for `ControlGetText `_ """ args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -416,17 +398,7 @@ async def control_get_position( blocking: bool = True, ) -> Union[Position, AsyncFutureResult[Position]]: """ - Analog to ``ControlGetPos`` - - :param control: - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: + Analog to `ControlGetPos `_ """ args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -486,20 +458,7 @@ async def control_send( blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: """ - Analog for ``ControlSend`` - - Reference: https://www.autohotkey.com/docs/commands/ControlSend.htm - - :param keys: - :param control: - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: + Analog for `ControlSend `_ """ args = [control, keys, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -545,17 +504,16 @@ def start_hotkeys(self) -> None: def stop_hotkeys(self) -> None: """ - Stop the Autohotkey process for triggering hotkeys + 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 AutoHotkey's `DetectHiddenWindows` - - :param value: The setting value. `True` to turn on hidden window detection, `False` to turn it off. + 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): @@ -633,15 +591,7 @@ async def list_windows( """ Enumerate all windows matching the criteria. - - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: + Analog for `WinGet List subcommand _` """ args = self._format_win_args( title=title, @@ -668,11 +618,7 @@ async def get_mouse_position( self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True ) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: """ - Analog for ``MouseGetPos`` - - :param coord_mode: - :param blocking: - :return: + Analog for `MouseGetPos `_ """ if coord_mode: args = [str(coord_mode)] @@ -684,9 +630,9 @@ async def get_mouse_position( @property def mouse_position(self) -> AsyncPropertyReturnTupleIntInt: """ - Convenience property for ``get_mouse_position`` + Convenience property for :py:meth:`get_mouse_position` - :return: + 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 @@ -699,7 +645,6 @@ 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 - :return: """ raise RuntimeError('Use of the mouse_position setter is not supported in the async API.') # unasync: remove x, y = new_position @@ -725,14 +670,7 @@ async def mouse_move( blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: """ - Analog for ``MouseMove`` - - :param x: - :param y: - :param speed: - :param relative: - :param blocking: - :return: + Analog for `MouseMove `_ """ if relative and (x is None or y is None): x = x or 0 @@ -752,7 +690,7 @@ async def mouse_move( async def a_run_script(self, *args: Any, **kwargs: Any) -> Union[str, AsyncFutureResult[str]]: """ - Deprecated. Use ``run_script`` instead. + 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) @@ -772,9 +710,6 @@ async def get_active_window( ) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: """ Gets the currently active window. - - :param blocking: - :return: """ return await self.win_get( title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking @@ -783,9 +718,7 @@ async def get_active_window( @property def active_window(self) -> AsyncPropertyReturnOptionalAsyncWindow: """ - Gets the currently active window - - :return: + 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 @@ -922,6 +855,9 @@ async def find_window_by_title( 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 @@ -937,6 +873,9 @@ async def key_down(self, key: Union[str, Key], *, blocking: Literal[False]) -> A 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: @@ -958,6 +897,10 @@ async def key_press(self, key: Union[str, Key], *, release: bool = True, blockin 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: @@ -980,6 +923,9 @@ async def key_release(self, key: Union[str, Key], *, blocking: Literal[False]) - 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 @@ -1008,6 +954,9 @@ async def key_state( AsyncFutureResult[float], AsyncFutureResult[None], ]: + """ + Analog for `GetKeyState `_ + """ args: List[str] = [key_name] if mode is not None: if mode not in ('T', 'P'): @@ -1027,6 +976,10 @@ async def key_up(self, key: Union[str, Key], *, blocking: Literal[False]) -> Asy 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: @@ -1054,6 +1007,9 @@ async def key_wait( released: bool = False, blocking: bool = True, ) -> Union[int, AsyncFutureResult[int]]: + """ + Analog for `KeyWait `_ + """ options = '' if not released: options += 'D' @@ -1065,15 +1021,22 @@ async def key_wait( if options: args.append(options) - resp = await self._transport.function_call('AHKKeyWait', args) + 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: @@ -1082,6 +1045,10 @@ async def set_send_level(self, level: int) -> None: 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 @@ -1104,6 +1071,9 @@ async def send( key_press_duration: Optional[int] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `Send `_ + """ args = [s] if key_delay: args.append(str(key_delay)) @@ -1139,6 +1109,9 @@ async def send_raw( 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 ) @@ -1155,6 +1128,9 @@ async def send_input(self, s: str, *, blocking: Literal[False]) -> AsyncFutureRe 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 @@ -1170,6 +1146,9 @@ async def type(self, s: str, *, blocking: Literal[False]) -> AsyncFutureResult[N 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 @@ -1191,6 +1170,9 @@ async def send_play( 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)) @@ -1217,6 +1199,9 @@ async def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'A async def set_capslock_state( self, state: Optional[Literal[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'): @@ -1241,6 +1226,9 @@ async def set_volume(self, value: int, device_number: int = 1, *, blocking: bool 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) @@ -1265,6 +1253,9 @@ async def show_traytip( large_icon: bool = False, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `TrayTip `_ + """ 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) @@ -1289,6 +1280,9 @@ async def show_error_traytip( 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 ) @@ -1313,6 +1307,9 @@ async def show_info_traytip( 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 ) @@ -1337,6 +1334,9 @@ async def show_warning_traytip( 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 ) @@ -1348,6 +1348,9 @@ async def show_tooltip( 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] @@ -1377,6 +1380,9 @@ async def sound_beep(self, frequency: int = 523, duration: int = 150, *, blockin 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 @@ -1399,6 +1405,9 @@ async def sound_get( *, 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) @@ -1413,6 +1422,9 @@ async def sound_play(self, filename: str, *, blocking: Literal[True]) -> None: . 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( @@ -1424,6 +1436,9 @@ async def sound_set( *, 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) @@ -1448,6 +1463,9 @@ async def win_get( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[None, AsyncWindow]]]: + """ + Analog for `WinGet `_ + """ args = self._format_win_args( title=title, text=text, @@ -1544,6 +1562,9 @@ async def win_get_class( 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, @@ -1576,6 +1597,9 @@ async def win_get_position( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Position, None, AsyncFutureResult[Union[Position, None]]]: + """ + Analog for `WinGetPos `_ + """ args = self._format_win_args( title=title, text=text, @@ -1608,6 +1632,9 @@ async def win_get_idlast( 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, @@ -1640,6 +1667,11 @@ async def win_get_pid( 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, @@ -1672,6 +1704,11 @@ async def win_get_process_name( 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, @@ -1704,6 +1741,11 @@ async def win_get_process_path( 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, @@ -1736,6 +1778,9 @@ async def win_get_count( 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, @@ -1768,6 +1813,9 @@ async def win_get_minmax( 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, @@ -1800,6 +1848,9 @@ async def win_get_control_list( 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, @@ -1880,6 +1931,9 @@ async def win_activate( 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, @@ -1913,6 +1967,9 @@ async def win_set_title( 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: @@ -1968,6 +2025,9 @@ async def win_set_always_on_top( 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: @@ -2022,6 +2082,9 @@ async def win_set_bottom( 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, @@ -2054,6 +2117,9 @@ async def win_set_top( 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, @@ -2086,6 +2152,9 @@ async def win_set_disable( 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, @@ -2118,6 +2187,9 @@ async def win_set_enable( 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, @@ -2150,6 +2222,10 @@ async def win_set_redraw( 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, @@ -2183,6 +2259,10 @@ async def win_set_style( 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: @@ -2238,6 +2318,9 @@ async def win_set_ex_style( 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: @@ -2293,6 +2376,9 @@ async def win_set_region( 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: @@ -2348,6 +2434,9 @@ async def win_set_transparent( 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: @@ -2403,6 +2492,9 @@ async def win_set_trans_color( 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: @@ -2494,6 +2586,9 @@ async def click( blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = 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 @@ -2538,7 +2633,7 @@ async def image_search( blocking: bool = True, ) -> Union[Tuple[int, int], None, AsyncFutureResult[Optional[Tuple[int, int]]]]: """ - https://www.autohotkey.com/docs/commands/ImageSearch.htm + Analog for `ImageSearch `_ """ if scale_height and not scale_width: @@ -2584,6 +2679,9 @@ async def mouse_drag( blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, ) -> None: + """ + Analog for `MouseClickDrag `_ + """ if from_position: x1, y1 = from_position args = [str(button), str(x1), str(y1), str(x), str(y)] @@ -2626,6 +2724,9 @@ async def pixel_get_color( 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) @@ -2656,6 +2757,9 @@ async def pixel_search( rgb: bool = True, blocking: bool = True, ) -> Union[Optional[Tuple[int, int]], AsyncFutureResult[Optional[Tuple[int, int]]]]: + """ + 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)] @@ -2687,6 +2791,9 @@ async def win_close( 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, @@ -2722,6 +2829,9 @@ async def win_kill( 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, @@ -2756,6 +2866,9 @@ async def win_minimize( 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, @@ -2788,6 +2901,9 @@ async def win_maximize( 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, @@ -2820,6 +2936,9 @@ async def win_restore( 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, @@ -2853,6 +2972,9 @@ async def win_wait( timeout: Optional[int] = None, blocking: bool = True, ) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: + """ + Analog for `WinWait `_ + """ args = self._format_win_args( title=title, text=text, @@ -2887,6 +3009,9 @@ async def win_wait_active( timeout: Optional[int] = None, blocking: bool = True, ) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: + """ + Analog for `WinWaitActive `_ + """ args = self._format_win_args( title=title, text=text, @@ -2921,6 +3046,9 @@ async def win_wait_not_active( timeout: Optional[int] = None, blocking: bool = True, ) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: + """ + Analog for `WinWaitNotActive `_ + """ args = self._format_win_args( title=title, text=text, @@ -2955,6 +3083,9 @@ async def win_wait_close( timeout: Optional[int] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: + """ + Analog for `WinWaitClose `_ + """ args = self._format_win_args( title=title, text=text, @@ -2988,6 +3119,9 @@ async def win_show( 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, @@ -3020,6 +3154,9 @@ async def win_hide( 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, @@ -3052,6 +3189,11 @@ async def win_is_active( 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, @@ -3088,6 +3230,9 @@ async def win_move( 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, @@ -3114,13 +3259,22 @@ async def get_clipboard(self, *, blocking: Literal[True]) -> str: ... 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 @@ -3136,6 +3290,9 @@ async def set_clipboard_all(self, contents: bytes, *, blocking: bool = True) -> 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') @@ -3157,6 +3314,10 @@ async def set_clipboard_all( 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 @@ -3172,6 +3333,11 @@ async def clip_wait(self, timeout: Optional[float] = None, wait_for_any_data: bo 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') @@ -3182,6 +3348,9 @@ async def block_input( 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 @@ -3197,6 +3366,9 @@ async def reg_delete(self, key_name: str, value_name: Optional[str] = None, *, b 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) @@ -3219,6 +3391,9 @@ async def reg_write( *, 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) @@ -3241,11 +3416,17 @@ async def reg_read(self, key_name: str, value_name: Optional[str] = None, *, blo 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) return await self._transport.function_call('AHKRegRead', 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) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index e5b43051..a8f66636 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -189,9 +189,7 @@ def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> None: """ Sets the default title match mode - Has no effect for `Window`/`Control` instance methods (these always use hwnd) - - Does not affect methods called with `blocking=True` (because these run in a separate AHK process) + 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 @@ -221,7 +219,7 @@ def get_title_match_mode(self) -> str: """ Get the title match mode. - I.E. the current value of `A_TitleMatchMode` + I.E. the current value of ``A_TitleMatchMode`` """ resp = self._transport.function_call('AHKGetTitleMatchMode') @@ -231,18 +229,24 @@ def get_title_match_speed(self) -> str: """ Get the title match mode speed. - I.E. the current value of `A_TitleMatchModeSpeed` + 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 @@ -273,19 +277,7 @@ def control_click( blocking: bool = True, ) -> Union[None, FutureResult[None]]: """ - - :param button: the mouse button to use - :param click_count: how many times to click - :param options: options -- same meaning as in AutoHotkey - :param control: the control to click - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: + Analog for `ControlClick `_ """ args = [control, title, text, str(button), str(click_count), options, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -344,17 +336,7 @@ def control_get_text( blocking: bool = True, ) -> Union[str, FutureResult[str]]: """ - Analog to ``ControlGetText`` - - :param control: - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: + Analog for `ControlGetText `_ """ args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -412,17 +394,7 @@ def control_get_position( blocking: bool = True, ) -> Union[Position, FutureResult[Position]]: """ - Analog to ``ControlGetPos`` - - :param control: - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: + Analog to `ControlGetPos `_ """ args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -482,20 +454,7 @@ def control_send( blocking: bool = True, ) -> Union[None, FutureResult[None]]: """ - Analog for ``ControlSend`` - - Reference: https://www.autohotkey.com/docs/commands/ControlSend.htm - - :param keys: - :param control: - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: + Analog for `ControlSend `_ """ args = [control, keys, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: @@ -541,17 +500,16 @@ def start_hotkeys(self) -> None: def stop_hotkeys(self) -> None: """ - Stop the Autohotkey process for triggering hotkeys + Stop the Autohotkey process for triggering hotkeys/hotstrings """ return self._transport.stop_hotkeys() def set_detect_hidden_windows(self, value: bool) -> None: """ - Analog for AutoHotkey's `DetectHiddenWindows` - - :param value: The setting value. `True` to turn on hidden window detection, `False` to turn it off. + 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): @@ -629,15 +587,7 @@ def list_windows( """ Enumerate all windows matching the criteria. - - :param title: - :param text: - :param exclude_title: - :param exclude_text: - :param title_match_mode: - :param detect_hidden_windows: - :param blocking: - :return: + Analog for `WinGet List subcommand _` """ args = self._format_win_args( title=title, @@ -664,11 +614,7 @@ def get_mouse_position( self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: bool = True ) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: """ - Analog for ``MouseGetPos`` - - :param coord_mode: - :param blocking: - :return: + Analog for `MouseGetPos `_ """ if coord_mode: args = [str(coord_mode)] @@ -680,9 +626,9 @@ def get_mouse_position( @property def mouse_position(self) -> SyncPropertyReturnTupleIntInt: """ - Convenience property for ``get_mouse_position`` + Convenience property for :py:meth:`get_mouse_position` - :return: + Setter accepts a tuple of x,y coordinates passed to :py:meth:`mouse_mouse` """ return self.get_mouse_position() @@ -692,7 +638,6 @@ 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 - :return: """ x, y = new_position return self.mouse_move(x=x, y=y, speed=0, relative=False) @@ -717,14 +662,7 @@ def mouse_move( blocking: bool = True, ) -> Union[None, FutureResult[None]]: """ - Analog for ``MouseMove`` - - :param x: - :param y: - :param speed: - :param relative: - :param blocking: - :return: + Analog for `MouseMove `_ """ if relative and (x is None or y is None): x = x or 0 @@ -744,7 +682,7 @@ def mouse_move( def a_run_script(self, *args: Any, **kwargs: Any) -> Union[str, FutureResult[str]]: """ - Deprecated. Use ``run_script`` instead. + 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) @@ -764,9 +702,6 @@ def get_active_window( ) -> Union[Optional[Window], FutureResult[Optional[Window]]]: """ Gets the currently active window. - - :param blocking: - :return: """ return self.win_get( title='A', detect_hidden_windows=False, title_match_mode=(1, 'Fast'), blocking=blocking @@ -775,9 +710,7 @@ def get_active_window( @property def active_window(self) -> SyncPropertyReturnOptionalAsyncWindow: """ - Gets the currently active window - - :return: + Gets the currently active window. Convenience property for :py:meth:`get_active_window` """ return self.get_active_window() @@ -911,6 +844,9 @@ def find_window_by_title( 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 @@ -926,6 +862,9 @@ def key_down(self, key: Union[str, Key], *, blocking: Literal[False]) -> FutureR 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: @@ -947,6 +886,10 @@ def key_press(self, key: Union[str, Key], *, release: bool = True, blocking: boo 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: @@ -969,6 +912,9 @@ def key_release(self, key: Union[str, Key], *, blocking: Literal[False]) -> Futu 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 @@ -997,6 +943,9 @@ def key_state( FutureResult[float], FutureResult[None], ]: + """ + Analog for `GetKeyState `_ + """ args: List[str] = [key_name] if mode is not None: if mode not in ('T', 'P'): @@ -1016,6 +965,10 @@ def key_up(self, key: Union[str, Key], *, blocking: Literal[False]) -> FutureRes 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: @@ -1043,6 +996,9 @@ def key_wait( released: bool = False, blocking: bool = True, ) -> Union[int, FutureResult[int]]: + """ + Analog for `KeyWait `_ + """ options = '' if not released: options += 'D' @@ -1054,15 +1010,22 @@ def key_wait( if options: args.append(options) - resp = self._transport.function_call('AHKKeyWait', args) + 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: @@ -1071,6 +1034,10 @@ def set_send_level(self, level: int) -> None: 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 @@ -1093,6 +1060,9 @@ def send( key_press_duration: Optional[int] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: + """ + Analog for `Send `_ + """ args = [s] if key_delay: args.append(str(key_delay)) @@ -1128,6 +1098,9 @@ def send_raw( 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 ) @@ -1144,6 +1117,9 @@ def send_input(self, s: str, *, blocking: Literal[False]) -> FutureResult[None]: 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 @@ -1159,6 +1135,9 @@ def type(self, s: str, *, blocking: Literal[False]) -> FutureResult[None]: ... 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 @@ -1180,6 +1159,9 @@ def send_play( 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)) @@ -1206,6 +1188,9 @@ def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysO def set_capslock_state( self, state: Optional[Literal[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'): @@ -1230,6 +1215,9 @@ def set_volume(self, value: int, device_number: int = 1, *, blocking: bool = Tru 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) @@ -1254,6 +1242,9 @@ def show_traytip( large_icon: bool = False, blocking: bool = True, ) -> Union[None, FutureResult[None]]: + """ + Analog for `TrayTip `_ + """ 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) @@ -1278,6 +1269,9 @@ def show_error_traytip( 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 ) @@ -1302,6 +1296,9 @@ def show_info_traytip( 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 ) @@ -1326,6 +1323,9 @@ def show_warning_traytip( 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 ) @@ -1337,6 +1337,9 @@ def show_tooltip( 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] @@ -1366,6 +1369,9 @@ def sound_beep(self, frequency: int = 523, duration: int = 150, *, blocking: boo 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 @@ -1388,6 +1394,9 @@ def sound_get( *, 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) @@ -1402,6 +1411,9 @@ def sound_play(self, filename: str, *, blocking: Literal[True]) -> None: ... 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( @@ -1413,6 +1425,9 @@ def sound_set( *, 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) @@ -1437,6 +1452,9 @@ def win_get( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Window, None, FutureResult[Union[None, Window]]]: + """ + Analog for `WinGet `_ + """ args = self._format_win_args( title=title, text=text, @@ -1533,6 +1551,9 @@ def win_get_class( 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, @@ -1565,6 +1586,9 @@ def win_get_position( detect_hidden_windows: Optional[bool] = None, blocking: bool = True, ) -> Union[Position, None, FutureResult[Union[Position, None]]]: + """ + Analog for `WinGetPos `_ + """ args = self._format_win_args( title=title, text=text, @@ -1597,6 +1621,9 @@ def win_get_idlast( 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, @@ -1629,6 +1656,11 @@ def win_get_pid( 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, @@ -1661,6 +1693,11 @@ def win_get_process_name( 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, @@ -1693,6 +1730,11 @@ def win_get_process_path( 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, @@ -1725,6 +1767,9 @@ def win_get_count( 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, @@ -1757,6 +1802,9 @@ def win_get_minmax( 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, @@ -1789,6 +1837,9 @@ def win_get_control_list( 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, @@ -1869,6 +1920,9 @@ def win_activate( 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, @@ -1902,6 +1956,9 @@ def win_set_title( 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: @@ -1957,6 +2014,9 @@ def win_set_always_on_top( 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: @@ -2011,6 +2071,9 @@ def win_set_bottom( 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, @@ -2043,6 +2106,9 @@ def win_set_top( 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, @@ -2075,6 +2141,9 @@ def win_set_disable( 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, @@ -2107,6 +2176,9 @@ def win_set_enable( 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, @@ -2139,6 +2211,10 @@ def win_set_redraw( 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, @@ -2172,6 +2248,10 @@ def win_set_style( 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: @@ -2227,6 +2307,9 @@ def win_set_ex_style( 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: @@ -2282,6 +2365,9 @@ def win_set_region( 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: @@ -2337,6 +2423,9 @@ def win_set_transparent( 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: @@ -2392,6 +2481,9 @@ def win_set_trans_color( 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: @@ -2483,6 +2575,9 @@ def click( blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = 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 @@ -2527,7 +2622,7 @@ def image_search( blocking: bool = True, ) -> Union[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: """ - https://www.autohotkey.com/docs/commands/ImageSearch.htm + Analog for `ImageSearch `_ """ if scale_height and not scale_width: @@ -2573,6 +2668,9 @@ def mouse_drag( blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, ) -> None: + """ + Analog for `MouseClickDrag `_ + """ if from_position: x1, y1 = from_position args = [str(button), str(x1), str(y1), str(x), str(y)] @@ -2615,6 +2713,9 @@ def pixel_get_color( 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) @@ -2645,6 +2746,9 @@ def pixel_search( rgb: bool = True, blocking: bool = True, ) -> Union[Optional[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: + """ + 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)] @@ -2676,6 +2780,9 @@ def win_close( 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, @@ -2711,6 +2818,9 @@ def win_kill( 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, @@ -2745,6 +2855,9 @@ def win_minimize( 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, @@ -2777,6 +2890,9 @@ def win_maximize( 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, @@ -2809,6 +2925,9 @@ def win_restore( 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, @@ -2842,6 +2961,9 @@ def win_wait( timeout: Optional[int] = None, blocking: bool = True, ) -> Union[Window, FutureResult[Window]]: + """ + Analog for `WinWait `_ + """ args = self._format_win_args( title=title, text=text, @@ -2876,6 +2998,9 @@ def win_wait_active( timeout: Optional[int] = None, blocking: bool = True, ) -> Union[Window, FutureResult[Window]]: + """ + Analog for `WinWaitActive `_ + """ args = self._format_win_args( title=title, text=text, @@ -2910,6 +3035,9 @@ def win_wait_not_active( timeout: Optional[int] = None, blocking: bool = True, ) -> Union[Window, FutureResult[Window]]: + """ + Analog for `WinWaitNotActive `_ + """ args = self._format_win_args( title=title, text=text, @@ -2944,6 +3072,9 @@ def win_wait_close( timeout: Optional[int] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: + """ + Analog for `WinWaitClose `_ + """ args = self._format_win_args( title=title, text=text, @@ -2977,6 +3108,9 @@ def win_show( 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, @@ -3009,6 +3143,9 @@ def win_hide( 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, @@ -3041,6 +3178,11 @@ def win_is_active( 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, @@ -3077,6 +3219,9 @@ def win_move( 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, @@ -3103,13 +3248,22 @@ def get_clipboard(self, *, blocking: Literal[True]) -> str: ... 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 @@ -3125,6 +3279,9 @@ def set_clipboard_all(self, contents: bytes, *, blocking: bool = True) -> Union[ 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') @@ -3146,6 +3303,10 @@ def set_clipboard_all( 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 @@ -3161,6 +3322,11 @@ def clip_wait(self, timeout: Optional[float] = None, wait_for_any_data: bool = F 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') @@ -3171,6 +3337,9 @@ def block_input( 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 @@ -3186,6 +3355,9 @@ def reg_delete(self, key_name: str, value_name: Optional[str] = None, *, blockin 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) @@ -3208,6 +3380,9 @@ def reg_write( *, 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) @@ -3230,11 +3405,17 @@ def reg_read(self, key_name: str, value_name: Optional[str] = None, *, blocking: 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) return self._transport.function_call('AHKRegRead', args, blocking=blocking) def block_forever(self) -> NoReturn: + """ + Blocks (sleeps) forever. Utility method to prevent script from exiting. + """ while True: sleep(1) 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 index 871d0be2..f67ce273 100644 --- a/docs/api/async.rst +++ b/docs/api/async.rst @@ -19,6 +19,12 @@ AsyncWindow :members: :undoc-members: +AsyncControl +------------ +.. autoclass:: ahk._async.window.AsyncControl + :members: + :undoc-members: + AsyncAHK -------- 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 index 863ba819..5563455f 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -13,3 +13,4 @@ about the programming interface. This is largely auto-generated documentation. sync async methods + directives diff --git a/docs/api/methods.md b/docs/api/methods.md deleted file mode 100644 index dc091a3f..00000000 --- a/docs/api/methods.md +++ /dev/null @@ -1,235 +0,0 @@ -# Available Methods - -Most useful methods from autohotkey are implemented in this wrapper. However, not everything is implemented (yet). This -page can serve as a quick reference of AutoHotkey methods that are implemented in the wrapper. - -(Coming soon: links to equivalent Python method(s)) - -### Mouse and Keyboard - -| AutoHotkey Command | Status | Notes | -|----------------------------------------------------------------------------------------------|-----------------|-------------------------------------------------------------------------| -| [#KeyHistory](https://www.autohotkey.com/docs/commands/_KeyHistory.htm) | Not Implemented | | -| [BlockInput](https://www.autohotkey.com/docs/commands/BlockInput.htm) | Implemented | | -| [Click](https://www.autohotkey.com/docs/commands/Click.htm) | Implemented | | -| [ControlClick](https://www.autohotkey.com/docs/commands/ControlClick.htm) | Implemented | | -| [ControlSend[Raw]](https://www.autohotkey.com/docs/commands/ControlSend.htm) | Implemented | | -| [CoordMode](https://www.autohotkey.com/docs/commands/CoordMode.htm) | Implemented | | -| [GetKeyName()](https://www.autohotkey.com/docs/commands/GetKey.htm) | Not Implemented | | -| [GetKeySC()](https://www.autohotkey.com/docs/commands/GetKey.htm) | Not Implemented | | -| [GetKeyState](https://www.autohotkey.com/docs/commands/GetKeyState.htm#command) | Implemented | | -| [GetKeyVK()](https://www.autohotkey.com/docs/commands/GetKey.htm) | Not Implemented | | -| [KeyHistory](https://www.autohotkey.com/docs/commands/KeyHistory.htm) | Not Implemented | | -| [KeyWait](https://www.autohotkey.com/docs/commands/KeyWait.htm) | Implemented | | -| [Input](https://www.autohotkey.com/docs/commands/Input.htm) | Not Implemented | Use python `input()` instead | -| [InputHook()](https://www.autohotkey.com/docs/commands/InputHook.htm) | Not Implemented | | -| [MouseClick](https://www.autohotkey.com/docs/commands/MouseClick.htm) | Implemented | | -| [MouseClickDrag](https://www.autohotkey.com/docs/commands/MouseClickDrag.htm) | Implemented | | -| [MouseGetPos](https://www.autohotkey.com/docs/commands/MouseGetPos.htm) | Implemented | | -| [MouseMove](https://www.autohotkey.com/docs/commands/MouseMove.htm) | Implemented | | -| [SendLevel](https://www.autohotkey.com/docs/commands/SendLevel.htm) | Implemented | | -| [SendMode](https://www.autohotkey.com/docs/commands/SendMode.htm) | Not Implemented | | -| [SetCapsLockState](https://www.autohotkey.com/docs/commands/SetNumScrollCapsLockState.htm) | Implemented | | -| [SetDefaultMouseSpeed](https://www.autohotkey.com/docs/commands/SetDefaultMouseSpeed.htm) | Implemented | Speed is controlled by the `speed` keyword argument of relevant methods | -| [SetKeyDelay](https://www.autohotkey.com/docs/commands/SetKeyDelay.htm) | Implemented | Delay is controlled by the `delay` keyword argument of relevant methods | -| [SetMouseDelay](https://www.autohotkey.com/docs/commands/SetMouseDelay.htm) | Not Implemented | Delays between mouse movements can be controlled in Python code | -| [SetNumLockState](https://www.autohotkey.com/docs/commands/SetNumScrollCapsLockState.htm) | Implemented | | -| [SetScrollLockState](https://www.autohotkey.com/docs/commands/SetNumScrollCapsLockState.htm) | Implemented | | -| [SetStoreCapsLockMode](https://www.autohotkey.com/docs/commands/SetStoreCapslockMode.htm) | Not Implemented | note | - - -### Hotkeys - -| AutoHotkey Command | Status | Notes | -|-----------------------------------------------------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------| -| [Hotkeys](https://www.autohotkey.com/docs/Hotkeys.htm) | Implemented | Before 1.0, callbacks were only supported as Autohotkey Scripts
In 1.0 and later, callbacks are supported as Python functions | -| [Hotstrings](https://www.autohotkey.com/docs/Hotstrings.htm) | Implemented | Available in 1.0+ | -| [Suspend](https://www.autohotkey.com/docs/commands/Suspend.htm) | Implemented* | Use stop_hotkeys and start_hotkeys to enable/disable hotkeys | - - - -### ClipBoard - -| AutoHotkey Command | Status | Notes | -|------------------------------------------------------------------------------------------------|-------------|-------| -| [OnClipboardChange()](https://www.autohotkey.com/docs/commands/OnClipboardChange.htm#function) | Implemented | | -| [Clipboard/ClipboardAll](https://www.autohotkey.com/docs/misc/Clipboard.htm#ClipboardAll) | Implemented | | -| [ClipWAit](https://www.autohotkey.com/docs/v1/lib/ClipWait.htm) | Implemented | note | - - - -### Screen/Image - -| AutoHotkey Command | Status | Notes | -|----------------------------------------------------------------------------------------------|-------------|-------| -| [ImageSearch](https://www.autohotkey.com/docs/commands/ImageSearch.htm) | Implemented | | -| [PixelGetColor](https://www.autohotkey.com/docs/commands/PixelGetColor.htm) | Implemented | | -| [PixelSearch](https://www.autohotkey.com/docs/commands/PixelSearch.htm) | Implemented | note | - - -### Registry - - - -| AutoHotkey Command | Status | Notes | -|----------------------------------------------------------------------------------------------|-----------------|-------| -| [RegDelete](https://www.autohotkey.com/docs/commands/RegDelete.htm) | Implemented | | -| [RegRead](https://www.autohotkey.com/docs/commands/RegRead.htm) | Implemented | | -| [RegWrite](https://www.autohotkey.com/docs/commands/RegWrite.htm) | Implemented | | -| [SetRegView](https://www.autohotkey.com/docs/commands/SetRegView.htm) | Not Implemented | note | - - - -### Window - - -#### Window | Controls - -| AutoHotkey Command | Status | Notes | -|----------------------------------------------------------------------------------------------|-----------------|-------| -| [Control](https://www.autohotkey.com/docs/commands/Control.htm) | Implemented | | -| [ControlClick](https://www.autohotkey.com/docs/commands/ControlClick.htm) | Implemented | | -| [ControlFocus](https://www.autohotkey.com/docs/commands/ControlFocus.htm) | Not Implemented | | -| [ControlGet](https://www.autohotkey.com/docs/commands/ControlGet.htm) | Implemented | | -| [ControlGetFocus](https://www.autohotkey.com/docs/commands/ControlGetFocus.htm) | Not Implemented | | -| [ControlGetPos](https://www.autohotkey.com/docs/commands/ControlGetPos.htm) | Implemented | | -| [ControlGetText](https://www.autohotkey.com/docs/commands/ControlGetText.htm) | Implemented | | -| [ControlMove](https://www.autohotkey.com/docs/commands/ControlMove.htm) | Implemented | | -| [ControlSend[Raw]](https://www.autohotkey.com/docs/commands/ControlSend.htm) | Implemented | | -| [ControlSetText](https://www.autohotkey.com/docs/commands/ControlSetText.htm) | Implemented | | -| [Menu](https://www.autohotkey.com/docs/commands/Menu.htm) | Not Implemented | | -| [PostMessage/SendMessage](https://www.autohotkey.com/docs/commands/PostMessage.htm) | Not Implemented | | -| [SetControlDelay](https://www.autohotkey.com/docs/commands/SetControlDelay.htm) | Not Implemented | | -| [WinMenuSelectItem](https://www.autohotkey.com/docs/commands/WinMenuSelectItem.htm) | Not Implemented | note | - - -#### Window | Groups - -| AutoHotkey Command | Status | Notes | -|----------------------------------------------------------------------------------------------|-----------------|-------| -| [GroupActivate](https://www.autohotkey.com/docs/commands/GroupActivate.htm) | | | -| [GroupAdd](https://www.autohotkey.com/docs/commands/GroupAdd.htm) | | | -| [GroupClose](https://www.autohotkey.com/docs/commands/GroupClose.htm) | | | -| [GroupDeactivate](https://www.autohotkey.com/docs/commands/GroupDeactivate.htm) | | note | - -### Window functions - -| AutoHotkey Command | Status | Notes | -|-----------------------------------------------------------------------------------------|-----------------|---------------------------------------------------| -| [#WinActivateForce](https://www.autohotkey.com/docs/commands/_WinActivateForce.htm) | Implemented | Any directive can be added to the daemon | -| [DetectHiddenText](https://www.autohotkey.com/docs/commands/DetectHiddenText.htm) | Planned | | -| [DetectHiddenWindows](https://www.autohotkey.com/docs/commands/DetectHiddenWindows.htm) | Implemented | | -| [IfWin[Not]Active](https://www.autohotkey.com/docs/commands/IfWinActive.htm) | Not Implemented | Use Python `if` with `win_active`/`win.is_active` | -| [IfWin[Not]Exist](https://www.autohotkey.com/docs/commands/IfWinExist.htm) | Not Implemented | Use Python `if` with `win_exists`/`win.exists` | -| [SetTitleMatchMode](https://www.autohotkey.com/docs/commands/SetTitleMatchMode.htm) | Implemented | | -| [SetWinDelay](https://www.autohotkey.com/docs/commands/SetWinDelay.htm) | Not Implemented | Delays can be controlled in Python code | -| [StatusBarGetText](https://www.autohotkey.com/docs/commands/StatusBarGetText.htm) | Not Implemented | | -| [StatusBarWait](https://www.autohotkey.com/docs/commands/StatusBarWait.htm) | Not Implemented | | -| [WinActivate](https://www.autohotkey.com/docs/commands/WinActivate.htm) | Implemented | | -| [WinActivateBottom](https://www.autohotkey.com/docs/commands/WinActivateBottom.htm) | Implemented | | -| [WinActive()](https://www.autohotkey.com/docs/commands/WinActive.htm) | Implemented | | -| [WinClose](https://www.autohotkey.com/docs/commands/WinClose.htm) | Implemented | | -| [WinExist()](https://www.autohotkey.com/docs/commands/WinExist.htm) | Implemented | | -| [WinGet](https://www.autohotkey.com/docs/commands/WinGet.htm) | Implemented | | -| [WinGetActiveStats](https://www.autohotkey.com/docs/commands/WinGetActiveStats.htm) | Not Implemented | | -| [WinGetActiveTitle](https://www.autohotkey.com/docs/commands/WinGetActiveTitle.htm) | Not Implemented | | -| [WinGetClass](https://www.autohotkey.com/docs/commands/WinGetClass.htm) | Implemented | | -| [WinGetPos](https://www.autohotkey.com/docs/commands/WinGetPos.htm) | Implemented | | -| [WinGetText](https://www.autohotkey.com/docs/commands/WinGetText.htm) | Implemented | | -| [WinGetTitle](https://www.autohotkey.com/docs/commands/WinGetTitle.htm) | Implemented | | -| [WinHide](https://www.autohotkey.com/docs/commands/WinHide.htm) | Implemented | | -| [WinKill](https://www.autohotkey.com/docs/commands/WinKill.htm) | Implemented | | -| [WinMaximize](https://www.autohotkey.com/docs/commands/WinMaximize.htm) | Implemented | | -| [WinMinimize](https://www.autohotkey.com/docs/commands/WinMinimize.htm) | Implemented | | -| [WinMinimizeAll[Undo]](https://www.autohotkey.com/docs/commands/WinMinimizeAll.htm) | Not Implemented | | -| [WinMove](https://www.autohotkey.com/docs/commands/WinMove.htm) | Implemented | | -| [WinRestore](https://www.autohotkey.com/docs/commands/WinRestore.htm) | Implemented | | -| [WinSet](https://www.autohotkey.com/docs/commands/WinSet.htm) | Implemented | | -| [WinSetTitle](https://www.autohotkey.com/docs/commands/WinSetTitle.htm) | Implemented | | -| [WinShow](https://www.autohotkey.com/docs/commands/WinShow.htm) | Implemented | | -| [WinWait](https://www.autohotkey.com/docs/commands/WinWait.htm) | Implemented | | -| [WinWait[Not]Active](https://www.autohotkey.com/docs/commands/WinWaitActive.htm) | Implemented | | -| [WinWaitClose](https://www.autohotkey.com/docs/commands/WinWaitClose.htm) | Implemented | note | - - - - -### Sound - -| AutoHotkey Command | Status | Notes | -|----------------------------------------------------------------------------------------------|-----------------|-------| -| [SoundBeep](https://www.autohotkey.com/docs/commands/SoundBeep.htm) | Implemented | | -| [SoundGet](https://www.autohotkey.com/docs/commands/SoundGet.htm) | Implemented | | -| [SoundGetWaveVolume](https://www.autohotkey.com/docs/commands/SoundGetWaveVolume.htm) | Not Implemented | | -| [SoundPlay](https://www.autohotkey.com/docs/commands/SoundPlay.htm) | Implemented | | -| [SoundSet](https://www.autohotkey.com/docs/commands/SoundSet.htm) | Implemented | | -| [SoundSetWaveVolume](https://www.autohotkey.com/docs/commands/SoundSetWaveVolume.htm) | Not Implemented | note | - - - -### GUI - -GUI methods are largely unimplmented, except `ToolTip` and `TrayTip`. -We recommend using one of the many Python GUI libraries, such as [easygui](https://github.com/robertlugg/easygui), [pysimplegui](https://www.pysimplegui.org/en/latest/) or similar. - - -| AutoHotkey Command | Status | Notes | -|-------------------------------------------------------------------------------|-----------------|-------| -| [Gui](https://www.autohotkey.com/docs/commands/Gui.htm) | Not Implemented | | -| [Gui control types](https://www.autohotkey.com/docs/commands/GuiControls.htm) | Not Implemented | | -| [GuiControl](https://www.autohotkey.com/docs/commands/GuiControl.htm) | Not Implemented | | -| [GuiControlGet](https://www.autohotkey.com/docs/commands/GuiControlGet.htm) | Not Implemented | | -| [Gui ListView control](https://www.autohotkey.com/docs/commands/ListView.htm) | Not Implemented | | -| [Gui TreeView control](https://www.autohotkey.com/docs/commands/TreeView.htm) | Not Implemented | | -| [IfMsgBox](https://www.autohotkey.com/docs/commands/IfMsgBox.htm) | Not Implemented | | -| [InputBox](https://www.autohotkey.com/docs/commands/InputBox.htm) | Not Implemented | | -| [LoadPicture()](https://www.autohotkey.com/docs/commands/LoadPicture.htm) | Not Implemented | | -| [Menu](https://www.autohotkey.com/docs/commands/Menu.htm) | Not Implemented | | -| [MenuGetHandle()](https://www.autohotkey.com/docs/commands/MenuGetHandle.htm) | Not Implemented | | -| [MenuGetName()](https://www.autohotkey.com/docs/commands/MenuGetName.htm) | Not Implemented | | -| [MsgBox](https://www.autohotkey.com/docs/commands/MsgBox.htm) | Not Implemented | | -| [OnMessage()](https://www.autohotkey.com/docs/commands/OnMessage.htm) | Not Implemented | | -| [Progress](https://www.autohotkey.com/docs/commands/Progress.htm) | Not Implemented | | -| [SplashImage](https://www.autohotkey.com/docs/commands/Progress.htm) | Not Implemented | | -| [SplashTextOn/Off](https://www.autohotkey.com/docs/commands/SplashTextOn.htm) | Not Implemented | | -| [ToolTip](https://www.autohotkey.com/docs/commands/ToolTip.htm) | Implemented | | -| [TrayTip](https://www.autohotkey.com/docs/commands/TrayTip.htm) | Implemented | note | - - - -### Directives - -In general, all directives are technically usable, however many do not have applicable context in the Python library. - -Some directives are mentioned in tables above and are omitted from this table. - -| AutoHotkey Command | Notes | -|-----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------| -| [#HotkeyInterval](https://www.autohotkey.com/docs/commands/_HotkeyInterval.htm) | | -| [#HotkeyModifierTimeout](https://www.autohotkey.com/docs/commands/_HotkeyModifierTimeout.htm) | | -| [#Hotstring](https://www.autohotkey.com/docs/commands/_Hotstring.htm) | | -| [#Include[Again]](https://www.autohotkey.com/docs/commands/_Include.htm) | Using this directive is strongly discouraged as it is **very** likely to cause issues. Use with extreme caution. | -| [#InputLevel](https://www.autohotkey.com/docs/commands/_InputLevel.htm) | | -| [#KeyHistory](https://www.autohotkey.com/docs/commands/_KeyHistory.htm) | | -| [#MaxHotkeysPerInterval](https://www.autohotkey.com/docs/commands/_MaxHotkeysPerInterval.htm) | | -| [#MaxMem](https://www.autohotkey.com/docs/commands/_MaxMem.htm) | | -| [#MaxThreads](https://www.autohotkey.com/docs/commands/_MaxThreads.htm) | | -| [#MaxThreadsBuffer](https://www.autohotkey.com/docs/commands/_MaxThreadsBuffer.htm) | | -| [#MaxThreadsPerHotkey](https://www.autohotkey.com/docs/commands/_MaxThreadsPerHotkey.htm) | Hotkey callbacks are run in Python, so this largely won't have any significant effect | -| [#MenuMaskKey](https://www.autohotkey.com/docs/commands/_MenuMaskKey.htm) | | -| [#NoEnv](https://www.autohotkey.com/docs/commands/_NoEnv.htm) | | -| [#NoTrayIcon](https://www.autohotkey.com/docs/commands/_NoTrayIcon.htm) | If you use hotkeys or hotstrings, you probably also want to configure this as a hotkey transport option | -| [#Persistent](https://www.autohotkey.com/docs/commands/_Persistent.htm) | This is on by default in scripts run by this library | -| [#Requires](https://www.autohotkey.com/docs/commands/_Requires.htm) | | -| [#SingleInstance](https://www.autohotkey.com/docs/commands/_SingleInstance.htm) | This directive is provided by default (SingleInstance Off for the main thread) | -| [#UseHook](https://www.autohotkey.com/docs/commands/_UseHook.htm) | | -| [#Warn](https://www.autohotkey.com/docs/commands/_Warn.htm) | Not relevant for this library | -| [#AllowSameLineComments](https://www.autohotkey.com/docs/commands/_AllowSameLineComments.htm) | Not relevant for this library | -| [#ClipboardTimeout](https://www.autohotkey.com/docs/commands/_ClipboardTimeout.htm) | Not relevant for this library | -| [#CommentFlag](https://www.autohotkey.com/docs/commands/_CommentFlag.htm) | Not relevant for this library | -| [#ErrorStdOut](https://www.autohotkey.com/docs/commands/_ErrorStdOut.htm) | Not relevant for this library | -| [#EscapeChar](https://www.autohotkey.com/docs/commands/_EscapeChar.htm) | Not relevant for this library | -| [#InstallKeybdHook](https://www.autohotkey.com/docs/commands/_InstallKeybdHook.htm) | Not relevant for this library | -| [#InstallMouseHook](https://www.autohotkey.com/docs/commands/_InstallMouseHook.htm) | Not relevant for this library | -| [#If](https://www.autohotkey.com/docs/commands/_If.htm) | Not relevant for this library | -| [#IfTimeout](https://www.autohotkey.com/docs/commands/_IfTimeout.htm) | Not relevant for this library | diff --git a/docs/api/methods.rst b/docs/api/methods.rst new file mode 100644 index 00000000..658b8abe --- /dev/null +++ b/docs/api/methods.rst @@ -0,0 +1,560 @@ +.. 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 `_ + - Not Implemented + - + * - `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 `_ + - Not Implemented + - + * - `SetScrollLockState `_ + - Not Implemented + - + * - `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 `easygui `_\ , `pysimplegui `_ 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 Implemented + - + * - `IfMsgBox `_ + - Not Implemented + - + * - `InputBox `_ + - Not Implemented + - + * - `LoadPicture() `_ + - Not Implemented + - + * - `Menu `_ + - Not Implemented + - + * - `MenuGetHandle() `_ + - Not Implemented + - + * - `MenuGetName() `_ + - Not Implemented + - + * - `MsgBox `_ + - Not Implemented + - + * - `OnMessage() `_ + - Not Implemented + - + * - `Progress `_ + - Not Implemented + - + * - `SplashImage `_ + - Not Implemented + - + * - `SplashTextOn/Off `_ + - Not Implemented + - + * - `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 `_ + - + * - `#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 index 8ec76647..4a91d746 100644 --- a/docs/api/sync.rst +++ b/docs/api/sync.rst @@ -23,6 +23,11 @@ Window :members: :undoc-members: +AsyncControl +------------ +.. autoclass:: ahk._sync.window.Control + :members: + :undoc-members: AHK --- diff --git a/docs/conf.py b/docs/conf.py index a8fb76ff..97177e12 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,3 +41,8 @@ } always_document_param_types = True +typehints_use_signature = True +typehints_use_signature_return = True +html_css_files = [ + 'css/custom.css', +] From 16bb96b9d926b4fc88827e91dfd6ff78244bdf40 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 2 May 2023 16:43:52 -0700 Subject: [PATCH 367/588] 1.0.1 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1584686a..1c700068 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.0.0.post1 +version = 1.0.1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 367db85a52fde922e5ca78e320f2605cb5e441e9 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 May 2023 14:45:42 -0700 Subject: [PATCH 368/588] implement custom tray tip text --- ahk/_async/engine.py | 12 ++++++++++++ ahk/_async/transport.py | 4 +++- ahk/_constants.py | 6 ++++++ ahk/_sync/engine.py | 12 ++++++++++++ ahk/_sync/transport.py | 4 +++- ahk/templates/daemon.ahk | 6 ++++++ 6 files changed, 42 insertions(+), 2 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 7329e894..07eef051 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1367,6 +1367,18 @@ async def show_tooltip( 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 + # fmt: off @overload async def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 424d757e..ace16401 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -95,6 +95,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKImageSearch', 'AHKKeyState', 'AHKKeyWait', + 'AHKMenuTrayTip', 'AHKMouseClickDrag', 'AHKMouseGetPos', 'AHKMouseMove', @@ -565,7 +566,8 @@ async def function_call(self, function_name: Literal['AHKRegRead'], args: Option 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]]: ... # fmt: on async def function_call( diff --git a/ahk/_constants.py b/ahk/_constants.py index 0e4e00a9..de6e042a 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2603,6 +2603,12 @@ return FormatNoValueResponse() } +AHKMenuTrayTip(ByRef command) { + value := command[2] + Menu, Tray, Tip, %value% + return FormatNoValueResponse() +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index a8f66636..e72da0da 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1356,6 +1356,18 @@ def show_tooltip( 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 + # fmt: off @overload def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index ff280260..21ffb94a 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -87,6 +87,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKImageSearch', 'AHKKeyState', 'AHKKeyWait', + 'AHKMenuTrayTip', 'AHKMouseClickDrag', 'AHKMouseGetPos', 'AHKMouseMove', @@ -546,7 +547,8 @@ def function_call(self, function_name: Literal['AHKRegRead'], args: Optional[Lis 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]]: ... # fmt: on def function_call( diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 17af1821..86d23ce0 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2600,6 +2600,12 @@ AHKBlockInput(ByRef command) { return FormatNoValueResponse() } +AHKMenuTrayTip(ByRef command) { + value := command[2] + Menu, Tray, Tip, %value% + return FormatNoValueResponse() +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw From 8755ae2ac0a75d12cbafb0bded774950f506ba0d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 May 2023 15:49:19 -0700 Subject: [PATCH 369/588] menu tray icon commands --- .github/workflows/release.yaml | 2 +- ahk/_async/engine.py | 25 +++++++++++++++++++++++++ ahk/_async/transport.py | 7 +++++++ ahk/_constants.py | 13 +++++++++++++ ahk/_sync/engine.py | 25 +++++++++++++++++++++++++ ahk/_sync/transport.py | 7 +++++++ ahk/templates/daemon.ahk | 13 +++++++++++++ docs/README.md | 22 ++++++++++++++++++++++ docs/conf.py | 2 -- 9 files changed, 113 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b23911c0..6c4bb05c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,7 +19,7 @@ jobs: - name: build shell: bash run: | - python -m pip install --upgrade wheel setuptools build + python -m pip install --upgrade wheel setuptools build unasync tokenize-rt python -m build - name: Release PyPI shell: bash diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 07eef051..4c767dc3 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1379,6 +1379,31 @@ async def menu_tray_tooltip(self, value: str) -> None: 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 + # fmt: off @overload async def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index ace16401..54ce34ba 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -95,6 +95,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKImageSearch', 'AHKKeyState', 'AHKKeyWait', + 'AHKMenuTrayIcon', + 'AHKMenuTrayShow', 'AHKMenuTrayTip', 'AHKMouseClickDrag', 'AHKMouseGetPos', @@ -568,6 +570,11 @@ async def function_call(self, function_name: Literal['AHKRegWrite'], args: Optio 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]]: ... + # fmt: on async def function_call( diff --git a/ahk/_constants.py b/ahk/_constants.py index de6e042a..21310a58 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2609,6 +2609,19 @@ return FormatNoValueResponse() } +AHKMenuTrayShow(ByRef command) { + Menu, Tray, Icon + return FormatNoValueResponse() +} + +AHKMenuTrayIcon(ByRef command) { + filename := command[2] + icon_number := command[3] + freeze := command[4] + Menu, Tray, Icon, %filename%, %icon_number%,%freeze% + return FormatNoValueResponse() +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index e72da0da..654dff23 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1368,6 +1368,31 @@ def menu_tray_tooltip(self, value: str) -> None: 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 + # fmt: off @overload def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 21ffb94a..fec29104 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -87,6 +87,8 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKImageSearch', 'AHKKeyState', 'AHKKeyWait', + 'AHKMenuTrayIcon', + 'AHKMenuTrayShow', 'AHKMenuTrayTip', 'AHKMouseClickDrag', 'AHKMouseGetPos', @@ -549,6 +551,11 @@ def function_call(self, function_name: Literal['AHKRegWrite'], args: Optional[Li 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]]: ... + # fmt: on def function_call( diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 86d23ce0..775f2e4d 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2606,6 +2606,19 @@ AHKMenuTrayTip(ByRef command) { return FormatNoValueResponse() } +AHKMenuTrayShow(ByRef command) { + Menu, Tray, Icon + return FormatNoValueResponse() +} + +AHKMenuTrayIcon(ByRef command) { + filename := command[2] + icon_number := command[3] + freeze := command[4] + Menu, Tray, Icon, %filename%, %icon_number%,%freeze% + return FormatNoValueResponse() +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw diff --git a/docs/README.md b/docs/README.md index 8324ed7b..ac20c4b7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -322,6 +322,28 @@ ahk = AHK(directives=[NoTrayIcon]) By default, some directives are automatically added to ensure functionality and are merged with any user-provided 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') + +# Show the tray icon that was previously hidden by ``NoTrayIcon`` +ahk.menu_tray_icon_show() +``` + ## Registry methods You can read/write/delete registry keys: diff --git a/docs/conf.py b/docs/conf.py index 97177e12..6bb5ce5d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,8 +41,6 @@ } always_document_param_types = True -typehints_use_signature = True -typehints_use_signature_return = True html_css_files = [ 'css/custom.css', ] From 4f84ebd2a39ebcbed0730608442787dd6833451d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 May 2023 15:58:36 -0700 Subject: [PATCH 370/588] 1.1.0rc1 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1c700068..c9f68906 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.0.1 +version = 1.1.0rc1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 23e744042fc07cec2f60f3492f4cc0ccb0be09d1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 May 2023 19:08:35 -0700 Subject: [PATCH 371/588] unasync changes --- .pre-commit-config.yaml | 2 +- .unasync-rewrite.py | 39 --------------------------------------- pyproject.toml | 2 +- 3 files changed, 2 insertions(+), 41 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d9a4d051..98cd40bf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: files: ^(ahk/_async/.*\.py|\.unasync-rewrite\.py|tests/_async/.*\.py) pass_filenames: false additional_dependencies: - - unasync + - git+https://github.com/spyoungtech/unasync.git@unasync-remove - tokenize_rt - black - id: set-constants diff --git a/.unasync-rewrite.py b/.unasync-rewrite.py index 22378572..d29a0680 100644 --- a/.unasync-rewrite.py +++ b/.unasync-rewrite.py @@ -1,44 +1,15 @@ -import ast import os import shutil import subprocess import sys import black -from black import check_stability_and_equivalence -from tokenize_rt import reversed_enumerate -from tokenize_rt import src_to_tokens -from tokenize_rt import tokens_to_src GIT_EXECUTABLE = shutil.which('git') changes = 0 -def _rewrite_file(filename: str) -> int: - with open(filename, encoding='UTF-8') as f: - contents = f.read() - tree = ast.parse(contents, filename=filename) - tokens = src_to_tokens(contents) - nodes_on_lines_to_remove = [] - for tok in tokens: - if tok.name == 'COMMENT' and 'unasync: remove' in tok.src: - nodes_on_lines_to_remove.append(tok.line) - lines_to_remove = set() - for node in ast.walk(tree): - if hasattr(node, 'lineno') and node.lineno in nodes_on_lines_to_remove: - for lineno in range(node.lineno, node.end_lineno + 1): - lines_to_remove.add(lineno) - for i, tok in reversed_enumerate(tokens): - if tok.line in lines_to_remove: - tokens.pop(i) - new_contents = tokens_to_src(tokens) - if new_contents != contents: - with open(filename, 'w') as f: - f.write(new_contents) - return new_contents != contents - - def _copyfunc(src, dst, *, follow_symlinks=True): global changes with open(src, encoding='UTF-8') as f: @@ -70,17 +41,7 @@ def main() -> int: if os.path.isdir('build'): shutil.rmtree('build') subprocess.run([sys.executable, 'setup.py', 'build_py'], check=True) - for root, dirs, files in os.walk('build/lib/ahk/_sync'): - for fname in files: - if fname.endswith('.py'): - fp = os.path.join(root, fname) - _rewrite_file(fp) subprocess.run([sys.executable, '_tests_setup.py', 'build_py'], check=True) - for root, dirs, files in os.walk('build/lib/tests/_sync'): - for fname in files: - if fname.endswith('.py'): - fp = os.path.join(root, fname) - _rewrite_file(fp) 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) diff --git a/pyproject.toml b/pyproject.toml index 209e9dcb..6a69f78a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,2 @@ [build-system] -requires = ["setuptools", "unasync", "tokenize-rt"] +requires = ["setuptools", "git+https://github.com/spyoungtech/unasync.git@unasync-remove", "tokenize-rt"] From a73d6200e680020856a557c1cd616b50264cd343 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 May 2023 19:16:22 -0700 Subject: [PATCH 372/588] fix dependency specification --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6a69f78a..9bee86a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,2 @@ [build-system] -requires = ["setuptools", "git+https://github.com/spyoungtech/unasync.git@unasync-remove", "tokenize-rt"] +requires = ["setuptools", "unasync @ https://github.com/spyoungtech/unasync/archive/refs/heads/unasync-remove.zip", "tokenize-rt"] From 512d9b0bf84fb85a2190d80f7122aaad1816e9fc Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 May 2023 19:24:06 -0700 Subject: [PATCH 373/588] 1.1.0rc2 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index c9f68906..fe870c6b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.1.0rc1 +version = 1.1.0rc2 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From c8b7d87588cb7bd4d5d1f39428c00a0f6d489427 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 May 2023 19:46:07 -0700 Subject: [PATCH 374/588] 1.1.0 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index fe870c6b..905517c2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.1.0rc2 +version = 1.1.0 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 7556ea17a443ac34838be4c86f21471bd089ca51 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 May 2023 20:20:02 -0700 Subject: [PATCH 375/588] from_pid and from_mouse_position classmethods (#197) reintroduce from_pid and from_mouse_position --- ahk/_async/window.py | 8 ++++++++ ahk/_sync/window.py | 29 ++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 5b04df1d..5de120ad 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -648,6 +648,14 @@ async def move( blocking=blocking, ) + @classmethod + async def from_pid(cls, engine: AsyncAHK, pid: int) -> Optional[AsyncWindow]: + return await engine.win_get(title=f'ahk_pid {pid}') + + @classmethod + async def from_mouse_position(cls, engine: AsyncAHK) -> Optional[AsyncWindow]: + return await engine.win_get_from_mouse_position() + class AsyncControl: def __init__(self, window: AsyncWindow, hwnd: str, control_class: str): diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index e13ff72e..95d10c22 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -613,16 +613,27 @@ def is_active(self) -> bool: 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, - ) + 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, + ) + + @classmethod + def from_pid(cls, engine: AHK, pid: int) -> Optional[Window]: + return engine.win_get(title=f'ahk_pid {pid}') + @classmethod + def from_mouse_position(cls, engine: AHK) -> Optional[Window]: + return engine.win_get_from_mouse_position() class Control: def __init__(self, window: Window, hwnd: str, control_class: str): From 594f6b1b10f16390a255e6e94683b7e265391979 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 May 2023 20:21:04 -0700 Subject: [PATCH 376/588] 1.1.1 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 905517c2..b6f79bfe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.1.0 +version = 1.1.1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From e4d00ae55671cdaef763251fb185c88b62c67d5b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 12:48:17 -0700 Subject: [PATCH 377/588] [pre-commit.ci] pre-commit autoupdate (#198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.1.0 → 23.3.0](https://github.com/psf/black/compare/23.1.0...23.3.0) - https://github.com/asottile/reorder_python_imports → https://github.com/asottile/reorder-python-imports - [github.com/pre-commit/mirrors-mypy: v0.991 → v1.2.0](https://github.com/pre-commit/mirrors-mypy/compare/v0.991...v1.2.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 98cd40bf..cab76add 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '23.1.0' + rev: '23.3.0' hooks: - id: black args: @@ -39,13 +39,13 @@ repos: - "-l" - "120" exclude: ^(ahk/_sync/.*\.py) -- repo: https://github.com/asottile/reorder_python_imports +- repo: https://github.com/asottile/reorder-python-imports rev: v3.9.0 hooks: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.991' + rev: 'v1.2.0' hooks: - id: mypy args: From 6745a702acae3940f0b046004f99ab6424a0e1f3 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 10 May 2023 18:12:21 -0700 Subject: [PATCH 378/588] gh-199 ensure image path is always passed in image_search --- ahk/_async/engine.py | 2 +- ahk/_sync/engine.py | 2 +- tests/_async/test_screen.py | 10 ++++++++++ tests/_sync/test_screen.py | 10 ++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 4c767dc3..83696a8f 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2698,7 +2698,7 @@ async def image_search( args = [str(x1), str(y1), str(x2), str(y2)] if options: opts = ' '.join(f'*{opt}' for opt in options) - args.append(opts) + args.append(opts + f' {image_path}') else: args.append(image_path) resp = await self._transport.function_call('AHKImageSearch', args, blocking=blocking) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 654dff23..afb760e3 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2687,7 +2687,7 @@ def image_search( args = [str(x1), str(y1), str(x2), str(y2)] if options: opts = ' '.join(f'*{opt}' for opt in options) - args.append(opts) + args.append(opts + f' {image_path}') else: args.append(image_path) resp = self._transport.function_call('AHKImageSearch', args, blocking=blocking) diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 709f8f7d..d59ab302 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -64,6 +64,16 @@ async def test_pixel_search(self): 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('testimage.png', 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) diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index 10d6c18e..00ff707b 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -64,6 +64,16 @@ def test_pixel_search(self): 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('testimage.png', 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) From bb916f21ae82c4e50a3761e51225cf0a26bf9e6e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 10 May 2023 18:28:12 -0700 Subject: [PATCH 379/588] fix coord_mode for ImageSearch --- ahk/_async/engine.py | 4 ++++ ahk/_constants.py | 15 +++++++++++++++ ahk/_sync/engine.py | 3 +++ ahk/templates/daemon.ahk | 15 +++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 83696a8f..634e13b4 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2701,6 +2701,10 @@ async def image_search( args.append(opts + f' {image_path}') else: args.append(image_path) + + if coord_mode is not None: + args.append(coord_mode) + resp = await self._transport.function_call('AHKImageSearch', args, blocking=blocking) return resp diff --git a/ahk/_constants.py b/ahk/_constants.py index 21310a58..5aaa5817 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -1590,6 +1590,13 @@ y1 := command[3] x2 := command[4] y2 := command[5] + coord_mode := command[7] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode, Pixel, %coord_mode% + } if (x2 = "A_ScreenWidth") { x2 := A_ScreenWidth @@ -1597,7 +1604,15 @@ 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(EXCEPTIONRESPONSEMESSAGE, "there was a problem that prevented the command from conducting the search (such as failure to open the image file or a badly formatted option)") } else if (ErrorLevel = 1) { diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index afb760e3..a4c12d38 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2690,6 +2690,9 @@ def image_search( args.append(opts + f' {image_path}') else: args.append(image_path) + + if coord_mode is not None: + args.append(coord_mode) resp = self._transport.function_call('AHKImageSearch', args, blocking=blocking) return resp diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 775f2e4d..415484b0 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1587,6 +1587,13 @@ AHKImageSearch(ByRef command) { y1 := command[3] x2 := command[4] y2 := command[5] + coord_mode := command[7] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode, Pixel, %coord_mode% + } if (x2 = "A_ScreenWidth") { x2 := A_ScreenWidth @@ -1594,7 +1601,15 @@ AHKImageSearch(ByRef command) { 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(EXCEPTIONRESPONSEMESSAGE, "there was a problem that prevented the command from conducting the search (such as failure to open the image file or a badly formatted option)") } else if (ErrorLevel = 1) { From b6e8abfc74ff373ba4b72aed61c12903a1ebabcc Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 10 May 2023 18:30:18 -0700 Subject: [PATCH 380/588] ensure global state is reverted in AHKWinSetTransColor --- ahk/_constants.py | 5 +++++ ahk/templates/daemon.ahk | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/ahk/_constants.py b/ahk/_constants.py index 5aaa5817..673c0def 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -1577,6 +1577,11 @@ WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() {% endblock AHKWinSetTransColor %} } diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 415484b0..3ab45313 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1574,6 +1574,11 @@ AHKWinSetTransColor(ByRef command) { WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% + + DetectHiddenWindows, %current_detect_hw% + SetTitleMatchMode, %current_match_mode% + SetTitleMatchMode, %current_match_speed% + return FormatNoValueResponse() {% endblock AHKWinSetTransColor %} } From 245d56413bfd2491e1cbb88c9df13c071ddb8ccf Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 10 May 2023 18:45:10 -0700 Subject: [PATCH 381/588] respect global state options for AHKWinClose --- ahk/_constants.py | 4 +++- ahk/templates/daemon.ahk | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 673c0def..08496046 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -161,11 +161,13 @@ DetectHiddenWindows, %detect_hw% } + + WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% - WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% return FormatNoValueResponse() {% endblock AHKWinClose %} diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 3ab45313..54585ee9 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -158,11 +158,13 @@ AHKWinClose(ByRef command) { DetectHiddenWindows, %detect_hw% } + + WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% + DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% - WinClose, %title%, %text%, %secondstowait%, %extitle%, %extext% return FormatNoValueResponse() {% endblock AHKWinClose %} From dd6b98a66693f3f7e26ce76e28d2ff42cbee8fa0 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 10 May 2023 18:59:19 -0700 Subject: [PATCH 382/588] 1.1.2 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b6f79bfe..4acc7814 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.1.1 +version = 1.1.2 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 08eaab88138dc622dab86b6160f530aaa35f4c59 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 02:21:52 +0000 Subject: [PATCH 383/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.2.0 → v1.3.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.2.0...v1.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cab76add..9586e973 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.2.0' + rev: 'v1.3.0' hooks: - id: mypy args: From c5b14313a1365c321f7e5b8ee654a13f8903ee83 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 16 May 2023 17:30:28 -0700 Subject: [PATCH 384/588] fallback to baseloader if packageloader fails --- ahk/_async/transport.py | 14 +++++++++++--- ahk/_sync/transport.py | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 54ce34ba..20131eff 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -632,9 +632,17 @@ def __init__( self.__template: jinja2.Template self._jinja_env: jinja2.Environment if jinja_loader is None: - self._jinja_env = jinja2.Environment( - loader=jinja2.PackageLoader('ahk', 'templates'), trim_blocks=True, autoescape=False - ) + 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: diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index fec29104..f5c9a99d 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -606,9 +606,17 @@ def __init__( self.__template: jinja2.Template self._jinja_env: jinja2.Environment if jinja_loader is None: - self._jinja_env = jinja2.Environment( - loader=jinja2.PackageLoader('ahk', 'templates'), trim_blocks=True, autoescape=False - ) + 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: From bc536fadb049145965c05a68d4ad96dacaf1be28 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 16 May 2023 17:34:03 -0700 Subject: [PATCH 385/588] prepre rc release --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4acc7814..c30e00de 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.1.2 +version = 1.1.3rc1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 58a61cf1134900614a5e0a79c70445f681c84027 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 17 May 2023 12:23:51 -0700 Subject: [PATCH 386/588] add packageloader fallback in threaded hotkey transport --- ahk/_hotkey.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index c09650c2..d730408c 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -124,9 +124,17 @@ def __init__(self, executable_path: str, default_ex_handler: Optional[Callable[[ self._callback_queue: Queue[Union[str, Type[STOP]]] = Queue() self._listener_thread: Optional[threading.Thread] = None self._dispatcher_thread: Optional[threading.Thread] = None - self._jinja_env: jinja2.Environment = jinja2.Environment( - loader=jinja2.PackageLoader('ahk', 'templates'), autoescape=False - ) + loader: jinja2.BaseLoader + 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('hotkeys.ahk') From 512ac8b128a5ffe7a945b6dce4cb478fb6f4da90 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 17 May 2023 12:27:28 -0700 Subject: [PATCH 387/588] prepare 1.1.3rc2 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index c30e00de..b00532ca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.1.3rc1 +version = 1.1.3rc2 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From d07c45bf91769528316bc081310c6efff9e5def8 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 17 May 2023 15:53:08 -0700 Subject: [PATCH 388/588] 1.1.3 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b00532ca..cba7ea43 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.1.3rc2 +version = 1.1.3 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 143da379d30fc962313745ab4c0922b9b42ec810 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 13 Jun 2023 13:29:29 -0700 Subject: [PATCH 389/588] raise protocol error from ValueError --- ahk/_async/transport.py | 12 +++++++++--- ahk/_sync/transport.py | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 20131eff..93b3bbca 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -720,7 +720,13 @@ async def _send_nonblocking( content_buffer = BytesIO() content_buffer.write(tom) content_buffer.write(num_lines) - for _ in range(int(num_lines) + 1): + try: + lines_to_read = int(num_lines) + 1 + except ValueError as e: + raise AHKProtocolError( + 'Unexpected data received. This is usually the result of an unhandled error in the AHK process.' + ) from e + for _ in range(lines_to_read): part = await proc.readline() content_buffer.write(part) content = content_buffer.getvalue()[:-1] @@ -767,10 +773,10 @@ async def send( content_buffer.write(num_lines) try: lines_to_read = int(num_lines) + 1 - except ValueError: + except ValueError as e: raise AHKProtocolError( 'Unexpected data received. This is usually the result of an unhandled error in the AHK process.' - ) + ) from e for _ in range(lines_to_read): part = await self._proc.readline() content_buffer.write(part) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index f5c9a99d..78f4808c 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -694,7 +694,13 @@ def _send_nonblocking( content_buffer = BytesIO() content_buffer.write(tom) content_buffer.write(num_lines) - for _ in range(int(num_lines) + 1): + try: + lines_to_read = int(num_lines) + 1 + except ValueError as e: + raise AHKProtocolError( + 'Unexpected data received. This is usually the result of an unhandled error in the AHK process.' + ) from e + for _ in range(lines_to_read): part = proc.readline() content_buffer.write(part) content = content_buffer.getvalue()[:-1] @@ -733,10 +739,10 @@ def send( content_buffer.write(num_lines) try: lines_to_read = int(num_lines) + 1 - except ValueError: + except ValueError as e: raise AHKProtocolError( 'Unexpected data received. This is usually the result of an unhandled error in the AHK process.' - ) + ) from e for _ in range(lines_to_read): part = self._proc.readline() content_buffer.write(part) From a9987444a1ce9dfec117dc0c9a47de543361dd3c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 03:27:44 +0000 Subject: [PATCH 390/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.9.0 → v3.10.0](https://github.com/asottile/reorder-python-imports/compare/v3.9.0...v3.10.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9586e973..ee46e21d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: - "120" exclude: ^(ahk/_sync/.*\.py) - repo: https://github.com/asottile/reorder-python-imports - rev: v3.9.0 + rev: v3.10.0 hooks: - id: reorder-python-imports From 2eadf2c89ede7948886cbfe351fbd3ed5c4a8a89 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 26 Jun 2023 17:15:34 -0700 Subject: [PATCH 391/588] add thread/concurrency safety --- ahk/_async/transport.py | 50 ++++++++++++++++++++++++----------------- ahk/_sync/transport.py | 48 ++++++++++++++++++++++----------------- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 93b3bbca..528f8c1c 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -6,6 +6,7 @@ import subprocess import sys import tempfile +import threading import warnings from abc import ABC from abc import abstractmethod @@ -631,6 +632,8 @@ def __init__( 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 if jinja_loader is None: try: loader: jinja2.BaseLoader @@ -667,7 +670,8 @@ async def init(self) -> 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: - self._proc = await self._create_process() + async with self.lock: + self._proc = await self._create_process() if caught_warnings: for warning in caught_warnings: warnings.warn(warning.message, warning.category, stacklevel=2) @@ -679,6 +683,11 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A 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, **kwargs) + @property + def lock(self) -> Any: + return self._a_execution_lock # unasync: remove + return self._execution_lock + async def _create_process( self, template: Optional[jinja2.Template] = None, **template_kwargs: Any ) -> AsyncAHKProcess: @@ -764,25 +773,26 @@ async def send( ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: msg = request.format() assert self._proc is not None - 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: - raise AHKProtocolError( - 'Unexpected data received. This is usually the result of an unhandled error in the AHK process.' - ) 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 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: + raise AHKProtocolError( + 'Unexpected data received. This is usually the result of an unhandled error in the AHK process.' + ) 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 diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 78f4808c..3d688401 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -6,6 +6,7 @@ import subprocess import sys import tempfile +import threading import warnings from abc import ABC from abc import abstractmethod @@ -605,6 +606,7 @@ def __init__( self._temp_script: Optional[str] = None self.__template: jinja2.Template self._jinja_env: jinja2.Environment + self._execution_lock = threading.Lock() if jinja_loader is None: try: loader: jinja2.BaseLoader @@ -641,7 +643,8 @@ def init(self) -> None: def start(self) -> None: assert self._proc is None, 'cannot start a process twice' with warnings.catch_warnings(record=True) as caught_warnings: - self._proc = self._create_process() + with self.lock: + self._proc = self._create_process() if caught_warnings: for warning in caught_warnings: warnings.warn(warning.message, warning.category, stacklevel=2) @@ -653,6 +656,10 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A 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, **kwargs) + @property + def lock(self) -> Any: + return self._execution_lock + def _create_process( self, template: Optional[jinja2.Template] = None, **template_kwargs: Any ) -> SyncAHKProcess: @@ -730,25 +737,26 @@ def send( ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: msg = request.format() assert self._proc is not None - 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: - raise AHKProtocolError( - 'Unexpected data received. This is usually the result of an unhandled error in the AHK process.' - ) 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 + 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: + raise AHKProtocolError( + 'Unexpected data received. This is usually the result of an unhandled error in the AHK process.' + ) 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( From b6a4dd89a3730b3afe4b4102a32d4d6d74fe250a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 02:48:24 +0000 Subject: [PATCH 392/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.3.0 → v1.4.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.3.0...v1.4.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee46e21d..ac948ac0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.3.0' + rev: 'v1.4.1' hooks: - id: mypy args: From 8d72d3c67b3006fb9a1329b3627231a0f04394ea Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 7 Jul 2023 23:44:31 -0700 Subject: [PATCH 393/588] add methods for removing hotkeys and hotstrings --- ahk/_async/engine.py | 21 +++++++++++++++++++ ahk/_async/transport.py | 16 +++++++++++++++ ahk/_hotkey.py | 40 ++++++++++++++++++++++++++++++++++++ ahk/_sync/engine.py | 22 ++++++++++++++++++++ ahk/_sync/transport.py | 19 +++++++++++++++++ tests/_async/test_hotkeys.py | 22 +++++++++++++++++++- tests/_async/test_keys.py | 20 ++++++++++++++++++ tests/_sync/test_hotkeys.py | 22 +++++++++++++++++++- tests/_sync/test_keys.py | 20 ++++++++++++++++++ 9 files changed, 200 insertions(+), 2 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 634e13b4..eac26765 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -189,6 +189,27 @@ def add_hotstring( 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 diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 528f8c1c..74027e21 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -362,6 +362,22 @@ def add_hotstring(self, hotstring: Hotstring) -> None: warnings.warn(warning.message, warning.category, stacklevel=2) return None + def remove_hotkey(self, hotkey: Hotkey) -> None: + self._hotkey_transport.remove_hotkey(hotkey) + return None + + def clear_hotkeys(self) -> None: + self._hotkey_transport.clear_hotkeys() + return None + + def remove_hotstring(self, hotstring: Hotstring) -> None: + self._hotkey_transport.remove_hotstring(hotstring) + return None + + def clear_hotstrings(self) -> None: + self._hotkey_transport.clear_hotstrings() + return None + def start_hotkeys(self) -> None: return self._hotkey_transport.start() diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index d730408c..40331d40 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -7,6 +7,7 @@ import subprocess import sys import threading +import time import warnings from abc import ABC from abc import abstractmethod @@ -102,6 +103,38 @@ def add_hotstring(self, hotstring: Hotstring) -> None: # 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: @@ -170,6 +203,13 @@ def start(self) -> None: 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, 6): + if self._proc is not None: + break + logging.debug(f'stop called before dispatched has started proc. Waiting for proc ({i}/5)') + time.sleep(0.2) assert self._proc is not None self._running = False diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index a4c12d38..bd82bfab 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -185,6 +185,27 @@ def add_hotstring( 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 @@ -2693,6 +2714,7 @@ def image_search( if coord_mode is not None: args.append(coord_mode) + resp = self._transport.function_call('AHKImageSearch', args, blocking=blocking) return resp diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 3d688401..8b2ede27 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -343,6 +343,25 @@ def add_hotstring(self, hotstring: Hotstring) -> None: warnings.warn(warning.message, warning.category, stacklevel=2) return None + def remove_hotkey(self, hotkey: Hotkey) -> None: + self._hotkey_transport.remove_hotkey(hotkey) + return None + + def clear_hotkeys(self) -> None: + self._hotkey_transport.clear_hotkeys() + return None + + def remove_hotstring(self, hotstring: Hotstring) -> None: + self._hotkey_transport.remove_hotstring(hotstring) + return None + + def clear_hotstrings(self) -> None: + self._hotkey_transport.clear_hotstrings() + return None + + + + def start_hotkeys(self) -> None: return self._hotkey_transport.start() diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index 6e2b618a..804252e9 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -14,7 +14,7 @@ sleep = time.sleep -class TestMouseAsync(IsolatedAsyncioTestCase): +class TestHotkeysAsync(IsolatedAsyncioTestCase): win: AsyncWindow async def asyncSetUp(self) -> None: @@ -47,3 +47,23 @@ def side_effect(): 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() diff --git a/tests/_async/test_keys.py b/tests/_async/test_keys.py index 87650e02..7fa44c40 100644 --- a/tests/_async/test_keys.py +++ b/tests/_async/test_keys.py @@ -48,6 +48,26 @@ async def test_hotstring(self): assert 'by the way' 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) diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py index d0cece8f..ec13a58d 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -11,7 +11,7 @@ sleep = time.sleep -class TestMouseAsync(TestCase): +class TestHotkeysAsync(TestCase): win: Window def setUp(self) -> None: @@ -44,3 +44,23 @@ def side_effect(): 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() diff --git a/tests/_sync/test_keys.py b/tests/_sync/test_keys.py index b4be92af..f8269404 100644 --- a/tests/_sync/test_keys.py +++ b/tests/_sync/test_keys.py @@ -47,6 +47,26 @@ def test_hotstring(self): assert 'by the way' 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) From cf375073cb4e1ed99958236f44afa2df52be91ea Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 7 Jul 2023 23:55:25 -0700 Subject: [PATCH 394/588] update readme for new hotstring and hotkey methods --- docs/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index ac20c4b7..ebc4b078 100644 --- a/docs/README.md +++ b/docs/README.md @@ -72,12 +72,20 @@ def my_ex_handler(hotkey: str, exception: 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 while the process is running, the underlying AHK process is restarted automatically +- 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) @@ -100,6 +108,13 @@ 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 hotkey by the trigger sequence +ahk.clear_hotstrings() # remove all registered hotstrings +``` + ## Mouse ```python From 660a6a57be748ec1cbc451aa63e3f6f7a8ad214f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 7 Jul 2023 23:55:49 -0700 Subject: [PATCH 395/588] prepare 1.2.0rc1 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index cba7ea43..de24d970 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.1.3 +version = 1.2.0rc1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From dc3b4bdef58f837143c3644cfdb08962065b037a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 8 Jul 2023 00:04:31 -0700 Subject: [PATCH 396/588] fix typo --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index ebc4b078..59d7b586 100644 --- a/docs/README.md +++ b/docs/README.md @@ -111,7 +111,7 @@ ahk.add_hotstring('btw', my_callback) # call python function in response to the You can also remove hotstrings: ```python -ahk.remove_hotstring('btw') # remove hotkey by the trigger sequence +ahk.remove_hotstring('btw') # remove a hotstring by its trigger sequence ahk.clear_hotstrings() # remove all registered hotstrings ``` From be53d46afd6451d6a4070a828b93ed7b3717ebd5 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 8 Jul 2023 09:49:25 -0700 Subject: [PATCH 397/588] improve stop hotkey performance --- ahk/_hotkey.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index 40331d40..7cb076a4 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -205,11 +205,11 @@ def start(self) -> None: 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, 6): + 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}/5)') - time.sleep(0.2) + 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 From 680452a452e662acf565d1494323730d612d7b45 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 9 Jul 2023 11:52:39 -0700 Subject: [PATCH 398/588] allow directives to be applied to hotkeys --- ahk/_async/transport.py | 2 +- ahk/_constants.py | 4 ++++ ahk/_hotkey.py | 15 +++++++++++++-- ahk/_sync/transport.py | 5 +---- ahk/directives.py | 3 ++- ahk/templates/hotkeys.ahk | 4 ++++ 6 files changed, 25 insertions(+), 8 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 74027e21..5460115a 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -337,7 +337,7 @@ def __init__( **kwargs: Any, ): self._executable_path: str = _resolve_executable_path(executable_path=executable_path) - self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) + self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path, directives=directives) self._directives: list[Union[Directive, Type[Directive]]] = directives or [] def on_clipboard_change( diff --git a/ahk/_constants.py b/ahk/_constants.py index 08496046..8cbf9474 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2777,6 +2777,10 @@ """ HOTKEYS_SCRIPT_TEMPLATE = r"""#Persistent +{% for directive in directives %} +{% if directive.apply_to_hotkeys_process %}{{ directive }}{% endif %} +{% endfor %} + {% if on_clipboard %} OnClipboardChange("ClipChanged") {% endif %} diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index 7cb076a4..ba50e83c 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -25,6 +25,8 @@ import jinja2 +from .directives import Directive + if sys.version_info >= (3, 10): from typing import ParamSpec else: @@ -150,13 +152,19 @@ class STOP: class ThreadedHotkeyTransport(HotkeyTransportBase): - def __init__(self, executable_path: str, default_ex_handler: Optional[Callable[[str, Exception], Any]] = None): + def __init__( + self, + executable_path: str, + default_ex_handler: Optional[Callable[[str, Exception], Any]] = None, + directives: Optional[list[Directive | Type[Directive]]] = None, + ): super().__init__(executable_path=executable_path, default_ex_handler=default_ex_handler) 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 + self._directives = directives loader: jinja2.BaseLoader try: loader = jinja2.PackageLoader('ahk', 'templates') @@ -281,7 +289,10 @@ def _render_hotkey_tempate(self) -> str: else: on_clipboard = False ret = self._template.render( - hotkeys=list(self._hotkeys.values()), hotstrings=self._hotstrings.values(), on_clipboard=on_clipboard + hotkeys=list(self._hotkeys.values()), + hotstrings=self._hotstrings.values(), + on_clipboard=on_clipboard, + directives=self._directives, ) return ret diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 8b2ede27..ec6559ec 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -318,7 +318,7 @@ def __init__( **kwargs: Any, ): self._executable_path: str = _resolve_executable_path(executable_path=executable_path) - self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path) + self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path, directives=directives) self._directives: list[Union[Directive, Type[Directive]]] = directives or [] def on_clipboard_change( @@ -359,9 +359,6 @@ def clear_hotstrings(self) -> None: self._hotkey_transport.clear_hotstrings() return None - - - def start_hotkeys(self) -> None: return self._hotkey_transport.start() diff --git a/ahk/directives.py b/ahk/directives.py index 259de495..f869a09d 100644 --- a/ahk/directives.py +++ b/ahk/directives.py @@ -30,7 +30,8 @@ class Directive(SimpleNamespace, metaclass=DirectiveMeta): """ def __init__(self, **kwargs: Any): - super().__init__(name=self.name, **kwargs) + 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 diff --git a/ahk/templates/hotkeys.ahk b/ahk/templates/hotkeys.ahk index af9e471d..57a1d29b 100644 --- a/ahk/templates/hotkeys.ahk +++ b/ahk/templates/hotkeys.ahk @@ -1,4 +1,8 @@ #Persistent +{% for directive in directives %} +{% if directive.apply_to_hotkeys_process %}{{ directive }}{% endif %} +{% endfor %} + {% if on_clipboard %} OnClipboardChange("ClipChanged") {% endif %} From 5bfc30d29341fc15790418705d4d66769d7f5f47 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 9 Jul 2023 11:53:25 -0700 Subject: [PATCH 399/588] prepare 1.2.0rc2 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index de24d970..8fe9a09e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.2.0rc1 +version = 1.2.0rc2 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 2084000176f71e1ed281a1a410d67aff875b7d4c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 9 Jul 2023 12:02:45 -0700 Subject: [PATCH 400/588] make template compatible with and without trim_blocks --- ahk/_constants.py | 5 ++++- ahk/templates/hotkeys.ahk | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 8cbf9474..e52cfcf1 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2778,7 +2778,10 @@ HOTKEYS_SCRIPT_TEMPLATE = r"""#Persistent {% for directive in directives %} -{% if directive.apply_to_hotkeys_process %}{{ directive }}{% endif %} +{% if directive.apply_to_hotkeys_process %} + +{{ directive }} +{% endif %} {% endfor %} {% if on_clipboard %} diff --git a/ahk/templates/hotkeys.ahk b/ahk/templates/hotkeys.ahk index 57a1d29b..d40361d7 100644 --- a/ahk/templates/hotkeys.ahk +++ b/ahk/templates/hotkeys.ahk @@ -1,6 +1,9 @@ #Persistent {% for directive in directives %} -{% if directive.apply_to_hotkeys_process %}{{ directive }}{% endif %} +{% if directive.apply_to_hotkeys_process %} + +{{ directive }} +{% endif %} {% endfor %} {% if on_clipboard %} From 1bcf4bd537b19516c0dc9e11e4a0d8ff4740c322 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 9 Jul 2023 12:06:16 -0700 Subject: [PATCH 401/588] set default when directives are not provided --- ahk/_hotkey.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index ba50e83c..d6489df9 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -59,7 +59,12 @@ def _default_ex_handler(failure: Union[str, int], ex: Exception) -> None: class HotkeyTransportBase(ABC): - def __init__(self, executable_path: str, default_ex_handler: Optional[Callable[[str, Exception], Any]] = None): + def __init__( + self, + executable_path: str, + default_ex_handler: Optional[Callable[[str, Exception], Any]] = None, + directives: Optional[list[Directive | Type[Directive]]] = None, + ): 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 @@ -68,6 +73,9 @@ def __init__(self, executable_path: str, default_ex_handler: Optional[Callable[[ 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]] = directives @property def _callback_registry(self) -> Dict[str, Union[Hotkey, Hotstring]]: @@ -158,13 +166,12 @@ def __init__( default_ex_handler: Optional[Callable[[str, Exception], Any]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, ): - super().__init__(executable_path=executable_path, default_ex_handler=default_ex_handler) + super().__init__(executable_path=executable_path, default_ex_handler=default_ex_handler, directives=directives) 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 - self._directives = directives loader: jinja2.BaseLoader try: loader = jinja2.PackageLoader('ahk', 'templates') From 58a1e158be47ba319019e78b2d87bd9d413cb261 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 9 Jul 2023 12:36:14 -0700 Subject: [PATCH 402/588] document directives for hotkeys process --- docs/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/README.md b/docs/README.md index 59d7b586..ebba2d3d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -337,6 +337,20 @@ 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 From e42b1b30891f94eb646f71375c79e3cee390702b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 9 Jul 2023 21:37:35 -0700 Subject: [PATCH 403/588] support asterisk in hotkey options --- ahk/_hotkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index 7cb076a4..026a4dea 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -406,7 +406,7 @@ def _validate(self) -> None: 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() + r'(\*|\?|C|C1|K\d+|O|P\d+|S[IPE]|T|Z)+', self.options.upper() ), f'Invalid options: {self.options!r}' return None From b497c08003dfeb616e7d9abecf8203042a16bbd7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 04:03:34 +0000 Subject: [PATCH 404/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.3.0 → 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ac948ac0..c18df7c1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '23.3.0' + rev: '23.7.0' hooks: - id: black args: From 4f935501ba6effdb3a1205c96f4888b2c9c3d4f7 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 13 Jul 2023 10:27:26 -0700 Subject: [PATCH 405/588] precompute applicable directives for hotkey --- ahk/_hotkey.py | 2 +- ahk/directives.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index d6489df9..66172dae 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -75,7 +75,7 @@ def __init__( self._clipboard_ex_handler: Optional[Callable[[int, Exception], Any]] = None if directives is None: directives = [] - self._directives: list[Directive | Type[Directive]] = 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]]: diff --git a/ahk/directives.py b/ahk/directives.py index f869a09d..7e16b9bd 100644 --- a/ahk/directives.py +++ b/ahk/directives.py @@ -21,6 +21,10 @@ def __hash__(self) -> int: 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): """ From 54069849b3aa20a9846f00adf2c27388564a3ab2 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 21 Jul 2023 20:43:23 -0700 Subject: [PATCH 406/588] 1.2.0 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 8fe9a09e..2eb4f44e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.2.0rc2 +version = 1.2.0 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 8e425d1f4d78fdff10fb8dc83361dbd2932898cf Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 29 Jul 2023 17:16:02 -0700 Subject: [PATCH 407/588] add msg_box method --- ahk/__init__.py | 17 +++++++++- ahk/_async/engine.py | 65 ++++++++++++++++++++++++++++++++++++++ ahk/_async/transport.py | 7 ++++- ahk/_async/window.py | 18 +++++++++++ ahk/_constants.py | 39 +++++++++++++++++++++++ ahk/_sync/engine.py | 67 +++++++++++++++++++++++++++++++++++++++- ahk/_sync/transport.py | 7 ++++- ahk/_sync/window.py | 18 +++++++++++ ahk/_utils.py | 37 ++++++++++++++++++++++ ahk/templates/daemon.ahk | 39 +++++++++++++++++++++++ 10 files changed, 310 insertions(+), 4 deletions(-) diff --git a/ahk/__init__.py b/ahk/__init__.py index 5c681140..7fdd7047 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -7,8 +7,23 @@ from ._sync import AHK from ._sync import Control from ._sync import Window +from ._utils import MsgBoxButtons +from ._utils import MsgBoxDefaultButton +from ._utils import MsgBoxIcon +from ._utils import MsgBoxModality -__all__ = ['AHK', 'Window', 'AsyncWindow', 'AsyncAHK', 'Control', 'AsyncControl'] +__all__ = [ + 'AHK', + 'Window', + 'AsyncWindow', + 'AsyncAHK', + 'Control', + 'AsyncControl', + 'MsgBoxButtons', + 'MsgBoxDefaultButton', + 'MsgBoxIcon', + 'MsgBoxModality', +] _global_instance: Optional[AHK] = None diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index eac26765..3608a963 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -21,6 +21,11 @@ from .._hotkey import Hotkey from .._hotkey import Hotstring +from .._utils import MsgBoxButtons +from .._utils import MsgBoxDefaultButton +from .._utils import MsgBoxIcon +from .._utils import MsgBoxModality +from .._utils import MsgBoxOtherOptions from .._utils import type_escape from ..directives import Directive @@ -35,8 +40,11 @@ from .transport import AsyncTransport from .window import AsyncControl from .window import AsyncWindow + +# from .window import AsyncGui from ahk.message import Position + async_sleep = asyncio.sleep # unasync: remove sleep = time.sleep @@ -3486,6 +3494,63 @@ async def reg_read( args.append(value_name) return await self._transport.function_call('AHKRegRead', args, blocking=blocking) + # XXX: the main auto-execute loop in the daemon will prevent user interaction with a GUI + # This needs to be addressed before implementing general gui functionality + # async def _new_gui(self, title: str, options: Optional[List[str]] = None) -> str: + # if options is not None: + # options.append('+Hwndhwnd') + # arg_options = ' '.join(options) + # else: + # arg_options = '+Hwndhwnd' + # + # args = [arg_options, title] + # return await self._transport.function_call('AHKGuiNew', args, engine=self) + # + # async def new_gui(self, title: str, options: Optional[List[str]] = None) -> AsyncGui: + # hwnd = await self._new_gui(title=title, options=options) + # return AsyncGui(engine=self, hwnd=hwnd) + + # 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)) + return await self._transport.function_call('AHKMsgBox', args, blocking=blocking) + async def block_forever(self) -> NoReturn: """ Blocks (sleeps) forever. Utility method to prevent script from exiting. diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 5460115a..ba459384 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -93,12 +93,14 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKGetTitleMatchMode', 'AHKGetTitleMatchSpeed', 'AHKGetVolume', + 'AHKGuiNew', 'AHKImageSearch', 'AHKKeyState', 'AHKKeyWait', 'AHKMenuTrayIcon', 'AHKMenuTrayShow', 'AHKMenuTrayTip', + 'AHKMsgBox', 'AHKMouseClickDrag', 'AHKMouseGetPos', 'AHKMouseMove', @@ -591,7 +593,10 @@ async def function_call(self, function_name: Literal['AHKMenuTrayTip'], args: Op 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['AHKGuiNew'], args: List[str], *, engine: AsyncAHK) -> str: ... + @overload + async def function_call(self, function_name: Literal['AHKMsgBox'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... # fmt: on async def function_call( diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 5de120ad..79af5f77 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -743,3 +743,21 @@ async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutu def __repr__(self) -> str: return f'<{self.__class__.__name__} window={self.window!r}, control_hwnd={self.hwnd!r}, control_class={self.control_class!r}>' + + +# +# class AsyncGui: +# def __init__(self, engine: AsyncAHK, hwnd: str): +# self._engine = engine +# self._hwnd: str = hwnd +# +# @property +# def hwnd(self) -> str: +# return self._hwnd +# +# @classmethod +# async def new(cls, engine: AsyncAHK, title: str, options: Optional[list[str]] = None) -> AsyncGui: +# return await engine.new_gui(title=title, options=options) +# +# def to_window(self) -> AsyncWindow: +# return AsyncWindow(engine=self._engine, ahk_id=self.hwnd) diff --git a/ahk/_constants.py b/ahk/_constants.py index e52cfcf1..43dd144b 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2644,6 +2644,45 @@ return FormatNoValueResponse() } +AHKGuiNew(ByRef command) { + global STRINGRESPONSEMESSAGE + options := command[2] + title := command[3] + Gui, New, %options%, %title% + return FormatResponse(STRINGRESPONSEMESSAGE, hwnd) +} + +AHKMsgBox(ByRef command) { + global TIMEOUTRESPONSEMESSAGE + global STRINGRESPONSEMESSAGE + options := command[2] + title := command[3] + text := command[4] + timeout := command[5] + MsgBox,% options, %title%, %text%, %timeout% + IfMsgBox, Yes + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Yes") + IfMsgBox, No + ret := FormatResponse(STRINGRESPONSEMESSAGE, "No") + IfMsgBox, OK + ret := FormatResponse(STRINGRESPONSEMESSAGE, "OK") + IfMsgBox, Cancel + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Cancel") + IfMsgBox, Abort + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Abort") + IfMsgBox, Ignore + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Ignore") + IfMsgBox, Retry + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Retry") + IfMsgBox, Continue + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Continue") + IfMsgBox, TryAgain + ret := FormatResponse(STRINGRESPONSEMESSAGE, "TryAgain") + IfMsgBox, Timeout + ret := FormatResponse(TIMEOUTRESPONSEMESSAGE, "MsgBox timed out") + return ret +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index bd82bfab..66948c27 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -21,6 +21,11 @@ from .._hotkey import Hotkey from .._hotkey import Hotstring +from .._utils import MsgBoxButtons +from .._utils import MsgBoxDefaultButton +from .._utils import MsgBoxIcon +from .._utils import MsgBoxModality +from .._utils import MsgBoxOtherOptions from .._utils import type_escape from ..directives import Directive @@ -35,8 +40,11 @@ from .transport import Transport from .window import Control from .window import Window + +# from .window import AsyncGui from ahk.message import Position + sleep = time.sleep SyncFilterFunc: TypeAlias = Callable[[Window], bool] @@ -188,6 +196,7 @@ def add_hotstring( 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 @@ -205,7 +214,6 @@ 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 @@ -3475,6 +3483,63 @@ def reg_read( args.append(value_name) return self._transport.function_call('AHKRegRead', args, blocking=blocking) + # XXX: the main auto-execute loop in the daemon will prevent user interaction with a GUI + # This needs to be addressed before implementing general gui functionality + # async def _new_gui(self, title: str, options: Optional[List[str]] = None) -> str: + # if options is not None: + # options.append('+Hwndhwnd') + # arg_options = ' '.join(options) + # else: + # arg_options = '+Hwndhwnd' + # + # args = [arg_options, title] + # return await self._transport.function_call('AHKGuiNew', args, engine=self) + # + # async def new_gui(self, title: str, options: Optional[List[str]] = None) -> AsyncGui: + # hwnd = await self._new_gui(title=title, options=options) + # return AsyncGui(engine=self, hwnd=hwnd) + + # 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)) + return self._transport.function_call('AHKMsgBox', args, blocking=blocking) + def block_forever(self) -> NoReturn: """ Blocks (sleeps) forever. Utility method to prevent script from exiting. diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index ec6559ec..fc1b9f4f 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -85,12 +85,14 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKGetTitleMatchMode', 'AHKGetTitleMatchSpeed', 'AHKGetVolume', + 'AHKGuiNew', 'AHKImageSearch', 'AHKKeyState', 'AHKKeyWait', 'AHKMenuTrayIcon', 'AHKMenuTrayShow', 'AHKMenuTrayTip', + 'AHKMsgBox', 'AHKMouseClickDrag', 'AHKMouseGetPos', 'AHKMouseMove', @@ -572,7 +574,10 @@ def function_call(self, function_name: Literal['AHKMenuTrayTip'], args: Optional 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['AHKGuiNew'], args: List[str], *, engine: AHK) -> str: ... + @overload + def function_call(self, function_name: Literal['AHKMsgBox'], args: Optional[List[str]] = None, *, blocking: bool = True) -> Union[str, FutureResult[str]]: ... # fmt: on def function_call( diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 95d10c22..497c9d6a 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -635,6 +635,7 @@ def from_pid(cls, engine: AHK, pid: int) -> Optional[Window]: def from_mouse_position(cls, engine: AHK) -> Optional[Window]: return engine.win_get_from_mouse_position() + class Control: def __init__(self, window: Window, hwnd: str, control_class: str): self.window: Window = window @@ -721,3 +722,20 @@ def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Po def __repr__(self) -> str: return f'<{self.__class__.__name__} window={self.window!r}, control_hwnd={self.hwnd!r}, control_class={self.control_class!r}>' + +# +# class AsyncGui: +# def __init__(self, engine: AsyncAHK, hwnd: str): +# self._engine = engine +# self._hwnd: str = hwnd +# +# @property +# def hwnd(self) -> str: +# return self._hwnd +# +# @classmethod +# async def new(cls, engine: AsyncAHK, title: str, options: Optional[list[str]] = None) -> AsyncGui: +# return await engine.new_gui(title=title, options=options) +# +# def to_window(self) -> AsyncWindow: +# return AsyncWindow(engine=self._engine, ahk_id=self.hwnd) diff --git a/ahk/_utils.py b/ahk/_utils.py index 7f58368b..951d4341 100644 --- a/ahk/_utils.py +++ b/ahk/_utils.py @@ -1,3 +1,5 @@ +import enum + HOTKEY_ESCAPE_SEQUENCE_MAP = { '\n': '`n', '\t': '`t', @@ -34,3 +36,38 @@ def hotkey_escape(s: str) -> str: 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 diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 54585ee9..e4b5c5c8 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2641,6 +2641,45 @@ AHKMenuTrayIcon(ByRef command) { return FormatNoValueResponse() } +AHKGuiNew(ByRef command) { + global STRINGRESPONSEMESSAGE + options := command[2] + title := command[3] + Gui, New, %options%, %title% + return FormatResponse(STRINGRESPONSEMESSAGE, hwnd) +} + +AHKMsgBox(ByRef command) { + global TIMEOUTRESPONSEMESSAGE + global STRINGRESPONSEMESSAGE + options := command[2] + title := command[3] + text := command[4] + timeout := command[5] + MsgBox,% options, %title%, %text%, %timeout% + IfMsgBox, Yes + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Yes") + IfMsgBox, No + ret := FormatResponse(STRINGRESPONSEMESSAGE, "No") + IfMsgBox, OK + ret := FormatResponse(STRINGRESPONSEMESSAGE, "OK") + IfMsgBox, Cancel + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Cancel") + IfMsgBox, Abort + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Abort") + IfMsgBox, Ignore + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Ignore") + IfMsgBox, Retry + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Retry") + IfMsgBox, Continue + ret := FormatResponse(STRINGRESPONSEMESSAGE, "Continue") + IfMsgBox, TryAgain + ret := FormatResponse(STRINGRESPONSEMESSAGE, "TryAgain") + IfMsgBox, Timeout + ret := FormatResponse(TIMEOUTRESPONSEMESSAGE, "MsgBox timed out") + return ret +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw From d164ef7e87ada5ed69d14222d002217e8e93e2ff Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 29 Jul 2023 17:24:56 -0700 Subject: [PATCH 408/588] add msgbox tests --- tests/_async/test_gui.py | 28 ++++++++++++++++++++++++++++ tests/_sync/test_gui.py | 27 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/_async/test_gui.py create mode 100644 tests/_sync/test_gui.py diff --git a/tests/_async/test_gui.py b/tests/_async/test_gui.py new file mode 100644 index 00000000..4f993d4b --- /dev/null +++ b/tests/_async/test_gui.py @@ -0,0 +1,28 @@ +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() diff --git a/tests/_sync/test_gui.py b/tests/_sync/test_gui.py new file mode 100644 index 00000000..36dd9618 --- /dev/null +++ b/tests/_sync/test_gui.py @@ -0,0 +1,27 @@ +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() From 3f1b0eede430653a5032b717af9fbf83627e1186 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 30 Jul 2023 16:13:19 -0700 Subject: [PATCH 409/588] Add InputBox --- ahk/_async/engine.py | 53 ++++++++++++++++++++++++++++++++++++++++ ahk/_async/transport.py | 3 +++ ahk/_constants.py | 26 ++++++++++++++++++++ ahk/_sync/engine.py | 53 ++++++++++++++++++++++++++++++++++++++++ ahk/_sync/transport.py | 3 +++ ahk/templates/daemon.ahk | 26 ++++++++++++++++++++ 6 files changed, 164 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 3608a963..12966b25 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3551,6 +3551,59 @@ async def msg_box( args.append(str(timeout)) 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) + async def block_forever(self) -> NoReturn: """ Blocks (sleeps) forever. Utility method to prevent script from exiting. diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index ba459384..6ad3e1aa 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -95,6 +95,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKGetVolume', 'AHKGuiNew', 'AHKImageSearch', + 'AHKInputBox', 'AHKKeyState', 'AHKKeyWait', 'AHKMenuTrayIcon', @@ -597,6 +598,8 @@ async def function_call(self, function_name: Literal['AHKMenuTrayShow'], args: O async def function_call(self, function_name: Literal['AHKGuiNew'], args: List[str], *, engine: AsyncAHK) -> 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]]: ... # fmt: on async def function_call( diff --git a/ahk/_constants.py b/ahk/_constants.py index 43dd144b..a8da3d3b 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2683,6 +2683,32 @@ return ret } +AHKInputBox(ByRef command) { + global INTEGERRESPONSEMESSAGE + global STRINGRESPONSEMESSAGE + global TIMEOUTRESPONSEMESSAGE + title := command[2] + prompt := command[3] + hide := command[4] + width := command[5] + height := command[6] + x := command[7] + y := command[8] + locale := command[9] + timeout := command[10] + default := command[11] + + InputBox, output, %title%, %prompt%, %hide%, %width%, %height%, %x%, %y%, %locale%, %timeout%, %default% + if (ErrorLevel = 2) { + ret := FormatResponse(TIMEOUTRESPONSEMESSAGE, "Input box timed out") + } else if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + return ret +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 66948c27..07e4a99e 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3540,6 +3540,59 @@ def msg_box( args.append(str(timeout)) 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) + def block_forever(self) -> NoReturn: """ Blocks (sleeps) forever. Utility method to prevent script from exiting. diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index fc1b9f4f..7a20b8e3 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -87,6 +87,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKGetVolume', 'AHKGuiNew', 'AHKImageSearch', + 'AHKInputBox', 'AHKKeyState', 'AHKKeyWait', 'AHKMenuTrayIcon', @@ -578,6 +579,8 @@ def function_call(self, function_name: Literal['AHKMenuTrayShow'], args: Optiona def function_call(self, function_name: Literal['AHKGuiNew'], args: List[str], *, engine: AHK) -> 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]]: ... # fmt: on def function_call( diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index e4b5c5c8..44a4b9ff 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2680,6 +2680,32 @@ AHKMsgBox(ByRef command) { return ret } +AHKInputBox(ByRef command) { + global INTEGERRESPONSEMESSAGE + global STRINGRESPONSEMESSAGE + global TIMEOUTRESPONSEMESSAGE + title := command[2] + prompt := command[3] + hide := command[4] + width := command[5] + height := command[6] + x := command[7] + y := command[8] + locale := command[9] + timeout := command[10] + default := command[11] + + InputBox, output, %title%, %prompt%, %hide%, %width%, %height%, %x%, %y%, %locale%, %timeout%, %default% + if (ErrorLevel = 2) { + ret := FormatResponse(TIMEOUTRESPONSEMESSAGE, "Input box timed out") + } else if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + return ret +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw From 980fa75a2dfd14e6810e5809cf508ff52d5cddd2 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 30 Jul 2023 16:15:47 -0700 Subject: [PATCH 410/588] add inputbox test --- tests/_async/test_gui.py | 8 ++++++++ tests/_sync/test_gui.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/tests/_async/test_gui.py b/tests/_async/test_gui.py index 4f993d4b..259d43c8 100644 --- a/tests/_async/test_gui.py +++ b/tests/_async/test_gui.py @@ -26,3 +26,11 @@ async def test_msg_box(self): 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() diff --git a/tests/_sync/test_gui.py b/tests/_sync/test_gui.py index 36dd9618..f5caf9b9 100644 --- a/tests/_sync/test_gui.py +++ b/tests/_sync/test_gui.py @@ -25,3 +25,11 @@ def test_msg_box(self): 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() From 27d7f6a34aaf49964b3ea02a874f2167859f3d21 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 30 Jul 2023 17:01:35 -0700 Subject: [PATCH 411/588] add file_select_box --- ahk/_async/engine.py | 46 ++++++++++++++++++++++++++++++++++++++++ ahk/_async/transport.py | 4 ++++ ahk/_constants.py | 16 +++++++++++++- ahk/_sync/engine.py | 34 +++++++++++++++++++++++++++++ ahk/_sync/transport.py | 4 ++++ ahk/templates/daemon.ahk | 16 +++++++++++++- 6 files changed, 118 insertions(+), 2 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 12966b25..630a3695 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3604,6 +3604,52 @@ async def input_box( 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) + async def block_forever(self) -> NoReturn: """ Blocks (sleeps) forever. Utility method to prevent script from exiting. diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 6ad3e1aa..072956f2 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -86,6 +86,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKControlGetPos', 'AHKControlGetText', 'AHKControlSend', + 'AHKFileSelectFile', 'AHKGetClipboard', 'AHKGetClipboardAll', 'AHKGetCoordMode', @@ -600,6 +601,9 @@ async def function_call(self, function_name: Literal['AHKGuiNew'], args: List[st 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]]: ... + # fmt: on async def function_call( diff --git a/ahk/_constants.py b/ahk/_constants.py index a8da3d3b..d71bcf68 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2684,7 +2684,6 @@ } AHKInputBox(ByRef command) { - global INTEGERRESPONSEMESSAGE global STRINGRESPONSEMESSAGE global TIMEOUTRESPONSEMESSAGE title := command[2] @@ -2709,6 +2708,21 @@ return ret } +AHKFileSelectFile(byRef command) { + global STRINGRESPONSEMESSAGE + options := command[2] + root := command[3] + title := command[4] + filter := command[5] + FileSelectFile, output, %options%, %root%, %title%, %filter% + if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + return ret +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 07e4a99e..06901fa9 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3593,6 +3593,40 @@ def input_box( 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) + + + def block_forever(self) -> NoReturn: """ Blocks (sleeps) forever. Utility method to prevent script from exiting. diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 7a20b8e3..75c56d1d 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -78,6 +78,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKControlGetPos', 'AHKControlGetText', 'AHKControlSend', + 'AHKFileSelectFile', 'AHKGetClipboard', 'AHKGetClipboardAll', 'AHKGetCoordMode', @@ -581,6 +582,9 @@ def function_call(self, function_name: Literal['AHKGuiNew'], args: List[str], *, 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]]: ... + # fmt: on def function_call( diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 44a4b9ff..f2bb8459 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2681,7 +2681,6 @@ AHKMsgBox(ByRef command) { } AHKInputBox(ByRef command) { - global INTEGERRESPONSEMESSAGE global STRINGRESPONSEMESSAGE global TIMEOUTRESPONSEMESSAGE title := command[2] @@ -2706,6 +2705,21 @@ AHKInputBox(ByRef command) { return ret } +AHKFileSelectFile(byRef command) { + global STRINGRESPONSEMESSAGE + options := command[2] + root := command[3] + title := command[4] + filter := command[5] + FileSelectFile, output, %options%, %root%, %title%, %filter% + if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + return ret +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw From 75a6ebaa756760eafdadcda6d7458d306da4cafe Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 30 Jul 2023 17:17:17 -0700 Subject: [PATCH 412/588] add folder select box --- ahk/_async/engine.py | 37 ++++++++++++++++++++++++++++ ahk/_async/transport.py | 4 ++- ahk/_constants.py | 16 ++++++++++++ ahk/_sync/engine.py | 53 ++++++++++++++++++++++++++++++++++++++-- ahk/_sync/transport.py | 4 ++- ahk/templates/daemon.ahk | 16 ++++++++++++ 6 files changed, 126 insertions(+), 4 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 630a3695..2f1313c5 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3650,6 +3650,43 @@ async def file_select_box( 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. diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 072956f2..d34f18bc 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -87,6 +87,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKControlGetText', 'AHKControlSend', 'AHKFileSelectFile', + 'AHKFileSelectFolder', 'AHKGetClipboard', 'AHKGetClipboardAll', 'AHKGetCoordMode', @@ -603,7 +604,8 @@ async def function_call(self, function_name: Literal['AHKMsgBox'], args: Optiona 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( diff --git a/ahk/_constants.py b/ahk/_constants.py index d71bcf68..f2aacd03 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2723,6 +2723,22 @@ return ret } +AHKFileSelectFolder(byRef command) { + global STRINGRESPONSEMESSAGE + starting_folder := command[2] + options := command[3] + prompt := command[4] + + FileSelectFolder, output, %starting_folder%, %options%, %prompt% + + if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + return ret +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 06901fa9..4b44f2ba 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3603,7 +3603,21 @@ def file_select_box(self, title: str = 'Select File', multi: bool = False, root: @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]]: + 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 @@ -3625,7 +3639,42 @@ def file_select_box(self, title: str = 'Select File', multi: bool = False, root: 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: """ diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 75c56d1d..0c964f8f 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -79,6 +79,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKControlGetText', 'AHKControlSend', 'AHKFileSelectFile', + 'AHKFileSelectFolder', 'AHKGetClipboard', 'AHKGetClipboardAll', 'AHKGetCoordMode', @@ -584,7 +585,8 @@ def function_call(self, function_name: Literal['AHKMsgBox'], args: Optional[List 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( diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index f2bb8459..fead6190 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2720,6 +2720,22 @@ AHKFileSelectFile(byRef command) { return ret } +AHKFileSelectFolder(byRef command) { + global STRINGRESPONSEMESSAGE + starting_folder := command[2] + options := command[3] + prompt := command[4] + + FileSelectFolder, output, %starting_folder%, %options%, %prompt% + + if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + } + return ret +} + b64decode(ByRef pszString) { ; TODO load DLL globally for performance ; REF: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinaryw From 4fc794d04816f9d8915e18cea72a3b909d7669d0 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 30 Jul 2023 17:26:44 -0700 Subject: [PATCH 413/588] remove unused code --- ahk/_async/engine.py | 16 ---------------- ahk/_async/window.py | 18 ------------------ ahk/_sync/engine.py | 16 ---------------- ahk/_sync/window.py | 17 ----------------- 4 files changed, 67 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 2f1313c5..c0a6d8b5 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3494,22 +3494,6 @@ async def reg_read( args.append(value_name) return await self._transport.function_call('AHKRegRead', args, blocking=blocking) - # XXX: the main auto-execute loop in the daemon will prevent user interaction with a GUI - # This needs to be addressed before implementing general gui functionality - # async def _new_gui(self, title: str, options: Optional[List[str]] = None) -> str: - # if options is not None: - # options.append('+Hwndhwnd') - # arg_options = ' '.join(options) - # else: - # arg_options = '+Hwndhwnd' - # - # args = [arg_options, title] - # return await self._transport.function_call('AHKGuiNew', args, engine=self) - # - # async def new_gui(self, title: str, options: Optional[List[str]] = None) -> AsyncGui: - # hwnd = await self._new_gui(title=title, options=options) - # return AsyncGui(engine=self, hwnd=hwnd) - # 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: ... diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 79af5f77..5de120ad 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -743,21 +743,3 @@ async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutu def __repr__(self) -> str: return f'<{self.__class__.__name__} window={self.window!r}, control_hwnd={self.hwnd!r}, control_class={self.control_class!r}>' - - -# -# class AsyncGui: -# def __init__(self, engine: AsyncAHK, hwnd: str): -# self._engine = engine -# self._hwnd: str = hwnd -# -# @property -# def hwnd(self) -> str: -# return self._hwnd -# -# @classmethod -# async def new(cls, engine: AsyncAHK, title: str, options: Optional[list[str]] = None) -> AsyncGui: -# return await engine.new_gui(title=title, options=options) -# -# def to_window(self) -> AsyncWindow: -# return AsyncWindow(engine=self._engine, ahk_id=self.hwnd) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 4b44f2ba..faf78531 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3483,22 +3483,6 @@ def reg_read( args.append(value_name) return self._transport.function_call('AHKRegRead', args, blocking=blocking) - # XXX: the main auto-execute loop in the daemon will prevent user interaction with a GUI - # This needs to be addressed before implementing general gui functionality - # async def _new_gui(self, title: str, options: Optional[List[str]] = None) -> str: - # if options is not None: - # options.append('+Hwndhwnd') - # arg_options = ' '.join(options) - # else: - # arg_options = '+Hwndhwnd' - # - # args = [arg_options, title] - # return await self._transport.function_call('AHKGuiNew', args, engine=self) - # - # async def new_gui(self, title: str, options: Optional[List[str]] = None) -> AsyncGui: - # hwnd = await self._new_gui(title=title, options=options) - # return AsyncGui(engine=self, hwnd=hwnd) - # 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: ... diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 497c9d6a..8701e485 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -722,20 +722,3 @@ def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Po def __repr__(self) -> str: return f'<{self.__class__.__name__} window={self.window!r}, control_hwnd={self.hwnd!r}, control_class={self.control_class!r}>' - -# -# class AsyncGui: -# def __init__(self, engine: AsyncAHK, hwnd: str): -# self._engine = engine -# self._hwnd: str = hwnd -# -# @property -# def hwnd(self) -> str: -# return self._hwnd -# -# @classmethod -# async def new(cls, engine: AsyncAHK, title: str, options: Optional[list[str]] = None) -> AsyncGui: -# return await engine.new_gui(title=title, options=options) -# -# def to_window(self) -> AsyncWindow: -# return AsyncWindow(engine=self._engine, ahk_id=self.hwnd) From b092add287cf2e32dd89bf2052f79433f7bddc35 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 30 Jul 2023 19:03:43 -0700 Subject: [PATCH 414/588] update methods documentation --- docs/README.md | 15 +++++++++++++++ docs/api/methods.rst | 33 ++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/docs/README.md b/docs/README.md index ebba2d3d..136523b6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -291,6 +291,9 @@ ahk.sound_set(50, device_number=1, component_type='MASTER', control_type='VOLUME ## GUI + +Tooltips/traytips + ```python import time from ahk import AHK @@ -304,6 +307,18 @@ ahk.show_warning_traytip("Warning", "It's a warning") 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 diff --git a/docs/api/methods.rst b/docs/api/methods.rst index 658b8abe..b13177b7 100644 --- a/docs/api/methods.rst +++ b/docs/api/methods.rst @@ -416,7 +416,8 @@ GUI ^^^ GUI methods are largely unimplmented, except ``ToolTip`` and ``TrayTip``. -We recommend using one of the many Python GUI libraries, such as `easygui `_\ , `pysimplegui `_ or similar. +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 `_ , `pysimplegui `_ or similar. .. list-table:: :header-rows: 1 @@ -440,14 +441,20 @@ We recommend using one of the many Python GUI libraries, such as `easygui `_ - - Not Implemented + - Not planned - * - `IfMsgBox `_ - - Not Implemented + - Not planned - * - `InputBox `_ - - Not Implemented - - + - 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 - @@ -455,25 +462,25 @@ We recommend using one of the many Python GUI libraries, such as `easygui `_ - - Not Implemented + - Not Planned - * - `MenuGetName() `_ - - Not Implemented + - Not Planned - * - `MsgBox `_ - - Not Implemented - - + - Implemented + - :py:meth:`~ahk._sync.engine.msg_box` * - `OnMessage() `_ - - Not Implemented + - Not Planned - * - `Progress `_ - - Not Implemented + - Not Planned - * - `SplashImage `_ - - Not Implemented + - Not Planned - * - `SplashTextOn/Off `_ - - Not Implemented + - Not Planned - * - `ToolTip `_ - Implemented From 659fbb1410b3ddd25677e12cee85bc2bce501719 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 30 Jul 2023 19:04:31 -0700 Subject: [PATCH 415/588] 1.3.0 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 2eb4f44e..f1cfc96d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.2.0 +version = 1.3.0 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From d83c5abc85cb91076d4f40f5e13c3e1e9ef86f78 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 05:43:37 +0000 Subject: [PATCH 416/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 6.0.0 → 6.1.0](https://github.com/pycqa/flake8/compare/6.0.0...6.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c18df7c1..d435c302 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,7 +55,7 @@ repos: - jinja2 - repo: https://github.com/pycqa/flake8 - rev: '6.0.0' # pick a git hash / tag to point to + rev: '6.1.0' # pick a git hash / tag to point to hooks: - id: flake8 args: From 99546cf7fb1041affbb37b6aed754ab59e7aa7ad Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 22 Aug 2023 12:53:31 -0700 Subject: [PATCH 417/588] LICENSE :balance_scale: --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..2844b2fa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Spencer Phillip Young + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 2f383cb651734dc0fc6c3185e96af843d3e05753 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 22 Aug 2023 13:02:08 -0700 Subject: [PATCH 418/588] include license in distributions --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index d7c30eeb..dd7fcd35 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,4 @@ include ahk/templates/daemon.ahk include ahk/templates/hotkeys.ahk include docs/README.md include buildunasync.py +include LICENSE From b5ac2003cefce8c65c0b4682917c364639cded3f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 22 Aug 2023 15:27:18 -0700 Subject: [PATCH 419/588] exclude abstract base class from message registry --- .pre-commit-config.yaml | 2 +- ahk/message.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d435c302..361cd16e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.4.1' + rev: 'v1.5.1' hooks: - id: mypy args: diff --git a/ahk/message.py b/ahk/message.py index b6a59df4..be103594 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -143,8 +143,7 @@ def unpack(self) -> Any: return NotImplemented -_message_registry: dict[bytes, 'ResponseMessageClassTypes'] -_message_registry = {ResponseMessage._type_order_mark: ResponseMessage} +_message_registry: dict[bytes, 'ResponseMessageClassTypes'] = {} class TupleResponseMessage(ResponseMessage): From dbcc090f56c71d08e9395b300a8d3156c1857553 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 22 Aug 2023 15:37:41 -0700 Subject: [PATCH 420/588] initial work to enable extensions --- ahk/_async/engine.py | 36 +++++++++++- ahk/_async/transport.py | 7 ++- ahk/_constants.py | 14 +++++ ahk/_sync/engine.py | 35 +++++++++++- ahk/_sync/transport.py | 5 +- ahk/_sync/window.py | 12 +--- ahk/extensions.py | 99 +++++++++++++++++++++++++++++++++ ahk/templates/daemon.ahk | 14 +++++ tests/_async/test_extensions.py | 67 ++++++++++++++++++++++ tests/_sync/test_extensions.py | 66 ++++++++++++++++++++++ 10 files changed, 342 insertions(+), 13 deletions(-) create mode 100644 ahk/extensions.py create mode 100644 tests/_async/test_extensions.py create mode 100644 tests/_sync/test_extensions.py diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index c0a6d8b5..88044261 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -6,6 +6,7 @@ import tempfile import time import warnings +from functools import partial from typing import Any from typing import Awaitable from typing import Callable @@ -34,6 +35,7 @@ else: from typing import TypeAlias +from ..extensions import Extension, _extension_method_registry, _ExtensionMethodRegistry from ..keys import Key from .transport import AsyncDaemonProcessTransport from .transport import AsyncFutureResult @@ -135,13 +137,45 @@ def __init__( TransportClass: Optional[Type[AsyncTransport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', + extensions: list[Extension] | None | Literal['auto'] = None, ): + self._extension_registry: _ExtensionMethodRegistry + self._extensions: list[Extension] + if extensions == 'auto': + is_async = False + is_async = True # unasync: remove + if is_async: + extensions = [entry.extension for name, entry in _extension_method_registry.async_methods.items()] + else: + extensions = [entry.extension for name, entry in _extension_method_registry.sync_methods.items()] + self._extension_registry = _extension_method_registry + self._extensions = extensions + else: + self._extensions = extensions or [] + self._extension_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) + for ext in self._extensions: + self._extension_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) + transport = TransportClass(executable_path=executable_path, directives=directives, extensions=extensions) self._transport: AsyncTransport = transport + def __getattr__(self, name: str) -> Callable[..., Any]: + is_async = False + is_async = True # unasync: remove + if is_async: + if name in self._extension_registry.async_methods: + method = self._extension_registry.async_methods[name].method + return partial(method, self) + else: + if name in self._extension_registry.sync_methods: + method = self._extension_registry.sync_methods[name].method + return partial(method, self) + + raise AttributeError(f'{self.__class__.__name__!r} object has no attribute {name!r}') + def add_hotkey( self, keyname: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None ) -> None: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index d34f18bc..46c4b97d 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -38,6 +38,7 @@ import jinja2 +from ahk.extensions import Extension from ahk._hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from ahk.message import RequestMessage from ahk.message import ResponseMessage @@ -656,7 +657,9 @@ def __init__( directives: Optional[list[Directive | Type[Directive]]] = None, jinja_loader: Optional[jinja2.BaseLoader] = None, template: Optional[jinja2.Template] = None, + extensions: list[Extension] | None = None, ): + self._extensions = extensions or [] self._proc: Optional[AsyncAHKProcess] self._proc = None self._temp_script: Optional[str] = None @@ -711,7 +714,9 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A 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, **kwargs) + return template.render( + directives=self._directives, message_types=message_types, extensions=self._extensions, **kwargs + ) @property def lock(self) -> Any: diff --git a/ahk/_constants.py b/ahk/_constants.py index f2aacd03..bf624f08 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -6,7 +6,16 @@ #NoEnv #Persistent #SingleInstance Off +{% block extension_directives %} +; BEGIN extension includes +{% for ext in extensions %} +{% for inc in ext.includes %} +{{ inc }} +{% endfor %} +{% endfor %} +; END extension includes +{% endblock extension_directives %} ; BEGIN user-defined directives {% block user_directives %} {% for directive in directives %} @@ -2833,7 +2842,12 @@ return decoded_commands } +; BEGIN extension scripts +{% for ext in extensions %} +{{ ext.script_text }} +{% endfor %} +; END extension scripts {% block before_autoexecute %} {% endblock before_autoexecute %} diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index faf78531..b9dfc193 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -6,6 +6,7 @@ import tempfile import time import warnings +from functools import partial from typing import Any from typing import Awaitable from typing import Callable @@ -34,6 +35,7 @@ else: from typing import TypeAlias +from ..extensions import Extension, _extension_method_registry, _ExtensionMethodRegistry from ..keys import Key from .transport import DaemonProcessTransport from .transport import FutureResult @@ -131,13 +133,44 @@ def __init__( TransportClass: Optional[Type[Transport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', + extensions: list[Extension] | None | Literal['auto'] = None ): + self._extension_registry: _ExtensionMethodRegistry + self._extensions: list[Extension] + if extensions == 'auto': + is_async = False + if is_async: + extensions = [entry.extension for name, entry in _extension_method_registry.async_methods.items()] + else: + extensions = [entry.extension for name, entry in _extension_method_registry.sync_methods.items()] + self._extension_registry = _extension_method_registry + self._extensions = extensions + else: + self._extensions = extensions or [] + self._extension_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) + for ext in self._extensions: + self._extension_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) + transport = TransportClass(executable_path=executable_path, directives=directives, extensions=extensions) self._transport: Transport = transport + def __getattr__(self, name: str) -> Callable[..., Any]: + is_async = False + if is_async: + if name in self._extension_registry.async_methods: + method = self._extension_registry.async_methods[name].method + return partial(method, self) + else: + if name in self._extension_registry.sync_methods: + method = self._extension_registry.sync_methods[name].method + return partial(method, self) + + raise AttributeError(f'{self.__class__.__name__!r} object has no attribute {name!r}') + def add_hotkey( self, keyname: str, callback: Callable[[], Any], ex_handler: Optional[Callable[[str, Exception], Any]] = None ) -> None: diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 0c964f8f..11ccb00a 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -38,6 +38,7 @@ import jinja2 +from ahk.extensions import Extension from ahk._hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from ahk.message import RequestMessage from ahk.message import ResponseMessage @@ -630,7 +631,9 @@ def __init__( directives: Optional[list[Directive | Type[Directive]]] = None, jinja_loader: Optional[jinja2.BaseLoader] = None, template: Optional[jinja2.Template] = None, + extensions: list[Extension] | None = None ): + self._extensions = extensions or [] self._proc: Optional[SyncAHKProcess] self._proc = None self._temp_script: Optional[str] = None @@ -684,7 +687,7 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A 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, **kwargs) + return template.render(directives=self._directives, message_types=message_types, extensions=self._extensions, **kwargs) @property def lock(self) -> Any: diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 8701e485..30d0a8b1 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -61,15 +61,11 @@ def __hash__(self) -> int: return hash(self._ahk_id) def close(self) -> None: - self._engine.win_close( - title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast') - ) + 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') - ) + 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( @@ -591,9 +587,7 @@ def set_transparent( blocking=blocking, ) - def set_trans_color( - self, color: Union[int, str], *, blocking: bool = True - ) -> Union[None, FutureResult[None]]: + 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}', diff --git a/ahk/extensions.py b/ahk/extensions.py new file mode 100644 index 00000000..7d582334 --- /dev/null +++ b/ahk/extensions.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import asyncio +import warnings +from dataclasses import dataclass +from typing import Any +from typing import Callable +from typing import ParamSpec +from typing import TypeVar + +from .directives import Include + + +@dataclass +class _ExtensionEntry: + extension: Extension + method: Callable[..., Any] + + +@dataclass +class _ExtensionMethodRegistry: + sync_methods: dict[str, _ExtensionEntry] + async_methods: dict[str, _ExtensionEntry] + + def register(self, ext: Extension, f: Callable[P, T]) -> Callable[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__].method!r} ' + f'will be overridden by {f!r}' + ) + self.async_methods[f.__name__] = _ExtensionEntry(extension=ext, method=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__].method!r} ' + f'will be overridden by {f!r}' + ) + self.sync_methods[f.__name__] = _ExtensionEntry(extension=ext, method=f) + return f + + def merge(self, other: _ExtensionMethodRegistry) -> None: + for fname, entry in other.async_methods.items(): + async_method = entry.method + if async_method.__name__ in self.async_methods: + warnings.warn( + f'Method of name {async_method.__name__!r} has already been registered. ' + f'Previously registered method {self.async_methods[async_method.__name__].method!r} ' + f'will be overridden by {async_method!r}' + ) + self.async_methods[async_method.__name__] = entry + for fname, entry in other.sync_methods.items(): + method = entry.method + if method.__name__ in self.sync_methods: + warnings.warn( + f'Method of name {method.__name__!r} has already been registered. ' + f'Previously registered method {self.sync_methods[method.__name__].method!r} ' + f'will be overridden by {method!r}' + ) + self.sync_methods[method.__name__] = entry + + +_extension_method_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) + + +T = TypeVar('T') +P = ParamSpec('P') + + +class Extension: + def __init__( + self, + includes: list[str] | None = None, + script_text: str | None = None, + # template: str | Template | None = None + ): + self._text: str = script_text or '' + # self._template: str | Template | None = template + self._includes: list[str] = includes or [] + self._extension_method_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) + + @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[P, T]) -> Callable[P, T]: + self._extension_method_registry.register(self, f) + _extension_method_registry.register(self, f) + return f diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index fead6190..22b7ab87 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -3,7 +3,16 @@ #NoEnv #Persistent #SingleInstance Off +{% block extension_directives %} +; BEGIN extension includes +{% for ext in extensions %} +{% for inc in ext.includes %} +{{ inc }} +{% endfor %} +{% endfor %} +; END extension includes +{% endblock extension_directives %} ; BEGIN user-defined directives {% block user_directives %} {% for directive in directives %} @@ -2830,7 +2839,12 @@ CommandArrayFromQuery(ByRef text) { return decoded_commands } +; BEGIN extension scripts +{% for ext in extensions %} +{{ ext.script_text }} +{% endfor %} +; END extension scripts {% block before_autoexecute %} {% endblock before_autoexecute %} diff --git a/tests/_async/test_extensions.py b/tests/_async/test_extensions.py new file mode 100644 index 00000000..95651dcf --- /dev/null +++ b/tests/_async/test_extensions.py @@ -0,0 +1,67 @@ +import asyncio +import time +import unittest + +import pytest + +from ahk import AsyncAHK +from ahk.extensions import Extension + +async_sleep = asyncio.sleep # unasync: remove + +sleep = time.sleep + +ext_text = '''\ +AHKDoSomething(ByRef command) { + global STRINGRESPONSEMESSAGE + arg := command[2] + return FormatResponse(STRINGRESPONSEMESSAGE, Format("test{}", arg)) +} +''' + +async_extension = Extension(script_text=ext_text) + + +@async_extension.register +async def do_something(ahk, arg: str) -> str: + res = await ahk._transport.function_call('AHKDoSomething', [arg]) + return res + + +class TestExtensions(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(extensions=[async_extension]) + + async def asyncTearDown(self) -> None: + self.ahk._transport._proc.kill() + time.sleep(0.2) + + async def test_ext(self): + res = await self.ahk.do_something('foo') + assert res == 'testfoo' + + +class TestExtensionsAuto(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.ahk = AsyncAHK(extensions='auto') + + async def asyncTearDown(self) -> None: + self.ahk._transport._proc.kill() + time.sleep(0.2) + + async def test_ext(self): + res = await self.ahk.do_something('foo') + assert res == 'testfoo' + + +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(self): + assert not hasattr(self.ahk, 'do_something') diff --git a/tests/_sync/test_extensions.py b/tests/_sync/test_extensions.py new file mode 100644 index 00000000..c78d392d --- /dev/null +++ b/tests/_sync/test_extensions.py @@ -0,0 +1,66 @@ +import asyncio +import time +import unittest + +import pytest + +from ahk import AHK +from ahk.extensions import Extension + + +sleep = time.sleep + +ext_text = '''\ +AHKDoSomething(ByRef command) { + global STRINGRESPONSEMESSAGE + arg := command[2] + return FormatResponse(STRINGRESPONSEMESSAGE, Format("test{}", arg)) +} +''' + +async_extension = Extension(script_text=ext_text) + + +@async_extension.register +def do_something(ahk, arg: str) -> str: + res = ahk._transport.function_call('AHKDoSomething', [arg]) + return res + + +class TestExtensions(unittest.TestCase): + def setUp(self) -> None: + self.ahk = AHK(extensions=[async_extension]) + + def tearDown(self) -> None: + self.ahk._transport._proc.kill() + time.sleep(0.2) + + def test_ext(self): + res = self.ahk.do_something('foo') + assert res == 'testfoo' + + +class TestExtensionsAuto(unittest.TestCase): + def setUp(self) -> None: + self.ahk = AHK(extensions='auto') + + def tearDown(self) -> None: + self.ahk._transport._proc.kill() + time.sleep(0.2) + + def test_ext(self): + res = self.ahk.do_something('foo') + assert res == 'testfoo' + + +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(self): + assert not hasattr(self.ahk, 'do_something') From 06a38f8a5c51826bce07ce9b39b1f2da028e6201 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 22 Aug 2023 17:32:12 -0700 Subject: [PATCH 421/588] fix typing import for python<3.10 --- ahk/extensions.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ahk/extensions.py b/ahk/extensions.py index 7d582334..af3deec3 100644 --- a/ahk/extensions.py +++ b/ahk/extensions.py @@ -1,13 +1,18 @@ from __future__ import annotations import asyncio +import sys import warnings from dataclasses import dataclass from typing import Any from typing import Callable -from typing import ParamSpec from typing import TypeVar +if sys.version_info < (3, 10): + from typing_extensions import ParamSpec +else: + from typing import ParamSpec + from .directives import Include @@ -17,6 +22,10 @@ class _ExtensionEntry: method: Callable[..., Any] +T = TypeVar('T') +P = ParamSpec('P') + + @dataclass class _ExtensionMethodRegistry: sync_methods: dict[str, _ExtensionEntry] @@ -65,10 +74,6 @@ def merge(self, other: _ExtensionMethodRegistry) -> None: _extension_method_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) -T = TypeVar('T') -P = ParamSpec('P') - - class Extension: def __init__( self, From 47c3b64eb70f42eaa89e4a85e620dc0c7f06a523 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 22 Aug 2023 18:49:26 -0700 Subject: [PATCH 422/588] prevent merging duplicate methods --- ahk/_async/engine.py | 8 ++++++-- ahk/_sync/engine.py | 7 +++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 88044261..38874f84 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -145,9 +145,13 @@ def __init__( is_async = False is_async = True # unasync: remove if is_async: - extensions = [entry.extension for name, entry in _extension_method_registry.async_methods.items()] + extensions = list( + set(entry.extension for name, entry in _extension_method_registry.async_methods.items()) + ) else: - extensions = [entry.extension for name, entry in _extension_method_registry.sync_methods.items()] + extensions = list( + set(entry.extension for name, entry in _extension_method_registry.sync_methods.items()) + ) self._extension_registry = _extension_method_registry self._extensions = extensions else: diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index b9dfc193..0a2c6a87 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -133,16 +133,16 @@ def __init__( TransportClass: Optional[Type[Transport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', - extensions: list[Extension] | None | Literal['auto'] = None + extensions: list[Extension] | None | Literal['auto'] = None, ): self._extension_registry: _ExtensionMethodRegistry self._extensions: list[Extension] if extensions == 'auto': is_async = False if is_async: - extensions = [entry.extension for name, entry in _extension_method_registry.async_methods.items()] + extensions = list(set(entry.extension for name, entry in _extension_method_registry.async_methods.items())) else: - extensions = [entry.extension for name, entry in _extension_method_registry.sync_methods.items()] + extensions = list(set(entry.extension for name, entry in _extension_method_registry.sync_methods.items())) self._extension_registry = _extension_method_registry self._extensions = extensions else: @@ -151,7 +151,6 @@ def __init__( for ext in self._extensions: self._extension_registry.merge(ext._extension_method_registry) - if TransportClass is None: TransportClass = DaemonProcessTransport assert TransportClass is not None From 8e38afaf1a5d26eb8ee0e8b699e0e65924611f43 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 22 Aug 2023 19:16:23 -0700 Subject: [PATCH 423/588] cleanup --- ahk/_async/engine.py | 10 ++++------ ahk/_sync/engine.py | 6 ++++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 38874f84..c7937d35 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -145,13 +145,11 @@ def __init__( is_async = False is_async = True # unasync: remove if is_async: - extensions = list( - set(entry.extension for name, entry in _extension_method_registry.async_methods.items()) - ) + methods = _extension_method_registry.async_methods else: - extensions = list( - set(entry.extension for name, entry in _extension_method_registry.sync_methods.items()) - ) + methods = _extension_method_registry.sync_methods + extensions = list(set(entry.extension for name, entry in methods.items())) + self._extension_registry = _extension_method_registry self._extensions = extensions else: diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 0a2c6a87..8f93d069 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -140,9 +140,11 @@ def __init__( if extensions == 'auto': is_async = False if is_async: - extensions = list(set(entry.extension for name, entry in _extension_method_registry.async_methods.items())) + methods = _extension_method_registry.async_methods else: - extensions = list(set(entry.extension for name, entry in _extension_method_registry.sync_methods.items())) + methods = _extension_method_registry.sync_methods + extensions = list(set(entry.extension for name, entry in methods.items())) + self._extension_registry = _extension_method_registry self._extensions = extensions else: From 71cb07b718390ebc35af4a22c30357603c076e58 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 24 Aug 2023 18:59:31 -0700 Subject: [PATCH 424/588] new message identification --- ahk/_async/transport.py | 6 +- ahk/_constants.py | 392 +++++++++++++++------------------------ ahk/_sync/transport.py | 10 +- ahk/message.py | 34 +--- ahk/templates/daemon.ahk | 392 +++++++++++++++------------------------ 5 files changed, 320 insertions(+), 514 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 46c4b97d..96277d3f 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -715,7 +715,11 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A 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, extensions=self._extensions, **kwargs + directives=self._directives, + message_types=message_types, + message_registry=_message_registry, + extensions=self._extensions, + **kwargs, ) @property diff --git a/ahk/_constants.py b/ahk/_constants.py index bf624f08..39b84fcf 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -28,30 +28,26 @@ {% endblock directives %} {% block message_types %} -{% for tom, name in message_types.items() %} -{{ name }} := "{{ tom }}" -{% endfor %} +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", MessageType, newline_count, payload) + response := Format("{}`n{}`n{}`n", MESSAGE_TYPES[MessageType], newline_count, payload) return response } FormatNoValueResponse() { global NOVALUE_SENTINEL - global NOVALUERESPONSEMESSAGE - return FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) + return FormatResponse("ahk.message.NoValueResponseMessage", NOVALUE_SENTINEL) } FormatBinaryResponse(ByRef bin) { - global B64BINARYRESPONSEMESSAGE b64 := b64encode(bin) - return FormatResponse(B64BINARYRESPONSEMESSAGE, b64) + return FormatResponse("ahk.message.B64BinaryResponseMessage", b64) } AHKSetDetectHiddenWindows(ByRef command) { @@ -78,15 +74,15 @@ AHKGetTitleMatchMode(ByRef command) { {% block AHKGetTitleMatchMode %} - global STRINGRESPONSEMESSAGE - return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchMode) + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) {% endblock AHKGetTitleMatchMode %} } AHKGetTitleMatchSpeed(ByRef command) { {% block AHKGetTitleMatchSpeed %} - global STRINGRESPONSEMESSAGE - return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchModeSpeed) + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) {% endblock AHKGetTitleMatchSpeed %} } @@ -100,14 +96,14 @@ AHKGetSendLevel(ByRef command) { {% block AHKGetSendLevel %} - global INTEGERRESPONSEMESSAGE - return FormatResponse(INTEGERRESPONSEMESSAGE, A_SendLevel) + + return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) {% endblock AHKGetSendLevel %} } AHKWinExist(ByRef command) { {% block AHKWinExist %} - global BOOLEANRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -132,9 +128,9 @@ } if WinExist(title, text, extitle, extext) { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) } else { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) } DetectHiddenWindows, %current_detect_hw% @@ -170,14 +166,12 @@ 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 %} } @@ -207,7 +201,6 @@ DetectHiddenWindows, %detect_hw% } - WinKill, %title%, %text%, %secondstowait%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% @@ -220,8 +213,6 @@ AHKWinWait(ByRef command) { {% block AHKWinWait %} - global WINDOWRESPONSEMESSAGE - global TIMEOUTRESPONSEMESSAGE title := command[2] text := command[3] @@ -250,10 +241,10 @@ WinWait, %title%, %text%,, %extitle%, %extext% } if (ErrorLevel = 1) { - resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") } else { WinGet, output, ID - resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + resp := FormatResponse("ahk.message.WindowResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% @@ -264,11 +255,8 @@ {% endblock AHKWinWait %} } - AHKWinWaitActive(ByRef command) { {% block AHKWinWaitActive %} - global WINDOWRESPONSEMESSAGE - global TIMEOUTRESPONSEMESSAGE title := command[2] text := command[3] @@ -297,10 +285,10 @@ WinWaitActive, %title%, %text%,, %extitle%, %extext% } if (ErrorLevel = 1) { - resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") } else { WinGet, output, ID - resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + resp := FormatResponse("ahk.message.WindowResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% @@ -311,11 +299,8 @@ {% endblock AHKWinWaitActive %} } - AHKWinWaitNotActive(ByRef command) { {% block AHKWinWaitNotActive %} - global WINDOWRESPONSEMESSAGE - global TIMEOUTRESPONSEMESSAGE title := command[2] text := command[3] @@ -344,10 +329,10 @@ WinWaitNotActive, %title%, %text%,, %extitle%, %extext% } if (ErrorLevel = 1) { - resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") } else { WinGet, output, ID - resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + resp := FormatResponse("ahk.message.WindowResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% @@ -360,7 +345,6 @@ AHKWinWaitClose(ByRef command) { {% block AHKWinWaitClose %} - global TIMEOUTRESPONSEMESSAGE title := command[2] text := command[3] @@ -389,7 +373,7 @@ WinWaitClose, %title%, %text%,, %extitle%, %extext% } if (ErrorLevel = 1) { - resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") } else { resp := FormatNoValueResponse() } @@ -412,7 +396,6 @@ match_mode := command[7] match_speed := command[8] - current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) if (match_mode != "") { @@ -427,7 +410,6 @@ DetectHiddenWindows, %detect_hw% } - WinMinimize, %title%, %text%, %secondstowait%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% @@ -462,7 +444,6 @@ DetectHiddenWindows, %detect_hw% } - WinMaximize, %title%, %text%, %secondstowait%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% @@ -483,7 +464,6 @@ match_mode := command[7] match_speed := command[8] - current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) if (match_mode != "") { @@ -498,7 +478,6 @@ DetectHiddenWindows, %detect_hw% } - WinRestore, %title%, %text%, %secondstowait%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% @@ -511,7 +490,7 @@ AHKWinIsActive(ByRef command) { {% block AHKWinIsActive %} - global BOOLEANRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -535,9 +514,9 @@ } if WinActive(title, text, extitle, extext) { - response := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + response := FormatResponse("ahk.message.BooleanResponseMessage", 1) } else { - response := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + response := FormatResponse("ahk.message.BooleanResponseMessage", 0) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -548,7 +527,7 @@ AHKWinGetID(ByRef command) { {% block AHKWinGetID %} - global WINDOWRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -576,7 +555,7 @@ if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(WINDOWRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.WindowResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -587,7 +566,7 @@ AHKWinGetTitle(ByRef command) { {% block AHKWinGetTitle %} - global STRINGRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -616,13 +595,13 @@ SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% - return FormatResponse(STRINGRESPONSEMESSAGE, text) + return FormatResponse("ahk.message.StringResponseMessage", text) {% endblock AHKWinGetTitle %} } AHKWinGetIDLast(ByRef command) { {% block AHKWinGetIDLast %} - global WINDOWRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -650,7 +629,7 @@ if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(WINDOWRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.WindowResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -659,10 +638,9 @@ {% endblock AHKWinGetIDLast %} } - AHKWinGetPID(ByRef command) { {% block AHKWinGetPID %} - global INTEGERRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -690,7 +668,7 @@ if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -699,10 +677,9 @@ {% endblock AHKWinGetPID %} } - AHKWinGetProcessName(ByRef command) { {% block AHKWinGetProcessName %} - global STRINGRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -730,7 +707,7 @@ if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.StringResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -741,7 +718,7 @@ AHKWinGetProcessPath(ByRef command) { {% block AHKWinGetProcessPath %} - global STRINGRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -769,7 +746,7 @@ if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.StringResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -778,10 +755,9 @@ {% endblock AHKWinGetProcessPath %} } - AHKWinGetCount(ByRef command) { {% block AHKWinGetCount %} - global INTEGERRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -807,9 +783,9 @@ WinGet, output, Count, %title%, %text%, %extitle%, %extext% if (output = 0) { - response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) } else { - response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -818,11 +794,9 @@ {% endblock AHKWinGetCount %} } - - AHKWinGetMinMax(ByRef command) { {% block AHKWinGetMinMax %} - global INTEGERRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -850,7 +824,7 @@ if (output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -861,8 +835,7 @@ AHKWinGetControlList(ByRef command) { {% block AHKWinGetControlList %} - global EXCEPTIONRESPONSEMESSAGE - global WINDOWCONTROLLISTRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -886,7 +859,6 @@ DetectHiddenWindows, %detect_hw% } - WinGet, ahkid, ID, %title%, %text%, %extitle%, %extext% if (ahkid = "") { @@ -897,13 +869,13 @@ WinGet, ctrListID, ControlListHWND, %title%, %text%, %extitle%, %extext% if (ctrListID = "") { - return FormatResponse(WINDOWCONTROLLISTRESPONSEMESSAGE, Format("('{}', [])", ahkid)) + return FormatResponse("ahk.message.WindowControlListResponseMessage", Format("('{}', [])", ahkid)) } ctrListArr := StrSplit(ctrList, "`n") ctrListIDArr := StrSplit(ctrListID, "`n") if (ctrListArr.Length() != ctrListIDArr.Length()) { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Control hwnd/class lists have unexpected lengths") + return FormatResponse("ahk.message.ExceptionResponseMessage", "Control hwnd/class lists have unexpected lengths") } output := Format("('{}', [", ahkid) @@ -914,7 +886,7 @@ } output .= "])" - response := FormatResponse(WINDOWCONTROLLISTRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.WindowControlListResponseMessage", output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -924,7 +896,7 @@ AHKWinGetTransparent(ByRef command) { {% block AHKWinGetTransparent %} - global INTEGERRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -949,7 +921,7 @@ } WinGet, output, Transparent, %title%, %text%, %extitle%, %extext% - response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -958,9 +930,7 @@ } AHKWinGetTransColor(ByRef command) { {% block AHKWinGetTransColor %} - global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -985,7 +955,7 @@ } WinGet, output, TransColor, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -994,9 +964,7 @@ } AHKWinGetStyle(ByRef command) { {% block AHKWinGetStyle %} - global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -1021,7 +989,7 @@ } WinGet, output, Style, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -1030,9 +998,7 @@ } AHKWinGetExStyle(ByRef command) { {% block AHKWinGetExStyle %} - global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -1057,7 +1023,7 @@ } WinGet, output, ExStyle, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -1067,8 +1033,7 @@ AHKWinGetText(ByRef command) { {% block AHKWinGetText %} - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -1094,9 +1059,9 @@ WinGetText, output,%title%,%text%,%extitle%,%extext% if (ErrorLevel = 1) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window text") + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was an error getting window text") } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.StringResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% @@ -1106,8 +1071,6 @@ {% endblock AHKWinGetText %} } - - AHKWinSetTitle(ByRef command) { {% block AHKWinSetTitle %} new_title := command[2] @@ -1272,7 +1235,6 @@ {% endblock AHKWinHide %} } - AHKWinSetTop(ByRef command) { {% block AHKWinSetTop %} title := command[2] @@ -1407,7 +1369,7 @@ AHKWinSetStyle(ByRef command) { {% block AHKWinSetStyle %} - global BOOLEANRESPONSEMESSAGE + style := command[2] title := command[3] text := command[4] @@ -1431,12 +1393,11 @@ DetectHiddenWindows, %detect_hw% } - WinSet, Style, %style%, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) } else { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -1447,7 +1408,7 @@ AHKWinSetExStyle(ByRef command) { {% block AHKWinSetExStyle %} - global BOOLEANRESPONSEMESSAGE + style := command[2] title := command[3] text := command[4] @@ -1471,12 +1432,11 @@ DetectHiddenWindows, %detect_hw% } - WinSet, ExStyle, %style%, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) } else { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -1487,7 +1447,7 @@ AHKWinSetRegion(ByRef command) { {% block AHKWinSetRegion %} - global BOOLEANRESPONSEMESSAGE + options := command[2] title := command[3] text := command[4] @@ -1511,12 +1471,11 @@ DetectHiddenWindows, %detect_hw% } - WinSet, Region, %options%, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) } else { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -1527,7 +1486,7 @@ AHKWinSetTransparent(ByRef command) { {% block AHKWinSetTransparent %} - global BOOLEANRESPONSEMESSAGE + transparency := command[2] title := command[3] text := command[4] @@ -1551,7 +1510,6 @@ DetectHiddenWindows, %detect_hw% } - WinSet, Transparent, %transparency%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -1562,7 +1520,7 @@ AHKWinSetTransColor(ByRef command) { {% block AHKWinSetTransColor %} - global BOOLEANRESPONSEMESSAGE + color := command[2] title := command[3] text := command[4] @@ -1586,7 +1544,6 @@ DetectHiddenWindows, %detect_hw% } - WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% @@ -1599,8 +1556,7 @@ AHKImageSearch(ByRef command) { {% block AHKImageSearch %} - global COORDINATERESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + imagepath := command[6] x1 := command[2] y1 := command[3] @@ -1623,18 +1579,16 @@ ImageSearch, xpos, ypos,% x1,% y1,% x2,% y2, %imagepath% - if (coord_mode != "") { CoordMode, Pixel, %current_mode% } - if (ErrorLevel = 2) { - s := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "there was a problem that prevented the command from conducting the search (such as failure to open the image file or a badly formatted option)") + s := FormatResponse("ahk.message.ExceptionResponseMessage", "there was a problem that prevented the command 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(COORDINATERESPONSEMESSAGE, Format("({}, {})", xpos, ypos)) + s := FormatResponse("ahk.message.CoordinateResponseMessage", Format("({}, {})", xpos, ypos)) } return s @@ -1643,7 +1597,7 @@ AHKPixelGetColor(ByRef command) { {% block AHKPixelGetColor %} - global STRINGRESPONSEMESSAGE + x := command[2] y := command[3] coord_mode := command[4] @@ -1662,14 +1616,13 @@ CoordMode, Pixel, %current_mode% } - return FormatResponse(STRINGRESPONSEMESSAGE, color) + return FormatResponse("ahk.message.StringResponseMessage", color) {% endblock AHKPixelGetColor %} } AHKPixelSearch(ByRef command) { {% block AHKPixelSearch %} - global COORDINATERESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + x1 := command[2] y1 := command[3] x2 := command[4] @@ -1695,20 +1648,19 @@ return FormatNoValueResponse() } else if (ErrorLevel = 0) { payload := Format("({}, {})", resultx, resulty) - return FormatResponse(COORDINATERESPONSEMESSAGE, payload) + return FormatResponse("ahk.message.CoordinateResponseMessage", payload) } else if (ErrorLevel = 2) { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem conducting the pixel search (ErrorLevel 2)") + return FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem conducting the pixel search (ErrorLevel 2)") } else { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Unexpected error. This is probably a bug. Please report this at https://github.com/spyoungtech/ahk/issues") + 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(ByRef command) { {% block AHKMouseGetPos %} - global COORDINATERESPONSEMESSAGE + coord_mode := command[2] current_coord_mode := Format("{}", A_CoordModeMouse) if (coord_mode != "") { @@ -1717,7 +1669,7 @@ MouseGetPos, xpos, ypos payload := Format("({}, {})", xpos, ypos) - resp := FormatResponse(COORDINATERESPONSEMESSAGE, payload) + resp := FormatResponse("ahk.message.CoordinateResponseMessage", payload) if (coord_mode != "") { CoordMode, Mouse, %current_coord_mode% @@ -1729,10 +1681,6 @@ AHKKeyState(ByRef command) { {% block AHKKeyState %} - global INTEGERRESPONSEMESSAGE - global FLOATRESPONSEMESSAGE - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE keyname := command[2] mode := command[3] @@ -1747,15 +1695,15 @@ } if state is integer - return FormatResponse(INTEGERRESPONSEMESSAGE, state) + return FormatResponse("ahk.message.IntegerResponseMessage", state) if state is float - return FormatResponse(FLOATRESPONSEMESSAGE, state) + return FormatResponse("ahk.message.FloatResponseMessage", state) if state is alnum - return FormatResponse(STRINGRESPONSEMESSAGE, state) + return FormatResponse("ahk.message.StringResponseMessage", state) - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, state) + return FormatResponse("ahk.message.ExceptionResponseMessage", state) {% endblock AHKKeyState %} } @@ -1775,7 +1723,6 @@ {% endblock AHKMouseMove %} } - AHKClick(ByRef command) { {% block AHKClick %} x := command[2] @@ -1804,26 +1751,25 @@ AHKGetCoordMode(ByRef command) { {% block AHKGetCoordMode %} - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + target := command[2] if (target = "ToolTip") { - return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeToolTip) + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeToolTip) } if (target = "Pixel") { - return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModePixel) + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModePixel) } if (target = "Mouse") { - return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeMouse) + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMouse) } if (target = "Caret") { - return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeCaret) + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeCaret) } if (target = "Menu") { - return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeMenu) + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMenu) } - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Invalid coord mode") + return FormatResponse("ahk.message.ExceptionResponseMessage", "Invalid coord mode") {% endblock AHKGetCoordMode %} } @@ -1867,35 +1813,32 @@ AHKRegRead(ByRef command) { {% block RegRead %} - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + key_name := command[2] value_name := command[3] RegRead, output, %key_name%, %value_name% if (ErrorLevel = 1) { - resp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + resp := FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) } else { - resp := FormatResponse(STRINGRESPONSEMESSAGE, Format("{}", output)) + resp := FormatResponse("ahk.message.StringResponseMessage", Format("{}", output)) } return resp {% endblock RegRead %} } - - AHKRegWrite(ByRef command) { {% block RegWrite %} - global EXCEPTIONRESPONSEMESSAGE + value_type := command[2] key_name := command[3] value_name := command[4] value := command[5] RegWrite, %value_type%, %key_name%, %value_name%, %value% if (ErrorLevel = 1) { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) } return FormatNoValueResponse() @@ -1904,12 +1847,12 @@ AHKRegDelete(ByRef command) { {% block RegDelete %} - global EXCEPTIONRESPONSEMESSAGE + key_name := command[2] value_name := command[3] RegDelete, %key_name%, %value_name% if (ErrorLevel = 1) { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) } return FormatNoValueResponse() @@ -1918,7 +1861,7 @@ AHKKeyWait(ByRef command) { {% block AHKKeyWait %} - global INTEGERRESPONSEMESSAGE + keyname := command[2] if (command.Length() = 2) { KeyWait,% keyname @@ -1926,7 +1869,7 @@ options := command[3] KeyWait,% keyname,% options } - return FormatResponse(INTEGERRESPONSEMESSAGE, ErrorLevel) + return FormatResponse("ahk.message.IntegerResponseMessage", ErrorLevel) {% endblock AHKKeyWait %} } @@ -1936,8 +1879,6 @@ {% endblock SetKeyDelay %} } - - AHKSend(ByRef command) { {% block AHKSend %} str := command[2] @@ -2001,7 +1942,6 @@ {% endblock AHKSendInput %} } - AHKSendEvent(ByRef command) { {% block AHKSendEvent %} str := command[2] @@ -2067,13 +2007,9 @@ {% endblock HideTrayTip %} } - - - AHKWinGetClass(ByRef command) { {% block AHKWinGetClass %} - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -2099,9 +2035,9 @@ WinGetClass, output,%title%,%text%,%extitle%,%extext% if (ErrorLevel = 1) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window class") + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was an error getting window class") } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.StringResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% @@ -2146,12 +2082,8 @@ {% endblock AHKWinActivate %} } - - - AHKWindowList(ByRef command) { {% block AHKWindowList %} - global WINDOWLISTRESPONSEMESSAGE current_detect_hw := Format("{}", A_DetectHiddenWindows) @@ -2182,7 +2114,7 @@ id := windows%A_Index% r .= id . "`," } - resp := FormatResponse(WINDOWLISTRESPONSEMESSAGE, r) + resp := FormatResponse("ahk.message.WindowListResponseMessage", r) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -2190,11 +2122,9 @@ {% endblock AHKWindowList %} } - - AHKControlClick(ByRef command) { {% block AHKControlClick %} - global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] title := command[3] text := command[4] @@ -2224,7 +2154,7 @@ ControlClick, %ctrl%, %title%, %text%, %button%, %click_count%, %options%, %exclude_title%, %exclude_text% if (ErrorLevel != 0) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Failed to click control") + response := FormatResponse("ahk.message.ExceptionResponseMessage", "Failed to click control") } else { response := FormatNoValueResponse() } @@ -2239,8 +2169,7 @@ AHKControlGetText(ByRef command) { {% block AHKControlGetText %} - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] title := command[3] text := command[4] @@ -2267,9 +2196,9 @@ ControlGetText, result, %ctrl%, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the text") + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the text") } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, result) + response := FormatResponse("ahk.message.StringResponseMessage", result) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -2279,11 +2208,9 @@ {% endblock AHKControlGetText %} } - AHKControlGetPos(ByRef command) { {% block AHKControlGetPos %} - global POSITIONRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] title := command[3] text := command[4] @@ -2309,10 +2236,10 @@ ControlGetPos, x, y, w, h, %ctrl%, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the text") + 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(PositionResponseMessage, result) + response := FormatResponse("ahk.message.PositionResponseMessage", result) } DetectHiddenWindows, %current_detect_hw% @@ -2321,7 +2248,6 @@ return response - {% endblock AHKControlGetPos %} } @@ -2358,39 +2284,34 @@ {% endblock AHKControlSend %} } - - - AHKWinFromMouse(ByRef command) { {% block AHKWinFromMouse %} - global WINDOWRESPONSEMESSAGE + MouseGetPos,,, MouseWin if (MouseWin = "") { return FormatNoValueResponse() } - return FormatResponse(WINDOWRESPONSEMESSAGE, MouseWin) + return FormatResponse("ahk.message.WindowResponseMessage", MouseWin) {% endblock AHKWinFromMouse %} } - AHKWinIsAlwaysOnTop(ByRef command) { {% block AHKWinIsAlwaysOnTop %} - global BOOLEANRESPONSEMESSAGE + title := command[2] WinGet, ExStyle, ExStyle, %title% if (ExStyle = "") return FormatNoValueResponse() if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. - return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + return FormatResponse("ahk.message.BooleanResponseMessage", 1) else - return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + return FormatResponse("ahk.message.BooleanResponseMessage", 0) {% endblock AHKWinIsAlwaysOnTop %} } - AHKWinMove(ByRef command) { {% block AHKWinMove %} title := command[2] @@ -2432,8 +2353,6 @@ AHKWinGetPos(ByRef command) { {% block AHKWinGetPos %} - global POSITIONRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE title := command[2] text := command[3] @@ -2460,10 +2379,10 @@ WinGetPos, x, y, w, h, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the position") + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the position") } else { result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) - response := FormatResponse(PositionResponseMessage, result) + response := FormatResponse("ahk.message.PositionResponseMessage", result) } DetectHiddenWindows, %current_detect_hw% @@ -2474,23 +2393,21 @@ {% endblock AHKWinGetPos %} } - AHKGetVolume(ByRef command) { {% block AHKGetVolume %} - global EXCEPTIONRESPONSEMESSAGE - global FLOATRESPONSEMESSAGE + device_number := command[2] try { SoundGetWaveVolume, retval, %device_number% } catch e { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {} ({})", device_number, e.message)) + 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(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {}", device_number)) + response := FormatResponse("ahk.message.ExceptionResponseMessage", Format("There was a problem getting the volume with device of index {}", device_number)) } else { - response := FormatResponse(FLOATRESPONSEMESSAGE, Format("{}", retval)) + response := FormatResponse("ahk.message.FloatResponseMessage", Format("{}", retval)) } return response {% endblock AHKGetVolume %} @@ -2507,14 +2424,14 @@ AHKSoundGet(ByRef command) { {% block AHKSoundGet %} - global STRINGRESPONSEMESSAGE + device_number := command[2] component_type := command[3] control_type := command[4] SoundGet, retval, %component_type%, %control_type%, %device_number% ; TODO interpret return type - return FormatResponse(STRINGRESPONSEMESSAGE, Format("{}", retval)) + return FormatResponse("ahk.message.StringResponseMessage", Format("{}", retval)) {% endblock AHKSoundGet %} } @@ -2555,8 +2472,8 @@ AHKEcho(ByRef command) { {% block AHKEcho %} - global STRINGRESPONSEMESSAGE - return FormatResponse(STRINGRESPONSEMESSAGE, command) + arg := command[2] + return FormatResponse("ahk.message.StringResponseMessage", arg) {% endblock AHKEcho %} } @@ -2585,8 +2502,8 @@ AHKGetClipboard(ByRef command) { {% block AHKGetClipboard %} - global STRINGRESPONSEMESSAGE - return FormatResponse(STRINGRESPONSEMESSAGE, Clipboard) + + return FormatResponse("ahk.message.StringResponseMessage", Clipboard) {% endblock AHKGetClipboard %} } @@ -2615,15 +2532,14 @@ } AHKClipWait(ByRef command) { - global TIMEOUTRESPONSEMESSAGE + timeout := command[2] wait_for_any_data := command[3] - ClipWait, %timeout%, %wait_for_any_data% if (ErrorLevel = 1) { - return FormatResponse(TIMEOUTRESPONSEMESSAGE, "timed out waiting for clipboard data") + return FormatResponse("ahk.message.TimeoutResponseMessage", "timed out waiting for clipboard data") } return FormatNoValueResponse() } @@ -2654,47 +2570,45 @@ } AHKGuiNew(ByRef command) { - global STRINGRESPONSEMESSAGE + options := command[2] title := command[3] Gui, New, %options%, %title% - return FormatResponse(STRINGRESPONSEMESSAGE, hwnd) + return FormatResponse("ahk.message.StringResponseMessage", hwnd) } AHKMsgBox(ByRef command) { - global TIMEOUTRESPONSEMESSAGE - global STRINGRESPONSEMESSAGE + options := command[2] title := command[3] text := command[4] timeout := command[5] MsgBox,% options, %title%, %text%, %timeout% IfMsgBox, Yes - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Yes") + ret := FormatResponse("ahk.message.StringResponseMessage", "Yes") IfMsgBox, No - ret := FormatResponse(STRINGRESPONSEMESSAGE, "No") + ret := FormatResponse("ahk.message.StringResponseMessage", "No") IfMsgBox, OK - ret := FormatResponse(STRINGRESPONSEMESSAGE, "OK") + ret := FormatResponse("ahk.message.StringResponseMessage", "OK") IfMsgBox, Cancel - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Cancel") + ret := FormatResponse("ahk.message.StringResponseMessage", "Cancel") IfMsgBox, Abort - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Abort") + ret := FormatResponse("ahk.message.StringResponseMessage", "Abort") IfMsgBox, Ignore - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Ignore") + ret := FormatResponse("ahk.message.StringResponseMessage", "Ignore") IfMsgBox, Retry - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Retry") + ret := FormatResponse("ahk.message.StringResponseMessage", "Retry") IfMsgBox, Continue - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Continue") + ret := FormatResponse("ahk.message.StringResponseMessage", "Continue") IfMsgBox, TryAgain - ret := FormatResponse(STRINGRESPONSEMESSAGE, "TryAgain") + ret := FormatResponse("ahk.message.StringResponseMessage", "TryAgain") IfMsgBox, Timeout - ret := FormatResponse(TIMEOUTRESPONSEMESSAGE, "MsgBox timed out") + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "MsgBox timed out") return ret } AHKInputBox(ByRef command) { - global STRINGRESPONSEMESSAGE - global TIMEOUTRESPONSEMESSAGE + title := command[2] prompt := command[3] hide := command[4] @@ -2708,17 +2622,17 @@ InputBox, output, %title%, %prompt%, %hide%, %width%, %height%, %x%, %y%, %locale%, %timeout%, %default% if (ErrorLevel = 2) { - ret := FormatResponse(TIMEOUTRESPONSEMESSAGE, "Input box timed out") + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "Input box timed out") } else if (ErrorLevel = 1) { ret := FormatNoValueResponse() } else { - ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + ret := FormatResponse("ahk.message.StringResponseMessage", output) } return ret } AHKFileSelectFile(byRef command) { - global STRINGRESPONSEMESSAGE + options := command[2] root := command[3] title := command[4] @@ -2727,13 +2641,13 @@ if (ErrorLevel = 1) { ret := FormatNoValueResponse() } else { - ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + ret := FormatResponse("ahk.message.StringResponseMessage", output) } return ret } AHKFileSelectFolder(byRef command) { - global STRINGRESPONSEMESSAGE + starting_folder := command[2] options := command[3] prompt := command[4] @@ -2743,7 +2657,7 @@ if (ErrorLevel = 1) { ret := FormatNoValueResponse() } else { - ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + ret := FormatResponse("ahk.message.StringResponseMessage", output) } return ret } @@ -2771,7 +2685,6 @@ 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) { @@ -2799,7 +2712,6 @@ ; [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 "" @@ -2813,7 +2725,6 @@ throw Exception(msg, -1) } - VarSetCapacity(ret, buff_size * (A_IsUnicode ? 2 : 1)) ; Now we do the conversion to base64 and rteturn the string @@ -2826,7 +2737,6 @@ return ret } - ; End of included content CommandArrayFromQuery(ByRef text) { @@ -2868,14 +2778,14 @@ } catch e { {% block function_error_handle %} message := Format("Error occurred in {}. The error message was: {}", e.What, e.message) - pyresp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, message) + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) {% endblock function_error_handle %} } {% block send_response %} if (pyresp) { FileAppend, %pyresp%, *, UTF-8 } else { - msg := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("Unknown Error when calling {}", func)) + msg := FormatResponse("ahk.message.ExceptionResponseMessage", Format("Unknown Error when calling {}", func)) FileAppend, %msg%, *, UTF-8 } {% endblock send_response %} diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 11ccb00a..e786a197 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -631,7 +631,7 @@ def __init__( directives: Optional[list[Directive | Type[Directive]]] = None, jinja_loader: Optional[jinja2.BaseLoader] = None, template: Optional[jinja2.Template] = None, - extensions: list[Extension] | None = None + extensions: list[Extension] | None = None, ): self._extensions = extensions or [] self._proc: Optional[SyncAHKProcess] @@ -687,7 +687,13 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A 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, extensions=self._extensions, **kwargs) + return template.render( + directives=self._directives, + message_types=message_types, + message_registry=_message_registry, + extensions=self._extensions, + **kwargs, + ) @property def lock(self) -> Any: diff --git a/ahk/message.py b/ahk/message.py index be103594..3a0f1316 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -100,16 +100,18 @@ def tom_generator() -> Generator[bytes, None, None]: class ResponseMessage: - type: Optional[str] = None _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 - assert cls.type is not None, f'must assign a type for class {cls!r}' super().__init_subclass__(**kwargs) def __init__(self, raw_content: bytes, engine: Optional[Union[AsyncAHK, AHK]] = None): @@ -117,7 +119,7 @@ def __init__(self, raw_content: bytes, engine: Optional[Union[AsyncAHK, AHK]] = self._engine: Optional[Union[AsyncAHK, AHK]] = engine def __repr__(self) -> str: - return f'ResponseMessage' + return f'ResponseMessage' @staticmethod def _tom_lookup(tom: bytes) -> 'ResponseMessageClassTypes': @@ -147,8 +149,6 @@ def unpack(self) -> Any: class TupleResponseMessage(ResponseMessage): - type = 'tuple' - def unpack(self) -> Tuple[Any, ...]: s = self._raw_content.decode(encoding='utf-8') val = ast.literal_eval(s) @@ -157,8 +157,6 @@ def unpack(self) -> Tuple[Any, ...]: class CoordinateResponseMessage(ResponseMessage): - type = 'coordinate' - def unpack(self) -> Tuple[int, int]: s = self._raw_content.decode(encoding='utf-8') val = ast.literal_eval(s) @@ -168,8 +166,6 @@ def unpack(self) -> Tuple[int, int]: class IntegerResponseMessage(ResponseMessage): - type = 'integer' - def unpack(self) -> int: s = self._raw_content.decode(encoding='utf-8') val = ast.literal_eval(s) @@ -178,8 +174,6 @@ def unpack(self) -> int: class BooleanResponseMessage(IntegerResponseMessage): - type = 'boolean' - def unpack(self) -> bool: val = super().unpack() assert val in (1, 0) @@ -187,15 +181,11 @@ def unpack(self) -> bool: class StringResponseMessage(ResponseMessage): - type = 'string' - def unpack(self) -> str: return self._raw_content.decode('utf-8') class WindowListResponseMessage(ResponseMessage): - type = 'windowlist' - def unpack(self) -> Union[List[Window], List[AsyncWindow]]: from ._async.engine import AsyncAHK from ._async.window import AsyncWindow @@ -216,8 +206,6 @@ def unpack(self) -> Union[List[Window], List[AsyncWindow]]: class NoValueResponseMessage(ResponseMessage): - type = 'novalue' - def unpack(self) -> None: assert self._raw_content == b'\xee\x80\x80', f'Unexpected or Malformed response: {self._raw_content!r}' return None @@ -228,7 +216,6 @@ class AHKExecutionException(Exception): class ExceptionResponseMessage(ResponseMessage): - type = 'exception' _exception_type: Type[Exception] = AHKExecutionException def unpack(self) -> NoReturn: @@ -237,8 +224,6 @@ def unpack(self) -> NoReturn: class WindowControlListResponseMessage(ResponseMessage): - type = 'windowcontrollist' - def unpack(self) -> Union[List[AsyncControl], List[Control]]: from ._async.engine import AsyncAHK from ._async.window import AsyncWindow, AsyncControl @@ -272,8 +257,6 @@ def unpack(self) -> Union[List[AsyncControl], List[Control]]: class WindowResponseMessage(ResponseMessage): - type = 'window' - def unpack(self) -> Union[Window, AsyncWindow]: from ._async.engine import AsyncAHK from ._async.window import AsyncWindow @@ -293,8 +276,6 @@ def unpack(self) -> Union[Window, AsyncWindow]: class PositionResponseMessage(TupleResponseMessage): - type = 'position' - def unpack(self) -> Position: resp = super().unpack() if not len(resp) == 4: @@ -304,8 +285,6 @@ def unpack(self) -> Position: class FloatResponseMessage(ResponseMessage): - type = 'float' - def unpack(self) -> float: s = self._raw_content.decode(encoding='utf-8') val = ast.literal_eval(s) @@ -314,13 +293,10 @@ def unpack(self) -> float: class TimeoutResponseMessage(ExceptionResponseMessage): - type = 'timeoutexception' _exception_type = TimeoutError class B64BinaryResponseMessage(ResponseMessage): - type = 'binary' - def unpack(self) -> bytes: b64_content = self._raw_content b = base64.b64decode(b64_content) diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 22b7ab87..3daf2c73 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -25,30 +25,26 @@ {% endblock directives %} {% block message_types %} -{% for tom, name in message_types.items() %} -{{ name }} := "{{ tom }}" -{% endfor %} +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", MessageType, newline_count, payload) + response := Format("{}`n{}`n{}`n", MESSAGE_TYPES[MessageType], newline_count, payload) return response } FormatNoValueResponse() { global NOVALUE_SENTINEL - global NOVALUERESPONSEMESSAGE - return FormatResponse(NOVALUERESPONSEMESSAGE, NOVALUE_SENTINEL) + return FormatResponse("ahk.message.NoValueResponseMessage", NOVALUE_SENTINEL) } FormatBinaryResponse(ByRef bin) { - global B64BINARYRESPONSEMESSAGE b64 := b64encode(bin) - return FormatResponse(B64BINARYRESPONSEMESSAGE, b64) + return FormatResponse("ahk.message.B64BinaryResponseMessage", b64) } AHKSetDetectHiddenWindows(ByRef command) { @@ -75,15 +71,15 @@ AHKSetTitleMatchMode(ByRef command) { AHKGetTitleMatchMode(ByRef command) { {% block AHKGetTitleMatchMode %} - global STRINGRESPONSEMESSAGE - return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchMode) + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) {% endblock AHKGetTitleMatchMode %} } AHKGetTitleMatchSpeed(ByRef command) { {% block AHKGetTitleMatchSpeed %} - global STRINGRESPONSEMESSAGE - return FormatResponse(STRINGRESPONSEMESSAGE, A_TitleMatchModeSpeed) + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) {% endblock AHKGetTitleMatchSpeed %} } @@ -97,14 +93,14 @@ AHKSetSendLevel(ByRef command) { AHKGetSendLevel(ByRef command) { {% block AHKGetSendLevel %} - global INTEGERRESPONSEMESSAGE - return FormatResponse(INTEGERRESPONSEMESSAGE, A_SendLevel) + + return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) {% endblock AHKGetSendLevel %} } AHKWinExist(ByRef command) { {% block AHKWinExist %} - global BOOLEANRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -129,9 +125,9 @@ AHKWinExist(ByRef command) { } if WinExist(title, text, extitle, extext) { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) } else { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) } DetectHiddenWindows, %current_detect_hw% @@ -167,14 +163,12 @@ AHKWinClose(ByRef command) { 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 %} } @@ -204,7 +198,6 @@ AHKWinKill(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinKill, %title%, %text%, %secondstowait%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% @@ -217,8 +210,6 @@ AHKWinKill(ByRef command) { AHKWinWait(ByRef command) { {% block AHKWinWait %} - global WINDOWRESPONSEMESSAGE - global TIMEOUTRESPONSEMESSAGE title := command[2] text := command[3] @@ -247,10 +238,10 @@ AHKWinWait(ByRef command) { WinWait, %title%, %text%,, %extitle%, %extext% } if (ErrorLevel = 1) { - resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") } else { WinGet, output, ID - resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + resp := FormatResponse("ahk.message.WindowResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% @@ -261,11 +252,8 @@ AHKWinWait(ByRef command) { {% endblock AHKWinWait %} } - AHKWinWaitActive(ByRef command) { {% block AHKWinWaitActive %} - global WINDOWRESPONSEMESSAGE - global TIMEOUTRESPONSEMESSAGE title := command[2] text := command[3] @@ -294,10 +282,10 @@ AHKWinWaitActive(ByRef command) { WinWaitActive, %title%, %text%,, %extitle%, %extext% } if (ErrorLevel = 1) { - resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") } else { WinGet, output, ID - resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + resp := FormatResponse("ahk.message.WindowResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% @@ -308,11 +296,8 @@ AHKWinWaitActive(ByRef command) { {% endblock AHKWinWaitActive %} } - AHKWinWaitNotActive(ByRef command) { {% block AHKWinWaitNotActive %} - global WINDOWRESPONSEMESSAGE - global TIMEOUTRESPONSEMESSAGE title := command[2] text := command[3] @@ -341,10 +326,10 @@ AHKWinWaitNotActive(ByRef command) { WinWaitNotActive, %title%, %text%,, %extitle%, %extext% } if (ErrorLevel = 1) { - resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") } else { WinGet, output, ID - resp := FormatResponse(WINDOWRESPONSEMESSAGE, output) + resp := FormatResponse("ahk.message.WindowResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% @@ -357,7 +342,6 @@ AHKWinWaitNotActive(ByRef command) { AHKWinWaitClose(ByRef command) { {% block AHKWinWaitClose %} - global TIMEOUTRESPONSEMESSAGE title := command[2] text := command[3] @@ -386,7 +370,7 @@ AHKWinWaitClose(ByRef command) { WinWaitClose, %title%, %text%,, %extitle%, %extext% } if (ErrorLevel = 1) { - resp := FormatResponse(TIMEOUTRESPONSEMESSAGE, "WinWait timed out waiting for window") + resp := FormatResponse("ahk.message.TimeoutResponseMessage", "WinWait timed out waiting for window") } else { resp := FormatNoValueResponse() } @@ -409,7 +393,6 @@ AHKWinMinimize(ByRef command) { match_mode := command[7] match_speed := command[8] - current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) if (match_mode != "") { @@ -424,7 +407,6 @@ AHKWinMinimize(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinMinimize, %title%, %text%, %secondstowait%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% @@ -459,7 +441,6 @@ AHKWinMaximize(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinMaximize, %title%, %text%, %secondstowait%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% @@ -480,7 +461,6 @@ AHKWinRestore(ByRef command) { match_mode := command[7] match_speed := command[8] - current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) if (match_mode != "") { @@ -495,7 +475,6 @@ AHKWinRestore(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinRestore, %title%, %text%, %secondstowait%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% @@ -508,7 +487,7 @@ AHKWinRestore(ByRef command) { AHKWinIsActive(ByRef command) { {% block AHKWinIsActive %} - global BOOLEANRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -532,9 +511,9 @@ AHKWinIsActive(ByRef command) { } if WinActive(title, text, extitle, extext) { - response := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + response := FormatResponse("ahk.message.BooleanResponseMessage", 1) } else { - response := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + response := FormatResponse("ahk.message.BooleanResponseMessage", 0) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -545,7 +524,7 @@ AHKWinIsActive(ByRef command) { AHKWinGetID(ByRef command) { {% block AHKWinGetID %} - global WINDOWRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -573,7 +552,7 @@ AHKWinGetID(ByRef command) { if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(WINDOWRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.WindowResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -584,7 +563,7 @@ AHKWinGetID(ByRef command) { AHKWinGetTitle(ByRef command) { {% block AHKWinGetTitle %} - global STRINGRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -613,13 +592,13 @@ AHKWinGetTitle(ByRef command) { SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% - return FormatResponse(STRINGRESPONSEMESSAGE, text) + return FormatResponse("ahk.message.StringResponseMessage", text) {% endblock AHKWinGetTitle %} } AHKWinGetIDLast(ByRef command) { {% block AHKWinGetIDLast %} - global WINDOWRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -647,7 +626,7 @@ AHKWinGetIDLast(ByRef command) { if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(WINDOWRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.WindowResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -656,10 +635,9 @@ AHKWinGetIDLast(ByRef command) { {% endblock AHKWinGetIDLast %} } - AHKWinGetPID(ByRef command) { {% block AHKWinGetPID %} - global INTEGERRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -687,7 +665,7 @@ AHKWinGetPID(ByRef command) { if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -696,10 +674,9 @@ AHKWinGetPID(ByRef command) { {% endblock AHKWinGetPID %} } - AHKWinGetProcessName(ByRef command) { {% block AHKWinGetProcessName %} - global STRINGRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -727,7 +704,7 @@ AHKWinGetProcessName(ByRef command) { if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.StringResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -738,7 +715,7 @@ AHKWinGetProcessName(ByRef command) { AHKWinGetProcessPath(ByRef command) { {% block AHKWinGetProcessPath %} - global STRINGRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -766,7 +743,7 @@ AHKWinGetProcessPath(ByRef command) { if (output = 0 || output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.StringResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -775,10 +752,9 @@ AHKWinGetProcessPath(ByRef command) { {% endblock AHKWinGetProcessPath %} } - AHKWinGetCount(ByRef command) { {% block AHKWinGetCount %} - global INTEGERRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -804,9 +780,9 @@ AHKWinGetCount(ByRef command) { WinGet, output, Count, %title%, %text%, %extitle%, %extext% if (output = 0) { - response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) } else { - response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -815,11 +791,9 @@ AHKWinGetCount(ByRef command) { {% endblock AHKWinGetCount %} } - - AHKWinGetMinMax(ByRef command) { {% block AHKWinGetMinMax %} - global INTEGERRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -847,7 +821,7 @@ AHKWinGetMinMax(ByRef command) { if (output = "") { response := FormatNoValueResponse() } else { - response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -858,8 +832,7 @@ AHKWinGetMinMax(ByRef command) { AHKWinGetControlList(ByRef command) { {% block AHKWinGetControlList %} - global EXCEPTIONRESPONSEMESSAGE - global WINDOWCONTROLLISTRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -883,7 +856,6 @@ AHKWinGetControlList(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinGet, ahkid, ID, %title%, %text%, %extitle%, %extext% if (ahkid = "") { @@ -894,13 +866,13 @@ AHKWinGetControlList(ByRef command) { WinGet, ctrListID, ControlListHWND, %title%, %text%, %extitle%, %extext% if (ctrListID = "") { - return FormatResponse(WINDOWCONTROLLISTRESPONSEMESSAGE, Format("('{}', [])", ahkid)) + return FormatResponse("ahk.message.WindowControlListResponseMessage", Format("('{}', [])", ahkid)) } ctrListArr := StrSplit(ctrList, "`n") ctrListIDArr := StrSplit(ctrListID, "`n") if (ctrListArr.Length() != ctrListIDArr.Length()) { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Control hwnd/class lists have unexpected lengths") + return FormatResponse("ahk.message.ExceptionResponseMessage", "Control hwnd/class lists have unexpected lengths") } output := Format("('{}', [", ahkid) @@ -911,7 +883,7 @@ AHKWinGetControlList(ByRef command) { } output .= "])" - response := FormatResponse(WINDOWCONTROLLISTRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.WindowControlListResponseMessage", output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -921,7 +893,7 @@ AHKWinGetControlList(ByRef command) { AHKWinGetTransparent(ByRef command) { {% block AHKWinGetTransparent %} - global INTEGERRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -946,7 +918,7 @@ AHKWinGetTransparent(ByRef command) { } WinGet, output, Transparent, %title%, %text%, %extitle%, %extext% - response := FormatResponse(INTEGERRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.IntegerResponseMessage", output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -955,9 +927,7 @@ AHKWinGetTransparent(ByRef command) { } AHKWinGetTransColor(ByRef command) { {% block AHKWinGetTransColor %} - global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -982,7 +952,7 @@ AHKWinGetTransColor(ByRef command) { } WinGet, output, TransColor, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -991,9 +961,7 @@ AHKWinGetTransColor(ByRef command) { } AHKWinGetStyle(ByRef command) { {% block AHKWinGetStyle %} - global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -1018,7 +986,7 @@ AHKWinGetStyle(ByRef command) { } WinGet, output, Style, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -1027,9 +995,7 @@ AHKWinGetStyle(ByRef command) { } AHKWinGetExStyle(ByRef command) { {% block AHKWinGetExStyle %} - global STRINGRESPONSEMESSAGE - global INTEGERRESPONSEMESSAGE - global NOVALUERESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -1054,7 +1020,7 @@ AHKWinGetExStyle(ByRef command) { } WinGet, output, ExStyle, %title%, %text%, %extitle%, %extext% - response := FormatResponse(NOVALUERESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.NoValueResponseMessage", output) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -1064,8 +1030,7 @@ AHKWinGetExStyle(ByRef command) { AHKWinGetText(ByRef command) { {% block AHKWinGetText %} - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -1091,9 +1056,9 @@ AHKWinGetText(ByRef command) { WinGetText, output,%title%,%text%,%extitle%,%extext% if (ErrorLevel = 1) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window text") + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was an error getting window text") } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.StringResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% @@ -1103,8 +1068,6 @@ AHKWinGetText(ByRef command) { {% endblock AHKWinGetText %} } - - AHKWinSetTitle(ByRef command) { {% block AHKWinSetTitle %} new_title := command[2] @@ -1269,7 +1232,6 @@ AHKWinHide(ByRef command) { {% endblock AHKWinHide %} } - AHKWinSetTop(ByRef command) { {% block AHKWinSetTop %} title := command[2] @@ -1404,7 +1366,7 @@ AHKWinSetRedraw(ByRef command) { AHKWinSetStyle(ByRef command) { {% block AHKWinSetStyle %} - global BOOLEANRESPONSEMESSAGE + style := command[2] title := command[3] text := command[4] @@ -1428,12 +1390,11 @@ AHKWinSetStyle(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinSet, Style, %style%, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) } else { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -1444,7 +1405,7 @@ AHKWinSetStyle(ByRef command) { AHKWinSetExStyle(ByRef command) { {% block AHKWinSetExStyle %} - global BOOLEANRESPONSEMESSAGE + style := command[2] title := command[3] text := command[4] @@ -1468,12 +1429,11 @@ AHKWinSetExStyle(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinSet, ExStyle, %style%, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) } else { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -1484,7 +1444,7 @@ AHKWinSetExStyle(ByRef command) { AHKWinSetRegion(ByRef command) { {% block AHKWinSetRegion %} - global BOOLEANRESPONSEMESSAGE + options := command[2] title := command[3] text := command[4] @@ -1508,12 +1468,11 @@ AHKWinSetRegion(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinSet, Region, %options%, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) } else { - resp := FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -1524,7 +1483,7 @@ AHKWinSetRegion(ByRef command) { AHKWinSetTransparent(ByRef command) { {% block AHKWinSetTransparent %} - global BOOLEANRESPONSEMESSAGE + transparency := command[2] title := command[3] text := command[4] @@ -1548,7 +1507,6 @@ AHKWinSetTransparent(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinSet, Transparent, %transparency%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -1559,7 +1517,7 @@ AHKWinSetTransparent(ByRef command) { AHKWinSetTransColor(ByRef command) { {% block AHKWinSetTransColor %} - global BOOLEANRESPONSEMESSAGE + color := command[2] title := command[3] text := command[4] @@ -1583,7 +1541,6 @@ AHKWinSetTransColor(ByRef command) { DetectHiddenWindows, %detect_hw% } - WinSet, TransColor, %color%, %title%, %text%, %extitle%, %extext% DetectHiddenWindows, %current_detect_hw% @@ -1596,8 +1553,7 @@ AHKWinSetTransColor(ByRef command) { AHKImageSearch(ByRef command) { {% block AHKImageSearch %} - global COORDINATERESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + imagepath := command[6] x1 := command[2] y1 := command[3] @@ -1620,18 +1576,16 @@ AHKImageSearch(ByRef command) { ImageSearch, xpos, ypos,% x1,% y1,% x2,% y2, %imagepath% - if (coord_mode != "") { CoordMode, Pixel, %current_mode% } - if (ErrorLevel = 2) { - s := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "there was a problem that prevented the command from conducting the search (such as failure to open the image file or a badly formatted option)") + s := FormatResponse("ahk.message.ExceptionResponseMessage", "there was a problem that prevented the command 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(COORDINATERESPONSEMESSAGE, Format("({}, {})", xpos, ypos)) + s := FormatResponse("ahk.message.CoordinateResponseMessage", Format("({}, {})", xpos, ypos)) } return s @@ -1640,7 +1594,7 @@ AHKImageSearch(ByRef command) { AHKPixelGetColor(ByRef command) { {% block AHKPixelGetColor %} - global STRINGRESPONSEMESSAGE + x := command[2] y := command[3] coord_mode := command[4] @@ -1659,14 +1613,13 @@ AHKPixelGetColor(ByRef command) { CoordMode, Pixel, %current_mode% } - return FormatResponse(STRINGRESPONSEMESSAGE, color) + return FormatResponse("ahk.message.StringResponseMessage", color) {% endblock AHKPixelGetColor %} } AHKPixelSearch(ByRef command) { {% block AHKPixelSearch %} - global COORDINATERESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + x1 := command[2] y1 := command[3] x2 := command[4] @@ -1692,20 +1645,19 @@ AHKPixelSearch(ByRef command) { return FormatNoValueResponse() } else if (ErrorLevel = 0) { payload := Format("({}, {})", resultx, resulty) - return FormatResponse(COORDINATERESPONSEMESSAGE, payload) + return FormatResponse("ahk.message.CoordinateResponseMessage", payload) } else if (ErrorLevel = 2) { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem conducting the pixel search (ErrorLevel 2)") + return FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem conducting the pixel search (ErrorLevel 2)") } else { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Unexpected error. This is probably a bug. Please report this at https://github.com/spyoungtech/ahk/issues") + 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(ByRef command) { {% block AHKMouseGetPos %} - global COORDINATERESPONSEMESSAGE + coord_mode := command[2] current_coord_mode := Format("{}", A_CoordModeMouse) if (coord_mode != "") { @@ -1714,7 +1666,7 @@ AHKMouseGetPos(ByRef command) { MouseGetPos, xpos, ypos payload := Format("({}, {})", xpos, ypos) - resp := FormatResponse(COORDINATERESPONSEMESSAGE, payload) + resp := FormatResponse("ahk.message.CoordinateResponseMessage", payload) if (coord_mode != "") { CoordMode, Mouse, %current_coord_mode% @@ -1726,10 +1678,6 @@ AHKMouseGetPos(ByRef command) { AHKKeyState(ByRef command) { {% block AHKKeyState %} - global INTEGERRESPONSEMESSAGE - global FLOATRESPONSEMESSAGE - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE keyname := command[2] mode := command[3] @@ -1744,15 +1692,15 @@ AHKKeyState(ByRef command) { } if state is integer - return FormatResponse(INTEGERRESPONSEMESSAGE, state) + return FormatResponse("ahk.message.IntegerResponseMessage", state) if state is float - return FormatResponse(FLOATRESPONSEMESSAGE, state) + return FormatResponse("ahk.message.FloatResponseMessage", state) if state is alnum - return FormatResponse(STRINGRESPONSEMESSAGE, state) + return FormatResponse("ahk.message.StringResponseMessage", state) - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, state) + return FormatResponse("ahk.message.ExceptionResponseMessage", state) {% endblock AHKKeyState %} } @@ -1772,7 +1720,6 @@ AHKMouseMove(ByRef command) { {% endblock AHKMouseMove %} } - AHKClick(ByRef command) { {% block AHKClick %} x := command[2] @@ -1801,26 +1748,25 @@ AHKClick(ByRef command) { AHKGetCoordMode(ByRef command) { {% block AHKGetCoordMode %} - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + target := command[2] if (target = "ToolTip") { - return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeToolTip) + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeToolTip) } if (target = "Pixel") { - return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModePixel) + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModePixel) } if (target = "Mouse") { - return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeMouse) + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMouse) } if (target = "Caret") { - return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeCaret) + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeCaret) } if (target = "Menu") { - return FormatResponse(STRINGRESPONSEMESSAGE, A_CoordModeMenu) + return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeMenu) } - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Invalid coord mode") + return FormatResponse("ahk.message.ExceptionResponseMessage", "Invalid coord mode") {% endblock AHKGetCoordMode %} } @@ -1864,35 +1810,32 @@ AHKMouseClickDrag(ByRef command) { AHKRegRead(ByRef command) { {% block RegRead %} - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + key_name := command[2] value_name := command[3] RegRead, output, %key_name%, %value_name% if (ErrorLevel = 1) { - resp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + resp := FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) } else { - resp := FormatResponse(STRINGRESPONSEMESSAGE, Format("{}", output)) + resp := FormatResponse("ahk.message.StringResponseMessage", Format("{}", output)) } return resp {% endblock RegRead %} } - - AHKRegWrite(ByRef command) { {% block RegWrite %} - global EXCEPTIONRESPONSEMESSAGE + value_type := command[2] key_name := command[3] value_name := command[4] value := command[5] RegWrite, %value_type%, %key_name%, %value_name%, %value% if (ErrorLevel = 1) { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) } return FormatNoValueResponse() @@ -1901,12 +1844,12 @@ AHKRegWrite(ByRef command) { AHKRegDelete(ByRef command) { {% block RegDelete %} - global EXCEPTIONRESPONSEMESSAGE + key_name := command[2] value_name := command[3] RegDelete, %key_name%, %value_name% if (ErrorLevel = 1) { - return FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("registry error: {}", A_LastError)) + return FormatResponse("ahk.message.ExceptionResponseMessage", Format("registry error: {}", A_LastError)) } return FormatNoValueResponse() @@ -1915,7 +1858,7 @@ AHKRegDelete(ByRef command) { AHKKeyWait(ByRef command) { {% block AHKKeyWait %} - global INTEGERRESPONSEMESSAGE + keyname := command[2] if (command.Length() = 2) { KeyWait,% keyname @@ -1923,7 +1866,7 @@ AHKKeyWait(ByRef command) { options := command[3] KeyWait,% keyname,% options } - return FormatResponse(INTEGERRESPONSEMESSAGE, ErrorLevel) + return FormatResponse("ahk.message.IntegerResponseMessage", ErrorLevel) {% endblock AHKKeyWait %} } @@ -1933,8 +1876,6 @@ SetKeyDelay(ByRef command) { {% endblock SetKeyDelay %} } - - AHKSend(ByRef command) { {% block AHKSend %} str := command[2] @@ -1998,7 +1939,6 @@ AHKSendInput(ByRef command) { {% endblock AHKSendInput %} } - AHKSendEvent(ByRef command) { {% block AHKSendEvent %} str := command[2] @@ -2064,13 +2004,9 @@ HideTrayTip(ByRef command) { {% endblock HideTrayTip %} } - - - AHKWinGetClass(ByRef command) { {% block AHKWinGetClass %} - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + title := command[2] text := command[3] extitle := command[4] @@ -2096,9 +2032,9 @@ AHKWinGetClass(ByRef command) { WinGetClass, output,%title%,%text%,%extitle%,%extext% if (ErrorLevel = 1) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was an error getting window class") + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was an error getting window class") } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, output) + response := FormatResponse("ahk.message.StringResponseMessage", output) } DetectHiddenWindows, %current_detect_hw% @@ -2143,12 +2079,8 @@ AHKWinActivate(ByRef command) { {% endblock AHKWinActivate %} } - - - AHKWindowList(ByRef command) { {% block AHKWindowList %} - global WINDOWLISTRESPONSEMESSAGE current_detect_hw := Format("{}", A_DetectHiddenWindows) @@ -2179,7 +2111,7 @@ AHKWindowList(ByRef command) { id := windows%A_Index% r .= id . "`," } - resp := FormatResponse(WINDOWLISTRESPONSEMESSAGE, r) + resp := FormatResponse("ahk.message.WindowListResponseMessage", r) DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% SetTitleMatchMode, %current_match_speed% @@ -2187,11 +2119,9 @@ AHKWindowList(ByRef command) { {% endblock AHKWindowList %} } - - AHKControlClick(ByRef command) { {% block AHKControlClick %} - global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] title := command[3] text := command[4] @@ -2221,7 +2151,7 @@ AHKControlClick(ByRef command) { ControlClick, %ctrl%, %title%, %text%, %button%, %click_count%, %options%, %exclude_title%, %exclude_text% if (ErrorLevel != 0) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "Failed to click control") + response := FormatResponse("ahk.message.ExceptionResponseMessage", "Failed to click control") } else { response := FormatNoValueResponse() } @@ -2236,8 +2166,7 @@ AHKControlClick(ByRef command) { AHKControlGetText(ByRef command) { {% block AHKControlGetText %} - global STRINGRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] title := command[3] text := command[4] @@ -2264,9 +2193,9 @@ AHKControlGetText(ByRef command) { ControlGetText, result, %ctrl%, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the text") + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the text") } else { - response := FormatResponse(STRINGRESPONSEMESSAGE, result) + response := FormatResponse("ahk.message.StringResponseMessage", result) } DetectHiddenWindows, %current_detect_hw% SetTitleMatchMode, %current_match_mode% @@ -2276,11 +2205,9 @@ AHKControlGetText(ByRef command) { {% endblock AHKControlGetText %} } - AHKControlGetPos(ByRef command) { {% block AHKControlGetPos %} - global POSITIONRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE + ctrl := command[2] title := command[3] text := command[4] @@ -2306,10 +2233,10 @@ AHKControlGetPos(ByRef command) { ControlGetPos, x, y, w, h, %ctrl%, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the text") + 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(PositionResponseMessage, result) + response := FormatResponse("ahk.message.PositionResponseMessage", result) } DetectHiddenWindows, %current_detect_hw% @@ -2318,7 +2245,6 @@ AHKControlGetPos(ByRef command) { return response - {% endblock AHKControlGetPos %} } @@ -2355,39 +2281,34 @@ AHKControlSend(ByRef command) { {% endblock AHKControlSend %} } - - - AHKWinFromMouse(ByRef command) { {% block AHKWinFromMouse %} - global WINDOWRESPONSEMESSAGE + MouseGetPos,,, MouseWin if (MouseWin = "") { return FormatNoValueResponse() } - return FormatResponse(WINDOWRESPONSEMESSAGE, MouseWin) + return FormatResponse("ahk.message.WindowResponseMessage", MouseWin) {% endblock AHKWinFromMouse %} } - AHKWinIsAlwaysOnTop(ByRef command) { {% block AHKWinIsAlwaysOnTop %} - global BOOLEANRESPONSEMESSAGE + title := command[2] WinGet, ExStyle, ExStyle, %title% if (ExStyle = "") return FormatNoValueResponse() if (ExStyle & 0x8) ; 0x8 is WS_EX_TOPMOST. - return FormatResponse(BOOLEANRESPONSEMESSAGE, 1) + return FormatResponse("ahk.message.BooleanResponseMessage", 1) else - return FormatResponse(BOOLEANRESPONSEMESSAGE, 0) + return FormatResponse("ahk.message.BooleanResponseMessage", 0) {% endblock AHKWinIsAlwaysOnTop %} } - AHKWinMove(ByRef command) { {% block AHKWinMove %} title := command[2] @@ -2429,8 +2350,6 @@ AHKWinMove(ByRef command) { AHKWinGetPos(ByRef command) { {% block AHKWinGetPos %} - global POSITIONRESPONSEMESSAGE - global EXCEPTIONRESPONSEMESSAGE title := command[2] text := command[3] @@ -2457,10 +2376,10 @@ AHKWinGetPos(ByRef command) { WinGetPos, x, y, w, h, %title%, %text%, %extitle%, %extext% if (ErrorLevel = 1) { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, "There was a problem getting the position") + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the position") } else { result := Format("({1:i}, {2:i}, {3:i}, {4:i})", x, y, w, h) - response := FormatResponse(PositionResponseMessage, result) + response := FormatResponse("ahk.message.PositionResponseMessage", result) } DetectHiddenWindows, %current_detect_hw% @@ -2471,23 +2390,21 @@ AHKWinGetPos(ByRef command) { {% endblock AHKWinGetPos %} } - AHKGetVolume(ByRef command) { {% block AHKGetVolume %} - global EXCEPTIONRESPONSEMESSAGE - global FLOATRESPONSEMESSAGE + device_number := command[2] try { SoundGetWaveVolume, retval, %device_number% } catch e { - response := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {} ({})", device_number, e.message)) + 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(EXCEPTIONRESPONSEMESSAGE, Format("There was a problem getting the volume with device of index {}", device_number)) + response := FormatResponse("ahk.message.ExceptionResponseMessage", Format("There was a problem getting the volume with device of index {}", device_number)) } else { - response := FormatResponse(FLOATRESPONSEMESSAGE, Format("{}", retval)) + response := FormatResponse("ahk.message.FloatResponseMessage", Format("{}", retval)) } return response {% endblock AHKGetVolume %} @@ -2504,14 +2421,14 @@ AHKSoundBeep(ByRef command) { AHKSoundGet(ByRef command) { {% block AHKSoundGet %} - global STRINGRESPONSEMESSAGE + device_number := command[2] component_type := command[3] control_type := command[4] SoundGet, retval, %component_type%, %control_type%, %device_number% ; TODO interpret return type - return FormatResponse(STRINGRESPONSEMESSAGE, Format("{}", retval)) + return FormatResponse("ahk.message.StringResponseMessage", Format("{}", retval)) {% endblock AHKSoundGet %} } @@ -2552,8 +2469,8 @@ CountNewlines(ByRef s) { AHKEcho(ByRef command) { {% block AHKEcho %} - global STRINGRESPONSEMESSAGE - return FormatResponse(STRINGRESPONSEMESSAGE, command) + arg := command[2] + return FormatResponse("ahk.message.StringResponseMessage", arg) {% endblock AHKEcho %} } @@ -2582,8 +2499,8 @@ AHKShowToolTip(ByRef command) { AHKGetClipboard(ByRef command) { {% block AHKGetClipboard %} - global STRINGRESPONSEMESSAGE - return FormatResponse(STRINGRESPONSEMESSAGE, Clipboard) + + return FormatResponse("ahk.message.StringResponseMessage", Clipboard) {% endblock AHKGetClipboard %} } @@ -2612,15 +2529,14 @@ AHKSetClipboardAll(ByRef command) { } AHKClipWait(ByRef command) { - global TIMEOUTRESPONSEMESSAGE + timeout := command[2] wait_for_any_data := command[3] - ClipWait, %timeout%, %wait_for_any_data% if (ErrorLevel = 1) { - return FormatResponse(TIMEOUTRESPONSEMESSAGE, "timed out waiting for clipboard data") + return FormatResponse("ahk.message.TimeoutResponseMessage", "timed out waiting for clipboard data") } return FormatNoValueResponse() } @@ -2651,47 +2567,45 @@ AHKMenuTrayIcon(ByRef command) { } AHKGuiNew(ByRef command) { - global STRINGRESPONSEMESSAGE + options := command[2] title := command[3] Gui, New, %options%, %title% - return FormatResponse(STRINGRESPONSEMESSAGE, hwnd) + return FormatResponse("ahk.message.StringResponseMessage", hwnd) } AHKMsgBox(ByRef command) { - global TIMEOUTRESPONSEMESSAGE - global STRINGRESPONSEMESSAGE + options := command[2] title := command[3] text := command[4] timeout := command[5] MsgBox,% options, %title%, %text%, %timeout% IfMsgBox, Yes - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Yes") + ret := FormatResponse("ahk.message.StringResponseMessage", "Yes") IfMsgBox, No - ret := FormatResponse(STRINGRESPONSEMESSAGE, "No") + ret := FormatResponse("ahk.message.StringResponseMessage", "No") IfMsgBox, OK - ret := FormatResponse(STRINGRESPONSEMESSAGE, "OK") + ret := FormatResponse("ahk.message.StringResponseMessage", "OK") IfMsgBox, Cancel - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Cancel") + ret := FormatResponse("ahk.message.StringResponseMessage", "Cancel") IfMsgBox, Abort - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Abort") + ret := FormatResponse("ahk.message.StringResponseMessage", "Abort") IfMsgBox, Ignore - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Ignore") + ret := FormatResponse("ahk.message.StringResponseMessage", "Ignore") IfMsgBox, Retry - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Retry") + ret := FormatResponse("ahk.message.StringResponseMessage", "Retry") IfMsgBox, Continue - ret := FormatResponse(STRINGRESPONSEMESSAGE, "Continue") + ret := FormatResponse("ahk.message.StringResponseMessage", "Continue") IfMsgBox, TryAgain - ret := FormatResponse(STRINGRESPONSEMESSAGE, "TryAgain") + ret := FormatResponse("ahk.message.StringResponseMessage", "TryAgain") IfMsgBox, Timeout - ret := FormatResponse(TIMEOUTRESPONSEMESSAGE, "MsgBox timed out") + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "MsgBox timed out") return ret } AHKInputBox(ByRef command) { - global STRINGRESPONSEMESSAGE - global TIMEOUTRESPONSEMESSAGE + title := command[2] prompt := command[3] hide := command[4] @@ -2705,17 +2619,17 @@ AHKInputBox(ByRef command) { InputBox, output, %title%, %prompt%, %hide%, %width%, %height%, %x%, %y%, %locale%, %timeout%, %default% if (ErrorLevel = 2) { - ret := FormatResponse(TIMEOUTRESPONSEMESSAGE, "Input box timed out") + ret := FormatResponse("ahk.message.TimeoutResponseMessage", "Input box timed out") } else if (ErrorLevel = 1) { ret := FormatNoValueResponse() } else { - ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + ret := FormatResponse("ahk.message.StringResponseMessage", output) } return ret } AHKFileSelectFile(byRef command) { - global STRINGRESPONSEMESSAGE + options := command[2] root := command[3] title := command[4] @@ -2724,13 +2638,13 @@ AHKFileSelectFile(byRef command) { if (ErrorLevel = 1) { ret := FormatNoValueResponse() } else { - ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + ret := FormatResponse("ahk.message.StringResponseMessage", output) } return ret } AHKFileSelectFolder(byRef command) { - global STRINGRESPONSEMESSAGE + starting_folder := command[2] options := command[3] prompt := command[4] @@ -2740,7 +2654,7 @@ AHKFileSelectFolder(byRef command) { if (ErrorLevel = 1) { ret := FormatNoValueResponse() } else { - ret := FormatResponse(STRINGRESPONSEMESSAGE, output) + ret := FormatResponse("ahk.message.StringResponseMessage", output) } return ret } @@ -2768,7 +2682,6 @@ b64decode(ByRef pszString) { 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) { @@ -2796,7 +2709,6 @@ b64encode(ByRef data) { ; [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 "" @@ -2810,7 +2722,6 @@ b64encode(ByRef data) { throw Exception(msg, -1) } - VarSetCapacity(ret, buff_size * (A_IsUnicode ? 2 : 1)) ; Now we do the conversion to base64 and rteturn the string @@ -2823,7 +2734,6 @@ b64encode(ByRef data) { return ret } - ; End of included content CommandArrayFromQuery(ByRef text) { @@ -2865,14 +2775,14 @@ Loop { } catch e { {% block function_error_handle %} message := Format("Error occurred in {}. The error message was: {}", e.What, e.message) - pyresp := FormatResponse(EXCEPTIONRESPONSEMESSAGE, message) + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) {% endblock function_error_handle %} } {% block send_response %} if (pyresp) { FileAppend, %pyresp%, *, UTF-8 } else { - msg := FormatResponse(EXCEPTIONRESPONSEMESSAGE, Format("Unknown Error when calling {}", func)) + msg := FormatResponse("ahk.message.ExceptionResponseMessage", Format("Unknown Error when calling {}", func)) FileAppend, %msg%, *, UTF-8 } {% endblock send_response %} From ae954efc4bc677f5753160042078a923b60b4f05 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 24 Aug 2023 19:01:20 -0700 Subject: [PATCH 425/588] use Critical on daemon thread --- ahk/_constants.py | 2 ++ ahk/templates/daemon.ahk | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ahk/_constants.py b/ahk/_constants.py index 39b84fcf..baea3122 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -27,6 +27,8 @@ {% 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 %} diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 3daf2c73..32f136ca 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -24,6 +24,8 @@ {% 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 %} From 52c18774c57fb902a90f158ce3798679bab9268b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 24 Aug 2023 21:08:49 -0700 Subject: [PATCH 426/588] extension documentation --- ahk/_async/engine.py | 6 + ahk/_sync/engine.py | 6 + docs/api/index.rst | 1 + docs/api/message.rst | 7 ++ docs/extending.rst | 256 +++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 6 files changed, 277 insertions(+) create mode 100644 docs/api/message.rst create mode 100644 docs/extending.rst diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index c7937d35..7d2e51cd 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -204,6 +204,12 @@ def add_hotkey( warnings.warn(warning.message, warning.category, stacklevel=2) return None + async def function_call(self, function_name: str, args: list[str], blocking: bool = True) -> Any: + """ + Call an AHK function defined in the daemon script. This method is intended for use by extension authors. + """ + return await self._transport.function_call(function_name, args, blocking=blocking, engine=self) # type: ignore[call-overload] + def add_hotstring( self, trigger: str, diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 8f93d069..2d7cfd65 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -198,6 +198,12 @@ def add_hotkey( warnings.warn(warning.message, warning.category, stacklevel=2) return None + def function_call(self, function_name: str, args: list[str], blocking: bool = True) -> Any: + """ + Call an AHK function defined in the daemon script. This method is intended for use by extension authors. + """ + return self._transport.function_call(function_name, args, blocking=blocking, engine=self) # type: ignore[call-overload] + def add_hotstring( self, trigger: str, diff --git a/docs/api/index.rst b/docs/api/index.rst index 5563455f..4a2b4612 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -14,3 +14,4 @@ about the programming interface. This is largely auto-generated documentation. 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/extending.rst b/docs/extending.rst new file mode 100644 index 00000000..26579afb --- /dev/null +++ b/docs/extending.rst @@ -0,0 +1,256 @@ +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 (v1) accept exactly one argument (an array of zero or more strings) and must return responses 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 (v1) that conforms to the required spec (accepts one argument of an array of strings 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 (v1) function that accepts a single argument, which +is an array containing the arguments of the function passed by Python. These start at index 2. + +Ultimately, the function will perform some operation utilizing these inputs and will return a formatted response +(using the ``FormatResponse`` function which is already defined for you. It accepts two arguments: the messaage type name +and the raw payload. + +.. code-block:: + + + SimpleMath(ByRef command) { + + ; `command` is an array with passed arguments, starting at index 2 + lhs := command[2] + rhs := command[3] + operator := command[4] + + 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) + } + + +Note that the ``FormatResponse`` function is already implemented for you! + + +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_meth_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: % + + + + +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" + + +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 ``DateTimeResponseMessage.fqn()``) + + + +Packaging +^^^^^^^^^ + +Coming soon. + +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 autoexecution + + +Extending with jinja +^^^^^^^^^^^^^^^^^^^^ + +Coming soon. diff --git a/docs/index.rst b/docs/index.rst index 736ce73f..90e24c2a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,6 +17,7 @@ ahk Python wrapper documentation quickstart README api/index + extending From 8327cb0808d110b5c87a914518f9cf5a03b6f6ca Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 24 Aug 2023 23:48:52 -0700 Subject: [PATCH 427/588] notes --- docs/extending.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/extending.rst b/docs/extending.rst index 26579afb..ed3badbb 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -236,6 +236,13 @@ For example, suppose you want your method to return a datetime object, you might 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 ``DateTimeResponseMessage.fqn()``) +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 autoexecution +- Extensions must be imported *before* instantiating the ``AHK`` instance +- Although extensions can be declared explicitly, using ``extensions='auto'`` is the recommended method for enabling extensions Packaging @@ -243,12 +250,6 @@ Packaging Coming soon. -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 autoexecution - Extending with jinja ^^^^^^^^^^^^^^^^^^^^ From be14d1c6620d46022c7960417ba4f9f6dbef38ad Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 25 Aug 2023 00:26:18 -0700 Subject: [PATCH 428/588] update tests --- tests/_async/test_extensions.py | 5 ++--- tests/_sync/test_extensions.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/_async/test_extensions.py b/tests/_async/test_extensions.py index 95651dcf..1842a2ea 100644 --- a/tests/_async/test_extensions.py +++ b/tests/_async/test_extensions.py @@ -13,9 +13,8 @@ ext_text = '''\ AHKDoSomething(ByRef command) { - global STRINGRESPONSEMESSAGE arg := command[2] - return FormatResponse(STRINGRESPONSEMESSAGE, Format("test{}", arg)) + return FormatResponse("ahk.message.StringResponseMessage", Format("test{}", arg)) } ''' @@ -24,7 +23,7 @@ @async_extension.register async def do_something(ahk, arg: str) -> str: - res = await ahk._transport.function_call('AHKDoSomething', [arg]) + res = await ahk.function_call('AHKDoSomething', [arg]) return res diff --git a/tests/_sync/test_extensions.py b/tests/_sync/test_extensions.py index c78d392d..8eadcb2d 100644 --- a/tests/_sync/test_extensions.py +++ b/tests/_sync/test_extensions.py @@ -12,9 +12,8 @@ ext_text = '''\ AHKDoSomething(ByRef command) { - global STRINGRESPONSEMESSAGE arg := command[2] - return FormatResponse(STRINGRESPONSEMESSAGE, Format("test{}", arg)) + return FormatResponse("ahk.message.StringResponseMessage", Format("test{}", arg)) } ''' @@ -23,7 +22,7 @@ @async_extension.register def do_something(ahk, arg: str) -> str: - res = ahk._transport.function_call('AHKDoSomething', [arg]) + res = ahk.function_call('AHKDoSomething', [arg]) return res From e3b66b147de5f4d73a7bf6b0642a71558358487c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 25 Aug 2023 01:31:23 -0700 Subject: [PATCH 429/588] prepare 1.4.0rc1 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f1cfc96d..ad170b66 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.3.0 +version = 1.4.0rc1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 92c0c7758dea690761c14c1c289eb6a3e70df9e2 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 25 Aug 2023 01:49:46 -0700 Subject: [PATCH 430/588] add stacklevel to warnings --- ahk/extensions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ahk/extensions.py b/ahk/extensions.py index af3deec3..00f8ddfe 100644 --- a/ahk/extensions.py +++ b/ahk/extensions.py @@ -37,7 +37,8 @@ def register(self, ext: Extension, f: Callable[P, T]) -> Callable[P, T]: warnings.warn( f'Method of name {f.__name__!r} has already been registered. ' f'Previously registered method {self.async_methods[f.__name__].method!r} ' - f'will be overridden by {f!r}' + f'will be overridden by {f!r}', + stacklevel=2, ) self.async_methods[f.__name__] = _ExtensionEntry(extension=ext, method=f) else: @@ -45,7 +46,8 @@ def register(self, ext: Extension, f: Callable[P, T]) -> Callable[P, T]: warnings.warn( f'Method of name {f.__name__!r} has already been registered. ' f'Previously registered method {self.sync_methods[f.__name__].method!r} ' - f'will be overridden by {f!r}' + f'will be overridden by {f!r}', + stacklevel=2, ) self.sync_methods[f.__name__] = _ExtensionEntry(extension=ext, method=f) return f From 09bc6767f6db4768bb11b8d2cb0b4b0be1032dcf Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 30 Aug 2023 14:56:58 -0700 Subject: [PATCH 431/588] extension registry cleanup --- ahk/_async/engine.py | 41 ++++++------ ahk/_async/transport.py | 6 +- ahk/_constants.py | 10 --- ahk/_sync/engine.py | 40 ++++++------ ahk/_sync/transport.py | 6 +- ahk/extensions.py | 108 ++++++++++++++++++++++---------- ahk/templates/daemon.ahk | 10 --- tests/_async/test_extensions.py | 6 +- tests/_sync/test_extensions.py | 6 +- 9 files changed, 129 insertions(+), 104 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 7d2e51cd..d0b1bf0a 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -35,7 +35,12 @@ else: from typing import TypeAlias -from ..extensions import Extension, _extension_method_registry, _ExtensionMethodRegistry +from ..extensions import ( + Extension, + _ExtensionMethodRegistry, + _extension_registry, + _resolve_extensions, +) from ..keys import Key from .transport import AsyncDaemonProcessTransport from .transport import AsyncFutureResult @@ -142,38 +147,28 @@ def __init__( self._extension_registry: _ExtensionMethodRegistry self._extensions: list[Extension] if extensions == 'auto': - is_async = False - is_async = True # unasync: remove - if is_async: - methods = _extension_method_registry.async_methods - else: - methods = _extension_method_registry.sync_methods - extensions = list(set(entry.extension for name, entry in methods.items())) - - self._extension_registry = _extension_method_registry - self._extensions = extensions + self._extensions = list(_extension_registry) else: - self._extensions = extensions or [] - self._extension_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) - for ext in self._extensions: - self._extension_registry.merge(ext._extension_method_registry) - + self._extensions = _resolve_extensions(extensions) if extensions else [] + self._method_registry = _ExtensionMethodRegistry(sync_methods={}, async_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=extensions) + transport = TransportClass(executable_path=executable_path, directives=directives, extensions=self._extensions) self._transport: AsyncTransport = transport def __getattr__(self, name: str) -> Callable[..., Any]: is_async = False is_async = True # unasync: remove if is_async: - if name in self._extension_registry.async_methods: - method = self._extension_registry.async_methods[name].method + if name in self._method_registry.async_methods: + method = self._method_registry.async_methods[name] return partial(method, self) else: - if name in self._extension_registry.sync_methods: - method = self._extension_registry.sync_methods[name].method + 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}') @@ -204,10 +199,12 @@ def add_hotkey( warnings.warn(warning.message, warning.category, stacklevel=2) return None - async def function_call(self, function_name: str, args: list[str], blocking: bool = True) -> Any: + 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( diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 96277d3f..c6b9842e 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -38,7 +38,7 @@ import jinja2 -from ahk.extensions import Extension +from ahk.extensions import Extension, _resolve_includes from ahk._hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from ahk.message import RequestMessage from ahk.message import ResponseMessage @@ -689,6 +689,10 @@ def __init__( if template is None: template = self.__template self._template: jinja2.Template = template + directives = directives or [] + if extensions: + includes = _resolve_includes(extensions) + directives = includes + directives super().__init__(executable_path=executable_path, directives=directives) @property diff --git a/ahk/_constants.py b/ahk/_constants.py index baea3122..f7485eb0 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -6,16 +6,6 @@ #NoEnv #Persistent #SingleInstance Off -{% block extension_directives %} -; BEGIN extension includes -{% for ext in extensions %} -{% for inc in ext.includes %} -{{ inc }} - -{% endfor %} -{% endfor %} -; END extension includes -{% endblock extension_directives %} ; BEGIN user-defined directives {% block user_directives %} {% for directive in directives %} diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 2d7cfd65..22401065 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -35,7 +35,12 @@ else: from typing import TypeAlias -from ..extensions import Extension, _extension_method_registry, _ExtensionMethodRegistry +from ..extensions import ( + Extension, + _ExtensionMethodRegistry, + _extension_registry, + _resolve_extensions, +) from ..keys import Key from .transport import DaemonProcessTransport from .transport import FutureResult @@ -138,36 +143,27 @@ def __init__( self._extension_registry: _ExtensionMethodRegistry self._extensions: list[Extension] if extensions == 'auto': - is_async = False - if is_async: - methods = _extension_method_registry.async_methods - else: - methods = _extension_method_registry.sync_methods - extensions = list(set(entry.extension for name, entry in methods.items())) - - self._extension_registry = _extension_method_registry - self._extensions = extensions + self._extensions = list(_extension_registry) else: - self._extensions = extensions or [] - self._extension_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) - for ext in self._extensions: - self._extension_registry.merge(ext._extension_method_registry) - + self._extensions = _resolve_extensions(extensions) if extensions else [] + self._method_registry = _ExtensionMethodRegistry(sync_methods={}, async_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=extensions) + transport = TransportClass(executable_path=executable_path, directives=directives, extensions=self._extensions) self._transport: Transport = transport def __getattr__(self, name: str) -> Callable[..., Any]: is_async = False if is_async: - if name in self._extension_registry.async_methods: - method = self._extension_registry.async_methods[name].method + if name in self._method_registry.async_methods: + method = self._method_registry.async_methods[name] return partial(method, self) else: - if name in self._extension_registry.sync_methods: - method = self._extension_registry.sync_methods[name].method + 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}') @@ -198,10 +194,12 @@ def add_hotkey( warnings.warn(warning.message, warning.category, stacklevel=2) return None - def function_call(self, function_name: str, args: list[str], blocking: bool = True) -> Any: + 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( diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index e786a197..8408566e 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -38,7 +38,7 @@ import jinja2 -from ahk.extensions import Extension +from ahk.extensions import Extension, _resolve_includes from ahk._hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring from ahk.message import RequestMessage from ahk.message import ResponseMessage @@ -662,6 +662,10 @@ def __init__( if template is None: template = self.__template self._template: jinja2.Template = template + directives = directives or [] + if extensions: + includes = _resolve_includes(extensions) + directives = includes + directives super().__init__(executable_path=executable_path, directives=directives) @property diff --git a/ahk/extensions.py b/ahk/extensions.py index 00f8ddfe..ee71c9d6 100644 --- a/ahk/extensions.py +++ b/ahk/extensions.py @@ -1,8 +1,11 @@ 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 @@ -10,8 +13,10 @@ 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 @@ -26,67 +31,64 @@ class _ExtensionEntry: P = ParamSpec('P') +if typing.TYPE_CHECKING: + from ahk import AHK, AsyncAHK + + TAHK = TypeVar('TAHK', bound=typing.Union[AHK, AsyncAHK]) + + @dataclass class _ExtensionMethodRegistry: - sync_methods: dict[str, _ExtensionEntry] - async_methods: dict[str, _ExtensionEntry] + sync_methods: dict[str, Callable[..., Any]] + async_methods: dict[str, Callable[..., Any]] - def register(self, ext: Extension, f: Callable[P, T]) -> Callable[P, T]: + 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__].method!r} ' + f'Previously registered method {self.async_methods[f.__name__]!r} ' f'will be overridden by {f!r}', stacklevel=2, ) - self.async_methods[f.__name__] = _ExtensionEntry(extension=ext, method=f) + 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__].method!r} ' + f'Previously registered method {self.sync_methods[f.__name__]!r} ' f'will be overridden by {f!r}', stacklevel=2, ) - self.sync_methods[f.__name__] = _ExtensionEntry(extension=ext, method=f) + self.sync_methods[f.__name__] = f return f def merge(self, other: _ExtensionMethodRegistry) -> None: - for fname, entry in other.async_methods.items(): - async_method = entry.method - if async_method.__name__ in self.async_methods: - warnings.warn( - f'Method of name {async_method.__name__!r} has already been registered. ' - f'Previously registered method {self.async_methods[async_method.__name__].method!r} ' - f'will be overridden by {async_method!r}' - ) - self.async_methods[async_method.__name__] = entry - for fname, entry in other.sync_methods.items(): - method = entry.method - if method.__name__ in self.sync_methods: - warnings.warn( - f'Method of name {method.__name__!r} has already been registered. ' - f'Previously registered method {self.sync_methods[method.__name__].method!r} ' - f'will be overridden by {method!r}' - ) - self.sync_methods[method.__name__] = entry + for name, method in other.methods: + self.register(method) + + @property + def methods(self) -> list[tuple[str, Callable[..., Any]]]: + return list(itertools.chain(self.async_methods.items(), self.sync_methods.items())) -_extension_method_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) +_extension_registry: dict[Extension, _ExtensionMethodRegistry] = {} class Extension: def __init__( self, - includes: list[str] | None = None, script_text: str | None = None, - # template: str | Template | None = None + includes: list[str] | None = None, + dependencies: list[Extension] | None = None, ): self._text: str = script_text or '' - # self._template: str | Template | None = template self._includes: list[str] = includes or [] - self._extension_method_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) + self.dependencies: list[Extension] = dependencies or [] + self._extension_method_registry: _ExtensionMethodRegistry = _ExtensionMethodRegistry( + sync_methods={}, async_methods={} + ) + _extension_registry[self] = self._extension_method_registry @property def script_text(self) -> str: @@ -100,7 +102,47 @@ def script_text(self, new_script: str) -> None: def includes(self) -> list[Include]: return [Include(inc) for inc in self._includes] - def register(self, f: Callable[P, T]) -> Callable[P, T]: - self._extension_method_registry.register(self, f) - _extension_method_registry.register(self, f) + def register(self, f: Callable[Concatenate[TAHK, P], T]) -> Callable[Concatenate[TAHK, P], T]: + self._extension_method_registry.register(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/templates/daemon.ahk b/ahk/templates/daemon.ahk index 32f136ca..112a3737 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -3,16 +3,6 @@ #NoEnv #Persistent #SingleInstance Off -{% block extension_directives %} -; BEGIN extension includes -{% for ext in extensions %} -{% for inc in ext.includes %} -{{ inc }} - -{% endfor %} -{% endfor %} -; END extension includes -{% endblock extension_directives %} ; BEGIN user-defined directives {% block user_directives %} {% for directive in directives %} diff --git a/tests/_async/test_extensions.py b/tests/_async/test_extensions.py index 1842a2ea..e76ebfee 100644 --- a/tests/_async/test_extensions.py +++ b/tests/_async/test_extensions.py @@ -35,7 +35,7 @@ async def asyncTearDown(self) -> None: self.ahk._transport._proc.kill() time.sleep(0.2) - async def test_ext(self): + async def test_ext_explicit(self): res = await self.ahk.do_something('foo') assert res == 'testfoo' @@ -48,7 +48,7 @@ async def asyncTearDown(self) -> None: self.ahk._transport._proc.kill() time.sleep(0.2) - async def test_ext(self): + async def test_ext_auto(self): res = await self.ahk.do_something('foo') assert res == 'testfoo' @@ -62,5 +62,5 @@ async def asyncTearDown(self) -> None: self.ahk._transport._proc.kill() time.sleep(0.2) - async def test_ext(self): + async def test_ext_no_ext(self): assert not hasattr(self.ahk, 'do_something') diff --git a/tests/_sync/test_extensions.py b/tests/_sync/test_extensions.py index 8eadcb2d..b7c8d14f 100644 --- a/tests/_sync/test_extensions.py +++ b/tests/_sync/test_extensions.py @@ -34,7 +34,7 @@ def tearDown(self) -> None: self.ahk._transport._proc.kill() time.sleep(0.2) - def test_ext(self): + def test_ext_explicit(self): res = self.ahk.do_something('foo') assert res == 'testfoo' @@ -47,7 +47,7 @@ def tearDown(self) -> None: self.ahk._transport._proc.kill() time.sleep(0.2) - def test_ext(self): + def test_ext_auto(self): res = self.ahk.do_something('foo') assert res == 'testfoo' @@ -61,5 +61,5 @@ def tearDown(self) -> None: self.ahk._transport._proc.kill() time.sleep(0.2) - def test_ext(self): + def test_ext_no_ext(self): assert not hasattr(self.ahk, 'do_something') From 3eba8b823d086b4df27b386390f01b15ab7de02e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 30 Aug 2023 16:12:50 -0700 Subject: [PATCH 432/588] better error messages --- ahk/_async/transport.py | 25 +++++++++++++++++++++---- ahk/_sync/transport.py | 23 ++++++++++++++++++++--- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index c6b9842e..8aab54d9 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -260,6 +260,13 @@ async def readline(self) -> bytes: 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() @@ -280,12 +287,12 @@ def communicate(self, input_bytes: Optional[bytes] = None, timeout: Optional[int 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.PIPE + *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.PIPE, stdout=subprocess.PIPE) + return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) class AhkExecutableNotFoundError(EnvironmentError): @@ -775,8 +782,13 @@ async def _send_nonblocking( 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.' + '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() @@ -827,8 +839,13 @@ async def send( 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.' + '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() diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 8408566e..a15b81c9 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -247,6 +247,13 @@ def readline(self) -> bytes: 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() @@ -266,7 +273,7 @@ def communicate(self, input_bytes: Optional[bytes] = None, timeout: Optional[int def sync_create_process(runargs: List[str]) -> subprocess.Popen[bytes]: - return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) class AhkExecutableNotFoundError(EnvironmentError): @@ -747,8 +754,13 @@ def _send_nonblocking( 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.' + '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() @@ -791,8 +803,13 @@ def send( 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.' + '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() From aa6afecc726b65e9c821c38a4a07c5c9f6392257 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 30 Aug 2023 16:13:07 -0700 Subject: [PATCH 433/588] fix extension tests --- tests/_async/test_extensions.py | 15 ++++++++++----- tests/_sync/test_extensions.py | 14 +++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/tests/_async/test_extensions.py b/tests/_async/test_extensions.py index e76ebfee..1a2b9d90 100644 --- a/tests/_async/test_extensions.py +++ b/tests/_async/test_extensions.py @@ -1,4 +1,6 @@ import asyncio +import random +import string import time import unittest @@ -11,11 +13,14 @@ sleep = time.sleep -ext_text = '''\ -AHKDoSomething(ByRef command) { +function_name = 'AHKDoSomething' +function_name = 'AAHKDoSomething' # unasync: remove + +ext_text = f'''\ +{function_name}(ByRef command) {{ arg := command[2] - return FormatResponse("ahk.message.StringResponseMessage", Format("test{}", arg)) -} + return FormatResponse("ahk.message.StringResponseMessage", Format("test{{}}", arg)) +}} ''' async_extension = Extension(script_text=ext_text) @@ -23,7 +28,7 @@ @async_extension.register async def do_something(ahk, arg: str) -> str: - res = await ahk.function_call('AHKDoSomething', [arg]) + res = await ahk.function_call(function_name, [arg]) return res diff --git a/tests/_sync/test_extensions.py b/tests/_sync/test_extensions.py index b7c8d14f..6c011ad7 100644 --- a/tests/_sync/test_extensions.py +++ b/tests/_sync/test_extensions.py @@ -1,4 +1,6 @@ import asyncio +import random +import string import time import unittest @@ -10,11 +12,13 @@ sleep = time.sleep -ext_text = '''\ -AHKDoSomething(ByRef command) { +function_name = 'AHKDoSomething' + +ext_text = f'''\ +{function_name}(ByRef command) {{ arg := command[2] - return FormatResponse("ahk.message.StringResponseMessage", Format("test{}", arg)) -} + return FormatResponse("ahk.message.StringResponseMessage", Format("test{{}}", arg)) +}} ''' async_extension = Extension(script_text=ext_text) @@ -22,7 +26,7 @@ @async_extension.register def do_something(ahk, arg: str) -> str: - res = ahk.function_call('AHKDoSomething', [arg]) + res = ahk.function_call(function_name, [arg]) return res From 0391679e6d75f6de765a9a6b29aaa929aadeb819 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 06:25:05 +0000 Subject: [PATCH 434/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 361cd16e..01b9d4f2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '23.7.0' + rev: '23.9.1' hooks: - id: black args: From b5325943d07a30220ca1e75661b83260111fb56c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Sep 2023 14:20:16 -0700 Subject: [PATCH 435/588] [WIP] initial v2 work --- ahk/_async/engine.py | 23 +- ahk/_async/transport.py | 33 +- ahk/_sync/engine.py | 23 +- ahk/_sync/transport.py | 33 +- ahk/templates/daemon-v2.ahk | 2840 +++++++++++++++++++++++++++++++ tests/_async/test_clipboard.py | 5 + tests/_async/test_extensions.py | 18 +- tests/_async/test_gui.py | 5 + tests/_async/test_hotkeys.py | 5 + tests/_async/test_keys.py | 10 + tests/_async/test_mouse.py | 5 + tests/_async/test_registry.py | 5 + tests/_async/test_screen.py | 10 + tests/_async/test_scripts.py | 5 + tests/_async/test_window.py | 9 + tests/_sync/test_clipboard.py | 5 + tests/_sync/test_extensions.py | 18 +- tests/_sync/test_gui.py | 5 + tests/_sync/test_hotkeys.py | 5 + tests/_sync/test_keys.py | 10 + tests/_sync/test_mouse.py | 5 + tests/_sync/test_registry.py | 5 + tests/_sync/test_screen.py | 10 + tests/_sync/test_scripts.py | 5 + tests/_sync/test_window.py | 9 + 25 files changed, 3082 insertions(+), 24 deletions(-) create mode 100644 ahk/templates/daemon-v2.ahk diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index d0b1bf0a..6f349278 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -143,6 +143,7 @@ def __init__( directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', extensions: list[Extension] | None | Literal['auto'] = None, + version: Optional[Literal['v1', 'v2']] = None, ): self._extension_registry: _ExtensionMethodRegistry self._extensions: list[Extension] @@ -156,7 +157,9 @@ def __init__( if TransportClass is None: TransportClass = AsyncDaemonProcessTransport assert TransportClass is not None - transport = TransportClass(executable_path=executable_path, directives=directives, extensions=self._extensions) + transport = TransportClass( + executable_path=executable_path, directives=directives, extensions=self._extensions, version=version + ) self._transport: AsyncTransport = transport def __getattr__(self, name: str) -> Callable[..., Any]: @@ -753,6 +756,8 @@ async def mouse_move( args = [str(x), str(y), str(speed)] if relative: args.append('R') + else: + args.append('') resp = await self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp @@ -1314,7 +1319,7 @@ async def show_traytip( self, title: str, text: str, - second: float = 1.0, + second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, @@ -1324,6 +1329,14 @@ async def show_traytip( """ Analog for `TrayTip `_ """ + if second is None: + second = 1.0 + else: + if self._transport._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) @@ -3410,7 +3423,7 @@ async def set_clipboard_all( with tempfile.NamedTemporaryFile(prefix='ahk-python', suffix='.clip', mode='wb', delete=False) as f: f.write(contents) - args = [f'*c {f.name}'] + 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 @@ -3510,6 +3523,8 @@ async def reg_write( 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 @@ -3531,6 +3546,8 @@ async def reg_read( 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 diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 8aab54d9..3e1c6ec6 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -299,14 +299,17 @@ class AhkExecutableNotFoundError(EnvironmentError): pass -def _resolve_executable_path(executable_path: str = '') -> str: +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') - or which('AutoHotkeyU32.exe') - or which('AutoHotkeyA32.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 '' ) @@ -347,11 +350,13 @@ def __init__( /, executable_path: str = '', directives: Optional[list[Union[Directive, Type[Directive]]]] = None, + version: Optional[Literal['v1', 'v2']] = None, **kwargs: Any, ): - self._executable_path: str = _resolve_executable_path(executable_path=executable_path) + self._executable_path: str = _resolve_executable_path(executable_path=executable_path, version=version) self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path, directives=directives) self._directives: list[Union[Directive, Type[Directive]]] = directives or [] + self._version: Literal['v1', 'v2'] = version or 'v1' def on_clipboard_change( self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None @@ -665,6 +670,7 @@ def __init__( jinja_loader: Optional[jinja2.BaseLoader] = None, template: Optional[jinja2.Template] = None, extensions: list[Extension] | None = None, + version: Optional[Literal['v1', 'v2']] = None, ): self._extensions = extensions or [] self._proc: Optional[AsyncAHKProcess] @@ -674,6 +680,16 @@ def __init__( self._jinja_env: jinja2.Environment self._execution_lock = threading.Lock() self._a_execution_lock = asyncio.Lock() # unasync: remove + + 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 = '' # TODO: set v2 constant + else: + raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') + if jinja_loader is None: try: loader: jinja2.BaseLoader @@ -689,10 +705,10 @@ def __init__( else: self._jinja_env = jinja2.Environment(loader=jinja_loader, trim_blocks=True, autoescape=False) try: - self.__template = self._jinja_env.get_template('daemon.ahk') + 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(_DAEMON_SCRIPT_TEMPLATE) + self.__template = self._jinja_env.from_string(const_script) if template is None: template = self.__template self._template: jinja2.Template = template @@ -700,7 +716,7 @@ def __init__( if extensions: includes = _resolve_includes(extensions) directives = includes + directives - super().__init__(executable_path=executable_path, directives=directives) + super().__init__(executable_path=executable_path, directives=directives, version=version) @property def template(self) -> jinja2.Template: @@ -730,6 +746,7 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A message_types=message_types, message_registry=_message_registry, extensions=self._extensions, + ahk_version=self._version, **kwargs, ) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 22401065..4858e43a 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -139,6 +139,7 @@ def __init__( directives: Optional[list[Directive | Type[Directive]]] = None, executable_path: str = '', extensions: list[Extension] | None | Literal['auto'] = None, + version: Optional[Literal['v1', 'v2']] = None, ): self._extension_registry: _ExtensionMethodRegistry self._extensions: list[Extension] @@ -152,7 +153,9 @@ def __init__( if TransportClass is None: TransportClass = DaemonProcessTransport assert TransportClass is not None - transport = TransportClass(executable_path=executable_path, directives=directives, extensions=self._extensions) + transport = TransportClass( + executable_path=executable_path, directives=directives, extensions=self._extensions, version=version + ) self._transport: Transport = transport def __getattr__(self, name: str) -> Callable[..., Any]: @@ -744,6 +747,8 @@ def mouse_move( args = [str(x), str(y), str(speed)] if relative: args.append('R') + else: + args.append('') resp = self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp @@ -1302,7 +1307,7 @@ def show_traytip( self, title: str, text: str, - second: float = 1.0, + second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, @@ -1312,6 +1317,14 @@ def show_traytip( """ Analog for `TrayTip `_ """ + if second is None: + second = 1.0 + else: + if self._transport._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) @@ -3398,7 +3411,7 @@ def set_clipboard_all( with tempfile.NamedTemporaryFile(prefix='ahk-python', suffix='.clip', mode='wb', delete=False) as f: f.write(contents) - args = [f'*c {f.name}'] + 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 @@ -3498,6 +3511,8 @@ def reg_write( args.append('') if value is not None: args.append(value) + else: + args.append('') return self._transport.function_call('AHKRegWrite', args, blocking=blocking) # fmt: off @@ -3519,6 +3534,8 @@ def reg_read( 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 diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index a15b81c9..e94db02c 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -280,14 +280,17 @@ class AhkExecutableNotFoundError(EnvironmentError): pass -def _resolve_executable_path(executable_path: str = '') -> str: +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') - or which('AutoHotkeyU32.exe') - or which('AutoHotkeyA32.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 '' ) @@ -328,11 +331,13 @@ def __init__( /, executable_path: str = '', directives: Optional[list[Union[Directive, Type[Directive]]]] = None, + version: Optional[Literal['v1', 'v2']] = None, **kwargs: Any, ): - self._executable_path: str = _resolve_executable_path(executable_path=executable_path) + self._executable_path: str = _resolve_executable_path(executable_path=executable_path, version=version) self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path, directives=directives) self._directives: list[Union[Directive, Type[Directive]]] = directives or [] + self._version: Literal['v1', 'v2'] = version or 'v1' def on_clipboard_change( self, callback: Callable[[int], Any], ex_handler: Optional[Callable[[int, Exception], Any]] = None @@ -639,6 +644,7 @@ def __init__( jinja_loader: Optional[jinja2.BaseLoader] = None, template: Optional[jinja2.Template] = None, extensions: list[Extension] | None = None, + version: Optional[Literal['v1', 'v2']] = None, ): self._extensions = extensions or [] self._proc: Optional[SyncAHKProcess] @@ -647,6 +653,16 @@ def __init__( self.__template: jinja2.Template self._jinja_env: jinja2.Environment self._execution_lock = threading.Lock() + + 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 = '' # TODO: set v2 constant + else: + raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') + if jinja_loader is None: try: loader: jinja2.BaseLoader @@ -662,10 +678,10 @@ def __init__( else: self._jinja_env = jinja2.Environment(loader=jinja_loader, trim_blocks=True, autoescape=False) try: - self.__template = self._jinja_env.get_template('daemon.ahk') + 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(_DAEMON_SCRIPT_TEMPLATE) + self.__template = self._jinja_env.from_string(const_script) if template is None: template = self.__template self._template: jinja2.Template = template @@ -673,7 +689,7 @@ def __init__( if extensions: includes = _resolve_includes(extensions) directives = includes + directives - super().__init__(executable_path=executable_path, directives=directives) + super().__init__(executable_path=executable_path, directives=directives, version=version) @property def template(self) -> jinja2.Template: @@ -703,6 +719,7 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A message_types=message_types, message_registry=_message_registry, extensions=self._extensions, + ahk_version=self._version, **kwargs, ) diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk new file mode 100644 index 00000000..f3bb0d41 --- /dev/null +++ b/ahk/templates/daemon-v2.ahk @@ -0,0 +1,2840 @@ +{% block daemon_script %} +{% block directives %} +;#NoEnv +;#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(command) { + {% block AHKSetDetectHiddenWindows %} + value := command[2] + DetectHiddenWindows(value) + return FormatNoValueResponse() + {% endblock AHKSetDetectHiddenWindows %} +} + +AHKSetTitleMatchMode(command) { + {% block AHKSetTitleMatchMode %} + val1 := command[2] + val2 := command[3] + if (val1 != "") { + SetTitleMatchMode(val1) + } + if (val2 != "") { + SetTitleMatchMode(val2) + } + return FormatNoValueResponse() + {% endblock AHKSetTitleMatchMode %} +} + +AHKGetTitleMatchMode(command) { + {% block AHKGetTitleMatchMode %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) + {% endblock AHKGetTitleMatchMode %} +} + +AHKGetTitleMatchSpeed(command) { + {% block AHKGetTitleMatchSpeed %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) + {% endblock AHKGetTitleMatchSpeed %} +} + +AHKSetSendLevel(command) { + {% block AHKSetSendLevel %} + level := command[2] + SendLevel(level) + return FormatNoValueResponse() + {% endblock AHKSetSendLevel %} +} + +AHKGetSendLevel(command) { + {% block AHKGetSendLevel %} + + return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) + {% endblock AHKGetSendLevel %} +} + +AHKWinExist(command) { + {% block AHKWinExist %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 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(command) { + {% block AHKWinClose %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + secondstowait := command[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) + } + + WinClose(title, text, secondstowait, extitle, extext) + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return FormatNoValueResponse() + {% endblock AHKWinClose %} +} + +AHKWinKill(command) { + {% block AHKWinKill %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + secondstowait := command[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) + } + + WinKill(title, text, secondstowait, extitle, extext) + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return FormatNoValueResponse() + {% endblock AHKWinKill %} +} + +AHKWinWait(command) { + {% block AHKWinWait %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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) + } + 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 { + output := WinGetId() + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return resp + {% endblock AHKWinWait %} +} + +AHKWinWaitActive(command) { + {% block AHKWinWaitActive %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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) + } + 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 { + output := WinGetID() + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return resp + {% endblock AHKWinWaitActive %} +} + +AHKWinWaitNotActive(command) { + {% block AHKWinWaitNotActive %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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) + } + 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 { + output := WinGetID() + resp := FormatResponse("ahk.message.WindowResponseMessage", output) + } + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return resp + {% endblock AHKWinWaitNotActive %} +} + +AHKWinWaitClose(command) { + {% block AHKWinWaitClose %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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) + } + 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(command) { + {% block AHKWinMinimize %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinMinimize(title, text, extitle, extext) + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return FormatNoValueResponse() + {% endblock AHKWinMinimize %} +} + +AHKWinMaximize(command) { + {% block AHKWinMaximize %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinMaximize(title, text, extitle, extext) + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return FormatNoValueResponse() + {% endblock AHKWinMaximize %} +} + +AHKWinRestore(command) { + {% block AHKWinRestore %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinRestore(title, text, extitle, extext) + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return FormatNoValueResponse() + {% endblock AHKWinRestore %} +} + +AHKWinIsActive(command) { + {% block AHKWinIsActive %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 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(command) { + {% block AHKWinGetID %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetID(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(command) { + {% block AHKWinGetTitle %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + text := WinGetTitle(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(command) { + {% block AHKWinGetIDLast %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetIDLast(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(command) { + {% block AHKWinGetPID %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetPID(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(command) { + {% block AHKWinGetProcessName %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetProcessName(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(command) { + {% block AHKWinGetProcessPath %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetProcessPath(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(command) { + {% block AHKWinGetCount %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetCount(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(command) { + {% block AHKWinGetMinMax %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetMinMax(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(command) { + {% block AHKWinGetControlList %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + ahkid := WinGetID(title, text, extitle, extext) + + if (ahkid = "") { + return FormatNoValueResponse() + } + + ctrList := WinGetControls(title, text, extitle, extext) + ctrListID := WinGetControlsHwnd(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()) { + 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(command) { + {% block AHKWinGetTransparent %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetTransparent(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(command) { + {% block AHKWinGetTransColor %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetTransColor(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(command) { + {% block AHKWinGetStyle %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetStyle(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(command) { + {% block AHKWinGetExStyle %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetExStyle(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(command) { + {% block AHKWinGetText %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetText(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(command) { + {% block AHKWinSetTitle %} + new_title := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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) + } + WinSetTitle(title, text, new_title, extitle, extext) + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + return FormatNoValueResponse() + {% endblock AHKWinSetTitle %} +} + +AHKWinSetAlwaysOnTop(command) { + {% block AHKWinSetAlwaysOnTop %} + toggle := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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) + } + + WinSetAlwaysOnTop(toggle, title, text, extitle, extext) + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + return FormatNoValueResponse() + {% endblock AHKWinSetAlwaysOnTop %} +} + +AHKWinSetBottom(command) { + {% block AHKWinSetBottom %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinMoveBottom(title, text, extitle, extext) + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + return FormatNoValueResponse() + {% endblock AHKWinSetBottom %} +} + +AHKWinShow(command) { + {% block AHKWinShow %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinShow(title, text, extitle, extext) + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + return FormatNoValueResponse() + {% endblock AHKWinShow %} +} + +AHKWinHide(command) { + {% block AHKWinHide %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinHide(title, text, extitle, extext) + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + return FormatNoValueResponse() + {% endblock AHKWinHide %} +} + +AHKWinSetTop(command) { + {% block AHKWinSetTop %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinMoveTop(title, text, extitle, extext) + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + return FormatNoValueResponse() + {% endblock AHKWinSetTop %} +} + +AHKWinSetEnable(command) { + {% block AHKWinSetEnable %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinSetEnabled(title, text, extitle, extext) + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + return FormatNoValueResponse() + {% endblock AHKWinSetEnable %} +} + +AHKWinSetDisable(command) { + {% block AHKWinSetDisable %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinSetDisabled(title, text, extitle, extext) + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + return FormatNoValueResponse() + {% endblock AHKWinSetDisable %} +} + +AHKWinSetRedraw(command) { + {% block AHKWinSetRedraw %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinRedraw(title, text, extitle, extext) + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + return FormatNoValueResponse() + {% endblock AHKWinSetRedraw %} +} + +AHKWinSetStyle(command) { + {% block AHKWinSetStyle %} + + style := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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) + } + + WinSetStyle(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(command) { + {% block AHKWinSetExStyle %} + + style := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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) + } + + WinSetExStyle(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(command) { + {% block AHKWinSetRegion %} + + options := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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) + } + + WinSetRegion(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(command) { + {% block AHKWinSetTransparent %} + + transparency := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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) + } + + WinSetTransparent(transparency, title, text, extitle, extext) + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + return FormatNoValueResponse() + {% endblock AHKWinSetTransparent %} +} + +AHKWinSetTransColor(command) { + {% block AHKWinSetTransColor %} + + color := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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) + } + + WinSetTransColor(color, title, text, extitle, extext) + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return FormatNoValueResponse() + {% endblock AHKWinSetTransColor %} +} + +AHKImageSearch(command) { + {% block AHKImageSearch %} + + imagepath := command[6] + x1 := command[2] + y1 := command[3] + x2 := command[4] + y2 := command[5] + coord_mode := command[7] + + 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 command 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(command) { + {% block AHKPixelGetColor %} + + x := command[2] + y := command[3] + coord_mode := command[4] + options := command[5] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode(Pixel, coord_mode) + } + + color := PixelGetColor(x, y, options) + ; TODO: check errorlevel + + if (coord_mode != "") { + CoordMode(Pixel, current_mode) + } + + return FormatResponse("ahk.message.StringResponseMessage", color) + {% endblock AHKPixelGetColor %} +} + +AHKPixelSearch(command) { + {% block AHKPixelSearch %} + + x1 := command[2] + y1 := command[3] + x2 := command[4] + y2 := command[5] + color := command[6] + variation := command[7] + options := command[8] + coord_mode := command[9] + + current_mode := Format("{}", A_CoordModePixel) + + if (coord_mode != "") { + CoordMode(Pixel, coord_mode) + } + + PixelSearch(resultx, resulty, x1, y1, x2, y2, color, variation) + + 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(command) { + {% block AHKMouseGetPos %} + + coord_mode := command[2] + 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(command) { + {% block AHKKeyState %} + + keyname := command[2] + mode := command[3] + 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) + + if state is alnum + return FormatResponse("ahk.message.StringResponseMessage", state) + + return FormatResponse("ahk.message.ExceptionResponseMessage", state) + {% endblock AHKKeyState %} +} + +AHKMouseMove(command) { + {% block AHKMouseMove %} + x := command[2] + y := command[3] + speed := command[4] + relative := command[5] + if (relative != "") { + MouseMove(x, y, speed, "R") + } else { + MouseMove(x, y, speed) + } + resp := FormatNoValueResponse() + return resp + {% endblock AHKMouseMove %} +} + +AHKClick(command) { + {% block AHKClick %} + x := command[2] + y := command[3] + button := command[4] + click_count := command[5] + direction := command[6] + r := command[7] + relative_to := command[8] + current_coord_rel := Format("{}", A_CoordModeMouse) + + if (relative_to != "") { + CoordMode(Mouse, relative_to) + } + + Click(x, y, button, direction, r) + + if (relative_to != "") { + CoordMode(Mouse, current_coord_rel) + } + + return FormatNoValueResponse() + + {% endblock AHKClick %} +} + +AHKGetCoordMode(command) { + {% block AHKGetCoordMode %} + + target := command[2] + + 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(command) { + {% block AHKSetCoordMode %} + target := command[2] + relative_to := command[3] + CoordMode(target, relative_to) + + return FormatNoValueResponse() + {% endblock AHKSetCoordMode %} +} + +AHKMouseClickDrag(command) { + {% block AHKMouseClickDrag %} + button := command[2] + x1 := command[3] + y1 := command[4] + x2 := command[5] + y2 := command[6] + speed := command[7] + relative := command[8] + relative_to := command[9] + + 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) + } + + return FormatNoValueResponse() + + {% endblock AHKMouseClickDrag %} +} + +AHKRegRead(command) { + {% block RegRead %} + + key_name := command[2] + value_name := command[3] + + output := RegRead(key_name, value_name) + resp := FormatResponse("ahk.message.StringResponseMessage", Format("{}", output)) + return resp + {% endblock RegRead %} +} + +AHKRegWrite(command) { + {% block RegWrite %} + value_type := command[2] + key_name := command[3] + value_name := command[4] + value := command[5] +; RegWrite(value_type, key_name, value_name, value) + if (value_name != "") { + RegWrite(value, value_type, key_name) + } else { + RegWrite(value, value_type, key_name, value_name) + } + return FormatNoValueResponse() + {% endblock RegWrite %} +} + +AHKRegDelete(command) { + {% block RegDelete %} + + key_name := command[2] + value_name := command[3] + if (value_name != "") { + RegDelete(key_name, value_name) + } else { + RegDelete(key_name) + } + return FormatNoValueResponse() + + {% endblock RegDelete %} +} + +AHKKeyWait(command) { + {% block AHKKeyWait %} + + keyname := command[2] + if (command.Length() = 2) { + KeyWait(keyname) + } else { + options := command[3] + KeyWait(keyname, options) + } + return FormatResponse("ahk.message.IntegerResponseMessage", ErrorLevel) + {% endblock AHKKeyWait %} +} + +;SetKeyDelay(command) { +; {% block SetKeyDelay %} +; SetKeyDelay(command[2], command[3]) +; {% endblock SetKeyDelay %} +;} + +AHKSend(command) { + {% block AHKSend %} + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } + + Send(str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSend %} +} + +AHKSendRaw(command) { + {% block AHKSendRaw %} + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } + + SendRaw(str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSendRaw %} +} + +AHKSendInput(command) { + {% block AHKSendInput %} + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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(command) { + {% block AHKSendEvent %} + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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(command) { + {% block AHKSendPlay %} + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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(command) { + {% block AHKSetCapsLockState %} + state := command[2] + if (state = "") { + SetCapsLockState(!GetKeyState("CapsLock", "T")) + } else { + SetCapsLockState(state) + } + return FormatNoValueResponse() + {% endblock AHKSetCapsLockState %} +} + +HideTrayTip(command) { + {% 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(command) { + {% block AHKWinGetClass %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + output := WinGetClass(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(command) { + {% block AHKWinActivate %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinActivate(title, text, extitle, extext) + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return FormatNoValueResponse() + {% endblock AHKWinActivate %} +} + +AHKWindowList(command) { + {% block AHKWindowList %} + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + if (detect_hw) { + DetectHiddenWindows(detect_hw) + } + + windows := WinGetList(title, text, extitle, extext) + r := "" + for id in windows + { + 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(command) { + {% block AHKControlClick %} + + ctrl := command[2] + title := command[3] + text := command[4] + button := command[5] + click_count := command[6] + options := command[7] + exclude_title := command[8] + exclude_text := command[9] + detect_hw := command[10] + match_mode := command[11] + match_speed := command[12] + + 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(command) { + {% block AHKControlGetText %} + + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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) + } + + result := ControlGetText(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(command) { + {% block AHKControlGetPos %} + + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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) + } + + 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(command) { + {% block AHKControlSend %} + ctrl := command[2] + keys := command[3] + title := command[4] + text := command[5] + extitle := command[6] + extext := command[7] + detect_hw := command[8] + match_mode := command[9] + match_speed := command[10] + + 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(command) { + {% block AHKWinFromMouse %} + + MouseGetPos(,, MouseWin) + + if (MouseWin = "") { + return FormatNoValueResponse() + } + + return FormatResponse("ahk.message.WindowResponseMessage", MouseWin) + {% endblock AHKWinFromMouse %} +} + +AHKWinIsAlwaysOnTop(command) { + {% block AHKWinIsAlwaysOnTop %} + + title := command[2] + 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(command) { + {% block AHKWinMove %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + x := command[9] + y := command[10] + width := command[11] + height := command[12] + + 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(command) { + {% block AHKWinGetPos %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinGetPos(x, y, w, h, title, text, extitle, extext) + + if (ErrorLevel = 1) { + response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the position") + } 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(command) { + {% block AHKGetVolume %} + + device_number := command[2] + + try { + SoundGetWaveVolume(retval, device_number) + } catch as 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(command) { + {% block AHKSoundBeep %} + freq := command[2] + duration := command[3] + SoundBeep(freq, duration) + return FormatNoValueResponse() + {% endblock AHKSoundBeep %} +} + +AHKSoundGet(command) { + {% block AHKSoundGet %} + + device_number := command[2] + component_type := command[3] + control_type := command[4] + + SoundGet(retval, component_type, control_type, device_number) + ; TODO interpret return type + return FormatResponse("ahk.message.StringResponseMessage", Format("{}", retval)) + {% endblock AHKSoundGet %} +} + +AHKSoundSet(command) { + {% block AHKSoundSet %} + device_number := command[2] + component_type := command[3] + control_type := command[4] + value := command[5] + SoundSet(value, component_type, control_type, device_number) + return FormatNoValueResponse() + {% endblock AHKSoundSet %} +} + +AHKSoundPlay(command) { + {% block AHKSoundPlay %} + filename := command[2] + SoundPlay(filename) + return FormatNoValueResponse() + {% endblock AHKSoundPlay %} +} + +AHKSetVolume(command) { + {% block AHKSetVolume %} + device_number := command[2] + value := command[3] + SoundSetWaveVolume(value, device_number) + return FormatNoValueResponse() + {% endblock AHKSetVolume %} +} + + +AHKEcho(command) { + {% block AHKEcho %} + arg := command[2] + return FormatResponse("ahk.message.StringResponseMessage", arg) + {% endblock AHKEcho %} +} + +AHKTraytip(command) { + {% block AHKTraytip %} + title := command[2] + text := command[3] + second := command[4] + option := command[5] + + TrayTip(title, text, option) + return FormatNoValueResponse() + {% endblock AHKTraytip %} +} + +AHKShowToolTip(command) { + {% block AHKShowToolTip %} + text := command[2] + x := command[3] + y := command[4] + which := command[5] + ToolTip(text, x, y, which) + return FormatNoValueResponse() + {% endblock AHKShowToolTip %} +} + +AHKGetClipboard(command) { + {% block AHKGetClipboard %} + + return FormatResponse("ahk.message.StringResponseMessage", A_Clipboard) + {% endblock AHKGetClipboard %} +} + +AHKGetClipboardAll(command) { + {% block AHKGetClipboardAll %} + data := ClipboardAll() + return FormatBinaryResponse(data) + {% endblock AHKGetClipboardAll %} +} + +AHKSetClipboard(command) { + {% block AHKSetClipboard %} + text := command[2] + A_Clipboard := text + return FormatNoValueResponse() + {% endblock AHKSetClipboard %} +} + +AHKSetClipboardAll(command) { + {% block AHKSetClipboardAll %} + ; TODO there should be a way for us to accept a base64 string instead + filename := command[2] + contents := FileRead(filename, "RAW") + ClipboardAll(contents) + return FormatNoValueResponse() + {% endblock AHKSetClipboardAll %} +} + +AHKClipWait(command) { + + timeout := command[2] + wait_for_any_data := command[3] + + ClipWait(timeout, wait_for_any_data) + + if (ErrorLevel = 1) { + return FormatResponse("ahk.message.TimeoutResponseMessage", "timed out waiting for clipboard data") + } + return FormatNoValueResponse() +} + +AHKBlockInput(command) { + value := command[2] + BlockInput(value) + return FormatNoValueResponse() +} + +AHKMenuTrayTip(command) { + value := command[2] + Menu(Tray, Tip, value) + return FormatNoValueResponse() +} + +AHKMenuTrayShow(command) { + Menu(Tray, Icon) + return FormatNoValueResponse() +} + +AHKMenuTrayIcon(command) { + filename := command[2] + icon_number := command[3] + freeze := command[4] + Menu(Tray, Icon, filename, icon_number,freeze) + return FormatNoValueResponse() +} + +AHKGuiNew(command) { + + options := command[2] + title := command[3] + Gui(New, options, title) + return FormatResponse("ahk.message.StringResponseMessage", hwnd) +} + +AHKMsgBox(command) { + + options := command[2] + title := command[3] + text := command[4] + timeout := command[5] + 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(command) { + + title := command[2] + prompt := command[3] + hide := command[4] + width := command[5] + height := command[6] + x := command[7] + y := command[8] + locale := command[9] + timeout := command[10] + default := command[11] + + ; 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(command) { + + options := command[2] + root := command[3] + title := command[4] + filter := command[5] + output := FileSelectFile(options, root, title, filter) + if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +AHKFileSelectFolder(command) { + + starting_folder := command[2] + options := command[3] + prompt := command[4] + + output := FileSelectFolder(starting_folder, options, prompt) + + if (ErrorLevel = 1) { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +; LC_* functions are substantially from Libcrypt +; Modified from https://github.com/ahkscript/libcrypt.ahk +; Ref: https://www.autohotkey.com/boards/viewtopic.php?t=112821 +; Original License: +; The MIT License (MIT) +; +; Copyright (c) 2014 The ahkscript community (ahkscript.org) +; +; Permission is hereby granted, free of charge, to any person obtaining a copy +; of this software and associated documentation files (the "Software"), to deal +; in the Software without restriction, including without limitation the rights +; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the Software is +; furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included in all +; copies or substantial portions of the Software. + + +LC_Base64_Encode_Text(Text_, Encoding_ := "UTF-8") +{ + Bin_ := Buffer(StrPut(Text_, Encoding_)) + LC_Base64_Encode(&Base64_, &Bin_, StrPut(Text_, Bin_, Encoding_) - 1) + return Base64_ +} + +LC_Base64_Decode_Text(Text_, Encoding_ := "UTF-8") +{ + Len_ := LC_Base64_Decode(&Bin_, &Text_) + return StrGet(StrPtr(Bin_), Len_, Encoding_) +} + +LC_Base64_Encode(&Out_, &In_, In_Len) +{ + return LC_Bin2Str(&Out_, &In_, In_Len, 0x40000001) +} + +LC_Base64_Decode(&Out_, &In_) +{ + return LC_Str2Bin(&Out_, &In_, 0x1) +} + +LC_Bin2Str(&Out_, &In_, In_Len, Flags_) +{ + DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0) + VarSetStrCapacity(&Out_, Out_Len * 2) + DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len) + return Out_Len +} + +LC_Str2Bin(&Out_, &In_, Flags_) +{ + DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0, "Ptr", 0, "Ptr", 0) + VarSetStrCapacity(&Out_, Out_Len) + DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len, "Ptr", 0, "Ptr", 0) + return Out_Len +} +; End of libcrypt code + +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. + return LC_Base64_Decode_Text(pszString) + 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, "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, 0) +; granted := VarSetStrCapacity(&ret, 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, "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 + LC_Base64_Encode(&Base64_, &data, data.Size) + return Base64_ + 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.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 Exception(msg, -1) + } + return ret +} + +; End of included content + +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") + commandArray := CommandArrayFromQuery(query) + try { + func_name := commandArray[1] + {% block before_function %} + {% endblock before_function %} + pyresp := %func_name%(commandArray) + {% block after_function %} + {% endblock after_function %} + } catch Any as e { + {% block function_error_handle %} + message := Format("Error occurred in {}. The error message was: {}", e.line, e.message) + pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) + {% endblock function_error_handle %} + } + {% block send_response %} + if (pyresp) { +; MsgBox(pyresp) + stdout.Write(pyresp) + stdout.Read(0) + } else { + msg := FormatResponse("ahk.message.ExceptionResponseMessage", Format("Unknown Error when calling {}", func_name)) +; MsgBox(msg) + stdout.Write(msg) + stdout.Read(0) + } + {% endblock send_response %} +} + +{% endblock autoexecute %} +{% endblock daemon_script %} diff --git a/tests/_async/test_clipboard.py b/tests/_async/test_clipboard.py index e3d526a3..516ae62f 100644 --- a/tests/_async/test_clipboard.py +++ b/tests/_async/test_clipboard.py @@ -39,3 +39,8 @@ async def test_on_clipboard_change(self): 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 index 1a2b9d90..11430eb6 100644 --- a/tests/_async/test_extensions.py +++ b/tests/_async/test_extensions.py @@ -17,7 +17,7 @@ function_name = 'AAHKDoSomething' # unasync: remove ext_text = f'''\ -{function_name}(ByRef command) {{ +{function_name}(command) {{ arg := command[2] return FormatResponse("ahk.message.StringResponseMessage", Format("test{{}}", arg)) }} @@ -69,3 +69,19 @@ async def asyncTearDown(self) -> None: 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') + + +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 diff --git a/tests/_async/test_gui.py b/tests/_async/test_gui.py index 259d43c8..69553135 100644 --- a/tests/_async/test_gui.py +++ b/tests/_async/test_gui.py @@ -34,3 +34,8 @@ async def test_input_box(self): 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 index 804252e9..462cbe9e 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -67,3 +67,8 @@ async def test_clear_hotkeys(self): 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 index 7fa44c40..d06309c5 100644 --- a/tests/_async/test_keys.py +++ b/tests/_async/test_keys.py @@ -77,3 +77,13 @@ async def test_hotstring_callback(self): await self.ahk.send('btw ') await async_sleep(1) m.assert_called() + + +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) + await self.ahk.set_capslock_state('Off') diff --git a/tests/_async/test_mouse.py b/tests/_async/test_mouse.py index da78fb88..26477e6c 100644 --- a/tests/_async/test_mouse.py +++ b/tests/_async/test_mouse.py @@ -64,3 +64,8 @@ async def test_mouse_move_nonblocking(self): assert pos != current_pos assert pos != (500, 500) await res.result() + + +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 index e6f9e8ee..11f88121 100644 --- a/tests/_async/test_registry.py +++ b/tests/_async/test_registry.py @@ -51,3 +51,8 @@ async def test_reg_delete_default(self): 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 index d59ab302..5efc4089 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -79,3 +79,13 @@ async def test_image_search_with_option(self): # 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 index cbb1d223..52dc0440 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -60,3 +60,8 @@ 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') diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index ade7374e..08654439 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -188,3 +188,12 @@ async def test_win_move(self): 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) diff --git a/tests/_sync/test_clipboard.py b/tests/_sync/test_clipboard.py index 46156e1f..22ec1e87 100644 --- a/tests/_sync/test_clipboard.py +++ b/tests/_sync/test_clipboard.py @@ -38,3 +38,8 @@ def test_on_clipboard_change(self): 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 index 6c011ad7..e74f41fb 100644 --- a/tests/_sync/test_extensions.py +++ b/tests/_sync/test_extensions.py @@ -15,7 +15,7 @@ function_name = 'AHKDoSomething' ext_text = f'''\ -{function_name}(ByRef command) {{ +{function_name}(command) {{ arg := command[2] return FormatResponse("ahk.message.StringResponseMessage", Format("test{{}}", arg)) }} @@ -67,3 +67,19 @@ def tearDown(self) -> None: 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') + + +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 diff --git a/tests/_sync/test_gui.py b/tests/_sync/test_gui.py index f5caf9b9..db35d5ce 100644 --- a/tests/_sync/test_gui.py +++ b/tests/_sync/test_gui.py @@ -33,3 +33,8 @@ def test_input_box(self): 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 index ec13a58d..701c39d3 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -64,3 +64,8 @@ def test_clear_hotkeys(self): 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 index f8269404..4a18cdfe 100644 --- a/tests/_sync/test_keys.py +++ b/tests/_sync/test_keys.py @@ -76,3 +76,13 @@ def test_hotstring_callback(self): self.ahk.send('btw ') sleep(1) m.assert_called() + + +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) + self.ahk.set_capslock_state('Off') diff --git a/tests/_sync/test_mouse.py b/tests/_sync/test_mouse.py index 97f1faf3..e5616c6e 100644 --- a/tests/_sync/test_mouse.py +++ b/tests/_sync/test_mouse.py @@ -63,3 +63,8 @@ def test_mouse_move_nonblocking(self): assert pos != current_pos assert pos != (500, 500) res.result() + + +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 index 735b0aad..c9d604f3 100644 --- a/tests/_sync/test_registry.py +++ b/tests/_sync/test_registry.py @@ -51,3 +51,8 @@ def test_reg_delete_default(self): 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 index 00ff707b..fb2164ec 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -79,3 +79,13 @@ def test_image_search_with_option(self): # 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 index ccf8758d..fae4751e 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -60,3 +60,8 @@ 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') diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index bdb5b0dc..55c63b16 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -188,3 +188,12 @@ def test_win_move(self): 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) From 2d7db7a71c993d59ad8f111137ee6e496e7f2b50 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Sep 2023 14:23:18 -0700 Subject: [PATCH 436/588] double timeout for double the tests --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5356b57c..91e42f35 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,7 +23,7 @@ jobs: python -m pip install tox python -m pip install ahk-binary - name: Test with coverage/pytest - timeout-minutes: 5 + timeout-minutes: 10 env: PYTHONUNBUFFERED: "1" run: | From 1a5f2f3690d6c4c81597c1b156bfee915cb85b24 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 18 Sep 2023 14:25:13 -0700 Subject: [PATCH 437/588] upgrade binary requirement --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 91e42f35..bf3e4ced 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,7 +21,7 @@ jobs: python -m pip install -r requirements-dev.txt python -m pip install . python -m pip install tox - python -m pip install ahk-binary + python -m pip install "ahk-binary==2023.9.0rc1" - name: Test with coverage/pytest timeout-minutes: 10 env: From dfed6627ad8974be72db890331d77e17f64b4bfe Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 12:10:11 -0700 Subject: [PATCH 438/588] fix v1 bug in edge case in listing window controls This bug could cause title match mode and detect hidden window settings to not be returned to original values if the lengths of `ctrList` and `ctrListID` do not match. --- ahk/_constants.py | 3 +++ ahk/templates/daemon.ahk | 3 +++ 2 files changed, 6 insertions(+) diff --git a/ahk/_constants.py b/ahk/_constants.py index f7485eb0..352b3295 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -867,6 +867,9 @@ 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") } diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 112a3737..842cc3e4 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -864,6 +864,9 @@ AHKWinGetControlList(ByRef command) { 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") } From bc672e95df81620ddafa3bedddae6d45b6a19258 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 12:10:49 -0700 Subject: [PATCH 439/588] v2 - fix uses of ErrorLevel from v1 --- ahk/templates/daemon-v2.ahk | 926 +++++++++++++++++++---------------- tests/_async/test_scripts.py | 32 ++ tests/_sync/test_scripts.py | 32 ++ 3 files changed, 564 insertions(+), 426 deletions(-) diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index f3bb0d41..96461d67 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -231,22 +231,24 @@ AHKWinWait(command) { 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 { - output := WinGetId() - resp := FormatResponse("ahk.message.WindowResponseMessage", output) + 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) } - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - return resp {% endblock AHKWinWait %} } @@ -275,22 +277,24 @@ AHKWinWaitActive(command) { 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 { - output := WinGetID() - resp := FormatResponse("ahk.message.WindowResponseMessage", output) + 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) } - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - return resp {% endblock AHKWinWaitActive %} } @@ -319,22 +323,25 @@ AHKWinWaitNotActive(command) { 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 { - output := WinGetID() - resp := FormatResponse("ahk.message.WindowResponseMessage", output) + 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) } - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - return resp {% endblock AHKWinWaitNotActive %} } @@ -363,21 +370,23 @@ AHKWinWaitClose(command) { 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() + 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) } - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - return resp {% endblock AHKWinWaitClose %} } @@ -405,13 +414,14 @@ AHKWinMinimize(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinMinimize(title, text, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + try { + WinMinimize(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinMinimize %} } @@ -473,13 +483,14 @@ AHKWinRestore(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinRestore(title, text, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + try { + WinRestore(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinRestore %} } @@ -494,7 +505,7 @@ AHKWinIsActive(command) { detect_hw := command[6] match_mode := command[7] match_speed := command[8] - current_match_mode := Format("{}", A_TitleMatchMode) + current_match_mode := Format("{}", A_TitleMatchMode) current_match_speed := Format("{}", A_TitleMatchModeSpeed) if (match_mode != "") { SetTitleMatchMode(match_mode) @@ -508,15 +519,18 @@ AHKWinIsActive(command) { 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) + 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) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinIsActive %} } @@ -546,16 +560,19 @@ AHKWinGetID(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - output := WinGetID(title, text, extitle, extext) - if (output = 0 || output = "") { - response := FormatNoValueResponse() - } else { - response := FormatResponse("ahk.message.WindowResponseMessage", output) + 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) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinGetID %} } @@ -585,12 +602,14 @@ AHKWinGetTitle(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - text := WinGetTitle(title, text, extitle, extext) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + 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 %} } @@ -621,15 +640,19 @@ AHKWinGetIDLast(command) { DetectHiddenWindows(detect_hw) } - output := WinGetIDLast(title, text, extitle, extext) - if (output = 0 || output = "") { - response := FormatNoValueResponse() - } else { - response := FormatResponse("ahk.message.WindowResponseMessage", output) + 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) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinGetIDLast %} } @@ -659,16 +682,19 @@ AHKWinGetPID(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - output := WinGetPID(title, text, extitle, extext) - if (output = 0 || output = "") { - response := FormatNoValueResponse() - } else { - response := FormatResponse("ahk.message.IntegerResponseMessage", output) + 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) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinGetPID %} } @@ -699,15 +725,19 @@ AHKWinGetProcessName(command) { DetectHiddenWindows(detect_hw) } - output := WinGetProcessName(title, text, extitle, extext) - if (output = 0 || output = "") { - response := FormatNoValueResponse() - } else { - response := FormatResponse("ahk.message.StringResponseMessage", output) + 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) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinGetProcessName %} } @@ -737,16 +767,19 @@ AHKWinGetProcessPath(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - output := WinGetProcessPath(title, text, extitle, extext) - if (output = 0 || output = "") { - response := FormatNoValueResponse() - } else { - response := FormatResponse("ahk.message.StringResponseMessage", output) + 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) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinGetProcessPath %} } @@ -777,15 +810,19 @@ AHKWinGetCount(command) { DetectHiddenWindows(detect_hw) } - output := WinGetCount(title, text, extitle, extext) - if (output = 0) { - response := FormatResponse("ahk.message.IntegerResponseMessage", output) - } else { - response := FormatResponse("ahk.message.IntegerResponseMessage", output) + 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) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinGetCount %} } @@ -816,15 +853,19 @@ AHKWinGetMinMax(command) { DetectHiddenWindows(detect_hw) } - output := WinGetMinMax(title, text, extitle, extext) - if (output = "") { - response := FormatNoValueResponse() - } else { - response := FormatResponse("ahk.message.IntegerResponseMessage", output) + 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) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinGetMinMax %} } @@ -861,15 +902,21 @@ AHKWinGetControlList(command) { return FormatNoValueResponse() } - ctrList := WinGetControls(title, text, extitle, extext) - ctrListID := WinGetControlsHwnd(title, text, extitle, extext) - + try { + 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)) } - ctrListArr := StrSplit(ctrList, "`n") - ctrListIDArr := StrSplit(ctrListID, "`n") + ; ctrListArr := StrSplit(ctrList, "`n") + ; ctrListIDArr := StrSplit(ctrListID, "`n") if (ctrListArr.Length() != ctrListIDArr.Length()) { return FormatResponse("ahk.message.ExceptionResponseMessage", "Control hwnd/class lists have unexpected lengths") } @@ -883,9 +930,6 @@ AHKWinGetControlList(command) { } output .= "])" response := FormatResponse("ahk.message.WindowControlListResponseMessage", output) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinGetControlList %} } @@ -915,12 +959,15 @@ AHKWinGetTransparent(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - output := WinGetTransparent(title, text, extitle, extext) - response := FormatResponse("ahk.message.IntegerResponseMessage", output) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + 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 %} } @@ -949,12 +996,15 @@ AHKWinGetTransColor(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - output := WinGetTransColor(title, text, extitle, extext) - response := FormatResponse("ahk.message.NoValueResponseMessage", output) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + 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 %} } @@ -984,14 +1034,19 @@ AHKWinGetStyle(command) { DetectHiddenWindows(detect_hw) } - output := WinGetStyle(title, text, extitle, extext) - response := FormatResponse("ahk.message.NoValueResponseMessage", output) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + 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(command) { {% block AHKWinGetExStyle %} @@ -1017,12 +1072,15 @@ AHKWinGetExStyle(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - output := WinGetExStyle(title, text, extitle, extext) - response := FormatResponse("ahk.message.NoValueResponseMessage", output) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + 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 %} } @@ -1052,17 +1110,16 @@ AHKWinGetText(command) { DetectHiddenWindows(detect_hw) } - output := WinGetText(title,text,extitle,extext) - - if (ErrorLevel = 1) { - response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was an error getting window text") - } else { + 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) + } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinGetText %} } @@ -1091,10 +1148,14 @@ AHKWinSetTitle(command) { 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) + try { + WinSetTitle(title, text, new_title, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinSetTitle %} } @@ -1123,11 +1184,14 @@ AHKWinSetAlwaysOnTop(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinSetAlwaysOnTop(toggle, title, text, extitle, extext) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + try { + WinSetAlwaysOnTop(toggle, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinSetAlwaysOnTop %} } @@ -1156,11 +1220,14 @@ AHKWinSetBottom(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinMoveBottom(title, text, extitle, extext) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + try { + WinMoveBottom(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinSetBottom %} } @@ -1189,11 +1256,14 @@ AHKWinShow(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinShow(title, text, extitle, extext) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + try { + WinShow(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinShow %} } @@ -1222,11 +1292,14 @@ AHKWinHide(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinHide(title, text, extitle, extext) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + try { + WinHide(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinHide %} } @@ -1255,11 +1328,14 @@ AHKWinSetTop(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinMoveTop(title, text, extitle, extext) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + try { + WinMoveTop(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinSetTop %} } @@ -1288,11 +1364,14 @@ AHKWinSetEnable(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinSetEnabled(title, text, extitle, extext) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + try { + WinSetEnabled(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinSetEnable %} } @@ -1321,11 +1400,14 @@ AHKWinSetDisable(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinSetDisabled(title, text, extitle, extext) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + try { + WinSetDisabled(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinSetDisable %} } @@ -1354,11 +1436,14 @@ AHKWinSetRedraw(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinRedraw(title, text, extitle, extext) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + try { + WinRedraw(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinSetRedraw %} } @@ -1388,17 +1473,15 @@ AHKWinSetStyle(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinSetStyle(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 + 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 %} } @@ -1428,16 +1511,15 @@ AHKWinSetExStyle(command) { DetectHiddenWindows(detect_hw) } - WinSetExStyle(style, title, text, extitle, extext) - if (ErrorLevel = 1) { - resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) - } else { - resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + try { + WinSetExStyle(style, title, text, extitle, extext) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - return resp + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatResponse("ahk.message.BooleanResponseMessage", 1) {% endblock AHKWinSetExStyle %} } @@ -1466,17 +1548,15 @@ AHKWinSetRegion(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinSetRegion(options, title, text, extitle, extext) - if (ErrorLevel = 1) { - resp := FormatResponse("ahk.message.BooleanResponseMessage", 0) - } else { - resp := FormatResponse("ahk.message.BooleanResponseMessage", 1) + try { + WinSetRegion(options, title, text, extitle, extext) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - return resp + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatResponse("ahk.message.BooleanResponseMessage", 1) {% endblock AHKWinSetRegion %} } @@ -1505,11 +1585,14 @@ AHKWinSetTransparent(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinSetTransparent(transparency, title, text, extitle, extext) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + try { + WinSetTransparent(transparency, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinSetTransparent %} } @@ -1539,13 +1622,14 @@ AHKWinSetTransColor(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinSetTransColor(color, title, text, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + try { + WinSetTransColor(color, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinSetTransColor %} } @@ -1573,18 +1657,16 @@ AHKImageSearch(command) { y2 := A_ScreenHeight } - ImageSearch(xpos, ypos, x1, y1, x2, y2, imagepath) - - if (coord_mode != "") { - CoordMode(Pixel, current_mode) + try { + if ImageSearch(&xpos, &ypos, x1, y1, x2, y2, imagepath) + s := FormatResponse("ahk.message.CoordinateResponseMessage", Format("({}, {})", xpos, ypos)) + else + s := FormatNoValueResponse() } - - if (ErrorLevel = 2) { - s := FormatResponse("ahk.message.ExceptionResponseMessage", "there was a problem that prevented the command 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)) + finally { + if (coord_mode != "") { + CoordMode(Pixel, current_mode) + } } return s @@ -1605,11 +1687,13 @@ AHKPixelGetColor(command) { CoordMode(Pixel, coord_mode) } - color := PixelGetColor(x, y, options) - ; TODO: check errorlevel - - if (coord_mode != "") { - CoordMode(Pixel, current_mode) + try { + color := PixelGetColor(x, y, options) + } + finally { + if (coord_mode != "") { + CoordMode(Pixel, current_mode) + } } return FormatResponse("ahk.message.StringResponseMessage", color) @@ -1633,24 +1717,22 @@ AHKPixelSearch(command) { if (coord_mode != "") { CoordMode(Pixel, coord_mode) } - - PixelSearch(resultx, resulty, x1, y1, x2, y2, color, variation) - - if (coord_mode != "") { - CoordMode(Pixel, current_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() + } } - - 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") + finally { + if (coord_mode != "") { + CoordMode(Pixel, current_mode) + } } + return ret + {% endblock AHKPixelSearch %} } @@ -1690,16 +1772,17 @@ AHKKeyState(command) { return FormatNoValueResponse() } - if state is integer + if IsInteger(state) return FormatResponse("ahk.message.IntegerResponseMessage", state) - if state is float + if IsFloat(state) return FormatResponse("ahk.message.FloatResponseMessage", state) - if state is alnum + if IsAlnum(state) return FormatResponse("ahk.message.StringResponseMessage", state) - return FormatResponse("ahk.message.ExceptionResponseMessage", state) + msg := Format("Unexpected key state {}", state) + return FormatResponse("ahk.message.ExceptionResponseMessage", msg) {% endblock AHKKeyState %} } @@ -1855,12 +1938,12 @@ AHKKeyWait(command) { keyname := command[2] if (command.Length() = 2) { - KeyWait(keyname) + ret := KeyWait(keyname) } else { options := command[3] - KeyWait(keyname, options) + ret := KeyWait(keyname, options) } - return FormatResponse("ahk.message.IntegerResponseMessage", ErrorLevel) + return FormatResponse("ahk.message.IntegerResponseMessage", ret) {% endblock AHKKeyWait %} } @@ -1882,6 +1965,7 @@ AHKSend(command) { SetKeyDelay(key_delay, key_press_duration) } + Send(str) if (key_delay != "" or key_press_duration != "") { @@ -2022,18 +2106,16 @@ AHKWinGetClass(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - output := WinGetClass(title,text,extitle,extext) - - if (ErrorLevel = 1) { - response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was an error getting window class") - } else { + 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) + } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return response {% endblock AHKWinGetClass %} } @@ -2063,12 +2145,14 @@ AHKWinActivate(command) { DetectHiddenWindows(detect_hw) } - WinActivate(title, text, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + try { + WinActivate(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinActivate %} } @@ -2097,17 +2181,20 @@ AHKWindowList(command) { if (detect_hw) { DetectHiddenWindows(detect_hw) } - - windows := WinGetList(title, text, extitle, extext) - r := "" - for id in windows - { - r .= id . "`," + 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) } - resp := FormatResponse("ahk.message.WindowListResponseMessage", r) - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) return resp {% endblock AHKWindowList %} } @@ -2141,18 +2228,15 @@ AHKControlClick(command) { 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() + try { + ControlClick(ctrl, title, text, button, click_count, options, exclude_title, exclude_text) } - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + response := FormatNoValueResponse() return response {% endblock AHKControlClick %} } @@ -2183,16 +2267,15 @@ AHKControlGetText(command) { DetectHiddenWindows(detect_hw) } - result := ControlGetText(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) + try { + result := ControlGetText(ctrl, title, text, extitle, extext) } - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + response := FormatResponse("ahk.message.StringResponseMessage", result) return response {% endblock AHKControlGetText %} @@ -2224,20 +2307,17 @@ AHKControlGetPos(command) { 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 { + 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) } - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return response - {% endblock AHKControlGetPos %} } @@ -2266,10 +2346,15 @@ AHKControlSend(command) { 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) + + try { + ControlSend(ctrl, keys, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKControlSend %} } @@ -2289,9 +2374,9 @@ AHKWinFromMouse(command) { AHKWinIsAlwaysOnTop(command) { {% block AHKWinIsAlwaysOnTop %} - + ; TODO: detect hidden windows / etc? title := command[2] - WinGet(ExStyle, ExStyle, title) + ExStyle := WinGetExStyle(title) if (ExStyle = "") return FormatNoValueResponse() @@ -2330,12 +2415,14 @@ AHKWinMove(command) { DetectHiddenWindows(detect_hw) } - WinMove(title, text, x, y, width, height, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + try { + WinMove(title, text, x, y, width, height, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinMove %} @@ -2366,18 +2453,16 @@ AHKWinGetPos(command) { DetectHiddenWindows(detect_hw) } - WinGetPos(x, y, w, h, title, text, extitle, extext) - - if (ErrorLevel = 1) { - response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the position") - } else { + 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) } - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return response {% endblock AHKWinGetPos %} @@ -2388,17 +2473,8 @@ AHKGetVolume(command) { device_number := command[2] - try { - SoundGetWaveVolume(retval, device_number) - } catch as 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)) - } + retval := SoundGetVolume(,device_number) + response := FormatResponse("ahk.message.FloatResponseMessage", Format("{}", retval)) return response {% endblock AHKGetVolume %} } @@ -2414,25 +2490,13 @@ AHKSoundBeep(command) { AHKSoundGet(command) { {% block AHKSoundGet %} - - device_number := command[2] - component_type := command[3] - control_type := command[4] - - SoundGet(retval, component_type, control_type, device_number) - ; TODO interpret return type - return FormatResponse("ahk.message.StringResponseMessage", Format("{}", retval)) + return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundGet is not supported in ahk v2") {% endblock AHKSoundGet %} } AHKSoundSet(command) { {% block AHKSoundSet %} - device_number := command[2] - component_type := command[3] - control_type := command[4] - value := command[5] - SoundSet(value, component_type, control_type, device_number) - return FormatNoValueResponse() + return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundSet is not supported in ahk v2") {% endblock AHKSoundSet %} } @@ -2448,7 +2512,7 @@ AHKSetVolume(command) { {% block AHKSetVolume %} device_number := command[2] value := command[3] - SoundSetWaveVolume(value, device_number) + SoundSetVolume(value,,device_number) return FormatNoValueResponse() {% endblock AHKSetVolume %} } @@ -2521,11 +2585,10 @@ AHKClipWait(command) { timeout := command[2] wait_for_any_data := command[3] - ClipWait(timeout, wait_for_any_data) - - if (ErrorLevel = 1) { + if ClipWait(timeout, wait_for_any_data) + return FormatNoValueResponse() + else return FormatResponse("ahk.message.TimeoutResponseMessage", "timed out waiting for clipboard data") - } return FormatNoValueResponse() } @@ -2615,11 +2678,26 @@ AHKFileSelectFile(command) { root := command[3] title := command[4] filter := command[5] - output := FileSelectFile(options, root, title, filter) - if (ErrorLevel = 1) { + output := FileSelect(options, root, title, filter) + if (output = "") { ret := FormatNoValueResponse() } else { - ret := FormatResponse("ahk.message.StringResponseMessage", output) + 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 } @@ -2630,9 +2708,9 @@ AHKFileSelectFolder(command) { options := command[3] prompt := command[4] - output := FileSelectFolder(starting_folder, options, prompt) + output := DirSelect(starting_folder, options, prompt) - if (ErrorLevel = 1) { + if (output = "") { ret := FormatNoValueResponse() } else { ret := FormatResponse("ahk.message.StringResponseMessage", output) @@ -2777,8 +2855,6 @@ b64encode(data) { return ret } -; End of included content - CommandArrayFromQuery(text) { decoded_commands := [] encoded_array := StrSplit(text, "|") @@ -2818,18 +2894,16 @@ Loop { {% endblock after_function %} } catch Any as e { {% block function_error_handle %} - message := Format("Error occurred in {}. The error message was: {}", e.line, e.message) + message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what e.line, e.message e.extra) pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) {% endblock function_error_handle %} } {% block send_response %} if (pyresp) { -; MsgBox(pyresp) stdout.Write(pyresp) stdout.Read(0) } else { msg := FormatResponse("ahk.message.ExceptionResponseMessage", Format("Unknown Error when calling {}", func_name)) -; MsgBox(msg) stdout.Write(msg) stdout.Read(0) } diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index 52dc0440..6afb46aa 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -65,3 +65,35 @@ async def test_run_script_nonblocking(self): class TestScriptsV2(TestScripts): async def asyncSetUp(self) -> None: self.ahk = AsyncAHK(version='v2') + + async def test_run_script_text(self): + assert await self.ahk.win_get(title='Untitled - Notepad') is None + 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 await self.ahk.win_get(title='Untitled - Notepad') is None + 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 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 = '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/_sync/test_scripts.py b/tests/_sync/test_scripts.py index fae4751e..ae48e13e 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -65,3 +65,35 @@ def test_run_script_nonblocking(self): class TestScriptsV2(TestScripts): def setUp(self) -> None: self.ahk = AHK(version='v2') + + def test_run_script_text(self): + assert self.ahk.win_get(title='Untitled - Notepad') is None + 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 self.ahk.win_get(title='Untitled - Notepad') is None + 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 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 = 'stdout := FileOpen("*", "w", "UTF-8")\nstdout.Write("foo")\nstdout.Read(0)' + fut = self.ahk.run_script(script, blocking=False) + assert fut.result() == 'foo' From 5bc8d17232dde479e2be919a95e9e2b67dcb0585 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 12:15:52 -0700 Subject: [PATCH 440/588] set constant script for v2 --- _set_constants.py | 6 + ahk/_async/transport.py | 7 +- ahk/_constants.py | 2917 +++++++++++++++++++++++++++++++++++++++ ahk/_sync/transport.py | 4 +- 4 files changed, 2930 insertions(+), 4 deletions(-) diff --git a/_set_constants.py b/_set_constants.py index b6ecb297..50c54712 100644 --- a/_set_constants.py +++ b/_set_constants.py @@ -8,6 +8,9 @@ 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() + GIT_EXECUTABLE = shutil.which('git') if not GIT_EXECUTABLE: @@ -22,6 +25,9 @@ HOTKEYS_SCRIPT_TEMPLATE = r"""{hotkey_script} """ + +DAEMON_SCRIPT_V2_TEMPLATE = r"""{daemon_script_v2} +""" ''' with open('ahk/_constants.py', encoding='utf-8') as f: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 3e1c6ec6..68f0233b 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -44,7 +44,10 @@ from ahk.message import ResponseMessage from ahk.message import Position from ahk.message import _message_registry -from ahk._constants import DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE +from ahk._constants import ( + DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE, + DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE, +) from ahk.directives import Directive from concurrent.futures import Future, ThreadPoolExecutor @@ -686,7 +689,7 @@ def __init__( const_script = _DAEMON_SCRIPT_TEMPLATE elif version == 'v2': template_name = 'daemon-v2.ahk' - const_script = '' # TODO: set v2 constant + const_script = _DAEMON_SCRIPT_V2_TEMPLATE else: raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') diff --git a/ahk/_constants.py b/ahk/_constants.py index 352b3295..a5c1521a 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2890,3 +2890,2920 @@ FileAppend, %KEEPALIVE%`n, *, UTF-8 """ + +DAEMON_SCRIPT_V2_TEMPLATE = r"""{% block daemon_script %} +{% block directives %} +;#NoEnv +;#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(command) { + {% block AHKSetDetectHiddenWindows %} + value := command[2] + DetectHiddenWindows(value) + return FormatNoValueResponse() + {% endblock AHKSetDetectHiddenWindows %} +} + +AHKSetTitleMatchMode(command) { + {% block AHKSetTitleMatchMode %} + val1 := command[2] + val2 := command[3] + if (val1 != "") { + SetTitleMatchMode(val1) + } + if (val2 != "") { + SetTitleMatchMode(val2) + } + return FormatNoValueResponse() + {% endblock AHKSetTitleMatchMode %} +} + +AHKGetTitleMatchMode(command) { + {% block AHKGetTitleMatchMode %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) + {% endblock AHKGetTitleMatchMode %} +} + +AHKGetTitleMatchSpeed(command) { + {% block AHKGetTitleMatchSpeed %} + + return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) + {% endblock AHKGetTitleMatchSpeed %} +} + +AHKSetSendLevel(command) { + {% block AHKSetSendLevel %} + level := command[2] + SendLevel(level) + return FormatNoValueResponse() + {% endblock AHKSetSendLevel %} +} + +AHKGetSendLevel(command) { + {% block AHKGetSendLevel %} + + return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) + {% endblock AHKGetSendLevel %} +} + +AHKWinExist(command) { + {% block AHKWinExist %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 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(command) { + {% block AHKWinClose %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + secondstowait := command[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) + } + + WinClose(title, text, secondstowait, extitle, extext) + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return FormatNoValueResponse() + {% endblock AHKWinClose %} +} + +AHKWinKill(command) { + {% block AHKWinKill %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + secondstowait := command[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) + } + + WinKill(title, text, secondstowait, extitle, extext) + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return FormatNoValueResponse() + {% endblock AHKWinKill %} +} + +AHKWinWait(command) { + {% block AHKWinWait %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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 { + 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(command) { + {% block AHKWinWaitActive %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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 { + 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(command) { + {% block AHKWinWaitNotActive %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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 { + 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(command) { + {% block AHKWinWaitClose %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + timeout := command[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 { + 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(command) { + {% block AHKWinMinimize %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + WinMinimize(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinMinimize %} +} + +AHKWinMaximize(command) { + {% block AHKWinMaximize %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + WinMaximize(title, text, extitle, extext) + + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + + return FormatNoValueResponse() + {% endblock AHKWinMaximize %} +} + +AHKWinRestore(command) { + {% block AHKWinRestore %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + WinRestore(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinRestore %} +} + +AHKWinIsActive(command) { + {% block AHKWinIsActive %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 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(command) { + {% block AHKWinGetID %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetTitle %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetIDLast %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetPID %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetProcessName %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetProcessPath %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetCount %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetMinMax %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetControlList %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + + ahkid := WinGetID(title, text, extitle, extext) + + if (ahkid = "") { + return FormatNoValueResponse() + } + + try { + 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)) + } + + ; ctrListArr := StrSplit(ctrList, "`n") + ; ctrListIDArr := StrSplit(ctrListID, "`n") + if (ctrListArr.Length() != ctrListIDArr.Length()) { + 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) + return response + {% endblock AHKWinGetControlList %} +} + +AHKWinGetTransparent(command) { + {% block AHKWinGetTransparent %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetTransColor %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetStyle %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetExStyle %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinGetText %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinSetTitle %} + new_title := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 { + WinSetTitle(title, text, new_title, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTitle %} +} + +AHKWinSetAlwaysOnTop(command) { + {% block AHKWinSetAlwaysOnTop %} + toggle := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 { + WinSetAlwaysOnTop(toggle, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetAlwaysOnTop %} +} + +AHKWinSetBottom(command) { + {% block AHKWinSetBottom %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + WinMoveBottom(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetBottom %} +} + +AHKWinShow(command) { + {% block AHKWinShow %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + WinShow(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinShow %} +} + +AHKWinHide(command) { + {% block AHKWinHide %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + WinHide(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinHide %} +} + +AHKWinSetTop(command) { + {% block AHKWinSetTop %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + WinMoveTop(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTop %} +} + +AHKWinSetEnable(command) { + {% block AHKWinSetEnable %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + WinSetEnabled(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetEnable %} +} + +AHKWinSetDisable(command) { + {% block AHKWinSetDisable %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + WinSetDisabled(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetDisable %} +} + +AHKWinSetRedraw(command) { + {% block AHKWinSetRedraw %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + WinRedraw(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetRedraw %} +} + +AHKWinSetStyle(command) { + {% block AHKWinSetStyle %} + + style := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 { + 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(command) { + {% block AHKWinSetExStyle %} + + style := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 { + 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(command) { + {% block AHKWinSetRegion %} + + options := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 { + 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(command) { + {% block AHKWinSetTransparent %} + + transparency := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 { + WinSetTransparent(transparency, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTransparent %} +} + +AHKWinSetTransColor(command) { + {% block AHKWinSetTransColor %} + + color := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 { + WinSetTransColor(color, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinSetTransColor %} +} + +AHKImageSearch(command) { + {% block AHKImageSearch %} + + imagepath := command[6] + x1 := command[2] + y1 := command[3] + x2 := command[4] + y2 := command[5] + coord_mode := command[7] + + 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) + s := FormatResponse("ahk.message.CoordinateResponseMessage", Format("({}, {})", xpos, ypos)) + else + s := FormatNoValueResponse() + } + finally { + if (coord_mode != "") { + CoordMode(Pixel, current_mode) + } + } + + return s + {% endblock AHKImageSearch %} +} + +AHKPixelGetColor(command) { + {% block AHKPixelGetColor %} + + x := command[2] + y := command[3] + coord_mode := command[4] + options := command[5] + + 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(command) { + {% block AHKPixelSearch %} + + x1 := command[2] + y1 := command[3] + x2 := command[4] + y2 := command[5] + color := command[6] + variation := command[7] + options := command[8] + coord_mode := command[9] + + 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(command) { + {% block AHKMouseGetPos %} + + coord_mode := command[2] + 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(command) { + {% block AHKKeyState %} + + keyname := command[2] + mode := command[3] + 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) + + if IsAlnum(state) + return FormatResponse("ahk.message.StringResponseMessage", state) + + msg := Format("Unexpected key state {}", state) + return FormatResponse("ahk.message.ExceptionResponseMessage", msg) + {% endblock AHKKeyState %} +} + +AHKMouseMove(command) { + {% block AHKMouseMove %} + x := command[2] + y := command[3] + speed := command[4] + relative := command[5] + if (relative != "") { + MouseMove(x, y, speed, "R") + } else { + MouseMove(x, y, speed) + } + resp := FormatNoValueResponse() + return resp + {% endblock AHKMouseMove %} +} + +AHKClick(command) { + {% block AHKClick %} + x := command[2] + y := command[3] + button := command[4] + click_count := command[5] + direction := command[6] + r := command[7] + relative_to := command[8] + current_coord_rel := Format("{}", A_CoordModeMouse) + + if (relative_to != "") { + CoordMode(Mouse, relative_to) + } + + Click(x, y, button, direction, r) + + if (relative_to != "") { + CoordMode(Mouse, current_coord_rel) + } + + return FormatNoValueResponse() + + {% endblock AHKClick %} +} + +AHKGetCoordMode(command) { + {% block AHKGetCoordMode %} + + target := command[2] + + 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(command) { + {% block AHKSetCoordMode %} + target := command[2] + relative_to := command[3] + CoordMode(target, relative_to) + + return FormatNoValueResponse() + {% endblock AHKSetCoordMode %} +} + +AHKMouseClickDrag(command) { + {% block AHKMouseClickDrag %} + button := command[2] + x1 := command[3] + y1 := command[4] + x2 := command[5] + y2 := command[6] + speed := command[7] + relative := command[8] + relative_to := command[9] + + 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) + } + + return FormatNoValueResponse() + + {% endblock AHKMouseClickDrag %} +} + +AHKRegRead(command) { + {% block RegRead %} + + key_name := command[2] + value_name := command[3] + + output := RegRead(key_name, value_name) + resp := FormatResponse("ahk.message.StringResponseMessage", Format("{}", output)) + return resp + {% endblock RegRead %} +} + +AHKRegWrite(command) { + {% block RegWrite %} + value_type := command[2] + key_name := command[3] + value_name := command[4] + value := command[5] +; RegWrite(value_type, key_name, value_name, value) + if (value_name != "") { + RegWrite(value, value_type, key_name) + } else { + RegWrite(value, value_type, key_name, value_name) + } + return FormatNoValueResponse() + {% endblock RegWrite %} +} + +AHKRegDelete(command) { + {% block RegDelete %} + + key_name := command[2] + value_name := command[3] + if (value_name != "") { + RegDelete(key_name, value_name) + } else { + RegDelete(key_name) + } + return FormatNoValueResponse() + + {% endblock RegDelete %} +} + +AHKKeyWait(command) { + {% block AHKKeyWait %} + + keyname := command[2] + if (command.Length() = 2) { + ret := KeyWait(keyname) + } else { + options := command[3] + ret := KeyWait(keyname, options) + } + return FormatResponse("ahk.message.IntegerResponseMessage", ret) + {% endblock AHKKeyWait %} +} + +;SetKeyDelay(command) { +; {% block SetKeyDelay %} +; SetKeyDelay(command[2], command[3]) +; {% endblock SetKeyDelay %} +;} + +AHKSend(command) { + {% block AHKSend %} + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } + + + Send(str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSend %} +} + +AHKSendRaw(command) { + {% block AHKSendRaw %} + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + current_delay := Format("{}", A_KeyDelay) + current_key_duration := Format("{}", A_KeyDuration) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(key_delay, key_press_duration) + } + + SendRaw(str) + + if (key_delay != "" or key_press_duration != "") { + SetKeyDelay(current_delay, current_key_duration) + } + return FormatNoValueResponse() + {% endblock AHKSendRaw %} +} + +AHKSendInput(command) { + {% block AHKSendInput %} + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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(command) { + {% block AHKSendEvent %} + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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(command) { + {% block AHKSendPlay %} + str := command[2] + key_delay := command[3] + key_press_duration := command[4] + 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(command) { + {% block AHKSetCapsLockState %} + state := command[2] + if (state = "") { + SetCapsLockState(!GetKeyState("CapsLock", "T")) + } else { + SetCapsLockState(state) + } + return FormatNoValueResponse() + {% endblock AHKSetCapsLockState %} +} + +HideTrayTip(command) { + {% 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(command) { + {% block AHKWinGetClass %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKWinActivate %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + WinActivate(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKWinActivate %} +} + +AHKWindowList(command) { + {% block AHKWindowList %} + + current_detect_hw := Format("{}", A_DetectHiddenWindows) + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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) + } + 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(command) { + {% block AHKControlClick %} + + ctrl := command[2] + title := command[3] + text := command[4] + button := command[5] + click_count := command[6] + options := command[7] + exclude_title := command[8] + exclude_text := command[9] + detect_hw := command[10] + match_mode := command[11] + match_speed := command[12] + + 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, 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(command) { + {% block AHKControlGetText %} + + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 { + 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(command) { + {% block AHKControlGetPos %} + + ctrl := command[2] + title := command[3] + text := command[4] + extitle := command[5] + extext := command[6] + detect_hw := command[7] + match_mode := command[8] + match_speed := command[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 { + 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(command) { + {% block AHKControlSend %} + ctrl := command[2] + keys := command[3] + title := command[4] + text := command[5] + extitle := command[6] + extext := command[7] + detect_hw := command[8] + match_mode := command[9] + match_speed := command[10] + + 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(ctrl, keys, title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + {% endblock AHKControlSend %} +} + +AHKWinFromMouse(command) { + {% block AHKWinFromMouse %} + + MouseGetPos(,, MouseWin) + + if (MouseWin = "") { + return FormatNoValueResponse() + } + + return FormatResponse("ahk.message.WindowResponseMessage", MouseWin) + {% endblock AHKWinFromMouse %} +} + +AHKWinIsAlwaysOnTop(command) { + {% block AHKWinIsAlwaysOnTop %} + ; TODO: detect hidden windows / etc? + title := command[2] + 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(command) { + {% block AHKWinMove %} + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[8] + x := command[9] + y := command[10] + width := command[11] + height := command[12] + + 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 { + WinMove(title, text, x, y, width, height, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } + return FormatNoValueResponse() + + {% endblock AHKWinMove %} +} + +AHKWinGetPos(command) { + {% block AHKWinGetPos %} + + title := command[2] + text := command[3] + extitle := command[4] + extext := command[5] + detect_hw := command[6] + match_mode := command[7] + match_speed := command[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 { + 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(command) { + {% block AHKGetVolume %} + + device_number := command[2] + + retval := SoundGetVolume(,device_number) + response := FormatResponse("ahk.message.FloatResponseMessage", Format("{}", retval)) + return response + {% endblock AHKGetVolume %} +} + +AHKSoundBeep(command) { + {% block AHKSoundBeep %} + freq := command[2] + duration := command[3] + SoundBeep(freq, duration) + return FormatNoValueResponse() + {% endblock AHKSoundBeep %} +} + +AHKSoundGet(command) { + {% block AHKSoundGet %} + return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundGet is not supported in ahk v2") + {% endblock AHKSoundGet %} +} + +AHKSoundSet(command) { + {% block AHKSoundSet %} + return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundSet is not supported in ahk v2") + {% endblock AHKSoundSet %} +} + +AHKSoundPlay(command) { + {% block AHKSoundPlay %} + filename := command[2] + SoundPlay(filename) + return FormatNoValueResponse() + {% endblock AHKSoundPlay %} +} + +AHKSetVolume(command) { + {% block AHKSetVolume %} + device_number := command[2] + value := command[3] + SoundSetVolume(value,,device_number) + return FormatNoValueResponse() + {% endblock AHKSetVolume %} +} + + +AHKEcho(command) { + {% block AHKEcho %} + arg := command[2] + return FormatResponse("ahk.message.StringResponseMessage", arg) + {% endblock AHKEcho %} +} + +AHKTraytip(command) { + {% block AHKTraytip %} + title := command[2] + text := command[3] + second := command[4] + option := command[5] + + TrayTip(title, text, option) + return FormatNoValueResponse() + {% endblock AHKTraytip %} +} + +AHKShowToolTip(command) { + {% block AHKShowToolTip %} + text := command[2] + x := command[3] + y := command[4] + which := command[5] + ToolTip(text, x, y, which) + return FormatNoValueResponse() + {% endblock AHKShowToolTip %} +} + +AHKGetClipboard(command) { + {% block AHKGetClipboard %} + + return FormatResponse("ahk.message.StringResponseMessage", A_Clipboard) + {% endblock AHKGetClipboard %} +} + +AHKGetClipboardAll(command) { + {% block AHKGetClipboardAll %} + data := ClipboardAll() + return FormatBinaryResponse(data) + {% endblock AHKGetClipboardAll %} +} + +AHKSetClipboard(command) { + {% block AHKSetClipboard %} + text := command[2] + A_Clipboard := text + return FormatNoValueResponse() + {% endblock AHKSetClipboard %} +} + +AHKSetClipboardAll(command) { + {% block AHKSetClipboardAll %} + ; TODO there should be a way for us to accept a base64 string instead + filename := command[2] + contents := FileRead(filename, "RAW") + ClipboardAll(contents) + return FormatNoValueResponse() + {% endblock AHKSetClipboardAll %} +} + +AHKClipWait(command) { + + timeout := command[2] + wait_for_any_data := command[3] + + if ClipWait(timeout, wait_for_any_data) + return FormatNoValueResponse() + else + return FormatResponse("ahk.message.TimeoutResponseMessage", "timed out waiting for clipboard data") + return FormatNoValueResponse() +} + +AHKBlockInput(command) { + value := command[2] + BlockInput(value) + return FormatNoValueResponse() +} + +AHKMenuTrayTip(command) { + value := command[2] + Menu(Tray, Tip, value) + return FormatNoValueResponse() +} + +AHKMenuTrayShow(command) { + Menu(Tray, Icon) + return FormatNoValueResponse() +} + +AHKMenuTrayIcon(command) { + filename := command[2] + icon_number := command[3] + freeze := command[4] + Menu(Tray, Icon, filename, icon_number,freeze) + return FormatNoValueResponse() +} + +AHKGuiNew(command) { + + options := command[2] + title := command[3] + Gui(New, options, title) + return FormatResponse("ahk.message.StringResponseMessage", hwnd) +} + +AHKMsgBox(command) { + + options := command[2] + title := command[3] + text := command[4] + timeout := command[5] + 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(command) { + + title := command[2] + prompt := command[3] + hide := command[4] + width := command[5] + height := command[6] + x := command[7] + y := command[8] + locale := command[9] + timeout := command[10] + default := command[11] + + ; 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(command) { + + options := command[2] + root := command[3] + title := command[4] + filter := command[5] + 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(command) { + + starting_folder := command[2] + options := command[3] + prompt := command[4] + + output := DirSelect(starting_folder, options, prompt) + + if (output = "") { + ret := FormatNoValueResponse() + } else { + ret := FormatResponse("ahk.message.StringResponseMessage", output) + } + return ret +} + +; LC_* functions are substantially from Libcrypt +; Modified from https://github.com/ahkscript/libcrypt.ahk +; Ref: https://www.autohotkey.com/boards/viewtopic.php?t=112821 +; Original License: +; The MIT License (MIT) +; +; Copyright (c) 2014 The ahkscript community (ahkscript.org) +; +; Permission is hereby granted, free of charge, to any person obtaining a copy +; of this software and associated documentation files (the "Software"), to deal +; in the Software without restriction, including without limitation the rights +; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the Software is +; furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included in all +; copies or substantial portions of the Software. + + +LC_Base64_Encode_Text(Text_, Encoding_ := "UTF-8") +{ + Bin_ := Buffer(StrPut(Text_, Encoding_)) + LC_Base64_Encode(&Base64_, &Bin_, StrPut(Text_, Bin_, Encoding_) - 1) + return Base64_ +} + +LC_Base64_Decode_Text(Text_, Encoding_ := "UTF-8") +{ + Len_ := LC_Base64_Decode(&Bin_, &Text_) + return StrGet(StrPtr(Bin_), Len_, Encoding_) +} + +LC_Base64_Encode(&Out_, &In_, In_Len) +{ + return LC_Bin2Str(&Out_, &In_, In_Len, 0x40000001) +} + +LC_Base64_Decode(&Out_, &In_) +{ + return LC_Str2Bin(&Out_, &In_, 0x1) +} + +LC_Bin2Str(&Out_, &In_, In_Len, Flags_) +{ + DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0) + VarSetStrCapacity(&Out_, Out_Len * 2) + DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len) + return Out_Len +} + +LC_Str2Bin(&Out_, &In_, Flags_) +{ + DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0, "Ptr", 0, "Ptr", 0) + VarSetStrCapacity(&Out_, Out_Len) + DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len, "Ptr", 0, "Ptr", 0) + return Out_Len +} +; End of libcrypt code + +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. + return LC_Base64_Decode_Text(pszString) + 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, "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, 0) +; granted := VarSetStrCapacity(&ret, 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, "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 + LC_Base64_Encode(&Base64_, &data, data.Size) + return Base64_ + 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.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 Exception(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") + commandArray := CommandArrayFromQuery(query) + try { + func_name := commandArray[1] + {% block before_function %} + {% endblock before_function %} + pyresp := %func_name%(commandArray) + {% 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: {}", e.what e.line, e.message e.extra) + 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/_sync/transport.py b/ahk/_sync/transport.py index e94db02c..ef7c74ba 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -44,7 +44,7 @@ from ahk.message import ResponseMessage from ahk.message import Position from ahk.message import _message_registry -from ahk._constants import DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE +from ahk._constants import DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE, DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE from ahk.directives import Directive from concurrent.futures import Future, ThreadPoolExecutor @@ -659,7 +659,7 @@ def __init__( const_script = _DAEMON_SCRIPT_TEMPLATE elif version == 'v2': template_name = 'daemon-v2.ahk' - const_script = '' # TODO: set v2 constant + const_script = _DAEMON_SCRIPT_V2_TEMPLATE else: raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') From dcc1e3654f7950dc056429b753e51fc62306bb8b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 12:22:43 -0700 Subject: [PATCH 441/588] Add #Requires directive to daemon scripts --- ahk/_constants.py | 2 ++ ahk/templates/daemon-v2.ahk | 1 + ahk/templates/daemon.ahk | 1 + 3 files changed, 4 insertions(+) diff --git a/ahk/_constants.py b/ahk/_constants.py index a5c1521a..f4f385aa 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -3,6 +3,7 @@ DAEMON_SCRIPT_TEMPLATE = r"""{% block daemon_script %} {% block directives %} +#Requires AutoHotkey v1.1.17+ #NoEnv #Persistent #SingleInstance Off @@ -2895,6 +2896,7 @@ {% block directives %} ;#NoEnv ;#Persistent +#Requires Autohotkey >= 2.0- #Warn All, Off #SingleInstance Off ; BEGIN user-defined directives diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 96461d67..629ef802 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2,6 +2,7 @@ {% block directives %} ;#NoEnv ;#Persistent +#Requires Autohotkey >= 2.0- #Warn All, Off #SingleInstance Off ; BEGIN user-defined directives diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 842cc3e4..83ca10c8 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1,5 +1,6 @@ {% block daemon_script %} {% block directives %} +#Requires AutoHotkey v1.1.17+ #NoEnv #Persistent #SingleInstance Off From 2a4bdd166505e496d477d16bbac7be56754446ed Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 12:24:29 -0700 Subject: [PATCH 442/588] fix error message --- ahk/_constants.py | 2 +- ahk/templates/daemon-v2.ahk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index f4f385aa..7a46306a 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -5789,7 +5789,7 @@ {% endblock after_function %} } catch Any as e { {% block function_error_handle %} - message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what e.line, e.message e.extra) + message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what, e.line, e.message e.extra) pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) {% endblock function_error_handle %} } diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 629ef802..de568fd2 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2895,7 +2895,7 @@ Loop { {% endblock after_function %} } catch Any as e { {% block function_error_handle %} - message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what e.line, e.message e.extra) + message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what, e.line, e.message e.extra) pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) {% endblock function_error_handle %} } From 71a0362c5fd089cfb44b398906094644631c6af6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 12:26:58 -0700 Subject: [PATCH 443/588] fix error message --- ahk/_constants.py | 8 +++----- ahk/templates/daemon-v2.ahk | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 7a46306a..2c54d19a 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -3810,9 +3810,7 @@ return FormatResponse("ahk.message.WindowControlListResponseMessage", Format("('{}', [])", ahkid)) } - ; ctrListArr := StrSplit(ctrList, "`n") - ; ctrListIDArr := StrSplit(ctrListID, "`n") - if (ctrListArr.Length() != ctrListIDArr.Length()) { + if (ctrList.Length != ctrListID.Length) { return FormatResponse("ahk.message.ExceptionResponseMessage", "Control hwnd/class lists have unexpected lengths") } @@ -4832,7 +4830,7 @@ {% block AHKKeyWait %} keyname := command[2] - if (command.Length() = 2) { + if (command.Length = 2) { ret := KeyWait(keyname) } else { options := command[3] @@ -5789,7 +5787,7 @@ {% endblock after_function %} } catch Any as e { {% block function_error_handle %} - message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what, e.line, e.message e.extra) + message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what, e.line, e.message, e.extra) pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) {% endblock function_error_handle %} } diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index de568fd2..5129370b 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -916,9 +916,7 @@ AHKWinGetControlList(command) { return FormatResponse("ahk.message.WindowControlListResponseMessage", Format("('{}', [])", ahkid)) } - ; ctrListArr := StrSplit(ctrList, "`n") - ; ctrListIDArr := StrSplit(ctrListID, "`n") - if (ctrListArr.Length() != ctrListIDArr.Length()) { + if (ctrList.Length != ctrListID.Length) { return FormatResponse("ahk.message.ExceptionResponseMessage", "Control hwnd/class lists have unexpected lengths") } @@ -1938,7 +1936,7 @@ AHKKeyWait(command) { {% block AHKKeyWait %} keyname := command[2] - if (command.Length() = 2) { + if (command.Length = 2) { ret := KeyWait(keyname) } else { options := command[3] @@ -2895,7 +2893,7 @@ Loop { {% endblock after_function %} } catch Any as e { {% block function_error_handle %} - message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what, e.line, e.message e.extra) + message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what, e.line, e.message, e.extra) pyresp := FormatResponse("ahk.message.ExceptionResponseMessage", message) {% endblock function_error_handle %} } From 5b0ccafa368e8acbd2473b4c62dfb4a3bb0b9764 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 12:30:17 -0700 Subject: [PATCH 444/588] Add stack to error message in v2 --- ahk/_constants.py | 2 +- ahk/templates/daemon-v2.ahk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 2c54d19a..2f08c310 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -5787,7 +5787,7 @@ {% endblock after_function %} } catch Any as e { {% block function_error_handle %} - message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what, e.line, e.message, e.extra) + 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 %} } diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 5129370b..c38dd3c7 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2893,7 +2893,7 @@ Loop { {% endblock after_function %} } catch Any as e { {% block function_error_handle %} - message := Format("Error occurred in {} (line {}). The error message was: {}. Specifically: {}", e.what, e.line, e.message, e.extra) + 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 %} } From f6c42f3cd7b5cf109a7f0fa9471653931235d2f4 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 13:06:23 -0700 Subject: [PATCH 445/588] misc fixes from warnings --- ahk/_constants.py | 183 ++++++++++++++++++------------------ ahk/templates/daemon-v2.ahk | 183 ++++++++++++++++++------------------ 2 files changed, 180 insertions(+), 186 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 2f08c310..505dc99f 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2895,8 +2895,8 @@ DAEMON_SCRIPT_V2_TEMPLATE = r"""{% block daemon_script %} {% block directives %} ;#NoEnv -;#Persistent #Requires Autohotkey >= 2.0- +Persistent #Warn All, Off #SingleInstance Off ; BEGIN user-defined directives @@ -3790,14 +3790,11 @@ if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - ahkid := WinGetID(title, text, extitle, extext) - - if (ahkid = "") { - return FormatNoValueResponse() - } - try { + ahkid := WinGetID(title, text, extitle, extext) + if (ahkid = "") { + return FormatNoValueResponse() + } ctrList := WinGetControls(title, text, extitle, extext) ctrListID := WinGetControlsHwnd(title, text, extitle, extext) } @@ -3816,8 +3813,8 @@ output := Format("('{}', [", ahkid) - for index, hwnd in ctrListIDArr { - classname := ctrListArr[index] + for index, hwnd in ctrListID { + classname := ctrList[index] output .= Format("('{}', '{}'), ", hwnd, classname) } @@ -4258,7 +4255,7 @@ DetectHiddenWindows(detect_hw) } try { - WinSetEnabled(title, text, extitle, extext) + WinSetEnabled(1, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) @@ -4294,7 +4291,7 @@ DetectHiddenWindows(detect_hw) } try { - WinSetDisabled(title, text, extitle, extext) + WinSetEnabled(0, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) @@ -4540,7 +4537,7 @@ current_mode := Format("{}", A_CoordModePixel) if (coord_mode != "") { - CoordMode(Pixel, coord_mode) + CoordMode("Pixel", coord_mode) } if (x2 = "A_ScreenWidth") { @@ -4558,7 +4555,7 @@ } finally { if (coord_mode != "") { - CoordMode(Pixel, current_mode) + CoordMode("Pixel", current_mode) } } @@ -4577,7 +4574,7 @@ current_mode := Format("{}", A_CoordModePixel) if (coord_mode != "") { - CoordMode(Pixel, coord_mode) + CoordMode("Pixel", coord_mode) } try { @@ -4585,7 +4582,7 @@ } finally { if (coord_mode != "") { - CoordMode(Pixel, current_mode) + CoordMode("Pixel", current_mode) } } @@ -4608,7 +4605,7 @@ current_mode := Format("{}", A_CoordModePixel) if (coord_mode != "") { - CoordMode(Pixel, coord_mode) + CoordMode("Pixel", coord_mode) } try { if (PixelSearch(&resultx, &resulty, x1, y1, x2, y2, color, variation) = 1) { @@ -4620,7 +4617,7 @@ } finally { if (coord_mode != "") { - CoordMode(Pixel, current_mode) + CoordMode("Pixel", current_mode) } } @@ -4635,7 +4632,7 @@ coord_mode := command[2] current_coord_mode := Format("{}", A_CoordModeMouse) if (coord_mode != "") { - CoordMode(Mouse, coord_mode) + CoordMode("Mouse", coord_mode) } MouseGetPos(&xpos, &ypos) @@ -4643,7 +4640,7 @@ resp := FormatResponse("ahk.message.CoordinateResponseMessage", payload) if (coord_mode != "") { - CoordMode(Mouse, current_coord_mode) + CoordMode("Mouse", current_coord_mode) } return resp @@ -4707,13 +4704,13 @@ current_coord_rel := Format("{}", A_CoordModeMouse) if (relative_to != "") { - CoordMode(Mouse, relative_to) + CoordMode("Mouse", relative_to) } Click(x, y, button, direction, r) if (relative_to != "") { - CoordMode(Mouse, current_coord_rel) + CoordMode("Mouse", current_coord_rel) } return FormatNoValueResponse() @@ -4769,13 +4766,13 @@ current_coord_rel := Format("{}", A_CoordModeMouse) if (relative_to != "") { - CoordMode(Mouse, relative_to) + CoordMode("Mouse", relative_to) } MouseClickDrag(button, x1, y1, x2, y2, speed, relative) if (relative_to != "") { - CoordMode(Mouse, current_coord_rel) + CoordMode("Mouse", current_coord_rel) } return FormatNoValueResponse() @@ -4880,7 +4877,7 @@ SetKeyDelay(key_delay, key_press_duration) } - SendRaw(str) + Send("{Raw}" str) if (key_delay != "" or key_press_duration != "") { SetKeyDelay(current_delay, current_key_duration) @@ -4940,7 +4937,7 @@ current_key_duration := Format("{}", A_KeyDurationPlay) if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, key_press_duration, Play) + SetKeyDelay(key_delay, key_press_duration, "Play") } SendPlay(str) @@ -4968,9 +4965,9 @@ {% block HideTrayTip %} TrayTip ; Attempt to hide it the normal way. if SubStr(A_OSVersion,1,3) = "10." { - Menu Tray, NoIcon + A_IconHidden := true Sleep 200 ; It may be necessary to adjust this sleep. - Menu Tray, Icon + A_IconHidden := false } {% endblock HideTrayTip %} } @@ -5255,7 +5252,7 @@ AHKWinFromMouse(command) { {% block AHKWinFromMouse %} - MouseGetPos(,, MouseWin) + MouseGetPos(,, &MouseWin) if (MouseWin = "") { return FormatNoValueResponse() @@ -5493,12 +5490,12 @@ AHKMenuTrayTip(command) { value := command[2] - Menu(Tray, Tip, value) + A_IconTip := value return FormatNoValueResponse() } AHKMenuTrayShow(command) { - Menu(Tray, Icon) + A_IconHidden := 0 return FormatNoValueResponse() } @@ -5506,17 +5503,17 @@ filename := command[2] icon_number := command[3] freeze := command[4] - Menu(Tray, Icon, filename, icon_number,freeze) + TraySetIcon(filename, icon_number, freeze) return FormatNoValueResponse() } -AHKGuiNew(command) { - - options := command[2] - title := command[3] - Gui(New, options, title) - return FormatResponse("ahk.message.StringResponseMessage", hwnd) -} +;AHKGuiNew(command) { +; +; options := command[2] +; title := command[3] +; Gui(New, options, title) +; return FormatResponse("ahk.message.StringResponseMessage", hwnd) +;} AHKMsgBox(command) { @@ -5681,36 +5678,36 @@ ; [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. return LC_Base64_Decode_Text(pszString) - 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, "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, 0) -; granted := VarSetStrCapacity(&ret, 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, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) - if (success=0) { - return "" - } - - return StrGet(ret, "UTF-8") +; 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, "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, 0) +;; granted := VarSetStrCapacity(&ret, 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, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) +; if (success=0) { +; return "" +; } +; +; return StrGet(ret, "UTF-8") } b64encode(data) { @@ -5722,30 +5719,30 @@ ; [in, out] DWORD *pcchString: A pointer to a DWORD variable that contains the size, in TCHARs, of the pszString buffer LC_Base64_Encode(&Base64_, &data, data.Size) return Base64_ - 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.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 Exception(msg, -1) - } - return ret +; 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.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 Exception(msg, -1) +; } +; return ret } CommandArrayFromQuery(text) { diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index c38dd3c7..3ecb9dae 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -1,8 +1,8 @@ {% block daemon_script %} {% block directives %} ;#NoEnv -;#Persistent #Requires Autohotkey >= 2.0- +Persistent #Warn All, Off #SingleInstance Off ; BEGIN user-defined directives @@ -896,14 +896,11 @@ AHKWinGetControlList(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - ahkid := WinGetID(title, text, extitle, extext) - - if (ahkid = "") { - return FormatNoValueResponse() - } - try { + ahkid := WinGetID(title, text, extitle, extext) + if (ahkid = "") { + return FormatNoValueResponse() + } ctrList := WinGetControls(title, text, extitle, extext) ctrListID := WinGetControlsHwnd(title, text, extitle, extext) } @@ -922,8 +919,8 @@ AHKWinGetControlList(command) { output := Format("('{}', [", ahkid) - for index, hwnd in ctrListIDArr { - classname := ctrListArr[index] + for index, hwnd in ctrListID { + classname := ctrList[index] output .= Format("('{}', '{}'), ", hwnd, classname) } @@ -1364,7 +1361,7 @@ AHKWinSetEnable(command) { DetectHiddenWindows(detect_hw) } try { - WinSetEnabled(title, text, extitle, extext) + WinSetEnabled(1, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) @@ -1400,7 +1397,7 @@ AHKWinSetDisable(command) { DetectHiddenWindows(detect_hw) } try { - WinSetDisabled(title, text, extitle, extext) + WinSetEnabled(0, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) @@ -1646,7 +1643,7 @@ AHKImageSearch(command) { current_mode := Format("{}", A_CoordModePixel) if (coord_mode != "") { - CoordMode(Pixel, coord_mode) + CoordMode("Pixel", coord_mode) } if (x2 = "A_ScreenWidth") { @@ -1664,7 +1661,7 @@ AHKImageSearch(command) { } finally { if (coord_mode != "") { - CoordMode(Pixel, current_mode) + CoordMode("Pixel", current_mode) } } @@ -1683,7 +1680,7 @@ AHKPixelGetColor(command) { current_mode := Format("{}", A_CoordModePixel) if (coord_mode != "") { - CoordMode(Pixel, coord_mode) + CoordMode("Pixel", coord_mode) } try { @@ -1691,7 +1688,7 @@ AHKPixelGetColor(command) { } finally { if (coord_mode != "") { - CoordMode(Pixel, current_mode) + CoordMode("Pixel", current_mode) } } @@ -1714,7 +1711,7 @@ AHKPixelSearch(command) { current_mode := Format("{}", A_CoordModePixel) if (coord_mode != "") { - CoordMode(Pixel, coord_mode) + CoordMode("Pixel", coord_mode) } try { if (PixelSearch(&resultx, &resulty, x1, y1, x2, y2, color, variation) = 1) { @@ -1726,7 +1723,7 @@ AHKPixelSearch(command) { } finally { if (coord_mode != "") { - CoordMode(Pixel, current_mode) + CoordMode("Pixel", current_mode) } } @@ -1741,7 +1738,7 @@ AHKMouseGetPos(command) { coord_mode := command[2] current_coord_mode := Format("{}", A_CoordModeMouse) if (coord_mode != "") { - CoordMode(Mouse, coord_mode) + CoordMode("Mouse", coord_mode) } MouseGetPos(&xpos, &ypos) @@ -1749,7 +1746,7 @@ AHKMouseGetPos(command) { resp := FormatResponse("ahk.message.CoordinateResponseMessage", payload) if (coord_mode != "") { - CoordMode(Mouse, current_coord_mode) + CoordMode("Mouse", current_coord_mode) } return resp @@ -1813,13 +1810,13 @@ AHKClick(command) { current_coord_rel := Format("{}", A_CoordModeMouse) if (relative_to != "") { - CoordMode(Mouse, relative_to) + CoordMode("Mouse", relative_to) } Click(x, y, button, direction, r) if (relative_to != "") { - CoordMode(Mouse, current_coord_rel) + CoordMode("Mouse", current_coord_rel) } return FormatNoValueResponse() @@ -1875,13 +1872,13 @@ AHKMouseClickDrag(command) { current_coord_rel := Format("{}", A_CoordModeMouse) if (relative_to != "") { - CoordMode(Mouse, relative_to) + CoordMode("Mouse", relative_to) } MouseClickDrag(button, x1, y1, x2, y2, speed, relative) if (relative_to != "") { - CoordMode(Mouse, current_coord_rel) + CoordMode("Mouse", current_coord_rel) } return FormatNoValueResponse() @@ -1986,7 +1983,7 @@ AHKSendRaw(command) { SetKeyDelay(key_delay, key_press_duration) } - SendRaw(str) + Send("{Raw}" str) if (key_delay != "" or key_press_duration != "") { SetKeyDelay(current_delay, current_key_duration) @@ -2046,7 +2043,7 @@ AHKSendPlay(command) { current_key_duration := Format("{}", A_KeyDurationPlay) if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, key_press_duration, Play) + SetKeyDelay(key_delay, key_press_duration, "Play") } SendPlay(str) @@ -2074,9 +2071,9 @@ HideTrayTip(command) { {% block HideTrayTip %} TrayTip ; Attempt to hide it the normal way. if SubStr(A_OSVersion,1,3) = "10." { - Menu Tray, NoIcon + A_IconHidden := true Sleep 200 ; It may be necessary to adjust this sleep. - Menu Tray, Icon + A_IconHidden := false } {% endblock HideTrayTip %} } @@ -2361,7 +2358,7 @@ AHKControlSend(command) { AHKWinFromMouse(command) { {% block AHKWinFromMouse %} - MouseGetPos(,, MouseWin) + MouseGetPos(,, &MouseWin) if (MouseWin = "") { return FormatNoValueResponse() @@ -2599,12 +2596,12 @@ AHKBlockInput(command) { AHKMenuTrayTip(command) { value := command[2] - Menu(Tray, Tip, value) + A_IconTip := value return FormatNoValueResponse() } AHKMenuTrayShow(command) { - Menu(Tray, Icon) + A_IconHidden := 0 return FormatNoValueResponse() } @@ -2612,17 +2609,17 @@ AHKMenuTrayIcon(command) { filename := command[2] icon_number := command[3] freeze := command[4] - Menu(Tray, Icon, filename, icon_number,freeze) + TraySetIcon(filename, icon_number, freeze) return FormatNoValueResponse() } -AHKGuiNew(command) { - - options := command[2] - title := command[3] - Gui(New, options, title) - return FormatResponse("ahk.message.StringResponseMessage", hwnd) -} +;AHKGuiNew(command) { +; +; options := command[2] +; title := command[3] +; Gui(New, options, title) +; return FormatResponse("ahk.message.StringResponseMessage", hwnd) +;} AHKMsgBox(command) { @@ -2787,36 +2784,36 @@ b64decode(pszString) { ; [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. return LC_Base64_Decode_Text(pszString) - 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, "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, 0) -; granted := VarSetStrCapacity(&ret, 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, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) - if (success=0) { - return "" - } - - return StrGet(ret, "UTF-8") +; 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, "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, 0) +;; granted := VarSetStrCapacity(&ret, 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, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) +; if (success=0) { +; return "" +; } +; +; return StrGet(ret, "UTF-8") } b64encode(data) { @@ -2828,30 +2825,30 @@ b64encode(data) { ; [in, out] DWORD *pcchString: A pointer to a DWORD variable that contains the size, in TCHARs, of the pszString buffer LC_Base64_Encode(&Base64_, &data, data.Size) return Base64_ - 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.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 Exception(msg, -1) - } - return ret +; 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.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 Exception(msg, -1) +; } +; return ret } CommandArrayFromQuery(text) { From a45e10d119c023d787978630b7c24c38e582f403 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 13:13:56 -0700 Subject: [PATCH 446/588] fix controlsend arg order, fix script tests for not found window --- ahk/_constants.py | 2 +- ahk/templates/daemon-v2.ahk | 2 +- tests/_async/test_scripts.py | 6 +++--- tests/_sync/test_scripts.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 505dc99f..c98988a1 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -5238,7 +5238,7 @@ } try { - ControlSend(ctrl, keys, title, text, extitle, extext) + ControlSend(keys, ctrl, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 3ecb9dae..bfa30b6b 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2344,7 +2344,7 @@ AHKControlSend(command) { } try { - ControlSend(ctrl, keys, title, text, extitle, extext) + ControlSend(keys, ctrl, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index 6afb46aa..7766c9c8 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -67,20 +67,20 @@ async def asyncSetUp(self) -> None: self.ahk = AsyncAHK(version='v2') async def test_run_script_text(self): - assert await self.ahk.win_get(title='Untitled - Notepad') is None + assert not await self.ahk.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 await self.ahk.win_get(title='Untitled - Notepad') is None + assert not await self.ahk.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 await self.ahk.win_get(title='Untitled - Notepad') is None + assert not await self.ahk.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: diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py index ae48e13e..09c87698 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -67,20 +67,20 @@ def setUp(self) -> None: self.ahk = AHK(version='v2') def test_run_script_text(self): - assert self.ahk.win_get(title='Untitled - Notepad') is None + assert not self.ahk.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 self.ahk.win_get(title='Untitled - Notepad') is None + assert not self.ahk.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 self.ahk.win_get(title='Untitled - Notepad') is None + assert not self.ahk.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: From 41d4e2f046b030864bb4cfe4aa2c02325e3d3fad Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 15:30:17 -0700 Subject: [PATCH 447/588] v2 window functionality --- ahk/_async/engine.py | 2 +- ahk/_async/window.py | 15 +++++++++------ ahk/_constants.py | 38 +++++++++++++++++++++++++++---------- ahk/_sync/engine.py | 2 +- ahk/_sync/window.py | 25 +++++++++++++++--------- ahk/templates/daemon-v2.ahk | 38 +++++++++++++++++++++++++++---------- tests/_async/test_window.py | 14 +++++++++++--- tests/_sync/test_window.py | 14 +++++++++++--- 8 files changed, 105 insertions(+), 43 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 6f349278..fb585982 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1204,7 +1204,7 @@ async def send_input(self, s: str, *, blocking: bool = True) -> Union[None, Asyn """ Analog for `SendInput `_ """ - args = [s] + args = [s, '', ''] resp = await self._transport.function_call('AHKSendInput', args, blocking=blocking) return resp diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 5de120ad..08229b0b 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -54,7 +54,7 @@ def __init__(self, engine: AsyncAHK, ahk_id: str): self._ahk_id: str = ahk_id def __repr__(self) -> str: - return f'<{self.__class__.__qualname__} ahk_id={self._ahk_id}>' + return f'<{self.__class__.__qualname__} ahk_id={self._ahk_id!r}>' def __eq__(self, other: object) -> bool: if not isinstance(other, AsyncWindow): @@ -302,17 +302,20 @@ def always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> Any # fmt: off @overload - async def send(self, keys: str) -> None: ... + async def send(self, keys: str, control: str = '') -> None: ... @overload - async def send(self, keys: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def send(self, keys: str, control: str = '', *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... @overload - async def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + async def send(self, keys: str, control: str = '', *, blocking: Literal[True]) -> None: ... @overload - async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + async def send(self, keys: str, control: str = '', *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... # fmt: on - async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + 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, diff --git a/ahk/_constants.py b/ahk/_constants.py index c98988a1..0bd28f19 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -3056,13 +3056,18 @@ if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinClose(title, text, secondstowait, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + 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 %} } @@ -4039,7 +4044,7 @@ DetectHiddenWindows(detect_hw) } try { - WinSetTitle(title, text, new_title, extitle, extext) + WinSetTitle(new_title, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) @@ -4074,6 +4079,15 @@ 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) } @@ -5238,7 +5252,11 @@ } try { - ControlSend(keys, ctrl, title, text, extitle, extext) + if (ctrl != "") { + ControlSendText(keys, ctrl, title, text, extitle, extext) + } else { + ControlSendText(keys,, title, text, extitle, extext) + } } finally { DetectHiddenWindows(current_detect_hw) @@ -5306,7 +5324,7 @@ } try { - WinMove(title, text, x, y, width, height, extitle, extext) + WinMove(x, y, width, height, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 4858e43a..c0b464d1 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1192,7 +1192,7 @@ def send_input(self, s: str, *, blocking: bool = True) -> Union[None, FutureResu """ Analog for `SendInput `_ """ - args = [s] + args = [s, '', ''] resp = self._transport.function_call('AHKSendInput', args, blocking=blocking) return resp diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 30d0a8b1..baaed410 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -50,7 +50,7 @@ def __init__(self, engine: AHK, ahk_id: str): self._ahk_id: str = ahk_id def __repr__(self) -> str: - return f'<{self.__class__.__qualname__} ahk_id={self._ahk_id}>' + return f'<{self.__class__.__qualname__} ahk_id={self._ahk_id!r}>' def __eq__(self, other: object) -> bool: if not isinstance(other, Window): @@ -61,11 +61,15 @@ def __hash__(self) -> int: return hash(self._ahk_id) def close(self) -> None: - self._engine.win_close(title=f'ahk_id {self._ahk_id}', detect_hidden_windows=True, title_match_mode=(1, 'Fast')) + 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')) + 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( @@ -277,17 +281,18 @@ def always_on_top(self, toggle: Literal['On', 'Off', 'Toggle', 1, -1, 0]) -> Any # fmt: off @overload - def send(self, keys: str) -> None: ... + def send(self, keys: str, control: str = '') -> None: ... @overload - def send(self, keys: str, *, blocking: Literal[False]) -> FutureResult[None]: ... + def send(self, keys: str, control: str = '', *, blocking: Literal[False]) -> FutureResult[None]: ... @overload - def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + def send(self, keys: str, control: str = '', *, blocking: Literal[True]) -> None: ... @overload - def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + def send(self, keys: str, control: str = '', *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... # fmt: on - def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + 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, @@ -587,7 +592,9 @@ def set_transparent( blocking=blocking, ) - def set_trans_color(self, color: Union[int, str], *, blocking: bool = True) -> Union[None, FutureResult[None]]: + 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}', diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index bfa30b6b..09a91288 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -162,13 +162,18 @@ AHKWinClose(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinClose(title, text, secondstowait, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + 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 %} } @@ -1145,7 +1150,7 @@ AHKWinSetTitle(command) { DetectHiddenWindows(detect_hw) } try { - WinSetTitle(title, text, new_title, extitle, extext) + WinSetTitle(new_title, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) @@ -1180,6 +1185,15 @@ AHKWinSetAlwaysOnTop(command) { 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) } @@ -2344,7 +2358,11 @@ AHKControlSend(command) { } try { - ControlSend(keys, ctrl, title, text, extitle, extext) + if (ctrl != "") { + ControlSendText(keys, ctrl, title, text, extitle, extext) + } else { + ControlSendText(keys,, title, text, extitle, extext) + } } finally { DetectHiddenWindows(current_detect_hw) @@ -2412,7 +2430,7 @@ AHKWinMove(command) { } try { - WinMove(title, text, x, y, width, height, extitle, extext) + WinMove(x, y, width, height, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 08654439..d5ab5d4f 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -6,6 +6,10 @@ import tracemalloc from unittest import IsolatedAsyncioTestCase +import pytest + +import ahk + tracemalloc.start() from ahk import AsyncAHK @@ -113,12 +117,12 @@ async def test_win_set_title(self): assert await self.win.get_title() == 'Foo' async def test_control_send_window(self): - await self.win.send('hello world') + 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') + await self.win.send('hello, world', control='Edit1') text = await self.win.get_text() assert 'hello, world' in text @@ -131,7 +135,7 @@ async def test_type_escape(self): async def test_send_literal_tilde_n(self): expected_text = '```nim\nimport std/strformat\n```' - await self.win.send(expected_text) + await self.win.send(expected_text, control='Edit1') text = await self.win.get_text() assert '```nim' in text assert '\nimport std/strformat' in text @@ -197,3 +201,7 @@ async def asyncSetUp(self) -> None: 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/test_window.py b/tests/_sync/test_window.py index 55c63b16..66ea08a4 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -6,6 +6,10 @@ import tracemalloc from unittest import TestCase +import pytest + +import ahk + tracemalloc.start() from ahk import AHK @@ -113,12 +117,12 @@ def test_win_set_title(self): assert self.win.get_title() == 'Foo' def test_control_send_window(self): - self.win.send('hello world') + 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') + self.win.send('hello, world', control='Edit1') text = self.win.get_text() assert 'hello, world' in text @@ -131,7 +135,7 @@ def test_type_escape(self): def test_send_literal_tilde_n(self): expected_text = '```nim\nimport std/strformat\n```' - self.win.send(expected_text) + self.win.send(expected_text, control='Edit1') text = self.win.get_text() assert '```nim' in text assert '\nimport std/strformat' in text @@ -197,3 +201,7 @@ def setUp(self) -> None: 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') From 5def14e348b34f7824089a4c4cbccce8af3e0a59 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 16:20:09 -0700 Subject: [PATCH 448/588] v2 hotkeys --- _set_constants.py | 6 ++ ahk/_async/transport.py | 4 +- ahk/_constants.py | 179 ++++++++++++++++++++++++++++++++++- ahk/_hotkey.py | 30 +++++- ahk/_sync/transport.py | 7 +- ahk/templates/daemon-v2.ahk | 4 +- ahk/templates/hotkeys-v2.ahk | 169 +++++++++++++++++++++++++++++++++ ahk/templates/hotkeys.ahk | 1 + tests/_async/test_keys.py | 4 +- tests/_async/test_scripts.py | 6 +- tests/_sync/test_keys.py | 4 +- tests/_sync/test_scripts.py | 6 +- 12 files changed, 397 insertions(+), 23 deletions(-) create mode 100644 ahk/templates/hotkeys-v2.ahk diff --git a/_set_constants.py b/_set_constants.py index 50c54712..a44a283b 100644 --- a/_set_constants.py +++ b/_set_constants.py @@ -11,6 +11,9 @@ 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: @@ -28,6 +31,9 @@ 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: diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 68f0233b..2483066e 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -357,7 +357,9 @@ def __init__( **kwargs: Any, ): self._executable_path: str = _resolve_executable_path(executable_path=executable_path, version=version) - self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path, directives=directives) + self._hotkey_transport = ThreadedHotkeyTransport( + executable_path=self._executable_path, directives=directives, version=version + ) self._directives: list[Union[Directive, Type[Directive]]] = directives or [] self._version: Literal['v1', 'v2'] = version or 'v1' diff --git a/ahk/_constants.py b/ahk/_constants.py index 0bd28f19..1da55998 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2791,7 +2791,8 @@ """ -HOTKEYS_SCRIPT_TEMPLATE = r"""#Persistent +HOTKEYS_SCRIPT_TEMPLATE = r"""#Requires AutoHotkey v1.1.17+ +#Persistent {% for directive in directives %} {% if directive.apply_to_hotkeys_process %} @@ -4814,9 +4815,9 @@ value := command[5] ; RegWrite(value_type, key_name, value_name, value) if (value_name != "") { - RegWrite(value, value_type, key_name) - } else { RegWrite(value, value_type, key_name, value_name) + } else { + RegWrite(value, value_type, key_name) } return FormatNoValueResponse() {% endblock RegWrite %} @@ -5822,3 +5823,175 @@ {% 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 %} + +{% if on_clipboard %} +OnClipboardChange("ClipChanged") +{% endif %} +KEEPALIVE := Chr(57344) +;SetTimer, keepalive, 1000 + +stdout := FileOpen("*", "w", "UTF-8") + +WriteStdout(s) { + global stdout + Critical "On" + stdout.Write(s) + stdout.Read(0) + Critical "Off" +} + +; LC_* functions are substantially from Libcrypt +; Modified from https://github.com/ahkscript/libcrypt.ahk +; Ref: https://www.autohotkey.com/boards/viewtopic.php?t=112821 +; Original License: +; The MIT License (MIT) +; +; Copyright (c) 2014 The ahkscript community (ahkscript.org) +; +; Permission is hereby granted, free of charge, to any person obtaining a copy +; of this software and associated documentation files (the "Software"), to deal +; in the Software without restriction, including without limitation the rights +; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the Software is +; furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included in all +; copies or substantial portions of the Software. + + +LC_Base64_Encode_Text(Text_, Encoding_ := "UTF-8") +{ + Bin_ := Buffer(StrPut(Text_, Encoding_)) + LC_Base64_Encode(&Base64_, &Bin_, StrPut(Text_, Bin_, Encoding_) - 1) + return Base64_ +} + +LC_Base64_Decode_Text(Text_, Encoding_ := "UTF-8") +{ + Len_ := LC_Base64_Decode(&Bin_, &Text_) + return StrGet(StrPtr(Bin_), Len_, Encoding_) +} + +LC_Base64_Encode(&Out_, &In_, In_Len) +{ + return LC_Bin2Str(&Out_, &In_, In_Len, 0x40000001) +} + +LC_Base64_Decode(&Out_, &In_) +{ + return LC_Str2Bin(&Out_, &In_, 0x1) +} + +LC_Bin2Str(&Out_, &In_, In_Len, Flags_) +{ + DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0) + VarSetStrCapacity(&Out_, Out_Len * 2) + DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len) + return Out_Len +} + +LC_Str2Bin(&Out_, &In_, Flags_) +{ + DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0, "Ptr", 0, "Ptr", 0) + VarSetStrCapacity(&Out_, Out_Len) + DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len, "Ptr", 0, "Ptr", 0) + return Out_Len +} +; End of libcrypt code + +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. + return LC_Base64_Decode_Text(pszString) +; 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, "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, 0) +;; granted := VarSetStrCapacity(&ret, 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, "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 +} +{% endif %} + + +;keepalive: +;global KEEPALIVE +;FileAppend, %KEEPALIVE%`n, *, UTF-8 + +""" diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index f36ebab9..a3058bf4 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -16,6 +16,7 @@ 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 @@ -38,7 +39,7 @@ from queue import Queue from ahk._utils import hotkey_escape -from ._constants import HOTKEYS_SCRIPT_TEMPLATE as _HOTKEY_SCRIPT +from ._constants import HOTKEYS_SCRIPT_TEMPLATE as _HOTKEY_SCRIPT, HOTKEYS_SCRIPT_V2_TEMPLATE as _HOTKEY_V2_SCRIPT P_HotkeyCallbackParam = ParamSpec('P_HotkeyCallbackParam') T_HotkeyCallbackReturn = TypeVar('T_HotkeyCallbackReturn') @@ -64,7 +65,9 @@ def __init__( 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 @@ -165,14 +168,30 @@ def __init__( 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) + 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: @@ -185,10 +204,10 @@ def __init__( self._jinja_env: jinja2.Environment = jinja2.Environment(loader=loader, autoescape=False) self._template: jinja2.Template try: - self._template = self._jinja_env.get_template('hotkeys.ahk') + 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(_HOTKEY_SCRIPT) + self._template = self._jinja_env.from_string(const_script) def _do_callback( self, @@ -314,12 +333,13 @@ def listener(self) -> None: [self._executable_path, '/CP65001', '/ErrorStdOut', exc_file], stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=subprocess.STDOUT, ) atexit.register(kill, self._proc) while self._running: assert self._proc.stdout is not None line = self._proc.stdout.readline() + print(line, file=sys.stderr) if line.rstrip(b'\n') == _KEEPALIVE_SENTINEL: logging.debug('keepalive received') continue diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index ef7c74ba..caaf58cf 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -44,7 +44,10 @@ from ahk.message import ResponseMessage from ahk.message import Position from ahk.message import _message_registry -from ahk._constants import DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE, DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE +from ahk._constants import ( + DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE, + DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE, +) from ahk.directives import Directive from concurrent.futures import Future, ThreadPoolExecutor @@ -335,7 +338,7 @@ def __init__( **kwargs: Any, ): self._executable_path: str = _resolve_executable_path(executable_path=executable_path, version=version) - self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path, directives=directives) + self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path, directives=directives, version=version) self._directives: list[Union[Directive, Type[Directive]]] = directives or [] self._version: Literal['v1', 'v2'] = version or 'v1' diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 09a91288..98bb4848 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -1920,9 +1920,9 @@ AHKRegWrite(command) { value := command[5] ; RegWrite(value_type, key_name, value_name, value) if (value_name != "") { - RegWrite(value, value_type, key_name) - } else { RegWrite(value, value_type, key_name, value_name) + } else { + RegWrite(value, value_type, key_name) } return FormatNoValueResponse() {% endblock RegWrite %} diff --git a/ahk/templates/hotkeys-v2.ahk b/ahk/templates/hotkeys-v2.ahk new file mode 100644 index 00000000..0619dee3 --- /dev/null +++ b/ahk/templates/hotkeys-v2.ahk @@ -0,0 +1,169 @@ +#Requires AutoHotkey >= 2.0- +{% for directive in directives %} +{% if directive.apply_to_hotkeys_process %} + +{{ directive }} +{% endif %} +{% endfor %} + +{% if on_clipboard %} +OnClipboardChange("ClipChanged") +{% endif %} +KEEPALIVE := Chr(57344) +;SetTimer, keepalive, 1000 + +stdout := FileOpen("*", "w", "UTF-8") + +WriteStdout(s) { + global stdout + Critical "On" + stdout.Write(s) + stdout.Read(0) + Critical "Off" +} + +; LC_* functions are substantially from Libcrypt +; Modified from https://github.com/ahkscript/libcrypt.ahk +; Ref: https://www.autohotkey.com/boards/viewtopic.php?t=112821 +; Original License: +; The MIT License (MIT) +; +; Copyright (c) 2014 The ahkscript community (ahkscript.org) +; +; Permission is hereby granted, free of charge, to any person obtaining a copy +; of this software and associated documentation files (the "Software"), to deal +; in the Software without restriction, including without limitation the rights +; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the Software is +; furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included in all +; copies or substantial portions of the Software. + + +LC_Base64_Encode_Text(Text_, Encoding_ := "UTF-8") +{ + Bin_ := Buffer(StrPut(Text_, Encoding_)) + LC_Base64_Encode(&Base64_, &Bin_, StrPut(Text_, Bin_, Encoding_) - 1) + return Base64_ +} + +LC_Base64_Decode_Text(Text_, Encoding_ := "UTF-8") +{ + Len_ := LC_Base64_Decode(&Bin_, &Text_) + return StrGet(StrPtr(Bin_), Len_, Encoding_) +} + +LC_Base64_Encode(&Out_, &In_, In_Len) +{ + return LC_Bin2Str(&Out_, &In_, In_Len, 0x40000001) +} + +LC_Base64_Decode(&Out_, &In_) +{ + return LC_Str2Bin(&Out_, &In_, 0x1) +} + +LC_Bin2Str(&Out_, &In_, In_Len, Flags_) +{ + DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0) + VarSetStrCapacity(&Out_, Out_Len * 2) + DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len) + return Out_Len +} + +LC_Str2Bin(&Out_, &In_, Flags_) +{ + DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0, "Ptr", 0, "Ptr", 0) + VarSetStrCapacity(&Out_, Out_Len) + DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len, "Ptr", 0, "Ptr", 0) + return Out_Len +} +; End of libcrypt code + +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. + return LC_Base64_Decode_Text(pszString) +; 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, "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, 0) +;; granted := VarSetStrCapacity(&ret, 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, "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 +} +{% endif %} + + +;keepalive: +;global KEEPALIVE +;FileAppend, %KEEPALIVE%`n, *, UTF-8 diff --git a/ahk/templates/hotkeys.ahk b/ahk/templates/hotkeys.ahk index d40361d7..08571340 100644 --- a/ahk/templates/hotkeys.ahk +++ b/ahk/templates/hotkeys.ahk @@ -1,3 +1,4 @@ +#Requires AutoHotkey v1.1.17+ #Persistent {% for directive in directives %} {% if directive.apply_to_hotkeys_process %} diff --git a/tests/_async/test_keys.py b/tests/_async/test_keys.py index d06309c5..a5116c59 100644 --- a/tests/_async/test_keys.py +++ b/tests/_async/test_keys.py @@ -13,7 +13,7 @@ sleep = time.sleep -class TestWindowAsync(unittest.IsolatedAsyncioTestCase): +class TestKeysAsync(unittest.IsolatedAsyncioTestCase): win: AsyncWindow async def asyncSetUp(self) -> None: @@ -79,7 +79,7 @@ async def test_hotstring_callback(self): m.assert_called() -class TestWindowAsyncV2(TestWindowAsync): +class TestKeysAsyncV2(TestKeysAsync): async def asyncSetUp(self) -> None: self.ahk = AsyncAHK(version='v2') self.p = subprocess.Popen('notepad') diff --git a/tests/_async/test_scripts.py b/tests/_async/test_scripts.py index 7766c9c8..00190061 100644 --- a/tests/_async/test_scripts.py +++ b/tests/_async/test_scripts.py @@ -67,20 +67,20 @@ async def asyncSetUp(self) -> None: self.ahk = AsyncAHK(version='v2') async def test_run_script_text(self): - assert not await self.ahk.exists(title='Untitled - Notepad') + 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.exists(title='Untitled - Notepad') + 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.exists(title='Untitled - Notepad') + 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: diff --git a/tests/_sync/test_keys.py b/tests/_sync/test_keys.py index 4a18cdfe..9f682393 100644 --- a/tests/_sync/test_keys.py +++ b/tests/_sync/test_keys.py @@ -12,7 +12,7 @@ sleep = time.sleep -class TestWindowAsync(unittest.TestCase): +class TestKeysAsync(unittest.TestCase): win: Window def setUp(self) -> None: @@ -78,7 +78,7 @@ def test_hotstring_callback(self): m.assert_called() -class TestWindowAsyncV2(TestWindowAsync): +class TestKeysAsyncV2(TestKeysAsync): def setUp(self) -> None: self.ahk = AHK(version='v2') self.p = subprocess.Popen('notepad') diff --git a/tests/_sync/test_scripts.py b/tests/_sync/test_scripts.py index 09c87698..2bfec2f9 100644 --- a/tests/_sync/test_scripts.py +++ b/tests/_sync/test_scripts.py @@ -67,20 +67,20 @@ def setUp(self) -> None: self.ahk = AHK(version='v2') def test_run_script_text(self): - assert not self.ahk.exists(title='Untitled - Notepad') + 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.exists(title='Untitled - Notepad') + 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.exists(title='Untitled - Notepad') + 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: From 19500a92718701c460a4fd50c139a5808fc7ef5a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 16:39:40 -0700 Subject: [PATCH 449/588] remove debug print --- ahk/_hotkey.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index a3058bf4..07e2c16f 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -339,7 +339,6 @@ def listener(self) -> None: while self._running: assert self._proc.stdout is not None line = self._proc.stdout.readline() - print(line, file=sys.stderr) if line.rstrip(b'\n') == _KEEPALIVE_SENTINEL: logging.debug('keepalive received') continue From 9170f9ec015510c5dcc508036160e881d9feac40 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 21:54:38 -0700 Subject: [PATCH 450/588] fix base64 encoding ; versioned extensions --- ahk/_async/engine.py | 4 +- ahk/_constants.py | 212 +++++++++++++---------------------- ahk/_sync/engine.py | 4 +- ahk/extensions.py | 2 + ahk/templates/daemon-v2.ahk | 205 +++++++++++++-------------------- ahk/templates/hotkeys-v2.ahk | 7 +- tests/_async/test_screen.py | 7 +- tests/_sync/test_screen.py | 7 +- 8 files changed, 174 insertions(+), 274 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index fb585982..8ec6b8c9 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -148,7 +148,7 @@ def __init__( self._extension_registry: _ExtensionMethodRegistry self._extensions: list[Extension] if extensions == 'auto': - self._extensions = list(_extension_registry) + self._extensions = [ext for ext in _extension_registry if ext._requires in (None, version)] else: self._extensions = _resolve_extensions(extensions) if extensions else [] self._method_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) @@ -2785,6 +2785,8 @@ async def image_search( 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 diff --git a/ahk/_constants.py b/ahk/_constants.py index 1da55998..4d7abcb8 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2898,7 +2898,7 @@ ;#NoEnv #Requires Autohotkey >= 2.0- Persistent -#Warn All, Off +;#Warn All, Off #SingleInstance Off ; BEGIN user-defined directives {% block user_directives %} @@ -3097,13 +3097,18 @@ if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinKill(title, text, secondstowait, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + 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 %} } @@ -4563,10 +4568,11 @@ } try { - if ImageSearch(&xpos, &ypos, x1, y1, x2, y2, imagepath) + if (ImageSearch(&xpos, &ypos, x1, y1, x2, y2, imagepath) = 1) { s := FormatResponse("ahk.message.CoordinateResponseMessage", Format("({}, {})", xpos, ypos)) - else + } else { s := FormatNoValueResponse() + } } finally { if (coord_mode != "") { @@ -5467,7 +5473,7 @@ AHKGetClipboardAll(command) { {% block AHKGetClipboardAll %} data := ClipboardAll() - return FormatBinaryResponse(data) + return FormatBinaryResponse(&data) {% endblock AHKGetClipboardAll %} } @@ -5484,7 +5490,7 @@ ; TODO there should be a way for us to accept a base64 string instead filename := command[2] contents := FileRead(filename, "RAW") - ClipboardAll(contents) + A_Clipboard := ClipboardAll(contents) return FormatNoValueResponse() {% endblock AHKSetClipboardAll %} } @@ -5627,66 +5633,7 @@ return ret } -; LC_* functions are substantially from Libcrypt -; Modified from https://github.com/ahkscript/libcrypt.ahk -; Ref: https://www.autohotkey.com/boards/viewtopic.php?t=112821 -; Original License: -; The MIT License (MIT) -; -; Copyright (c) 2014 The ahkscript community (ahkscript.org) -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in all -; copies or substantial portions of the Software. - - -LC_Base64_Encode_Text(Text_, Encoding_ := "UTF-8") -{ - Bin_ := Buffer(StrPut(Text_, Encoding_)) - LC_Base64_Encode(&Base64_, &Bin_, StrPut(Text_, Bin_, Encoding_) - 1) - return Base64_ -} - -LC_Base64_Decode_Text(Text_, Encoding_ := "UTF-8") -{ - Len_ := LC_Base64_Decode(&Bin_, &Text_) - return StrGet(StrPtr(Bin_), Len_, Encoding_) -} - -LC_Base64_Encode(&Out_, &In_, In_Len) -{ - return LC_Bin2Str(&Out_, &In_, In_Len, 0x40000001) -} - -LC_Base64_Decode(&Out_, &In_) -{ - return LC_Str2Bin(&Out_, &In_, 0x1) -} - -LC_Bin2Str(&Out_, &In_, In_Len, Flags_) -{ - DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0) - VarSetStrCapacity(&Out_, Out_Len * 2) - DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len) - return Out_Len -} - -LC_Str2Bin(&Out_, &In_, Flags_) -{ - DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0, "Ptr", 0, "Ptr", 0) - VarSetStrCapacity(&Out_, Out_Len) - DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len, "Ptr", 0, "Ptr", 0) - return Out_Len -} -; End of libcrypt code - -b64decode(pszString) { +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. @@ -5696,74 +5643,72 @@ ; [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. - return LC_Base64_Decode_Text(pszString) -; 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) + + 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, "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, 0) -;; granted := VarSetStrCapacity(&ret, 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, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) -; if (success=0) { -; return "" -; } -; -; return StrGet(ret, "UTF-8") + 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) { + +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 - LC_Base64_Encode(&Base64_, &data, data.Size) - return Base64_ -; 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.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 Exception(msg, -1) -; } -; return ret + + 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, "|") @@ -5771,7 +5716,7 @@ encoded_array.RemoveAt(1) decoded_commands.push(function_name) for index, encoded_value in encoded_array { - decoded_value := b64decode(encoded_value) + decoded_value := b64decode(&encoded_value) decoded_commands.push(decoded_value) } return decoded_commands @@ -5832,9 +5777,7 @@ {% endif %} {% endfor %} -{% if on_clipboard %} -OnClipboardChange("ClipChanged") -{% endif %} + KEEPALIVE := Chr(57344) ;SetTimer, keepalive, 1000 @@ -5987,6 +5930,9 @@ WriteStdout(ret) return } + +OnClipboardChange(ClipChanged) + {% endif %} diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index c0b464d1..1a0246e0 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -144,7 +144,7 @@ def __init__( self._extension_registry: _ExtensionMethodRegistry self._extensions: list[Extension] if extensions == 'auto': - self._extensions = list(_extension_registry) + self._extensions = [ext for ext in _extension_registry if ext._requires in (None, version)] else: self._extensions = _resolve_extensions(extensions) if extensions else [] self._method_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) @@ -2773,6 +2773,8 @@ def image_search( if coord_mode is not None: args.append(coord_mode) + else: + args.append('') resp = self._transport.function_call('AHKImageSearch', args, blocking=blocking) return resp diff --git a/ahk/extensions.py b/ahk/extensions.py index ee71c9d6..88b387da 100644 --- a/ahk/extensions.py +++ b/ahk/extensions.py @@ -81,7 +81,9 @@ def __init__( 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 [] diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 98bb4848..2e41cd8e 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -3,7 +3,7 @@ ;#NoEnv #Requires Autohotkey >= 2.0- Persistent -#Warn All, Off +;#Warn All, Off #SingleInstance Off ; BEGIN user-defined directives {% block user_directives %} @@ -202,13 +202,18 @@ AHKWinKill(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinKill(title, text, secondstowait, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + 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 %} } @@ -1668,10 +1673,11 @@ AHKImageSearch(command) { } try { - if ImageSearch(&xpos, &ypos, x1, y1, x2, y2, imagepath) + if (ImageSearch(&xpos, &ypos, x1, y1, x2, y2, imagepath) = 1) { s := FormatResponse("ahk.message.CoordinateResponseMessage", Format("({}, {})", xpos, ypos)) - else + } else { s := FormatNoValueResponse() + } } finally { if (coord_mode != "") { @@ -2572,7 +2578,7 @@ AHKGetClipboard(command) { AHKGetClipboardAll(command) { {% block AHKGetClipboardAll %} data := ClipboardAll() - return FormatBinaryResponse(data) + return FormatBinaryResponse(&data) {% endblock AHKGetClipboardAll %} } @@ -2589,7 +2595,7 @@ AHKSetClipboardAll(command) { ; TODO there should be a way for us to accept a base64 string instead filename := command[2] contents := FileRead(filename, "RAW") - ClipboardAll(contents) + A_Clipboard := ClipboardAll(contents) return FormatNoValueResponse() {% endblock AHKSetClipboardAll %} } @@ -2732,66 +2738,7 @@ AHKFileSelectFolder(command) { return ret } -; LC_* functions are substantially from Libcrypt -; Modified from https://github.com/ahkscript/libcrypt.ahk -; Ref: https://www.autohotkey.com/boards/viewtopic.php?t=112821 -; Original License: -; The MIT License (MIT) -; -; Copyright (c) 2014 The ahkscript community (ahkscript.org) -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in all -; copies or substantial portions of the Software. - - -LC_Base64_Encode_Text(Text_, Encoding_ := "UTF-8") -{ - Bin_ := Buffer(StrPut(Text_, Encoding_)) - LC_Base64_Encode(&Base64_, &Bin_, StrPut(Text_, Bin_, Encoding_) - 1) - return Base64_ -} - -LC_Base64_Decode_Text(Text_, Encoding_ := "UTF-8") -{ - Len_ := LC_Base64_Decode(&Bin_, &Text_) - return StrGet(StrPtr(Bin_), Len_, Encoding_) -} - -LC_Base64_Encode(&Out_, &In_, In_Len) -{ - return LC_Bin2Str(&Out_, &In_, In_Len, 0x40000001) -} - -LC_Base64_Decode(&Out_, &In_) -{ - return LC_Str2Bin(&Out_, &In_, 0x1) -} - -LC_Bin2Str(&Out_, &In_, In_Len, Flags_) -{ - DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0) - VarSetStrCapacity(&Out_, Out_Len * 2) - DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len) - return Out_Len -} - -LC_Str2Bin(&Out_, &In_, Flags_) -{ - DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0, "Ptr", 0, "Ptr", 0) - VarSetStrCapacity(&Out_, Out_Len) - DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len, "Ptr", 0, "Ptr", 0) - return Out_Len -} -; End of libcrypt code - -b64decode(pszString) { +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. @@ -2801,74 +2748,72 @@ b64decode(pszString) { ; [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. - return LC_Base64_Decode_Text(pszString) -; 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) + + 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, "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, 0) -;; granted := VarSetStrCapacity(&ret, 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, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) -; if (success=0) { -; return "" -; } -; -; return StrGet(ret, "UTF-8") + 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) { + +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 - LC_Base64_Encode(&Base64_, &data, data.Size) - return Base64_ -; 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.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 Exception(msg, -1) -; } -; return ret + + 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, "|") @@ -2876,7 +2821,7 @@ CommandArrayFromQuery(text) { encoded_array.RemoveAt(1) decoded_commands.push(function_name) for index, encoded_value in encoded_array { - decoded_value := b64decode(encoded_value) + decoded_value := b64decode(&encoded_value) decoded_commands.push(decoded_value) } return decoded_commands diff --git a/ahk/templates/hotkeys-v2.ahk b/ahk/templates/hotkeys-v2.ahk index 0619dee3..9ce36776 100644 --- a/ahk/templates/hotkeys-v2.ahk +++ b/ahk/templates/hotkeys-v2.ahk @@ -6,9 +6,7 @@ {% endif %} {% endfor %} -{% if on_clipboard %} -OnClipboardChange("ClipChanged") -{% endif %} + KEEPALIVE := Chr(57344) ;SetTimer, keepalive, 1000 @@ -161,6 +159,9 @@ ClipChanged(Type) { WriteStdout(ret) return } + +OnClipboardChange(ClipChanged) + {% endif %} diff --git a/tests/_async/test_screen.py b/tests/_async/test_screen.py index 5efc4089..5cf283ca 100644 --- a/tests/_async/test_screen.py +++ b/tests/_async/test_screen.py @@ -1,5 +1,6 @@ import asyncio import os +import pathlib import threading import time from itertools import product @@ -43,7 +44,7 @@ async def test_image_search(self): self._show_in_thread() time.sleep(3) self.im.save('testimage.png') - position = await self.ahk.image_search('testimage.png') + position = await self.ahk.image_search(str(pathlib.Path('testimage.png').absolute())) assert isinstance(position, tuple) async def test_pixel_search(self): @@ -53,7 +54,7 @@ async def test_pixel_search(self): self._show_in_thread() time.sleep(3) self.im.save('testimage.png') - position = await self.ahk.image_search('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) @@ -71,7 +72,7 @@ async def test_image_search_with_option(self): self._show_in_thread() time.sleep(3) self.im.save('testimage.png') - position = await self.ahk.image_search('testimage.png', color_variation=50) + 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): diff --git a/tests/_sync/test_screen.py b/tests/_sync/test_screen.py index fb2164ec..45e4d5f0 100644 --- a/tests/_sync/test_screen.py +++ b/tests/_sync/test_screen.py @@ -1,5 +1,6 @@ import asyncio import os +import pathlib import threading import time from itertools import product @@ -43,7 +44,7 @@ def test_image_search(self): self._show_in_thread() time.sleep(3) self.im.save('testimage.png') - position = self.ahk.image_search('testimage.png') + position = self.ahk.image_search(str(pathlib.Path('testimage.png').absolute())) assert isinstance(position, tuple) def test_pixel_search(self): @@ -53,7 +54,7 @@ def test_pixel_search(self): self._show_in_thread() time.sleep(3) self.im.save('testimage.png') - position = self.ahk.image_search('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) @@ -71,7 +72,7 @@ def test_image_search_with_option(self): self._show_in_thread() time.sleep(3) self.im.save('testimage.png') - position = self.ahk.image_search('testimage.png', color_variation=50) + 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): From 39669b74818b4c7fec63684a0f302537c0c6f48c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 19 Sep 2023 23:47:42 -0700 Subject: [PATCH 451/588] fix base64 decoding for hotkey scripts --- ahk/templates/hotkeys-v2.ahk | 121 +++++++++-------------------------- 1 file changed, 31 insertions(+), 90 deletions(-) diff --git a/ahk/templates/hotkeys-v2.ahk b/ahk/templates/hotkeys-v2.ahk index 9ce36776..e13e7461 100644 --- a/ahk/templates/hotkeys-v2.ahk +++ b/ahk/templates/hotkeys-v2.ahk @@ -20,66 +20,8 @@ WriteStdout(s) { Critical "Off" } -; LC_* functions are substantially from Libcrypt -; Modified from https://github.com/ahkscript/libcrypt.ahk -; Ref: https://www.autohotkey.com/boards/viewtopic.php?t=112821 -; Original License: -; The MIT License (MIT) -; -; Copyright (c) 2014 The ahkscript community (ahkscript.org) -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in all -; copies or substantial portions of the Software. - - -LC_Base64_Encode_Text(Text_, Encoding_ := "UTF-8") -{ - Bin_ := Buffer(StrPut(Text_, Encoding_)) - LC_Base64_Encode(&Base64_, &Bin_, StrPut(Text_, Bin_, Encoding_) - 1) - return Base64_ -} - -LC_Base64_Decode_Text(Text_, Encoding_ := "UTF-8") -{ - Len_ := LC_Base64_Decode(&Bin_, &Text_) - return StrGet(StrPtr(Bin_), Len_, Encoding_) -} - -LC_Base64_Encode(&Out_, &In_, In_Len) -{ - return LC_Bin2Str(&Out_, &In_, In_Len, 0x40000001) -} - -LC_Base64_Decode(&Out_, &In_) -{ - return LC_Str2Bin(&Out_, &In_, 0x1) -} - -LC_Bin2Str(&Out_, &In_, In_Len, Flags_) -{ - DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0) - VarSetStrCapacity(&Out_, Out_Len * 2) - DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len) - return Out_Len -} -LC_Str2Bin(&Out_, &In_, Flags_) -{ - DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0, "Ptr", 0, "Ptr", 0) - VarSetStrCapacity(&Out_, Out_Len) - DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len, "Ptr", 0, "Ptr", 0) - return Out_Len -} -; End of libcrypt code - -b64decode(pszString) { +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. @@ -89,40 +31,39 @@ b64decode(pszString) { ; [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. - return LC_Base64_Decode_Text(pszString) -; 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) + + 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, "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, 0) -;; granted := VarSetStrCapacity(&ret, 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, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) -; if (success=0) { -; return "" -; } -; -; return StrGet(ret, "UTF-8") + 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 }}:: @@ -137,7 +78,7 @@ b64decode(pszString) { :{{ hotstring.options }}:{{ hotstring.trigger }}:: hostring_{{ hotstring._id }}_func(hs) { replacement_b64 := "{{ hotstring._replacement_as_b64 }}" - replacement := b64decode(replacement_b64) + replacement := b64decode(&replacement_b64) Send(replacement) } {% else %} From 2054def561ae9a9fae043b45f33ddc5f07a43423 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 06:48:06 +0000 Subject: [PATCH 452/588] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ahk/_constants.py | 121 ++++++++++++---------------------------------- 1 file changed, 31 insertions(+), 90 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 4d7abcb8..35998447 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -5791,66 +5791,8 @@ Critical "Off" } -; LC_* functions are substantially from Libcrypt -; Modified from https://github.com/ahkscript/libcrypt.ahk -; Ref: https://www.autohotkey.com/boards/viewtopic.php?t=112821 -; Original License: -; The MIT License (MIT) -; -; Copyright (c) 2014 The ahkscript community (ahkscript.org) -; -; Permission is hereby granted, free of charge, to any person obtaining a copy -; of this software and associated documentation files (the "Software"), to deal -; in the Software without restriction, including without limitation the rights -; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -; copies of the Software, and to permit persons to whom the Software is -; furnished to do so, subject to the following conditions: -; -; The above copyright notice and this permission notice shall be included in all -; copies or substantial portions of the Software. - - -LC_Base64_Encode_Text(Text_, Encoding_ := "UTF-8") -{ - Bin_ := Buffer(StrPut(Text_, Encoding_)) - LC_Base64_Encode(&Base64_, &Bin_, StrPut(Text_, Bin_, Encoding_) - 1) - return Base64_ -} - -LC_Base64_Decode_Text(Text_, Encoding_ := "UTF-8") -{ - Len_ := LC_Base64_Decode(&Bin_, &Text_) - return StrGet(StrPtr(Bin_), Len_, Encoding_) -} - -LC_Base64_Encode(&Out_, &In_, In_Len) -{ - return LC_Bin2Str(&Out_, &In_, In_Len, 0x40000001) -} - -LC_Base64_Decode(&Out_, &In_) -{ - return LC_Str2Bin(&Out_, &In_, 0x1) -} - -LC_Bin2Str(&Out_, &In_, In_Len, Flags_) -{ - DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0) - VarSetStrCapacity(&Out_, Out_Len * 2) - DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", In_, "UInt", In_Len, "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len) - return Out_Len -} -LC_Str2Bin(&Out_, &In_, Flags_) -{ - DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Ptr", 0, "UInt*", &Out_Len := 0, "Ptr", 0, "Ptr", 0) - VarSetStrCapacity(&Out_, Out_Len) - DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", StrPtr(In_), "UInt", StrLen(In_), "UInt", Flags_, "Str", Out_, "UInt*", &Out_Len, "Ptr", 0, "Ptr", 0) - return Out_Len -} -; End of libcrypt code - -b64decode(pszString) { +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. @@ -5860,40 +5802,39 @@ ; [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. - return LC_Base64_Decode_Text(pszString) -; 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) + + 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, "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, 0) -;; granted := VarSetStrCapacity(&ret, 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, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags ) -; if (success=0) { -; return "" -; } -; -; return StrGet(ret, "UTF-8") + 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 }}:: @@ -5908,7 +5849,7 @@ :{{ hotstring.options }}:{{ hotstring.trigger }}:: hostring_{{ hotstring._id }}_func(hs) { replacement_b64 := "{{ hotstring._replacement_as_b64 }}" - replacement := b64decode(replacement_b64) + replacement := b64decode(&replacement_b64) Send(replacement) } {% else %} From c6741d85c529ec498afdacb39f4b030e0d8619f8 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Sep 2023 11:57:41 -0700 Subject: [PATCH 453/588] add project urls --- setup.cfg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.cfg b/setup.cfg index ad170b66..91ecdf04 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,6 +8,11 @@ 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 From 6c5d33e1298b2c508bb9cb15214816866955adf1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Sep 2023 15:11:46 -0700 Subject: [PATCH 454/588] add AutoHotkey version autodetection --- ahk/_async/engine.py | 6 ++ ahk/_async/transport.py | 137 ++++++++++++++++++-------------- ahk/_sync/engine.py | 6 ++ ahk/_sync/transport.py | 137 +++++++++++++++++--------------- ahk/_utils.py | 96 ++++++++++++++++++++++ buildunasync.py | 3 +- tests/_async/test_hotkeys.py | 2 +- tests/_async/test_versioning.py | 45 +++++++++++ tests/_sync/test_hotkeys.py | 2 +- tests/_sync/test_versioning.py | 45 +++++++++++ 10 files changed, 354 insertions(+), 125 deletions(-) create mode 100644 tests/_async/test_versioning.py create mode 100644 tests/_sync/test_versioning.py diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 8ec6b8c9..74a8b092 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3735,3 +3735,9 @@ async def block_forever(self) -> NoReturn: """ 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 index 2483066e..2daa5d7a 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -3,6 +3,7 @@ import asyncio.subprocess import atexit import os +import re import subprocess import sys import tempfile @@ -11,7 +12,6 @@ from abc import ABC from abc import abstractmethod from io import BytesIO -from shutil import which from typing import Any from typing import Callable from typing import Generic @@ -48,11 +48,11 @@ DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE, DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE, ) +from ahk._utils import _version_detection_script, _resolve_executable_path, _get_executable_major_version from ahk.directives import Directive from concurrent.futures import Future, ThreadPoolExecutor -DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' T_AsyncFuture = TypeVar('T_AsyncFuture') # unasync: remove T_SyncFuture = TypeVar('T_SyncFuture') @@ -216,7 +216,9 @@ class Communicable(Protocol): def communicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: ... - async def acommunicate(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 @@ -274,7 +276,7 @@ def kill(self) -> None: assert self._proc is not None, 'no process to kill' self._proc.kill() - async def acommunicate( + async def acommunicate( # unasync: remove self, input_bytes: Optional[bytes] = None, timeout: Optional[int] = None ) -> Tuple[bytes, bytes]: assert self._proc is not None @@ -298,78 +300,49 @@ def sync_create_process(runargs: List[str]) -> subprocess.Popen[bytes]: return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) -class AhkExecutableNotFoundError(EnvironmentError): - pass - - -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 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 may be able to resolve this error by installing the binary extra: pip 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 - - class AsyncTransport(ABC): _started: bool = False def __init__( self, /, - executable_path: str = '', directives: Optional[list[Union[Directive, Type[Directive]]]] = None, - version: Optional[Literal['v1', 'v2']] = None, + version: Optional[Literal['v1', 'v2']] = 'v1', + hotkey_transport: Optional[ThreadedHotkeyTransport] = None, **kwargs: Any, ): - self._executable_path: str = _resolve_executable_path(executable_path=executable_path, version=version) - self._hotkey_transport = ThreadedHotkeyTransport( - executable_path=self._executable_path, directives=directives, version=version - ) + self._hotkey_transport = hotkey_transport self._directives: list[Union[Directive, Type[Directive]]] = directives or [] - self._version: Literal['v1', 'v2'] = version or 'v1' + 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: @@ -378,6 +351,7 @@ def add_hotkey(self, hotkey: Hotkey) -> None: 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: @@ -386,31 +360,47 @@ def add_hotstring(self, hotstring: Hotstring) -> None: 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 @@ -676,6 +666,7 @@ def __init__( 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] @@ -685,6 +676,14 @@ def __init__( self._jinja_env: jinja2.Environment self._execution_lock = threading.Lock() self._a_execution_lock = asyncio.Lock() # unasync: remove + self._executable_path = _resolve_executable_path(executable_path=executable_path, version=version) + if version is None: + try: + version = _get_executable_major_version(self._executable_path) + except Exception as e: + warnings.warn(f'Could not detect AHK version ({e}). Defaulting to v1') + version = 'v1' + skip_version_check = True if version is None or version == 'v1': template_name = 'daemon.ahk' @@ -695,6 +694,13 @@ def __init__( else: raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') + if not skip_version_check: + detected_version = _get_executable_major_version(self._executable_path) + if version != detected_version: + raise RuntimeError( + f'AutoHotkey {version} was requested but AutoHotkey {detected_version} was detected for executable {self._executable_path}' + ) + if jinja_loader is None: try: loader: jinja2.BaseLoader @@ -721,7 +727,10 @@ def __init__( if extensions: includes = _resolve_includes(extensions) directives = includes + directives - super().__init__(executable_path=executable_path, directives=directives, version=version) + 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: @@ -911,6 +920,16 @@ def f() -> str: 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]]: diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 1a0246e0..efe26ca6 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3723,3 +3723,9 @@ def block_forever(self) -> NoReturn: """ 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 index caaf58cf..9d23c0d1 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -3,6 +3,7 @@ import asyncio.subprocess import atexit import os +import re import subprocess import sys import tempfile @@ -11,7 +12,6 @@ from abc import ABC from abc import abstractmethod from io import BytesIO -from shutil import which from typing import Any from typing import Callable from typing import Generic @@ -48,11 +48,11 @@ DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE, DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE, ) +from ahk._utils import _version_detection_script, _resolve_executable_path, _get_executable_major_version from ahk.directives import Directive from concurrent.futures import Future, ThreadPoolExecutor -DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' T_SyncFuture = TypeVar('T_SyncFuture') @@ -208,8 +208,6 @@ class Communicable(Protocol): def communicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: ... - def acommunicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: - ... @property def returncode(self) -> Optional[int]: @@ -261,11 +259,6 @@ def kill(self) -> None: assert self._proc is not None, 'no process to kill' self._proc.kill() - def acommunicate( - self, input_bytes: Optional[bytes] = None, timeout: Optional[int] = None - ) -> Tuple[bytes, bytes]: - assert self._proc is not None - return 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 @@ -279,76 +272,49 @@ def sync_create_process(runargs: List[str]) -> subprocess.Popen[bytes]: return subprocess.Popen(runargs, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) -class AhkExecutableNotFoundError(EnvironmentError): - pass - - -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 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 may be able to resolve this error by installing the binary extra: pip 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 - - class Transport(ABC): _started: bool = False def __init__( self, /, - executable_path: str = '', directives: Optional[list[Union[Directive, Type[Directive]]]] = None, - version: Optional[Literal['v1', 'v2']] = None, + version: Optional[Literal['v1', 'v2']] = 'v1', + hotkey_transport: Optional[ThreadedHotkeyTransport] = None, **kwargs: Any, ): - self._executable_path: str = _resolve_executable_path(executable_path=executable_path, version=version) - self._hotkey_transport = ThreadedHotkeyTransport(executable_path=self._executable_path, directives=directives, version=version) + self._hotkey_transport = hotkey_transport self._directives: list[Union[Directive, Type[Directive]]] = directives or [] - self._version: Literal['v1', 'v2'] = version or 'v1' + 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: @@ -357,6 +323,7 @@ def add_hotkey(self, hotkey: Hotkey) -> None: 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: @@ -365,31 +332,47 @@ def add_hotstring(self, hotstring: Hotstring) -> None: 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 @@ -648,6 +631,7 @@ def __init__( 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] @@ -656,6 +640,15 @@ def __init__( self.__template: jinja2.Template self._jinja_env: jinja2.Environment self._execution_lock = threading.Lock() + self._executable_path = _resolve_executable_path(executable_path=executable_path, version=version) + if version is None: + try: + version = _get_executable_major_version(self._executable_path) + except Exception as e: + warnings.warn(f'Could not detect AHK version ({e}). Defaulting to v1') + version = 'v1' + skip_version_check = True + if version is None or version == 'v1': template_name = 'daemon.ahk' @@ -666,6 +659,11 @@ def __init__( else: raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') + if not skip_version_check: + detected_version = _get_executable_major_version(self._executable_path) + if version != detected_version: + raise RuntimeError(f'AutoHotkey {version} was requested but AutoHotkey {detected_version} was detected for executable {self._executable_path}') + if jinja_loader is None: try: loader: jinja2.BaseLoader @@ -692,7 +690,10 @@ def __init__( if extensions: includes = _resolve_includes(extensions) directives = includes + directives - super().__init__(executable_path=executable_path, directives=directives, version=version) + 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: @@ -858,6 +859,16 @@ def f() -> str: 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]]: @@ -870,7 +881,7 @@ def run_script( proc = SyncAHKProcess(runargs) proc.start() if blocking: - stdout, stderr = proc.acommunicate(script_bytes, timeout=timeout) + 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) diff --git a/ahk/_utils.py b/ahk/_utils.py index 951d4341..42c2914d 100644 --- a/ahk/_utils.py +++ b/ahk/_utils.py @@ -1,4 +1,11 @@ import enum +import os +import re +import subprocess +import warnings +from shutil import which +from typing import Literal +from typing import Optional HOTKEY_ESCAPE_SEQUENCE_MAP = { '\n': '`n', @@ -71,3 +78,92 @@ 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' + + +class AhkExecutableNotFoundError(EnvironmentError): + pass + + +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 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 may be able to resolve this error by installing the binary extra: pip 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}') diff --git a/buildunasync.py b/buildunasync.py index 15a2bba2..d187ee84 100644 --- a/buildunasync.py +++ b/buildunasync.py @@ -17,7 +17,8 @@ 'a_send_nonblocking': 'send_nonblocking', 'async_sleep': 'sleep', 'AsyncFutureResult': 'FutureResult', - '_async_run_nonblocking': '_sync_run_nonblocking' + '_async_run_nonblocking': '_sync_run_nonblocking', + 'acommunicate': 'communicate' # "__aenter__": "__aenter__", }, ), diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index 462cbe9e..66056660 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -23,7 +23,7 @@ async def asyncSetUp(self) -> None: async def asyncTearDown(self) -> None: self.ahk.stop_hotkeys() self.ahk._transport._proc.kill() - subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey.exe'], capture_output=True) + subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey*.exe'], capture_output=True) time.sleep(0.2) async def test_hotkey(self): 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/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py index 701c39d3..6b1c0a9c 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -20,7 +20,7 @@ def setUp(self) -> None: def tearDown(self) -> None: self.ahk.stop_hotkeys() self.ahk._transport._proc.kill() - subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey.exe'], capture_output=True) + subprocess.run(['TASKKILL', '/F', '/IM', 'AutoHotkey*.exe'], capture_output=True) time.sleep(0.2) def test_hotkey(self): 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') From dce2eaf471761f60ba4a4fdbec5b9bcb93dce4d2 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Sep 2023 16:36:55 -0700 Subject: [PATCH 455/588] support v2 default install location --- ahk/_utils.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ahk/_utils.py b/ahk/_utils.py index 42c2914d..15c281d1 100644 --- a/ahk/_utils.py +++ b/ahk/_utils.py @@ -81,6 +81,7 @@ class MsgBoxOtherOptions(enum.IntEnum): DEFAULT_EXECUTABLE_PATH = r'C:\Program Files\AutoHotkey\AutoHotkey.exe' +DEFAULT_EXECUTABLE_PATH_V2 = r'C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe' class AhkExecutableNotFoundError(EnvironmentError): @@ -102,15 +103,19 @@ def _resolve_executable_path(executable_path: str = '', version: Optional[Litera ) if not executable_path: - if os.path.exists(DEFAULT_EXECUTABLE_PATH): - executable_path = DEFAULT_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 may be able to resolve this error by installing the binary extra: pip install "ahk[binary]"' + '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): From fe0a7551a7b08d332ed0553510f757b420a28e36 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Sep 2023 16:37:15 -0700 Subject: [PATCH 456/588] document v2 support --- docs/README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/docs/README.md b/docs/README.md index 136523b6..676366ec 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # ahk -A fully typed Python wrapper around 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) @@ -14,9 +14,10 @@ A fully typed Python wrapper around AHK. ``` pip install ahk ``` + Requires Python 3.8+ -See also [Non-Python dependencies](#deps) +Supports AutoHotkey v1 and v2. See also: [Non-Python dependencies](#deps) # Usage @@ -533,31 +534,75 @@ ahk.run_script(script_path) # 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. +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) -Note: this should be AutoHotkey V1. AutoHotkey V2 is not yet supported. +AutoHotkey v1 is fully supported. AutoHotkey v2 support is available, but is considered to be in beta status. -A convenient way to do this is to install the `binary` extra +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 ``` -Alternatively, you may provide the path in code +## 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='C:\\path\\to\\AutoHotkey.exe') + +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 between the two versions. + +Some of the differences that you will experience when using AutoHotkey v2 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. + # Contributing From 71539726a8623039ee0eb264f7423ca8dfc1fc07 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Sep 2023 16:37:34 -0700 Subject: [PATCH 457/588] set binary extra to 2023.9.0 --- .github/workflows/test.yaml | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bf3e4ced..d1ed71b0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,7 +21,7 @@ jobs: python -m pip install -r requirements-dev.txt python -m pip install . python -m pip install tox - python -m pip install "ahk-binary==2023.9.0rc1" + python -m pip install "ahk-binary==2023.9.0" - name: Test with coverage/pytest timeout-minutes: 10 env: diff --git a/setup.cfg b/setup.cfg index 91ecdf04..44c63fd5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -50,7 +50,7 @@ cmdclass = build_py = buildunasync.build_py [options.extras_require] -binary = ahk-binary==1.1.33.9 +binary = ahk-binary==2023.9.0 [options.package_data] ahk = From 2d70afbd3d032e868c40a369f525063a2093124c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Sep 2023 17:17:49 -0700 Subject: [PATCH 458/588] preload crypt32 --- ahk/_constants.py | 30 ++++++++++++++++++------------ ahk/templates/daemon-v2.ahk | 18 ++++++++++-------- ahk/templates/daemon.ahk | 10 ++++++---- ahk/templates/hotkeys-v2.ahk | 1 + ahk/templates/hotkeys.ahk | 1 + 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 35998447..fecfff5e 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2658,8 +2658,10 @@ return ret } +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. @@ -2682,7 +2684,7 @@ 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 ) + success := DllCall("Crypt32\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "UInt", getsize, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags) if (success = 0) { return "" } @@ -2692,7 +2694,7 @@ ; 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 ) + success := DllCall("Crypt32\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "Ptr", &ret, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags) if (success=0) { return "" } @@ -2725,7 +2727,7 @@ ; 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) + 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) @@ -2806,6 +2808,7 @@ KEEPALIVE := Chr(57344) SetTimer, keepalive, 1000 +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") b64decode(ByRef pszString) { ; TODO load DLL globally for performance @@ -3355,13 +3358,14 @@ if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinMaximize(title, text, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + try { + WinMaximize(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinMaximize %} } @@ -5633,8 +5637,9 @@ return ret } +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. @@ -5791,6 +5796,7 @@ Critical "Off" } +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") b64decode(&pszString) { ; TODO load DLL globally for performance diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 2e41cd8e..3f4c3648 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -460,13 +460,14 @@ AHKWinMaximize(command) { if (detect_hw != "") { DetectHiddenWindows(detect_hw) } - - WinMaximize(title, text, extitle, extext) - - DetectHiddenWindows(current_detect_hw) - SetTitleMatchMode(current_match_mode) - SetTitleMatchMode(current_match_speed) - + try { + WinMaximize(title, text, extitle, extext) + } + finally { + DetectHiddenWindows(current_detect_hw) + SetTitleMatchMode(current_match_mode) + SetTitleMatchMode(current_match_speed) + } return FormatNoValueResponse() {% endblock AHKWinMaximize %} } @@ -2738,8 +2739,9 @@ AHKFileSelectFolder(command) { return ret } +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. diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 83ca10c8..83ce0a19 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2655,8 +2655,10 @@ AHKFileSelectFolder(byRef command) { return ret } +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. @@ -2679,7 +2681,7 @@ b64decode(ByRef pszString) { 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 ) + success := DllCall("Crypt32\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "UInt", getsize, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags) if (success = 0) { return "" } @@ -2689,7 +2691,7 @@ b64decode(ByRef pszString) { ; 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 ) + success := DllCall("Crypt32\CryptStringToBinary", "Ptr", &pszString, "UInt", cchString, "UInt", dwFlags, "Ptr", &ret, "UIntP", buff_size, "Int", pdwSkip, "Int", pdwFlags) if (success=0) { return "" } @@ -2722,7 +2724,7 @@ b64encode(ByRef data) { ; 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) + 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) diff --git a/ahk/templates/hotkeys-v2.ahk b/ahk/templates/hotkeys-v2.ahk index e13e7461..09667d7f 100644 --- a/ahk/templates/hotkeys-v2.ahk +++ b/ahk/templates/hotkeys-v2.ahk @@ -20,6 +20,7 @@ WriteStdout(s) { Critical "Off" } +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") b64decode(&pszString) { ; TODO load DLL globally for performance diff --git a/ahk/templates/hotkeys.ahk b/ahk/templates/hotkeys.ahk index 08571340..43cb2d9b 100644 --- a/ahk/templates/hotkeys.ahk +++ b/ahk/templates/hotkeys.ahk @@ -13,6 +13,7 @@ OnClipboardChange("ClipChanged") KEEPALIVE := Chr(57344) SetTimer, keepalive, 1000 +Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") b64decode(ByRef pszString) { ; TODO load DLL globally for performance From becba02ae5f3ca1b1beaa1650782574f9009394b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 21 Sep 2023 11:04:52 -0700 Subject: [PATCH 459/588] fix ControlSend in v2 --- ahk/_constants.py | 4 ++-- ahk/templates/daemon-v2.ahk | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index fecfff5e..15638ed5 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -5264,9 +5264,9 @@ try { if (ctrl != "") { - ControlSendText(keys, ctrl, title, text, extitle, extext) + ControlSend(keys, ctrl, title, text, extitle, extext) } else { - ControlSendText(keys,, title, text, extitle, extext) + ControlSend(keys,, title, text, extitle, extext) } } finally { diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 3f4c3648..9e52bb70 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2366,9 +2366,9 @@ AHKControlSend(command) { try { if (ctrl != "") { - ControlSendText(keys, ctrl, title, text, extitle, extext) + ControlSend(keys, ctrl, title, text, extitle, extext) } else { - ControlSendText(keys,, title, text, extitle, extext) + ControlSend(keys,, title, text, extitle, extext) } } finally { From f442c565c5e3f8a8ceeb1686cc96f3a9c9fcab01 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 21 Sep 2023 14:15:22 -0700 Subject: [PATCH 460/588] move version resolution into the engine --- ahk/_async/engine.py | 28 +++++++++++++++++++++++++++- ahk/_async/transport.py | 18 ++---------------- ahk/_sync/engine.py | 28 +++++++++++++++++++++++++++- ahk/_sync/transport.py | 16 +++------------- tests/_async/test_window.py | 7 +++++++ tests/_sync/test_window.py | 7 +++++++ 6 files changed, 73 insertions(+), 31 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 74a8b092..8edaae4e 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -22,6 +22,8 @@ from .._hotkey import Hotkey from .._hotkey import Hotstring +from .._utils import _get_executable_major_version +from .._utils import _resolve_executable_path from .._utils import MsgBoxButtons from .._utils import MsgBoxDefaultButton from .._utils import MsgBoxIcon @@ -145,6 +147,27 @@ def __init__( 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': @@ -162,6 +185,9 @@ def __init__( ) 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 @@ -1332,7 +1358,7 @@ async def show_traytip( if second is None: second = 1.0 else: - if self._transport._version == 'v2': + if self._version == 'v2': warnings.warn( 'supplying seconds is not supported when using AutoHotkey v2. This parameter will be ignored' ) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 2daa5d7a..0e715f9c 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -48,7 +48,7 @@ DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE, DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE, ) -from ahk._utils import _version_detection_script, _resolve_executable_path, _get_executable_major_version +from ahk._utils import _version_detection_script from ahk.directives import Directive from concurrent.futures import Future, ThreadPoolExecutor @@ -676,14 +676,7 @@ def __init__( self._jinja_env: jinja2.Environment self._execution_lock = threading.Lock() self._a_execution_lock = asyncio.Lock() # unasync: remove - self._executable_path = _resolve_executable_path(executable_path=executable_path, version=version) - if version is None: - try: - version = _get_executable_major_version(self._executable_path) - except Exception as e: - warnings.warn(f'Could not detect AHK version ({e}). Defaulting to v1') - version = 'v1' - skip_version_check = True + self._executable_path = executable_path if version is None or version == 'v1': template_name = 'daemon.ahk' @@ -694,13 +687,6 @@ def __init__( else: raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') - if not skip_version_check: - detected_version = _get_executable_major_version(self._executable_path) - if version != detected_version: - raise RuntimeError( - f'AutoHotkey {version} was requested but AutoHotkey {detected_version} was detected for executable {self._executable_path}' - ) - if jinja_loader is None: try: loader: jinja2.BaseLoader diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index efe26ca6..afe44ca0 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -22,6 +22,8 @@ from .._hotkey import Hotkey from .._hotkey import Hotstring +from .._utils import _get_executable_major_version +from .._utils import _resolve_executable_path from .._utils import MsgBoxButtons from .._utils import MsgBoxDefaultButton from .._utils import MsgBoxIcon @@ -141,6 +143,27 @@ def __init__( 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': @@ -158,6 +181,9 @@ def __init__( ) 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: @@ -1320,7 +1346,7 @@ def show_traytip( if second is None: second = 1.0 else: - if self._transport._version == 'v2': + if self._version == 'v2': warnings.warn( 'supplying seconds is not supported when using AutoHotkey v2. This parameter will be ignored' ) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 9d23c0d1..d5f1df1e 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -48,7 +48,7 @@ DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE, DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE, ) -from ahk._utils import _version_detection_script, _resolve_executable_path, _get_executable_major_version +from ahk._utils import _version_detection_script from ahk.directives import Directive from concurrent.futures import Future, ThreadPoolExecutor @@ -640,14 +640,7 @@ def __init__( self.__template: jinja2.Template self._jinja_env: jinja2.Environment self._execution_lock = threading.Lock() - self._executable_path = _resolve_executable_path(executable_path=executable_path, version=version) - if version is None: - try: - version = _get_executable_major_version(self._executable_path) - except Exception as e: - warnings.warn(f'Could not detect AHK version ({e}). Defaulting to v1') - version = 'v1' - skip_version_check = True + self._executable_path = executable_path if version is None or version == 'v1': @@ -659,10 +652,7 @@ def __init__( else: raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') - if not skip_version_check: - detected_version = _get_executable_major_version(self._executable_path) - if version != detected_version: - raise RuntimeError(f'AutoHotkey {version} was requested but AutoHotkey {detected_version} was detected for executable {self._executable_path}') + if jinja_loader is None: try: diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index d5ab5d4f..f3127bf8 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -133,6 +133,13 @@ async def test_type_escape(self): 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') diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 66ea08a4..723ec47a 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -133,6 +133,13 @@ def test_type_escape(self): 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') From ad0f22144fbc0dd0aa5a92ae667ce45de8473e6b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 21 Sep 2023 14:58:15 -0700 Subject: [PATCH 461/588] use variadic calls --- ahk/_constants.py | 2370 ++++++++++++++++--------------- ahk/templates/daemon-v2.ahk | 1177 +++++++-------- ahk/templates/daemon.ahk | 1193 ++++++++-------- tests/_async/test_extensions.py | 17 +- tests/_sync/test_extensions.py | 17 +- 5 files changed, 2388 insertions(+), 2386 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 15638ed5..a07357bc 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -43,18 +43,18 @@ return FormatResponse("ahk.message.B64BinaryResponseMessage", b64) } -AHKSetDetectHiddenWindows(ByRef command) { +AHKSetDetectHiddenWindows(args*) { {% block AHKSetDetectHiddenWindows %} - value := command[2] + value := args[1] DetectHiddenWindows, %value% return FormatNoValueResponse() {% endblock AHKSetDetectHiddenWindows %} } -AHKSetTitleMatchMode(ByRef command) { +AHKSetTitleMatchMode(args*) { {% block AHKSetTitleMatchMode %} - val1 := command[2] - val2 := command[3] + val1 := args[1] + val2 := args[2] if (val1 != "") { SetTitleMatchMode, %val1% } @@ -65,45 +65,45 @@ {% endblock AHKSetTitleMatchMode %} } -AHKGetTitleMatchMode(ByRef command) { +AHKGetTitleMatchMode(args*) { {% block AHKGetTitleMatchMode %} return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) {% endblock AHKGetTitleMatchMode %} } -AHKGetTitleMatchSpeed(ByRef command) { +AHKGetTitleMatchSpeed(args*) { {% block AHKGetTitleMatchSpeed %} return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) {% endblock AHKGetTitleMatchSpeed %} } -AHKSetSendLevel(ByRef command) { +AHKSetSendLevel(args*) { {% block AHKSetSendLevel %} - level := command[2] + level := args[1] SendLevel, %level% return FormatNoValueResponse() {% endblock AHKSetSendLevel %} } -AHKGetSendLevel(ByRef command) { +AHKGetSendLevel(args*) { {% block AHKGetSendLevel %} return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) {% endblock AHKGetSendLevel %} } -AHKWinExist(ByRef command) { +AHKWinExist(args*) { {% block AHKWinExist %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -134,16 +134,16 @@ {% endblock AHKWinExist %} } -AHKWinClose(ByRef command) { +AHKWinClose(args*) { {% block AHKWinClose %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - secondstowait := command[9] + 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) @@ -169,16 +169,16 @@ {% endblock AHKWinClose %} } -AHKWinKill(ByRef command) { +AHKWinKill(args*) { {% block AHKWinKill %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - secondstowait := command[9] + 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) @@ -204,17 +204,17 @@ {% endblock AHKWinKill %} } -AHKWinWait(ByRef command) { +AHKWinWait(args*) { {% block AHKWinWait %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -248,17 +248,17 @@ {% endblock AHKWinWait %} } -AHKWinWaitActive(ByRef command) { +AHKWinWaitActive(args*) { {% block AHKWinWaitActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -292,17 +292,17 @@ {% endblock AHKWinWaitActive %} } -AHKWinWaitNotActive(ByRef command) { +AHKWinWaitNotActive(args*) { {% block AHKWinWaitNotActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -336,17 +336,17 @@ {% endblock AHKWinWaitNotActive %} } -AHKWinWaitClose(ByRef command) { +AHKWinWaitClose(args*) { {% block AHKWinWaitClose %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -379,15 +379,15 @@ {% endblock AHKWinWaitClose %} } -AHKWinMinimize(ByRef command) { +AHKWinMinimize(args*) { {% block AHKWinMinimize %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -413,15 +413,15 @@ {% endblock AHKWinMinimize %} } -AHKWinMaximize(ByRef command) { +AHKWinMaximize(args*) { {% block AHKWinMaximize %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -447,15 +447,15 @@ {% endblock AHKWinMaximize %} } -AHKWinRestore(ByRef command) { +AHKWinRestore(args*) { {% block AHKWinRestore %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -481,16 +481,16 @@ {% endblock AHKWinRestore %} } -AHKWinIsActive(ByRef command) { +AHKWinIsActive(args*) { {% block AHKWinIsActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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 != "") { @@ -518,16 +518,16 @@ {% endblock AHKWinIsActive %} } -AHKWinGetID(ByRef command) { +AHKWinGetID(args*) { {% block AHKWinGetID %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -557,16 +557,16 @@ {% endblock AHKWinGetID %} } -AHKWinGetTitle(ByRef command) { +AHKWinGetTitle(args*) { {% block AHKWinGetTitle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -592,16 +592,16 @@ {% endblock AHKWinGetTitle %} } -AHKWinGetIDLast(ByRef command) { +AHKWinGetIDLast(args*) { {% block AHKWinGetIDLast %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -631,16 +631,16 @@ {% endblock AHKWinGetIDLast %} } -AHKWinGetPID(ByRef command) { +AHKWinGetPID(args*) { {% block AHKWinGetPID %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -670,16 +670,16 @@ {% endblock AHKWinGetPID %} } -AHKWinGetProcessName(ByRef command) { +AHKWinGetProcessName(args*) { {% block AHKWinGetProcessName %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -709,16 +709,16 @@ {% endblock AHKWinGetProcessName %} } -AHKWinGetProcessPath(ByRef command) { +AHKWinGetProcessPath(args*) { {% block AHKWinGetProcessPath %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -748,16 +748,16 @@ {% endblock AHKWinGetProcessPath %} } -AHKWinGetCount(ByRef command) { +AHKWinGetCount(args*) { {% block AHKWinGetCount %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -787,16 +787,16 @@ {% endblock AHKWinGetCount %} } -AHKWinGetMinMax(ByRef command) { +AHKWinGetMinMax(args*) { {% block AHKWinGetMinMax %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -826,16 +826,16 @@ {% endblock AHKWinGetMinMax %} } -AHKWinGetControlList(ByRef command) { +AHKWinGetControlList(args*) { {% block AHKWinGetControlList %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -890,16 +890,16 @@ {% endblock AHKWinGetControlList %} } -AHKWinGetTransparent(ByRef command) { +AHKWinGetTransparent(args*) { {% block AHKWinGetTransparent %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -924,16 +924,16 @@ return response {% endblock AHKWinGetTransparent %} } -AHKWinGetTransColor(ByRef command) { +AHKWinGetTransColor(args*) { {% block AHKWinGetTransColor %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -958,16 +958,16 @@ return response {% endblock AHKWinGetTransColor %} } -AHKWinGetStyle(ByRef command) { +AHKWinGetStyle(args*) { {% block AHKWinGetStyle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -992,16 +992,16 @@ return response {% endblock AHKWinGetStyle %} } -AHKWinGetExStyle(ByRef command) { +AHKWinGetExStyle(args*) { {% block AHKWinGetExStyle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1027,16 +1027,16 @@ {% endblock AHKWinGetExStyle %} } -AHKWinGetText(ByRef command) { +AHKWinGetText(args*) { {% block AHKWinGetText %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1067,16 +1067,16 @@ {% endblock AHKWinGetText %} } -AHKWinSetTitle(ByRef command) { +AHKWinSetTitle(args*) { {% block AHKWinSetTitle %} - new_title := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1099,16 +1099,16 @@ {% endblock AHKWinSetTitle %} } -AHKWinSetAlwaysOnTop(ByRef command) { +AHKWinSetAlwaysOnTop(args*) { {% block AHKWinSetAlwaysOnTop %} - toggle := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1132,15 +1132,15 @@ {% endblock AHKWinSetAlwaysOnTop %} } -AHKWinSetBottom(ByRef command) { +AHKWinSetBottom(args*) { {% block AHKWinSetBottom %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1165,15 +1165,15 @@ {% endblock AHKWinSetBottom %} } -AHKWinShow(ByRef command) { +AHKWinShow(args*) { {% block AHKWinShow %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1198,15 +1198,15 @@ {% endblock AHKWinShow %} } -AHKWinHide(ByRef command) { +AHKWinHide(args*) { {% block AHKWinHide %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1231,15 +1231,15 @@ {% endblock AHKWinHide %} } -AHKWinSetTop(ByRef command) { +AHKWinSetTop(args*) { {% block AHKWinSetTop %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1264,15 +1264,15 @@ {% endblock AHKWinSetTop %} } -AHKWinSetEnable(ByRef command) { +AHKWinSetEnable(args*) { {% block AHKWinSetEnable %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1297,15 +1297,15 @@ {% endblock AHKWinSetEnable %} } -AHKWinSetDisable(ByRef command) { +AHKWinSetDisable(args*) { {% block AHKWinSetDisable %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1330,15 +1330,15 @@ {% endblock AHKWinSetDisable %} } -AHKWinSetRedraw(ByRef command) { +AHKWinSetRedraw(args*) { {% block AHKWinSetRedraw %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1363,17 +1363,17 @@ {% endblock AHKWinSetRedraw %} } -AHKWinSetStyle(ByRef command) { +AHKWinSetStyle(args*) { {% block AHKWinSetStyle %} - style := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1402,17 +1402,17 @@ {% endblock AHKWinSetStyle %} } -AHKWinSetExStyle(ByRef command) { +AHKWinSetExStyle(args*) { {% block AHKWinSetExStyle %} - style := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1441,17 +1441,17 @@ {% endblock AHKWinSetExStyle %} } -AHKWinSetRegion(ByRef command) { +AHKWinSetRegion(args*) { {% block AHKWinSetRegion %} - options := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1480,17 +1480,17 @@ {% endblock AHKWinSetRegion %} } -AHKWinSetTransparent(ByRef command) { +AHKWinSetTransparent(args*) { {% block AHKWinSetTransparent %} - transparency := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1514,17 +1514,17 @@ {% endblock AHKWinSetTransparent %} } -AHKWinSetTransColor(ByRef command) { +AHKWinSetTransColor(args*) { {% block AHKWinSetTransColor %} - color := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1550,15 +1550,15 @@ {% endblock AHKWinSetTransColor %} } -AHKImageSearch(ByRef command) { +AHKImageSearch(args*) { {% block AHKImageSearch %} - imagepath := command[6] - x1 := command[2] - y1 := command[3] - x2 := command[4] - y2 := command[5] - coord_mode := command[7] + imagepath := args[5] + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + coord_mode := args[6] current_mode := Format("{}", A_CoordModePixel) @@ -1580,7 +1580,7 @@ } if (ErrorLevel = 2) { - s := FormatResponse("ahk.message.ExceptionResponseMessage", "there was a problem that prevented the command from conducting the search (such as failure to open the image file or a badly formatted option)") + 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 { @@ -1591,13 +1591,13 @@ {% endblock AHKImageSearch %} } -AHKPixelGetColor(ByRef command) { +AHKPixelGetColor(args*) { {% block AHKPixelGetColor %} - x := command[2] - y := command[3] - coord_mode := command[4] - options := command[5] + x := args[1] + y := args[2] + coord_mode := args[3] + options := args[4] current_mode := Format("{}", A_CoordModePixel) @@ -1616,17 +1616,17 @@ {% endblock AHKPixelGetColor %} } -AHKPixelSearch(ByRef command) { +AHKPixelSearch(args*) { {% block AHKPixelSearch %} - x1 := command[2] - y1 := command[3] - x2 := command[4] - y2 := command[5] - color := command[6] - variation := command[7] - options := command[8] - coord_mode := command[9] + 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) @@ -1654,10 +1654,10 @@ {% endblock AHKPixelSearch %} } -AHKMouseGetPos(ByRef command) { +AHKMouseGetPos(args*) { {% block AHKMouseGetPos %} - coord_mode := command[2] + coord_mode := args[1] current_coord_mode := Format("{}", A_CoordModeMouse) if (coord_mode != "") { CoordMode, Mouse, %coord_mode% @@ -1675,11 +1675,11 @@ {% endblock AHKMouseGetPos %} } -AHKKeyState(ByRef command) { +AHKKeyState(args*) { {% block AHKKeyState %} - keyname := command[2] - mode := command[3] + keyname := args[1] + mode := args[2] if (mode != "") { state := GetKeyState(keyname, mode) } else{ @@ -1703,12 +1703,12 @@ {% endblock AHKKeyState %} } -AHKMouseMove(ByRef command) { +AHKMouseMove(args*) { {% block AHKMouseMove %} - x := command[2] - y := command[3] - speed := command[4] - relative := command[5] + x := args[1] + y := args[2] + speed := args[3] + relative := args[4] if (relative != "") { MouseMove, %x%, %y%, %speed%, R } else { @@ -1719,15 +1719,15 @@ {% endblock AHKMouseMove %} } -AHKClick(ByRef command) { +AHKClick(args*) { {% block AHKClick %} - x := command[2] - y := command[3] - button := command[4] - click_count := command[5] - direction := command[6] - r := command[7] - relative_to := command[8] + x := args[1] + y := args[2] + button := args[3] + click_count := args[4] + direction := args[5] + r := args[6] + relative_to := args[7] current_coord_rel := Format("{}", A_CoordModeMouse) if (relative_to != "") { @@ -1745,10 +1745,10 @@ {% endblock AHKClick %} } -AHKGetCoordMode(ByRef command) { +AHKGetCoordMode(args*) { {% block AHKGetCoordMode %} - target := command[2] + target := args[1] if (target = "ToolTip") { return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeToolTip) @@ -1769,26 +1769,26 @@ {% endblock AHKGetCoordMode %} } -AHKSetCoordMode(ByRef command) { +AHKSetCoordMode(args*) { {% block AHKSetCoordMode %} - target := command[2] - relative_to := command[3] + target := args[1] + relative_to := args[2] CoordMode, %target%, %relative_to% return FormatNoValueResponse() {% endblock AHKSetCoordMode %} } -AHKMouseClickDrag(ByRef command) { +AHKMouseClickDrag(args*) { {% block AHKMouseClickDrag %} - button := command[2] - x1 := command[3] - y1 := command[4] - x2 := command[5] - y2 := command[6] - speed := command[7] - relative := command[8] - relative_to := command[9] + 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] current_coord_rel := Format("{}", A_CoordModeMouse) @@ -1807,11 +1807,11 @@ {% endblock AHKMouseClickDrag %} } -AHKRegRead(ByRef command) { +AHKRegRead(args*) { {% block RegRead %} - key_name := command[2] - value_name := command[3] + key_name := args[1] + value_name := args[2] RegRead, output, %key_name%, %value_name% @@ -1825,13 +1825,13 @@ {% endblock RegRead %} } -AHKRegWrite(ByRef command) { +AHKRegWrite(args*) { {% block RegWrite %} - value_type := command[2] - key_name := command[3] - value_name := command[4] - value := command[5] + 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)) @@ -1841,11 +1841,11 @@ {% endblock RegWrite %} } -AHKRegDelete(ByRef command) { +AHKRegDelete(args*) { {% block RegDelete %} - key_name := command[2] - value_name := command[3] + 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)) @@ -1855,31 +1855,31 @@ {% endblock RegDelete %} } -AHKKeyWait(ByRef command) { +AHKKeyWait(args*) { {% block AHKKeyWait %} - keyname := command[2] - if (command.Length() = 2) { + keyname := args[1] + if (args.Length() = 2) { KeyWait,% keyname } else { - options := command[3] + options := args[2] KeyWait,% keyname,% options } return FormatResponse("ahk.message.IntegerResponseMessage", ErrorLevel) {% endblock AHKKeyWait %} } -SetKeyDelay(ByRef command) { +SetKeyDelay(args*) { {% block SetKeyDelay %} - SetKeyDelay, command[2], command[3] + SetKeyDelay, args[1], args[2] {% endblock SetKeyDelay %} } -AHKSend(ByRef command) { +AHKSend(args*) { {% block AHKSend %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -1896,11 +1896,11 @@ {% endblock AHKSend %} } -AHKSendRaw(ByRef command) { +AHKSendRaw(args*) { {% block AHKSendRaw %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -1917,11 +1917,11 @@ {% endblock AHKSendRaw %} } -AHKSendInput(ByRef command) { +AHKSendInput(args*) { {% block AHKSendInput %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -1938,11 +1938,11 @@ {% endblock AHKSendInput %} } -AHKSendEvent(ByRef command) { +AHKSendEvent(args*) { {% block AHKSendEvent %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -1959,11 +1959,11 @@ {% endblock AHKSendEvent %} } -AHKSendPlay(ByRef command) { +AHKSendPlay(args*) { {% block AHKSendPlay %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelayPlay) current_key_duration := Format("{}", A_KeyDurationPlay) @@ -1980,9 +1980,9 @@ {% endblock AHKSendPlay %} } -AHKSetCapsLockState(ByRef command) { +AHKSetCapsLockState(args*) { {% block AHKSetCapsLockState %} - state := command[2] + state := args[1] if (state = "") { SetCapsLockState % !GetKeyState("CapsLock", "T") } else { @@ -1992,7 +1992,7 @@ {% endblock AHKSetCapsLockState %} } -HideTrayTip(ByRef command) { +HideTrayTip(args*) { {% block HideTrayTip %} TrayTip ; Attempt to hide it the normal way. if SubStr(A_OSVersion,1,3) = "10." { @@ -2003,16 +2003,16 @@ {% endblock HideTrayTip %} } -AHKWinGetClass(ByRef command) { +AHKWinGetClass(args*) { {% block AHKWinGetClass %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2043,15 +2043,15 @@ {% endblock AHKWinGetClass %} } -AHKWinActivate(ByRef command) { +AHKWinActivate(args*) { {% block AHKWinActivate %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2078,18 +2078,18 @@ {% endblock AHKWinActivate %} } -AHKWindowList(ByRef command) { +AHKWindowList(args*) { {% block AHKWindowList %} current_detect_hw := Format("{}", A_DetectHiddenWindows) - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2118,20 +2118,20 @@ {% endblock AHKWindowList %} } -AHKControlClick(ByRef command) { +AHKControlClick(args*) { {% block AHKControlClick %} - ctrl := command[2] - title := command[3] - text := command[4] - button := command[5] - click_count := command[6] - options := command[7] - exclude_title := command[8] - exclude_text := command[9] - detect_hw := command[10] - match_mode := command[11] - match_speed := command[12] + 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) @@ -2163,17 +2163,17 @@ {% endblock AHKControlClick %} } -AHKControlGetText(ByRef command) { +AHKControlGetText(args*) { {% block AHKControlGetText %} - ctrl := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -2204,17 +2204,17 @@ {% endblock AHKControlGetText %} } -AHKControlGetPos(ByRef command) { +AHKControlGetPos(args*) { {% block AHKControlGetPos %} - ctrl := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -2247,17 +2247,17 @@ {% endblock AHKControlGetPos %} } -AHKControlSend(ByRef command) { +AHKControlSend(args*) { {% block AHKControlSend %} - ctrl := command[2] - keys := command[3] - title := command[4] - text := command[5] - extitle := command[6] - extext := command[7] - detect_hw := command[8] - match_mode := command[9] - match_speed := command[10] + 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) @@ -2280,7 +2280,7 @@ {% endblock AHKControlSend %} } -AHKWinFromMouse(ByRef command) { +AHKWinFromMouse(args*) { {% block AHKWinFromMouse %} MouseGetPos,,, MouseWin @@ -2293,10 +2293,10 @@ {% endblock AHKWinFromMouse %} } -AHKWinIsAlwaysOnTop(ByRef command) { +AHKWinIsAlwaysOnTop(args*) { {% block AHKWinIsAlwaysOnTop %} - title := command[2] + title := args[1] WinGet, ExStyle, ExStyle, %title% if (ExStyle = "") return FormatNoValueResponse() @@ -2308,19 +2308,19 @@ {% endblock AHKWinIsAlwaysOnTop %} } -AHKWinMove(ByRef command) { +AHKWinMove(args*) { {% block AHKWinMove %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - x := command[9] - y := command[10] - width := command[11] - height := command[12] + 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) @@ -2347,16 +2347,16 @@ {% endblock AHKWinMove %} } -AHKWinGetPos(ByRef command) { +AHKWinGetPos(args*) { {% block AHKWinGetPos %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2389,10 +2389,10 @@ {% endblock AHKWinGetPos %} } -AHKGetVolume(ByRef command) { +AHKGetVolume(args*) { {% block AHKGetVolume %} - device_number := command[2] + device_number := args[1] try { SoundGetWaveVolume, retval, %device_number% @@ -2409,21 +2409,21 @@ {% endblock AHKGetVolume %} } -AHKSoundBeep(ByRef command) { +AHKSoundBeep(args*) { {% block AHKSoundBeep %} - freq := command[2] - duration := command[3] + freq := args[1] + duration := args[2] SoundBeep , %freq%, %duration% return FormatNoValueResponse() {% endblock AHKSoundBeep %} } -AHKSoundGet(ByRef command) { +AHKSoundGet(args*) { {% block AHKSoundGet %} - device_number := command[2] - component_type := command[3] - control_type := command[4] + device_number := args[1] + component_type := args[2] + control_type := args[3] SoundGet, retval, %component_type%, %control_type%, %device_number% ; TODO interpret return type @@ -2431,29 +2431,29 @@ {% endblock AHKSoundGet %} } -AHKSoundSet(ByRef command) { +AHKSoundSet(args*) { {% block AHKSoundSet %} - device_number := command[2] - component_type := command[3] - control_type := command[4] - value := command[5] + 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(ByRef command) { +AHKSoundPlay(args*) { {% block AHKSoundPlay %} - filename := command[2] + filename := args[1] SoundPlay, %filename% return FormatNoValueResponse() {% endblock AHKSoundPlay %} } -AHKSetVolume(ByRef command) { +AHKSetVolume(args*) { {% block AHKSetVolume %} - device_number := command[2] - value := command[3] + device_number := args[1] + value := args[2] SoundSetWaveVolume, %value%, %device_number% return FormatNoValueResponse() {% endblock AHKSetVolume %} @@ -2466,71 +2466,71 @@ return count } -AHKEcho(ByRef command) { +AHKEcho(args*) { {% block AHKEcho %} - arg := command[2] + arg := args[1] return FormatResponse("ahk.message.StringResponseMessage", arg) {% endblock AHKEcho %} } -AHKTraytip(ByRef command) { +AHKTraytip(args*) { {% block AHKTraytip %} - title := command[2] - text := command[3] - second := command[4] - option := command[5] + title := args[1] + text := args[2] + second := args[3] + option := args[4] TrayTip, %title%, %text%, %second%, %option% return FormatNoValueResponse() {% endblock AHKTraytip %} } -AHKShowToolTip(ByRef command) { +AHKShowToolTip(args*) { {% block AHKShowToolTip %} - text := command[2] - x := command[3] - y := command[4] - which := command[5] + text := args[1] + x := args[2] + y := args[3] + which := args[4] ToolTip, %text%, %x%, %y%, %which% return FormatNoValueResponse() {% endblock AHKShowToolTip %} } -AHKGetClipboard(ByRef command) { +AHKGetClipboard(args*) { {% block AHKGetClipboard %} return FormatResponse("ahk.message.StringResponseMessage", Clipboard) {% endblock AHKGetClipboard %} } -AHKGetClipboardAll(ByRef command) { +AHKGetClipboardAll(args*) { {% block AHKGetClipboardAll %} data := ClipboardAll return FormatBinaryResponse(data) {% endblock AHKGetClipboardAll %} } -AHKSetClipboard(ByRef command) { +AHKSetClipboard(args*) { {% block AHKSetClipboard %} - text := command[2] + text := args[1] Clipboard := text return FormatNoValueResponse() {% endblock AHKSetClipboard %} } -AHKSetClipboardAll(ByRef command) { +AHKSetClipboardAll(args*) { {% block AHKSetClipboardAll %} ; TODO there should be a way for us to accept a base64 string instead - filename := command[2] + filename := args[1] FileRead, Clipboard, %filename% return FormatNoValueResponse() {% endblock AHKSetClipboardAll %} } -AHKClipWait(ByRef command) { +AHKClipWait(args*) { - timeout := command[2] - wait_for_any_data := command[3] + timeout := args[1] + wait_for_any_data := args[2] ClipWait, %timeout%, %wait_for_any_data% @@ -2540,45 +2540,45 @@ return FormatNoValueResponse() } -AHKBlockInput(ByRef command) { - value := command[2] +AHKBlockInput(args*) { + value := args[1] BlockInput, %value% return FormatNoValueResponse() } -AHKMenuTrayTip(ByRef command) { - value := command[2] +AHKMenuTrayTip(args*) { + value := args[1] Menu, Tray, Tip, %value% return FormatNoValueResponse() } -AHKMenuTrayShow(ByRef command) { +AHKMenuTrayShow(args*) { Menu, Tray, Icon return FormatNoValueResponse() } -AHKMenuTrayIcon(ByRef command) { - filename := command[2] - icon_number := command[3] - freeze := command[4] +AHKMenuTrayIcon(args*) { + filename := args[1] + icon_number := args[2] + freeze := args[3] Menu, Tray, Icon, %filename%, %icon_number%,%freeze% return FormatNoValueResponse() } -AHKGuiNew(ByRef command) { +AHKGuiNew(args*) { - options := command[2] - title := command[3] + options := args[1] + title := args[2] Gui, New, %options%, %title% return FormatResponse("ahk.message.StringResponseMessage", hwnd) } -AHKMsgBox(ByRef command) { +AHKMsgBox(args*) { - options := command[2] - title := command[3] - text := command[4] - timeout := command[5] + 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") @@ -2603,18 +2603,18 @@ return ret } -AHKInputBox(ByRef command) { +AHKInputBox(args*) { - title := command[2] - prompt := command[3] - hide := command[4] - width := command[5] - height := command[6] - x := command[7] - y := command[8] - locale := command[9] - timeout := command[10] - default := command[11] + 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) { @@ -2627,12 +2627,12 @@ return ret } -AHKFileSelectFile(byRef command) { +AHKFileSelectFile(byRef args) { - options := command[2] - root := command[3] - title := command[4] - filter := command[5] + options := args[1] + root := args[2] + title := args[3] + filter := args[4] FileSelectFile, output, %options%, %root%, %title%, %filter% if (ErrorLevel = 1) { ret := FormatNoValueResponse() @@ -2642,11 +2642,11 @@ return ret } -AHKFileSelectFolder(byRef command) { +AHKFileSelectFolder(byRef args) { - starting_folder := command[2] - options := command[3] - prompt := command[4] + starting_folder := args[1] + options := args[2] + prompt := args[3] FileSelectFolder, output, %starting_folder%, %options%, %prompt% @@ -2765,12 +2765,13 @@ Loop { query := RTrim(stdin.ReadLine(), "`n") - commandArray := CommandArrayFromQuery(query) + argsArray := CommandArrayFromQuery(query) try { - func := commandArray[1] + func := argsArray[1] + argsArray.RemoveAt(1) {% block before_function %} {% endblock before_function %} - pyresp := %func%(commandArray) + pyresp := %func%(argsArray*) {% block after_function %} {% endblock after_function %} } catch e { @@ -2945,18 +2946,18 @@ return FormatResponse("ahk.message.B64BinaryResponseMessage", b64) } -AHKSetDetectHiddenWindows(command) { +AHKSetDetectHiddenWindows(args*) { {% block AHKSetDetectHiddenWindows %} - value := command[2] + value := args[1] DetectHiddenWindows(value) return FormatNoValueResponse() {% endblock AHKSetDetectHiddenWindows %} } -AHKSetTitleMatchMode(command) { +AHKSetTitleMatchMode(args*) { {% block AHKSetTitleMatchMode %} - val1 := command[2] - val2 := command[3] + val1 := args[1] + val2 := args[2] if (val1 != "") { SetTitleMatchMode(val1) } @@ -2967,45 +2968,45 @@ {% endblock AHKSetTitleMatchMode %} } -AHKGetTitleMatchMode(command) { +AHKGetTitleMatchMode(args*) { {% block AHKGetTitleMatchMode %} return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) {% endblock AHKGetTitleMatchMode %} } -AHKGetTitleMatchSpeed(command) { +AHKGetTitleMatchSpeed(args*) { {% block AHKGetTitleMatchSpeed %} return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) {% endblock AHKGetTitleMatchSpeed %} } -AHKSetSendLevel(command) { +AHKSetSendLevel(args*) { {% block AHKSetSendLevel %} - level := command[2] + level := args[1] SendLevel(level) return FormatNoValueResponse() {% endblock AHKSetSendLevel %} } -AHKGetSendLevel(command) { +AHKGetSendLevel(args*) { {% block AHKGetSendLevel %} return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) {% endblock AHKGetSendLevel %} } -AHKWinExist(command) { +AHKWinExist(args*) { {% block AHKWinExist %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3036,16 +3037,16 @@ {% endblock AHKWinExist %} } -AHKWinClose(command) { +AHKWinClose(args*) { {% block AHKWinClose %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - secondstowait := command[9] + 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) @@ -3076,16 +3077,16 @@ {% endblock AHKWinClose %} } -AHKWinKill(command) { +AHKWinKill(args*) { {% block AHKWinKill %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - secondstowait := command[9] + 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) @@ -3116,17 +3117,17 @@ {% endblock AHKWinKill %} } -AHKWinWait(command) { +AHKWinWait(args*) { {% block AHKWinWait %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -3162,17 +3163,17 @@ {% endblock AHKWinWait %} } -AHKWinWaitActive(command) { +AHKWinWaitActive(args*) { {% block AHKWinWaitActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -3208,17 +3209,17 @@ {% endblock AHKWinWaitActive %} } -AHKWinWaitNotActive(command) { +AHKWinWaitNotActive(args*) { {% block AHKWinWaitNotActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -3255,17 +3256,17 @@ {% endblock AHKWinWaitNotActive %} } -AHKWinWaitClose(command) { +AHKWinWaitClose(args*) { {% block AHKWinWaitClose %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -3300,15 +3301,15 @@ {% endblock AHKWinWaitClose %} } -AHKWinMinimize(command) { +AHKWinMinimize(args*) { {% block AHKWinMinimize %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3335,15 +3336,15 @@ {% endblock AHKWinMinimize %} } -AHKWinMaximize(command) { +AHKWinMaximize(args*) { {% block AHKWinMaximize %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3370,15 +3371,15 @@ {% endblock AHKWinMaximize %} } -AHKWinRestore(command) { +AHKWinRestore(args*) { {% block AHKWinRestore %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3405,16 +3406,16 @@ {% endblock AHKWinRestore %} } -AHKWinIsActive(command) { +AHKWinIsActive(args*) { {% block AHKWinIsActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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 != "") { @@ -3445,16 +3446,16 @@ {% endblock AHKWinIsActive %} } -AHKWinGetID(command) { +AHKWinGetID(args*) { {% block AHKWinGetID %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3487,16 +3488,16 @@ {% endblock AHKWinGetID %} } -AHKWinGetTitle(command) { +AHKWinGetTitle(args*) { {% block AHKWinGetTitle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3524,16 +3525,16 @@ {% endblock AHKWinGetTitle %} } -AHKWinGetIDLast(command) { +AHKWinGetIDLast(args*) { {% block AHKWinGetIDLast %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3567,16 +3568,16 @@ {% endblock AHKWinGetIDLast %} } -AHKWinGetPID(command) { +AHKWinGetPID(args*) { {% block AHKWinGetPID %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3609,16 +3610,16 @@ {% endblock AHKWinGetPID %} } -AHKWinGetProcessName(command) { +AHKWinGetProcessName(args*) { {% block AHKWinGetProcessName %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3652,16 +3653,16 @@ {% endblock AHKWinGetProcessName %} } -AHKWinGetProcessPath(command) { +AHKWinGetProcessPath(args*) { {% block AHKWinGetProcessPath %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3694,16 +3695,16 @@ {% endblock AHKWinGetProcessPath %} } -AHKWinGetCount(command) { +AHKWinGetCount(args*) { {% block AHKWinGetCount %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3737,16 +3738,16 @@ {% endblock AHKWinGetCount %} } -AHKWinGetMinMax(command) { +AHKWinGetMinMax(args*) { {% block AHKWinGetMinMax %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3780,16 +3781,16 @@ {% endblock AHKWinGetMinMax %} } -AHKWinGetControlList(command) { +AHKWinGetControlList(args*) { {% block AHKWinGetControlList %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3839,16 +3840,16 @@ {% endblock AHKWinGetControlList %} } -AHKWinGetTransparent(command) { +AHKWinGetTransparent(args*) { {% block AHKWinGetTransparent %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3876,16 +3877,16 @@ return response {% endblock AHKWinGetTransparent %} } -AHKWinGetTransColor(command) { +AHKWinGetTransColor(args*) { {% block AHKWinGetTransColor %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3913,16 +3914,16 @@ return response {% endblock AHKWinGetTransColor %} } -AHKWinGetStyle(command) { +AHKWinGetStyle(args*) { {% block AHKWinGetStyle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3952,16 +3953,16 @@ {% endblock AHKWinGetStyle %} } -AHKWinGetExStyle(command) { +AHKWinGetExStyle(args*) { {% block AHKWinGetExStyle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -3990,16 +3991,16 @@ {% endblock AHKWinGetExStyle %} } -AHKWinGetText(command) { +AHKWinGetText(args*) { {% block AHKWinGetText %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -4029,16 +4030,16 @@ {% endblock AHKWinGetText %} } -AHKWinSetTitle(command) { +AHKWinSetTitle(args*) { {% block AHKWinSetTitle %} - new_title := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -4065,16 +4066,16 @@ {% endblock AHKWinSetTitle %} } -AHKWinSetAlwaysOnTop(command) { +AHKWinSetAlwaysOnTop(args*) { {% block AHKWinSetAlwaysOnTop %} - toggle := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -4110,15 +4111,15 @@ {% endblock AHKWinSetAlwaysOnTop %} } -AHKWinSetBottom(command) { +AHKWinSetBottom(args*) { {% block AHKWinSetBottom %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -4146,15 +4147,15 @@ {% endblock AHKWinSetBottom %} } -AHKWinShow(command) { +AHKWinShow(args*) { {% block AHKWinShow %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -4182,15 +4183,15 @@ {% endblock AHKWinShow %} } -AHKWinHide(command) { +AHKWinHide(args*) { {% block AHKWinHide %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -4218,15 +4219,15 @@ {% endblock AHKWinHide %} } -AHKWinSetTop(command) { +AHKWinSetTop(args*) { {% block AHKWinSetTop %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -4254,15 +4255,15 @@ {% endblock AHKWinSetTop %} } -AHKWinSetEnable(command) { +AHKWinSetEnable(args*) { {% block AHKWinSetEnable %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -4290,15 +4291,15 @@ {% endblock AHKWinSetEnable %} } -AHKWinSetDisable(command) { +AHKWinSetDisable(args*) { {% block AHKWinSetDisable %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -4326,15 +4327,15 @@ {% endblock AHKWinSetDisable %} } -AHKWinSetRedraw(command) { +AHKWinSetRedraw(args*) { {% block AHKWinSetRedraw %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -4362,17 +4363,17 @@ {% endblock AHKWinSetRedraw %} } -AHKWinSetStyle(command) { +AHKWinSetStyle(args*) { {% block AHKWinSetStyle %} - style := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -4399,17 +4400,17 @@ {% endblock AHKWinSetStyle %} } -AHKWinSetExStyle(command) { +AHKWinSetExStyle(args*) { {% block AHKWinSetExStyle %} - style := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -4437,17 +4438,17 @@ {% endblock AHKWinSetExStyle %} } -AHKWinSetRegion(command) { +AHKWinSetRegion(args*) { {% block AHKWinSetRegion %} - options := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -4474,17 +4475,17 @@ {% endblock AHKWinSetRegion %} } -AHKWinSetTransparent(command) { +AHKWinSetTransparent(args*) { {% block AHKWinSetTransparent %} - transparency := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -4511,17 +4512,17 @@ {% endblock AHKWinSetTransparent %} } -AHKWinSetTransColor(command) { +AHKWinSetTransColor(args*) { {% block AHKWinSetTransColor %} - color := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -4548,15 +4549,15 @@ {% endblock AHKWinSetTransColor %} } -AHKImageSearch(command) { +AHKImageSearch(args*) { {% block AHKImageSearch %} - imagepath := command[6] - x1 := command[2] - y1 := command[3] - x2 := command[4] - y2 := command[5] - coord_mode := command[7] + imagepath := args[5] + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + coord_mode := args[6] current_mode := Format("{}", A_CoordModePixel) @@ -4588,13 +4589,13 @@ {% endblock AHKImageSearch %} } -AHKPixelGetColor(command) { +AHKPixelGetColor(args*) { {% block AHKPixelGetColor %} - x := command[2] - y := command[3] - coord_mode := command[4] - options := command[5] + x := args[1] + y := args[2] + coord_mode := args[3] + options := args[4] current_mode := Format("{}", A_CoordModePixel) @@ -4615,17 +4616,17 @@ {% endblock AHKPixelGetColor %} } -AHKPixelSearch(command) { +AHKPixelSearch(args*) { {% block AHKPixelSearch %} - x1 := command[2] - y1 := command[3] - x2 := command[4] - y2 := command[5] - color := command[6] - variation := command[7] - options := command[8] - coord_mode := command[9] + 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) @@ -4651,10 +4652,10 @@ {% endblock AHKPixelSearch %} } -AHKMouseGetPos(command) { +AHKMouseGetPos(args*) { {% block AHKMouseGetPos %} - coord_mode := command[2] + coord_mode := args[1] current_coord_mode := Format("{}", A_CoordModeMouse) if (coord_mode != "") { CoordMode("Mouse", coord_mode) @@ -4672,11 +4673,11 @@ {% endblock AHKMouseGetPos %} } -AHKKeyState(command) { +AHKKeyState(args*) { {% block AHKKeyState %} - keyname := command[2] - mode := command[3] + keyname := args[1] + mode := args[2] if (mode != "") { state := GetKeyState(keyname, mode) } else{ @@ -4701,12 +4702,12 @@ {% endblock AHKKeyState %} } -AHKMouseMove(command) { +AHKMouseMove(args*) { {% block AHKMouseMove %} - x := command[2] - y := command[3] - speed := command[4] - relative := command[5] + x := args[1] + y := args[2] + speed := args[3] + relative := args[4] if (relative != "") { MouseMove(x, y, speed, "R") } else { @@ -4717,15 +4718,15 @@ {% endblock AHKMouseMove %} } -AHKClick(command) { +AHKClick(args*) { {% block AHKClick %} - x := command[2] - y := command[3] - button := command[4] - click_count := command[5] - direction := command[6] - r := command[7] - relative_to := command[8] + x := args[1] + y := args[2] + button := args[3] + click_count := args[4] + direction := args[5] + r := args[6] + relative_to := args[7] current_coord_rel := Format("{}", A_CoordModeMouse) if (relative_to != "") { @@ -4743,10 +4744,10 @@ {% endblock AHKClick %} } -AHKGetCoordMode(command) { +AHKGetCoordMode(args*) { {% block AHKGetCoordMode %} - target := command[2] + target := args[1] if (target = "ToolTip") { return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeToolTip) @@ -4767,26 +4768,26 @@ {% endblock AHKGetCoordMode %} } -AHKSetCoordMode(command) { +AHKSetCoordMode(args*) { {% block AHKSetCoordMode %} - target := command[2] - relative_to := command[3] + target := args[1] + relative_to := args[2] CoordMode(target, relative_to) return FormatNoValueResponse() {% endblock AHKSetCoordMode %} } -AHKMouseClickDrag(command) { +AHKMouseClickDrag(args*) { {% block AHKMouseClickDrag %} - button := command[2] - x1 := command[3] - y1 := command[4] - x2 := command[5] - y2 := command[6] - speed := command[7] - relative := command[8] - relative_to := command[9] + 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] current_coord_rel := Format("{}", A_CoordModeMouse) @@ -4805,11 +4806,11 @@ {% endblock AHKMouseClickDrag %} } -AHKRegRead(command) { +AHKRegRead(args*) { {% block RegRead %} - key_name := command[2] - value_name := command[3] + key_name := args[1] + value_name := args[2] output := RegRead(key_name, value_name) resp := FormatResponse("ahk.message.StringResponseMessage", Format("{}", output)) @@ -4817,12 +4818,12 @@ {% endblock RegRead %} } -AHKRegWrite(command) { +AHKRegWrite(args*) { {% block RegWrite %} - value_type := command[2] - key_name := command[3] - value_name := command[4] - value := command[5] + 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) @@ -4833,11 +4834,11 @@ {% endblock RegWrite %} } -AHKRegDelete(command) { +AHKRegDelete(args*) { {% block RegDelete %} - key_name := command[2] - value_name := command[3] + key_name := args[1] + value_name := args[2] if (value_name != "") { RegDelete(key_name, value_name) } else { @@ -4848,31 +4849,31 @@ {% endblock RegDelete %} } -AHKKeyWait(command) { +AHKKeyWait(args*) { {% block AHKKeyWait %} - keyname := command[2] - if (command.Length = 2) { + keyname := args[1] + if (args.Length = 2) { ret := KeyWait(keyname) } else { - options := command[3] + options := args[2] ret := KeyWait(keyname, options) } return FormatResponse("ahk.message.IntegerResponseMessage", ret) {% endblock AHKKeyWait %} } -;SetKeyDelay(command) { +;SetKeyDelay(args*) { ; {% block SetKeyDelay %} -; SetKeyDelay(command[2], command[3]) +; SetKeyDelay(args[1], args[2]) ; {% endblock SetKeyDelay %} ;} -AHKSend(command) { +AHKSend(args*) { {% block AHKSend %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -4890,11 +4891,11 @@ {% endblock AHKSend %} } -AHKSendRaw(command) { +AHKSendRaw(args*) { {% block AHKSendRaw %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -4911,11 +4912,11 @@ {% endblock AHKSendRaw %} } -AHKSendInput(command) { +AHKSendInput(args*) { {% block AHKSendInput %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -4932,11 +4933,11 @@ {% endblock AHKSendInput %} } -AHKSendEvent(command) { +AHKSendEvent(args*) { {% block AHKSendEvent %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -4953,11 +4954,11 @@ {% endblock AHKSendEvent %} } -AHKSendPlay(command) { +AHKSendPlay(args*) { {% block AHKSendPlay %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelayPlay) current_key_duration := Format("{}", A_KeyDurationPlay) @@ -4974,9 +4975,9 @@ {% endblock AHKSendPlay %} } -AHKSetCapsLockState(command) { +AHKSetCapsLockState(args*) { {% block AHKSetCapsLockState %} - state := command[2] + state := args[1] if (state = "") { SetCapsLockState(!GetKeyState("CapsLock", "T")) } else { @@ -4986,7 +4987,7 @@ {% endblock AHKSetCapsLockState %} } -HideTrayTip(command) { +HideTrayTip(args*) { {% block HideTrayTip %} TrayTip ; Attempt to hide it the normal way. if SubStr(A_OSVersion,1,3) = "10." { @@ -4997,16 +4998,16 @@ {% endblock HideTrayTip %} } -AHKWinGetClass(command) { +AHKWinGetClass(args*) { {% block AHKWinGetClass %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -5035,15 +5036,15 @@ {% endblock AHKWinGetClass %} } -AHKWinActivate(command) { +AHKWinActivate(args*) { {% block AHKWinActivate %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -5072,18 +5073,18 @@ {% endblock AHKWinActivate %} } -AHKWindowList(command) { +AHKWindowList(args*) { {% block AHKWindowList %} current_detect_hw := Format("{}", A_DetectHiddenWindows) - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -5114,20 +5115,20 @@ {% endblock AHKWindowList %} } -AHKControlClick(command) { +AHKControlClick(args*) { {% block AHKControlClick %} - ctrl := command[2] - title := command[3] - text := command[4] - button := command[5] - click_count := command[6] - options := command[7] - exclude_title := command[8] - exclude_text := command[9] - detect_hw := command[10] - match_mode := command[11] - match_speed := command[12] + 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) @@ -5156,17 +5157,17 @@ {% endblock AHKControlClick %} } -AHKControlGetText(command) { +AHKControlGetText(args*) { {% block AHKControlGetText %} - ctrl := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -5196,17 +5197,17 @@ {% endblock AHKControlGetText %} } -AHKControlGetPos(command) { +AHKControlGetPos(args*) { {% block AHKControlGetPos %} - ctrl := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -5236,17 +5237,17 @@ {% endblock AHKControlGetPos %} } -AHKControlSend(command) { +AHKControlSend(args*) { {% block AHKControlSend %} - ctrl := command[2] - keys := command[3] - title := command[4] - text := command[5] - extitle := command[6] - extext := command[7] - detect_hw := command[8] - match_mode := command[9] - match_speed := command[10] + 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) @@ -5278,7 +5279,7 @@ {% endblock AHKControlSend %} } -AHKWinFromMouse(command) { +AHKWinFromMouse(args*) { {% block AHKWinFromMouse %} MouseGetPos(,, &MouseWin) @@ -5291,10 +5292,10 @@ {% endblock AHKWinFromMouse %} } -AHKWinIsAlwaysOnTop(command) { +AHKWinIsAlwaysOnTop(args*) { {% block AHKWinIsAlwaysOnTop %} ; TODO: detect hidden windows / etc? - title := command[2] + title := args[1] ExStyle := WinGetExStyle(title) if (ExStyle = "") return FormatNoValueResponse() @@ -5306,19 +5307,19 @@ {% endblock AHKWinIsAlwaysOnTop %} } -AHKWinMove(command) { +AHKWinMove(args*) { {% block AHKWinMove %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - x := command[9] - y := command[10] - width := command[11] - height := command[12] + 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) @@ -5347,16 +5348,16 @@ {% endblock AHKWinMove %} } -AHKWinGetPos(command) { +AHKWinGetPos(args*) { {% block AHKWinGetPos %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -5387,10 +5388,10 @@ {% endblock AHKWinGetPos %} } -AHKGetVolume(command) { +AHKGetVolume(args*) { {% block AHKGetVolume %} - device_number := command[2] + device_number := args[1] retval := SoundGetVolume(,device_number) response := FormatResponse("ahk.message.FloatResponseMessage", Format("{}", retval)) @@ -5398,111 +5399,111 @@ {% endblock AHKGetVolume %} } -AHKSoundBeep(command) { +AHKSoundBeep(args*) { {% block AHKSoundBeep %} - freq := command[2] - duration := command[3] + freq := args[1] + duration := args[2] SoundBeep(freq, duration) return FormatNoValueResponse() {% endblock AHKSoundBeep %} } -AHKSoundGet(command) { +AHKSoundGet(args*) { {% block AHKSoundGet %} return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundGet is not supported in ahk v2") {% endblock AHKSoundGet %} } -AHKSoundSet(command) { +AHKSoundSet(args*) { {% block AHKSoundSet %} return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundSet is not supported in ahk v2") {% endblock AHKSoundSet %} } -AHKSoundPlay(command) { +AHKSoundPlay(args*) { {% block AHKSoundPlay %} - filename := command[2] + filename := args[1] SoundPlay(filename) return FormatNoValueResponse() {% endblock AHKSoundPlay %} } -AHKSetVolume(command) { +AHKSetVolume(args*) { {% block AHKSetVolume %} - device_number := command[2] - value := command[3] + device_number := args[1] + value := args[2] SoundSetVolume(value,,device_number) return FormatNoValueResponse() {% endblock AHKSetVolume %} } -AHKEcho(command) { +AHKEcho(args*) { {% block AHKEcho %} - arg := command[2] + arg := args[1] return FormatResponse("ahk.message.StringResponseMessage", arg) {% endblock AHKEcho %} } -AHKTraytip(command) { +AHKTraytip(args*) { {% block AHKTraytip %} - title := command[2] - text := command[3] - second := command[4] - option := command[5] + title := args[1] + text := args[2] + second := args[3] + option := args[4] TrayTip(title, text, option) return FormatNoValueResponse() {% endblock AHKTraytip %} } -AHKShowToolTip(command) { +AHKShowToolTip(args*) { {% block AHKShowToolTip %} - text := command[2] - x := command[3] - y := command[4] - which := command[5] + text := args[1] + x := args[2] + y := args[3] + which := args[4] ToolTip(text, x, y, which) return FormatNoValueResponse() {% endblock AHKShowToolTip %} } -AHKGetClipboard(command) { +AHKGetClipboard(args*) { {% block AHKGetClipboard %} return FormatResponse("ahk.message.StringResponseMessage", A_Clipboard) {% endblock AHKGetClipboard %} } -AHKGetClipboardAll(command) { +AHKGetClipboardAll(args*) { {% block AHKGetClipboardAll %} data := ClipboardAll() return FormatBinaryResponse(&data) {% endblock AHKGetClipboardAll %} } -AHKSetClipboard(command) { +AHKSetClipboard(args*) { {% block AHKSetClipboard %} - text := command[2] + text := args[1] A_Clipboard := text return FormatNoValueResponse() {% endblock AHKSetClipboard %} } -AHKSetClipboardAll(command) { +AHKSetClipboardAll(args*) { {% block AHKSetClipboardAll %} ; TODO there should be a way for us to accept a base64 string instead - filename := command[2] + filename := args[1] contents := FileRead(filename, "RAW") A_Clipboard := ClipboardAll(contents) return FormatNoValueResponse() {% endblock AHKSetClipboardAll %} } -AHKClipWait(command) { +AHKClipWait(args*) { - timeout := command[2] - wait_for_any_data := command[3] + timeout := args[1] + wait_for_any_data := args[2] if ClipWait(timeout, wait_for_any_data) return FormatNoValueResponse() @@ -5511,45 +5512,45 @@ return FormatNoValueResponse() } -AHKBlockInput(command) { - value := command[2] +AHKBlockInput(args*) { + value := args[1] BlockInput(value) return FormatNoValueResponse() } -AHKMenuTrayTip(command) { - value := command[2] +AHKMenuTrayTip(args*) { + value := args[1] A_IconTip := value return FormatNoValueResponse() } -AHKMenuTrayShow(command) { +AHKMenuTrayShow(args*) { A_IconHidden := 0 return FormatNoValueResponse() } -AHKMenuTrayIcon(command) { - filename := command[2] - icon_number := command[3] - freeze := command[4] +AHKMenuTrayIcon(args*) { + filename := args[1] + icon_number := args[2] + freeze := args[3] TraySetIcon(filename, icon_number, freeze) return FormatNoValueResponse() } -;AHKGuiNew(command) { +;AHKGuiNew(args*) { ; -; options := command[2] -; title := command[3] +; options := args[1] +; title := args[2] ; Gui(New, options, title) ; return FormatResponse("ahk.message.StringResponseMessage", hwnd) ;} -AHKMsgBox(command) { +AHKMsgBox(args*) { - options := command[2] - title := command[3] - text := command[4] - timeout := command[5] + options := args[1] + title := args[2] + text := args[3] + timeout := args[4] if (timeout != "") { options := "" options " T" timeout } @@ -5562,18 +5563,18 @@ return ret } -AHKInputBox(command) { +AHKInputBox(args*) { - title := command[2] - prompt := command[3] - hide := command[4] - width := command[5] - height := command[6] - x := command[7] - y := command[8] - locale := command[9] - timeout := command[10] - default := command[11] + 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 := "" @@ -5591,12 +5592,12 @@ return ret } -AHKFileSelectFile(command) { +AHKFileSelectFile(args*) { - options := command[2] - root := command[3] - title := command[4] - filter := command[5] + options := args[1] + root := args[2] + title := args[3] + filter := args[4] output := FileSelect(options, root, title, filter) if (output = "") { ret := FormatNoValueResponse() @@ -5621,11 +5622,11 @@ return ret } -AHKFileSelectFolder(command) { +AHKFileSelectFolder(args*) { - starting_folder := command[2] - options := command[3] - prompt := command[4] + starting_folder := args[1] + options := args[2] + prompt := args[3] output := DirSelect(starting_folder, options, prompt) @@ -5743,12 +5744,13 @@ Loop { query := RTrim(stdin.ReadLine(), "`n") - commandArray := CommandArrayFromQuery(query) + argsArray := CommandArrayFromQuery(query) try { - func_name := commandArray[1] + func_name := argsArray[1] + argsArray.RemoveAt(1) {% block before_function %} {% endblock before_function %} - pyresp := %func_name%(commandArray) + pyresp := %func_name%(argsArray*) {% block after_function %} {% endblock after_function %} } catch Any as e { diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 9e52bb70..e9ca86af 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -47,18 +47,18 @@ FormatBinaryResponse(bin) { return FormatResponse("ahk.message.B64BinaryResponseMessage", b64) } -AHKSetDetectHiddenWindows(command) { +AHKSetDetectHiddenWindows(args*) { {% block AHKSetDetectHiddenWindows %} - value := command[2] + value := args[1] DetectHiddenWindows(value) return FormatNoValueResponse() {% endblock AHKSetDetectHiddenWindows %} } -AHKSetTitleMatchMode(command) { +AHKSetTitleMatchMode(args*) { {% block AHKSetTitleMatchMode %} - val1 := command[2] - val2 := command[3] + val1 := args[1] + val2 := args[2] if (val1 != "") { SetTitleMatchMode(val1) } @@ -69,45 +69,45 @@ AHKSetTitleMatchMode(command) { {% endblock AHKSetTitleMatchMode %} } -AHKGetTitleMatchMode(command) { +AHKGetTitleMatchMode(args*) { {% block AHKGetTitleMatchMode %} return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) {% endblock AHKGetTitleMatchMode %} } -AHKGetTitleMatchSpeed(command) { +AHKGetTitleMatchSpeed(args*) { {% block AHKGetTitleMatchSpeed %} return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) {% endblock AHKGetTitleMatchSpeed %} } -AHKSetSendLevel(command) { +AHKSetSendLevel(args*) { {% block AHKSetSendLevel %} - level := command[2] + level := args[1] SendLevel(level) return FormatNoValueResponse() {% endblock AHKSetSendLevel %} } -AHKGetSendLevel(command) { +AHKGetSendLevel(args*) { {% block AHKGetSendLevel %} return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) {% endblock AHKGetSendLevel %} } -AHKWinExist(command) { +AHKWinExist(args*) { {% block AHKWinExist %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -138,16 +138,16 @@ AHKWinExist(command) { {% endblock AHKWinExist %} } -AHKWinClose(command) { +AHKWinClose(args*) { {% block AHKWinClose %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - secondstowait := command[9] + 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) @@ -178,16 +178,16 @@ AHKWinClose(command) { {% endblock AHKWinClose %} } -AHKWinKill(command) { +AHKWinKill(args*) { {% block AHKWinKill %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - secondstowait := command[9] + 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) @@ -218,17 +218,17 @@ AHKWinKill(command) { {% endblock AHKWinKill %} } -AHKWinWait(command) { +AHKWinWait(args*) { {% block AHKWinWait %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -264,17 +264,17 @@ AHKWinWait(command) { {% endblock AHKWinWait %} } -AHKWinWaitActive(command) { +AHKWinWaitActive(args*) { {% block AHKWinWaitActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -310,17 +310,17 @@ AHKWinWaitActive(command) { {% endblock AHKWinWaitActive %} } -AHKWinWaitNotActive(command) { +AHKWinWaitNotActive(args*) { {% block AHKWinWaitNotActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -357,17 +357,17 @@ AHKWinWaitNotActive(command) { {% endblock AHKWinWaitNotActive %} } -AHKWinWaitClose(command) { +AHKWinWaitClose(args*) { {% block AHKWinWaitClose %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -402,15 +402,15 @@ AHKWinWaitClose(command) { {% endblock AHKWinWaitClose %} } -AHKWinMinimize(command) { +AHKWinMinimize(args*) { {% block AHKWinMinimize %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -437,15 +437,15 @@ AHKWinMinimize(command) { {% endblock AHKWinMinimize %} } -AHKWinMaximize(command) { +AHKWinMaximize(args*) { {% block AHKWinMaximize %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -472,15 +472,15 @@ AHKWinMaximize(command) { {% endblock AHKWinMaximize %} } -AHKWinRestore(command) { +AHKWinRestore(args*) { {% block AHKWinRestore %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -507,16 +507,16 @@ AHKWinRestore(command) { {% endblock AHKWinRestore %} } -AHKWinIsActive(command) { +AHKWinIsActive(args*) { {% block AHKWinIsActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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 != "") { @@ -547,16 +547,16 @@ AHKWinIsActive(command) { {% endblock AHKWinIsActive %} } -AHKWinGetID(command) { +AHKWinGetID(args*) { {% block AHKWinGetID %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -589,16 +589,16 @@ AHKWinGetID(command) { {% endblock AHKWinGetID %} } -AHKWinGetTitle(command) { +AHKWinGetTitle(args*) { {% block AHKWinGetTitle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -626,16 +626,16 @@ AHKWinGetTitle(command) { {% endblock AHKWinGetTitle %} } -AHKWinGetIDLast(command) { +AHKWinGetIDLast(args*) { {% block AHKWinGetIDLast %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -669,16 +669,16 @@ AHKWinGetIDLast(command) { {% endblock AHKWinGetIDLast %} } -AHKWinGetPID(command) { +AHKWinGetPID(args*) { {% block AHKWinGetPID %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -711,16 +711,16 @@ AHKWinGetPID(command) { {% endblock AHKWinGetPID %} } -AHKWinGetProcessName(command) { +AHKWinGetProcessName(args*) { {% block AHKWinGetProcessName %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -754,16 +754,16 @@ AHKWinGetProcessName(command) { {% endblock AHKWinGetProcessName %} } -AHKWinGetProcessPath(command) { +AHKWinGetProcessPath(args*) { {% block AHKWinGetProcessPath %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -796,16 +796,16 @@ AHKWinGetProcessPath(command) { {% endblock AHKWinGetProcessPath %} } -AHKWinGetCount(command) { +AHKWinGetCount(args*) { {% block AHKWinGetCount %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -839,16 +839,16 @@ AHKWinGetCount(command) { {% endblock AHKWinGetCount %} } -AHKWinGetMinMax(command) { +AHKWinGetMinMax(args*) { {% block AHKWinGetMinMax %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -882,16 +882,16 @@ AHKWinGetMinMax(command) { {% endblock AHKWinGetMinMax %} } -AHKWinGetControlList(command) { +AHKWinGetControlList(args*) { {% block AHKWinGetControlList %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -941,16 +941,16 @@ AHKWinGetControlList(command) { {% endblock AHKWinGetControlList %} } -AHKWinGetTransparent(command) { +AHKWinGetTransparent(args*) { {% block AHKWinGetTransparent %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -978,16 +978,16 @@ AHKWinGetTransparent(command) { return response {% endblock AHKWinGetTransparent %} } -AHKWinGetTransColor(command) { +AHKWinGetTransColor(args*) { {% block AHKWinGetTransColor %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1015,16 +1015,16 @@ AHKWinGetTransColor(command) { return response {% endblock AHKWinGetTransColor %} } -AHKWinGetStyle(command) { +AHKWinGetStyle(args*) { {% block AHKWinGetStyle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1054,16 +1054,16 @@ AHKWinGetStyle(command) { {% endblock AHKWinGetStyle %} } -AHKWinGetExStyle(command) { +AHKWinGetExStyle(args*) { {% block AHKWinGetExStyle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1092,16 +1092,16 @@ AHKWinGetExStyle(command) { {% endblock AHKWinGetExStyle %} } -AHKWinGetText(command) { +AHKWinGetText(args*) { {% block AHKWinGetText %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1131,16 +1131,16 @@ AHKWinGetText(command) { {% endblock AHKWinGetText %} } -AHKWinSetTitle(command) { +AHKWinSetTitle(args*) { {% block AHKWinSetTitle %} - new_title := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1167,16 +1167,16 @@ AHKWinSetTitle(command) { {% endblock AHKWinSetTitle %} } -AHKWinSetAlwaysOnTop(command) { +AHKWinSetAlwaysOnTop(args*) { {% block AHKWinSetAlwaysOnTop %} - toggle := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1212,15 +1212,15 @@ AHKWinSetAlwaysOnTop(command) { {% endblock AHKWinSetAlwaysOnTop %} } -AHKWinSetBottom(command) { +AHKWinSetBottom(args*) { {% block AHKWinSetBottom %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1248,15 +1248,15 @@ AHKWinSetBottom(command) { {% endblock AHKWinSetBottom %} } -AHKWinShow(command) { +AHKWinShow(args*) { {% block AHKWinShow %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1284,15 +1284,15 @@ AHKWinShow(command) { {% endblock AHKWinShow %} } -AHKWinHide(command) { +AHKWinHide(args*) { {% block AHKWinHide %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1320,15 +1320,15 @@ AHKWinHide(command) { {% endblock AHKWinHide %} } -AHKWinSetTop(command) { +AHKWinSetTop(args*) { {% block AHKWinSetTop %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1356,15 +1356,15 @@ AHKWinSetTop(command) { {% endblock AHKWinSetTop %} } -AHKWinSetEnable(command) { +AHKWinSetEnable(args*) { {% block AHKWinSetEnable %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1392,15 +1392,15 @@ AHKWinSetEnable(command) { {% endblock AHKWinSetEnable %} } -AHKWinSetDisable(command) { +AHKWinSetDisable(args*) { {% block AHKWinSetDisable %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1428,15 +1428,15 @@ AHKWinSetDisable(command) { {% endblock AHKWinSetDisable %} } -AHKWinSetRedraw(command) { +AHKWinSetRedraw(args*) { {% block AHKWinSetRedraw %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1464,17 +1464,17 @@ AHKWinSetRedraw(command) { {% endblock AHKWinSetRedraw %} } -AHKWinSetStyle(command) { +AHKWinSetStyle(args*) { {% block AHKWinSetStyle %} - style := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1501,17 +1501,17 @@ AHKWinSetStyle(command) { {% endblock AHKWinSetStyle %} } -AHKWinSetExStyle(command) { +AHKWinSetExStyle(args*) { {% block AHKWinSetExStyle %} - style := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1539,17 +1539,17 @@ AHKWinSetExStyle(command) { {% endblock AHKWinSetExStyle %} } -AHKWinSetRegion(command) { +AHKWinSetRegion(args*) { {% block AHKWinSetRegion %} - options := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1576,17 +1576,17 @@ AHKWinSetRegion(command) { {% endblock AHKWinSetRegion %} } -AHKWinSetTransparent(command) { +AHKWinSetTransparent(args*) { {% block AHKWinSetTransparent %} - transparency := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1613,17 +1613,17 @@ AHKWinSetTransparent(command) { {% endblock AHKWinSetTransparent %} } -AHKWinSetTransColor(command) { +AHKWinSetTransColor(args*) { {% block AHKWinSetTransColor %} - color := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1650,15 +1650,15 @@ AHKWinSetTransColor(command) { {% endblock AHKWinSetTransColor %} } -AHKImageSearch(command) { +AHKImageSearch(args*) { {% block AHKImageSearch %} - imagepath := command[6] - x1 := command[2] - y1 := command[3] - x2 := command[4] - y2 := command[5] - coord_mode := command[7] + imagepath := args[5] + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + coord_mode := args[6] current_mode := Format("{}", A_CoordModePixel) @@ -1690,13 +1690,13 @@ AHKImageSearch(command) { {% endblock AHKImageSearch %} } -AHKPixelGetColor(command) { +AHKPixelGetColor(args*) { {% block AHKPixelGetColor %} - x := command[2] - y := command[3] - coord_mode := command[4] - options := command[5] + x := args[1] + y := args[2] + coord_mode := args[3] + options := args[4] current_mode := Format("{}", A_CoordModePixel) @@ -1717,17 +1717,17 @@ AHKPixelGetColor(command) { {% endblock AHKPixelGetColor %} } -AHKPixelSearch(command) { +AHKPixelSearch(args*) { {% block AHKPixelSearch %} - x1 := command[2] - y1 := command[3] - x2 := command[4] - y2 := command[5] - color := command[6] - variation := command[7] - options := command[8] - coord_mode := command[9] + 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) @@ -1753,10 +1753,10 @@ AHKPixelSearch(command) { {% endblock AHKPixelSearch %} } -AHKMouseGetPos(command) { +AHKMouseGetPos(args*) { {% block AHKMouseGetPos %} - coord_mode := command[2] + coord_mode := args[1] current_coord_mode := Format("{}", A_CoordModeMouse) if (coord_mode != "") { CoordMode("Mouse", coord_mode) @@ -1774,11 +1774,11 @@ AHKMouseGetPos(command) { {% endblock AHKMouseGetPos %} } -AHKKeyState(command) { +AHKKeyState(args*) { {% block AHKKeyState %} - keyname := command[2] - mode := command[3] + keyname := args[1] + mode := args[2] if (mode != "") { state := GetKeyState(keyname, mode) } else{ @@ -1803,12 +1803,12 @@ AHKKeyState(command) { {% endblock AHKKeyState %} } -AHKMouseMove(command) { +AHKMouseMove(args*) { {% block AHKMouseMove %} - x := command[2] - y := command[3] - speed := command[4] - relative := command[5] + x := args[1] + y := args[2] + speed := args[3] + relative := args[4] if (relative != "") { MouseMove(x, y, speed, "R") } else { @@ -1819,15 +1819,15 @@ AHKMouseMove(command) { {% endblock AHKMouseMove %} } -AHKClick(command) { +AHKClick(args*) { {% block AHKClick %} - x := command[2] - y := command[3] - button := command[4] - click_count := command[5] - direction := command[6] - r := command[7] - relative_to := command[8] + x := args[1] + y := args[2] + button := args[3] + click_count := args[4] + direction := args[5] + r := args[6] + relative_to := args[7] current_coord_rel := Format("{}", A_CoordModeMouse) if (relative_to != "") { @@ -1845,10 +1845,10 @@ AHKClick(command) { {% endblock AHKClick %} } -AHKGetCoordMode(command) { +AHKGetCoordMode(args*) { {% block AHKGetCoordMode %} - target := command[2] + target := args[1] if (target = "ToolTip") { return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeToolTip) @@ -1869,26 +1869,26 @@ AHKGetCoordMode(command) { {% endblock AHKGetCoordMode %} } -AHKSetCoordMode(command) { +AHKSetCoordMode(args*) { {% block AHKSetCoordMode %} - target := command[2] - relative_to := command[3] + target := args[1] + relative_to := args[2] CoordMode(target, relative_to) return FormatNoValueResponse() {% endblock AHKSetCoordMode %} } -AHKMouseClickDrag(command) { +AHKMouseClickDrag(args*) { {% block AHKMouseClickDrag %} - button := command[2] - x1 := command[3] - y1 := command[4] - x2 := command[5] - y2 := command[6] - speed := command[7] - relative := command[8] - relative_to := command[9] + 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] current_coord_rel := Format("{}", A_CoordModeMouse) @@ -1907,11 +1907,11 @@ AHKMouseClickDrag(command) { {% endblock AHKMouseClickDrag %} } -AHKRegRead(command) { +AHKRegRead(args*) { {% block RegRead %} - key_name := command[2] - value_name := command[3] + key_name := args[1] + value_name := args[2] output := RegRead(key_name, value_name) resp := FormatResponse("ahk.message.StringResponseMessage", Format("{}", output)) @@ -1919,12 +1919,12 @@ AHKRegRead(command) { {% endblock RegRead %} } -AHKRegWrite(command) { +AHKRegWrite(args*) { {% block RegWrite %} - value_type := command[2] - key_name := command[3] - value_name := command[4] - value := command[5] + 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) @@ -1935,11 +1935,11 @@ AHKRegWrite(command) { {% endblock RegWrite %} } -AHKRegDelete(command) { +AHKRegDelete(args*) { {% block RegDelete %} - key_name := command[2] - value_name := command[3] + key_name := args[1] + value_name := args[2] if (value_name != "") { RegDelete(key_name, value_name) } else { @@ -1950,31 +1950,31 @@ AHKRegDelete(command) { {% endblock RegDelete %} } -AHKKeyWait(command) { +AHKKeyWait(args*) { {% block AHKKeyWait %} - keyname := command[2] - if (command.Length = 2) { + keyname := args[1] + if (args.Length = 2) { ret := KeyWait(keyname) } else { - options := command[3] + options := args[2] ret := KeyWait(keyname, options) } return FormatResponse("ahk.message.IntegerResponseMessage", ret) {% endblock AHKKeyWait %} } -;SetKeyDelay(command) { +;SetKeyDelay(args*) { ; {% block SetKeyDelay %} -; SetKeyDelay(command[2], command[3]) +; SetKeyDelay(args[1], args[2]) ; {% endblock SetKeyDelay %} ;} -AHKSend(command) { +AHKSend(args*) { {% block AHKSend %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -1992,11 +1992,11 @@ AHKSend(command) { {% endblock AHKSend %} } -AHKSendRaw(command) { +AHKSendRaw(args*) { {% block AHKSendRaw %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -2013,11 +2013,11 @@ AHKSendRaw(command) { {% endblock AHKSendRaw %} } -AHKSendInput(command) { +AHKSendInput(args*) { {% block AHKSendInput %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -2034,11 +2034,11 @@ AHKSendInput(command) { {% endblock AHKSendInput %} } -AHKSendEvent(command) { +AHKSendEvent(args*) { {% block AHKSendEvent %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -2055,11 +2055,11 @@ AHKSendEvent(command) { {% endblock AHKSendEvent %} } -AHKSendPlay(command) { +AHKSendPlay(args*) { {% block AHKSendPlay %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelayPlay) current_key_duration := Format("{}", A_KeyDurationPlay) @@ -2076,9 +2076,9 @@ AHKSendPlay(command) { {% endblock AHKSendPlay %} } -AHKSetCapsLockState(command) { +AHKSetCapsLockState(args*) { {% block AHKSetCapsLockState %} - state := command[2] + state := args[1] if (state = "") { SetCapsLockState(!GetKeyState("CapsLock", "T")) } else { @@ -2088,7 +2088,7 @@ AHKSetCapsLockState(command) { {% endblock AHKSetCapsLockState %} } -HideTrayTip(command) { +HideTrayTip(args*) { {% block HideTrayTip %} TrayTip ; Attempt to hide it the normal way. if SubStr(A_OSVersion,1,3) = "10." { @@ -2099,16 +2099,16 @@ HideTrayTip(command) { {% endblock HideTrayTip %} } -AHKWinGetClass(command) { +AHKWinGetClass(args*) { {% block AHKWinGetClass %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2137,15 +2137,15 @@ AHKWinGetClass(command) { {% endblock AHKWinGetClass %} } -AHKWinActivate(command) { +AHKWinActivate(args*) { {% block AHKWinActivate %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2174,18 +2174,18 @@ AHKWinActivate(command) { {% endblock AHKWinActivate %} } -AHKWindowList(command) { +AHKWindowList(args*) { {% block AHKWindowList %} current_detect_hw := Format("{}", A_DetectHiddenWindows) - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2216,20 +2216,20 @@ AHKWindowList(command) { {% endblock AHKWindowList %} } -AHKControlClick(command) { +AHKControlClick(args*) { {% block AHKControlClick %} - ctrl := command[2] - title := command[3] - text := command[4] - button := command[5] - click_count := command[6] - options := command[7] - exclude_title := command[8] - exclude_text := command[9] - detect_hw := command[10] - match_mode := command[11] - match_speed := command[12] + 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) @@ -2258,17 +2258,17 @@ AHKControlClick(command) { {% endblock AHKControlClick %} } -AHKControlGetText(command) { +AHKControlGetText(args*) { {% block AHKControlGetText %} - ctrl := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -2298,17 +2298,17 @@ AHKControlGetText(command) { {% endblock AHKControlGetText %} } -AHKControlGetPos(command) { +AHKControlGetPos(args*) { {% block AHKControlGetPos %} - ctrl := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -2338,17 +2338,17 @@ AHKControlGetPos(command) { {% endblock AHKControlGetPos %} } -AHKControlSend(command) { +AHKControlSend(args*) { {% block AHKControlSend %} - ctrl := command[2] - keys := command[3] - title := command[4] - text := command[5] - extitle := command[6] - extext := command[7] - detect_hw := command[8] - match_mode := command[9] - match_speed := command[10] + 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) @@ -2380,7 +2380,7 @@ AHKControlSend(command) { {% endblock AHKControlSend %} } -AHKWinFromMouse(command) { +AHKWinFromMouse(args*) { {% block AHKWinFromMouse %} MouseGetPos(,, &MouseWin) @@ -2393,10 +2393,10 @@ AHKWinFromMouse(command) { {% endblock AHKWinFromMouse %} } -AHKWinIsAlwaysOnTop(command) { +AHKWinIsAlwaysOnTop(args*) { {% block AHKWinIsAlwaysOnTop %} ; TODO: detect hidden windows / etc? - title := command[2] + title := args[1] ExStyle := WinGetExStyle(title) if (ExStyle = "") return FormatNoValueResponse() @@ -2408,19 +2408,19 @@ AHKWinIsAlwaysOnTop(command) { {% endblock AHKWinIsAlwaysOnTop %} } -AHKWinMove(command) { +AHKWinMove(args*) { {% block AHKWinMove %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - x := command[9] - y := command[10] - width := command[11] - height := command[12] + 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) @@ -2449,16 +2449,16 @@ AHKWinMove(command) { {% endblock AHKWinMove %} } -AHKWinGetPos(command) { +AHKWinGetPos(args*) { {% block AHKWinGetPos %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2489,10 +2489,10 @@ AHKWinGetPos(command) { {% endblock AHKWinGetPos %} } -AHKGetVolume(command) { +AHKGetVolume(args*) { {% block AHKGetVolume %} - device_number := command[2] + device_number := args[1] retval := SoundGetVolume(,device_number) response := FormatResponse("ahk.message.FloatResponseMessage", Format("{}", retval)) @@ -2500,111 +2500,111 @@ AHKGetVolume(command) { {% endblock AHKGetVolume %} } -AHKSoundBeep(command) { +AHKSoundBeep(args*) { {% block AHKSoundBeep %} - freq := command[2] - duration := command[3] + freq := args[1] + duration := args[2] SoundBeep(freq, duration) return FormatNoValueResponse() {% endblock AHKSoundBeep %} } -AHKSoundGet(command) { +AHKSoundGet(args*) { {% block AHKSoundGet %} return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundGet is not supported in ahk v2") {% endblock AHKSoundGet %} } -AHKSoundSet(command) { +AHKSoundSet(args*) { {% block AHKSoundSet %} return FormatResponse("ahk.message.ExceptionResponseMessage", "SoundSet is not supported in ahk v2") {% endblock AHKSoundSet %} } -AHKSoundPlay(command) { +AHKSoundPlay(args*) { {% block AHKSoundPlay %} - filename := command[2] + filename := args[1] SoundPlay(filename) return FormatNoValueResponse() {% endblock AHKSoundPlay %} } -AHKSetVolume(command) { +AHKSetVolume(args*) { {% block AHKSetVolume %} - device_number := command[2] - value := command[3] + device_number := args[1] + value := args[2] SoundSetVolume(value,,device_number) return FormatNoValueResponse() {% endblock AHKSetVolume %} } -AHKEcho(command) { +AHKEcho(args*) { {% block AHKEcho %} - arg := command[2] + arg := args[1] return FormatResponse("ahk.message.StringResponseMessage", arg) {% endblock AHKEcho %} } -AHKTraytip(command) { +AHKTraytip(args*) { {% block AHKTraytip %} - title := command[2] - text := command[3] - second := command[4] - option := command[5] + title := args[1] + text := args[2] + second := args[3] + option := args[4] TrayTip(title, text, option) return FormatNoValueResponse() {% endblock AHKTraytip %} } -AHKShowToolTip(command) { +AHKShowToolTip(args*) { {% block AHKShowToolTip %} - text := command[2] - x := command[3] - y := command[4] - which := command[5] + text := args[1] + x := args[2] + y := args[3] + which := args[4] ToolTip(text, x, y, which) return FormatNoValueResponse() {% endblock AHKShowToolTip %} } -AHKGetClipboard(command) { +AHKGetClipboard(args*) { {% block AHKGetClipboard %} return FormatResponse("ahk.message.StringResponseMessage", A_Clipboard) {% endblock AHKGetClipboard %} } -AHKGetClipboardAll(command) { +AHKGetClipboardAll(args*) { {% block AHKGetClipboardAll %} data := ClipboardAll() return FormatBinaryResponse(&data) {% endblock AHKGetClipboardAll %} } -AHKSetClipboard(command) { +AHKSetClipboard(args*) { {% block AHKSetClipboard %} - text := command[2] + text := args[1] A_Clipboard := text return FormatNoValueResponse() {% endblock AHKSetClipboard %} } -AHKSetClipboardAll(command) { +AHKSetClipboardAll(args*) { {% block AHKSetClipboardAll %} ; TODO there should be a way for us to accept a base64 string instead - filename := command[2] + filename := args[1] contents := FileRead(filename, "RAW") A_Clipboard := ClipboardAll(contents) return FormatNoValueResponse() {% endblock AHKSetClipboardAll %} } -AHKClipWait(command) { +AHKClipWait(args*) { - timeout := command[2] - wait_for_any_data := command[3] + timeout := args[1] + wait_for_any_data := args[2] if ClipWait(timeout, wait_for_any_data) return FormatNoValueResponse() @@ -2613,45 +2613,45 @@ AHKClipWait(command) { return FormatNoValueResponse() } -AHKBlockInput(command) { - value := command[2] +AHKBlockInput(args*) { + value := args[1] BlockInput(value) return FormatNoValueResponse() } -AHKMenuTrayTip(command) { - value := command[2] +AHKMenuTrayTip(args*) { + value := args[1] A_IconTip := value return FormatNoValueResponse() } -AHKMenuTrayShow(command) { +AHKMenuTrayShow(args*) { A_IconHidden := 0 return FormatNoValueResponse() } -AHKMenuTrayIcon(command) { - filename := command[2] - icon_number := command[3] - freeze := command[4] +AHKMenuTrayIcon(args*) { + filename := args[1] + icon_number := args[2] + freeze := args[3] TraySetIcon(filename, icon_number, freeze) return FormatNoValueResponse() } -;AHKGuiNew(command) { +;AHKGuiNew(args*) { ; -; options := command[2] -; title := command[3] +; options := args[1] +; title := args[2] ; Gui(New, options, title) ; return FormatResponse("ahk.message.StringResponseMessage", hwnd) ;} -AHKMsgBox(command) { +AHKMsgBox(args*) { - options := command[2] - title := command[3] - text := command[4] - timeout := command[5] + options := args[1] + title := args[2] + text := args[3] + timeout := args[4] if (timeout != "") { options := "" options " T" timeout } @@ -2664,18 +2664,18 @@ AHKMsgBox(command) { return ret } -AHKInputBox(command) { +AHKInputBox(args*) { - title := command[2] - prompt := command[3] - hide := command[4] - width := command[5] - height := command[6] - x := command[7] - y := command[8] - locale := command[9] - timeout := command[10] - default := command[11] + 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 := "" @@ -2693,12 +2693,12 @@ AHKInputBox(command) { return ret } -AHKFileSelectFile(command) { +AHKFileSelectFile(args*) { - options := command[2] - root := command[3] - title := command[4] - filter := command[5] + options := args[1] + root := args[2] + title := args[3] + filter := args[4] output := FileSelect(options, root, title, filter) if (output = "") { ret := FormatNoValueResponse() @@ -2723,11 +2723,11 @@ AHKFileSelectFile(command) { return ret } -AHKFileSelectFolder(command) { +AHKFileSelectFolder(args*) { - starting_folder := command[2] - options := command[3] - prompt := command[4] + starting_folder := args[1] + options := args[2] + prompt := args[3] output := DirSelect(starting_folder, options, prompt) @@ -2845,12 +2845,13 @@ pyresp := "" Loop { query := RTrim(stdin.ReadLine(), "`n") - commandArray := CommandArrayFromQuery(query) + argsArray := CommandArrayFromQuery(query) try { - func_name := commandArray[1] + func_name := argsArray[1] + argsArray.RemoveAt(1) {% block before_function %} {% endblock before_function %} - pyresp := %func_name%(commandArray) + pyresp := %func_name%(argsArray*) {% block after_function %} {% endblock after_function %} } catch Any as e { diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 83ce0a19..358c0082 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -40,18 +40,18 @@ FormatBinaryResponse(ByRef bin) { return FormatResponse("ahk.message.B64BinaryResponseMessage", b64) } -AHKSetDetectHiddenWindows(ByRef command) { +AHKSetDetectHiddenWindows(args*) { {% block AHKSetDetectHiddenWindows %} - value := command[2] + value := args[1] DetectHiddenWindows, %value% return FormatNoValueResponse() {% endblock AHKSetDetectHiddenWindows %} } -AHKSetTitleMatchMode(ByRef command) { +AHKSetTitleMatchMode(args*) { {% block AHKSetTitleMatchMode %} - val1 := command[2] - val2 := command[3] + val1 := args[1] + val2 := args[2] if (val1 != "") { SetTitleMatchMode, %val1% } @@ -62,45 +62,45 @@ AHKSetTitleMatchMode(ByRef command) { {% endblock AHKSetTitleMatchMode %} } -AHKGetTitleMatchMode(ByRef command) { +AHKGetTitleMatchMode(args*) { {% block AHKGetTitleMatchMode %} return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchMode) {% endblock AHKGetTitleMatchMode %} } -AHKGetTitleMatchSpeed(ByRef command) { +AHKGetTitleMatchSpeed(args*) { {% block AHKGetTitleMatchSpeed %} return FormatResponse("ahk.message.StringResponseMessage", A_TitleMatchModeSpeed) {% endblock AHKGetTitleMatchSpeed %} } -AHKSetSendLevel(ByRef command) { +AHKSetSendLevel(args*) { {% block AHKSetSendLevel %} - level := command[2] + level := args[1] SendLevel, %level% return FormatNoValueResponse() {% endblock AHKSetSendLevel %} } -AHKGetSendLevel(ByRef command) { +AHKGetSendLevel(args*) { {% block AHKGetSendLevel %} return FormatResponse("ahk.message.IntegerResponseMessage", A_SendLevel) {% endblock AHKGetSendLevel %} } -AHKWinExist(ByRef command) { +AHKWinExist(args*) { {% block AHKWinExist %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -131,16 +131,16 @@ AHKWinExist(ByRef command) { {% endblock AHKWinExist %} } -AHKWinClose(ByRef command) { +AHKWinClose(args*) { {% block AHKWinClose %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - secondstowait := command[9] + 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) @@ -166,16 +166,16 @@ AHKWinClose(ByRef command) { {% endblock AHKWinClose %} } -AHKWinKill(ByRef command) { +AHKWinKill(args*) { {% block AHKWinKill %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - secondstowait := command[9] + 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) @@ -201,17 +201,17 @@ AHKWinKill(ByRef command) { {% endblock AHKWinKill %} } -AHKWinWait(ByRef command) { +AHKWinWait(args*) { {% block AHKWinWait %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -245,17 +245,17 @@ AHKWinWait(ByRef command) { {% endblock AHKWinWait %} } -AHKWinWaitActive(ByRef command) { +AHKWinWaitActive(args*) { {% block AHKWinWaitActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -289,17 +289,17 @@ AHKWinWaitActive(ByRef command) { {% endblock AHKWinWaitActive %} } -AHKWinWaitNotActive(ByRef command) { +AHKWinWaitNotActive(args*) { {% block AHKWinWaitNotActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -333,17 +333,17 @@ AHKWinWaitNotActive(ByRef command) { {% endblock AHKWinWaitNotActive %} } -AHKWinWaitClose(ByRef command) { +AHKWinWaitClose(args*) { {% block AHKWinWaitClose %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - timeout := command[9] + 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 != "") { @@ -376,15 +376,15 @@ AHKWinWaitClose(ByRef command) { {% endblock AHKWinWaitClose %} } -AHKWinMinimize(ByRef command) { +AHKWinMinimize(args*) { {% block AHKWinMinimize %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -410,15 +410,15 @@ AHKWinMinimize(ByRef command) { {% endblock AHKWinMinimize %} } -AHKWinMaximize(ByRef command) { +AHKWinMaximize(args*) { {% block AHKWinMaximize %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -444,15 +444,15 @@ AHKWinMaximize(ByRef command) { {% endblock AHKWinMaximize %} } -AHKWinRestore(ByRef command) { +AHKWinRestore(args*) { {% block AHKWinRestore %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -478,16 +478,16 @@ AHKWinRestore(ByRef command) { {% endblock AHKWinRestore %} } -AHKWinIsActive(ByRef command) { +AHKWinIsActive(args*) { {% block AHKWinIsActive %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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 != "") { @@ -515,16 +515,16 @@ AHKWinIsActive(ByRef command) { {% endblock AHKWinIsActive %} } -AHKWinGetID(ByRef command) { +AHKWinGetID(args*) { {% block AHKWinGetID %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -554,16 +554,16 @@ AHKWinGetID(ByRef command) { {% endblock AHKWinGetID %} } -AHKWinGetTitle(ByRef command) { +AHKWinGetTitle(args*) { {% block AHKWinGetTitle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -589,16 +589,16 @@ AHKWinGetTitle(ByRef command) { {% endblock AHKWinGetTitle %} } -AHKWinGetIDLast(ByRef command) { +AHKWinGetIDLast(args*) { {% block AHKWinGetIDLast %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -628,16 +628,16 @@ AHKWinGetIDLast(ByRef command) { {% endblock AHKWinGetIDLast %} } -AHKWinGetPID(ByRef command) { +AHKWinGetPID(args*) { {% block AHKWinGetPID %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -667,16 +667,16 @@ AHKWinGetPID(ByRef command) { {% endblock AHKWinGetPID %} } -AHKWinGetProcessName(ByRef command) { +AHKWinGetProcessName(args*) { {% block AHKWinGetProcessName %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -706,16 +706,16 @@ AHKWinGetProcessName(ByRef command) { {% endblock AHKWinGetProcessName %} } -AHKWinGetProcessPath(ByRef command) { +AHKWinGetProcessPath(args*) { {% block AHKWinGetProcessPath %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -745,16 +745,16 @@ AHKWinGetProcessPath(ByRef command) { {% endblock AHKWinGetProcessPath %} } -AHKWinGetCount(ByRef command) { +AHKWinGetCount(args*) { {% block AHKWinGetCount %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -784,16 +784,16 @@ AHKWinGetCount(ByRef command) { {% endblock AHKWinGetCount %} } -AHKWinGetMinMax(ByRef command) { +AHKWinGetMinMax(args*) { {% block AHKWinGetMinMax %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -823,16 +823,16 @@ AHKWinGetMinMax(ByRef command) { {% endblock AHKWinGetMinMax %} } -AHKWinGetControlList(ByRef command) { +AHKWinGetControlList(args*) { {% block AHKWinGetControlList %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -887,16 +887,16 @@ AHKWinGetControlList(ByRef command) { {% endblock AHKWinGetControlList %} } -AHKWinGetTransparent(ByRef command) { +AHKWinGetTransparent(args*) { {% block AHKWinGetTransparent %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -921,16 +921,16 @@ AHKWinGetTransparent(ByRef command) { return response {% endblock AHKWinGetTransparent %} } -AHKWinGetTransColor(ByRef command) { +AHKWinGetTransColor(args*) { {% block AHKWinGetTransColor %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -955,16 +955,16 @@ AHKWinGetTransColor(ByRef command) { return response {% endblock AHKWinGetTransColor %} } -AHKWinGetStyle(ByRef command) { +AHKWinGetStyle(args*) { {% block AHKWinGetStyle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -989,16 +989,16 @@ AHKWinGetStyle(ByRef command) { return response {% endblock AHKWinGetStyle %} } -AHKWinGetExStyle(ByRef command) { +AHKWinGetExStyle(args*) { {% block AHKWinGetExStyle %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1024,16 +1024,16 @@ AHKWinGetExStyle(ByRef command) { {% endblock AHKWinGetExStyle %} } -AHKWinGetText(ByRef command) { +AHKWinGetText(args*) { {% block AHKWinGetText %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1064,16 +1064,16 @@ AHKWinGetText(ByRef command) { {% endblock AHKWinGetText %} } -AHKWinSetTitle(ByRef command) { +AHKWinSetTitle(args*) { {% block AHKWinSetTitle %} - new_title := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1096,16 +1096,16 @@ AHKWinSetTitle(ByRef command) { {% endblock AHKWinSetTitle %} } -AHKWinSetAlwaysOnTop(ByRef command) { +AHKWinSetAlwaysOnTop(args*) { {% block AHKWinSetAlwaysOnTop %} - toggle := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1129,15 +1129,15 @@ AHKWinSetAlwaysOnTop(ByRef command) { {% endblock AHKWinSetAlwaysOnTop %} } -AHKWinSetBottom(ByRef command) { +AHKWinSetBottom(args*) { {% block AHKWinSetBottom %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1162,15 +1162,15 @@ AHKWinSetBottom(ByRef command) { {% endblock AHKWinSetBottom %} } -AHKWinShow(ByRef command) { +AHKWinShow(args*) { {% block AHKWinShow %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1195,15 +1195,15 @@ AHKWinShow(ByRef command) { {% endblock AHKWinShow %} } -AHKWinHide(ByRef command) { +AHKWinHide(args*) { {% block AHKWinHide %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1228,15 +1228,15 @@ AHKWinHide(ByRef command) { {% endblock AHKWinHide %} } -AHKWinSetTop(ByRef command) { +AHKWinSetTop(args*) { {% block AHKWinSetTop %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1261,15 +1261,15 @@ AHKWinSetTop(ByRef command) { {% endblock AHKWinSetTop %} } -AHKWinSetEnable(ByRef command) { +AHKWinSetEnable(args*) { {% block AHKWinSetEnable %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1294,15 +1294,15 @@ AHKWinSetEnable(ByRef command) { {% endblock AHKWinSetEnable %} } -AHKWinSetDisable(ByRef command) { +AHKWinSetDisable(args*) { {% block AHKWinSetDisable %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1327,15 +1327,15 @@ AHKWinSetDisable(ByRef command) { {% endblock AHKWinSetDisable %} } -AHKWinSetRedraw(ByRef command) { +AHKWinSetRedraw(args*) { {% block AHKWinSetRedraw %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -1360,17 +1360,17 @@ AHKWinSetRedraw(ByRef command) { {% endblock AHKWinSetRedraw %} } -AHKWinSetStyle(ByRef command) { +AHKWinSetStyle(args*) { {% block AHKWinSetStyle %} - style := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1399,17 +1399,17 @@ AHKWinSetStyle(ByRef command) { {% endblock AHKWinSetStyle %} } -AHKWinSetExStyle(ByRef command) { +AHKWinSetExStyle(args*) { {% block AHKWinSetExStyle %} - style := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1438,17 +1438,17 @@ AHKWinSetExStyle(ByRef command) { {% endblock AHKWinSetExStyle %} } -AHKWinSetRegion(ByRef command) { +AHKWinSetRegion(args*) { {% block AHKWinSetRegion %} - options := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1477,17 +1477,17 @@ AHKWinSetRegion(ByRef command) { {% endblock AHKWinSetRegion %} } -AHKWinSetTransparent(ByRef command) { +AHKWinSetTransparent(args*) { {% block AHKWinSetTransparent %} - transparency := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1511,17 +1511,17 @@ AHKWinSetTransparent(ByRef command) { {% endblock AHKWinSetTransparent %} } -AHKWinSetTransColor(ByRef command) { +AHKWinSetTransColor(args*) { {% block AHKWinSetTransColor %} - color := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -1547,15 +1547,15 @@ AHKWinSetTransColor(ByRef command) { {% endblock AHKWinSetTransColor %} } -AHKImageSearch(ByRef command) { +AHKImageSearch(args*) { {% block AHKImageSearch %} - imagepath := command[6] - x1 := command[2] - y1 := command[3] - x2 := command[4] - y2 := command[5] - coord_mode := command[7] + imagepath := args[5] + x1 := args[1] + y1 := args[2] + x2 := args[3] + y2 := args[4] + coord_mode := args[6] current_mode := Format("{}", A_CoordModePixel) @@ -1577,7 +1577,7 @@ AHKImageSearch(ByRef command) { } if (ErrorLevel = 2) { - s := FormatResponse("ahk.message.ExceptionResponseMessage", "there was a problem that prevented the command from conducting the search (such as failure to open the image file or a badly formatted option)") + 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 { @@ -1588,13 +1588,13 @@ AHKImageSearch(ByRef command) { {% endblock AHKImageSearch %} } -AHKPixelGetColor(ByRef command) { +AHKPixelGetColor(args*) { {% block AHKPixelGetColor %} - x := command[2] - y := command[3] - coord_mode := command[4] - options := command[5] + x := args[1] + y := args[2] + coord_mode := args[3] + options := args[4] current_mode := Format("{}", A_CoordModePixel) @@ -1613,17 +1613,17 @@ AHKPixelGetColor(ByRef command) { {% endblock AHKPixelGetColor %} } -AHKPixelSearch(ByRef command) { +AHKPixelSearch(args*) { {% block AHKPixelSearch %} - x1 := command[2] - y1 := command[3] - x2 := command[4] - y2 := command[5] - color := command[6] - variation := command[7] - options := command[8] - coord_mode := command[9] + 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) @@ -1651,10 +1651,10 @@ AHKPixelSearch(ByRef command) { {% endblock AHKPixelSearch %} } -AHKMouseGetPos(ByRef command) { +AHKMouseGetPos(args*) { {% block AHKMouseGetPos %} - coord_mode := command[2] + coord_mode := args[1] current_coord_mode := Format("{}", A_CoordModeMouse) if (coord_mode != "") { CoordMode, Mouse, %coord_mode% @@ -1672,11 +1672,11 @@ AHKMouseGetPos(ByRef command) { {% endblock AHKMouseGetPos %} } -AHKKeyState(ByRef command) { +AHKKeyState(args*) { {% block AHKKeyState %} - keyname := command[2] - mode := command[3] + keyname := args[1] + mode := args[2] if (mode != "") { state := GetKeyState(keyname, mode) } else{ @@ -1700,12 +1700,12 @@ AHKKeyState(ByRef command) { {% endblock AHKKeyState %} } -AHKMouseMove(ByRef command) { +AHKMouseMove(args*) { {% block AHKMouseMove %} - x := command[2] - y := command[3] - speed := command[4] - relative := command[5] + x := args[1] + y := args[2] + speed := args[3] + relative := args[4] if (relative != "") { MouseMove, %x%, %y%, %speed%, R } else { @@ -1716,15 +1716,15 @@ AHKMouseMove(ByRef command) { {% endblock AHKMouseMove %} } -AHKClick(ByRef command) { +AHKClick(args*) { {% block AHKClick %} - x := command[2] - y := command[3] - button := command[4] - click_count := command[5] - direction := command[6] - r := command[7] - relative_to := command[8] + x := args[1] + y := args[2] + button := args[3] + click_count := args[4] + direction := args[5] + r := args[6] + relative_to := args[7] current_coord_rel := Format("{}", A_CoordModeMouse) if (relative_to != "") { @@ -1742,10 +1742,10 @@ AHKClick(ByRef command) { {% endblock AHKClick %} } -AHKGetCoordMode(ByRef command) { +AHKGetCoordMode(args*) { {% block AHKGetCoordMode %} - target := command[2] + target := args[1] if (target = "ToolTip") { return FormatResponse("ahk.message.StringResponseMessage", A_CoordModeToolTip) @@ -1766,26 +1766,26 @@ AHKGetCoordMode(ByRef command) { {% endblock AHKGetCoordMode %} } -AHKSetCoordMode(ByRef command) { +AHKSetCoordMode(args*) { {% block AHKSetCoordMode %} - target := command[2] - relative_to := command[3] + target := args[1] + relative_to := args[2] CoordMode, %target%, %relative_to% return FormatNoValueResponse() {% endblock AHKSetCoordMode %} } -AHKMouseClickDrag(ByRef command) { +AHKMouseClickDrag(args*) { {% block AHKMouseClickDrag %} - button := command[2] - x1 := command[3] - y1 := command[4] - x2 := command[5] - y2 := command[6] - speed := command[7] - relative := command[8] - relative_to := command[9] + 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] current_coord_rel := Format("{}", A_CoordModeMouse) @@ -1804,11 +1804,11 @@ AHKMouseClickDrag(ByRef command) { {% endblock AHKMouseClickDrag %} } -AHKRegRead(ByRef command) { +AHKRegRead(args*) { {% block RegRead %} - key_name := command[2] - value_name := command[3] + key_name := args[1] + value_name := args[2] RegRead, output, %key_name%, %value_name% @@ -1822,13 +1822,13 @@ AHKRegRead(ByRef command) { {% endblock RegRead %} } -AHKRegWrite(ByRef command) { +AHKRegWrite(args*) { {% block RegWrite %} - value_type := command[2] - key_name := command[3] - value_name := command[4] - value := command[5] + 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)) @@ -1838,11 +1838,11 @@ AHKRegWrite(ByRef command) { {% endblock RegWrite %} } -AHKRegDelete(ByRef command) { +AHKRegDelete(args*) { {% block RegDelete %} - key_name := command[2] - value_name := command[3] + 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)) @@ -1852,31 +1852,31 @@ AHKRegDelete(ByRef command) { {% endblock RegDelete %} } -AHKKeyWait(ByRef command) { +AHKKeyWait(args*) { {% block AHKKeyWait %} - keyname := command[2] - if (command.Length() = 2) { + keyname := args[1] + if (args.Length() = 2) { KeyWait,% keyname } else { - options := command[3] + options := args[2] KeyWait,% keyname,% options } return FormatResponse("ahk.message.IntegerResponseMessage", ErrorLevel) {% endblock AHKKeyWait %} } -SetKeyDelay(ByRef command) { +SetKeyDelay(args*) { {% block SetKeyDelay %} - SetKeyDelay, command[2], command[3] + SetKeyDelay, args[1], args[2] {% endblock SetKeyDelay %} } -AHKSend(ByRef command) { +AHKSend(args*) { {% block AHKSend %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -1893,11 +1893,11 @@ AHKSend(ByRef command) { {% endblock AHKSend %} } -AHKSendRaw(ByRef command) { +AHKSendRaw(args*) { {% block AHKSendRaw %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -1914,11 +1914,11 @@ AHKSendRaw(ByRef command) { {% endblock AHKSendRaw %} } -AHKSendInput(ByRef command) { +AHKSendInput(args*) { {% block AHKSendInput %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -1935,11 +1935,11 @@ AHKSendInput(ByRef command) { {% endblock AHKSendInput %} } -AHKSendEvent(ByRef command) { +AHKSendEvent(args*) { {% block AHKSendEvent %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelay) current_key_duration := Format("{}", A_KeyDuration) @@ -1956,11 +1956,11 @@ AHKSendEvent(ByRef command) { {% endblock AHKSendEvent %} } -AHKSendPlay(ByRef command) { +AHKSendPlay(args*) { {% block AHKSendPlay %} - str := command[2] - key_delay := command[3] - key_press_duration := command[4] + str := args[1] + key_delay := args[2] + key_press_duration := args[3] current_delay := Format("{}", A_KeyDelayPlay) current_key_duration := Format("{}", A_KeyDurationPlay) @@ -1977,9 +1977,9 @@ AHKSendPlay(ByRef command) { {% endblock AHKSendPlay %} } -AHKSetCapsLockState(ByRef command) { +AHKSetCapsLockState(args*) { {% block AHKSetCapsLockState %} - state := command[2] + state := args[1] if (state = "") { SetCapsLockState % !GetKeyState("CapsLock", "T") } else { @@ -1989,7 +1989,7 @@ AHKSetCapsLockState(ByRef command) { {% endblock AHKSetCapsLockState %} } -HideTrayTip(ByRef command) { +HideTrayTip(args*) { {% block HideTrayTip %} TrayTip ; Attempt to hide it the normal way. if SubStr(A_OSVersion,1,3) = "10." { @@ -2000,16 +2000,16 @@ HideTrayTip(ByRef command) { {% endblock HideTrayTip %} } -AHKWinGetClass(ByRef command) { +AHKWinGetClass(args*) { {% block AHKWinGetClass %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2040,15 +2040,15 @@ AHKWinGetClass(ByRef command) { {% endblock AHKWinGetClass %} } -AHKWinActivate(ByRef command) { +AHKWinActivate(args*) { {% block AHKWinActivate %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2075,18 +2075,18 @@ AHKWinActivate(ByRef command) { {% endblock AHKWinActivate %} } -AHKWindowList(ByRef command) { +AHKWindowList(args*) { {% block AHKWindowList %} current_detect_hw := Format("{}", A_DetectHiddenWindows) - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2115,20 +2115,20 @@ AHKWindowList(ByRef command) { {% endblock AHKWindowList %} } -AHKControlClick(ByRef command) { +AHKControlClick(args*) { {% block AHKControlClick %} - ctrl := command[2] - title := command[3] - text := command[4] - button := command[5] - click_count := command[6] - options := command[7] - exclude_title := command[8] - exclude_text := command[9] - detect_hw := command[10] - match_mode := command[11] - match_speed := command[12] + 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) @@ -2160,17 +2160,17 @@ AHKControlClick(ByRef command) { {% endblock AHKControlClick %} } -AHKControlGetText(ByRef command) { +AHKControlGetText(args*) { {% block AHKControlGetText %} - ctrl := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -2201,17 +2201,17 @@ AHKControlGetText(ByRef command) { {% endblock AHKControlGetText %} } -AHKControlGetPos(ByRef command) { +AHKControlGetPos(args*) { {% block AHKControlGetPos %} - ctrl := command[2] - title := command[3] - text := command[4] - extitle := command[5] - extext := command[6] - detect_hw := command[7] - match_mode := command[8] - match_speed := command[9] + 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) @@ -2244,17 +2244,17 @@ AHKControlGetPos(ByRef command) { {% endblock AHKControlGetPos %} } -AHKControlSend(ByRef command) { +AHKControlSend(args*) { {% block AHKControlSend %} - ctrl := command[2] - keys := command[3] - title := command[4] - text := command[5] - extitle := command[6] - extext := command[7] - detect_hw := command[8] - match_mode := command[9] - match_speed := command[10] + 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) @@ -2277,7 +2277,7 @@ AHKControlSend(ByRef command) { {% endblock AHKControlSend %} } -AHKWinFromMouse(ByRef command) { +AHKWinFromMouse(args*) { {% block AHKWinFromMouse %} MouseGetPos,,, MouseWin @@ -2290,10 +2290,10 @@ AHKWinFromMouse(ByRef command) { {% endblock AHKWinFromMouse %} } -AHKWinIsAlwaysOnTop(ByRef command) { +AHKWinIsAlwaysOnTop(args*) { {% block AHKWinIsAlwaysOnTop %} - title := command[2] + title := args[1] WinGet, ExStyle, ExStyle, %title% if (ExStyle = "") return FormatNoValueResponse() @@ -2305,19 +2305,19 @@ AHKWinIsAlwaysOnTop(ByRef command) { {% endblock AHKWinIsAlwaysOnTop %} } -AHKWinMove(ByRef command) { +AHKWinMove(args*) { {% block AHKWinMove %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] - x := command[9] - y := command[10] - width := command[11] - height := command[12] + 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) @@ -2344,16 +2344,16 @@ AHKWinMove(ByRef command) { {% endblock AHKWinMove %} } -AHKWinGetPos(ByRef command) { +AHKWinGetPos(args*) { {% block AHKWinGetPos %} - title := command[2] - text := command[3] - extitle := command[4] - extext := command[5] - detect_hw := command[6] - match_mode := command[7] - match_speed := command[8] + 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) @@ -2386,10 +2386,10 @@ AHKWinGetPos(ByRef command) { {% endblock AHKWinGetPos %} } -AHKGetVolume(ByRef command) { +AHKGetVolume(args*) { {% block AHKGetVolume %} - device_number := command[2] + device_number := args[1] try { SoundGetWaveVolume, retval, %device_number% @@ -2406,21 +2406,21 @@ AHKGetVolume(ByRef command) { {% endblock AHKGetVolume %} } -AHKSoundBeep(ByRef command) { +AHKSoundBeep(args*) { {% block AHKSoundBeep %} - freq := command[2] - duration := command[3] + freq := args[1] + duration := args[2] SoundBeep , %freq%, %duration% return FormatNoValueResponse() {% endblock AHKSoundBeep %} } -AHKSoundGet(ByRef command) { +AHKSoundGet(args*) { {% block AHKSoundGet %} - device_number := command[2] - component_type := command[3] - control_type := command[4] + device_number := args[1] + component_type := args[2] + control_type := args[3] SoundGet, retval, %component_type%, %control_type%, %device_number% ; TODO interpret return type @@ -2428,29 +2428,29 @@ AHKSoundGet(ByRef command) { {% endblock AHKSoundGet %} } -AHKSoundSet(ByRef command) { +AHKSoundSet(args*) { {% block AHKSoundSet %} - device_number := command[2] - component_type := command[3] - control_type := command[4] - value := command[5] + 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(ByRef command) { +AHKSoundPlay(args*) { {% block AHKSoundPlay %} - filename := command[2] + filename := args[1] SoundPlay, %filename% return FormatNoValueResponse() {% endblock AHKSoundPlay %} } -AHKSetVolume(ByRef command) { +AHKSetVolume(args*) { {% block AHKSetVolume %} - device_number := command[2] - value := command[3] + device_number := args[1] + value := args[2] SoundSetWaveVolume, %value%, %device_number% return FormatNoValueResponse() {% endblock AHKSetVolume %} @@ -2463,71 +2463,71 @@ CountNewlines(ByRef s) { return count } -AHKEcho(ByRef command) { +AHKEcho(args*) { {% block AHKEcho %} - arg := command[2] + arg := args[1] return FormatResponse("ahk.message.StringResponseMessage", arg) {% endblock AHKEcho %} } -AHKTraytip(ByRef command) { +AHKTraytip(args*) { {% block AHKTraytip %} - title := command[2] - text := command[3] - second := command[4] - option := command[5] + title := args[1] + text := args[2] + second := args[3] + option := args[4] TrayTip, %title%, %text%, %second%, %option% return FormatNoValueResponse() {% endblock AHKTraytip %} } -AHKShowToolTip(ByRef command) { +AHKShowToolTip(args*) { {% block AHKShowToolTip %} - text := command[2] - x := command[3] - y := command[4] - which := command[5] + text := args[1] + x := args[2] + y := args[3] + which := args[4] ToolTip, %text%, %x%, %y%, %which% return FormatNoValueResponse() {% endblock AHKShowToolTip %} } -AHKGetClipboard(ByRef command) { +AHKGetClipboard(args*) { {% block AHKGetClipboard %} return FormatResponse("ahk.message.StringResponseMessage", Clipboard) {% endblock AHKGetClipboard %} } -AHKGetClipboardAll(ByRef command) { +AHKGetClipboardAll(args*) { {% block AHKGetClipboardAll %} data := ClipboardAll return FormatBinaryResponse(data) {% endblock AHKGetClipboardAll %} } -AHKSetClipboard(ByRef command) { +AHKSetClipboard(args*) { {% block AHKSetClipboard %} - text := command[2] + text := args[1] Clipboard := text return FormatNoValueResponse() {% endblock AHKSetClipboard %} } -AHKSetClipboardAll(ByRef command) { +AHKSetClipboardAll(args*) { {% block AHKSetClipboardAll %} ; TODO there should be a way for us to accept a base64 string instead - filename := command[2] + filename := args[1] FileRead, Clipboard, %filename% return FormatNoValueResponse() {% endblock AHKSetClipboardAll %} } -AHKClipWait(ByRef command) { +AHKClipWait(args*) { - timeout := command[2] - wait_for_any_data := command[3] + timeout := args[1] + wait_for_any_data := args[2] ClipWait, %timeout%, %wait_for_any_data% @@ -2537,45 +2537,45 @@ AHKClipWait(ByRef command) { return FormatNoValueResponse() } -AHKBlockInput(ByRef command) { - value := command[2] +AHKBlockInput(args*) { + value := args[1] BlockInput, %value% return FormatNoValueResponse() } -AHKMenuTrayTip(ByRef command) { - value := command[2] +AHKMenuTrayTip(args*) { + value := args[1] Menu, Tray, Tip, %value% return FormatNoValueResponse() } -AHKMenuTrayShow(ByRef command) { +AHKMenuTrayShow(args*) { Menu, Tray, Icon return FormatNoValueResponse() } -AHKMenuTrayIcon(ByRef command) { - filename := command[2] - icon_number := command[3] - freeze := command[4] +AHKMenuTrayIcon(args*) { + filename := args[1] + icon_number := args[2] + freeze := args[3] Menu, Tray, Icon, %filename%, %icon_number%,%freeze% return FormatNoValueResponse() } -AHKGuiNew(ByRef command) { +AHKGuiNew(args*) { - options := command[2] - title := command[3] + options := args[1] + title := args[2] Gui, New, %options%, %title% return FormatResponse("ahk.message.StringResponseMessage", hwnd) } -AHKMsgBox(ByRef command) { +AHKMsgBox(args*) { - options := command[2] - title := command[3] - text := command[4] - timeout := command[5] + 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") @@ -2600,18 +2600,18 @@ AHKMsgBox(ByRef command) { return ret } -AHKInputBox(ByRef command) { +AHKInputBox(args*) { - title := command[2] - prompt := command[3] - hide := command[4] - width := command[5] - height := command[6] - x := command[7] - y := command[8] - locale := command[9] - timeout := command[10] - default := command[11] + 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) { @@ -2624,12 +2624,12 @@ AHKInputBox(ByRef command) { return ret } -AHKFileSelectFile(byRef command) { +AHKFileSelectFile(byRef args) { - options := command[2] - root := command[3] - title := command[4] - filter := command[5] + options := args[1] + root := args[2] + title := args[3] + filter := args[4] FileSelectFile, output, %options%, %root%, %title%, %filter% if (ErrorLevel = 1) { ret := FormatNoValueResponse() @@ -2639,11 +2639,11 @@ AHKFileSelectFile(byRef command) { return ret } -AHKFileSelectFolder(byRef command) { +AHKFileSelectFolder(byRef args) { - starting_folder := command[2] - options := command[3] - prompt := command[4] + starting_folder := args[1] + options := args[2] + prompt := args[3] FileSelectFolder, output, %starting_folder%, %options%, %prompt% @@ -2762,12 +2762,13 @@ pyresp := "" Loop { query := RTrim(stdin.ReadLine(), "`n") - commandArray := CommandArrayFromQuery(query) + argsArray := CommandArrayFromQuery(query) try { - func := commandArray[1] + func := argsArray[1] + argsArray.RemoveAt(1) {% block before_function %} {% endblock before_function %} - pyresp := %func%(commandArray) + pyresp := %func%(argsArray*) {% block after_function %} {% endblock after_function %} } catch e { diff --git a/tests/_async/test_extensions.py b/tests/_async/test_extensions.py index 11430eb6..ff09c5d6 100644 --- a/tests/_async/test_extensions.py +++ b/tests/_async/test_extensions.py @@ -17,9 +17,8 @@ function_name = 'AAHKDoSomething' # unasync: remove ext_text = f'''\ -{function_name}(command) {{ - arg := command[2] - return FormatResponse("ahk.message.StringResponseMessage", Format("test{{}}", arg)) +{function_name}(first, second) {{ + return FormatResponse("ahk.message.StringResponseMessage", Format("{{}} and {{}}", first, second)) }} ''' @@ -27,8 +26,8 @@ @async_extension.register -async def do_something(ahk, arg: str) -> str: - res = await ahk.function_call(function_name, [arg]) +async def do_something(ahk, first: str, second: str) -> str: + res = await ahk.function_call(function_name, [first, second]) return res @@ -41,8 +40,8 @@ async def asyncTearDown(self) -> None: time.sleep(0.2) async def test_ext_explicit(self): - res = await self.ahk.do_something('foo') - assert res == 'testfoo' + res = await self.ahk.do_something('foo', 'bar') + assert res == 'foo and bar' class TestExtensionsAuto(unittest.IsolatedAsyncioTestCase): @@ -54,8 +53,8 @@ async def asyncTearDown(self) -> None: time.sleep(0.2) async def test_ext_auto(self): - res = await self.ahk.do_something('foo') - assert res == 'testfoo' + res = await self.ahk.do_something('foo', 'bar') + assert res == 'foo and bar' class TestNoExtensions(unittest.IsolatedAsyncioTestCase): diff --git a/tests/_sync/test_extensions.py b/tests/_sync/test_extensions.py index e74f41fb..3fb960fb 100644 --- a/tests/_sync/test_extensions.py +++ b/tests/_sync/test_extensions.py @@ -15,9 +15,8 @@ function_name = 'AHKDoSomething' ext_text = f'''\ -{function_name}(command) {{ - arg := command[2] - return FormatResponse("ahk.message.StringResponseMessage", Format("test{{}}", arg)) +{function_name}(first, second) {{ + return FormatResponse("ahk.message.StringResponseMessage", Format("{{}} and {{}}", first, second)) }} ''' @@ -25,8 +24,8 @@ @async_extension.register -def do_something(ahk, arg: str) -> str: - res = ahk.function_call(function_name, [arg]) +def do_something(ahk, first: str, second: str) -> str: + res = ahk.function_call(function_name, [first, second]) return res @@ -39,8 +38,8 @@ def tearDown(self) -> None: time.sleep(0.2) def test_ext_explicit(self): - res = self.ahk.do_something('foo') - assert res == 'testfoo' + res = self.ahk.do_something('foo', 'bar') + assert res == 'foo and bar' class TestExtensionsAuto(unittest.TestCase): @@ -52,8 +51,8 @@ def tearDown(self) -> None: time.sleep(0.2) def test_ext_auto(self): - res = self.ahk.do_something('foo') - assert res == 'testfoo' + res = self.ahk.do_something('foo', 'bar') + assert res == 'foo and bar' class TestNoExtensions(unittest.TestCase): From 32e9e9ff95876c09432413a99087dc0b8a233d4b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 21 Sep 2023 15:51:40 -0700 Subject: [PATCH 462/588] extension documentation and tests --- docs/extending.rst | 125 +++++++++++++++++++++++--------- tests/_async/test_extensions.py | 35 +++++++++ tests/_sync/test_extensions.py | 34 +++++++++ 3 files changed, 160 insertions(+), 34 deletions(-) diff --git a/docs/extending.rst b/docs/extending.rst index ed3badbb..1b47cd0f 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -31,7 +31,7 @@ First, a little background is necessary into the inner mechanisms of how ``ahk`` 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 (v1) accept exactly one argument (an array of zero or more strings) and must return responses in a specific message format (we'll discuss these specifics later) +- 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). @@ -42,8 +42,8 @@ Writing an extension The basics of writing an extension requires two key components: -- A function written in AHK (v1) that conforms to the required spec (accepts one argument of an array of strings 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. +- 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 @@ -62,23 +62,18 @@ When complete, the interface will look something like this: Let's begin writing the extension. -First, we'll start with the AutoHotkey code. This will be an AHK (v1) function that accepts a single argument, which -is an array containing the arguments of the function passed by Python. These start at index 2. +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 -(using the ``FormatResponse`` function which is already defined for you. It accepts two arguments: the messaage type name -and the raw payload. - -.. code-block:: +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). - SimpleMath(ByRef command) { +.. code-block:: - ; `command` is an array with passed arguments, starting at index 2 - lhs := command[2] - rhs := command[3] - operator := command[4] + SimpleMath(lhs, rhs, operator) { if (operator = "+") { result := (lhs + rhs) } else if (operator = "*") { @@ -86,13 +81,10 @@ and the raw payload. } else { ; invalid operator argument return FormatResponse("ahk.message.ExceptionResponseMessage", Format("Invalid operator: {}", operator)) } - return FormatResponse("ahk.message.IntegerResponseMessage", result) } -Note that the ``FormatResponse`` function is already implemented for you! - 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 @@ -161,6 +153,83 @@ In addition to supplying AutoHotkey extension code via ``script_text``, you may 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 your function + + +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]) + + @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]) # 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 ^^^^^^^^^^^^^^^^^^^^^^^ @@ -234,24 +303,12 @@ For example, suppose you want your method to return a datetime object, you might 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 ``DateTimeResponseMessage.fqn()``) +(if you're not sure what this means, you can see this value by calling the ``fqn()`` method, e.g. ``DateTimeResponseMessage.fqn()``) 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 autoexecution -- Extensions must be imported *before* instantiating the ``AHK`` instance -- Although extensions can be declared explicitly, using ``extensions='auto'`` is the recommended method for enabling extensions - - -Packaging -^^^^^^^^^ - -Coming soon. - - -Extending with jinja -^^^^^^^^^^^^^^^^^^^^ - -Coming soon. +- 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'`` is generally the easiest method for enabling all available extensions diff --git a/tests/_async/test_extensions.py b/tests/_async/test_extensions.py index ff09c5d6..f058deb2 100644 --- a/tests/_async/test_extensions.py +++ b/tests/_async/test_extensions.py @@ -3,6 +3,7 @@ import string import time import unittest +from typing import Literal import pytest @@ -22,7 +23,33 @@ }} ''' +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) +}} +''' + 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 @@ -56,6 +83,14 @@ 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: diff --git a/tests/_sync/test_extensions.py b/tests/_sync/test_extensions.py index 3fb960fb..47e83961 100644 --- a/tests/_sync/test_extensions.py +++ b/tests/_sync/test_extensions.py @@ -3,6 +3,7 @@ import string import time import unittest +from typing import Literal import pytest @@ -20,7 +21,32 @@ }} ''' +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) +}} +''' + 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 @@ -54,6 +80,14 @@ 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: From 5e4a5bcaa827db13af479cc96489ef598d661276 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 24 Sep 2023 15:00:23 -0700 Subject: [PATCH 463/588] more extension documentation --- docs/extending.rst | 13 ++++++++++++- requirements-dev.txt | 1 + tests/_async/test_extensions.py | 28 +++++++++++++++++++++++++++- tests/_sync/test_extensions.py | 27 ++++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/docs/extending.rst b/docs/extending.rst index 1b47cd0f..9737bd5d 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -160,7 +160,18 @@ When registering an extension function, if the decorated function is a coroutine 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 your function +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 diff --git a/requirements-dev.txt b/requirements-dev.txt index f321754b..e897cac5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,3 +8,4 @@ mypy typing_extensions jinja2 pytest-rerunfailures +ahk-json diff --git a/tests/_async/test_extensions.py b/tests/_async/test_extensions.py index f058deb2..989ed2aa 100644 --- a/tests/_async/test_extensions.py +++ b/tests/_async/test_extensions.py @@ -39,6 +39,28 @@ }} ''' +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]) + + +@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) @@ -60,7 +82,7 @@ async def do_something(ahk, first: str, second: str) -> str: class TestExtensions(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: - self.ahk = AsyncAHK(extensions=[async_extension]) + self.ahk = AsyncAHK(extensions=[async_extension, dependency_extension]) async def asyncTearDown(self) -> None: self.ahk._transport._proc.kill() @@ -70,6 +92,10 @@ 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: diff --git a/tests/_sync/test_extensions.py b/tests/_sync/test_extensions.py index 47e83961..5bbb4a01 100644 --- a/tests/_sync/test_extensions.py +++ b/tests/_sync/test_extensions.py @@ -36,6 +36,27 @@ }} ''' +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]) + + +@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) @@ -57,7 +78,7 @@ def do_something(ahk, first: str, second: str) -> str: class TestExtensions(unittest.TestCase): def setUp(self) -> None: - self.ahk = AHK(extensions=[async_extension]) + self.ahk = AHK(extensions=[async_extension, dependency_extension]) def tearDown(self) -> None: self.ahk._transport._proc.kill() @@ -67,6 +88,10 @@ 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: From c5898ab324866a0e0bec109146df480116c29087 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 24 Sep 2023 15:27:07 -0700 Subject: [PATCH 464/588] more extension documentation --- docs/extending.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/extending.rst b/docs/extending.rst index 9737bd5d..3b3f8980 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -216,7 +216,7 @@ To specify dependencies, provide a list of ``Extension`` instance objects in the 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]) + 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]: @@ -230,7 +230,7 @@ Then users may use such an extension simply as follows, and both ``JXON`` and `` from ahk import AHK from my_extension import MY_EXTENSION - ahk = AHK(extensions=[MY_EXTENSION]) # same effect as extensions=[JXON, MY_EXTENSION] + ahk = AHK(extensions=[MY_EXTENSION], version='v1') # same effect as extensions=[JXON, MY_EXTENSION] Best practices for extension authors ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 67d2432b933198ffe02f51845c22d10d596c6298 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 24 Sep 2023 15:54:06 -0700 Subject: [PATCH 465/588] extension compatibility checks --- ahk/_async/engine.py | 5 +++++ ahk/_sync/engine.py | 5 +++++ tests/_async/test_extensions.py | 28 +++++++++++++++++++++++----- tests/_sync/test_extensions.py | 24 +++++++++++++++++++++--- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 8edaae4e..f46ef535 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -174,6 +174,11 @@ def __init__( 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={}) for ext in self._extensions: self._method_registry.merge(ext._extension_method_registry) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index afe44ca0..b0d7ca11 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -170,6 +170,11 @@ def __init__( 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={}) for ext in self._extensions: self._method_registry.merge(ext._extension_method_registry) diff --git a/tests/_async/test_extensions.py b/tests/_async/test_extensions.py index 989ed2aa..f2fdeb55 100644 --- a/tests/_async/test_extensions.py +++ b/tests/_async/test_extensions.py @@ -52,13 +52,13 @@ }} ''' -dependency_extension = Extension(script_text=dependency_test_script, dependencies=[JXON]) +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]: +async def my_function(ahk, one: str, two: str) -> list[str]: args = [one, two] - return ahk.function_call(dependency_func_name, args) + return await ahk.function_call(dependency_func_name, args) async_extension = Extension(script_text=ext_text) @@ -85,7 +85,10 @@ async def asyncSetUp(self) -> None: self.ahk = AsyncAHK(extensions=[async_extension, dependency_extension]) async def asyncTearDown(self) -> None: - self.ahk._transport._proc.kill() + try: + self.ahk._transport._proc.kill() + except: + pass time.sleep(0.2) async def test_ext_explicit(self): @@ -102,7 +105,10 @@ async def asyncSetUp(self) -> None: self.ahk = AsyncAHK(extensions='auto') async def asyncTearDown(self) -> None: - self.ahk._transport._proc.kill() + try: + self.ahk._transport._proc.kill() + except: + pass time.sleep(0.2) async def test_ext_auto(self): @@ -135,6 +141,9 @@ 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: @@ -145,3 +154,12 @@ 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/_sync/test_extensions.py b/tests/_sync/test_extensions.py index 5bbb4a01..2694854a 100644 --- a/tests/_sync/test_extensions.py +++ b/tests/_sync/test_extensions.py @@ -48,7 +48,7 @@ }} ''' -dependency_extension = Extension(script_text=dependency_test_script, dependencies=[JXON]) +dependency_extension = Extension(script_text=dependency_test_script, dependencies=[JXON], requires_autohotkey='v1') @dependency_extension.register @@ -81,7 +81,10 @@ def setUp(self) -> None: self.ahk = AHK(extensions=[async_extension, dependency_extension]) def tearDown(self) -> None: - self.ahk._transport._proc.kill() + try: + self.ahk._transport._proc.kill() + except: + pass time.sleep(0.2) def test_ext_explicit(self): @@ -98,7 +101,10 @@ def setUp(self) -> None: self.ahk = AHK(extensions='auto') def tearDown(self) -> None: - self.ahk._transport._proc.kill() + try: + self.ahk._transport._proc.kill() + except: + pass time.sleep(0.2) def test_ext_auto(self): @@ -131,6 +137,9 @@ 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: @@ -141,3 +150,12 @@ 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]) From c83358724975608fcf7c4d7851edce170fc0d989 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 24 Sep 2023 16:25:07 -0700 Subject: [PATCH 466/588] fix python3.8 compatibility in tests --- tests/_async/test_extensions.py | 2 ++ tests/_sync/test_extensions.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/_async/test_extensions.py b/tests/_async/test_extensions.py index f2fdeb55..c61e7126 100644 --- a/tests/_async/test_extensions.py +++ b/tests/_async/test_extensions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import random import string diff --git a/tests/_sync/test_extensions.py b/tests/_sync/test_extensions.py index 2694854a..d983e0fd 100644 --- a/tests/_sync/test_extensions.py +++ b/tests/_sync/test_extensions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import random import string From d634432a0a72059c5b71b32ba7966e464dec59c4 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 24 Sep 2023 16:49:21 -0700 Subject: [PATCH 467/588] 1.4.0rc2 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 44c63fd5..76ca2f25 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.4.0rc1 +version = 1.4.0rc2 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 1fcce26a4b42169156486f9ec7967c00a5dc99f8 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 27 Sep 2023 19:07:41 -0700 Subject: [PATCH 468/588] fix bug in win_get_position --- ahk/_constants.py | 4 ++-- ahk/templates/daemon.ahk | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index a07357bc..0e8b6319 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2374,8 +2374,8 @@ WinGetPos, x, y, w, h, %title%, %text%, %extitle%, %extext% - if (ErrorLevel = 1) { - response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the position") + 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) diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 358c0082..6a3ead5b 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2371,8 +2371,8 @@ AHKWinGetPos(args*) { WinGetPos, x, y, w, h, %title%, %text%, %extitle%, %extext% - if (ErrorLevel = 1) { - response := FormatResponse("ahk.message.ExceptionResponseMessage", "There was a problem getting the position") + 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) From f63d0623b8b6c306791372dd231ce1c28e2169b4 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 28 Sep 2023 17:32:14 -0700 Subject: [PATCH 469/588] initial generic version typing --- ahk/__init__.py | 2 +- ahk/_async/engine.py | 144 ++++++++++++++++++++++++++++---------- ahk/_async/transport.py | 148 ++++++++++++++++++++-------------------- ahk/_async/window.py | 19 ++++-- ahk/_sync/engine.py | 144 ++++++++++++++++++++++++++++---------- ahk/_sync/transport.py | 147 +++++++++++++++++++-------------------- ahk/_sync/window.py | 23 ++++--- ahk/extensions.py | 2 +- ahk/message.py | 6 +- 9 files changed, 394 insertions(+), 241 deletions(-) diff --git a/ahk/__init__.py b/ahk/__init__.py index 7fdd7047..8ae34b52 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -25,7 +25,7 @@ 'MsgBoxModality', ] -_global_instance: Optional[AHK] = None +_global_instance: Optional[AHK[None]] = None def __getattr__(name: str) -> Any: diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index f46ef535..0aa064d4 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -11,6 +11,7 @@ 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 @@ -18,6 +19,7 @@ from typing import overload from typing import Tuple from typing import Type +from typing import TypeVar from typing import Union from .._hotkey import Hotkey @@ -137,9 +139,22 @@ def _resolve_button(button: Union[str, int]) -> str: return resolved_button -class AsyncAHK: +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, + self: AsyncAHK[Optional[Literal['v1', 'v2']]], *, TransportClass: Optional[Type[AsyncTransport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, @@ -810,8 +825,8 @@ async def get_active_window(self, blocking: Literal[False]) -> AsyncFutureResult async def get_active_window(self, blocking: bool = True) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: ... # fmt: on async def get_active_window( - self, blocking: bool = True - ) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: + self: AsyncAHK[Any], blocking: bool = True + ) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]], AsyncFutureResult[AsyncWindow]]: """ Gets the currently active window. """ @@ -1337,14 +1352,25 @@ async def set_volume( 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, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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: AsyncAHK[Optional[Literal['v1']]], 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: AsyncAHK[Optional[Literal['v1']]], 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, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + async def show_traytip(self: AsyncAHK[Optional[Literal['v1']]], 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: AsyncAHK[Optional[Literal['v1']]], 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, @@ -1374,19 +1400,29 @@ async def show_traytip( # fmt: off @overload - async def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_error_traytip(self: AsyncAHK[Optional[Literal['v1']]], 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + async def show_error_traytip(self: AsyncAHK[Optional[Literal['v1']]], 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + async def show_error_traytip(self: AsyncAHK[Optional[Literal['v1']]], 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, + self: AsyncAHK[Any], title: str, text: str, - second: float = 1.0, + second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, @@ -1401,19 +1437,28 @@ async def show_error_traytip( # fmt: off @overload - async def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + async def show_info_traytip(self: AsyncAHK[Optional[Literal['v1']]], 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: AsyncAHK[Optional[Literal['v1']]], 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: AsyncAHK[Optional[Literal['v1']]], 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, + self: AsyncAHK[Any], title: str, text: str, - second: float = 1.0, + second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, @@ -1428,19 +1473,28 @@ async def show_info_traytip( # fmt: off @overload - async def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... @overload - async def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def show_warning_traytip(self: AsyncAHK[Optional[Literal['v1']]], 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + async def show_warning_traytip(self: AsyncAHK[Optional[Literal['v1']]], 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + async def show_warning_traytip(self: AsyncAHK[Optional[Literal['v1']]], 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, + self: AsyncAHK[Any], title: str, text: str, - second: float = 1.0, + second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, @@ -1593,13 +1647,22 @@ async def sound_set( # fmt: off @overload - 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) -> Union[AsyncWindow, None]: ... + 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: AsyncAHK[Optional[Literal['v1']]], 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, 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]]: ... + async def win_get(self: AsyncAHK[Optional[Literal['v1']]], 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, 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]: ... + async def win_get(self: AsyncAHK[Optional[Literal['v1']]], 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, 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]]]: ... + async def win_get(self: AsyncAHK[Optional[Literal['v1']]], 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, @@ -1611,7 +1674,7 @@ async def win_get( title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[None, AsyncWindow]]]: + ) -> Union[AsyncWindow, None, AsyncFutureResult[Union[None, AsyncWindow]], AsyncFutureResult[AsyncWindow]]: """ Analog for `WinGet `_ """ @@ -1727,13 +1790,22 @@ async def win_get_class( # fmt: off @overload - 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) -> Union[Position, None]: ... + 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: AsyncAHK[Optional[Literal['v1']]], 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, 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]]: ... + async def win_get_position(self: AsyncAHK[Optional[Literal['v1']]], 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, 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]: ... + async def win_get_position(self: AsyncAHK[Optional[Literal['v1']]], 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, 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]]]: ... + async def win_get_position(self: AsyncAHK[Optional[Literal['v1']]], 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, @@ -1745,7 +1817,7 @@ async def win_get_position( title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[Position, None, AsyncFutureResult[Union[Position, None]]]: + ) -> Union[Position, None, AsyncFutureResult[Union[Position, None]], AsyncFutureResult[Position]]: """ Analog for `WinGetPos `_ """ diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 0e715f9c..69b7e1e6 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -409,119 +409,119 @@ async def run_script( # fmt: off @overload - async def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + 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] = None) -> Union[Tuple[int, int], None, AsyncFutureResult[Union[Tuple[int, int], None]]]: ... + async def function_call(self, function_name: Literal['AHKImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Tuple[int, int], None, AsyncFutureResult[Union[Tuple[int, int], None]]]: ... @overload - async def function_call(self, function_name: Literal['AHKPixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... + 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] = None) -> Union[Optional[Tuple[int, int]], AsyncFutureResult[Optional[Tuple[int, int]]]]: ... + async def function_call(self, function_name: Literal['AHKPixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Optional[Tuple[int, int]], AsyncFutureResult[Optional[Tuple[int, int]]]]: ... @overload - async def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... + async def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None) -> Union[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... @overload - async def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[int, float, str, None, AsyncFutureResult[None], AsyncFutureResult[str], AsyncFutureResult[int], AsyncFutureResult[float]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[int, AsyncFutureResult[int]]: ... + async def function_call(self, function_name: Literal['AHKKeyWait'], 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['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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['AHKWinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AsyncAHK] = None) -> Union[str, AsyncFutureResult[str]]: ... + 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] = None) -> Union[str, AsyncFutureResult[str]]: ... + 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] = None) -> Union[str, AsyncFutureResult[str]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[Optional[AsyncWindow], AsyncFutureResult[Optional[AsyncWindow]]]: ... + 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] = None) -> Union[Optional[bool], AsyncFutureResult[Optional[bool]]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[Union[Position, None], AsyncFutureResult[Union[None, Position]]]: ... + 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] = None) -> Union[Union[None, AsyncWindow], AsyncFutureResult[Union[None, AsyncWindow]]]: ... + 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] = None) -> Union[Union[None, AsyncWindow], AsyncFutureResult[Union[None, AsyncWindow]]]: ... + 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] = None) -> Union[Union[int, None], AsyncFutureResult[Union[int, None]]]: ... + 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] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... + 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] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... + 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] = None) -> Union[int, AsyncFutureResult[int]]: ... + 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] = None) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + 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] = None) -> Union[Union[None, int], AsyncFutureResult[Union[None, int]]]: ... + 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] = None) -> Union[List[AsyncControl], None, AsyncFutureResult[Union[List[AsyncControl], None]]]: ... + 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] = None) -> Union[Union[None, int], AsyncFutureResult[Union[None, int]]]: ... + 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] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... + 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] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... + 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] = None) -> Union[Union[None, str], AsyncFutureResult[Union[None, str]]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + 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] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + 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] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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 @@ -533,7 +533,7 @@ async def function_call(self, function_name: Literal['AHKGetTitleMatchMode']) -> @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] = None, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: ... + 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 @@ -547,18 +547,18 @@ async def function_call(self, function_name: Literal['AHKGetSendLevel']) -> int: @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] = None) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... + 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] = None) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... + 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] = None) -> Union[AsyncWindow, AsyncFutureResult[AsyncWindow]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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] = None) -> Union[bool, AsyncFutureResult[bool]]: ... + 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 @@ -591,7 +591,7 @@ async def function_call(self, function_name: Literal['AHKClipWait'], args: Optio # @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] = None) -> Union[None, AsyncFutureResult[None]]: ... + 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 @@ -605,7 +605,7 @@ async def function_call(self, function_name: Literal['AHKMenuTrayIcon'], args: O @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['AHKGuiNew'], args: List[str], *, engine: AsyncAHK) -> str: ... + 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 @@ -621,7 +621,7 @@ async def function_call( function_name: FunctionName, args: Optional[List[str]] = None, blocking: bool = True, - engine: Optional[AsyncAHK] = None, + engine: Optional[AsyncAHK[Any]] = None, ) -> Any: if not self._started: with warnings.catch_warnings(record=True) as caught_warnings: @@ -637,13 +637,13 @@ async def function_call( @abstractmethod async def send( - self, request: RequestMessage, engine: Optional[AsyncAHK] = None + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: return NotImplemented @abstractmethod # unasync: remove async def a_send_nonblocking( # unasync: remove - self, request: RequestMessage, engine: Optional[AsyncAHK] = None + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None ) -> AsyncFutureResult[ Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] ]: @@ -651,7 +651,7 @@ async def a_send_nonblocking( # unasync: remove @abstractmethod def send_nonblocking( - self, request: RequestMessage, engine: Optional[AsyncAHK] = None + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None ) -> FutureResult[Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]]: return NotImplemented @@ -784,7 +784,7 @@ async def _create_process( return proc async def _send_nonblocking( - self, request: RequestMessage, engine: Optional[AsyncAHK] = None + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None ) -> Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]: msg = request.format() proc = await self._create_process() @@ -820,7 +820,7 @@ async def _send_nonblocking( return response.unpack() # type: ignore async def a_send_nonblocking( # unasync: remove - self, request: RequestMessage, engine: Optional[AsyncAHK] = None + self, request: RequestMessage, engine: Optional[AsyncAHK[Any]] = None ) -> AsyncFutureResult[ Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] ]: @@ -829,7 +829,7 @@ async def a_send_nonblocking( # unasync: remove return AsyncFutureResult(task) def send_nonblocking( - self, request: RequestMessage, engine: Optional[AsyncAHK] = None + 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) @@ -841,7 +841,7 @@ def send_nonblocking( return FutureResult(fut) async def send( - self, request: RequestMessage, engine: Optional[AsyncAHK] = None + 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 diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 08229b0b..d3080837 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -10,6 +10,7 @@ from typing import Sequence from typing import Tuple from typing import TYPE_CHECKING +from typing import TypeVar from typing import Union from ahk.message import Position @@ -45,10 +46,12 @@ class WindowNotFoundException(Exception): '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, ahk_id: str): - self._engine: AsyncAHK = engine + 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 @@ -381,10 +384,12 @@ async def get_position(self, *, blocking: Literal[False]) -> AsyncFutureResult[O @overload async def get_position(self, *, blocking: Literal[True]) -> Position: ... @overload - async def get_position(self, *, blocking: bool = True) -> Union[Position, AsyncFutureResult[Optional[Position]]]: ... + 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]]]: - resp = await self._engine.win_get_position( + 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, @@ -652,11 +657,11 @@ async def move( ) @classmethod - async def from_pid(cls, engine: AsyncAHK, pid: int) -> Optional[AsyncWindow]: + 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) -> Optional[AsyncWindow]: + async def from_mouse_position(cls, engine: AsyncAHK[Any]) -> Optional[AsyncWindow]: return await engine.win_get_from_mouse_position() diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index b0d7ca11..57505a91 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -11,6 +11,7 @@ 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 @@ -18,6 +19,7 @@ from typing import overload from typing import Tuple from typing import Type +from typing import TypeVar from typing import Union from .._hotkey import Hotkey @@ -133,9 +135,22 @@ def _resolve_button(button: Union[str, int]) -> str: return resolved_button -class AHK: +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, + self: AHK[Optional[Literal['v1', 'v2']]], *, TransportClass: Optional[Type[Transport]] = None, directives: Optional[list[Directive | Type[Directive]]] = None, @@ -801,8 +816,8 @@ def get_active_window(self, blocking: Literal[False]) -> FutureResult[Optional[W def get_active_window(self, blocking: bool = True) -> Union[Optional[Window], FutureResult[Optional[Window]]]: ... # fmt: on def get_active_window( - self, blocking: bool = True - ) -> Union[Optional[Window], FutureResult[Optional[Window]]]: + self: AHK[Any], blocking: bool = True + ) -> Union[Optional[Window], FutureResult[Optional[Window]], FutureResult[Window]]: """ Gets the currently active window. """ @@ -1325,14 +1340,25 @@ def set_volume( 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, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + 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, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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: AHK[Optional[Literal['v1']]], 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: AHK[Optional[Literal['v1']]], 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, title: str, text: str, second: float = 1.0, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + def show_traytip(self: AHK[Optional[Literal['v1']]], 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: AHK[Optional[Literal['v1']]], 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, @@ -1362,19 +1388,29 @@ def show_traytip( # fmt: off @overload - def show_error_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_error_traytip(self: AHK[Optional[Literal['v1']]], 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + def show_error_traytip(self: AHK[Optional[Literal['v1']]], 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + def show_error_traytip(self: AHK[Optional[Literal['v1']]], 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, + self: AHK[Any], title: str, text: str, - second: float = 1.0, + second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, @@ -1389,19 +1425,28 @@ def show_error_traytip( # fmt: off @overload - def show_info_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + @overload + def show_info_traytip(self: AHK[Optional[Literal['v1']]], 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: AHK[Optional[Literal['v1']]], 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: AHK[Optional[Literal['v1']]], 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, + self: AHK[Any], title: str, text: str, - second: float = 1.0, + second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, @@ -1416,19 +1461,28 @@ def show_info_traytip( # fmt: off @overload - def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... @overload - def show_warning_traytip(self, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + def show_warning_traytip(self: AHK[Optional[Literal['v1']]], 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + def show_warning_traytip(self: AHK[Optional[Literal['v1']]], 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, title: str, text: str, second: float = 1.0, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + def show_warning_traytip(self: AHK[Optional[Literal['v1']]], 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, + self: AHK[Any], title: str, text: str, - second: float = 1.0, + second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, @@ -1581,13 +1635,22 @@ def sound_set( # fmt: off @overload - 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) -> Union[Window, None]: ... + 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: AHK[Optional[Literal['v1']]], 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, 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]]: ... + def win_get(self: AHK[Optional[Literal['v1']]], 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, 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]: ... + def win_get(self: AHK[Optional[Literal['v1']]], 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, 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]]]: ... + def win_get(self: AHK[Optional[Literal['v1']]], 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, @@ -1599,7 +1662,7 @@ def win_get( title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[Window, None, FutureResult[Union[None, Window]]]: + ) -> Union[Window, None, FutureResult[Union[None, Window]], FutureResult[Window]]: """ Analog for `WinGet `_ """ @@ -1715,13 +1778,22 @@ def win_get_class( # fmt: off @overload - 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) -> Union[Position, None]: ... + 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: AHK[Optional[Literal['v1']]], 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, 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]]: ... + def win_get_position(self: AHK[Optional[Literal['v1']]], 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, 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]: ... + def win_get_position(self: AHK[Optional[Literal['v1']]], 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, 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]]]: ... + def win_get_position(self: AHK[Optional[Literal['v1']]], 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, @@ -1733,7 +1805,7 @@ def win_get_position( title_match_mode: Optional[TitleMatchMode] = None, detect_hidden_windows: Optional[bool] = None, blocking: bool = True, - ) -> Union[Position, None, FutureResult[Union[Position, None]]]: + ) -> Union[Position, None, FutureResult[Union[Position, None]], FutureResult[Position]]: """ Analog for `WinGetPos `_ """ diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index d5f1df1e..07664914 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -381,119 +381,119 @@ def run_script( # fmt: off @overload - def function_call(self, function_name: Literal['AHKWinExist'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[bool, FutureResult[bool]]: ... + 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] = None) -> Union[Tuple[int, int], None, FutureResult[Union[Tuple[int, int], None]]]: ... + def function_call(self, function_name: Literal['AHKImageSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Tuple[int, int], None, FutureResult[Union[Tuple[int, int], None]]]: ... @overload - def function_call(self, function_name: Literal['AHKPixelGetColor'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... + 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] = None) -> Union[Optional[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: ... + def function_call(self, function_name: Literal['AHKPixelSearch'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Optional[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: ... @overload - def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... + def function_call(self, function_name: Literal['AHKMouseGetPos'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK[Any]] = None) -> Union[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... @overload - def function_call(self, function_name: Literal['AHKKeyState'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[int, float, str, None, FutureResult[None], FutureResult[str], FutureResult[int], FutureResult[float]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[int, FutureResult[int]]: ... + def function_call(self, function_name: Literal['AHKKeyWait'], 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['SetKeyDelay'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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['AHKWinGetTitle'], args: Optional[List[str]] = None, *, blocking: bool = True, engine: Optional[AHK] = None) -> Union[str, FutureResult[str]]: ... + 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] = None) -> Union[str, FutureResult[str]]: ... + 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] = None) -> Union[str, FutureResult[str]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[List[Window], FutureResult[List[Window]]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[Optional[Window], FutureResult[Optional[Window]]]: ... + 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] = None) -> Union[Optional[bool], FutureResult[Optional[bool]]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[Union[Position, None], FutureResult[Union[None, Position]]]: ... + 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] = None) -> Union[Union[None, Window], FutureResult[Union[None, Window]]]: ... + 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] = None) -> Union[Union[None, Window], FutureResult[Union[None, Window]]]: ... + 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] = None) -> Union[Union[int, None], FutureResult[Union[int, None]]]: ... + 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] = None) -> Union[Union[None, str], FutureResult[Union[None, str]]]: ... + 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] = None) -> Union[Union[None, str], FutureResult[Union[None, str]]]: ... + 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] = None) -> Union[int, FutureResult[int]]: ... + 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] = None) -> Union[List[Window], FutureResult[List[Window]]]: ... + 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] = None) -> Union[Union[None, int], FutureResult[Union[None, int]]]: ... + 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] = None) -> Union[List[Control], None, FutureResult[Union[List[Control], None]]]: ... + 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] = None) -> Union[Union[None, int], FutureResult[Union[None, int]]]: ... + 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] = None) -> Union[Union[None, str], FutureResult[Union[None, str]]]: ... + 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] = None) -> Union[Union[None, str], FutureResult[Union[None, str]]]: ... + 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] = None) -> Union[Union[None, str], FutureResult[Union[None, str]]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[bool, FutureResult[bool]]: ... + 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] = None) -> Union[bool, FutureResult[bool]]: ... + 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] = None) -> Union[bool, FutureResult[bool]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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 @@ -505,7 +505,7 @@ 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] = None, blocking: bool = True) -> Union[str, FutureResult[str]]: ... + 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 @@ -519,18 +519,18 @@ def function_call(self, function_name: Literal['AHKGetSendLevel']) -> int: ... @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] = None) -> Union[Window, FutureResult[Window]]: ... + 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] = None) -> Union[Window, FutureResult[Window]]: ... + 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] = None) -> Union[Window, FutureResult[Window]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[None, FutureResult[None]]: ... + 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] = None) -> Union[bool, FutureResult[bool]]: ... + 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 @@ -563,7 +563,7 @@ def function_call(self, function_name: Literal['AHKClipWait'], args: Optional[Li # @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] = None) -> Union[None, FutureResult[None]]: ... + 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 @@ -577,7 +577,7 @@ def function_call(self, function_name: Literal['AHKMenuTrayIcon'], args: Optiona @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['AHKGuiNew'], args: List[str], *, engine: AHK) -> str: ... + 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 @@ -593,7 +593,7 @@ def function_call( function_name: FunctionName, args: Optional[List[str]] = None, blocking: bool = True, - engine: Optional[AHK] = None, + engine: Optional[AHK[Any]] = None, ) -> Any: if not self._started: with warnings.catch_warnings(record=True) as caught_warnings: @@ -609,14 +609,14 @@ def function_call( @abstractmethod def send( - self, request: RequestMessage, engine: Optional[AHK] = None + self, request: RequestMessage, engine: Optional[AHK[Any]] = None ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: return NotImplemented @abstractmethod def send_nonblocking( - self, request: RequestMessage, engine: Optional[AHK] = None + self, request: RequestMessage, engine: Optional[AHK[Any]] = None ) -> FutureResult[Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]]: return NotImplemented @@ -642,7 +642,6 @@ def __init__( 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 @@ -652,8 +651,6 @@ def __init__( else: raise ValueError(f'Invalid version {version!r} - must be one of "v1" or "v2"') - - if jinja_loader is None: try: loader: jinja2.BaseLoader @@ -750,7 +747,7 @@ def _create_process( return proc def _send_nonblocking( - self, request: RequestMessage, engine: Optional[AHK] = None + self, request: RequestMessage, engine: Optional[AHK[Any]] = None ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: msg = request.format() proc = self._create_process() @@ -787,7 +784,7 @@ def _send_nonblocking( def send_nonblocking( - self, request: RequestMessage, engine: Optional[AHK] = None + 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) @@ -799,7 +796,7 @@ def send_nonblocking( return FutureResult(fut) def send( - self, request: RequestMessage, engine: Optional[AHK] = None + 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 diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index baaed410..7c6f6f61 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -10,6 +10,7 @@ from typing import Sequence from typing import Tuple from typing import TYPE_CHECKING +from typing import TypeVar from typing import Union from ahk.message import Position @@ -41,10 +42,12 @@ class WindowNotFoundException(Exception): '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, ahk_id: str): - self._engine: AHK = engine + 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 @@ -289,7 +292,9 @@ 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]]: + def send( + self, keys: str, control: str = '', *, blocking: bool = True + ) -> Union[None, FutureResult[None]]: return self._engine.control_send( keys=keys, control=control, @@ -358,10 +363,12 @@ def get_position(self, *, blocking: Literal[False]) -> FutureResult[Optional[Pos @overload def get_position(self, *, blocking: Literal[True]) -> Position: ... @overload - def get_position(self, *, blocking: bool = True) -> Union[Position, FutureResult[Optional[Position]]]: ... + 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]]]: - resp = self._engine.win_get_position( + 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, @@ -629,11 +636,11 @@ def move( ) @classmethod - def from_pid(cls, engine: AHK, pid: int) -> Optional[Window]: + 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) -> Optional[Window]: + def from_mouse_position(cls, engine: AHK[Any]) -> Optional[Window]: return engine.win_get_from_mouse_position() diff --git a/ahk/extensions.py b/ahk/extensions.py index 88b387da..3677e300 100644 --- a/ahk/extensions.py +++ b/ahk/extensions.py @@ -34,7 +34,7 @@ class _ExtensionEntry: if typing.TYPE_CHECKING: from ahk import AHK, AsyncAHK - TAHK = TypeVar('TAHK', bound=typing.Union[AHK, AsyncAHK]) + TAHK = TypeVar('TAHK', bound=typing.Union[AHK[Any], AsyncAHK[Any]]) @dataclass diff --git a/ahk/message.py b/ahk/message.py index 3a0f1316..a78206c6 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -114,9 +114,9 @@ def __init_subclass__(cls: Type[T_ResponseMessageType], **kwargs: Any) -> None: _message_registry[tom] = cls super().__init_subclass__(**kwargs) - def __init__(self, raw_content: bytes, engine: Optional[Union[AsyncAHK, AHK]] = None): + 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, AHK]] = engine + self._engine: Optional[Union[AsyncAHK[Any], AHK[Any]]] = engine def __repr__(self) -> str: return f'ResponseMessage' @@ -130,7 +130,7 @@ def _tom_lookup(tom: bytes) -> 'ResponseMessageClassTypes': @classmethod def from_bytes( - cls: Type[T_ResponseMessageType], b: bytes, engine: Optional[Union[AsyncAHK, AHK]] = None + 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) From f7fbbe7d3bf3be2e9be7d1a8cca3460ff260c232 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 20:13:09 +0000 Subject: [PATCH 470/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/psf/black: 23.9.1 → 23.11.0](https://github.com/psf/black/compare/23.9.1...23.11.0) - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.12.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.12.0) - [github.com/pre-commit/mirrors-mypy: v1.5.1 → v1.7.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.1...v1.7.0) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01b9d4f2..3492172e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: files: ^(ahk/daemon\.ahk|ahk/_constants\.py) - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: mixed-line-ending args: ["-f", "lf"] @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '23.9.1' + rev: '23.11.0' hooks: - id: black args: @@ -40,12 +40,12 @@ repos: - "120" exclude: ^(ahk/_sync/.*\.py) - repo: https://github.com/asottile/reorder-python-imports - rev: v3.10.0 + rev: v3.12.0 hooks: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.5.1' + rev: 'v1.7.0' hooks: - id: mypy args: From 5e35f632696ef35712adb3a82e51477c2ac3156a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 22 Nov 2023 22:17:43 -0800 Subject: [PATCH 471/588] update actions --- .github/workflows/release.yaml | 2 +- .github/workflows/test.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6c4bb05c..b23911c0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,7 +19,7 @@ jobs: - name: build shell: bash run: | - python -m pip install --upgrade wheel setuptools build unasync tokenize-rt + python -m pip install --upgrade wheel setuptools build python -m build - name: Release PyPI shell: bash diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d1ed71b0..78b50d49 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -33,4 +33,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | pip install --upgrade coveralls - coveralls --service=github + coveralls --service=github-actions From ea22dcccfa3651df8918187255b79f4c1b5c99d7 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 22 Nov 2023 22:44:47 -0800 Subject: [PATCH 472/588] coveralls parallel --- .github/workflows/test.yaml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 78b50d49..d4de6f93 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -31,6 +31,18 @@ jobs: - 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-actions + 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 From 2533efd18f88ca4d4db109cabd7c3a821cbf6e0e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:42:18 +0000 Subject: [PATCH 473/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.7.0 → v1.7.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.0...v1.7.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3492172e..9d63609f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.7.0' + rev: 'v1.7.1' hooks: - id: mypy args: From 5741ba0b046a7db168918cd89bbe0474aef4f39a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 15 Dec 2023 03:48:11 -0800 Subject: [PATCH 474/588] 1.4.0 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 76ca2f25..c2685048 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.4.0rc2 +version = 1.4.0 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 341dbbaed0e6d95cb8cbcd161570d5722da5504a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 20:15:07 +0000 Subject: [PATCH 475/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.11.0 → 23.12.0](https://github.com/psf/black/compare/23.11.0...23.12.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9d63609f..cc862bf9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '23.11.0' + rev: '23.12.0' hooks: - id: black args: From 434c57e7614f87c6ee2edb31eb418e3c44772428 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 19:57:45 +0000 Subject: [PATCH 476/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.12.0 → 23.12.1](https://github.com/psf/black/compare/23.12.0...23.12.1) - [github.com/pre-commit/mirrors-mypy: v1.7.1 → v1.8.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.1...v1.8.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc862bf9..98e7e505 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '23.12.0' + rev: '23.12.1' hooks: - id: black args: @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.7.1' + rev: 'v1.8.0' hooks: - id: mypy args: From 7796089f97cd830f8444d2dcc0d8f2d5ce2f9e9b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:59:40 +0000 Subject: [PATCH 477/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 6.1.0 → 7.0.0](https://github.com/pycqa/flake8/compare/6.1.0...7.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 98e7e505..e1ea268c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,7 +55,7 @@ repos: - jinja2 - repo: https://github.com/pycqa/flake8 - rev: '6.1.0' # pick a git hash / tag to point to + rev: '7.0.0' # pick a git hash / tag to point to hooks: - id: flake8 args: From 859161bc2d6ca862648d81c23c4e5736aa97384b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 5 Feb 2024 19:08:01 -0800 Subject: [PATCH 478/588] exit AHK daemon process if parent Python process exits unexpectedly --- ahk/_constants.py | 19 +++++++++++++++++++ ahk/templates/daemon-v2.ahk | 9 +++++++++ ahk/templates/daemon.ahk | 10 ++++++++++ 3 files changed, 38 insertions(+) diff --git a/ahk/_constants.py b/ahk/_constants.py index 0e8b6319..504dbd4f 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2756,6 +2756,7 @@ {% endfor %} ; END extension scripts + {% block before_autoexecute %} {% endblock before_autoexecute %} @@ -2765,6 +2766,15 @@ 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] @@ -5744,6 +5754,15 @@ 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] diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index e9ca86af..37d59153 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2845,6 +2845,15 @@ 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] diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 6a3ead5b..7017fe8c 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2753,6 +2753,7 @@ CommandArrayFromQuery(ByRef text) { {% endfor %} ; END extension scripts + {% block before_autoexecute %} {% endblock before_autoexecute %} @@ -2762,6 +2763,15 @@ 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] From 03f065c147fa34c4406aa8646f61cb160ad61c89 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 5 Feb 2024 20:10:05 -0800 Subject: [PATCH 479/588] Add handling to detect abrupt parent Python process termination for hotkeys process --- ahk/_constants.py | 31 +++++++++++++++++++++++-------- ahk/_hotkey.py | 5 ++++- ahk/templates/hotkeys-v2.ahk | 19 +++++++++++++------ ahk/templates/hotkeys.ahk | 12 ++++++++++-- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 504dbd4f..cbb8edad 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2806,6 +2806,7 @@ HOTKEYS_SCRIPT_TEMPLATE = r"""#Requires AutoHotkey v1.1.17+ #Persistent +#Warn {% for directive in directives %} {% if directive.apply_to_hotkeys_process %} @@ -2817,6 +2818,7 @@ OnClipboardChange("ClipChanged") {% endif %} KEEPALIVE := Chr(57344) +stdin := FileOpen("*", "r `n", "UTF-8") SetTimer, keepalive, 1000 Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") @@ -2902,8 +2904,14 @@ keepalive: -global KEEPALIVE -FileAppend, %KEEPALIVE%`n, *, UTF-8 + global KEEPALIVE + global stdin + FileAppend, %KEEPALIVE%`n, *, UTF-8 + alivesignal := RTrim(stdin.ReadLine(), "`n") + if (alivesignal = "") { + ExitApp + } + return """ @@ -5805,9 +5813,9 @@ KEEPALIVE := Chr(57344) -;SetTimer, keepalive, 1000 stdout := FileOpen("*", "w", "UTF-8") +stdin := FileOpen("*", "r `n", "UTF-8") WriteStdout(s) { global stdout @@ -5902,10 +5910,17 @@ OnClipboardChange(ClipChanged) {% endif %} - - -;keepalive: -;global KEEPALIVE -;FileAppend, %KEEPALIVE%`n, *, UTF-8 +SetTimer KeepAliveFunc, 1000 + +KeepAliveFunc() { + global stdin + global KEEPALIVE + WriteStdout(Format("{}`n", KEEPALIVE)) + alivesignal := RTrim(stdin.ReadLine(), "`n") + if (alivesignal = "") { + ExitApp + } + return +} """ diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index 07e2c16f..d2982ce5 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -336,11 +336,14 @@ def listener(self) -> None: 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: - assert self._proc.stdout is not None line = self._proc.stdout.readline() if line.rstrip(b'\n') == _KEEPALIVE_SENTINEL: logging.debug('keepalive received') + self._proc.stdin.write(b'alive\n') + self._proc.stdin.flush() continue if not line.strip(): logging.debug('Listener: Process probably died, exiting') diff --git a/ahk/templates/hotkeys-v2.ahk b/ahk/templates/hotkeys-v2.ahk index 09667d7f..530f5215 100644 --- a/ahk/templates/hotkeys-v2.ahk +++ b/ahk/templates/hotkeys-v2.ahk @@ -8,9 +8,9 @@ KEEPALIVE := Chr(57344) -;SetTimer, keepalive, 1000 stdout := FileOpen("*", "w", "UTF-8") +stdin := FileOpen("*", "r `n", "UTF-8") WriteStdout(s) { global stdout @@ -105,8 +105,15 @@ ClipChanged(Type) { OnClipboardChange(ClipChanged) {% endif %} - - -;keepalive: -;global KEEPALIVE -;FileAppend, %KEEPALIVE%`n, *, UTF-8 +SetTimer KeepAliveFunc, 1000 + +KeepAliveFunc() { + global stdin + global KEEPALIVE + WriteStdout(Format("{}`n", KEEPALIVE)) + alivesignal := RTrim(stdin.ReadLine(), "`n") + if (alivesignal = "") { + ExitApp + } + return +} diff --git a/ahk/templates/hotkeys.ahk b/ahk/templates/hotkeys.ahk index 43cb2d9b..567b4344 100644 --- a/ahk/templates/hotkeys.ahk +++ b/ahk/templates/hotkeys.ahk @@ -1,5 +1,6 @@ #Requires AutoHotkey v1.1.17+ #Persistent +#Warn {% for directive in directives %} {% if directive.apply_to_hotkeys_process %} @@ -11,6 +12,7 @@ OnClipboardChange("ClipChanged") {% endif %} KEEPALIVE := Chr(57344) +stdin := FileOpen("*", "r `n", "UTF-8") SetTimer, keepalive, 1000 Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") @@ -96,5 +98,11 @@ ClipChanged(Type) { keepalive: -global KEEPALIVE -FileAppend, %KEEPALIVE%`n, *, UTF-8 + global KEEPALIVE + global stdin + FileAppend, %KEEPALIVE%`n, *, UTF-8 + alivesignal := RTrim(stdin.ReadLine(), "`n") + if (alivesignal = "") { + ExitApp + } + return From f252c9327e68c38202dab64810e67b1c4c943ca9 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Feb 2024 01:10:23 -0800 Subject: [PATCH 480/588] double keepalive interval to 2000ms --- ahk/_constants.py | 18 +++++++++++------- ahk/_hotkey.py | 2 +- ahk/templates/hotkeys-v2.ahk | 8 +++++--- ahk/templates/hotkeys.ahk | 10 ++++++---- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index cbb8edad..0686deb8 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2806,7 +2806,7 @@ HOTKEYS_SCRIPT_TEMPLATE = r"""#Requires AutoHotkey v1.1.17+ #Persistent -#Warn + {% for directive in directives %} {% if directive.apply_to_hotkeys_process %} @@ -2819,7 +2819,7 @@ {% endif %} KEEPALIVE := Chr(57344) stdin := FileOpen("*", "r `n", "UTF-8") -SetTimer, keepalive, 1000 +SetTimer, keepalive, 2000 Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") @@ -2907,8 +2907,10 @@ global KEEPALIVE global stdin FileAppend, %KEEPALIVE%`n, *, UTF-8 - alivesignal := RTrim(stdin.ReadLine(), "`n") - if (alivesignal = "") { + 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 @@ -5910,14 +5912,16 @@ OnClipboardChange(ClipChanged) {% endif %} -SetTimer KeepAliveFunc, 1000 +SetTimer KeepAliveFunc, 2000 KeepAliveFunc() { global stdin global KEEPALIVE WriteStdout(Format("{}`n", KEEPALIVE)) - alivesignal := RTrim(stdin.ReadLine(), "`n") - if (alivesignal = "") { + 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 index d2982ce5..ba378630 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -342,7 +342,7 @@ def listener(self) -> None: line = self._proc.stdout.readline() if line.rstrip(b'\n') == _KEEPALIVE_SENTINEL: logging.debug('keepalive received') - self._proc.stdin.write(b'alive\n') + self._proc.stdin.write(b'\xee\x80\x80\n') self._proc.stdin.flush() continue if not line.strip(): diff --git a/ahk/templates/hotkeys-v2.ahk b/ahk/templates/hotkeys-v2.ahk index 530f5215..02f94edf 100644 --- a/ahk/templates/hotkeys-v2.ahk +++ b/ahk/templates/hotkeys-v2.ahk @@ -105,14 +105,16 @@ ClipChanged(Type) { OnClipboardChange(ClipChanged) {% endif %} -SetTimer KeepAliveFunc, 1000 +SetTimer KeepAliveFunc, 2000 KeepAliveFunc() { global stdin global KEEPALIVE WriteStdout(Format("{}`n", KEEPALIVE)) - alivesignal := RTrim(stdin.ReadLine(), "`n") - if (alivesignal = "") { + 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 index 567b4344..e04d0e20 100644 --- a/ahk/templates/hotkeys.ahk +++ b/ahk/templates/hotkeys.ahk @@ -1,6 +1,6 @@ #Requires AutoHotkey v1.1.17+ #Persistent -#Warn + {% for directive in directives %} {% if directive.apply_to_hotkeys_process %} @@ -13,7 +13,7 @@ OnClipboardChange("ClipChanged") {% endif %} KEEPALIVE := Chr(57344) stdin := FileOpen("*", "r `n", "UTF-8") -SetTimer, keepalive, 1000 +SetTimer, keepalive, 2000 Crypt32 := DllCall("LoadLibrary", "Str", "Crypt32.dll", "Ptr") @@ -101,8 +101,10 @@ keepalive: global KEEPALIVE global stdin FileAppend, %KEEPALIVE%`n, *, UTF-8 - alivesignal := RTrim(stdin.ReadLine(), "`n") - if (alivesignal = "") { + 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 From 065581d1783b3296ffc9ea7e1cb4f09d48f5121b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Feb 2024 01:18:24 -0800 Subject: [PATCH 481/588] update docs --- docs/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 676366ec..82f312a6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,7 +35,8 @@ print(ahk.mouse_position) # (150, 150) # Examples -Non-exhaustive examples of some functions available with this package. Full documentation coming soon! +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 From c98925ae299dba554bd48e73ef3f047801bfbb81 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 7 Feb 2024 01:35:14 -0800 Subject: [PATCH 482/588] 1.5.0rc1 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index c2685048..0450732d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.4.0 +version = 1.5.0rc1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 746d1f7554ccd3e68b123f6e2c4be1c9dbb02c47 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 12 Feb 2024 16:08:49 -0800 Subject: [PATCH 483/588] 1.5.0 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 0450732d..93595a48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.5.0rc1 +version = 1.5.0 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From c40469e8c5de33c083a8de682887779c69dd96d6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 12 Feb 2024 16:32:29 -0800 Subject: [PATCH 484/588] fix conflicts with black and flake8/reorder-imports --- .pre-commit-config.yaml | 4 ++-- _tests_setup.py | 5 +---- ahk/_async/transport.py | 15 +++++---------- ahk/_async/window.py | 39 +++++++++++++-------------------------- ahk/_hotkey.py | 6 ++---- ahk/_sync/transport.py | 12 ++++-------- ahk/_sync/window.py | 39 +++++++++++++-------------------------- ahk/directives.py | 3 --- ahk/keys.py | 6 +----- ahk/message.py | 6 ++---- buildunasync.py | 2 +- 11 files changed, 44 insertions(+), 93 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e1ea268c..10ece8eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '23.12.1' + rev: '24.2.0' hooks: - id: black args: @@ -60,5 +60,5 @@ repos: - id: flake8 args: - "--ignore" - - "E501,E704,E301,W503" + - "E501,E704,E301,W503,E701" files: ahk\/(?!_sync).* diff --git a/_tests_setup.py b/_tests_setup.py index 34639b64..949b414c 100644 --- a/_tests_setup.py +++ b/_tests_setup.py @@ -1,7 +1,4 @@ -""" -Not a real setup package -This is just to unasync our tests files -""" +# Not a real setup package. This is just to unasync our tests files import setuptools import unasync diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 69b7e1e6..1501b4c5 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -58,8 +58,7 @@ T_SyncFuture = TypeVar('T_SyncFuture') -class AHKProtocolError(Exception): - ... +class AHKProtocolError(Exception): ... class AsyncFutureResult(Generic[T_AsyncFuture]): # unasync: remove @@ -191,8 +190,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: @runtime_checkable class Killable(Protocol): - def kill(self) -> None: - ... + def kill(self) -> None: ... def kill(proc: Killable) -> None: @@ -213,17 +211,14 @@ def async_assert_send_nonblocking_type_correct( class Communicable(Protocol): runargs: List[str] - def communicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: - ... + 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]: - ... + ) -> Tuple[bytes, bytes]: ... @property - def returncode(self) -> Optional[int]: - ... + def returncode(self) -> Optional[int]: ... class AsyncAHKProcess: diff --git a/ahk/_async/window.py b/ahk/_async/window.py index d3080837..656e0d9b 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -25,8 +25,7 @@ from .transport import AsyncFutureResult -class WindowNotFoundException(Exception): - ... +class WindowNotFoundException(Exception): ... AsyncPropertyReturnStr: TypeAlias = Coroutine[None, None, str] # unasync: remove @@ -535,20 +534,16 @@ async def redraw(self, *, blocking: bool = True) -> Union[None, AsyncFutureResul ) @overload - async def set_style(self, style: str) -> bool: - ... + async def set_style(self, style: str) -> bool: ... @overload - async def set_style(self, style: str, *, blocking: Literal[True]) -> bool: - ... + async def set_style(self, style: str, *, blocking: Literal[True]) -> bool: ... @overload - async def set_style(self, style: str, *, blocking: Literal[False]) -> AsyncFutureResult[bool]: - ... + 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]]: ... async def set_style(self, style: str, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: return await self._engine.win_set_style( @@ -560,20 +555,16 @@ async def set_style(self, style: str, *, blocking: bool = True) -> Union[bool, A ) @overload - async def set_ex_style(self, style: str) -> bool: - ... + async def set_ex_style(self, style: str) -> bool: ... @overload - async def set_ex_style(self, style: str, *, blocking: Literal[False]) -> AsyncFutureResult[bool]: - ... + 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: - ... + 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]]: ... async def set_ex_style(self, style: str, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: return await self._engine.win_set_ex_style( @@ -585,20 +576,16 @@ async def set_ex_style(self, style: str, *, blocking: bool = True) -> Union[bool ) @overload - async def set_region(self, options: str) -> bool: - ... + async def set_region(self, options: str) -> bool: ... @overload - async def set_region(self, options: str, *, blocking: Literal[True]) -> bool: - ... + async def set_region(self, options: str, *, blocking: Literal[True]) -> bool: ... @overload - async def set_region(self, options: str, *, blocking: Literal[False]) -> AsyncFutureResult[bool]: - ... + 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]]: ... async def set_region(self, options: str, *, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: return await self._engine.win_set_region( diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index ba378630..90194974 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -158,8 +158,7 @@ def on_clipboard_change( self.restart() -class STOP: - ... +class STOP: ... class ThreadedHotkeyTransport(HotkeyTransportBase): @@ -453,8 +452,7 @@ def _validate(self) -> None: @runtime_checkable class Killable(Protocol): - def kill(self) -> None: - ... + def kill(self) -> None: ... def kill(proc: Killable) -> None: diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 07664914..27cf3b7c 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -57,8 +57,7 @@ T_SyncFuture = TypeVar('T_SyncFuture') -class AHKProtocolError(Exception): - ... +class AHKProtocolError(Exception): ... @@ -183,8 +182,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: @runtime_checkable class Killable(Protocol): - def kill(self) -> None: - ... + def kill(self) -> None: ... def kill(proc: Killable) -> None: @@ -205,13 +203,11 @@ def async_assert_send_nonblocking_type_correct( class Communicable(Protocol): runargs: List[str] - def communicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: - ... + def communicate(self, input_bytes: Optional[bytes], timeout: Optional[int] = None) -> Tuple[bytes, bytes]: ... @property - def returncode(self) -> Optional[int]: - ... + def returncode(self) -> Optional[int]: ... class SyncAHKProcess: diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 7c6f6f61..65769036 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -25,8 +25,7 @@ from .transport import FutureResult -class WindowNotFoundException(Exception): - ... +class WindowNotFoundException(Exception): ... SyncPropertyReturnStr: TypeAlias = str @@ -514,20 +513,16 @@ def redraw(self, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ) @overload - def set_style(self, style: str) -> bool: - ... + def set_style(self, style: str) -> bool: ... @overload - def set_style(self, style: str, *, blocking: Literal[True]) -> bool: - ... + def set_style(self, style: str, *, blocking: Literal[True]) -> bool: ... @overload - def set_style(self, style: str, *, blocking: Literal[False]) -> FutureResult[bool]: - ... + 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]]: ... def set_style(self, style: str, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: return self._engine.win_set_style( @@ -539,20 +534,16 @@ def set_style(self, style: str, *, blocking: bool = True) -> Union[bool, FutureR ) @overload - def set_ex_style(self, style: str) -> bool: - ... + def set_ex_style(self, style: str) -> bool: ... @overload - def set_ex_style(self, style: str, *, blocking: Literal[False]) -> FutureResult[bool]: - ... + def set_ex_style(self, style: str, *, blocking: Literal[False]) -> FutureResult[bool]: ... @overload - def set_ex_style(self, style: str, *, blocking: Literal[True]) -> bool: - ... + 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]]: ... def set_ex_style(self, style: str, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: return self._engine.win_set_ex_style( @@ -564,20 +555,16 @@ def set_ex_style(self, style: str, *, blocking: bool = True) -> Union[bool, Futu ) @overload - def set_region(self, options: str) -> bool: - ... + def set_region(self, options: str) -> bool: ... @overload - def set_region(self, options: str, *, blocking: Literal[True]) -> bool: - ... + def set_region(self, options: str, *, blocking: Literal[True]) -> bool: ... @overload - def set_region(self, options: str, *, blocking: Literal[False]) -> FutureResult[bool]: - ... + 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]]: ... def set_region(self, options: str, *, blocking: bool = True) -> Union[bool, FutureResult[bool]]: return self._engine.win_set_region( diff --git a/ahk/directives.py b/ahk/directives.py index 7e16b9bd..dbb54824 100644 --- a/ahk/directives.py +++ b/ahk/directives.py @@ -1,6 +1,3 @@ -""" -Contains directive classes -""" from types import SimpleNamespace from typing import Any from typing import NoReturn diff --git a/ahk/keys.py b/ahk/keys.py index c978ce73..6eb0c851 100644 --- a/ahk/keys.py +++ b/ahk/keys.py @@ -1,6 +1,3 @@ -""" -The ahk.keys module contains some useful constants and classes for working with keys. -""" from __future__ import annotations from typing import Any @@ -115,8 +112,7 @@ def __repr__(self) -> str: @runtime_checkable class Stringable(Protocol): - def __str__(self) -> str: - ... + def __str__(self) -> str: ... class KeyModifier(Key): diff --git a/ahk/message.py b/ahk/message.py index a78206c6..c9bf1273 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -28,8 +28,7 @@ from typing import Union -class OutOfMessageTypes(Exception): - ... +class OutOfMessageTypes(Exception): ... Position = namedtuple('Position', ('x', 'y', 'width', 'height')) @@ -37,8 +36,7 @@ class OutOfMessageTypes(Exception): @runtime_checkable class BytesLineReadable(Protocol): - def readline(self) -> bytes: - ... + def readline(self) -> bytes: ... def is_window_control_list_response(resp_obj: object) -> TypeGuard[Tuple[str, List[Tuple[str, str]]]]: diff --git a/buildunasync.py b/buildunasync.py index d187ee84..d53b3995 100644 --- a/buildunasync.py +++ b/buildunasync.py @@ -18,7 +18,7 @@ 'async_sleep': 'sleep', 'AsyncFutureResult': 'FutureResult', '_async_run_nonblocking': '_sync_run_nonblocking', - 'acommunicate': 'communicate' + 'acommunicate': 'communicate', # "__aenter__": "__aenter__", }, ), From 074e82f608815831802771aa07b13fab4e992148 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 26 Feb 2024 15:46:06 -0800 Subject: [PATCH 485/588] GH-264 fix mouse_drag error when using AutoHotkey v2 --- ahk/_async/engine.py | 4 +++- ahk/_constants.py | 12 +++++++++++- ahk/_sync/engine.py | 4 +++- ahk/templates/daemon-v2.ahk | 12 +++++++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 0aa064d4..b7dde234 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2901,7 +2901,7 @@ async def mouse_drag( *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, - button: MouseButton = 1, + button: MouseButton = 'left', relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, @@ -2927,6 +2927,8 @@ async def mouse_drag( if coord_mode: args.append(coord_mode) + else: + args.append('') await self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) diff --git a/ahk/_constants.py b/ahk/_constants.py index 0686deb8..4158936d 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -4815,7 +4815,17 @@ CoordMode("Mouse", relative_to) } - MouseClickDrag(button, x1, y1, x2, y2, speed, relative) + 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) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 57505a91..d3839329 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2889,7 +2889,7 @@ def mouse_drag( *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, - button: MouseButton = 1, + button: MouseButton = 'left', relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, @@ -2915,6 +2915,8 @@ def mouse_drag( if coord_mode: args.append(coord_mode) + else: + args.append('') self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 37d59153..2742a6f7 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -1896,7 +1896,17 @@ AHKMouseClickDrag(args*) { CoordMode("Mouse", relative_to) } - MouseClickDrag(button, x1, y1, x2, y2, speed, relative) + 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) From a89a5f5fcde174e64246c9f446632f610f075985 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 26 Feb 2024 15:58:51 -0800 Subject: [PATCH 486/588] 1.5.1 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 93595a48..7d07cb3c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.5.0 +version = 1.5.1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 41e73e14566c8ea2759fdf460ee09ababa20fe1e Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 27 Feb 2024 15:06:39 -0800 Subject: [PATCH 487/588] make mouse_drag button argument consistent with click --- ahk/_async/engine.py | 6 +++++- ahk/_sync/engine.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index b7dde234..4634d6e7 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2901,7 +2901,7 @@ async def mouse_drag( *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, - button: MouseButton = 'left', + button: Optional[Union[MouseButton, str]] = None, relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, @@ -2909,6 +2909,10 @@ async def mouse_drag( """ 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)] diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index d3839329..dd8da8be 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2889,7 +2889,7 @@ def mouse_drag( *, from_position: Optional[Tuple[int, int]] = None, speed: Optional[int] = None, - button: MouseButton = 'left', + button: Optional[Union[MouseButton, str]] = None, relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, @@ -2897,6 +2897,10 @@ def mouse_drag( """ 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)] From 8ab9a985a15ba3e9fe2587e41b007f8db94f413d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 27 Feb 2024 16:18:26 -0800 Subject: [PATCH 488/588] [feat] add ability to change SendMode --- ahk/_async/engine.py | 127 ++++++++++++++++++++++++++++++------ ahk/_async/transport.py | 6 ++ ahk/_constants.py | 114 +++++++++++++++++++++++++++++++- ahk/_sync/engine.py | 127 ++++++++++++++++++++++++++++++------ ahk/_sync/transport.py | 6 ++ ahk/templates/daemon-v2.ahk | 59 ++++++++++++++++- ahk/templates/daemon.ahk | 55 ++++++++++++++++ tests/_async/test_mouse.py | 33 +++++++++- tests/_sync/test_mouse.py | 33 +++++++++- 9 files changed, 514 insertions(+), 46 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 4634d6e7..33e664f4 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -109,6 +109,8 @@ ], ] +SendMode: TypeAlias = Literal['Event', 'Input', 'InputThenPlay', 'Play', ''] + AsyncPropertyReturnTupleIntInt: TypeAlias = Coroutine[None, None, Tuple[int, int]] # unasync: remove SyncPropertyReturnTupleIntInt: TypeAlias = Tuple[int, int] @@ -372,6 +374,15 @@ async def get_coord_mode(self, target: CoordModeTargets) -> str: resp = await self._transport.function_call('AHKGetCoordMode', args) return resp + async def set_send_mode(self, mode: SendMode) -> None: + 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: ... @@ -769,13 +780,13 @@ def mouse_position(self, new_position: Tuple[int, int]) -> None: # 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) -> None: ... + 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) -> 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) -> None: ... + 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) -> 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, ) -> AsyncFutureResult[None]: ... + 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) -> 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) -> Union[None, AsyncFutureResult[None]]: ... + 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) -> Union[None, AsyncFutureResult[None]]: ... # fmt: on async def mouse_move( self, @@ -784,6 +795,7 @@ async def mouse_move( *, speed: Optional[int] = None, relative: bool = False, + send_mode: Optional[SendMode] = None, blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: """ @@ -804,6 +816,11 @@ async def mouse_move( args.append('R') else: args.append('') + if send_mode: + args.append(send_mode) + else: + args.append('') + resp = await self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp @@ -1173,13 +1190,13 @@ async def get_send_level(self) -> int: # fmt: off @overload - async def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None) -> None: ... + 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, blocking: Literal[True]) -> None: ... + 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, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + 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, @@ -1188,6 +1205,7 @@ async def send( 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]]: """ @@ -1202,6 +1220,10 @@ async def send( 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) @@ -2754,13 +2776,13 @@ async def win_set_trans_color( # 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) -> None: ... + 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) -> None: ... + 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) -> AsyncFutureResult[None]: ... + 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) -> Union[None, AsyncFutureResult[None]]: ... + 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, @@ -2772,6 +2794,7 @@ async def right_click( 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( @@ -2783,17 +2806,18 @@ async def right_click( 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) -> None: ... + 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) -> None: ... + 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) -> AsyncFutureResult[None]: ... + 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) -> Union[None, AsyncFutureResult[None]]: ... + 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, @@ -2806,6 +2830,7 @@ async def click( relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, + send_mode: Optional[SendMode] = None, ) -> Union[None, AsyncFutureResult[None]]: """ Analog for `Click `_ @@ -2825,7 +2850,9 @@ async def click( r = '' if coord_mode is None: coord_mode = '' - args = [str(x), str(y), button, str(click_count), direction or '', r, 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 @@ -2894,6 +2921,48 @@ async def image_search( resp = await self._transport.function_call('AHKImageSearch', args, blocking=blocking) return resp + @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, @@ -2905,7 +2974,21 @@ async def mouse_drag( relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, - ) -> None: + send_mode: Optional[SendMode] = None, + ) -> Union[None, AsyncFutureResult[None]]: ... + 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 `_ """ @@ -2934,7 +3017,13 @@ async def mouse_drag( else: args.append('') - await self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) + 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 diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 1501b4c5..4e367504 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -95,6 +95,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKGetClipboardAll', 'AHKGetCoordMode', 'AHKGetSendLevel', + 'AHKGetSendMode', 'AHKGetTitleMatchMode', 'AHKGetTitleMatchSpeed', 'AHKGetVolume', @@ -125,6 +126,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSetCoordMode', 'AHKSetDetectHiddenWindows', 'AHKSetSendLevel', + 'AHKSetSendMode', 'AHKSetTitleMatchMode', 'AHKSetVolume', 'AHKShowToolTip', @@ -540,6 +542,10 @@ async def function_call(self, function_name: Literal['AHKSetCoordMode'], args: L @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]]: ... diff --git a/ahk/_constants.py b/ahk/_constants.py index 4158936d..e7ca2c95 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -1728,14 +1728,25 @@ 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% } @@ -1779,6 +1790,18 @@ {% 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] @@ -1789,6 +1812,11 @@ 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) @@ -1802,6 +1830,10 @@ CoordMode, Mouse, %current_coord_rel% } + if (send_mode != "") { + SendMode, %current_send_mode% + } + return FormatNoValueResponse() {% endblock AHKMouseClickDrag %} @@ -1880,18 +1912,29 @@ 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 %} } @@ -1901,18 +1944,30 @@ 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 %} } @@ -4728,11 +4783,23 @@ y := args[2] speed := args[3] relative := args[4] + send_mode := args[5] + current_send_mode := Format("{}", A_SendMode) + + if (send_mode != "") { + SendMode send_mode + } + if (relative != "") { - MouseMove(x, y, speed, "R") + MouseMove(x, y, speed, "R") } else { - MouseMove(x, y, speed) + MouseMove(x, y, speed) } + + if (send_mode != "") { + SendMode current_send_mode + } + resp := FormatNoValueResponse() return resp {% endblock AHKMouseMove %} @@ -4747,7 +4814,13 @@ 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) @@ -4759,6 +4832,9 @@ CoordMode("Mouse", current_coord_rel) } + if (send_mode != "") { + SendMode current_send_mode + } return FormatNoValueResponse() {% endblock AHKClick %} @@ -4798,6 +4874,19 @@ {% 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] @@ -4808,8 +4897,13 @@ 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) @@ -4831,6 +4925,10 @@ CoordMode("Mouse", current_coord_rel) } + if (send_mode != "") { + SendMode current_send_mode + } + return FormatNoValueResponse() {% endblock AHKMouseClickDrag %} @@ -4904,8 +5002,14 @@ 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 != "") { SetKeyDelay(key_delay, key_press_duration) @@ -4914,6 +5018,10 @@ Send(str) + if (send_mode != "") { + SendMode current_send_mode + } + if (key_delay != "" or key_press_duration != "") { SetKeyDelay(current_delay, current_key_duration) } diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index dd8da8be..926c830a 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -107,6 +107,8 @@ ], ] +SendMode: TypeAlias = Literal['Event', 'Input', 'InputThenPlay', 'Play', ''] + SyncPropertyReturnTupleIntInt: TypeAlias = Tuple[int, int] SyncPropertyReturnOptionalAsyncWindow: TypeAlias = Optional[Window] @@ -367,6 +369,15 @@ def get_coord_mode(self, target: CoordModeTargets) -> str: resp = self._transport.function_call('AHKGetCoordMode', args) return resp + def set_send_mode(self, mode: SendMode) -> None: + 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: ... @@ -760,13 +771,13 @@ def mouse_position(self, new_position: Tuple[int, int]) -> None: # 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) -> None: ... + 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) -> 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) -> None: ... + 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) -> 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, ) -> FutureResult[None]: ... + 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) -> 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) -> Union[None, FutureResult[None]]: ... + 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) -> Union[None, FutureResult[None]]: ... # fmt: on def mouse_move( self, @@ -775,6 +786,7 @@ def mouse_move( *, speed: Optional[int] = None, relative: bool = False, + send_mode: Optional[SendMode] = None, blocking: bool = True, ) -> Union[None, FutureResult[None]]: """ @@ -795,6 +807,11 @@ def mouse_move( args.append('R') else: args.append('') + if send_mode: + args.append(send_mode) + else: + args.append('') + resp = self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp @@ -1161,13 +1178,13 @@ def get_send_level(self) -> int: # fmt: off @overload - def send(self, s: str, *, raw: bool = False, key_delay: Optional[int] = None, key_press_duration: Optional[int] = None) -> None: ... + 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, blocking: Literal[True]) -> None: ... + 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, blocking: Literal[False]) -> FutureResult[None]: ... + 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, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + 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, @@ -1176,6 +1193,7 @@ def send( 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]]: """ @@ -1190,6 +1208,10 @@ def send( 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) @@ -2742,13 +2764,13 @@ def win_set_trans_color( # 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) -> None: ... + 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) -> None: ... + 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) -> FutureResult[None]: ... + 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) -> Union[None, FutureResult[None]]: ... + 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, @@ -2760,6 +2782,7 @@ def right_click( 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( @@ -2771,17 +2794,18 @@ def right_click( 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) -> None: ... + 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) -> None: ... + 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) -> FutureResult[None]: ... + 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) -> Union[None, FutureResult[None]]: ... + 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, @@ -2794,6 +2818,7 @@ def click( relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, + send_mode: Optional[SendMode] = None, ) -> Union[None, FutureResult[None]]: """ Analog for `Click `_ @@ -2813,7 +2838,9 @@ def click( r = '' if coord_mode is None: coord_mode = '' - args = [str(x), str(y), button, str(click_count), direction or '', r, 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 @@ -2882,6 +2909,48 @@ def image_search( resp = self._transport.function_call('AHKImageSearch', args, blocking=blocking) return resp + @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, @@ -2893,7 +2962,21 @@ def mouse_drag( relative: Optional[bool] = None, blocking: bool = True, coord_mode: Optional[CoordModeRelativeTo] = None, - ) -> None: + send_mode: Optional[SendMode] = None, + ) -> Union[None, FutureResult[None]]: ... + 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 `_ """ @@ -2922,7 +3005,13 @@ def mouse_drag( else: args.append('') - self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) + if send_mode: + args.append(send_mode) + else: + args.append('') + + resp = self._transport.function_call('AHKMouseClickDrag', args, blocking=blocking) + return resp # fmt: off @overload diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 27cf3b7c..f697964b 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -87,6 +87,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKGetClipboardAll', 'AHKGetCoordMode', 'AHKGetSendLevel', + 'AHKGetSendMode', 'AHKGetTitleMatchMode', 'AHKGetTitleMatchSpeed', 'AHKGetVolume', @@ -117,6 +118,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKSetCoordMode', 'AHKSetDetectHiddenWindows', 'AHKSetSendLevel', + 'AHKSetSendMode', 'AHKSetTitleMatchMode', 'AHKSetVolume', 'AHKShowToolTip', @@ -513,6 +515,10 @@ def function_call(self, function_name: Literal['AHKSetCoordMode'], args: List[st @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]]: ... diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 2742a6f7..8678f8f8 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -1809,11 +1809,23 @@ AHKMouseMove(args*) { y := args[2] speed := args[3] relative := args[4] + send_mode := args[5] + current_send_mode := Format("{}", A_SendMode) + + if (send_mode != "") { + SendMode send_mode + } + if (relative != "") { - MouseMove(x, y, speed, "R") + MouseMove(x, y, speed, "R") } else { - MouseMove(x, y, speed) + MouseMove(x, y, speed) } + + if (send_mode != "") { + SendMode current_send_mode + } + resp := FormatNoValueResponse() return resp {% endblock AHKMouseMove %} @@ -1828,7 +1840,13 @@ AHKClick(args*) { 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) @@ -1840,6 +1858,9 @@ AHKClick(args*) { CoordMode("Mouse", current_coord_rel) } + if (send_mode != "") { + SendMode current_send_mode + } return FormatNoValueResponse() {% endblock AHKClick %} @@ -1879,6 +1900,19 @@ AHKSetCoordMode(args*) { {% 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] @@ -1889,8 +1923,13 @@ AHKMouseClickDrag(args*) { 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) @@ -1912,6 +1951,10 @@ AHKMouseClickDrag(args*) { CoordMode("Mouse", current_coord_rel) } + if (send_mode != "") { + SendMode current_send_mode + } + return FormatNoValueResponse() {% endblock AHKMouseClickDrag %} @@ -1985,8 +2028,14 @@ AHKSend(args*) { 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 != "") { SetKeyDelay(key_delay, key_press_duration) @@ -1995,6 +2044,10 @@ AHKSend(args*) { Send(str) + if (send_mode != "") { + SendMode current_send_mode + } + if (key_delay != "" or key_press_duration != "") { SetKeyDelay(current_delay, current_key_duration) } diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 7017fe8c..558677e6 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1725,14 +1725,25 @@ AHKClick(args*) { 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% } @@ -1776,6 +1787,18 @@ AHKSetCoordMode(args*) { {% 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] @@ -1786,6 +1809,11 @@ AHKMouseClickDrag(args*) { 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) @@ -1799,6 +1827,10 @@ AHKMouseClickDrag(args*) { CoordMode, Mouse, %current_coord_rel% } + if (send_mode != "") { + SendMode, %current_send_mode% + } + return FormatNoValueResponse() {% endblock AHKMouseClickDrag %} @@ -1877,18 +1909,29 @@ AHKSend(args*) { 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 %} } @@ -1898,18 +1941,30 @@ AHKSendRaw(args*) { 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 %} } diff --git a/tests/_async/test_mouse.py b/tests/_async/test_mouse.py index 26477e6c..ea412711 100644 --- a/tests/_async/test_mouse.py +++ b/tests/_async/test_mouse.py @@ -57,7 +57,7 @@ async def test_mouse_move_rel(self): async def test_mouse_move_nonblocking(self): await self.ahk.mouse_move(100, 100) - res = await self.ahk.mouse_move(500, 500, speed=5, blocking=False) + 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() @@ -65,6 +65,37 @@ async def test_mouse_move_nonblocking(self): 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: diff --git a/tests/_sync/test_mouse.py b/tests/_sync/test_mouse.py index e5616c6e..a8b580c4 100644 --- a/tests/_sync/test_mouse.py +++ b/tests/_sync/test_mouse.py @@ -56,7 +56,7 @@ def test_mouse_move_rel(self): def test_mouse_move_nonblocking(self): self.ahk.mouse_move(100, 100) - res = self.ahk.mouse_move(500, 500, speed=5, blocking=False) + 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() @@ -64,6 +64,37 @@ def test_mouse_move_nonblocking(self): 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: From 0612ecddc10794504757d88ff6117e98f1f0b6a9 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 27 Feb 2024 20:33:35 -0800 Subject: [PATCH 489/588] GH-266 fix key_wait --- ahk/_async/engine.py | 20 +++++++++++--------- ahk/_async/transport.py | 2 +- ahk/_constants.py | 29 +++++++++++++++++++++++------ ahk/_sync/engine.py | 20 +++++++++++--------- ahk/_sync/transport.py | 2 +- ahk/templates/daemon-v2.ahk | 12 +++++++++--- ahk/templates/daemon.ahk | 17 ++++++++++++++--- docs/README.md | 6 ++++-- tests/_async/test_keys.py | 16 ++++++++++++++++ tests/_sync/test_keys.py | 16 ++++++++++++++++ 10 files changed, 106 insertions(+), 34 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 33e664f4..15fbad14 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -375,6 +375,9 @@ async def get_coord_mode(self, target: CoordModeTargets) -> str: 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 @@ -1126,13 +1129,13 @@ async def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[Non # fmt: off @overload - async def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... + async def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> bool: ... @overload - async def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... + async def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> bool: ... @overload - async def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> AsyncFutureResult[int]: ... + async def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> AsyncFutureResult[bool]: ... @overload - async def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[int, AsyncFutureResult[int]]: ... + async def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... # fmt: on async def key_wait( self, @@ -1142,7 +1145,7 @@ async def key_wait( logical_state: bool = False, released: bool = False, blocking: bool = True, - ) -> Union[int, AsyncFutureResult[int]]: + ) -> Union[bool, AsyncFutureResult[bool]]: """ Analog for `KeyWait `_ """ @@ -1151,11 +1154,10 @@ async def key_wait( options += 'D' if logical_state: options += 'L' - if timeout: + if timeout is not None: + assert timeout >= 0, 'Timeout value must be non-negative' options += f'T{timeout}' - args = [key_name] - if options: - args.append(options) + args = [key_name, options] resp = await self._transport.function_call('AHKKeyWait', args, blocking=blocking) return resp diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 4e367504..6aa7ce03 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -424,7 +424,7 @@ async def function_call(self, function_name: Literal['AHKClick'], args: Optional @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[int, AsyncFutureResult[int]]: ... + 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 diff --git a/ahk/_constants.py b/ahk/_constants.py index e7ca2c95..c4b2c903 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -1891,13 +1891,24 @@ {% block AHKKeyWait %} keyname := args[1] - if (args.Length() = 2) { + options := args[2] + + if (options = "") { KeyWait,% keyname } else { - options := args[2] KeyWait,% keyname,% options } - return FormatResponse("ahk.message.IntegerResponseMessage", ErrorLevel) + 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 %} } @@ -4981,13 +4992,19 @@ {% block AHKKeyWait %} keyname := args[1] - if (args.Length = 2) { + options := args[2] + + if (options = "") { ret := KeyWait(keyname) } else { - options := args[2] ret := KeyWait(keyname, options) } - return FormatResponse("ahk.message.IntegerResponseMessage", ret) + + if (ret = 0) { + return FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else { + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + } {% endblock AHKKeyWait %} } diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 926c830a..38ec383f 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -370,6 +370,9 @@ def get_coord_mode(self, target: CoordModeTargets) -> str: return resp def set_send_mode(self, mode: SendMode) -> None: + """ + Analog for `SendMode `_ + """ args = [str(mode)] self._transport.function_call('AHKSetSendMode', args) return None @@ -1114,13 +1117,13 @@ def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, Fut # fmt: off @overload - def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... + def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> bool: ... @overload - def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> int: ... + def key_wait(self, key_name: str, *, blocking: Literal[True], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> bool: ... @overload - def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> FutureResult[int]: ... + def key_wait(self, key_name: str, *, blocking: Literal[False], timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> FutureResult[bool]: ... @overload - def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[int, FutureResult[int]]: ... + def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... # fmt: on def key_wait( self, @@ -1130,7 +1133,7 @@ def key_wait( logical_state: bool = False, released: bool = False, blocking: bool = True, - ) -> Union[int, FutureResult[int]]: + ) -> Union[bool, FutureResult[bool]]: """ Analog for `KeyWait `_ """ @@ -1139,11 +1142,10 @@ def key_wait( options += 'D' if logical_state: options += 'L' - if timeout: + if timeout is not None: + assert timeout >= 0, 'Timeout value must be non-negative' options += f'T{timeout}' - args = [key_name] - if options: - args.append(options) + args = [key_name, options] resp = self._transport.function_call('AHKKeyWait', args, blocking=blocking) return resp diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index f697964b..97a7f27d 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -397,7 +397,7 @@ def function_call(self, function_name: Literal['AHKClick'], args: Optional[List[ @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[int, FutureResult[int]]: ... + 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 diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 8678f8f8..c371233e 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2007,13 +2007,19 @@ AHKKeyWait(args*) { {% block AHKKeyWait %} keyname := args[1] - if (args.Length = 2) { + options := args[2] + + if (options = "") { ret := KeyWait(keyname) } else { - options := args[2] ret := KeyWait(keyname, options) } - return FormatResponse("ahk.message.IntegerResponseMessage", ret) + + if (ret = 0) { + return FormatResponse("ahk.message.BooleanResponseMessage", 0) + } else { + return FormatResponse("ahk.message.BooleanResponseMessage", 1) + } {% endblock AHKKeyWait %} } diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 558677e6..d194fc9e 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1888,13 +1888,24 @@ AHKKeyWait(args*) { {% block AHKKeyWait %} keyname := args[1] - if (args.Length() = 2) { + options := args[2] + + if (options = "") { KeyWait,% keyname } else { - options := args[2] KeyWait,% keyname,% options } - return FormatResponse("ahk.message.IntegerResponseMessage", ErrorLevel) + 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 %} } diff --git a/docs/README.md b/docs/README.md index 82f312a6..1c8fddf1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -153,8 +153,10 @@ 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 -ahk.key_wait('a', timeout=3) # Wait up to 3 seconds for the "a" key to be pressed. NOTE: This throws - # a TimeoutError if the key isn't pressed within the timeout window +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 diff --git a/tests/_async/test_keys.py b/tests/_async/test_keys.py index a5116c59..5615794c 100644 --- a/tests/_async/test_keys.py +++ b/tests/_async/test_keys.py @@ -5,6 +5,8 @@ import time import unittest.mock +import pytest + from ahk import AsyncAHK from ahk import AsyncWindow @@ -78,6 +80,20 @@ async def test_hotstring_callback(self): 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: diff --git a/tests/_sync/test_keys.py b/tests/_sync/test_keys.py index 9f682393..e690cdee 100644 --- a/tests/_sync/test_keys.py +++ b/tests/_sync/test_keys.py @@ -5,6 +5,8 @@ import time import unittest.mock +import pytest + from ahk import AHK from ahk import Window @@ -77,6 +79,20 @@ def test_hotstring_callback(self): 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: From 2c67f3ebf6c62d9aded7d7824dadb3fbb601e625 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 27 Feb 2024 20:42:24 -0800 Subject: [PATCH 490/588] update methods docs --- docs/api/methods.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/methods.rst b/docs/api/methods.rst index b13177b7..a733ebe2 100644 --- a/docs/api/methods.rst +++ b/docs/api/methods.rst @@ -82,8 +82,8 @@ Mouse and Keyboard - Implemented - :py:meth:`~ahk._sync.engine.AHK.set_send_level` * - `SendMode `_ - - Not Implemented - - + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.set_send_mode` * - `SetCapsLockState `_ - Implemented - :py:meth:`~ahk._sync.engine.AHK.set_capslock_state` From baf74aea4e752811e0b8c82240e692c97e56d698 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 28 Feb 2024 16:21:27 -0800 Subject: [PATCH 491/588] fix overload formatting --- ahk/_async/engine.py | 57 +++++--------------------------------------- ahk/_sync/engine.py | 57 +++++--------------------------------------- 2 files changed, 12 insertions(+), 102 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 15fbad14..0713787b 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -2923,61 +2923,16 @@ async def image_search( 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: ... + 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]: ... + 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: ... + 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]]: ... + 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, diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 38ec383f..af5f0fc9 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -2911,61 +2911,16 @@ def image_search( 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: ... + 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]: ... + 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: ... + 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]]: ... + 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, From 3f11d3a40c28224b6123993db7ad612b1668c98c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 9 Mar 2024 12:27:56 -0800 Subject: [PATCH 492/588] update documentation --- docs/README.md | 100 +++++++++++++++++++++++++++++++++++-------- docs/api/methods.rst | 2 +- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/docs/README.md b/docs/README.md index 1c8fddf1..211c946d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -194,29 +194,44 @@ from ahk import AHK ahk = AHK() ahk.run_script('Run Notepad') # Open notepad -win = ahk.find_window(title='Untitled - Notepad') # Find the opened window +win = ahk.find_window(title='Untitled - Notepad') # Find the opened window; returns a `Window` object -win.send('hello') # Send keys directly to the window (does not need focus!) +# 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 windwow -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.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(): +for window in ahk.list_windows(): # list all (non-hidden) windows -- ``detect_hidden_windows=True`` to include hidden print(window.title) # Some more attributes @@ -232,8 +247,20 @@ 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 @@ -261,6 +288,10 @@ 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 @@ -277,6 +308,29 @@ 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 @@ -340,6 +394,7 @@ ahk.set_send_level(5) # Change send https://www.autohotkey.com/docs/v1/lib/Send 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 @@ -540,7 +595,8 @@ ahk.run_script(script_path) 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 is fully supported. AutoHotkey v2 support is available, but is considered to be in beta status. +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. @@ -596,16 +652,22 @@ the `version` keyword is omitted, the version is determined automatically from t 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 between the two versions. - -Some of the differences that you will experience when using AutoHotkey v2 include: +[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) + + +## 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 diff --git a/docs/api/methods.rst b/docs/api/methods.rst index a733ebe2..0f89ae07 100644 --- a/docs/api/methods.rst +++ b/docs/api/methods.rst @@ -534,7 +534,7 @@ For example, to use the :py:class:`~ahk.directives.NoTrayIcon` directive * - `#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 `_ From 0a5778441e0dba943a0c743f65e27220c7e83322 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 9 Mar 2024 12:29:27 -0800 Subject: [PATCH 493/588] add issue templates --- .github/ISSUE_TEMPLATE/00_bug.yaml | 60 ++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/01_feature.yaml | 33 ++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 +++ 3 files changed, 98 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/00_bug.yaml create mode 100644 .github/ISSUE_TEMPLATE/01_feature.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yml 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 From a9f06fbb65199b175233add9dbb795ff6416acc3 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 9 Mar 2024 12:40:01 -0800 Subject: [PATCH 494/588] GH-270 add ability to hide tray icon at runtime --- ahk/_async/engine.py | 8 ++++++++ ahk/_async/transport.py | 3 +++ ahk/_constants.py | 10 ++++++++++ ahk/_sync/engine.py | 9 +++++++++ ahk/_sync/transport.py | 3 +++ ahk/templates/daemon-v2.ahk | 5 +++++ ahk/templates/daemon.ahk | 5 +++++ 7 files changed, 43 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 0713787b..d36d7300 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1594,6 +1594,14 @@ async def menu_tray_icon_show(self) -> None: 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: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 6aa7ce03..97126ceb 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -106,6 +106,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKKeyWait', 'AHKMenuTrayIcon', 'AHKMenuTrayShow', + 'AHKMenuTrayHide', 'AHKMenuTrayTip', 'AHKMsgBox', 'AHKMouseClickDrag', @@ -606,6 +607,8 @@ async def function_call(self, function_name: Literal['AHKMenuTrayIcon'], args: O @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]]: ... diff --git a/ahk/_constants.py b/ahk/_constants.py index c4b2c903..a8958ae0 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2623,6 +2623,11 @@ return FormatNoValueResponse() } +AHKMenuTrayHide(args*) { + Menu, Tray, NoIcon + return FormatNoValueResponse() +} + AHKMenuTrayIcon(args*) { filename := args[1] icon_number := args[2] @@ -5684,6 +5689,11 @@ return FormatNoValueResponse() } +AHKMenuTrayHide(args*) { + A_IconHidden := 1 + return FormatNoValueResponse() +} + AHKMenuTrayIcon(args*) { filename := args[1] icon_number := args[2] diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index af5f0fc9..cb43e1bd 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1582,6 +1582,15 @@ def menu_tray_icon_show(self) -> None: 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: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 97a7f27d..9cb2f669 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -98,6 +98,7 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: 'AHKKeyWait', 'AHKMenuTrayIcon', 'AHKMenuTrayShow', + 'AHKMenuTrayHide', 'AHKMenuTrayTip', 'AHKMsgBox', 'AHKMouseClickDrag', @@ -579,6 +580,8 @@ def function_call(self, function_name: Literal['AHKMenuTrayIcon'], args: Optiona @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]]: ... diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index c371233e..d32fda1d 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2699,6 +2699,11 @@ AHKMenuTrayShow(args*) { return FormatNoValueResponse() } +AHKMenuTrayHide(args*) { + A_IconHidden := 1 + return FormatNoValueResponse() +} + AHKMenuTrayIcon(args*) { filename := args[1] icon_number := args[2] diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index d194fc9e..80f4ac74 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2620,6 +2620,11 @@ AHKMenuTrayShow(args*) { return FormatNoValueResponse() } +AHKMenuTrayHide(args*) { + Menu, Tray, NoIcon + return FormatNoValueResponse() +} + AHKMenuTrayIcon(args*) { filename := args[1] icon_number := args[2] From fe278200116d94827c4e555d2d83156099db78dc Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 9 Mar 2024 12:43:21 -0800 Subject: [PATCH 495/588] update readme --- docs/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 211c946d..8e60ad1d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -443,7 +443,10 @@ 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') -# Show the tray icon that was previously hidden by ``NoTrayIcon`` +# 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() ``` From 35cf522804aa4db85cc6301e42a8ff481c294a40 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 9 Mar 2024 14:43:54 -0800 Subject: [PATCH 496/588] 1.5.2 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 7d07cb3c..1f30c73c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.5.1 +version = 1.5.2 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 904df98b33e1a320279a977665440a2b2901f255 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 23:53:39 +0000 Subject: [PATCH 497/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.8.0 → v1.9.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.8.0...v1.9.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10ece8eb..bc67a744 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.8.0' + rev: 'v1.9.0' hooks: - id: mypy args: From a7ebb13fe78c3cbd7e6f14cedd60f96fe1444189 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:22:46 +0000 Subject: [PATCH 498/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 24.2.0 → 24.3.0](https://github.com/psf/black/compare/24.2.0...24.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bc67a744..f198f6be 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '24.2.0' + rev: '24.3.0' hooks: - id: black args: From 82330d7110c77fb17e016782bc97d4a723dc7281 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Mar 2024 15:53:52 -0700 Subject: [PATCH 499/588] fix incorrect return type annotation for `list_windows` when `blocking=False` --- ahk/_async/engine.py | 2 +- ahk/_sync/engine.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index d36d7300..ab0c0687 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -702,7 +702,7 @@ def _format_win_args( @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]) -> Union[List[AsyncWindow], AsyncFutureResult[List[AsyncWindow]]]: ... + 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 diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index cb43e1bd..9e2df2d2 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -697,7 +697,7 @@ def _format_win_args( @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]) -> Union[List[Window], FutureResult[List[Window]]]: ... + 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 @@ -1590,7 +1590,6 @@ def menu_tray_icon_hide(self) -> None: self._transport.function_call('AHKMenuTrayHide') return None - # fmt: off @overload def sound_beep(self, frequency: int = 523, duration: int = 150) -> None: ... From 185258fc93b1a952da1760ada8bbe77128d59acd Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Mar 2024 15:54:54 -0700 Subject: [PATCH 500/588] minor changes to extensions doc --- docs/extending.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/extending.rst b/docs/extending.rst index 3b3f8980..43423ff1 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -97,7 +97,7 @@ containing the AutoHotkey code we just wrote above. from ahk.extensions import Extension from typing import Literal - script_text = r'''\ + script_text = r''' ; a string of your AHK script ; Omitted here for brevity -- copy/paste from the previous code block ''' @@ -322,4 +322,4 @@ 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'`` is generally the easiest method for enabling all available extensions +- Although extensions can be declared explicitly, using ``extensions='auto'`` can be used for convenience/portability. From 0ef89ef31823206a6bb9dcd7cc35a3b988aafc18 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 20 Mar 2024 17:07:33 -0700 Subject: [PATCH 501/588] v1.5.3 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1f30c73c..dcd5c285 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.5.2 +version = 1.5.3 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From fc95d0775fc139940b78e68dcc0206ae990dc208 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 Apr 2024 10:09:44 -0700 Subject: [PATCH 502/588] fix key_state for AHKv2 when no mode argument is given --- ahk/_async/engine.py | 2 ++ ahk/_sync/engine.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index ab0c0687..01cff8d6 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1101,6 +1101,8 @@ async def key_state( 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 diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 9e2df2d2..bdfeb632 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1089,6 +1089,8 @@ def key_state( 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 From 2e70f6f7aa9539b1f81cec450749149de9abf3a1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 Apr 2024 10:25:58 -0700 Subject: [PATCH 503/588] v1.5.4 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index dcd5c285..8664b513 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.5.3 +version = 1.5.4 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 3ea660abe7e73921751539bb1682b94d9ba01529 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 Apr 2024 12:35:22 -0700 Subject: [PATCH 504/588] fix warning message to use correct argument name --- ahk/_async/engine.py | 2 +- ahk/_sync/engine.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 01cff8d6..a5a6872e 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -878,7 +878,7 @@ async def find_windows( 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 match_mode=3 instead', stacklevel=2) + warnings.warn('exact parameter is deprecated. Use title_match_mode instead', stacklevel=2) if exact: title_match_mode = (3, 'Fast') else: diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index bdfeb632..de874bb8 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -866,7 +866,7 @@ def find_windows( 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 match_mode=3 instead', stacklevel=2) + warnings.warn('exact parameter is deprecated. Use title_match_mode instead', stacklevel=2) if exact: title_match_mode = (3, 'Fast') else: From 8481a2159d807cce91dee77a1b326fea68167a2f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 Apr 2024 12:35:59 -0700 Subject: [PATCH 505/588] document title match mode differences in AHK v2 --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 8e60ad1d..54296d7b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -664,7 +664,7 @@ Some of the notable differences that you may experience when using AutoHotkey 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) From 680a9222ba029f8f3dfb213b357c3d3c2c03556b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 Apr 2024 12:39:28 -0700 Subject: [PATCH 506/588] GH-280 v2 workaround for when width and height are not specified --- ahk/_constants.py | 9 +++++++++ ahk/templates/daemon-v2.ahk | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/ahk/_constants.py b/ahk/_constants.py index a8958ae0..6c78db61 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -5494,6 +5494,15 @@ 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) diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index d32fda1d..b9f03d76 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2504,6 +2504,15 @@ AHKWinMove(args*) { 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) From 9bbbc144fefe53035a55e6940deb5862f1d97c8a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 Apr 2024 13:22:52 -0700 Subject: [PATCH 507/588] GH-281 add use_hwnd parameter for Control methods --- ahk/_async/window.py | 67 +++++++++++++++++++++++++++++--------------- ahk/_sync/window.py | 67 +++++++++++++++++++++++++++++--------------- 2 files changed, 88 insertions(+), 46 deletions(-) diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 656e0d9b..ffeaa6ca 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -10,6 +10,7 @@ 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 @@ -20,6 +21,11 @@ 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 @@ -652,22 +658,34 @@ async def from_mouse_position(cls, engine: AsyncAHK[Any]) -> Optional[AsyncWindo 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 = '') -> None: ... + 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 = '', blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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 = '', blocking: Literal[True]) -> None: ... + 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 = '', blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + 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, @@ -675,65 +693,68 @@ async def click( 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, - control=self.control_class, click_count=click_count, options=options, - title=f'ahk_id {self.window._ahk_id}', 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) -> None: ... + async def send(self, keys: str, *, use_hwnd: Optional[bool] = None) -> None: ... @overload - async def send(self, keys: str, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + async def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[None]: ... @overload - async def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + async def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: Literal[True]) -> None: ... @overload - async def send(self, keys: str, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + 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, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: + 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, - control=self.control_class, - title=f'ahk_id {self.window._ahk_id}', blocking=blocking, detect_hidden_windows=True, title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), ) - async def get_text(self, blocking: bool = True) -> Union[str, AsyncFutureResult[str]]: + async def get_text( + self, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[str, AsyncFutureResult[str]]: return await self._engine.control_get_text( - control=self.control_class, - title=f'ahk_id {self.window._ahk_id}', 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) -> Position: ... + async def get_position(self, *, use_hwnd: Optional[bool] = None) -> Position: ... @overload - async def get_position(self, blocking: Literal[False]) -> AsyncFutureResult[Position]: ... + async def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: Literal[False]) -> AsyncFutureResult[Position]: ... @overload - async def get_position(self, blocking: Literal[True]) -> Position: ... + async def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: Literal[True]) -> Position: ... @overload - async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutureResult[Position]]: ... + async def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: bool = True) -> Union[Position, AsyncFutureResult[Position]]: ... # fmt: on - async def get_position(self, blocking: bool = True) -> Union[Position, AsyncFutureResult[Position]]: + async def get_position( + self, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[Position, AsyncFutureResult[Position]]: return await self._engine.control_get_position( - control=self.control_class, - title=f'ahk_id {self.window._ahk_id}', blocking=blocking, detect_hidden_windows=True, title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), ) def __repr__(self) -> str: diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 65769036..f7dd9b2c 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -10,6 +10,7 @@ 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 @@ -20,6 +21,11 @@ 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 @@ -631,22 +637,34 @@ 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 = '') -> None: ... + 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 = '', blocking: Literal[False]) -> FutureResult[None]: ... + 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 = '', blocking: Literal[True]) -> None: ... + 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 = '', blocking: bool = True) -> Union[None, FutureResult[None]]: ... + 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, @@ -654,65 +672,68 @@ def click( 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, - control=self.control_class, click_count=click_count, options=options, - title=f'ahk_id {self.window._ahk_id}', 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) -> None: ... + def send(self, keys: str, *, use_hwnd: Optional[bool] = None) -> None: ... @overload - def send(self, keys: str, *, blocking: Literal[False]) -> FutureResult[None]: ... + def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[None]: ... @overload - def send(self, keys: str, *, blocking: Literal[True]) -> None: ... + def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: Literal[True]) -> None: ... @overload - def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + def send(self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: bool = True) -> Union[None, FutureResult[None]]: ... # fmt: on - def send(self, keys: str, *, blocking: bool = True) -> Union[None, FutureResult[None]]: + def send( + self, keys: str, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[None, FutureResult[None]]: return self._engine.control_send( keys=keys, - control=self.control_class, - title=f'ahk_id {self.window._ahk_id}', blocking=blocking, detect_hidden_windows=True, title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), ) - def get_text(self, blocking: bool = True) -> Union[str, FutureResult[str]]: + def get_text( + self, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[str, FutureResult[str]]: return self._engine.control_get_text( - control=self.control_class, - title=f'ahk_id {self.window._ahk_id}', blocking=blocking, detect_hidden_windows=True, title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), ) # fmt: off @overload - def get_position(self) -> Position: ... + def get_position(self, *, use_hwnd: Optional[bool] = None) -> Position: ... @overload - def get_position(self, blocking: Literal[False]) -> FutureResult[Position]: ... + def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: Literal[False]) -> FutureResult[Position]: ... @overload - def get_position(self, blocking: Literal[True]) -> Position: ... + def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: Literal[True]) -> Position: ... @overload - def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Position]]: ... + def get_position(self, *, use_hwnd: Optional[bool] = None, blocking: bool = True) -> Union[Position, FutureResult[Position]]: ... # fmt: on - def get_position(self, blocking: bool = True) -> Union[Position, FutureResult[Position]]: + def get_position( + self, *, use_hwnd: Optional[bool] = None, blocking: bool = True + ) -> Union[Position, FutureResult[Position]]: return self._engine.control_get_position( - control=self.control_class, - title=f'ahk_id {self.window._ahk_id}', blocking=blocking, detect_hidden_windows=True, title_match_mode=(1, 'Fast'), + **self._get_target_params(use_hwnd), ) def __repr__(self) -> str: From 737a3add987b5ad6bb961d5d52b749f89c6b9bbe Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 Apr 2024 13:44:23 -0700 Subject: [PATCH 508/588] add tests for detect hidden windows --- tests/_async/test_window.py | 12 ++++++++++++ tests/_sync/test_window.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index f3127bf8..7479b481 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -84,6 +84,18 @@ async def test_set_detect_hidden_windows(self): all_windows = await self.ahk.list_windows() assert len(all_windows) > len(non_hidden) + async def test_detect_hidden_windows_false_works(self): + await ahk.set_detect_hidden_windows(True) + all_windows = await ahk.list_windows() + await 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 ahk.list_windows() + all_windows = await 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) diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 723ec47a..2de56d53 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -84,6 +84,18 @@ def test_set_detect_hidden_windows(self): all_windows = self.ahk.list_windows() assert len(all_windows) > len(non_hidden) + def test_detect_hidden_windows_false_works(self): + ahk.set_detect_hidden_windows(True) + all_windows = ahk.list_windows() + 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 = ahk.list_windows() + all_windows = 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) From 457862f7edc8388420a1de4c0f375d4f2fec343b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 Apr 2024 14:10:47 -0700 Subject: [PATCH 509/588] GH-284 fix detect hidden windows in AHK v2 --- ahk/_async/engine.py | 52 ++++++++++++++++++------------------- ahk/_sync/engine.py | 52 ++++++++++++++++++------------------- tests/_async/test_window.py | 10 +++---- tests/_sync/test_window.py | 10 +++---- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index a5a6872e..89186339 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -417,9 +417,9 @@ async def control_click( 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('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -476,9 +476,9 @@ async def control_get_text( args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -534,9 +534,9 @@ async def control_get_position( args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -594,9 +594,9 @@ async def control_send( args = [control, keys, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -651,9 +651,9 @@ async def set_detect_hidden_windows(self, value: bool) -> None: raise TypeError(f'detect hidden windows must be a boolean, got object of type {type(value)}') args = [] if value is True: - args.append('On') + args.append('1') else: - args.append('Off') + args.append('0') await self._transport.function_call('AHKSetDetectHiddenWindows', args=args) return None @@ -669,9 +669,9 @@ def _format_win_args( args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2228,9 +2228,9 @@ async def win_set_title( args = [new_title, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2286,9 +2286,9 @@ async def win_set_always_on_top( args = [str(toggle), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2521,9 +2521,9 @@ async def win_set_style( args = [style, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2579,9 +2579,9 @@ async def win_set_ex_style( args = [style, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2637,9 +2637,9 @@ async def win_set_region( args = [options, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2695,9 +2695,9 @@ async def win_set_transparent( args = [str(transparency), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2753,9 +2753,9 @@ async def win_set_trans_color( args = [str(color), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index de874bb8..afbba1ed 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -412,9 +412,9 @@ def control_click( 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('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -471,9 +471,9 @@ def control_get_text( args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -529,9 +529,9 @@ def control_get_position( args = [control, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -589,9 +589,9 @@ def control_send( args = [control, keys, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -646,9 +646,9 @@ def set_detect_hidden_windows(self, value: bool) -> None: raise TypeError(f'detect hidden windows must be a boolean, got object of type {type(value)}') args = [] if value is True: - args.append('On') + args.append('1') else: - args.append('Off') + args.append('0') self._transport.function_call('AHKSetDetectHiddenWindows', args=args) return None @@ -664,9 +664,9 @@ def _format_win_args( args = [title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2216,9 +2216,9 @@ def win_set_title( args = [new_title, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2274,9 +2274,9 @@ def win_set_always_on_top( args = [str(toggle), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2509,9 +2509,9 @@ def win_set_style( args = [style, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2567,9 +2567,9 @@ def win_set_ex_style( args = [style, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2625,9 +2625,9 @@ def win_set_region( args = [options, title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2683,9 +2683,9 @@ def win_set_transparent( args = [str(transparency), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' @@ -2741,9 +2741,9 @@ def win_set_trans_color( args = [str(color), title, text, exclude_title, exclude_text] if detect_hidden_windows is not None: if detect_hidden_windows is True: - args.append('On') + args.append('1') elif detect_hidden_windows is False: - args.append('Off') + args.append('0') else: raise TypeError( f'Invalid value for parameter detect_hidden_windows. Expected boolean or None, got {detect_hidden_windows!r}' diff --git a/tests/_async/test_window.py b/tests/_async/test_window.py index 7479b481..aedd298a 100644 --- a/tests/_async/test_window.py +++ b/tests/_async/test_window.py @@ -85,15 +85,15 @@ async def test_set_detect_hidden_windows(self): assert len(all_windows) > len(non_hidden) async def test_detect_hidden_windows_false_works(self): - await ahk.set_detect_hidden_windows(True) - all_windows = await ahk.list_windows() - await ahk.set_detect_hidden_windows(False) + 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 ahk.list_windows() - all_windows = await ahk.list_windows(detect_hidden_windows=False) + 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): diff --git a/tests/_sync/test_window.py b/tests/_sync/test_window.py index 2de56d53..0f74ad2a 100644 --- a/tests/_sync/test_window.py +++ b/tests/_sync/test_window.py @@ -85,15 +85,15 @@ def test_set_detect_hidden_windows(self): assert len(all_windows) > len(non_hidden) def test_detect_hidden_windows_false_works(self): - ahk.set_detect_hidden_windows(True) - all_windows = ahk.list_windows() - ahk.set_detect_hidden_windows(False) + 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 = ahk.list_windows() - all_windows = ahk.list_windows(detect_hidden_windows=False) + 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): From f1b19b78e7262e530da13bbc922e9ee6fd31bde6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 3 Apr 2024 15:15:09 -0700 Subject: [PATCH 510/588] v1.6.0 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 8664b513..822d8556 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.5.4 +version = 1.6.0 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From f9ef0aa032f8f5937420cea3ee1dab7b74b8bb7f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 5 Apr 2024 00:11:57 -0700 Subject: [PATCH 511/588] refactor exceptions to exceptions module --- ahk/_async/transport.py | 4 +--- ahk/_async/window.py | 4 +--- ahk/_sync/transport.py | 2 +- ahk/_sync/window.py | 2 +- ahk/_utils.py | 6 ++---- ahk/exceptions.py | 14 ++++++++++++++ ahk/message.py | 6 ++---- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 97126ceb..fd5126de 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -50,6 +50,7 @@ ) from ahk._utils import _version_detection_script from ahk.directives import Directive +from ahk.exceptions import AHKProtocolError from concurrent.futures import Future, ThreadPoolExecutor @@ -58,9 +59,6 @@ T_SyncFuture = TypeVar('T_SyncFuture') -class AHKProtocolError(Exception): ... - - class AsyncFutureResult(Generic[T_AsyncFuture]): # unasync: remove def __init__(self, task: asyncio.Task[T_AsyncFuture]): self._task: asyncio.Task[T_AsyncFuture] = task diff --git a/ahk/_async/window.py b/ahk/_async/window.py index ffeaa6ca..d08d7891 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -14,6 +14,7 @@ from typing import TypeVar from typing import Union +from ahk.exceptions import WindowNotFoundException from ahk.message import Position if sys.version_info < (3, 10): @@ -31,9 +32,6 @@ from .transport import AsyncFutureResult -class WindowNotFoundException(Exception): ... - - AsyncPropertyReturnStr: TypeAlias = Coroutine[None, None, str] # unasync: remove SyncPropertyReturnStr: TypeAlias = str diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 9cb2f669..5761309a 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -50,6 +50,7 @@ ) from ahk._utils import _version_detection_script from ahk.directives import Directive +from ahk.exceptions import AHKProtocolError from concurrent.futures import Future, ThreadPoolExecutor @@ -57,7 +58,6 @@ T_SyncFuture = TypeVar('T_SyncFuture') -class AHKProtocolError(Exception): ... diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index f7dd9b2c..84070990 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -14,6 +14,7 @@ from typing import TypeVar from typing import Union +from ahk.exceptions import WindowNotFoundException from ahk.message import Position if sys.version_info < (3, 10): @@ -31,7 +32,6 @@ from .transport import FutureResult -class WindowNotFoundException(Exception): ... SyncPropertyReturnStr: TypeAlias = str diff --git a/ahk/_utils.py b/ahk/_utils.py index 15c281d1..cbb7a98a 100644 --- a/ahk/_utils.py +++ b/ahk/_utils.py @@ -7,6 +7,8 @@ from typing import Literal from typing import Optional +from ahk.exceptions import AhkExecutableNotFoundError + HOTKEY_ESCAPE_SEQUENCE_MAP = { '\n': '`n', '\t': '`t', @@ -84,10 +86,6 @@ class MsgBoxOtherOptions(enum.IntEnum): DEFAULT_EXECUTABLE_PATH_V2 = r'C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe' -class AhkExecutableNotFoundError(EnvironmentError): - pass - - def _resolve_executable_path(executable_path: str = '', version: Optional[Literal['v1', 'v2']] = None) -> str: if not executable_path: executable_path = ( diff --git a/ahk/exceptions.py b/ahk/exceptions.py index 337c3665..0f850221 100644 --- a/ahk/exceptions.py +++ b/ahk/exceptions.py @@ -1,3 +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/message.py b/ahk/message.py index c9bf1273..62ba30d2 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -27,6 +27,8 @@ from typing import TypeVar from typing import Union +from ahk.exceptions import AHKExecutionException + class OutOfMessageTypes(Exception): ... @@ -209,10 +211,6 @@ def unpack(self) -> None: return None -class AHKExecutionException(Exception): - pass - - class ExceptionResponseMessage(ResponseMessage): _exception_type: Type[Exception] = AHKExecutionException From c7c1747ce5c40cf2ab1a061b4fc4c5d4be22f87a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 5 Apr 2024 00:15:24 -0700 Subject: [PATCH 512/588] v1.6.1 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 822d8556..bdb13455 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.6.0 +version = 1.6.1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 1f58b5d4a27bcaa59160ca7d4fc81edc4346a212 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 21:42:58 +0000 Subject: [PATCH 513/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f198f6be..e5de94ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: files: ^(ahk/daemon\.ahk|ahk/_constants\.py) - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: mixed-line-ending args: ["-f", "lf"] From 368575d6a54f5f2fe58941325030a3b910b84b5f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 21:33:00 +0000 Subject: [PATCH 514/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 24.3.0 → 24.4.0](https://github.com/psf/black/compare/24.3.0...24.4.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e5de94ce..a766f3bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '24.3.0' + rev: '24.4.0' hooks: - id: black args: From 61d266385ab372b3106d49e382015a1b1d1cdcef Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 19 Apr 2024 10:38:12 -0700 Subject: [PATCH 515/588] GH-289 fix message box in AHKv2 --- ahk/_async/engine.py | 3 +++ ahk/_sync/engine.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 89186339..9232fa46 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3756,6 +3756,9 @@ async def msg_box( 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 diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index afbba1ed..fa98fd6b 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3744,6 +3744,9 @@ def msg_box( 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 From 02d21dd350386fa35589423a39affcb9fe2d7e3f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 19 Apr 2024 11:06:08 -0700 Subject: [PATCH 516/588] 1.6.2 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index bdb13455..9b0449c9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.6.1 +version = 1.6.2 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From e87c12dbe6c4459fb07f21630b93bbc2f7f4b3d5 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 19 Apr 2024 11:58:28 -0700 Subject: [PATCH 517/588] fix clip_wait in AHKv2 --- ahk/_async/engine.py | 2 ++ ahk/_sync/engine.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 9232fa46..afb7e7fe 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3630,6 +3630,8 @@ async def clip_wait( 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( diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index fa98fd6b..539157ea 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3618,6 +3618,8 @@ def clip_wait( 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( From 64838efae230e11a82b61c11357d4b2a9f4c6845 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 19 Apr 2024 15:38:29 -0700 Subject: [PATCH 518/588] 1.6.3 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9b0449c9..d1b126d3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.6.2 +version = 1.6.3 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 913955264fa0ad8919eda3c3fe5c051cc5422782 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 20 Apr 2024 20:39:33 -0700 Subject: [PATCH 519/588] typing improvements --- ahk/__init__.py | 28 ++++++ ahk/_async/engine.py | 96 ++++++--------------- ahk/_async/transport.py | 159 ++++++---------------------------- ahk/_async/window.py | 2 +- ahk/_sync/engine.py | 85 ++++--------------- ahk/_sync/transport.py | 161 ++++++----------------------------- ahk/_sync/window.py | 4 +- ahk/_types.py | 183 ++++++++++++++++++++++++++++++++++++++++ ahk/message.py | 9 +- 9 files changed, 316 insertions(+), 411 deletions(-) create mode 100644 ahk/_types.py diff --git a/ahk/__init__.py b/ahk/__init__.py index 8ae34b52..eda73d85 100644 --- a/ahk/__init__.py +++ b/ahk/__init__.py @@ -4,9 +4,21 @@ 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 @@ -23,6 +35,22 @@ '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 diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index afb7e7fe..00e20a61 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -52,9 +52,16 @@ from .window import AsyncControl from .window import AsyncWindow -# from .window import AsyncGui -from ahk.message import Position - +from ahk._types import ( + Position, + CoordModeTargets, + CoordModeRelativeTo, + TitleMatchMode, + _BUTTONS, + MouseButton, + SendMode, + Coordinates, +) async_sleep = asyncio.sleep # unasync: remove sleep = time.sleep @@ -62,57 +69,8 @@ AsyncFilterFunc: TypeAlias = Callable[[AsyncWindow], Awaitable[bool]] # unasync: remove SyncFilterFunc: TypeAlias = Callable[[AsyncWindow], bool] -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', ''] - -AsyncPropertyReturnTupleIntInt: TypeAlias = Coroutine[None, None, Tuple[int, int]] # unasync: remove -SyncPropertyReturnTupleIntInt: TypeAlias = Tuple[int, int] +AsyncPropertyReturnTupleIntInt: TypeAlias = Coroutine[None, None, Coordinates] # unasync: remove +SyncPropertyReturnTupleIntInt: TypeAlias = Coordinates AsyncPropertyReturnOptionalAsyncWindow: TypeAlias = Coroutine[None, None, Optional[AsyncWindow]] # unasync: remove SyncPropertyReturnOptionalAsyncWindow: TypeAlias = Optional[AsyncWindow] @@ -737,17 +695,17 @@ async def list_windows( # fmt: off @overload - async def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[True]) -> Tuple[int, int]: ... + 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[Tuple[int, int]]: ... + 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) -> Tuple[int, int]: ... + 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[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... + 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[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: + ) -> Union[Coordinates, AsyncFutureResult[Coordinates]]: """ Analog for `MouseGetPos `_ """ @@ -2870,13 +2828,13 @@ async def click( # 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[Tuple[int, int]]: ... + 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[Tuple[int, int]]]: ... + 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[Tuple[int, int]]: ... + 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[Tuple[int, int], None, AsyncFutureResult[Optional[Tuple[int, int]]]]: ... + 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, @@ -2891,7 +2849,7 @@ async def image_search( transparent: Optional[str] = None, icon: Optional[int] = None, blocking: bool = True, - ) -> Union[Tuple[int, int], None, AsyncFutureResult[Optional[Tuple[int, int]]]]: + ) -> Union[Coordinates, None, AsyncFutureResult[Optional[Coordinates]]]: """ Analog for `ImageSearch `_ """ @@ -3026,13 +2984,13 @@ async def pixel_get_color( # 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[Tuple[int, int]]: ... + 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[Tuple[int, int]]: ... + 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[Tuple[int, int]]]: ... + 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[Tuple[int, int]], AsyncFutureResult[Optional[Tuple[int, int]]]]: ... + 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, @@ -3045,7 +3003,7 @@ async def pixel_search( fast: bool = True, rgb: bool = True, blocking: bool = True, - ) -> Union[Optional[Tuple[int, int]], AsyncFutureResult[Optional[Tuple[int, int]]]]: + ) -> Union[Optional[Coordinates], AsyncFutureResult[Optional[Coordinates]]]: """ Analog for `PixelSearch `_ """ diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index fd5126de..ceefe69c 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -11,6 +11,8 @@ 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 @@ -27,6 +29,26 @@ 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.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 @@ -36,24 +58,6 @@ else: from typing import TypeAlias, TypeGuard -import jinja2 - -from ahk.extensions import Extension, _resolve_includes -from ahk._hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring -from ahk.message import RequestMessage -from ahk.message import ResponseMessage -from ahk.message import Position -from ahk.message import _message_registry -from ahk._constants import ( - DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE, - DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE, -) -from ahk._utils import _version_detection_script -from ahk.directives import Directive -from ahk.exceptions import AHKProtocolError - -from concurrent.futures import Future, ThreadPoolExecutor - T_AsyncFuture = TypeVar('T_AsyncFuture') # unasync: remove T_SyncFuture = TypeVar('T_SyncFuture') @@ -80,115 +84,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: SyncIOProcess: TypeAlias = 'subprocess.Popen[bytes]' -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', - 'SetKeyDelay', - 'WinActivateBottom', - 'AHKWinGetClass', - 'AHKWinKill', - 'AHKWinMaximize', - 'AHKWinMinimize', - 'AHKWinRestore', -] - - @runtime_checkable class Killable(Protocol): def kill(self) -> None: ... @@ -204,7 +99,9 @@ def kill(proc: Killable) -> None: def async_assert_send_nonblocking_type_correct( obj: Any, ) -> TypeGuard[ - Future[Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]]] + Future[ + Union[None, Coordinates, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] + ] ]: return True @@ -407,13 +304,13 @@ async def run_script( @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[Tuple[int, int], None, AsyncFutureResult[Union[Tuple[int, int], None]]]: ... + 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[Tuple[int, int]], AsyncFutureResult[Optional[Tuple[int, int]]]]: ... + 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[Tuple[int, int], AsyncFutureResult[Tuple[int, int]]]: ... + 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 diff --git a/ahk/_async/window.py b/ahk/_async/window.py index d08d7891..455d67fb 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -14,8 +14,8 @@ from typing import TypeVar from typing import Union +from ahk._types import Position from ahk.exceptions import WindowNotFoundException -from ahk.message import Position if sys.version_info < (3, 10): from typing_extensions import TypeAlias diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 539157ea..5f7c2489 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -52,64 +52,13 @@ from .window import Control from .window import Window -# from .window import AsyncGui -from ahk.message import Position - +from ahk._types import Position, CoordModeTargets, CoordModeRelativeTo, TitleMatchMode, _BUTTONS, MouseButton, SendMode, Coordinates sleep = time.sleep SyncFilterFunc: TypeAlias = Callable[[Window], bool] -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', ''] - -SyncPropertyReturnTupleIntInt: TypeAlias = Tuple[int, int] +SyncPropertyReturnTupleIntInt: TypeAlias = Coordinates SyncPropertyReturnOptionalAsyncWindow: TypeAlias = Optional[Window] @@ -732,17 +681,17 @@ def list_windows( # fmt: off @overload - def get_mouse_position(self, coord_mode: Optional[CoordModeRelativeTo] = None, *, blocking: Literal[True]) -> Tuple[int, int]: ... + 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[Tuple[int, int]]: ... + 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) -> Tuple[int, int]: ... + 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[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... + 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[Tuple[int, int], FutureResult[Tuple[int, int]]]: + ) -> Union[Coordinates, FutureResult[Coordinates]]: """ Analog for `MouseGetPos `_ """ @@ -2858,13 +2807,13 @@ def click( # 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[Tuple[int, int]]: ... + 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[Tuple[int, int]]]: ... + 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[Tuple[int, int]]: ... + 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[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: ... + 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, @@ -2879,7 +2828,7 @@ def image_search( transparent: Optional[str] = None, icon: Optional[int] = None, blocking: bool = True, - ) -> Union[Tuple[int, int], None, FutureResult[Optional[Tuple[int, int]]]]: + ) -> Union[Coordinates, None, FutureResult[Optional[Coordinates]]]: """ Analog for `ImageSearch `_ """ @@ -3014,13 +2963,13 @@ def pixel_get_color( # 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[Tuple[int, int]]: ... + 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[Tuple[int, int]]: ... + 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[Tuple[int, int]]]: ... + 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[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: ... + 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, @@ -3033,7 +2982,7 @@ def pixel_search( fast: bool = True, rgb: bool = True, blocking: bool = True, - ) -> Union[Optional[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: + ) -> Union[Optional[Coordinates], FutureResult[Optional[Coordinates]]]: """ Analog for `PixelSearch `_ """ diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 5761309a..32d7b4a4 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -11,6 +11,8 @@ 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 @@ -27,6 +29,26 @@ 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.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 @@ -36,32 +58,12 @@ else: from typing import TypeAlias, TypeGuard -import jinja2 - -from ahk.extensions import Extension, _resolve_includes -from ahk._hotkey import ThreadedHotkeyTransport, Hotkey, Hotstring -from ahk.message import RequestMessage -from ahk.message import ResponseMessage -from ahk.message import Position -from ahk.message import _message_registry -from ahk._constants import ( - DAEMON_SCRIPT_TEMPLATE as _DAEMON_SCRIPT_TEMPLATE, - DAEMON_SCRIPT_V2_TEMPLATE as _DAEMON_SCRIPT_V2_TEMPLATE, -) -from ahk._utils import _version_detection_script -from ahk.directives import Directive -from ahk.exceptions import AHKProtocolError - -from concurrent.futures import Future, ThreadPoolExecutor - T_SyncFuture = TypeVar('T_SyncFuture') - - class FutureResult(Generic[T_SyncFuture]): def __init__(self, future: Future[T_SyncFuture]): self._fut: Future[T_SyncFuture] = future @@ -74,115 +76,6 @@ def result(self, timeout: Optional[float] = None) -> T_SyncFuture: SyncIOProcess: TypeAlias = 'subprocess.Popen[bytes]' -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', - 'SetKeyDelay', - 'WinActivateBottom', - 'AHKWinGetClass', - 'AHKWinKill', - 'AHKWinMaximize', - 'AHKWinMinimize', - 'AHKWinRestore', -] - - @runtime_checkable class Killable(Protocol): def kill(self) -> None: ... @@ -198,7 +91,9 @@ def kill(proc: Killable) -> None: def async_assert_send_nonblocking_type_correct( obj: Any, ) -> TypeGuard[ - Future[Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]] + Future[ + Union[None, Coordinates, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]] + ] ]: return True @@ -382,13 +277,13 @@ def run_script( @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[Tuple[int, int], None, FutureResult[Union[Tuple[int, int], None]]]: ... + 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[Tuple[int, int]], FutureResult[Optional[Tuple[int, int]]]]: ... + 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[Tuple[int, int], FutureResult[Tuple[int, int]]]: ... + 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 diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index 84070990..a7f47909 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -14,8 +14,8 @@ from typing import TypeVar from typing import Union +from ahk._types import Position from ahk.exceptions import WindowNotFoundException -from ahk.message import Position if sys.version_info < (3, 10): from typing_extensions import TypeAlias @@ -32,8 +32,6 @@ from .transport import FutureResult - - SyncPropertyReturnStr: TypeAlias = str SyncPropertyReturnInt: TypeAlias = int diff --git a/ahk/_types.py b/ahk/_types.py new file mode 100644 index 00000000..7ac54b2a --- /dev/null +++ b/ahk/_types.py @@ -0,0 +1,183 @@ +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', + 'SetKeyDelay', + 'WinActivateBottom', + 'AHKWinGetClass', + 'AHKWinKill', + 'AHKWinMaximize', + 'AHKWinMinimize', + 'AHKWinRestore', +] diff --git a/ahk/message.py b/ahk/message.py index 62ba30d2..c33ffd8e 100644 --- a/ahk/message.py +++ b/ahk/message.py @@ -7,7 +7,6 @@ import sys from abc import abstractmethod from base64 import b64encode -from collections import namedtuple from typing import Any from typing import cast from typing import Generator @@ -28,14 +27,12 @@ from typing import Union from ahk.exceptions import AHKExecutionException +from ahk._types import Position, Coordinates class OutOfMessageTypes(Exception): ... -Position = namedtuple('Position', ('x', 'y', 'width', 'height')) - - @runtime_checkable class BytesLineReadable(Protocol): def readline(self) -> bytes: ... @@ -157,12 +154,12 @@ def unpack(self) -> Tuple[Any, ...]: class CoordinateResponseMessage(ResponseMessage): - def unpack(self) -> Tuple[int, int]: + 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 x, y + return Coordinates(x, y) class IntegerResponseMessage(ResponseMessage): From c6a8006f739197088401e3447f339fde9d2e54e0 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 20 Apr 2024 20:58:52 -0700 Subject: [PATCH 520/588] 1.7.0 :package: --- setup.cfg | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index d1b126d3..89adfa14 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.6.3 +version = 1.7.0 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK @@ -34,6 +34,8 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 + Typing :: Typed [options] include_package_data = True From e11c3a977e8a0d63e5a8587faa7a1187f50411d7 Mon Sep 17 00:00:00 2001 From: filantus <13046849+filantus@users.noreply.github.com> Date: Sun, 21 Apr 2024 11:47:04 +0300 Subject: [PATCH 521/588] Typo fix --- ahk/_hotkey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index 90194974..1835e031 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -308,7 +308,7 @@ def f() -> None: t.start() self._callback_queue.task_done() # maybe _do_callback should handle this? - def _render_hotkey_tempate(self) -> str: + def _render_hotkey_template(self) -> str: if self._clipboard_callback is not None: on_clipboard = True else: @@ -322,7 +322,7 @@ def _render_hotkey_tempate(self) -> str: return ret def listener(self) -> None: - hotkey_script_contents = self._render_hotkey_tempate() + hotkey_script_contents = self._render_hotkey_template() logging.debug('hotkey script contents:\n%s', hotkey_script_contents) with tempfile.TemporaryDirectory(prefix='python-ahk') as tmpdirname: exc_file = os.path.join(tmpdirname, 'executor.ahk') From a388d756d790cc3a2044492a9e253c56e19e88e0 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 21 Apr 2024 13:13:47 -0700 Subject: [PATCH 522/588] fix ordering of TrayTip arguments for v2 --- ahk/_constants.py | 2 +- ahk/templates/daemon-v2.ahk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 6c78db61..8dde0c36 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -5621,7 +5621,7 @@ second := args[3] option := args[4] - TrayTip(title, text, option) + TrayTip(text, title, option) return FormatNoValueResponse() {% endblock AHKTraytip %} } diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index b9f03d76..8ab82a22 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2631,7 +2631,7 @@ AHKTraytip(args*) { second := args[3] option := args[4] - TrayTip(title, text, option) + TrayTip(text, title, option) return FormatNoValueResponse() {% endblock AHKTraytip %} } From 168c7536b4f1b26e2535d583286bfacb4365af6f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 21 Apr 2024 13:27:17 -0700 Subject: [PATCH 523/588] 1.7.1 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 89adfa14..0f7620d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.7.0 +version = 1.7.1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From ea9dd20c5037dbcd2a6665f986357dc4dbba57ed Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 21:39:07 +0000 Subject: [PATCH 524/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 24.4.0 → 24.4.2](https://github.com/psf/black/compare/24.4.0...24.4.2) - [github.com/pre-commit/mirrors-mypy: v1.9.0 → v1.10.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.9.0...v1.10.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a766f3bc..c1b27d44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '24.4.0' + rev: '24.4.2' hooks: - id: black args: @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.9.0' + rev: 'v1.10.0' hooks: - id: mypy args: From efb04f40c69d24c6ddf22f3b8d1b2c3aa5c9c4b9 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 1 May 2024 15:03:50 -0700 Subject: [PATCH 525/588] fix operator spacing in fstring --- ahk/_async/engine.py | 2 +- ahk/_sync/engine.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 00e20a61..555a54fb 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -92,7 +92,7 @@ def _resolve_button(button: Union[str, int]) -> str: resolved_button = _BUTTONS[button] elif isinstance(button, int) and button > 3: # for addtional mouse buttons - resolved_button = f'X{button-3}' + resolved_button = f'X{button - 3}' else: assert isinstance(button, str) resolved_button = button diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 5f7c2489..87c14aa3 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -79,7 +79,7 @@ def _resolve_button(button: Union[str, int]) -> str: resolved_button = _BUTTONS[button] elif isinstance(button, int) and button > 3: # for addtional mouse buttons - resolved_button = f'X{button-3}' + resolved_button = f'X{button - 3}' else: assert isinstance(button, str) resolved_button = button From bf249cfb6debc6101c699ab1f212820a4a397226 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 1 May 2024 15:31:17 -0700 Subject: [PATCH 526/588] use NamedTemporaryFile for hotkeys, like regular daemon --- ahk/_hotkey.py | 64 ++++++++++++++++++++++++-------------------------- ahk/_utils.py | 8 +++++++ 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index 1835e031..774bfeda 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -2,16 +2,18 @@ import atexit import functools -import os +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 @@ -26,7 +28,11 @@ 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 @@ -34,13 +40,6 @@ from typing_extensions import ParamSpec -import logging -import tempfile -from queue import Queue - -from ahk._utils import hotkey_escape -from ._constants import HOTKEYS_SCRIPT_TEMPLATE as _HOTKEY_SCRIPT, HOTKEYS_SCRIPT_V2_TEMPLATE as _HOTKEY_V2_SCRIPT - P_HotkeyCallbackParam = ParamSpec('P_HotkeyCallbackParam') T_HotkeyCallbackReturn = TypeVar('T_HotkeyCallbackReturn') @@ -324,31 +323,30 @@ def _render_hotkey_template(self) -> str: def listener(self) -> None: hotkey_script_contents = self._render_hotkey_template() logging.debug('hotkey script contents:\n%s', hotkey_script_contents) - with tempfile.TemporaryDirectory(prefix='python-ahk') as tmpdirname: - exc_file = os.path.join(tmpdirname, 'executor.ahk') - with open(exc_file, 'w') as f: - f.write(hotkey_script_contents) - self._proc = subprocess.Popen( - [self._executable_path, '/CP65001', '/ErrorStdOut', exc_file], - 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()) + with tempfile.NamedTemporaryFile(mode='w', prefix='python-ahk-hotkeys-', suffix='.ahk', delete=False) 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()) class Hotkey: diff --git a/ahk/_utils.py b/ahk/_utils.py index cbb7a98a..c61c8a26 100644 --- a/ahk/_utils.py +++ b/ahk/_utils.py @@ -1,4 +1,5 @@ import enum +import logging import os import re import subprocess @@ -170,3 +171,10 @@ def _get_executable_major_version(executable_path: str) -> Literal['v1', 'v2']: 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}') From 07f9c079035156c434089bdfedbc0e6999e1dd93 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 1 May 2024 15:37:50 -0700 Subject: [PATCH 527/588] prevent buildup of tempfiles when hotkeys are restarted --- ahk/_hotkey.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index 774bfeda..f126651d 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -347,6 +347,9 @@ def listener(self) -> None: 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: From f2ce272ad419c3b820aeaf2db9a9ff7cf95b0159 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 1 May 2024 15:38:25 -0700 Subject: [PATCH 528/588] use try_remove utility to suppress potential atexit failures --- ahk/_async/engine.py | 38 ++++++++++++++++---------------------- ahk/_async/transport.py | 5 +++-- ahk/_sync/engine.py | 29 ++++++++++++++++------------- ahk/_sync/transport.py | 5 +++-- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 555a54fb..a013941d 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -33,35 +33,29 @@ from .._utils import MsgBoxOtherOptions from .._utils import type_escape from ..directives import Directive - -if sys.version_info < (3, 10): - from typing_extensions import TypeAlias -else: - from typing import TypeAlias - -from ..extensions import ( - Extension, - _ExtensionMethodRegistry, - _extension_registry, - _resolve_extensions, -) +from ..extensions import _extension_registry +from ..extensions import _ExtensionMethodRegistry +from ..extensions import _resolve_extensions +from ..extensions import Extension from ..keys import Key from .transport import AsyncDaemonProcessTransport from .transport import AsyncFutureResult from .transport import AsyncTransport from .window import AsyncControl from .window import AsyncWindow +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._types import ( - Position, - CoordModeTargets, - CoordModeRelativeTo, - TitleMatchMode, - _BUTTONS, - MouseButton, - SendMode, - Coordinates, -) +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 diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index ceefe69c..0b2bd10d 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -40,6 +40,7 @@ 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 @@ -668,7 +669,7 @@ async def _create_process( tempscriptfile.write(script_text) # XXX: can we make this async? self._temp_script = tempscriptfile.name daemon_script = self._temp_script - atexit.register(os.remove, tempscriptfile.name) + atexit.register(try_remove, tempscriptfile.name) else: daemon_script = self._temp_script else: @@ -676,7 +677,7 @@ async def _create_process( with tempfile.NamedTemporaryFile(mode='w', prefix='python-ahk-', suffix='.ahk', delete=False) as tempscript: tempscript.write(script_text) daemon_script = tempscript.name - atexit.register(os.remove, tempscript.name) + atexit.register(try_remove, tempscript.name) runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] proc = AsyncAHKProcess(runargs=runargs) await proc.start() diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 87c14aa3..4cb79130 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -33,26 +33,29 @@ from .._utils import MsgBoxOtherOptions from .._utils import type_escape from ..directives import Directive - -if sys.version_info < (3, 10): - from typing_extensions import TypeAlias -else: - from typing import TypeAlias - -from ..extensions import ( - Extension, - _ExtensionMethodRegistry, - _extension_registry, - _resolve_extensions, -) +from ..extensions import _extension_registry +from ..extensions import _ExtensionMethodRegistry +from ..extensions import _resolve_extensions +from ..extensions import Extension from ..keys import Key from .transport import DaemonProcessTransport from .transport import FutureResult from .transport import Transport from .window import Control from .window import Window +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._types import Position, CoordModeTargets, CoordModeRelativeTo, TitleMatchMode, _BUTTONS, MouseButton, SendMode, Coordinates +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias sleep = time.sleep diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 32d7b4a4..2ccc393f 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -40,6 +40,7 @@ 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 @@ -632,7 +633,7 @@ def _create_process( tempscriptfile.write(script_text) # XXX: can we make this async? self._temp_script = tempscriptfile.name daemon_script = self._temp_script - atexit.register(os.remove, tempscriptfile.name) + atexit.register(try_remove, tempscriptfile.name) else: daemon_script = self._temp_script else: @@ -640,7 +641,7 @@ def _create_process( with tempfile.NamedTemporaryFile(mode='w', prefix='python-ahk-', suffix='.ahk', delete=False) as tempscript: tempscript.write(script_text) daemon_script = tempscript.name - atexit.register(os.remove, tempscript.name) + atexit.register(try_remove, tempscript.name) runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] proc = SyncAHKProcess(runargs=runargs) proc.start() From 6bbcae589719527cf207e695e133c9692cf4ca8d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 9 May 2024 20:36:49 -0700 Subject: [PATCH 529/588] fix typehint for keywait --- ahk/_async/engine.py | 10 +++++----- ahk/_sync/engine.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index a013941d..1a24e464 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1083,19 +1083,19 @@ async def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[Non # fmt: off @overload - async def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> bool: ... + 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] = None, logical_state: bool = False, released: bool = False) -> bool: ... + 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] = None, logical_state: bool = False, released: bool = False) -> AsyncFutureResult[bool]: ... + 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] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[bool, AsyncFutureResult[bool]]: ... + 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] = None, + timeout: Optional[int | float] = None, logical_state: bool = False, released: bool = False, blocking: bool = True, diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 4cb79130..b10614ee 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1071,19 +1071,19 @@ def key_up(self, key: Union[str, Key], blocking: bool = True) -> Union[None, Fut # fmt: off @overload - def key_wait(self, key_name: str, *, timeout: Optional[int] = None, logical_state: bool = False, released: bool = False) -> bool: ... + 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] = None, logical_state: bool = False, released: bool = False) -> bool: ... + 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] = None, logical_state: bool = False, released: bool = False) -> FutureResult[bool]: ... + 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] = None, logical_state: bool = False, released: bool = False, blocking: bool = True) -> Union[bool, FutureResult[bool]]: ... + 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] = None, + timeout: Optional[int | float] = None, logical_state: bool = False, released: bool = False, blocking: bool = True, From de90e941e50a8d7e5ff60cba60d299043d3353fa Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 10 May 2024 00:58:47 -0700 Subject: [PATCH 530/588] 1.7.2 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 0f7620d2..08fdf8e7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.7.1 +version = 1.7.2 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 17532048efda2af154d1baf45704fe9957f9b927 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 11 May 2024 14:11:24 -0700 Subject: [PATCH 531/588] fix bug introduced by https://github.com/psf/black/pull/4270 where `black.assert_equivalent` no longer raises an AssertionError --- .unasync-rewrite.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.unasync-rewrite.py b/.unasync-rewrite.py index d29a0680..01c379d8 100644 --- a/.unasync-rewrite.py +++ b/.unasync-rewrite.py @@ -9,6 +9,11 @@ changes = 0 +if hasattr(black, 'ASTSafetyError'): + exceptions = (AssertionError, black.ASTSafetyError) +else: + exceptions = (AssertionError,) + def _copyfunc(src, dst, *, follow_symlinks=True): global changes @@ -22,7 +27,7 @@ def _copyfunc(src, dst, *, follow_symlinks=True): src=contents, dst=dst_contents, ) - except AssertionError: + except exceptions: changes += 1 print('MODIFIED', dst) shutil.copy2(src, dst, follow_symlinks=follow_symlinks) From c41493c509f0844292983e3ac80aa3583f7b0ae7 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sun, 12 May 2024 17:40:12 -0700 Subject: [PATCH 532/588] Remove recommendation for psg, since it is no longer open source --- docs/api/methods.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/methods.rst b/docs/api/methods.rst index 0f89ae07..1aa235c2 100644 --- a/docs/api/methods.rst +++ b/docs/api/methods.rst @@ -417,7 +417,7 @@ 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 `_ , `pysimplegui `_ or similar. +party package such as `pyqt `_ , `FreeSimpleGUI `_ or similar. .. list-table:: :header-rows: 1 From 7f53f9bef46992333074493dfbeb584386e8b30a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 21 May 2024 17:52:04 -0700 Subject: [PATCH 533/588] GH-307 implement SetNumLockState/SetScrollLockState --- ahk/_async/engine.py | 94 +++++++++++++++++++++++++++++++++++-- ahk/_async/transport.py | 4 ++ ahk/_constants.py | 51 ++++++++++++++++++++ ahk/_sync/engine.py | 94 +++++++++++++++++++++++++++++++++++-- ahk/_sync/transport.py | 4 ++ ahk/_types.py | 2 + ahk/templates/daemon-v2.ahk | 26 ++++++++++ ahk/templates/daemon.ahk | 25 ++++++++++ docs/api/methods.rst | 8 ++-- 9 files changed, 294 insertions(+), 14 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 1a24e464..8a4644bb 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1285,16 +1285,19 @@ async def send_play( # fmt: off @overload - async def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + 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[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + 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[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + 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[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True + self, + state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, + *, + blocking: bool = True, ) -> Union[None, AsyncFutureResult[None]]: """ Analog for `SetCapsLockState `_ @@ -1305,11 +1308,92 @@ async def set_capslock_state( 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: ... diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 0b2bd10d..c0762fe9 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -337,6 +337,10 @@ async def function_call(self, function_name: Literal['AHKSendPlay'], args: Optio @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]]: ... diff --git a/ahk/_constants.py b/ahk/_constants.py index 8dde0c36..5b275050 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -2058,6 +2058,31 @@ {% 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. @@ -5147,6 +5172,32 @@ {% 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. diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index b10614ee..de3e6c7f 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1273,16 +1273,19 @@ def send_play( # fmt: off @overload - def set_capslock_state(self, state: Optional[Literal[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None) -> None: ... + 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[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[True]) -> None: ... + 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[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: Literal[False]) -> FutureResult[None]: ... + 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[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + 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[0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, *, blocking: bool = True + self, + state: Optional[Literal[True, False, 0, 1, 'On', 'Off', 'AlwaysOn', 'AlwaysOff']] = None, + *, + blocking: bool = True, ) -> Union[None, FutureResult[None]]: """ Analog for `SetCapsLockState `_ @@ -1293,11 +1296,92 @@ def set_capslock_state( 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: ... diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 2ccc393f..af308a57 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -310,6 +310,10 @@ def function_call(self, function_name: Literal['AHKSendPlay'], args: Optional[Li @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]]: ... diff --git a/ahk/_types.py b/ahk/_types.py index 7ac54b2a..69a920ab 100644 --- a/ahk/_types.py +++ b/ahk/_types.py @@ -173,6 +173,8 @@ class Coordinates(NamedTuple): 'AHKWinWaitNotActive', 'AHKClick', 'AHKSetCapsLockState', + 'AHKSetNumLockState', + 'AHKSetScrollLockState', 'SetKeyDelay', 'WinActivateBottom', 'AHKWinGetClass', diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 8ab82a22..f2cd2dce 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2157,6 +2157,32 @@ AHKSetCapsLockState(args*) { {% 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. diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 80f4ac74..1e724b55 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -2055,6 +2055,31 @@ AHKSetCapsLockState(args*) { {% 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. diff --git a/docs/api/methods.rst b/docs/api/methods.rst index 1aa235c2..ef26710b 100644 --- a/docs/api/methods.rst +++ b/docs/api/methods.rst @@ -97,11 +97,11 @@ Mouse and Keyboard - Not Implemented - Delays between mouse movements can be controlled in Python code between calls to ``mouse_move`` * - `SetNumLockState `_ - - Not Implemented - - + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.set_numlock_state` * - `SetScrollLockState `_ - - Not Implemented - - + - Implemented + - :py:meth:`~ahk._sync.engine.AHK.set_scroll_lock_state` * - `SetStoreCapsLockMode `_ - Not Implemented - From 3b61207722e80bae49b257f9ac8fed91a82966ca Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 21 May 2024 18:08:32 -0700 Subject: [PATCH 534/588] 1.7.3 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 08fdf8e7..b50690b1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.7.2 +version = 1.7.3 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 1455c177cd82dbd86f38216239945954a07b5112 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 28 May 2024 01:10:04 -0700 Subject: [PATCH 535/588] Fallback to string when keystate is not numeric --- ahk/_constants.py | 9 ++------- ahk/templates/daemon-v2.ahk | 5 +---- ahk/templates/daemon.ahk | 4 +--- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 5b275050..e1b78d01 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -1696,10 +1696,8 @@ if state is float return FormatResponse("ahk.message.FloatResponseMessage", state) - if state is alnum - return FormatResponse("ahk.message.StringResponseMessage", state) + return FormatResponse("ahk.message.StringResponseMessage", state) - return FormatResponse("ahk.message.ExceptionResponseMessage", state) {% endblock AHKKeyState %} } @@ -4810,11 +4808,8 @@ if IsFloat(state) return FormatResponse("ahk.message.FloatResponseMessage", state) - if IsAlnum(state) - return FormatResponse("ahk.message.StringResponseMessage", state) + return FormatResponse("ahk.message.StringResponseMessage", state) - msg := Format("Unexpected key state {}", state) - return FormatResponse("ahk.message.ExceptionResponseMessage", msg) {% endblock AHKKeyState %} } diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index f2cd2dce..9b200a4b 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -1795,11 +1795,8 @@ AHKKeyState(args*) { if IsFloat(state) return FormatResponse("ahk.message.FloatResponseMessage", state) - if IsAlnum(state) - return FormatResponse("ahk.message.StringResponseMessage", state) + return FormatResponse("ahk.message.StringResponseMessage", state) - msg := Format("Unexpected key state {}", state) - return FormatResponse("ahk.message.ExceptionResponseMessage", msg) {% endblock AHKKeyState %} } diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index 1e724b55..be1a6c35 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1693,10 +1693,8 @@ AHKKeyState(args*) { if state is float return FormatResponse("ahk.message.FloatResponseMessage", state) - if state is alnum - return FormatResponse("ahk.message.StringResponseMessage", state) + return FormatResponse("ahk.message.StringResponseMessage", state) - return FormatResponse("ahk.message.ExceptionResponseMessage", state) {% endblock AHKKeyState %} } From f6d1b1ec802f92d645fe618cd79f8fb31f7bdb3f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 28 May 2024 01:26:55 -0700 Subject: [PATCH 536/588] 1.7.4 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b50690b1..1a55c047 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.7.3 +version = 1.7.4 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From b9f5b6a3a8ef80aa342ab71c133990f56e35c991 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 30 May 2024 16:42:05 -0700 Subject: [PATCH 537/588] GH-315 fix usage of SetKeyDelay in AHKv2 when only one of key_press_duration or key_delay is provided --- ahk/_constants.py | 40 ++++++++++++++++++++++++++++++++----- ahk/templates/daemon-v2.ahk | 40 ++++++++++++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index e1b78d01..c643eabc 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -5054,7 +5054,13 @@ } if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, 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) + } } @@ -5080,7 +5086,13 @@ current_key_duration := Format("{}", A_KeyDuration) if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, 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) @@ -5101,7 +5113,13 @@ current_key_duration := Format("{}", A_KeyDuration) if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, 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) @@ -5122,7 +5140,13 @@ current_key_duration := Format("{}", A_KeyDuration) if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, 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) @@ -5143,7 +5167,13 @@ current_key_duration := Format("{}", A_KeyDurationPlay) if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, key_press_duration, "Play") + 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) diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 9b200a4b..78e815fc 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2041,7 +2041,13 @@ AHKSend(args*) { } if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, 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) + } } @@ -2067,7 +2073,13 @@ AHKSendRaw(args*) { current_key_duration := Format("{}", A_KeyDuration) if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, 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) @@ -2088,7 +2100,13 @@ AHKSendInput(args*) { current_key_duration := Format("{}", A_KeyDuration) if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, 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) @@ -2109,7 +2127,13 @@ AHKSendEvent(args*) { current_key_duration := Format("{}", A_KeyDuration) if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, 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) @@ -2130,7 +2154,13 @@ AHKSendPlay(args*) { current_key_duration := Format("{}", A_KeyDurationPlay) if (key_delay != "" or key_press_duration != "") { - SetKeyDelay(key_delay, key_press_duration, "Play") + 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) From ed959b3d7ae5738cfddbc1db9db288034afa2c8f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Thu, 30 May 2024 17:38:27 -0700 Subject: [PATCH 538/588] 1.7.5 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1a55c047..7e50920f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.7.4 +version = 1.7.5 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 12475606c88c8ac5c9e2a59f891221fae0ad3355 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 21:26:39 +0000 Subject: [PATCH 539/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.12.0 → v3.13.0](https://github.com/asottile/reorder-python-imports/compare/v3.12.0...v3.13.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c1b27d44..adda1f99 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: - "120" exclude: ^(ahk/_sync/.*\.py) - repo: https://github.com/asottile/reorder-python-imports - rev: v3.12.0 + rev: v3.13.0 hooks: - id: reorder-python-imports From 1baa479dae1d22783f2661d9b8a65666145ce838 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 3 Jun 2024 18:35:24 -0700 Subject: [PATCH 540/588] do not initialize transport for nonblocking calls --- ahk/_async/transport.py | 2 +- ahk/_sync/transport.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index c0762fe9..e0f14c8f 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -527,7 +527,7 @@ async def function_call( blocking: bool = True, engine: Optional[AsyncAHK[Any]] = None, ) -> Any: - if not self._started: + if not self._started and blocking: with warnings.catch_warnings(record=True) as caught_warnings: await self.init() if caught_warnings: diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index af308a57..ed369c5a 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -500,7 +500,7 @@ def function_call( blocking: bool = True, engine: Optional[AHK[Any]] = None, ) -> Any: - if not self._started: + if not self._started and blocking: with warnings.catch_warnings(record=True) as caught_warnings: self.init() if caught_warnings: From 969bbec16f871902f0c55ed646dd479aa553e92f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 3 Jun 2024 18:35:54 -0700 Subject: [PATCH 541/588] use absolute imports --- ahk/_async/engine.py | 32 ++++++++++++++++---------------- ahk/_sync/engine.py | 32 ++++++++++++++++---------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 8a4644bb..66e88502 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -22,27 +22,13 @@ from typing import TypeVar from typing import Union -from .._hotkey import Hotkey -from .._hotkey import Hotstring -from .._utils import _get_executable_major_version -from .._utils import _resolve_executable_path -from .._utils import MsgBoxButtons -from .._utils import MsgBoxDefaultButton -from .._utils import MsgBoxIcon -from .._utils import MsgBoxModality -from .._utils import MsgBoxOtherOptions -from .._utils import type_escape -from ..directives import Directive -from ..extensions import _extension_registry -from ..extensions import _ExtensionMethodRegistry -from ..extensions import _resolve_extensions -from ..extensions import Extension -from ..keys import Key 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 @@ -51,6 +37,20 @@ 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 diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index de3e6c7f..95bfc3c8 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -22,27 +22,13 @@ from typing import TypeVar from typing import Union -from .._hotkey import Hotkey -from .._hotkey import Hotstring -from .._utils import _get_executable_major_version -from .._utils import _resolve_executable_path -from .._utils import MsgBoxButtons -from .._utils import MsgBoxDefaultButton -from .._utils import MsgBoxIcon -from .._utils import MsgBoxModality -from .._utils import MsgBoxOtherOptions -from .._utils import type_escape -from ..directives import Directive -from ..extensions import _extension_registry -from ..extensions import _ExtensionMethodRegistry -from ..extensions import _resolve_extensions -from ..extensions import Extension -from ..keys import Key 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 @@ -51,6 +37,20 @@ 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 From cfe4595bf645f3997d3e57da4faa71a68b67e69c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 3 Jun 2024 20:56:35 -0700 Subject: [PATCH 542/588] 1.7.6 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 7e50920f..a3b3b852 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.7.5 +version = 1.7.6 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From d1626695906d71906c5c9ff61159c8f83e0df22e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:34:03 +0000 Subject: [PATCH 543/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.0.0 → 7.1.0](https://github.com/pycqa/flake8/compare/7.0.0...7.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index adda1f99..f2bd2763 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,7 +55,7 @@ repos: - jinja2 - repo: https://github.com/pycqa/flake8 - rev: '7.0.0' # pick a git hash / tag to point to + rev: '7.1.0' # pick a git hash / tag to point to hooks: - id: flake8 args: From 1051917ceb454d3ab2fe589ad90748f19805c8b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 22:58:59 +0000 Subject: [PATCH 544/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.0...v1.10.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f2bd2763..a7e642db 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.10.0' + rev: 'v1.10.1' hooks: - id: mypy args: From 47aaad9908a675baddb2e5ef38f7e1ba1d608f46 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 8 Jul 2024 17:58:43 -0700 Subject: [PATCH 545/588] add test for GH-328 --- tests/_async/test_keys.py | 11 +++++++++++ tests/_sync/test_keys.py | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/tests/_async/test_keys.py b/tests/_async/test_keys.py index 5615794c..669ce842 100644 --- a/tests/_async/test_keys.py +++ b/tests/_async/test_keys.py @@ -50,6 +50,17 @@ async def test_hotstring(self): 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() diff --git a/tests/_sync/test_keys.py b/tests/_sync/test_keys.py index e690cdee..d4335aeb 100644 --- a/tests/_sync/test_keys.py +++ b/tests/_sync/test_keys.py @@ -49,6 +49,16 @@ def test_hotstring(self): 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() From f10649754be8ed186b0e7a41e66fe630f45b35ea Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 8 Jul 2024 17:59:40 -0700 Subject: [PATCH 546/588] specify encoding for hotkeys --- ahk/_hotkey.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ahk/_hotkey.py b/ahk/_hotkey.py index f126651d..1aa52a81 100644 --- a/ahk/_hotkey.py +++ b/ahk/_hotkey.py @@ -323,7 +323,9 @@ def _render_hotkey_template(self) -> str: 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) as f: + 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( From 013e43f808c264358ff6bc06ce9fc388e20840e8 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 8 Jul 2024 18:12:47 -0700 Subject: [PATCH 547/588] 1.7.7 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index a3b3b852..90735b5c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.7.6 +version = 1.7.7 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 400c11b0c18365cfa7e9c81f439382eb7f7193e6 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Jul 2024 15:35:27 -0700 Subject: [PATCH 548/588] support for extending window classes --- ahk/_async/engine.py | 17 ++++++++++++++++- ahk/_async/window.py | 9 +++++++++ ahk/_sync/engine.py | 16 +++++++++++++++- ahk/_sync/window.py | 9 +++++++++ ahk/extensions.py | 40 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 87 insertions(+), 4 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 66e88502..64b9b337 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -148,7 +148,9 @@ def __init__( raise ValueError( f'Incompatible extension detected. Extension requires AutoHotkey {ext._requires} but current version is {version}' ) - self._method_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) + 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: @@ -176,6 +178,19 @@ def __getattr__(self, name: str) -> Callable[..., Any]: 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: diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 455d67fb..57217ce9 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -2,7 +2,9 @@ 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 @@ -70,6 +72,13 @@ def __eq__(self, other: object) -> bool: 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') diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 95bfc3c8..e6d2da88 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -144,7 +144,9 @@ def __init__( raise ValueError( f'Incompatible extension detected. Extension requires AutoHotkey {ext._requires} but current version is {version}' ) - self._method_registry = _ExtensionMethodRegistry(sync_methods={}, async_methods={}) + 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: @@ -171,6 +173,18 @@ def __getattr__(self, name: str) -> Callable[..., Any]: 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: diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index a7f47909..a08060eb 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -2,7 +2,9 @@ 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 @@ -66,6 +68,13 @@ def __eq__(self, other: object) -> bool: 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') diff --git a/ahk/extensions.py b/ahk/extensions.py index 3677e300..9b920421 100644 --- a/ahk/extensions.py +++ b/ahk/extensions.py @@ -32,15 +32,18 @@ class _ExtensionEntry: if typing.TYPE_CHECKING: - from ahk import AHK, AsyncAHK + 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): @@ -63,14 +66,41 @@ def register(self, f: Callable[Concatenate[TAHK, P], T]) -> Callable[Concatenate 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] = {} @@ -88,7 +118,7 @@ def __init__( self._includes: list[str] = includes or [] self.dependencies: list[Extension] = dependencies or [] self._extension_method_registry: _ExtensionMethodRegistry = _ExtensionMethodRegistry( - sync_methods={}, async_methods={} + sync_methods={}, async_methods={}, sync_window_methods={}, async_window_methods={} ) _extension_registry[self] = self._extension_method_registry @@ -108,6 +138,12 @@ def register(self, f: Callable[Concatenate[TAHK, P], T]) -> Callable[Concatenate 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))) From f862ea7859a88aa96d4166c2c3ce007b04a780a7 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Jul 2024 16:18:45 -0700 Subject: [PATCH 549/588] document window extensions feature --- docs/extending.rst | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/extending.rst b/docs/extending.rst index 43423ff1..276cf534 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -103,7 +103,7 @@ containing the AutoHotkey code we just wrote above. ''' simple_math_extension = Extension(script_text=script_text) - @simple_meth_extension.register # register the method for the extension + @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) @@ -141,6 +141,13 @@ If you use this example code, it should output something like this: :: 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 @@ -316,6 +323,18 @@ For example, suppose you want your method to return a datetime object, you might 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](https://github.com/spyoungtech/ahk-wmutil) an extension providing utility support for working with multiple monitors. Includes examples of window extensions. +- [ahk-json](https://github.com/spyoungtech/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 ^^^^^ From 46e51a4ef293393f88e6f811420cf7fcc91aaf87 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Jul 2024 16:21:51 -0700 Subject: [PATCH 550/588] fix links --- docs/extending.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/extending.rst b/docs/extending.rst index 276cf534..468a84e1 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -330,8 +330,8 @@ 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](https://github.com/spyoungtech/ahk-wmutil) an extension providing utility support for working with multiple monitors. Includes examples of window extensions. -- [ahk-json](https://github.com/spyoungtech/ahk-json) an extension providing custom a JSON message type that can be used by other extensions. +- `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. From 85b62e40344ef8c8dacdfa4cc5f535c5fbd22e5d Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 9 Jul 2024 16:34:49 -0700 Subject: [PATCH 551/588] 1.7.8 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 90735b5c..ccb6184c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.7.7 +version = 1.7.8 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From d9278fdf674c40566d00b6cd6ed89c2015746beb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 21:41:41 +0000 Subject: [PATCH 552/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.10.1 → v1.11.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.1...v1.11.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a7e642db..867db0b5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.10.1' + rev: 'v1.11.0' hooks: - id: mypy args: From a11a4bcc10b0bf65ae91bb8ec56416ba8b58808e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 22:14:28 +0000 Subject: [PATCH 553/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 24.4.2 → 24.8.0](https://github.com/psf/black/compare/24.4.2...24.8.0) - [github.com/pre-commit/mirrors-mypy: v1.11.0 → v1.11.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.0...v1.11.1) - [github.com/pycqa/flake8: 7.1.0 → 7.1.1](https://github.com/pycqa/flake8/compare/7.1.0...7.1.1) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 867db0b5..e9360233 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '24.4.2' + rev: '24.8.0' hooks: - id: black args: @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.11.0' + rev: 'v1.11.1' hooks: - id: mypy args: @@ -55,7 +55,7 @@ repos: - jinja2 - repo: https://github.com/pycqa/flake8 - rev: '7.1.0' # pick a git hash / tag to point to + rev: '7.1.1' # pick a git hash / tag to point to hooks: - id: flake8 args: From 62e5940945fb04e00ea160d14f8aef447cbb8f9b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 17 Aug 2024 14:38:02 -0700 Subject: [PATCH 554/588] GH-338 allow garbage collection of non-daemon processes --- ahk/_async/transport.py | 77 ++++++++++++++++++++++++++++------------- ahk/_sync/transport.py | 62 ++++++++++++++++++++++----------- buildunasync.py | 1 + 3 files changed, 95 insertions(+), 45 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index e0f14c8f..71c5c48a 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -59,6 +59,10 @@ 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') @@ -110,6 +114,9 @@ def async_assert_send_nonblocking_type_correct( 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 @@ -119,6 +126,8 @@ async def acommunicate( # unasync: remove @property def returncode(self) -> Optional[int]: ... + def kill(self) -> None: ... + class AsyncAHKProcess: def __init__(self, runargs: List[str]): @@ -130,9 +139,12 @@ def returncode(self) -> Optional[int]: assert self._proc is not None return self._proc.returncode - async def start(self) -> None: + 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) - atexit.register(kill, self._proc) + if atexit_cleanup: + atexit.register(kill, self._proc) return None async def adrain_stdin(self) -> None: # unasync: remove @@ -183,6 +195,17 @@ def communicate(self, input_bytes: Optional[bytes] = None, timeout: Optional[int 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( @@ -635,7 +658,8 @@ 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 = await self._create_process() + 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) @@ -659,9 +683,7 @@ def lock(self) -> Any: return self._a_execution_lock # unasync: remove return self._execution_lock - async def _create_process( - self, template: Optional[jinja2.Template] = None, **template_kwargs: Any - ) -> AsyncAHKProcess: + 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') @@ -684,15 +706,13 @@ async def _create_process( atexit.register(try_remove, tempscript.name) runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] proc = AsyncAHKProcess(runargs=runargs) - await proc.start() 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() - proc = await self._create_process() - try: + async with self._create_process() as proc: proc.write(msg) await proc.adrain_stdin() tom = await proc.readline() @@ -715,11 +735,6 @@ async def _send_nonblocking( part = await proc.readline() content_buffer.write(part) content = content_buffer.getvalue()[:-1] - finally: - try: - proc.kill() - except: # noqa - pass response = ResponseMessage.from_bytes(content, engine=engine) return response.unpack() # type: ignore @@ -781,11 +796,17 @@ async def _async_run_nonblocking( # unasync: remove loop = asyncio.get_running_loop() async def f() -> str: - stdout, stderr = await proc.acommunicate(script_bytes, timeout) + 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()) @@ -797,15 +818,23 @@ def _sync_run_nonblocking( script_bytes: Optional[bytes], timeout: Optional[int] = None, ) -> FutureResult[str]: - pool = ThreadPoolExecutor(max_workers=1) + raise RuntimeError('This method can only be called from the sync API') # unasync: remove def f() -> str: - stdout, stderr = proc.communicate(script_bytes, timeout) + 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) @@ -830,13 +859,13 @@ async def run_script( script_bytes = bytes(script_text_or_path, 'utf-8') runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', '*'] proc = AsyncAHKProcess(runargs) - await proc.start() if blocking: - 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') + 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) diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index ed369c5a..74931b08 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -59,6 +59,10 @@ 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') @@ -102,12 +106,16 @@ def async_assert_send_nonblocking_type_correct( 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]): @@ -119,9 +127,11 @@ def returncode(self) -> Optional[int]: assert self._proc is not None return self._proc.returncode - def start(self) -> None: + + def start(self, atexit_cleanup: bool = True) -> None: self._proc = sync_create_process(self.runargs) - atexit.register(kill, self._proc) + if atexit_cleanup: + atexit.register(kill, self._proc) return None @@ -160,6 +170,17 @@ def communicate(self, input_bytes: Optional[bytes] = None, timeout: Optional[int 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 + @@ -601,6 +622,7 @@ def start(self) -> None: 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) @@ -623,9 +645,7 @@ def _render_script(self, template: Optional[jinja2.Template] = None, **kwargs: A def lock(self) -> Any: return self._execution_lock - def _create_process( - self, template: Optional[jinja2.Template] = None, **template_kwargs: Any - ) -> SyncAHKProcess: + 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') @@ -648,15 +668,13 @@ def _create_process( atexit.register(try_remove, tempscript.name) runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script] proc = SyncAHKProcess(runargs=runargs) - proc.start() 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() - proc = self._create_process() - try: + with self._create_process() as proc: proc.write(msg) proc.drain_stdin() tom = proc.readline() @@ -679,11 +697,6 @@ def _send_nonblocking( part = proc.readline() content_buffer.write(part) content = content_buffer.getvalue()[:-1] - finally: - try: - proc.kill() - except: # noqa - pass response = ResponseMessage.from_bytes(content, engine=engine) return response.unpack() # type: ignore @@ -738,15 +751,22 @@ def _sync_run_nonblocking( script_bytes: Optional[bytes], timeout: Optional[int] = None, ) -> FutureResult[str]: - pool = ThreadPoolExecutor(max_workers=1) def f() -> str: - stdout, stderr = proc.communicate(script_bytes, timeout) + 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) @@ -771,13 +791,13 @@ def run_script( script_bytes = bytes(script_text_or_path, 'utf-8') runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', '*'] proc = SyncAHKProcess(runargs) - proc.start() if blocking: - 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') + 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) diff --git a/buildunasync.py b/buildunasync.py index d53b3995..40323aff 100644 --- a/buildunasync.py +++ b/buildunasync.py @@ -19,6 +19,7 @@ 'AsyncFutureResult': 'FutureResult', '_async_run_nonblocking': '_sync_run_nonblocking', 'acommunicate': 'communicate', + 'astart': 'start', # "__aenter__": "__aenter__", }, ), From ace137e765597110a1593869c2c4dff0a1d12eaf Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 17 Aug 2024 15:06:49 -0700 Subject: [PATCH 555/588] 1.8.0 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ccb6184c..ddb2793c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.7.8 +version = 1.8.0 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 8da462b7703463d98af0eaf6dde79bc7b78aa31b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 21:53:54 +0000 Subject: [PATCH 556/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.11.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.1...v1.11.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e9360233..78e2c07c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.11.1' + rev: 'v1.11.2' hooks: - id: mypy args: From 8418d5e6490ecfa7efeb52f49157adc6c8ca28db Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 23:27:49 +0000 Subject: [PATCH 557/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) - [github.com/psf/black: 24.8.0 → 24.10.0](https://github.com/psf/black/compare/24.8.0...24.10.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 78e2c07c..866a5ccc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: files: ^(ahk/daemon\.ahk|ahk/_constants\.py) - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: mixed-line-ending args: ["-f", "lf"] @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '24.8.0' + rev: '24.10.0' hooks: - id: black args: From c6b7d770c5ca54a28a3157ce756a1ca6ff132841 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:10:16 +0000 Subject: [PATCH 558/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.13.0 → v3.14.0](https://github.com/asottile/reorder-python-imports/compare/v3.13.0...v3.14.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 866a5ccc..327e7c57 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: - "120" exclude: ^(ahk/_sync/.*\.py) - repo: https://github.com/asottile/reorder-python-imports - rev: v3.13.0 + rev: v3.14.0 hooks: - id: reorder-python-imports From 0f00e9d8fefc41a988564329a586859e897b38fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:06:49 +0000 Subject: [PATCH 559/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.11.2 → v1.12.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.2...v1.12.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 327e7c57..239f8076 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.11.2' + rev: 'v1.12.1' hooks: - id: mypy args: From 7cffbd642e5704edd9019247427947ea356e3f25 Mon Sep 17 00:00:00 2001 From: EtorixDev <92535668+EtorixDev@users.noreply.github.com> Date: Mon, 28 Oct 2024 00:23:18 -0700 Subject: [PATCH 560/588] WinWait requires at least one of the main 4 keys be set. --- ahk/_async/engine.py | 4 ++++ ahk/_sync/engine.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 64b9b337..20306cec 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3315,6 +3315,10 @@ async def win_wait( """ 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, diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index e6d2da88..3779feb8 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3302,6 +3302,10 @@ def win_wait( """ 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, From 9507775c6f4312cf76612036fe195223ffa5f766 Mon Sep 17 00:00:00 2001 From: EtorixDev <92535668+EtorixDev@users.noreply.github.com> Date: Mon, 28 Oct 2024 01:20:29 -0700 Subject: [PATCH 561/588] Cast control to number if numeric & unset ctrl if falsy for v2. --- ahk/templates/daemon-v2.ahk | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 78e815fc..6989193e 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2341,7 +2341,7 @@ AHKWindowList(args*) { AHKControlClick(args*) { {% block AHKControlClick %} - ctrl := args[1] + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] title := args[2] text := args[3] button := args[4] @@ -2368,7 +2368,7 @@ AHKControlClick(args*) { } try { - ControlClick(ctrl, title, text, button, click_count, options, exclude_title, exclude_text) + ControlClick(ctrl || unset, title, text, button, click_count, options, exclude_title, exclude_text) } finally { DetectHiddenWindows(current_detect_hw) @@ -2383,7 +2383,7 @@ AHKControlClick(args*) { AHKControlGetText(args*) { {% block AHKControlGetText %} - ctrl := args[1] + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] title := args[2] text := args[3] extitle := args[4] @@ -2423,7 +2423,7 @@ AHKControlGetText(args*) { AHKControlGetPos(args*) { {% block AHKControlGetPos %} - ctrl := args[1] + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] title := args[2] text := args[3] extitle := args[4] @@ -2462,7 +2462,7 @@ AHKControlGetPos(args*) { AHKControlSend(args*) { {% block AHKControlSend %} - ctrl := args[1] + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] keys := args[2] title := args[3] text := args[4] @@ -2487,11 +2487,7 @@ AHKControlSend(args*) { } try { - if (ctrl != "") { - ControlSend(keys, ctrl, title, text, extitle, extext) - } else { - ControlSend(keys,, title, text, extitle, extext) - } + ControlSend(keys, ctrl || unset, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) From a1bedae37838f0a69a057a0bc5898810a41b756e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:31:06 +0000 Subject: [PATCH 562/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.12.1 → v1.13.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.12.1...v1.13.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 239f8076..0b5c7072 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.12.1' + rev: 'v1.13.0' hooks: - id: mypy args: From 323bc5f93d389c2e7846ee4b11ccd0239cdba762 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:11:11 +0000 Subject: [PATCH 563/588] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ahk/_async/engine.py | 2 +- ahk/_constants.py | 16 ++++++---------- ahk/_sync/engine.py | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 20306cec..3e01a7b1 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -3317,7 +3317,7 @@ async def win_wait( """ 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" + 'Expected non-blank value for at least one of the following: title, text, exclude_title, exclude_text' ) args = self._format_win_args( title=title, diff --git a/ahk/_constants.py b/ahk/_constants.py index c643eabc..60f5a603 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -5354,7 +5354,7 @@ AHKControlClick(args*) { {% block AHKControlClick %} - ctrl := args[1] + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] title := args[2] text := args[3] button := args[4] @@ -5381,7 +5381,7 @@ } try { - ControlClick(ctrl, title, text, button, click_count, options, exclude_title, exclude_text) + ControlClick(ctrl || unset, title, text, button, click_count, options, exclude_title, exclude_text) } finally { DetectHiddenWindows(current_detect_hw) @@ -5396,7 +5396,7 @@ AHKControlGetText(args*) { {% block AHKControlGetText %} - ctrl := args[1] + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] title := args[2] text := args[3] extitle := args[4] @@ -5436,7 +5436,7 @@ AHKControlGetPos(args*) { {% block AHKControlGetPos %} - ctrl := args[1] + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] title := args[2] text := args[3] extitle := args[4] @@ -5475,7 +5475,7 @@ AHKControlSend(args*) { {% block AHKControlSend %} - ctrl := args[1] + ctrl := IsNumber(args[1]) ? Number(args[1]) : args[1] keys := args[2] title := args[3] text := args[4] @@ -5500,11 +5500,7 @@ } try { - if (ctrl != "") { - ControlSend(keys, ctrl, title, text, extitle, extext) - } else { - ControlSend(keys,, title, text, extitle, extext) - } + ControlSend(keys, ctrl || unset, title, text, extitle, extext) } finally { DetectHiddenWindows(current_detect_hw) diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 3779feb8..cbf44eb4 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -3304,7 +3304,7 @@ def win_wait( """ 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" + 'Expected non-blank value for at least one of the following: title, text, exclude_title, exclude_text' ) args = self._format_win_args( title=title, From 044f3f1105efc113078d009fdcac115320335c29 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 20 Dec 2024 01:28:04 -0800 Subject: [PATCH 564/588] 1.8.1 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ddb2793c..34fe8b75 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.8.0 +version = 1.8.1 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 387d701379f413277ac7d7e22b3de18dd79ff28c Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 13 Jan 2025 23:23:07 -0800 Subject: [PATCH 565/588] gh-361 fix bug in show_tooltip that silently omitted `which` argument --- ahk/_async/engine.py | 1 + ahk/_sync/engine.py | 1 + 2 files changed, 2 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 3e01a7b1..10f53f21 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1605,6 +1605,7 @@ async def show_tooltip( 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: diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index cbf44eb4..1cba90d6 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1592,6 +1592,7 @@ def show_tooltip( 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: From aee92995d0b8a26be7c1959927362d06af2473d1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Mon, 13 Jan 2025 23:37:43 -0800 Subject: [PATCH 566/588] pin mypy for now --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e897cac5..a5eb4cd8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ unasync black tokenize-rt coverage -mypy +mypy==1.13.0 typing_extensions jinja2 pytest-rerunfailures From d5f4db2737a4cd94266887c1a30be74fa1ad4e37 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 14 Jan 2025 17:20:19 -0800 Subject: [PATCH 567/588] fix show_tooltip for AHK v2 ensures that arguments are correctly parsed as numbers when provided and fixes a problem in AHKv2 where the tooltip window creation may be blocked by returning to the blocking daemon input loop until the next command invocation --- ahk/_constants.py | 8 +++++++- ahk/templates/daemon-v2.ahk | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ahk/_constants.py b/ahk/_constants.py index 60f5a603..fa7a1651 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -5704,7 +5704,13 @@ x := args[2] y := args[3] which := args[4] - ToolTip(text, x, y, which) + + 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 %} } diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 6989193e..4164f07a 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -2691,7 +2691,13 @@ AHKShowToolTip(args*) { x := args[2] y := args[3] which := args[4] - ToolTip(text, x, y, which) + + 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 %} } From caadd3a9714891598d0899ebe701e4744445052a Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 14 Jan 2025 17:28:12 -0800 Subject: [PATCH 568/588] unpin mypy, fix typing issues --- ahk/_async/transport.py | 14 ++++++-------- ahk/_sync/transport.py | 6 +++--- requirements-dev.txt | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index 71c5c48a..d3f9b655 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -321,8 +321,7 @@ async def run_script(self, script_text_or_path: str, /, *, blocking: bool = True @abstractmethod async def run_script( self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None - ) -> Union[str, AsyncFutureResult[str]]: - return NotImplemented + ) -> Union[str, AsyncFutureResult[str]]: ... # fmt: off @overload @@ -565,22 +564,21 @@ async def function_call( @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]]: - return NotImplemented + ) -> 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]] - ]: - return NotImplemented + ]: ... @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]]]: - return NotImplemented + ) -> FutureResult[ + Union[None, Tuple[int, int], int, str, bool, AsyncWindow, List[AsyncWindow], List[AsyncControl]] + ]: ... class AsyncDaemonProcessTransport(AsyncTransport): diff --git a/ahk/_sync/transport.py b/ahk/_sync/transport.py index 74931b08..eaa16988 100644 --- a/ahk/_sync/transport.py +++ b/ahk/_sync/transport.py @@ -293,7 +293,7 @@ def run_script(self, script_text_or_path: str, /, *, blocking: bool = True, time def run_script( self, script_text_or_path: str, /, *, blocking: bool = True, timeout: Optional[int] = None ) -> Union[str, FutureResult[str]]: - return NotImplemented + ... # fmt: off @overload @@ -537,14 +537,14 @@ def function_call( def send( self, request: RequestMessage, engine: Optional[AHK[Any]] = None ) -> Union[None, Tuple[int, int], int, str, bool, Window, List[Window], List[Control]]: - return NotImplemented + ... @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]]]: - return NotImplemented + ... class DaemonProcessTransport(Transport): diff --git a/requirements-dev.txt b/requirements-dev.txt index a5eb4cd8..e897cac5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ unasync black tokenize-rt coverage -mypy==1.13.0 +mypy typing_extensions jinja2 pytest-rerunfailures From 70f43c4b752fa2fc3ff6ea984759229b8c26470f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Tue, 14 Jan 2025 19:12:50 -0800 Subject: [PATCH 569/588] 1.8.2 :package: --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 34fe8b75..b8cd6ad8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.8.1 +version = 1.8.2 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK @@ -35,6 +35,7 @@ classifiers = Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 Typing :: Typed [options] From 801a95a604f7b26a8da4f5f7df24b37669ad201f Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 31 Jan 2025 17:13:20 -0800 Subject: [PATCH 570/588] fix version-specific type hinting --- ahk/_async/engine.py | 48 ++++++++++++++++++++++---------------------- ahk/_sync/engine.py | 48 ++++++++++++++++++++++---------------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 10f53f21..da9899da 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -1441,13 +1441,13 @@ async def show_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str, sec 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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: AsyncAHK[Optional[Literal['v1']]], 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]]: ... + 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, @@ -1486,13 +1486,13 @@ async def show_error_traytip(self: AsyncAHK[Literal['v2']], title: str, text: st 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + 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( @@ -1523,13 +1523,13 @@ async def show_info_traytip(self: AsyncAHK[Literal['v2']], title: str, text: str 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + 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], @@ -1559,13 +1559,13 @@ async def show_warning_traytip(self: AsyncAHK[Literal['v2']], title: str, text: 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> AsyncFutureResult[None]: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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: AsyncAHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, AsyncFutureResult[None]]: ... + 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], @@ -1742,13 +1742,13 @@ async def win_get(self: AsyncAHK[Literal['v2']], title: str = '', text: str = '' 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: AsyncAHK[Optional[Literal['v1']]], 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]: ... + 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: AsyncAHK[Optional[Literal['v1']]], 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]]: ... + 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: AsyncAHK[Optional[Literal['v1']]], 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]: ... + 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: AsyncAHK[Optional[Literal['v1']]], 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]]: ... + 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, @@ -1885,13 +1885,13 @@ async def win_get_position(self: AsyncAHK[Literal['v2']], title: str = '', text: 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: AsyncAHK[Optional[Literal['v1']]], 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]: ... + 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: AsyncAHK[Optional[Literal['v1']]], 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]]: ... + 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: AsyncAHK[Optional[Literal['v1']]], 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]: ... + 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: AsyncAHK[Optional[Literal['v1']]], 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]]]: ... + 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, diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 1cba90d6..30161031 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -1428,13 +1428,13 @@ def show_traytip(self: AHK[Literal['v2']], title: str, text: str, second: None = 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, type_id: int = 1, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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: AHK[Optional[Literal['v1']]], 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]]: ... + 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, @@ -1473,13 +1473,13 @@ def show_error_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silen 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + 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( @@ -1510,13 +1510,13 @@ def show_info_traytip(self: AHK[Literal['v2']], title: str, text: str, *, silent 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + 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], @@ -1546,13 +1546,13 @@ def show_warning_traytip(self: AHK[Literal['v2']], title: str, text: str, *, sil 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False) -> None: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[False]) -> FutureResult[None]: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: Literal[True]) -> None: ... + 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: AHK[Optional[Literal['v1']]], title: str, text: str, second: Optional[float] = None, *, silent: bool = False, large_icon: bool = False, blocking: bool = True) -> Union[None, FutureResult[None]]: ... + 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], @@ -1729,13 +1729,13 @@ def win_get(self: AHK[Literal['v2']], title: str = '', text: str = '', exclude_t 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: AHK[Optional[Literal['v1']]], 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]: ... + 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: AHK[Optional[Literal['v1']]], 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]]: ... + 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: AHK[Optional[Literal['v1']]], 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]: ... + 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: AHK[Optional[Literal['v1']]], 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]]: ... + 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, @@ -1872,13 +1872,13 @@ def win_get_position(self: AHK[Literal['v2']], title: str = '', text: str = '', 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: AHK[Optional[Literal['v1']]], 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]: ... + 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: AHK[Optional[Literal['v1']]], 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]]: ... + 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: AHK[Optional[Literal['v1']]], 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]: ... + 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: AHK[Optional[Literal['v1']]], 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]]]: ... + 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, From 9fe8ac3324dfe5d8128ad6861c38bdafb0d2f2c5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:59:11 +0000 Subject: [PATCH 571/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.13.0 → v1.14.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.13.0...v1.14.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0b5c7072..c5569ba8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.13.0' + rev: 'v1.14.1' hooks: - id: mypy args: From 3059d71e45236db28cb19b5ccc5d97f2d7aaff43 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Fri, 31 Jan 2025 18:14:53 -0800 Subject: [PATCH 572/588] type-hint Window classmethods for engine version --- ahk/_async/engine.py | 1 + ahk/_async/window.py | 8 ++++++++ ahk/_sync/engine.py | 1 + ahk/_sync/window.py | 8 ++++++++ 4 files changed, 18 insertions(+) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index da9899da..d5a21e02 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -821,6 +821,7 @@ async def get_active_window( 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: """ diff --git a/ahk/_async/window.py b/ahk/_async/window.py index 57217ce9..3c5e5e8c 100644 --- a/ahk/_async/window.py +++ b/ahk/_async/window.py @@ -656,6 +656,14 @@ async def move( 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}') diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 30161031..5f42c81b 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -811,6 +811,7 @@ def get_active_window( 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: """ diff --git a/ahk/_sync/window.py b/ahk/_sync/window.py index a08060eb..5304f4c0 100644 --- a/ahk/_sync/window.py +++ b/ahk/_sync/window.py @@ -635,6 +635,14 @@ def move( 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}') From 109b95d8dfa19b7670b7a66469d8e36c9995df35 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Sat, 1 Feb 2025 12:39:51 -0800 Subject: [PATCH 573/588] 1.8.3 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b8cd6ad8..b3928d83 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.8.2 +version = 1.8.3 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From 2086182670e248f1bdadc5beb541fa052c864f64 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:18:35 +0000 Subject: [PATCH 574/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 24.10.0 → 25.1.0](https://github.com/psf/black/compare/24.10.0...25.1.0) - [github.com/pre-commit/mirrors-mypy: v1.14.1 → v1.15.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.14.1...v1.15.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c5569ba8..bc223975 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black - rev: '24.10.0' + rev: '25.1.0' hooks: - id: black args: @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.14.1' + rev: 'v1.15.0' hooks: - id: mypy args: From 6c01ea8937f27714eb28e2840ba5dd14bd39c3c3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 20:41:35 +0000 Subject: [PATCH 575/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.1.1 → 7.1.2](https://github.com/pycqa/flake8/compare/7.1.1...7.1.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bc223975..1c0c0e5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,7 +55,7 @@ repos: - jinja2 - repo: https://github.com/pycqa/flake8 - rev: '7.1.1' # pick a git hash / tag to point to + rev: '7.1.2' # pick a git hash / tag to point to hooks: - id: flake8 args: From a4d374dbc6b4666a2e63a169389e49ba1a2e60e1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 12 Mar 2025 17:05:56 -0700 Subject: [PATCH 576/588] contribution guide --- .github/workflows/test.yaml | 2 - CONTRIBUTING.md | 238 ++++++++++++++++++++++++++++++++++++ requirements-dev.txt | 5 +- 3 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d4de6f93..187afd9d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,8 +20,6 @@ jobs: python -m pip install --upgrade pip python -m pip install -r requirements-dev.txt python -m pip install . - python -m pip install tox - python -m pip install "ahk-binary==2023.9.0" - name: Test with coverage/pytest timeout-minutes: 10 env: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..576556de --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,238 @@ +# 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. + +## 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 + +- 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`) + + +### 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. + + +## 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`: + +```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 + +## 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 +``` + +## How this project works, generally + +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 Python. + + +## 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 +``` diff --git a/requirements-dev.txt b/requirements-dev.txt index e897cac5..4986a9fa 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ pytest pillow -unasync +unasync@https://github.com/spyoungtech/unasync/archive/refs/heads/unasync-remove.zip black tokenize-rt coverage @@ -9,3 +9,6 @@ typing_extensions jinja2 pytest-rerunfailures ahk-json +ahk-binary +pre-commit +tox From c8e7ef5b8a5ef08f14e465198b45469511ba941b Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 12 Mar 2025 17:30:56 -0700 Subject: [PATCH 577/588] clarify contribution expectations --- CONTRIBUTING.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 576556de..9ea5b581 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -236,3 +236,20 @@ 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. From ecb6c51c8d4fc103cb12b1bde235151f5ee16ee1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 12 Mar 2025 17:43:14 -0700 Subject: [PATCH 578/588] update doc ordering, format, TOC --- CONTRIBUTING.md | 91 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ea5b581..716d5543 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,25 @@ This guide is a work in progress, but aims to help a new contributor make a succ 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. -## Before contributing + +* [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, @@ -14,14 +32,17 @@ improvements. When in doubt, create an issue. -## Initial development setup +# 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. +## 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 @@ -30,7 +51,29 @@ project, it will likely produce a lot of unintended changes that will not be acc For these reasons and more, it is critical that you use the `pre-commit` hooks in order to make a successful contribution. -## Running tests +# 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`) @@ -43,7 +86,7 @@ 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`: +you can run the tests directly with `pytest` (but be sure to run code generation first!): ```bash pytest tests @@ -58,28 +101,12 @@ Notes: - 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 -## 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 -``` +# How this project works, briefly -## How this project works, generally +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. @@ -103,16 +130,16 @@ alternate transports can be used, such as in the [ahk-client](https://github.com AHK function calls over HTTP (to a server running [ahk-server](https://github.com/spyoungtech/ahk-server)). -### Hotkeys +## 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 Python. +(found in `ahk/hotkeys.py`) is not implemented async-first -- it is all synchronous/threaded Python. -## Implementing a new method +# 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 @@ -138,7 +165,7 @@ git checkout -b gh-12345 ``` -### Writing the AutoHotkey code +## Writing the AutoHotkey code For example, in `ahk/templates/daemon-v2.ahk`, you may add a new function as so: @@ -161,7 +188,7 @@ Note that functions must always return a response (e.g. as provided by `FormatRe for more information about available message formats and implementing new message formats. -### Writing the Python code +## Writing the Python code For example, in `ahk/_async/engine.py` you might add the following method to the `AsyncAHK` class: @@ -190,7 +217,7 @@ The most important part of this code is that the last part of the function retur should be implemented in the message type instead. -### Testing and code generation +## 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: @@ -239,7 +266,7 @@ 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: +# 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`). From 1ac1d9ced70fd80cbb90e9ef2db3e7c5cb4fc9ab Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 19:26:44 +0000 Subject: [PATCH 579/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.1.2 → 7.2.0](https://github.com/pycqa/flake8/compare/7.1.2...7.2.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1c0c0e5c..e541b1f3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,7 +55,7 @@ repos: - jinja2 - repo: https://github.com/pycqa/flake8 - rev: '7.1.2' # pick a git hash / tag to point to + rev: '7.2.0' # pick a git hash / tag to point to hooks: - id: flake8 args: From 5810aa5db383270372627ba5d3521aec8d5c39ce Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 2 Apr 2025 12:11:07 -0700 Subject: [PATCH 580/588] add coord_mode keyword arg for mouse_move --- ahk/_async/engine.py | 14 ++++++++++---- ahk/_constants.py | 35 +++++++++++++++++++++++++++++++++++ ahk/_sync/engine.py | 14 ++++++++++---- ahk/templates/daemon-v2.ahk | 12 ++++++++++++ ahk/templates/daemon.ahk | 23 +++++++++++++++++++++++ 5 files changed, 90 insertions(+), 8 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index d5a21e02..4cce4359 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -750,13 +750,13 @@ def mouse_position(self, new_position: Tuple[int, int]) -> None: # 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) -> None: ... + 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) -> None: ... + 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) -> AsyncFutureResult[None]: ... + 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) -> Union[None, AsyncFutureResult[None]]: ... + 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, @@ -767,6 +767,7 @@ async def mouse_move( relative: bool = False, send_mode: Optional[SendMode] = None, blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, ) -> Union[None, AsyncFutureResult[None]]: """ Analog for `MouseMove `_ @@ -791,6 +792,11 @@ async def mouse_move( 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 diff --git a/ahk/_constants.py b/ahk/_constants.py index fa7a1651..03f3830e 100644 --- a/ahk/_constants.py +++ b/ahk/_constants.py @@ -1707,11 +1707,34 @@ 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 %} @@ -4820,8 +4843,16 @@ 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 } @@ -4836,6 +4867,10 @@ SendMode current_send_mode } + if (coord_mode != "") { + CoordMode("Mouse", current_coord_mode) + } + resp := FormatNoValueResponse() return resp {% endblock AHKMouseMove %} diff --git a/ahk/_sync/engine.py b/ahk/_sync/engine.py index 5f42c81b..fef493ef 100644 --- a/ahk/_sync/engine.py +++ b/ahk/_sync/engine.py @@ -740,13 +740,13 @@ def mouse_position(self, new_position: Tuple[int, int]) -> None: # 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) -> None: ... + 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) -> None: ... + 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) -> FutureResult[None]: ... + 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) -> Union[None, FutureResult[None]]: ... + 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, @@ -757,6 +757,7 @@ def mouse_move( relative: bool = False, send_mode: Optional[SendMode] = None, blocking: bool = True, + coord_mode: Optional[CoordModeRelativeTo] = None, ) -> Union[None, FutureResult[None]]: """ Analog for `MouseMove `_ @@ -781,6 +782,11 @@ def mouse_move( else: args.append('') + if coord_mode: + args.append(coord_mode) + else: + args.append('') + resp = self._transport.function_call('AHKMouseMove', args, blocking=blocking) return resp diff --git a/ahk/templates/daemon-v2.ahk b/ahk/templates/daemon-v2.ahk index 4164f07a..ebe4e4d4 100644 --- a/ahk/templates/daemon-v2.ahk +++ b/ahk/templates/daemon-v2.ahk @@ -1807,8 +1807,16 @@ AHKMouseMove(args*) { 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 } @@ -1823,6 +1831,10 @@ AHKMouseMove(args*) { SendMode current_send_mode } + if (coord_mode != "") { + CoordMode("Mouse", current_coord_mode) + } + resp := FormatNoValueResponse() return resp {% endblock AHKMouseMove %} diff --git a/ahk/templates/daemon.ahk b/ahk/templates/daemon.ahk index be1a6c35..a6a19119 100644 --- a/ahk/templates/daemon.ahk +++ b/ahk/templates/daemon.ahk @@ -1704,11 +1704,34 @@ AHKMouseMove(args*) { 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 %} From 1bfb9924b7e4453dab1eaf59141367246089b5d1 Mon Sep 17 00:00:00 2001 From: Spencer Phillip Young Date: Wed, 2 Apr 2025 12:45:58 -0700 Subject: [PATCH 581/588] 1.8.4 :package: --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b3928d83..4660e8e7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ahk -version = 1.8.3 +version = 1.8.4 author_email = spencer.young@spyoung.com author = Spencer Young description = A Python wrapper for AHK From cdfeafd4d081cb89e38ca65eeab0a87d522198fe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 19:29:58 +0000 Subject: [PATCH 582/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.14.0 → v3.15.0](https://github.com/asottile/reorder-python-imports/compare/v3.14.0...v3.15.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e541b1f3..010ead80 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: - "120" exclude: ^(ahk/_sync/.*\.py) - repo: https://github.com/asottile/reorder-python-imports - rev: v3.14.0 + rev: v3.15.0 hooks: - id: reorder-python-imports From fb5ebd35ba03bf5873fc24f13ae5c6b0a1b803ed Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 19:39:46 +0000 Subject: [PATCH 583/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.15.0 → v1.16.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.15.0...v1.16.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 010ead80..963d55c7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.15.0' + rev: 'v1.16.0' hooks: - id: mypy args: From 7f74a2d827264f41f7a35fc28d07af74d80690df Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:26:45 +0000 Subject: [PATCH 584/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0) - [github.com/pre-commit/mirrors-mypy: v1.16.0 → v1.17.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.16.0...v1.17.1) - [github.com/pycqa/flake8: 7.2.0 → 7.3.0](https://github.com/pycqa/flake8/compare/7.2.0...7.3.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 963d55c7..8613997b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: files: ^(ahk/daemon\.ahk|ahk/_constants\.py) - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: mixed-line-ending args: ["-f", "lf"] @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.16.0' + rev: 'v1.17.1' hooks: - id: mypy args: @@ -55,7 +55,7 @@ repos: - jinja2 - repo: https://github.com/pycqa/flake8 - rev: '7.2.0' # pick a git hash / tag to point to + rev: '7.3.0' # pick a git hash / tag to point to hooks: - id: flake8 args: From dfd7ca391bc1e0d82b1742c9e325082530da4438 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 20:07:15 +0000 Subject: [PATCH 585/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.17.1 → v1.18.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.17.1...v1.18.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8613997b..d37d6600 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.17.1' + rev: 'v1.18.1' hooks: - id: mypy args: From 1b2f5dc683d35440a6f7b88696e86bcee4d7c5a9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 20:28:13 +0000 Subject: [PATCH 586/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - https://github.com/psf/black → https://github.com/psf/black-pre-commit-mirror - [github.com/psf/black-pre-commit-mirror: 25.1.0 → 25.12.0](https://github.com/psf/black-pre-commit-mirror/compare/25.1.0...25.12.0) - [github.com/asottile/reorder-python-imports: v3.15.0 → v3.16.0](https://github.com/asottile/reorder-python-imports/compare/v3.15.0...v3.16.0) - [github.com/pre-commit/mirrors-mypy: v1.18.1 → v1.19.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.18.1...v1.19.1) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d37d6600..d07115d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,8 +30,8 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - id: double-quote-string-fixer -- repo: https://github.com/psf/black - rev: '25.1.0' +- repo: https://github.com/psf/black-pre-commit-mirror + rev: '25.12.0' hooks: - id: black args: @@ -40,12 +40,12 @@ repos: - "120" exclude: ^(ahk/_sync/.*\.py) - repo: https://github.com/asottile/reorder-python-imports - rev: v3.15.0 + rev: v3.16.0 hooks: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.18.1' + rev: 'v1.19.1' hooks: - id: mypy args: From 79abf0dc67a95c3b8e2c3b1850113d8e9e40b9d5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 21:01:53 +0000 Subject: [PATCH 587/588] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black-pre-commit-mirror: 25.12.0 → 26.5.1](https://github.com/psf/black-pre-commit-mirror/compare/25.12.0...26.5.1) - [github.com/asottile/reorder-python-imports: v3.16.0 → v3.17.0](https://github.com/asottile/reorder-python-imports/compare/v3.16.0...v3.17.0) - [github.com/pre-commit/mirrors-mypy: v1.19.1 → v2.3.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.19.1...v2.3.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d07115d0..f120defc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - id: trailing-whitespace - id: double-quote-string-fixer - repo: https://github.com/psf/black-pre-commit-mirror - rev: '25.12.0' + rev: '26.5.1' hooks: - id: black args: @@ -40,12 +40,12 @@ repos: - "120" exclude: ^(ahk/_sync/.*\.py) - repo: https://github.com/asottile/reorder-python-imports - rev: v3.16.0 + rev: v3.17.0 hooks: - id: reorder-python-imports - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.19.1' + rev: 'v2.3.0' hooks: - id: mypy args: From 1bd3f317c65be564d09c2da3709ce25aa24c4452 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 21:02:30 +0000 Subject: [PATCH 588/588] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ahk/_async/engine.py | 2 +- ahk/_async/transport.py | 1 - tests/_async/test_hotkeys.py | 1 - tests/_sync/test_clipboard.py | 1 - tests/_sync/test_extensions.py | 1 - tests/_sync/test_gui.py | 1 - tests/_sync/test_hotkeys.py | 1 - tests/_sync/test_keys.py | 1 - tests/_sync/test_mouse.py | 1 - 9 files changed, 1 insertion(+), 9 deletions(-) diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 4cce4359..8bb9c6e9 100644 --- a/ahk/_async/engine.py +++ b/ahk/_async/engine.py @@ -289,7 +289,7 @@ async def set_title_match_mode(self, title_match_mode: TitleMatchMode, /) -> Non args = [] if isinstance(title_match_mode, tuple): - (match_mode, match_speed) = title_match_mode + match_mode, match_speed = title_match_mode elif title_match_mode in (1, 2, 3, 'RegEx'): match_mode = title_match_mode match_speed = '' diff --git a/ahk/_async/transport.py b/ahk/_async/transport.py index d3f9b655..06fdf8cd 100644 --- a/ahk/_async/transport.py +++ b/ahk/_async/transport.py @@ -49,7 +49,6 @@ from ahk.message import RequestMessage from ahk.message import ResponseMessage - if TYPE_CHECKING: from ahk import AsyncControl from ahk import AsyncWindow diff --git a/tests/_async/test_hotkeys.py b/tests/_async/test_hotkeys.py index 66056660..5d87f71e 100644 --- a/tests/_async/test_hotkeys.py +++ b/tests/_async/test_hotkeys.py @@ -8,7 +8,6 @@ from ahk import AsyncAHK from ahk import AsyncWindow - async_sleep = asyncio.sleep # unasync: remove sleep = time.sleep diff --git a/tests/_sync/test_clipboard.py b/tests/_sync/test_clipboard.py index 22ec1e87..fbb305ac 100644 --- a/tests/_sync/test_clipboard.py +++ b/tests/_sync/test_clipboard.py @@ -4,7 +4,6 @@ from ahk import AHK - sleep = time.sleep diff --git a/tests/_sync/test_extensions.py b/tests/_sync/test_extensions.py index d983e0fd..bf26443d 100644 --- a/tests/_sync/test_extensions.py +++ b/tests/_sync/test_extensions.py @@ -12,7 +12,6 @@ from ahk import AHK from ahk.extensions import Extension - sleep = time.sleep function_name = 'AHKDoSomething' diff --git a/tests/_sync/test_gui.py b/tests/_sync/test_gui.py index db35d5ce..dac14845 100644 --- a/tests/_sync/test_gui.py +++ b/tests/_sync/test_gui.py @@ -6,7 +6,6 @@ from ahk import AHK - sleep = time.sleep diff --git a/tests/_sync/test_hotkeys.py b/tests/_sync/test_hotkeys.py index 6b1c0a9c..7c1e9955 100644 --- a/tests/_sync/test_hotkeys.py +++ b/tests/_sync/test_hotkeys.py @@ -7,7 +7,6 @@ from ahk import AHK from ahk import Window - sleep = time.sleep diff --git a/tests/_sync/test_keys.py b/tests/_sync/test_keys.py index d4335aeb..b95fd932 100644 --- a/tests/_sync/test_keys.py +++ b/tests/_sync/test_keys.py @@ -10,7 +10,6 @@ from ahk import AHK from ahk import Window - sleep = time.sleep diff --git a/tests/_sync/test_mouse.py b/tests/_sync/test_mouse.py index a8b580c4..50e56167 100644 --- a/tests/_sync/test_mouse.py +++ b/tests/_sync/test_mouse.py @@ -8,7 +8,6 @@ from ahk import AHK from ahk import Window - sleep = time.sleep