InputHook [v1.1.31+]

Creates an object which can be used to collect or intercept keyboard input.

InputHook := InputHook(Options, EndKeys, MatchList)

Parameters

Options

A string of zero or more of the following letters (in any order, with optional spaces in between):

B: Sets BackspaceIsUndo to false, which causes Backspace to be ignored.

C: Sets CaseSensitive to true, making MatchList case sensitive.

I: Sets MinSendLevel to 1 or a given value, causing any input with send level below this value to be ignored. For example, I2 would ignore any input with a level of 0 (the default) or 1, but would capture input at level 2.

L: Length limit (e.g. L5). The maximum allowed length of the input. When the text reaches this length, the Input is terminated and EndReason is set to the word Max (unless the text matches one of the MatchList phrases, in which case EndReason is set to the word Match). If unspecified, the length limit is 16383.

Specifying L0 disables collection of text and the length limit, but does not affect which keys are counted as producing text (see VisibleText). This can be useful in combination with OnChar, OnKeyDown, KeyOpt or EndKeys.

M: Modified keystrokes such as Control+A through Control+Z are recognized and transcribed if they correspond to real ASCII characters. Consider this example, which recognizes Control+C:

CtrlC := Chr(3) ; Store the character for Ctrl-C in the CtrlC var.
Input, OutputVar, L1 M
if (OutputVar = CtrlC)
    MsgBox, You pressed Control-C.
ExitApp

Note: The characters Ctrl+A through Ctrl+Z correspond to Chr(1) through Chr(26). Also, the M option might cause some keyboard shortcuts such as Ctrl+ to misbehave while an Input is in progress.

T: Sets Timeout (e.g. T3 or T2.5).

V: Sets VisibleText and VisibleNonText to true. Normally, the user's input is blocked (hidden from the system). Use this option to have the user's keystrokes sent to the active window.

*: Wildcard. Sets FindAnywhere to true, allowing matches to be found anywhere within what the user types.

E: Handle single-character end keys by character code instead of by keycode. This provides more consistent results if the active window's keyboard layout is different to the script's keyboard layout. It also prevents key combinations which don't actually produce the given end characters from ending input; for example, if @ is an end key, on the US layout Shift+2 will trigger it but Ctrl+Shift+2 will not (if the E option is used). If the C option is also used, the end character is case-sensitive.

EndKeys

A list of zero or more keys, any one of which terminates the Input when pressed (the end key itself is not written to the Input buffer). When an Input is terminated this way, EndReason is set to the word EndKey and the EndKey property is set to the name of the key.

The EndKeys list uses a format similar to the Send command. For example, specifying {Enter}.{Esc} would cause either Enter, ., or Escape to terminate the Input. To use the braces themselves as end keys, specify {{} and/or {}}.

To use Control, Alt, or Shift as end-keys, specify the left and/or right version of the key, not the neutral version. For example, specify {LControl}{RControl} rather than {Control}.

Although modified keys such as Alt+C (!c) are not supported, non-alphanumeric characters such as ?!:@&{} by default require the Shift key to be pressed or not pressed depending on how the character is normally typed. If the E option is present, single character key names are interpreted as characters instead, and in those cases the modifier keys must be in the correct state to produce that character. When the E and M options are both used, Control+A through Control+Z are supported by including the corresponding ASCII control characters in EndKeys.

An explicit key code such as {vkFF} or {sc001} may also be specified. This is useful in the rare case where a key has no name and produces no visible character when pressed. Its virtual key code can be determined by following the steps at the bottom fo the key list page.

MatchList

A comma-separated list of key phrases, any of which will cause the Input to be terminated (in which case EndReason will be set to the word Match). The entirety of what the user types must exactly match one of the phrases for a match to occur (unless the * option is present). In addition, any spaces or tabs around the delimiting commas are significant, meaning that they are part of the match string. For example, if MatchList is ABC , XYZ, the user must type a space after ABC or before XYZ to cause a match.

Two consecutive commas results in a single literal comma. For example, the following would produce a single literal comma at the end of string: string1,,,string2. Similarly, the following list contains only a single item with a literal comma inside it: single,,item.

Because the items in MatchList are not treated as individual parameters, the list can be contained entirely within a variable. In fact, all or part of it must be contained in a variable if its length exceeds 16383 since that is the maximum length of any script line. For example, MatchList might consist of %List1%,%List2%,%List3% -- where each of the variables contains a large sub-list of match phrases.

