Skip to content

Commit cf429f0

Browse files
committed
Experiment: Interruptible AHK commands
This essentially reverts the following commits: - 55a95e0 - b4a2ae5 - a6b013e - a478d4f The trick to interruptible AHK commands is setting the timer to check for signals *before* calling the main Python script. This makes sense, because the only time the AHK timers tick during the main Python code, is when the Python code calls a blocking AHK command. However, the biggest downside of this approach is that the signal that was detected by the timer cannot interrupt the blocking AHK command that allowed this timer to execute. Thus, there's no way to raise KeyboardInterrupt during the AHKCall.
1 parent 678664f commit cf429f0

File tree

5 files changed

+57
-48
lines changed

5 files changed

+57
-48
lines changed

ahkpy/Python.ahk

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,14 @@ Main() {
109109
handleCtrlEventCB := RegisterCallback("HandleCtrlEvent", "Fast")
110110
DllCall("SetConsoleCtrlHandler", "Ptr", handleCtrlEventCB, "Int", true)
111111

112+
SetTimer, CheckSignals, 100
113+
112114
result := PyObject_CallObject(mainFunc, NULL)
113115
Py_DecRef(mainFunc)
114116
if (result == NULL) {
115117
PrintErrorOrExit()
116118
}
117119
Py_DecRef(result)
118-
119-
SetTimer, CheckSignals, 100
120120
}
121121

