Thanks for the useful pointer to the Input command. I'd never really noticed it before, so time well spent playing around with it.
I had to alter the logic a bit to make your example work properly with both character and non-character keys. Mostly I was testing with LCtrl as the hotkey, and where the spurious key could be say LShift. LAlt, or nearby letter keys. (I've left your script in the test version below in case you want to compare).
I think the version in the script here does the job well enough, although it's still possible to defeat it with very fast typing. If I send say LCtrl-Shift-LCtrl very fast it should never register a double hit, yet occasionally it does whereas the 'do-nothing hotkeys' approach seems bomb-proof. I'll see how the new version works out in daily use.
Code:
;;;;;;;;;;;;;;;;;;;
; Modified versions of RapidHotkey() and Morse() to eliminate false alarms.
; Modifications by DAT, 16-09-2009, based on Laszlo's 15-09-2009 suggestions.
;
; The Issue:
; If you're using RapidHotkey to detect multiple hits on keys such
; Control, Shift, and Alt, to do things like opening search windows,
; the script tries to ensure no false alarms during normal typing.
;
; Test:
; Action1 happens for single keypress of the right control key,
; Action2 for double press, and so on.
; If you change the parameters in RapidHotkey() to say,
; ("Action1""Action2""Action3""Action4",2,0.3,1), then Action1
; happens for double keypress and Action2 for triple, and so on.
Pip = 75
Tone = 2000
+Backspace::MsgBox % "Morse press pattern " Morse() ; Laszlo's example
#!z::MsgBox % "Morse press pattern " Morse() ; Laszlo's example
~LControl::RapidHotkey("Action1""Action2""Action3""Action4",1,0.3,1)
Action1:
Peep(1)
Return
Action2:
Peep(2)
Return
Action3:
Peep(3)
Return
Action4:
Peep(4)
Return
Peep(Rpts) {
Global
Loop , %Rpts% {
SoundBeep , %Tone% , %Pip%
sleep, %pip%
}
Return
}
;;;;;;;;;;;;;;;;;;;;
/*
RapidHotkey() is unchanged from 24.02.2009 version posted by
Hotkeyit at
http://www.autohotkey.com/forum/viewtopic.php?t=38795.
Morse() is based on Laszlo's Morse() function at
http://www.autohotkey.com/forum/viewtopic.php?t=16951. The morse aspect is not
used - it just has to detect multiple hits of the same key that are
separated by less than 'timeout', returning 0 for one hit, 00 for two,
and so on. It returns blank if any other key is detected during what would
otherwise count as a multiple key-press.
*/
RapidHotkey(keystroke, times="1", 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)
Loop % times
backspace .= "{Backspace}"
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|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
}
;/*
; ;;;;
; DAT version based on Laszlo's suggestion of 15-09-2009 to use 'Input' but fettled
; to work with character keys and non-character keys.
;
; 'Input' terminates with ErrorLevel=Max after one ('L1') input character,
; or with ErrorLevel=Timeout if it times out, or with ErrorLevel=EndKey:name if it
; detects one of the EndKeys. All the non-character keys used as hotkeys
; or recognisable as false hits must be listed as EndKeys in the Input command.
; Input doesn't put non-character keys into its output variable so false non-character
; keys are detected as EndKeys.
Morse(timeout = 400) {
tout := timeout/1000
key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^]") ; remove modifiers: +BS -> BS
Loop
{ ; Loops until times out, or until spurious keypress intervenes.
KeyWait %key%, T%tout% ; Wait for key to be released.
Pattern .= 0 ; appends 0 for each valid key-press in the multiple
If(ErrorLevel)
Return Pattern ; Timed out, so finish.
Input k , L1MT%tout%V , {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}
If (ErrorLevel="Timeout")
Goto , End
If ((k && k!=key) || !(k=key && Errorlevel="Max"|| !k && Errorlevel="EndKey:" key))
{
soundbeep , 300 , 20 ; beep on invalid key.
Pattern =
Goto , End
}
}
End:
ToolTip , key=%key%`nErrorLevel=%ErrorLevel%`nk=%k%`nPattern=%Pattern%
Return Pattern
}
;*/
/*
; Laszlo's 15-09-2009 11:10 example
Morse(timeout = 400) {
tout := timeout/1000
key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^]") ; remove modifiers: +BS -> BS
Loop {
t := A_TickCount
KeyWait %key% ; Wait for key release
Pattern .= A_TickCount-t > timeout ; How long the key was pressed
Input k,L1MT%tout%V,{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}
If (ErrorLevel && ErrorLevel != "Max" && ErrorLevel != "EndKey:" key
|| !ErrorLevel && k != key) ; Break at long no-press time or foreign keys
Return Pattern
}
}
*/