here are my(win10 x64 19041.572) observations based on the following(similar to OP's,
OutputDebugStr since im launching from VS) script:
Code: Select all
global cnt := 0
#If delay()
q::OutputDebug Received %cnt%`n
#If
*w::OutputDebug Still here %cnt%`n
Esc::Exitapp
delay() {
cnt++
sleep, 400
return true
}
Ctrl replaced with
q to see if keybd input gets through to a
notepad window
Alt replaced with
w for the same reason, and hardcoded(
*) hook-enabled hotkey since standalone modifiers are always implemented with a hook
- run the script(from VS or otherwise)
- open up a notepad
- focus notepad's Edit1
- now observe both notepad and the debug console
- press q and wait
- if u see q's print statement but a q doesnt appear in notepad: the hotkey was activated and correctly suppressed by ahk, that is to say, "the hotkey works". Goto step 6.
- if u see q's print statement and a q appears in notepad: the processing of the WH_KEYBOARD_LL hookproc took longer than Windows permits(apparently governed by LowLevelHooksTimeout, although theres no such entry on my pc. more on that later), so Windows aborted any further processing of that proc and let the buffered input through. by contrast, AHK allows #If-expressions to evaluate for up to 1000ms(default), it eventually returns true, so the hotkey label is executed, thus printing to the debug log. Goto step 6.
if u dont see q's print statement and a q appears in notepad: the hook had already been long since disabled and u didnt follow closely the instructions in step 6. that told u to stop, dumdum!
if u dont see q's print statement but a q doesnt appear in notepad: logic dictates this cant happen
- press w and wait
- if u see w's print statement but a w doesnt appear in notepad: the hotkey was activated and correctly suppressed by ahk, that is to say, "the hotkey works". Repeat from step 5.
if u see w's print statement and a w appears in notepad: if ure following the instructions stepwise, this cant happen
- if u dont see w's print statement and a w appears in notepad: the WH_KEYBOARD_LL hook wasnt active when u pressed w. Windows must have disabled it by that point, because
the docs copied some 10yo blog post wrote:On Windows 7 we have to make sure that the
callback function of the hook can return in
less than LowLevelHooksTimeout, which is
300 ms. And we allow for the application to be
timed out 10 times when processing the hook callback message. If it times out an
11th time, Windows will
unhook the application from the hook chain. This is a
by design feature and it was added in Win7 RTM.
Goto end, we're done here.
if u dont see q's print statement but a q doesnt appear in notepad: this cant happen
with ahk 1.1.33.02 U32(note that there was an update recently...ish to address probably similar hangup situations. see
viewtopic.php?f=14&t=62932):
- if sleep < 300ms: everything works, indefinitely, without breaking
- if sleep ~ 300ms: occasionally a q will slip through, which means Windows aborted the hookproc's processing. after 11 q's have slipped through, Windows disables the hook altogether. Suspend/Unsuspend does nothing to reenable it.
- if sleep > 350ms: every time u press q a q will slip through, which means Windows aborted the hookproc's processing. after 6 q's have slipped through, Windows disables the hook altogether. Suspend/Unsuspend does nothing to reenable it.
with ahk 1.1.33.02 U64:
- if sleep < 300ms: everything works, indefinitely, without breaking
- if sleep ~ 300ms: occasionally a q will slip through, which means Windows aborted the hookproc's processing. However, Windows never disables the hook
- if sleep > 350ms: every time u press q a q will slip through, which means Windows aborted the hookproc's processing. However, Windows never disables the hook
- if sleep > 1000ms(which requires raising AHK's default #IfTimeout):
since a microsoft(?) guy wrote:There was an intentional change made here to limit the minimum value for a
LowLevelHooksTimeout to
1 second starting in RS3 (RedStone 3) [ie 2017 Fall update]. The documentation has not yet been updated to reflect this change.
every time u press q a q will slip through, which means Windows aborted the hookproc's processing. However, Windows never disables the hook
what can we glean from all this?
- the latest version of x64 windows10(mine) kicks the hooks of 32bit applications that install them globally when they take around 300ms to complete after 11 timeouts.
a stricter heuristic is employed if it takes them longer than that to complete(eg longer than 350ms -> kicked after 6 timeouts).
it could be the case that the number of consecutive timeouts also contributes to the likelihood of the hook getting kicked, but that would take a microsoft employee to confirm
- for 64bit applications that install global hooks, Windows will abort any further processing of the hookprocs after about 300ms but it wont kick the hooks themselves ever
why doesnt Suspend/Unsuspend(on 32bit) do anything after the hook has been kicked?
- the hook handle is stored in the global variable g_KeybdHook
- the HookThreadProc, which is launched from the main thread to run in a separate thread, idles in a blocking call to GetMessage() until a message is posted to its message queue from the main thread
- the AddRemoveHooks() function is called in response to (among other things) Suspend/Unsuspend. it has a guard in the beginning. the way GetActiveHooks() determines whether hooks are active or not, is by checking whether the global variables contain any hook handles(ie whether they have nonzero values). however, even if they do have values, this doesnt mean that the hook handles are actually valid, as will become apparent soon enough.
- to change a hook's state and signal to AHK that the hook is no longer active, ud have to zero the global variable. the only place where this is attempted is in the hook thread's message pump(HookThreadProc) and only after AHK has successfully managed to unhook the hook!
- now suppose ur script is running, HookThreadProc idles on GetMessage(), g_KeybdHook has contains a handle. u intentionally cause a bunch of timeouts until Windows disables the hook(by what mechanism it does that is not clear but it probably calls UnhookWindowsHookEx() on the handle)
- u Suspend(prompting a removal of hooks, aHooksToBeActive = 0)
- a check is made to see if its necessary to actually do anything(it is, since GetActiveHooks() returned 1 because g_KeybdHook has a value)
- the main thread posts a message to the hook thread telling it to unhook itself
- the hook thread's GetMessage() unblocks and processes the message that tells it to unhook the keybdhook
- the hook thread tries to unhook the keybdhook. However, it fails because Windows had already disabled the hook due to the timeouts, thus invalidating the hook handle that AHK had stored(last error 1404 ERROR_INVALID_HOOK_HANDLE, but this is not checked by AHK). now g_KeybdHook doesnt get reset. the hook thread goes back to idling on GetMessage()
- u UnSuspend(prompting the installation of a keybdhook since u have hotkeys which require it, aHooksToBeActive = 1)
- a check is made to see if its necessary to actually do anything(it is NOT, since GetActiveHooks() returned 1 because g_KeybdHook still contains an invalidated hook handle because it could never get reset). the function returns without having done anything, so no hooks get reenabled(AHK assumed they already were)
what can be done to fix this?
i dont know. handling
ERROR_INVALID_HOOK_HANDLE when unhooking is probably a good start. or changing something else but there might be other hidden interactions between other components im not aware of, so i cant suggest anything meaningful. only
@Lexikos can say
why do my hotkeys break when ive made sure their context-sensitive expressions finish fast?
other code might be preventing them from executing in a timely manner(eg calling into slow external code, slowmode
PixelSearch, long running operations and blocking calls(files, network, etc)). consider this script
Code: Select all
global cnt := 0
PixelSearch, , , 0, 0, 40, 40, 123456
OutputDebug PixelSearch completed`n
#If delay()
q::OutputDebug Received %cnt%`n
#If
*w::OutputDebug Still here %cnt%`n
Esc::Exitapp
delay() {
cnt++
; sleep, 400
return true
}
here slowmode PixelSearch will do its thing for about 10sec(on my pc). if during those 10sec, u spam
q enough times to trigger enough timeouts, Windows will still disable the hook, despite the fact that the
#If-condition would have been evaluated almost instantly(under normal circumstances)
e 9.2.2023: fix github links