diff --git a/ahk/_async/engine.py b/ahk/_async/engine.py index 634e13b..eac2676 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 528f8c1..74027e2 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 d730408..40331d4 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 a4c12d3..bd82bfa 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 3d68840..8b2ede2 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 6e2b618..804252e 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 87650e0..7fa44c4 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 d0cece8..ec13a58 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 b4be92a..f826940 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)