122122
PackBuiltinModule() {

ahkpy/clipboard.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import functools
33
from typing import Callable
44

5-
from .flow import ahk_call, _wait_for, _wrap_callback
5+
from .flow import ahk_call, _wrap_callback
66

77
__all__ = [
88
"ClipboardHandler",
@@ -36,13 +36,18 @@ def wait_clipboard(timeout: float = None) -> str:
3636
3737
If there is no text in the clipboard after *timeout* seconds, then an empty
3838
string will be returned. If *timeout* is not specified or ``None``, there is
39-
no limit to the wait time.
39+
no limit to the wait time. Specifying 0 is the same as specifying 0.5.
4040
4141
:command: `ClipWait
4242
<https://www.autohotkey.com/docs/commands/ClipWait.htm>`_
4343
"""
4444
# TODO: Implement WaitForAnyData argument.
45-
return _wait_for(timeout, get_clipboard) or ""
45+
if timeout is not None:
46+
timeout = float(timeout)
47+
ok = ahk_call("ClipWait", timeout)
48+
if not ok:
49+
return ""
50+
return get_clipboard()
4651

4752

4853
def on_clipboard_change(func: Callable = None, *args, prepend_handler=False):

ahkpy/flow.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import queue
55
import sys
66
import threading
7-
import time
87

98
import _ahk
109

@@ -55,31 +54,12 @@ def sleep(secs):
5554
"""
5655
if not isinstance(secs, (int, float)):
5756
raise TypeError(f"a number is required (got type {secs.__class__.__name__})")
58-
_wait_for(secs, None)
59-
60-
61-
def _wait_for(secs, check_fn):
62-
if secs is None:
63-
secs = float("inf")
64-
6557
if secs < 0:
6658
raise ValueError("sleep length must be non-negative")
67-
elif secs <= _poll_interval:
68-
time.sleep(secs)
69-
poll()
70-
return check_fn and check_fn()
59+
elif secs == 0:
60+
ahk_call("Sleep", 0)
7161
else:
72-
stop = time.perf_counter() + secs
73-
while time.perf_counter() < stop:
74-
time.sleep(_poll_interval)
75-
poll()
76-
result = check_fn and check_fn()
77-
if result:
78-
return result
79-
80-
81-
# The interval between AHK message queue polls during the blocking operations.
82-
_poll_interval = 0.01
62+
ahk_call("Sleep", int(secs * 1000))
8363

8464

8565
def poll():
@@ -150,6 +130,10 @@ def output_debug(*objects, sep=" "):
150130
ctypes.windll.kernel32.OutputDebugStringW(debug_str)
151131

152132

133+
# The interval between AHK message queue polls during the blocking operations.
134+
_poll_interval = 0.01
135+
136+
153137
def coop(func, *args, **kwargs):
154138
"""Run the given function in a new thread and make it cooperate with AHK's
155139
event loop.

ahkpy/key_state.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import functools
2-
3-
from .flow import ahk_call, _wait_for
1+
from .flow import ahk_call
42

53
__all__ = [
64
"get_caps_lock_state",
@@ -98,22 +96,34 @@ def wait_key_pressed(key_name, timeout: float = None) -> bool:
9896
after *timeout* seconds, then ``False`` will be returned. If *timeout* is
9997
not specified or ``None``, there is no limit to the wait time.
10098
"""
101-
return _wait_for(timeout, functools.partial(is_key_pressed, key_name)) or False
99+
return _key_wait(key_name, down=True, logical=False, timeout=timeout)
102100

103101

104102
def wait_key_released(key_name, timeout: float = None) -> bool:
105103
"""Wait for a key or mouse/joystick button to be released physically."""
106-
return _wait_for(timeout, lambda: not is_key_pressed(key_name)) or False
104+
return _key_wait(key_name, down=False, logical=False, timeout=timeout)
107105

108106

109107
def wait_key_pressed_logical(key_name, timeout: float = None) -> bool:
110108
"""Wait for a key or mouse/joystick button logical state to be pressed."""
111-
return _wait_for(timeout, functools.partial(is_key_pressed_logical, key_name)) or False
109+
return _key_wait(key_name, down=True, logical=True, timeout=timeout)
112110

113111

114112
def wait_key_released_logical(key_name, timeout: float = None) -> bool:
115113
"""Wait for a key or mouse/joystick button logical state to be released."""
116-
return _wait_for(timeout, lambda: not is_key_pressed_logical(key_name)) or False
114+
return _key_wait(key_name, down=False, logical=True, timeout=timeout)
115+
116+
117+
def _key_wait(key_name, down=False, logical=False, timeout=None) -> bool:
118+
options = []
119+
if down:
120+
options.append("D")
121+
if logical:
122+
options.append("L")
123+
if timeout is not None:
124+
options.append(f"T{timeout}")
125+
ok = ahk_call("KeyWait", str(key_name), "".join(options))
126+
return bool(ok)
117127

118128

119129
def get_key_name(key_name: str) -> str:

ahkpy/window.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from . import colors
1010
from . import sending
1111
from .exceptions import Error
12-
from .flow import ahk_call, global_ahk_lock, _wait_for
12+
from .flow import ahk_call, global_ahk_lock
1313
from .hotkey_context import HotkeyContext
1414
from .settings import get_settings, optional_ms
1515
from .unset import UNSET, UnsetType
@@ -280,7 +280,7 @@ def wait(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET,
280280
<https://www.autohotkey.com/docs/commands/WinWait.htm>`_
281281
"""
282282
self = self._filter(title, class_name, id, pid, exe, text, match)
283-
return _wait_for(timeout, self.exist) or Window(None)
283+
return self._wait("WinWait", timeout)
284284

285285
def wait_active(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None,
286286
timeout=None):
@@ -297,7 +297,7 @@ def wait_active(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe
297297
query = self._query()
298298
if query == ("", "", "", ""):
299299
self = dc.replace(self, title="A")
300-
return _wait_for(timeout, self.get_active) or Window(None)
300+
return self._wait("WinWaitActive", timeout)
301301

302302
def wait_inactive(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None,
303303
timeout=None) -> bool:
@@ -316,7 +316,7 @@ def wait_inactive(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, e
316316
<https://www.autohotkey.com/docs/commands/WinWaitActive.htm>`_
317317
"""
318318
self = self._filter(title, class_name, id, pid, exe, text, match)
319-
return _wait_for(timeout, lambda: not self.get_active()) or False
319+
return self._wait_close("WinWaitNotActive", timeout)
320320

321321
def wait_close(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None,
322322
timeout=None):
@@ -335,9 +335,22 @@ def wait_close(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=
335335
<https://www.autohotkey.com/docs/commands/WinWaitClose.htm>`_
336336
"""
337337
self = self._filter(title, class_name, id, pid, exe, text, match)
338-
# WinWaitClose doesn't set Last Found Window, return False if the wait
339-
# was timed out.
340-
return _wait_for(timeout, lambda: not self.exist()) or False
338+
return self._wait_close("WinWaitClose", timeout)
339+
340+
def _wait(self, cmd, timeout):
341+
win_id = self._call(cmd, *self._include(), timeout, *self._exclude(), set_delay=True)
342+
if not win_id:
343+
return Window(None)
344+
return Window(win_id)
345+
346+
def _wait_close(self, cmd, timeout):
347+
# WinWaitClose and WinWaitNotActive don't set the Last Found Window,
348+
# return False if the wait was timed out.
349+
ok = self._call(cmd, *self._include(), timeout, *self._exclude(), set_delay=True)
350+
if ok is None:
351+
# There are no matching windows, and that's what we are waiting for.
352+
return True
353+
return bool(ok)
341354

342355
def close_all(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None,
343356
timeout=None):
@@ -455,9 +468,9 @@ def _group_action(self, cmd, timeout=UNSET):
455468
query_hash_str = str(query_hash).replace("-", "m") # AHK doesn't allow "-" in group names
456469
label = ""
457470
self._call("GroupAdd", query_hash_str, *self._include(), label, *self._exclude())
458-
self._call(cmd, f"ahk_group {query_hash_str}", "", "", set_delay=True)
471+
self._call(cmd, f"ahk_group {query_hash_str}", "", timeout or "", set_delay=True)
459472
if timeout is not UNSET:
460-
return self.wait_close(timeout=timeout)
473+
return not self.exist()
461474

462475
def window_context(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
463476
"""window_context(title: str = UNSET, **criteria) -> ahkpy.HotkeyContext
@@ -1961,14 +1974,11 @@ def wait_status_bar(self, bar_text="", *,
19611974
:command: `StatusBarWait
19621975
<https://www.autohotkey.com/docs/commands/StatusBarWait.htm>`_
19631976
"""
1964-
# TODO: StatusBarWait is blocking and is not interruptable. However, it
1965-
# is usually more efficient to use StatusBarWait rather than calling
1966-
# StatusBarGetText in a loop.
19671977
try:
19681978
ok = self._call(
19691979
"StatusBarWait",
19701980
bar_text,
1971-
timeout,
1981+
timeout if timeout is not None else "",
19721982
part + 1,
19731983
*self._include(),
19741984
interval * 1000,

0 commit comments

Comments
 (0)