InputHook

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

InputHookObj := InputHook(Options, EndKeys, MatchList)

Parameters

Options

Type: String

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

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

C: Sets CaseSensitive to 1 (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 1023.

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 the EndKeys parameter.

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

CtrlC := Chr(3) ; Store the character for Ctrl-C in the CtrlC var.
ih := InputHook("L1 M")
ih.Start()
ih.Wait()
if (ih.Input = CtrlC)
    MsgBox "You pressed Control-C."

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 1 (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 1 (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

Type: String

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 EndKey is set to the name of the key.

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

To use Ctrl, 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 Shift or AltGr 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 both the E option and M option are used, Ctrl+A through Ctrl+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 key code can be determined by following the steps at the bottom of the key list page.

MatchList

Type: String

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. 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), 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.

InputHook Object

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

"InputHookObj" is used below as a placeholder for any InputHook object, as "InputHook" is the class itself.

Methods

KeyOpt

Sets options for a key or list of keys.

InputHookObj.KeyOpt(Keys, KeyOptions)

Parameters

Keys

Type: String

A list of keys. Braces are used to enclose key names, virtual key codes or scan codes, similar to the Send function. For example, {Enter}.{{} would apply to Enter, . and {. 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, including {vkE7} and {sc000} as described below. KeyOpt may then be called a second time to remove options from specific keys.

Specify {sc000} to apply KeyOptions to all events which lack a scan code.

Specify {vkE7} to apply KeyOptions to Unicode events, such as those sent by SendEvent "{U+221e}" or SendEvent "{Text}∞".

KeyOptions

Type: String

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 EndKey to the key's normalized name. Unlike the EndKeys parameter, the state of Shift or AltGr 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 and OnKeyUp callbacks 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.

Remarks

Options can be set by both virtual key code (VK) and scan code (SC), and are accumulative.

When a key is specified by name, options are added 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.

To prevent an option from affecting a key, the option must be removed from both the VK and the SC of that key, or sc000 if the key has no SC.

Unicode events, such as those sent by SendEvent "{U+221e}" or SendEvent "{Text}∞", are affected by options which have been set for either vkE7 or sc000. Any option applied to {All} is applied to both vkE7 and sc000, so to exclude Unicode events, remove the option from both. For example:

InputHookObj.KeyOpt("{All}", "+I")  ; Ignore text produced by any event
InputHookObj.KeyOpt("{vkE7}{sc000}", "-I")  ; except Unicode events.

Start

Starts collecting input.

InputHookObj.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).

Stop

Terminates the Input and sets EndReason to the word Stopped.

InputHookObj.Stop()

Has no effect if the Input is not in progress.

Wait

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

EndReason := InputHookObj.Wait(MaxTime)

Parameters

MaxTime

Type: Float

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

Return Value

Type: String

This method returns EndReason.

General Properties

EndKey

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

KeyName := InputHookObj.EndKey

Note that EndKey returns the "normalized" name of the key regardless of how it was written in the EndKeys parameter. 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 := InputHookObj.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 := InputHookObj.EndReason

If the Input is still in progress, an empty string is returned.

InProgress

Returns 1 (true) if the Input is in progress, otherwise 0 (false).

Boolean := InputHookObj.InProgress

Input

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

String := InputHookObj.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 := InputHookObj.Match

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

OnEnd

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

MyCallback := InputHookObj.OnEnd
InputHookObj.OnEnd := MyCallback

MyCallback is the function object to call. An empty string means no function object.

The callback accepts one parameter and can be defined as follows:

MyCallback(InputHookObj) { ...

Although the name you give the parameter does not matter, it is assigned a reference to the InputHook object.

You can omit the callback's parameter if the corresponding information is not needed, but in this case an asterisk must be specified, e.g. MyCallback(*).

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.

MyCallback := InputHookObj.OnChar
InputHookObj.OnChar := MyCallback

MyCallback is the function object to call. An empty string means no function object.

The callback accepts two parameters and can be defined as follows:

MyCallback(InputHookObj, Char) { ...

Although the names you give the parameters do not matter, the following values are sequentially assigned to them:

  1. A reference to the InputHook object.
  2. A string containing the character (or multiple characters, see below for details).

You can omit one or more parameters from the end of the callback's parameter list if the corresponding information is not needed, but in this case an asterisk must be specified as the final parameter, e.g. MyCallback(Param1, *).

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.

MyCallback := InputHookObj.OnKeyDown
InputHookObj.OnKeyDown := MyCallback

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

MyCallback is the function object to call. An empty string means no function object.

The callback accepts three parameters and can be defined as follows:

MyCallback(InputHookObj, VK, SC) { ...

Although the names you give the parameters do not matter, the following values are sequentially assigned to them:

  1. A reference to the InputHook object.
  2. An integer representing the virtual key code of the key.
  3. An integer representing the scan code of the key.

You can omit one or more parameters from the end of the callback's parameter list if the corresponding information is not needed, but in this case an asterisk must be specified as the final parameter, e.g. MyCallback(Param1, *).

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.

OnKeyUp

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

MyCallback := InputHookObj.OnKeyUp
InputHookObj.OnKeyUp := MyCallback

Key-up notifications must first be enabled by KeyOpt or NotifyNonText. Whether a key is considered text or non-text is determined when the key is pressed. If an InputHook detects a key-up without having detected key-down, it is considered non-text.

MyCallback is the function object to call. An empty string means no function object.

The callback accepts three parameters and can be defined as follows:

MyCallback(InputHookObj, VK, SC) { ...

Although the names you give the parameters do not matter, the following values are sequentially assigned to them:

  1. A reference to the InputHook object.
  2. An integer representing the virtual key code of the key.
  3. An integer representing the scan code of the key.

You can omit one or more parameters from the end of the callback's parameter list if the corresponding information is not needed, but in this case an asterisk must be specified as the final parameter, e.g. MyCallback(Param1, *).

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.

Option Properties

BackspaceIsUndo

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

CurrentSetting := InputHookObj.BackspaceIsUndo
InputHookObj.BackspaceIsUndo := NewSetting

CurrentSetting is NewSetting if assigned, otherwise 1 (true) by default unless overwritten by the B option.

NewSetting is a boolean value that enables or disables this setting.

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.

CurrentSetting := InputHookObj.CaseSensitive
InputHookObj.CaseSensitive := NewSetting

CurrentSetting is NewSetting if assigned, otherwise 0 (false) by default unless overwritten by the C option.

NewSetting is a boolean value that enables or disables this setting.

FindAnywhere

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

CurrentSetting := InputHookObj.FindAnywhere
InputHookObj.FindAnywhere := NewSetting

CurrentSetting is NewSetting if assigned, otherwise 0 (false) by default unless overwritten by the * option.

NewSetting is a boolean value that enables or disables this setting. 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.

CurrentLevel := InputHookObj.MinSendLevel
InputHookObj.MinSendLevel := NewLevel

CurrentLevel is NewLevel if assigned, otherwise 0 by default unless overwritten by the I option.

NewLevel 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 and OnKeyUp callbacks are called whenever a non-text key is pressed.

CurrentSetting := InputHookObj.NotifyNonText
InputHookObj.NotifyNonText := NewSetting

CurrentSetting is NewSetting if assigned, otherwise 0 (false) by default.

NewSetting is a boolean value that enables or disables this setting. If true, notifications are enabled for all keypresses which do not produce text, such as when pressing 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.

NotifyNonText is applied to key-up events by considering whether a previous key-down with a matching VK code was classified as text or non-text. For example, if NotifyNonText is true, pressing Ctrl+A will produce OnKeyDown and OnKeyUp calls for both Ctrl and A, while pressing A on its own will not call OnKeyDown or OnKeyUp unless KeyOpt has been used to enable notifications for that key.

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

Timeout

Retrieves or sets the timeout value in seconds.

CurrentSeconds := InputHookObj.Timeout
InputHookObj.Timeout := NewSeconds

CurrentSeconds is NewSeconds if assigned, otherwise 0 by default unless overwritten by the T option.

NewSeconds is a floating-point number representing the timeout. 0 means no timeout.

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).

CurrentSetting := InputHookObj.VisibleNonText
InputHookObj.VisibleNonText := NewSetting

CurrentSetting is NewSetting if assigned, otherwise 1 (true) by default. The V option sets this to 1 (true).

NewSetting is a boolean value that enables or disables this setting. 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).

CurrentSetting := InputHookObj.VisibleText
InputHookObj.VisibleText := NewSetting

CurrentSetting is NewSetting if assigned, otherwise 0 (false) by default unless overwritten by the V option.

NewSetting is a boolean value that enables or disables this setting. 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 Esc 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:

String Description
Stopped The Stop method was called or the Start method 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 activate if any of the required modifier keys are suppressed, or if the hotkey uses the reg method and its suffix key is suppressed. For example, the hotkey ^+a:: might be overridden by InputHook, whereas the hotkey $^+a:: would take priority unless the InputHook suppressed Ctrl or Shift.

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 script is automatically persistent while an Input is in progress, so it will continue monitoring input even if there are no running threads. The script may exit automatically when input ends (if there are no running threads and the script is not persistent for some other reason).

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, InputHook 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 (v1)

In AutoHotkey v1.1, InputHook is a replacement for the Input command, offering greater flexbility. The Input command was removed for v2.0, but the code below is mostly equivalent:

; Input OutputVar, % Options, % EndKeys, % MatchList  ; v1
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:

KeyWait, Hotstrings, InputBox, InstallKeybdHook, Threads

Examples

Waits 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)
    if !InStr(Options, "V")
        ih.VisibleNonText := false
    ih.KeyOpt("{All}", "E")  ; End
    ih.Start()
    ih.Wait()
    return ih.EndKey  ; Return the key name
}

Waits for any key in combination with Ctrl/Alt/Shift/Win.

MsgBox KeyWaitCombo()

KeyWaitCombo(Options:="")
{
    ih := InputHook(Options)
    if !InStr(Options, "V")
        ih.VisibleNonText := false
    ih.KeyOpt("{All}", "E")  ; End
    ; Exclude the modifiers
    ih.KeyOpt("{LCtrl}{RCtrl}{LAlt}{RAlt}{LShift}{RShift}{LWin}{RWin}", "-E")
    ih.Start()
    ih.Wait()
    return ih.EndMods . ih.EndKey  ; Return a string like <^<+Esc
}

Simple auto-complete: any day of the week. Pun aside, this is a mostly functional example. Simply run the script and start typing today, press Tab to complete or press Esc to exit.

WordList := "Monday`nTuesday`nWednesday`nThursday`nFriday`nSaturday`nSunday"

Suffix := ""

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

SacChar(ih, char)  ; Called when a character is added to SacHook.Input.
{
    global Suffix := ""
    if RegExMatch(ih.Input, "`nm)\w+$", &prefix)
        && RegExMatch(WordList, "`nmi)^" prefix[0] "\K.*", &Suffix)
        Suffix := Suffix[0]
    
    if CaretGetPos(&cx, &cy)
        ToolTip Suffix, cx + 15, cy
    else
        ToolTip Suffix

    ; 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
}

Waits for the user to press any key. Keys that produce no visible character -- such as the modifier keys, function keys, and arrow keys -- are listed as end keys so that they will be detected too.

ih := InputHook("L1", "{LControl}{RControl}{LAlt}{RAlt}{LShift}{RShift}{LWin}{RWin}{AppsKey}{F1}{F2}{F3}{F4}{F5}{F6}{F7}{F8}{F9}{F10}{F11}{F12}{Left}{Right}{Up}{Down}{Home}{End}{PgUp}{PgDn}{Del}{Ins}{BS}{CapsLock}{NumLock}{PrintScreen}{Pause}")
ih.Start()
ih.Wait()

This is a working hotkey example. Since the hotkey has the tilde (~) prefix, its own keystroke will pass through to the active window. Thus, if you type [btw (or one of the other match phrases) in any editor, the script will automatically perform an action of your choice (such as replacing the typed text). For an alternative version of this example, see Switch.

~[::
{
    msg := ""
    ih := InputHook("V T5 L4 C", "{enter}.{esc}{tab}", "btw,otoh,fl,ahk,ca")
    ih.Start()
    ih.Wait()
    if (ih.EndReason = "Max")
        msg := 'You entered "{1}", which is the maximum length of text.'
    else if (ih.EndReason = "Timeout")
        msg := 'You entered "{1}" at which time the input timed out.'
    else if (ih.EndReason = "EndKey")
        msg := 'You entered "{1}" and terminated the input with {2}.'

    if msg  ; If an EndReason was found, skip the rest below.
    {
        MsgBox Format(msg, ih.Input, ih.EndKey)
        return
    }

    ; Otherwise, a match was found.
    if (ih.Input = "btw")
        Send("{backspace 4}by the way")
    else if (ih.Input = "otoh")
        Send("{backspace 5}on the other hand")
    else if (ih.Input = "fl")
        Send("{backspace 3}Florida")
    else if (ih.Input = "ca")
        Send("{backspace 3}California")
    else if (ih.Input = "ahk")
        Run("https://www.autohotkey.com")
}