# InputHook

创建一个对象, 该对象可用于收集或拦截键盘输入.

    InputHook := InputHook(Options, EndKeys, MatchList)

## 参数

Options

  类型: 字符串

  由零个或多个下列选项组成的字符串(可任意顺序, 中间可选空格):

  B: 设置 BackspaceIsUndo 为 false, 这会导致 `Backspace` 被忽略.

  C: 设置 CaseSensitive 为 true, 使 MatchList 区分大小写.

  I: 设置 MinSendLevel 为 1 或给定值, 使任何输入级别低于该值的输入被忽略. 例如, `I2` 将忽略级别为 0(默认值) 或 1 的任何输入, 但将捕获级别为 2 的输入.

  L: 长度限制(例如 `L5`). 输入的最大允许长度. 当文本达到这个长度时, 输入被终止, EndReason 被设置为单词 Max(除非文本匹配 MatchList 中的一个短语, 在这种情况下 EndReason 被设置为单词 Match). 如果未指定, 则长度限制为 1023.

  指定 `L0` 禁用文本的收集和长度限制, 但并不影响按键生成的文本的统计(请参阅 VisibleText). 这可以与 OnChar, OnKeyDown, KeyOpt 或 EndKeys 组合使用.

  M: 将修饰键击对应于真正的 ASCII 字符, 识别并转录修饰键击(如 `Ctrl`+`A` 到 `Ctrl`+`Z`). 参考这个例子, 它识别 `Ctrl`+`C`:

      CtrlC := Chr(3) ; 将 Ctrl-C 对应的字符存储在 CtrlC 变量中.
      ih := InputHook("L1 M")
      ih.Start()
      ih.Wait()
      if (ih.Input = CtrlC)
          MsgBox "You pressed Control-C."

  注意: 字符 `Ctrl`+`A` 到 `Ctrl`+`Z` 对应于 Chr(1) 到 Chr(26). 此外, M 选项可能会导致某些键盘快捷键(如 `Ctrl`+`←`) 在输入正在进行时出现异常.

  T: 设置 Timeout (例如 `T3` 或 `T2.5`).

  V: 设置 VisibleText 和 VisibleNonText 为 true. 通常, 用户的输入被阻止(对系统隐藏). 使用此选项可将用户的击键发送到活动窗口.

  *: 通配符. 设置 FindAnywhere 为 true, 允许在用户键入的任何位置找到匹配项.

  E: 按字符代码而不是键码处理单字符结束键. 如果活动窗口的键盘布局与脚本的键盘布局不同, 则可以提供更一致的结果. 它还可以防止实际上不会产生给定结束字符的键组合结束 Input(输入); 例如, 如果 @ 是结束键, 则在美式键盘中 `Shift`+`2` 将触发它, 但 `Ctrl`+`Shift`+`2` 不会触发(在使用 E 选项时). 如果还使用 C 选项, 则结束字符区分大小写.

EndKeys

  类型: 字符串

  一个由零个或多个按键组成的列表, 其中任何一个键在按下时终止输入(结束键本身不会写入输入缓冲). 当 Input 以这种方式终止时, EndReason 设置为单词 EndKey, EndKey 属性设置为键的名称.

  EndKeys 使用类似于 Send 函数的格式. 例如, 指定 `{Enter}.{Esc}` 将使 `Enter`, `.` 或 `Esc` 任一一个都能终止 Input. 使用大括号本身作为结束键, 指定 `{{}` 和/或 `{}}`.

  要使用 `Ctrl`, `Alt` 或 `Shift` 作为结束键, 请指定键的左和/或右的版本, 而不是中性版本. 例如, 指定 `{LControl}{RControl}` 而不是 `{Control}`.

  尽管不支持诸如 `Alt`+`C`(!c) 这样的修饰键, 而非-字母数字字符(如 `?!:@&{}`) 默认情况下需要 `Shift` 或 `AltGr` 按键按下与否, 取决于字符的正常输入方式. 如果有 E 选项, 则将单个字符键名解释为字符, 在这种情况下, 修饰符键必须处于正确的状态才能生成该字符. 当同时使用 E 和 M 选项时, 通过在 EndKeys 中包括相应的 ASCII 控制字符来支持 `Ctrl`+`A` 到 `Ctrl`+`Z`.

  还可以指定明确的虚拟按键代码, 例如 `{vkFF}` 或 `{sc001}`. 这在键没有名称且按下时不产生可见字符的罕见情况下非常有用. 它的键码可以通过按键列表页面底部的步骤来确定.

