autoHotkey — 连击/双击/重复 按键触发
基本环境
- autohotkey
- SciTE4AutoHotkey-Plus 编辑器, 非必须,打算长用ahk的可以考虑.
- autohotkey基础教程系列(一) ———— 怎么学,哪里学,有哪些现成的demo
为什么要做这个事情
是为了让少数的常用的快捷键发挥出更多的作用. 设计快捷键的思维应是增强现有的按键组合,而不是为了新功能去开发冷门的组合键.
而连续按键的检测,是为了引入上下文特征而用的. 并不是简单地,同一个组合按键固定连续按下两次就固定触发一个功能.
个人认为的快捷键设计原则:
- 键少功能多. 少量的人机接口方式,实现更多的功能.
- 连击最多2次单击,且由双手食指触发. 因为食指灵敏, 且我们习惯了鼠标的双击动作.
- 如果为双键结合,那么修饰键最好按整体功能进行分离,例如 shift 用来修饰编辑文本的. alt用来处理窗口操作的.
- 尽可能保留已经习惯的触发键,例如s为保存,f为查找. c为复制
- 不同软件的同概念性的快捷键应该保持一致. 例如 ctrl f 为一般的搜索功能,那么可以把百度激活搜索框的快捷键设置为ctrl f. 这样就不需要每次都用鼠标去单击搜索框,也不需要安装vim等插件. 因为是搜索引擎.没有必要保留原本的ctrl+F的功能.
- 可将同概念性的功能按软件的先后层次进行顺序切换,例如,在百度里面, 如果没有在输入框中,则按一次ctrl+f先激活输入框,再按一次搜索打开的标签或者直接跳转到系统的全局搜索 ,例如everything这类工具,再按一次则切换回浏览器的输入框,完全没有必要设置那么多个快捷键。
- 设计的时候先考虑新添加的功能是否与旧功能具有概念相似性。切记一上来就是一个新的快捷键。这是很多人一开始常犯的毛病。
举个具体的例子,在pycharm中,单独使用了一个ctrl +d 作为重复一行或者选中内容的快捷键. 其实完全没必要这么做. 只要在原始的复制ctrl+c加一个是否选取内容的判断就可以,当没有选择内容的时候,默认就是重复复制一行. 在选中多行,或者一行中局部内容的时候,直接重复填充反而会很乱. 还要额外去记一个快捷键.
代码
- 官方版本
在中文手册中,是这么来检测的. 他的原理是按下某个热键的时候,同时触发一个定时器,然后在这个计时器被调用之前去累计按下的累计的次数,在计时器到时的时候才触发相应的功能.
这种设计方式有多种局限,看后面.
这部分实际上当初参考了某个博客,但是找不到了,所以这边没有引用.
$1::
sendinput,+1 ;这边是自己修改的,为了弥补按一下也许等待计时器的问题
if winc_presses_1 > 0 ; SetTimer 已经启动, 所以我们记录键击.
{
winc_presses_1 += 1
return
}
winc_presses_1 = 1
SetTimer, KeyWin_1_大写, %tout% ; 在 100 毫秒内等待更多的键击.
return
KeyWin_1_大写:
SetTimer, KeyWin_1_大写,off
if winc_presses_1 = 2
{
sendinput,{backSpace}
sendinput,{backSpace}
sendinput,+1
}
winc_presses_1 = 0
return
局限性:
- 导致功能的触发点延迟,即无论你按多少次,都得等到计时器的时间到了 才能触发相应的功能.
- 代码冗长, 当需要设置多个按键时,会发现代码量蹭蹭的就上去了.
- 不灵活,每个热键都需要设置一个定时器,代码不能够复用.
- 可读性差,看代码就知道了.
- 没有检测其他按键的插入 . 当打字速度快的时候, 即便不是连续按下热键,而是穿插按下,也会被认为是连续按下. 例如 我们的理想状态是 AA ->触发 但是如果短时内输入的是ABA,同样也会被认为是AA,而触发功能。
- 他人版本
这个版本的功能比较强,他提供的 "RapidHotkey"函数,可以让我们传入多个待触发的命令,并指定这些命令是在按下多少次后触发的.
但是他也有很严重的局限性
- 同样仅有当约定的多键等待时间到了,才能触发功能.
- 当有修饰键参与热键组合的时候,只能等到修饰键也一起弹起的才能够触发,这对于一些想要快速连发的功能而言是满足不了需求的.例如用shift+字母映射方向键的时候.
- 不方便触发一些复杂的操作.
- 同样,不检测其他按键的插队问题.
;http://www.autohotkey.com/board/topic/35566-rapidhotkey/
RapidHotkey(keystroke, times="2", delay=0.2, IsLabel=0)
{
Pattern := Morse(delay*1000)
If (StrLen(Pattern) < 2 and Chr(Asc(times)) != "1")
Return
If (times = "" and InStr(keystroke, """"))
{
Loop, Parse, keystroke,""
If (StrLen(Pattern) = A_Index+1)
continue := A_Index, times := StrLen(Pattern)
}
Else if (RegExMatch(times, "^\d+$") and InStr(keystroke, """"))
{
Loop, Parse, keystroke,""
If (StrLen(Pattern) = A_Index+times-1)
times := StrLen(Pattern), continue := A_Index
}
Else if InStr(times, """")
{
Loop, Parse, times,""
If (StrLen(Pattern) = A_LoopField)
continue := A_Index, times := A_LoopField
}
Else if (times = "")
continue := 1, times := 2
Else if (times = StrLen(Pattern))
continue = 1
If !continue
Return
Loop, Parse, keystroke,""
If (continue = A_Index)
keystr := A_LoopField
Loop, Parse, IsLabel,""
If (continue = A_Index)
IsLabel := A_LoopField
hotkey := RegExReplace(A_ThisHotkey, "[\*\~\$\#\+\!\^]")
IfInString, hotkey, %A_Space%
StringTrimLeft, hotkey,hotkey,% InStr(hotkey,A_Space,1,0)
backspace := "{BS " times "}"
keywait = Ctrl|Alt|Shift|LWin|RWin
Loop, Parse, keywait, |
KeyWait, %A_LoopField%
If ((!IsLabel or (IsLabel and IsLabel(keystr))) and InStr(A_ThisHotkey, "~") and !RegExMatch(A_ThisHotkey
, "i)\^[^\!\d]|![^\d]|#|Control|Ctrl|LCtrl|RCtrl|Shift|RShift|LShift|RWin|LWin|Alt|LAlt|RAlt|Escape|BackSpace|F\d\d?|"
. "Insert|Esc|Escape|BS|Delete|Home|End|PgDn|PgUp|Up|Down|Left|Right|ScrollLock|CapsLock|NumLock|AppsKey|"
. "PrintScreen|CtrlDown|Pause|Break|Help|Sleep|Browser_Back|Browser_Forward|Browser_Refresh|Browser_Stop|"
. "Browser_Search|Browser_Favorites|Browser_Home|Volume_Mute|Volume_Down|Volume_Up|MButton|RButton|LButton|"
. "Media_Next|Media_Prev|Media_Stop|Media_Play_Pause|Launch_Mail|Launch_Media|Launch_App1|Launch_App2"))
Send % backspace
If (WinExist("AHK_class #32768") and hotkey = "RButton")
WinClose, AHK_class #32768
If !IsLabel
Send % keystr
else if IsLabel(keystr)
Gosub, %keystr%
Return
}
Morse(timeout = 400) {
tout := timeout/1000
key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^]")
IfInString, key, %A_Space%
StringTrimLeft, key, key,% InStr(key,A_Space,1,0)
If Key in Shift,Win,Ctrl,Alt
key1:="{L" key "}{R" key "}"
Loop {
t := A_TickCount
KeyWait %key%, T%tout%
Pattern .= A_TickCount-t > timeout
If(ErrorLevel)
Return Pattern
If key in Capslock,LButton,RButton,MButton,ScrollLock,CapsLock,NumLock
KeyWait,%key%,T%tout% D
else if Asc(A_ThisHotkey)=36
KeyWait,%key%,T%tout% D
else
Input,pressed,T%tout% L1 V,{%key%}%key1%
If (ErrorLevel="Timeout" or ErrorLevel=1)
Return Pattern
else if (ErrorLevel="Max")
Return
}
}
- 个人版本
这边只实现了检测连续按下两次,因为不推荐检测3次以上的连发,不自然。
原理是通过判断上次热键和这次热键是否相同,且在规定的时间内.
这边用了ahk内置的三个变量
A_ThisHotkey, 当前触发的热键
A_PriorHotkey, 上次触发点热键
A_TimeSincePriorHotkey , 上次触发按键的时刻到目前的时长,单位是ms
- 代码
; 函数名是中文,因为我的编辑器支持,如果你的不支持的话,改成自己的就行.
; 我们手指的灵敏度不同,避免一股脑的给所有的连发设置同样的时间参数.
; 平时常用的手指可以设置快点.
不间断触发了两次(tout)
{
return (A_ThisHotkey = A_PriorHotkey) and (A_TimeSincePriorHotkey < tout)
}
优点:
-
触发受实际手速的影响,无须等待最大时长.
-
代码通用,在任何的热键中直接调用,用作判断条件即可
-
不影响单次触发的速度.
-
使用案例
; 注释
+e:: ; 用e作为注释键,是因为在word等文本编辑器中,我们常用他做居中的快捷键,因此比较熟练.
;而且都有一种代码右移的视觉感.
if 在这个程序中(ahkIDE) ; 这边是我自己判断在ahkIDE的情况下才生效
{
if (不间断触发了两次(300))
{
SendInput, ^q ;取消之前的注释代码
ToolTip,在300毫秒内连续按下了两次. ; 为了演示,这边就不控制这个提示自动隐藏了
SendInput,{End} ; 到代码段的末尾
SendInput,{space} `; ; 添加注释说明的符号
}
else
{
SendInput, ^q ;只按一次的话
}
}
if 在这个程序中(pythonIDE)
{
SendInput,!f
}
return
- 效果