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, KeyOptEndKeys 组合使用.

M: 将修饰键击对应于真正的 ASCII 字符, 识别并转录修饰键击(如 Ctrl+ACtrl+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+ACtrl+Z 对应于 Chr(1)Chr(26). 此外, M 选项可能会导致某些键盘快捷键(如 Ctrl+) 在输入正在进行时出现异常.

T: 设置 Timeout (例如 T3T2.5).

V: 设置 VisibleTextVisibleNonText 为 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, AltShift 作为结束键, 请指定键的左和/或右的版本, 而不是中性版本. 例如, 指定 {LControl}{RControl} 而不是 {Control}.

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

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

MatchList

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

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

InputHook.KeyOpt(Keys, KeyOptions)
Keys

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

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

KeyOptions

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

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

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

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

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

N: 通知. 在每次按下键时调用 OnKeyDownOnKeyUp 回调.

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

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

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

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

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

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

Start

启动收集输入.

InputHook.Start()

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

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

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

Wait

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

InputHook.Wait(MaxTime)
MaxTime

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

返回 EndReason.

Stop

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

InputHook.Stop()

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

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

如果输入正在进行, 返回 true, 否则返回 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 对象的引用.

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

OnChar

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

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

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

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

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

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

OnKeyDown

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

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

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

键-按下通知必须首先由 KeyOptNotifyNonText 启用.

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

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

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

OnKeyUp

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

MyFunc := InputHook.OnKeyUp
InputHook.OnKeyUp := MyFunc

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

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

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

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

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

Type: Integer. 默认值: 0. 选项 I 设置该值为 1(或给定值).

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

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

NotifyNonText

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

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

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

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

NotifyNonText 通过考虑先前匹配的 VK 码的 key-down 是被归类为文本还是非文本来应用 key-up 事件. 例如, 如果 NotifyNonText 为 true, 那么按下 Ctrl+A 将同时产生对 CtrlAOnKeyDownOnKeyUp 调用, 而按下 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 抑制 CtrlShift.

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

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

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

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

如果您使用多种语言或键盘布局, 则 Input 会使用活动窗口的键盘布局而不是脚本的(不论 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 命令在进行时会阻塞线程, 而 InputHook 允许线程继续, 甚至退出(这允许中断的任何线程继续运行). 脚本可以注册一个 OnEnd 函数, 当输入终止时调用该函数, 而不是等待.

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

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

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

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

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

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

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