MatchList

  类型: 字符串

  以逗号分隔的关键词列表, 其中任何一个都将导致终止输入(在这种情况下, EndReason 将被设置为单词 Match). 用户输入的内容必须完全匹配匹配列表中的某个词组(除非有 * 选项). 此外, 分隔符逗号周围的任何空格或制表符都是有意义的, 这意味着它们是匹配字符串的一部分. 例如, 如果 MatchList 为 `ABC , XYZ`, 则用户必须在 ABC 之后或 XYZ 之前键入空格以形成匹配.

  两个连续的逗号产生单个原义逗号. 例如, 后面的匹配列表会在 string1 的末尾产生单个原义逗号: `string1,,,string2`. 类似的, 后面的匹配列表仅包含其中有一个原义逗号的单个项目: `single,,item`.

  因为 MatchList 中的项目不被视为单独的参数, 所以列表可以完全包含在一个变量中. 事实上, 如果此列表的长度超过 16383, 那么列表的全部或部分必须包含在变量中, 因为这个长度是任何脚本行的最大长度. 例如, MatchList 可能由 `List1 "," List2 "," List3` 组成 -- 其中每个变量都包含匹配词组的子列表.

## Input 堆栈

任何数量的 InputHook 对象都可以在任何时候创建和进行, 但是它们启动的顺序会影响 Input 的收集方式.

当每个 Input 开始时(通过 Start 方法), 它被推到堆栈的顶部, 只有当 Input 终止时才从堆栈中删除. 键盘事件按最近开始到最早的顺序传递给每个输入. 如果一个输入抑制了一个给定的键盘事件, 那么它就不会再向下传递.

如果击键的发送级别低于 InputHook 的 MinSendLevel, 则忽略 Sent 的击键. 在这种情况下, 击键仍然可以由堆栈中较低的输入来处理.

多个 InputHook 都可以与 MinSendLevel 一起使用, 以分别收集发送的击键和实际击键.

## InputHook 对象

InputHook 函数返回一个 InputHook 对象, 该对象具有以下方法和属性.

- 方法:
  - KeyOpt: 设置按键或按键列表的选项.
  - Start: 启动收集输入.
  - Stop: 终止 Input 并将 EndReason 设置为单词 Stopped.
  - Wait: 等待, 直到 Input 终止(InProgress 为 false).
- 常规属性:
  - EndKey: 返回终止 Input 而按下的结束建的名称.
  - EndMods: 返回在 Input 终止时逻辑上是按下的修饰符键的字符串.
  - EndReason: 返回 EndReason 字符串, 该字符串表明了 Input 是如何终止的.
  - InProgress: 如果输入正在进行, 则返回 1(true), 否则返回 0(false).
  - Input: 返回自上次 Input 启动以来收集的任何文本.
  - Match: 返回导致 Input 终止的 MatchList 项目.
  - OnEnd: 检索或设置在 Input 终止时调用的函数对象.
  - OnChar: 检索或设置函数对象, 该函数对象将在字符添加到输入缓冲后调用.
  - OnKeyDown: 检索或设置函数对象, 该函数对象将在按下启用通知的按键时调用.
  - OnKeyUp: 检索或设置函数对象, 该函数对象将在释放启用通知按键时被调用.
- 选项属性:
  - BackspaceIsUndo: 控制 Backspace 键是否从输入缓冲的末尾删除最近按下的字符.
  - CaseSensitive: 控制 MatchList 是否区分大小写.
  - FindAnywhere: 控制每个匹配项是否可以是输入文本的子字符串.
  - MinSendLevel: 检索或设置要收集的输入的最小发送级别.
  - NotifyNonText: 控制当按下非文本键时是否调用 OnKeyDown 和 OnKeyUp 回调.
  - Timeout: 检索或设置超时值(以秒为单位).
  - VisibleNonText: 控制不产生文本的键或键组合是否可见(不阻止).
  - VisibleText: 控制产生文本的键或键组合是否可见(不阻止).

### 方法

### KeyOpt

设置按键或按键列表的选项.

    InputHook.KeyOpt(Keys, KeyOptions)

