To clarify the title, this topic will look at creating short automations which run seamlessly in the background and do not interfere with the users input, nor are the automations themselves affected by user input.
These two points sound easy, but are quite difficult with the tool set provided in the beautiful language which is AHK.
This method revolves around using custom low level input hooks and exploiting AHK's single threaded nature.
The basic procedure looks like this:
Install low level (LL) keyboard and mouse hooks
Alter your LL hooks to only allow injected/synthetic keystrokes through.
Record and then Release physically pressed keys using SendInput
If a key was released, sleep for a short amount of time 15ms seems adequate
Place the thread in critical mode and increase AHks delay in checking its internal message queue (e.g. critical, 1000)
Reset your LL hooks so they allow user input
Perform your automations using postmessage
If required Restore physically pressed keys using postmessage (The ones we previously released)
Turn off critical
It's best to be release physically pressed keys using SendInput, as using postmessage can lead to keys being stuck (seen as down) outside of the program we are using. There are some situations where you do not want to actually restore the users pressed keys. For example if the user is typing in chat/text box and the 'a' key is currently down, releasing this key with postmessage (or sendinput) will produce the 'a' character which is good, but if we were to restore this key, then when the user physically releases the key a second 'a' character will be generated. Unfortunately, since we used postmessage to release the key everything but our program of interest will still see the 'a' as being down.
As AHK is single threaded, placing the automation thread into critical prevents any callbacks to the LL hooks (i.e. the user pressing or releasing keys) being processed until after we finish our automation and the thread either ends or comes out of critical.
The automation must be performed using postmessage, as any sendInput command will not be processed until after the thread comes out of critical.
Postmessage has a lower latency than sendinput, consequently a small sleep (see the note below about sleep) is required between using a sendinput command and then posting a message to a window. Without this delay some of the postmessage input may arrive before the actual sendinput!
Once you have activated critical, you can not use AHKs normal sleep command or any AHK command which has an internal sleep component. This is because a sleep causes AHK to check its internal message queue which will result in it immediately processing any calls to the LL hooks. In other words, this will allow user input to interrupt the automation! This also means that you can't use controlsend/controlclick with any keydelay.
If the automation requires a sleep, for example to allow some action to register in the window before deciding on how to proceed, then you can use
DllCall("Sleep", Uint, x)
where x is the time in milliseconds. This command will sleep the entire AHK program.
Unfortunately using controlsend without any key delay can result in the window ignoring some commands. A simple workaround would be to use a series of controlsend commands with a DLLcall sleep between them. Although sendcontrol is fast, it does have some overhead associated with it and in testing i found repeated controlsends took around 10x longer than a similar postmessage function. Although I have misplaced my testing data, if the automation string is quite long this delay can become quite substantial - at least compared to the overall time of the automation.
I will include some functions I wrote which can be used as alternatives to controlsend and controlclick. These functions are undoubtedly less robust than their counterparts (and may have a bug), but so far they have worked perfectly for my needs.
LowLevel HookTime outs:
Although this this is intended for fast automations say less than 50ms, it works perfectly fine for longer ones except for one issue. Windows monitors the time it takes for a program to process info it sends via the low level hook. If our automation takes longer than 300ms windows will automatically pass the information on to the next program in the hook chain which will result in the user input occuring before our automation finishes. If the hook times out 10 times (not sure about the exact number) then windows will permanently (and on vista onwards silently) remove our low level hook from the hook chain.
You can increase the hook time out interval using
RegWrite, REG_DWORD, HKEY_CURRENT_USER, Control Panel\Desktop, LowLevelHooksTimeout, x ; default = 300
where X is the time in milliseconds. This requires a restart to take effect.
is used to prevent any user keypress affecting the automation before the thread enters critical. As this is such a short timing window, this probably isn't necessary but it seems to work well. I've often thought I should also allow user key_Up messages through too, what do other think?
Lets look at a basic example. You will need to set the windows LowLevelHooksTimeout value to 4000 and restart your computer. This will allow plenty of time for you to to press buttons while the automation is occurring.
Open notepad and run the test script (you will need to include the functions at the end of this post). Press F1 and then when you hear the beep press any sequence of keys you want. Notice the keys you pressed will show up after the posted "abc123". You will also notice that the mouse cursor won't move while the thread is in critical, but once the thread finishes the mouse does indeed move the correct distance. This delay isn't noticeable for short automatons.
Note, the presence of low level hooks can impair sendInput. For example, I had a function bound to Alt+Tab which would often fail while the LLHooks were installed. Simply removing the hooks before the using sendInput fixed this. AHK even removes (and then re-installs) it's own LLHooks during a sendInput command.
I hope someone finds this useful. I've been using this for the last month or so in application where I'm (physically) performing over 200 actions per minute and haven't noticed any problems or conflicts with the automations.
Note: The functions which use post message do have a two naughty global variables GameIdentifier and classIdentifier - you have to set this to your window/control of of interest
The two commands you want to use for to post input are MTclick and pSend.