Input Stack

Any number of InputHook objects can be created and in progress at any time, but the order in which they are started affects how input is collected.

When each Input is started (by the Start method or Input command), it is pushed onto the top of a stack, and is removed from this stack only when the Input is terminated. Keyboard events are passed to each Input in order of most recently started to least. If an Input suppresses a given keyboard event, it is passed no further down the stack.

Sent keystrokes are ignored if the send level of the keystroke is below the InputHook's MinSendLevel. In such cases, the keystroke may still be processed by an Input lower on the stack.

Multiple InputHooks can be used in combination with MinSendLevel to separately collect both sent keystrokes and real ones.

Calling the Input command terminates any previous Input started by the Input command, but leaves any InputHooks active. If the Input is not visible, any InputHooks which it interrupts will generally not collect any input until the Input command returns.

InputHook Object

The InputHook function returns an InputHook object, which has the following methods and properties.

Methods:

General Properties:

Option Properties:

KeyOpt

Sets options for a key or list of keys.

InputHook.KeyOpt(Keys, KeyOptions)
Keys

A list of keys. Braces are used to enclose key names, virtual key codes or scan codes, similar to the Send command. For example, {Enter}.{{} would apply to the Enter, . and { keys. Specifying a key by name, by {vkNN} or by {scNNN} may produce three different results; see below for details.

Specify the string {All} (case-insensitive) on its own to apply KeyOptions to all VK and all SC. KeyOpt may then be called a second time to remove options from specific keys.

KeyOptions

One or more of the following single-character options (spaces and tabs are ignored).

- (minus): Removes any of the options following the -, up to the next +.

+ (plus): Cancels any previous -, otherwise has no effect.

E: End key. If enabled, pressing the key terminates Input, sets EndReason to the word EndKey and the EndKey property to the key's normalized name. Unlike the EndKeys parameter, the state of the Shift key is ignored. For example, @ and 2 are both equivalent to {vk32} on the US keyboard layout.

I: Ignore text. Any text normally produced by this key is ignored, and the key is treated as a non-text key (see VisibleNonText). Has no effect if the key normally does not produce text.

N: Notify. Causes the OnKeyDown callback to be called each time the key is pressed.

S: Suppresses (blocks) the key after processing it. This overrides VisibleText or VisibleNonText until -S is used. +S implies -V.

V: Visible. Prevents the key from being suppressed (blocked). This overrides VisibleText or VisibleNonText until -V is used. +V implies -S.

Options can be set by both virtual key code and scan code, and are accumulative.

When a key is specified by name, the options are set either by VK or by SC. Where two physical keys share the same VK but differ by SC (such as Up and NumpadUp), they are handled by SC. By contrast, if a VK number is used, it will apply to any physical key which produces that VK (and this may vary over time as it depends on the active keyboard layout).

Removing an option by VK number does not affect any options that were set by SC, or vice versa. However, when an option is removed by key name and that name is handled by VK, the option is also removed for the corresponding SC (according to the script's keyboard layout). This allows keys to be excluded by name after applying an option to all keys.

If +V is set by VK and +S is set by SC (or vice versa), +V takes precedence.

Start

Starts collecting input.

InputHook.Start()

Has no effect if the Input is already in progress.

The newly started Input is placed on the top of the InputHook stack, which allows it to override any previously started Input.

This method installs the keyboard hook (if it was not already).

Wait

Waits until the Input is terminated (InProgress is false).

InputHook.Wait(MaxTime)
MaxTime

The maximum number of seconds to wait. If Input is still in progress after MaxTime seconds, the method returns and does not terminate Input.

Returns EndReason.

Stop

Terminates the Input and sets EndReason to the word Stopped.

InputHook.Stop()

Has no effect if the Input is not in progress.

EndKey

Returns the name of the end key which was pressed to terminate the Input.

KeyName := InputHook.EndKey

Note that EndKey returns the "normalized" name of the key regardless of how it was written in EndKeys. For example, {Esc} and {vk1B} both produce Escape. GetKeyName() can be used to retrieve the normalized name.

If the E option was used, EndKey returns the actual character which was typed (if applicable). Otherwise, the key name is determined according to the script's active keyboard layout.

EndKey returns an empty string if EndReason is not "EndKey".

EndMods

Returns a string of the modifiers which were logically down when Input was terminated.

Mods := InputHook.EndMods

If all modifiers were logically down (pressed), the full string is:

<^>^<!>!<+>+<#>#

These modifiers have the same meaning as with hotkeys. Each modifier is always qualified with < (left) or > (right). The corresponding key names are: LCtrl, RCtrl, LAlt, RAlt, LShift, RShift, LWin, RWin.

InStr can be used to check whether a given modifier (such as >! or ^) is present. The following line can be used to convert Mods to a string of neutral modifiers, such as ^!+#:

Mods := RegExReplace(Mods, "[<>](.)(?:>\1)?", "$1")

Due to split-second timing, this property may be more reliable than GetKeyState even if it is used immediately after Input terminates, or in the OnEnd callback.

EndReason

Returns an EndReason string indicating how Input was terminated.

Reason := InputHook.EndReason

Returns an empty string if the Input is still in progress.

InProgress

Returns true if the Input is in progress and false otherwise.

Boolean := InputHook.InProgress

Input

Returns any text collected since the last time Input was started.

String := InputHook.Input

This property can be used while the Input is in progress, or after it has ended.

Match

Returns the MatchList item which caused the Input to terminate.

String := InputHook.Match

Returns the matched item with its original case, which may differ from what the user typed if the C option was omitted. Returns an empty string if EndReason is not "Match".

OnEnd

Retrieves or sets the function object which is called when Input is terminated.

MyFunc := InputHook.OnEnd
InputHook.OnEnd := MyFunc

Type: function object or empty string. Default: empty string.

The function is passed one parameter: a reference to the InputHook object.

The function is called as a new thread, so starts off fresh with the default values for settings such as SendMode and DetectHiddenWindows.

OnChar

Retrieves or sets the function object which is called after a character is added to the input buffer.

MyFunc := InputHook.OnChar
InputHook.OnChar := MyFunc

Type: function object or empty string. Default: empty string.

The function is passed the following parameters: InputHook, Char. Char is a string containing the character or characters.

The presence of multiple characters indicates that a dead key was used prior to the last keypress, but the two keys could not be transliterated to a single character. For example, on some keyboard layouts `e produces รจ while `z produces `z.

The function is never called when an end key is pressed.

OnKeyDown

Retrieves or sets the function object which is called when a notification-enabled key is pressed.

MyFunc := InputHook.OnKeyDown
InputHook.OnKeyDown := MyFunc

Type: function object or empty string. Default: empty string.

Key-down notifications must first be enabled by KeyOpt or NotifyNonText.

The function is passed the following parameters: InputHook, VK, SC. VK and SC are integers. To retrieve the key name (if any), use GetKeyName(Format("vk{:x}sc{:x}", VK, SC)).

The function is called as a new thread, so starts off fresh with the default values for settings such as SendMode and DetectHiddenWindows.

The function is never called when an end key is pressed.

BackspaceIsUndo

Controls whether Backspace removes the most recently pressed character from the end of the Input buffer.

Boolean := InputHook.BackspaceIsUndo
InputHook.BackspaceIsUndo := Boolean

Type: Integer (boolean). Default: true. Option B sets the value to false.

When Backspace acts as undo, it is treated as a text entry key. Specifically, whether the key is suppressed depends on VisibleText rather than VisibleNonText.

Backspace is always ignored if pressed in combination with a modifier key such as Ctrl (the logical modifier state is checked rather than the physical state).

Note: If the input text is visible (such as in an editor) and the arrow keys or other means are used to navigate within it, Backspace will still remove the last character rather than the one behind the caret (insertion point).

CaseSensitive

Controls whether MatchList is case sensitive.

Boolean := InputHook.CaseSensitive
InputHook.CaseSensitive := Boolean

Type: Integer (boolean). Default: false. Option C sets the value to true.

FindAnywhere

Controls whether each match can be a substring of the input text.

Boolean := InputHook.FindAnywhere
InputHook.FindAnywhere := Boolean

Type: Integer (boolean). Default: false. Option * sets the value to true.

If true, a match can be found anywhere within what the user types (the match can be a substring of the input text). If false, the entirety of what the user types must match one of the MatchList phrases. In both cases, one of the MatchList phrases must be typed in full.

MinSendLevel

Retrieves or sets the minimum send level of input to collect.

Level := InputHook.MinSendLevel
InputHook.MinSendLevel := Level

Type: Integer. Default: 0. Option I sets the value to 1 (or a given value).

Level should be an integer between 0 and 101. Events which have a send level lower than this value are ignored. For example, a value of 101 causes all input generated by SendEvent to be ignored, while a value of 1 only ignores input at the default send level (zero).

The SendInput and SendPlay methods are always ignored, regardless of this setting. Input generated by any source other than AutoHotkey is never ignored as a result of this setting.

NotifyNonText

Controls whether the OnKeyDown callback is called whenever a non-text key is pressed.

Boolean := InputHook.NotifyNonText
InputHook.NotifyNonText := Boolean

Type: Integer (boolean). Default: false.

Setting this to true enables notifications for all keypresses which do not produce text, such as when pressing Left or Alt+F. Setting this property does not affect a key's options, since the production of text depends on the active window's keyboard layout at the time the key is pressed.

See VisibleText for details about which keys are counted as producing text.

Timeout

Retrieves or sets the timeout value in seconds.

Seconds := InputHook.Timeout
InputHook.Timeout := Seconds

Type: Float. Default: 0.0 (none). Option T also sets the timeout value.

The timeout period ordinarily starts when Start is called, but will restart if this property is assigned a value while Input is in progress. If Input is still in progress when the timeout period elapses, it is terminated and EndReason is set to the word Timeout.

VisibleNonText

Controls whether keys or key combinations which do not produce text are visible (not blocked).

Boolean := InputHook.VisibleNonText
InputHook.VisibleNonText := Boolean

Type: Integer (boolean). Default: true. Option V sets the value to true.

If true, keys and key combinations which do not produce text may trigger hotkeys or be passed on to the active window. If false, they are blocked.

See VisibleText for details about which keys are counted as producing text.

VisibleText

Controls whether keys or key combinations which produce text are visible (not blocked).

Boolean := InputHook.VisibleText
InputHook.VisibleText := Boolean

Type: Integer (boolean). Default: false. Option V sets the value to true.

If true, keys and key combinations which produce text may trigger hotkeys or be passed on to the active window. If false, they are blocked.

Any keystrokes which cause text to be appended to the Input buffer are counted as producing text, even if they do not normally do so in other applications. For instance, Ctrl+A produces text if the M option is used, and Escape produces the control character Chr(27).

Dead keys are counted as producing text, although they do not typically produce an immediate effect. Pressing a dead key might also cause the following key to produce text (if only the dead key's character).

Backspace is counted as producing text only when it acts as undo.

The standard modifier keys and CapsLock, NumLock and ScrollLock are always visible (not blocked).

EndReason

The EndReason property returns one of the following strings:

Stopped The Stop method was called or Start has not yet been called for the first time.
Max The Input reached the maximum allowed length and it does not match any of the items in MatchList.
Timeout The Input timed out.
Match The Input matches one of the items in MatchList. The Match property contains the matched item.
EndKey

One of the EndKeys was pressed to terminate the Input. The EndKey property contains the terminating key name or character without braces.

If the Input is in progress, EndReason is blank.

Remarks

The Start method must be called before input will be collected.

InputHook is designed to allow different parts of the script to monitor input, with minimal conflicts. It can operate continuously, such as to watch for arbitrary words or other patterns. It can also operate temporarily, such as to collect user input or temporarily override specific (or non-specific) keys without interfering with hotkeys.

Keyboard hotkeys are still in effect while an Input is in progress, but cannot be triggered by keys which the Input suppresses.

Keys are either suppressed (blocked) or not depending on the following factors (in order):

The keyboard hook is required while an Input is in progress, but will be uninstalled automatically if it is no longer needed when the Input is terminated. The presence of the keyboard hook causes the script to become temporarily persistent, meaning that ExitApp may be needed to terminate it.

AutoHotkey does not support Input Method Editors (IME). The keyboard hook intercepts keyboard events and translates them to text by using ToUnicodeEx or ToAsciiEx (except in the case of VK_PACKET events, which encapsulate a single character).

If you use multiple languages or keyboard layouts, Input uses the keyboard layout of the active window rather than the script's (regardless of whether the Input is visible).

Although not as flexible, hotstrings are generally easier to use.

InputHook vs. Input

InputHook and the Input command are two different interfaces for the same underlying functionality. The following are mostly equivalent:

Input, OutputVar, %Options%, %EndKeys%, %MatchList%
ih := InputHook(Options, EndKeys, MatchList)
ih.Start()
ErrorLevel := ih.Wait()
if (ErrorLevel = "EndKey")
    ErrorLevel .= ":" ih.EndKey
OutputVar := ih.Input

The Input command terminates any previous Input which it started, whereas InputHook allows more than one Input at a time.

Options is interpreted the same, but the default settings differ:

The Input command blocks the thread while it is in progress, whereas InputHook allows the thread to continue, or even exit (which allows any thread that it interrupted to resume). Instead of waiting, the script can register an OnEnd function to be called when the Input is terminated.

The Input command returns the user's input only after the Input is terminated, whereas InputHook's Input property allows it to be retrieved at any time. The script can register an OnChar function to be called whenever a character is added, instead of continuously checking the Input property.

InputHook gives much more control over individual keys via the KeyOpt method. This includes adding or removing end keys, suppressing or not suppressing specific keys, or ignoring the text produced by specific keys.

Unlike the Input command, InputHook can be used to detect keys which do not produce text, without terminating the Input. This is done by registering an OnKeyDown function and using KeyOpt or NotifyNonText to specify which keys are of interest.

If a MatchList item caused the Input to terminate, the Match property can be consulted to determine exactly which match (this is more useful when the * option is present).

Although the script can consult GetKeyState after the Input command returns, sometimes it does not accurately reflect which keys were pressed when the Input was terminated. InputHook's EndMods property reflects the logical state of the modifier keys at the time Input was terminated.

There are some differences relating to backward-compatibility:

Related

Input, KeyWait, Hotstrings, InputBox, #InstallKeybdHook, Threads, if var in/contains MatchList

Examples

; Wait for the user to press any single key.
MsgBox % KeyWaitAny()

; Same again, but don't block the key.
MsgBox % KeyWaitAny("V")

KeyWaitAny(Options:="")
{
    ih := InputHook(Options)
    ih.KeyOpt("{All}", "ES")  ; End and Suppress
    ih.Start()
    ErrorLevel := ih.Wait()  ; Store EndReason in ErrorLevel
    return ih.EndKey  ; Return the key name
}
; Wait for any key in combination with Ctrl/Alt/Shift/Win.
MsgBox % KeyWaitCombo()

KeyWaitCombo(Option:="")
{
    ih := InputHook(Options)
    ih.KeyOpt("{All}", "ES")  ; End and Suppress
    ; Exclude the modifiers
    ih.KeyOpt("{LCtrl}{RCtrl}{LAlt}{RAlt}{LShift}{RShift}{LWin}{RWin}", "-ES")
    ih.Start()
    ErrorLevel := ih.Wait()  ; Store EndReason in ErrorLevel
    return ih.EndMods . ih.EndKey  ; Return a string like <^<+Esc
}
; Simple auto-complete... any day of the week!
; Pun aside, this is a fully functional example.
; Simply run the script and start typing today,
; press Tab to complete or press Escape to exit.
global WordList := "Monday`nTuesday`nWednesday`nThursday`nFriday`nSaturday`nSunday"

global Suffix := "", SacHook

SacHook := InputHook("V", "{Esc}")
SacHook.OnChar := Func("SacChar")
SacHook.OnKeyDown := Func("SacKeyDown")
SacHook.OnEnd := Func("SacEnd")
SacHook.KeyOpt("{Backspace}", "N")
SacHook.Start()

SacChar(ih, char)  ; Called when a character is added to SacHook.Input.
{
    Suffix := ""
    if RegExMatch(ih.Input, "`nm)\w+$", prefix)
        RegExMatch(WordList, "`nmi)^" prefix "\K.*", Suffix)
    
    ToolTip % Suffix, % A_CaretX + 15, % A_CaretY    
    
    ; Intercept Tab only while we're showing a Tooltip.
    ih.KeyOpt("{Tab}", Suffix = "" ? "-NS" : "+NS")
}

SacKeyDown(ih, vk, sc)
{
    if (vk = 8) ; Backspace
        SacChar(ih, "")
    else if (vk = 9) ; Tab
        Send % "{Text}" Suffix
}

SacEnd()
{
    ExitApp
}