Keys

  类型: 字符串

  按键列表. 大括号用于括起按键名称, 虚拟键码或扫描码, 类似于 Send 函数. 例如, `{Enter}.{{}` 将应用于 `Enter`, `.` 和 `{`. 按名称, 按 `{vkNN}` 或按 `{scNNN}` 指定按键可能会产生三种不同的结果; 有关详情, 请参阅下文.

  单独指定字符串 `{All}`(不区分大小写) 以便将 KeyOptions 应用于所有 VK 和所有 SC. 然后可以再次调用 KeyOpt 从特定按键中删除选项.

KeyOptions

  类型: 字符串

  下列单字符选项中的一个或多个(空格和制表符).

  -(减号): 移除 `-` 后面的任何选项, 直到下一个 `+`.

  +(加号): 取消任何先前的 `-`, 否则无效.

  E: 结束键. 如果启用, 则按下键终止 Input, 将 EndReason 设置为单词 EndKey, 将 EndKey 属性设置为键的标准名称. 与 EndKeys 参数不同, `Shift` 或 `AltGr` 键的状态将被忽略. 例如, `@` 和 `2` 在美式键盘布局中都相当于 `{vk32}`.

  I: 忽略文本. 通常由该键生成的任何文本都将被忽略, 并且该键被视为非文本键(请参阅 VisibleNonText). 如果键通常不产生文本, 则没有效果.

  N: 通知. 在每次按下键时调用 OnKeyDown 和 OnKeyUp 回调.

  S: 处理它后抑制(阻止) 按键. 这将覆盖 VisibleText 或 VisibleNonText 直到使用 `-S`. `+S` 意味着 `-V`.

  V: 可见. 防止键按被抑制(阻止). 这将覆盖 VisibleText 或 VisibleNonText 直到使用 `-V`. `+V` 意味着 `-S`.

选项可以通过虚拟键码和扫描码来设置, 并且是累加的.

当按名称指定一个键时, 选项通过 VK 或 SC 来设置. 如果两个物理键共享相同的 VK, 但 SC 不同(如 `Up` 和 `NumpadUp`), 则由 SC 处理. 相反, 如果使用 VK 码, 则它将应用于产生该 VK 的任何物理键(这可能会随着时间的推移而变化, 这取决于活动键盘布局).

通过 VK 码删除选项不会影响 SC 设置的任何选项, 反之亦然. 但是, 当按键名删除一个选项并且该名称由 VK 处理时, 对应的 SC 也会删除该选项(根据脚本的键盘布局). 这允许在对所有按键应用一个选项后按名称排除按键.

如果通过 VK 设置 `+V`, 而通过 SC 设置 `+S`(反之亦然), 则 `+V` 优先.

### Start

启动收集输入.

    InputHook.Start()

如果 Input 已经在进行中, 则没有效果.

新启动的 Input 放在 InputHook 堆栈的顶部, 这允许它覆盖任何先前启动的 Input.

此方法安装键盘钩子(如果还没有安装).

### Stop

终止 Input 并将 EndReason 设置为单词 Stopped.

    InputHook.Stop()

如果 Input 不在进行中, 则没有效果.

### Wait

等待, 直到 Input 终止(InProgress 为 false).

    InputHook.Wait(MaxTime)

MaxTime

  类型: 浮点数

  等待的最大秒数. 如果 Input 在 MaxTime 秒后仍在进行中, 则该方法返回且不终止 Input.

返回 EndReason.

### 常规属性

### EndKey

返回结束键的名称, 按下结束键以终止 Input.

    KeyName := InputHook.EndKey

注意, EndKey 返回按键的 "标准" 名称, 而不管它在 EndKeys 中是如何编写的. 例如, `{Esc}` 和 `{vk1B}` 都产生 `Escape`. GetKeyName 可用于检索标准名称.

如果使用 E 选项, EndKey 返回输入的实际字符(如果合适). 否则, 键名将根据脚本的活动键盘布局来确定.

如果 EndReason 不是 "EndKey", EndKey 返回一个空字符串.

### EndMods

返回在 Input 终止时逻辑上按下的修饰符的字符串.

    Mods := InputHook.EndMods

如果所有修饰符在逻辑上都是按下的, 则完整的字符串为:

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

这些修饰符与热键的具有相同的含义. 每个修饰符总是用 <(左) 或 >(右) 限定. 对应的键名是: LCtrl, RCtrl, LAlt, RAlt, LShift, RShift, LWin, RWin.

InStr 可以用来检查给定的修饰符(如 `>!` 或 `^`) 是否存在的. 下面的行可以用来将 Mods 转换成中性修饰符的字符串, 如 `^!+#`:

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

由于这是瞬间发生的, 此属性可能比 GetKeyState 更可靠, 即使在 Input 终止后立即调用 GetKeyState, 或者在 OnEnd 回调中.

### EndReason

返回 EndReason 字符串, 该字符串表明了 Input 是如何终止的.

    Reason := InputHook.EndReason

如果 Input 正在进行, 则返回空字符串.

### InProgress

如果输入正在进行, 返回 1(true), 否则返回 0(false).

    Boolean := InputHook.InProgress

### Input

返回自上次 Input 启动以来收集的任何文本.

    String := InputHook.Input

此属性可在输入进行中或输入结束后使用.

### Match

返回导致 Input 终止的 MatchList 项目.

    String := InputHook.Match

返回匹配项及其原始大小写, 如果省略了 C 选项, 则返回的大小写可能与用户键入的大小写不同. 如果 EndReason 不是 "Match", 则返回空字符串.

### OnEnd

检索或设置在 Input 终止时调用的函数对象.

    MyFunc := InputHook.OnEnd

    InputHook.OnEnd := MyFunc

类型: 函数对象或空字符串. 默认值: 空字符串.

该函数传递一个参数: 对 InputHook 对象的引用.

该函数作为一个新线程被调用, 因此使用 SendMode 和 DetectHiddenWindows 等设置的默认值重新开始.

### OnChar

检索或设置将字符添加到输入缓冲后调用的函数对象.

    MyFunc := InputHook.OnChar

    InputHook.OnChar := MyFunc

类型: 函数对象或空字符串. 默认值: 空字符串.

函数被传递以下参数: `InputHook, Char`. Char 是包含一个或多个字符的字符串.

多个字符的存在表明在先前最后的键击中使用了一个死键, 但是这两个键不能转换为单个字符. 例如, 在一些键盘布局中 ````e` 产生 `è`, 而 ````z` 产生 ``z`.

当按下结束键时, 该函数不会被调用.

### OnKeyDown

检索或设置当按下启用通知的按键时调用的函数对象.

    MyFunc := InputHook.OnKeyDown

    InputHook.OnKeyDown := MyFunc

类型: 函数对象或空字符串. 默认值: 空字符串.

键-按下通知必须首先由 KeyOpt 或 NotifyNonText 启用.

函数被传递如下参数: `InputHook, VK, SC`. VK 和 SC 都是整数. 要检索按键名称(如果有), 请使用 `GetKeyName(Format("vk{:x}sc{:x}", VK, SC))`.

该函数作为一个新线程被调用, 因此使用设置(如 SendMode 和 DetectHiddenWindows) 的默认值重新开始.

当按下结束键时, 该函数不会被调用.

### OnKeyUp

检索或设置释放启用通知的按键时调用的函数对象.

    MyFunc := InputHook.OnKeyUp

    InputHook.OnKeyUp := MyFunc

类型: 函数对象或空字符串. 默认值: 空字符串.

按键-释放通知必须首先由 KeyOpt 或 NotifyNonText 启用. 键被认为是文本还是非文本取决于什么时候键被按下. 如果 InputHook 在没有检测到 key-down 的情况下检测到 key-up, 则认为它是非文本.

函数被传递如下参数: `InputHook, VK, SC`. VK 和 SC 都是整数. 要检索按键名称(如果有), 请使用 `GetKeyName(Format("vk{:x}sc{:x}", VK, SC))`.

该函数作为一个新线程, 调用, 因此使用设置(如 SendMode 和 DetectHiddenWindows) 的默认值重新开始.

### 选项属性

### BackspaceIsUndo

控制 `Backspace` 是否从 Input 缓冲的末尾删除最近按下的字符.

    Boolean := InputHook.BackspaceIsUndo

    InputHook.BackspaceIsUndo := Boolean

类型: 整数(布尔值). 默认值: true. 选项 B 设置该值为 false.

当 `Backspace` 用作 undo 时, 它被视为文本输入键. 具体来说, 是否抑制该键取决于 VisibleText 而不是 VisibleNonText.

`Backspace` 与修饰符键(如 `Ctrl`) 结合使用, 则始终忽略 `Backspace`(检查的是修饰符的逻辑状态, 而不是物理状态).

注意: 如果输入文本是可见的(如在编辑器中) 并且使用箭头键或其他方法在其中导航, `Backspace` 仍然会删除最后一个字符, 而不是插入符号(插入点) 后面的字符.

### CaseSensitive

控制 MatchList 是否区分大小写.

    Boolean := InputHook.CaseSensitive

    InputHook.CaseSensitive := Boolean

类型: 整数(布尔值). 默认值: false. 选项 C 会设置该值为 true.

### FindAnywhere

控制每个匹配项是否可以是输入文本的子字符串.

    Boolean := InputHook.FindAnywhere

    InputHook.FindAnywhere := Boolean

类型: 整数(布尔值). 默认值: false. 选项 * 设置该值为 true.

如果为真(true), 则可以在用户键入的任何位置找到匹配(匹配可以是输入文本的子字符串). 如果为假(false), 则用户键入的所有内容必须与 MatchList 中的一个短语匹配. 在这两种情况下, 都必须完整输入 MatchList 中的一个短语.

### MinSendLevel

检索或设置要收集输入的最小发送级别.

    Level := InputHook.MinSendLevel

    InputHook.MinSendLevel := Level

类型: 整数. 默认值: 0. 选项 I 设置该值为 1(或给定值).

Level 应该是 0 到 101 之间的整数. 发送级别 低于 此值的事件将被忽略. 例如, 值 101 会忽略 SendEvent 生成的所有输入, 而值 1 只会忽略默认发送级别(0) 的输入.

无论这个设置如何, SendInput 和 SendPlay 方法总是被忽略. 除了 AutoHotkey 以外的任何其他源生成的输入都不会因为这个设置而被忽略.

### NotifyNonText

控制当按下非文本键时是否调用 OnKeyDown 和 OnKeyUp 回调.

    Boolean := InputHook.NotifyNonText

    InputHook.NotifyNonText := Boolean

类型: 整数(布尔值). 默认值: false.

将此设置为 true 可启用所有不产生文本的按键的通知, 如按下 `Left` 或 `Alt`+`F`. 设置此属性不会影响按键的 options, 因为文本的生成取决于按下按键时活动窗口的键盘布局.

NotifyNonText 通过考虑先前匹配的 VK 码的 key-down 是被归类为文本还是非文本来应用 key-up 事件. 例如, 如果 NotifyNonText 为 true, 那么按下 `Ctrl`+`A` 将同时产生对 `Ctrl` 和 `A` 的 OnKeyDown 和 OnKeyUp 调用, 而按下 `A` 本身不会调用 OnKeyDown 或 OnKeyUp, 除非使用 KeyOpt 来启用该键的通知.

有关计为生成文本的键详细信息, 请参阅 VisibleText.

### Timeout

检索或设置超时值(以秒为单位).

    Seconds := InputHook.Timeout

    InputHook.Timeout := Seconds

类型: Float. 默认值: 0.0(无). 选项 T 也能设置超时值.

超时周期通常在调用 Start 时启动, 但如果在 Input 进行中为该属性赋值, 则会重新启动. 如果在超时时间过去后 Input 仍在进行中, 则终止输入并将 EndReason 设置为单词 Timeout.

### VisibleNonText

控制不产生文本的键或键组合是否可见(不阻止).

    Boolean := InputHook.VisibleNonText

    InputHook.VisibleNonText := Boolean

类型: 整数(布尔值). 默认值: true. 选项 V 设置该值为 true.

如果为真, 不产生文本的键和键组合可能触发热键或被传递到活动窗口. 如果为假, 则会阻止它们.

有关计为生成文本的键详细信息, 请参阅 VisibleText.

### VisibleText

控制产生文本的键或键组合是否可见(不阻止).

    Boolean := InputHook.VisibleText

    InputHook.VisibleText := Boolean

类型: 整数(布尔值). 默认值: false. 选项 V 设置该值为 true.

如果为真(true), 产生文本的键和键组合可以触发热键或被传递到活动窗口. 如果为假(false), 则会阻止它们.

导致文本被附加到输入缓冲的任何击键都被视为生成文本, 即使在其他应用程序中通常不会这样做. 例如, 如果使用 M 选项, `Ctrl`+`A` 生成文本, 而 `Esc` 生成控制字符 `Chr(27)`.

尽管死键通常不会立即产生效果, 但它们被视为产生文本. 按下死键也可能导致后面的键生成文本(如果只有死键的字符).

`Backspace` 仅在作为撤销时才被计为生成文本.

标准的修饰符键和 `CapsLock`, `NumLock` 和 `ScrollLock` 总是可见的(不被阻止).

## EndReason

EndReason 属性返回以下字符串之一:

字符串 | 描述
--- | ---
Stopped | Stop 方法已被调用或 Start 尚未被调用.
Max | 输入达到允许的最大长度, 且它不匹配 MatchList 中的任何项.
Timeout | Input 超时.
Match | Input(输入) 匹配 MatchList 中的一项. Match 属性包含匹配的项.
EndKey | EndKeys 中的一个被按下终止了输入. EndKey 属性包含终止键的名称或字符, 不带大括号.
| 如果 Input(输入) 正在进行中, EndReason 为空.

## 备注

必须先调用 Start 方法, 然后才能收集输入.

InputHook 设计为允许脚本的不同部分都能监视输入, 并且冲突最小. 它可以连续操作, 例如观察任意的单词或其他模式. 它还可以临时运行, 例如收集用户输入或临时覆盖特定(或非-特定) 按键而不干扰热键.

当 Input 正在进行时, 键盘热键仍然有效, 但是如果任何必需的修饰符键被抑制, 或者如果热键使用 reg 方法并且其后缀键被抑制, 则不能激活热键. 例如, 热键 `^+a::` 可能 会被 InputHook 覆盖, 而热键 `$^+a::` 会优先, 除非 InputHook 抑制 `Ctrl` 或 `Shift`.

按键抑制(阻止) 与否取决于以下因素(按顺序):

- 如果 V 选项对 VK 或 SC 有效, 则不会抑制.
- 如果 S 选项对 VK 或 SC 有效, 则抑制.
- 如果按键是标准修饰键或 `CapsLock`, `NumLock` 或 `ScrollLock`, 则不会抑制.
- VisibleText 或 VisibleNonText 是否被考虑, 具体取决于按键是否生成文本. 如果该属性为 false, 则抑制按键. 有关将哪些按键计为生成文本的详细信息, 请参阅 VisibleText.

Input 正在进行时需要键盘钩子, 但当 Input 终止时, 如果不再需要键盘钩子, 则会自动卸载.

Input 正在进行时, 脚本为自动持续运行, 所以即使没有正在运行的线程, 它也会继续监视输入. 当输入结束时, 脚本可能会自动退出(如果没有正在运行的线程, 并且该脚本由于其他原因而不会持续运行).

AutoHotkey 不支持输入法编辑器(IME). 键盘钩子拦截键盘事件, 并通过使用 ToUnicodeEx 或 ToAsciiEx 将它们转换为文本(VK_PACKET 事件除外, 它封装了单个字符).

如果您使用多种语言或键盘布局, 则 InputHook 会使用活动窗口的键盘布局而不是脚本的(不论 Input 是否可见).

尽管不够灵活, 但热字串更容易使用.

## InputHook vs. Input (v1)

在 AutoHotkey v1.1 中, InputHook 是 Input 命令的替代品, 提供了更大的灵活性. 在 2.0 版本中删除了 Input 命令, 但下面的代码基本相同:

    ; 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

Input 命令终止任何先前由它启动的 Input, 而 InputHook 允许同时多个 Input.

Options 解释相同, 但默认设置不同:

- Input 命令将输入的长度限制为 16383, 而 InputHook 将其限制为 1023. 这可以使用 L 选项来改写, 并且没有绝对最大值.
- 默认情况下, Input 命令会阻止文本和非文本击键, 如果使用 V 选项, 则不会阻止. 相比之下, InputHook 默认只阻止文本击键(VisibleNonText 默认为 true), 因此大多数热键可以在输入正在进行时使用.

Input 命令在进行时会阻塞线程, 而 InputHook 允许线程继续, 甚至退出(这允许中断的任何线程继续运行). 脚本可以注册一个 OnEnd 函数, 当输入终止时调用该函数, 而不是等待.

Input 命令只在输入终止后返回用户的输入, 而 InputHook 的 Input 属性允许在任何时候检索它. 脚本可以注册一个 OnChar 函数, 以便在字符增加时调用, 而不是不断地检查 Input 属性.

InputHook 通过 KeyOpt 方法对单个按键提供了更多控制. 这包括添加或删除结束键, 抑制或不抑制特定键, 或忽略特定键生成的文本.

与 Input 命令不同, InputHook 可用于检测不产生文本的键, 而 不 终止输入. 这是通过注册 OnKeyDown 函数和使用 KeyOpt 或 NotifyNonText 来指定相关键来完成的.

如果 MatchList 中的项目导致 Input 终止, 可以参考 Match 属性来确定到底是哪个匹配(当 * 选项存在时这更有用).

尽管脚本可以在 Input 函数返回后查询 GetKeyState, 但有时它无法准确反映输入终止时按下了哪些键. InputHook 的 EndMods 属性反映了 Input 终止时修饰键的逻辑状态.

在向后兼容性方面有一些不同之处:

- Input 命令以大写存储结束键 `A`-`Z`, 即使某些键盘布局中的一些字母是小写的. 将值传递给 Send 会产生 Shift 的击键, 而不是普通的击键. 相比之下, InputHook 的 EndKeys 属性始终返回规范化的名称; 即按下该键而不按住 `Shift` 或其他修饰键时产生的字符.

- 如果 EndKeys 中使用的键的名称对应于两个物理键共享 VK(如 `NumpadUp` 和 `Up`), 则 Input 命令通过 VK 处理主键通过 SC 处理次要键, 而 InputHook 都通过 SC 来处理. `{vkNN}` 表示可以通过 VK 来处理按键.

  当结束键由 VK 处理时, 两个物理键都可以终止输入. 例如, `{NumpadUp}` 将导致通过按下 `Up` 终止 Input 命令, 但是 ErrorLevel 将包含 `EndKey:NumpadUp`, 因为只考虑 VK.

  当结束键由 SC 处理时, Input 命令将始终为任何给定 VK 的已知辅助 SC 生成名称, 并始终为任何其他键生成 `scNNN`. 与之相比, InputHook 产生按键名(如果有).

## 相关

KeyWait, 热字串, InputBox, InstallKeybdHook, 线程

## 示例

等待用户按下任意一个键.

    MsgBox KeyWaitAny()

    ; 再来一遍, 但不阻止按键.
    MsgBox KeyWaitAny("V")

    KeyWaitAny(Options:="")
    {
        ih := InputHook(Options)
        if !InStr(Options, "V")
            ih.VisibleNonText := false
        ih.KeyOpt("{All}", "E")  ; 结束
        ih.Start()
        ih.Wait()
        return ih.EndKey  ; 返回按键名称
    }

等待任何与 Ctrl/Alt/Shift/Win 组合的按键.

    MsgBox KeyWaitCombo()

    KeyWaitCombo(Options:="")
    {
        ih := InputHook(Options)
        if !InStr(Options, "V")
            ih.VisibleNonText := false
        ih.KeyOpt("{All}", "E")  ; 结束
        ; Exclude the modifiers
        ih.KeyOpt("{LCtrl}{RCtrl}{LAlt}{RAlt}{LShift}{RShift}{LWin}{RWin}", "-E")
        ih.Start()
        ih.Wait()
        return ih.EndMods . ih.EndKey  ; 返回字符串, 例如 <^<+Esc
    }

简单的自动完成: 一周中的任何一天. 除了字母双关外, 这是一个功能齐全的例子. 只需运行脚本并开始输入, 按 `Tab` 完成或按 `Esc` 退出.

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

    global Suffix := "", SacHook

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

    SacChar(ih, char)  ; 当一个字符被添加到 SacHook.Input 时调用.
    {
        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

        ; 只在显示工具提示时拦截 Tab 键.
        ih.KeyOpt("{Tab}", Suffix = "" ? "-NS" : "+NS")
    }

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

等待用户按下任意键. 不产生可见字符的键 -- 比如修饰键, 功能键和箭头键 -- 被列为结束键, 这样它们也会被检测到.

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

这是一个可工作的热键示例. 由于热键有波浪(~) 前缀, 它自己的击键将传递到活动窗口. 因此, 如果您在任何编辑器中键入 `[btw`(或其他匹配短语之一), 脚本将自动执行您选择的操作(例如替换键入的文本). 有关此示例的另一个版本, 请参阅 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  ; 如果发现了EndReason, 跳过下面的部分.
        {
            MsgBox Format(msg, ih.Input, ih.EndKey)
            return
        }

        ; 否则, 找到了匹配.
        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")
    }

dftg