Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

RapidHotkey()


  • Please log in to reply
101 replies to this topic
HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Using this function you can send keystrokes or launch a Label by pressing a key several times.

Include the function in your script or save it in .\Lib folder (RapidHotkey.ahk).

Many thanks to Laszo for Morse(), I have modified it to capture invalid keys.
:!: Please note, my Morse() version does not detect long key presses :!:

Syntax:
Key::RapidHotkey("keystrokes" ;Enter keystrokes here. E.g.: "^o"
           , times          ;optional. The number of times the key must be pressed to execute.  E.g.: 3
           , delay          ;optional. How quick the key must be pressed to execute. E.g.: 0.2
           , IsLabel)        ;optional. specify 1 to indicate that parameter 1 is a label.
;E.g. 
~o::RapidHotkey("^o") ;open file dialog if o pressed twice

;To specify several actions , use " as separator and leave times parameter empty.
If press times parameter is omitted, first action would be triggered on 2 presses.
~e::RapidHotkey("#r""#e""#f") ; #r if pressed twice, #e 3 times and so on
;You can specify also one (can be also 1) or separated value for times
~s::RapidHotkey("^s""{F12}""^+s", 5) ;so pressing 5 times = ^s, 6 times = {F12} and so on
;You can also specify separated times value
$x::RapidHotkey("x""#r""#e", "1""5""3")

;use same separator for delay and islabel parameter


ChangeLog:
02.06.2010
- Added exeption for Capslock,LButton,MButton + RButton
20.05.2010
- Invalid hotkey presses are detected using Input command now.
19.09.2009
- Detects invalid hotkey presses as well now
18.09.2009
- Removed some invalid keys from GetKeyState list
17.09.2009
- Detects invalid key presses and terminates execution
24.02.2009
- Use combination of Hotkeys, e.g. ~LButton & RButton::...

14.01.2009
- Use "" instead of Chr(4). E.g. ...RapidHotkey("^a""Hello""World")

14-12-2008
- You can use 1 for times to do action on one press as well.
- changed standard delay parameter to 0.1.


Examples:
~+::RapidHotkey("Plus")
~h::RapidHotkey("{Raw}Hello World!", 3) ;Press h 3 times rapidly to send Hello World!
~o::RapidHotkey("^o", 4, 0.2) ;be careful, if you use this hotkey, above will not work properly
~Esc::RapidHotkey("exit", 4, 0.2, 1) ;Press Esc 4 times rapidly to exit this script
~LControl::RapidHotkey("!{TAB}",2) ;Press LControl rapidly twice to AltTab
~RControl::RapidHotkey("+!{TAB}",2) ;Press RControl rapidly twice to ShiftAltTab
~LShift::RapidHotkey("^{TAB}", 2) ;Switch back in internal windows
~RShift::RapidHotkey("^+{TAB}", 2) ;Switch between internal windows
~e::RapidHotkey("#e""#r",3) ;Run Windows Explorer
~^!7::RapidHotkey("{{}{}}{Left}", 2)

~a::RapidHotkey("test", 2, 0.3, 1) ;You Can also specify a Label to be launched
test:
MsgBox, Test
Return

Exit:
ExitApp

~LButton & RButton::RapidHotkey("Menu1""Menu2""Menu3",1,0.3,1)
Menu1:
Menu2:
Menu3:
MsgBox % A_ThisLabel
Return
Functions
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) { ;by Laszo -> http://www.autohotkey.com/forum/viewtopic.php?t=16951 (Modified to return: KeyWait %key%, T%tout%)
   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
   }
}


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Nice! You can extend the idea with detecting the lengths of key presses, like here.

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008

Nice! You can extend the idea with detecting the lengths of key presses, like here.

Thank you a lot for that :D

Using your function now, RapidHotkey looks to work perfectly and you can use modifiers as well. I've updated the post.

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Now you can specify a different action on specified times of presses. :)

;To specify several actions continuously, so one if pressed twice, another if 3 times, and so on use "" as separator and leave times parameter empty ("").
~e::RapidHotkey("#r""#e""#f") ; #r if pressed twice, #e 3 times and so on

;You can also specify separated times value
~x::RapidHotkey("#r""#e", "5""3")


HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Easy MultiMenu example.

Press RButton twice to display Programs Menu, 3 times for Settings Menu, 4 times for FolderMenu.

To add a menu item, you need to add a new Label in your script and add its name to programs, settings or folders variable in first lines.

Menu = Programs|Settings|Folders
Programs = PROGRAMS|Notepad|Explorer|Calculator
Settings = SETTINGS|ControlPanel|Display
Folders = FOLDERS|Windows|ProgramFiles|Desktop
Loop, Parse, Menu, |
{
	B_LoopField := A_LoopField
	Loop, Parse, %B_LoopField%, |
	{
		Menu, %B_LoopField%, Add, %A_LoopField%, %A_LoopField%
		If A_Index = 1
		{
			Menu, %B_LoopField%, Default, %A_LoopField%
			Menu, %B_LoopField%, Add
		}
	}
}
Return

~RButton::RapidHotkey("Programs""Settings""Folders", 2,0.4,1)

Folders:
Settings:
Programs:
Menu,%A_ThisLabel%, Show
Return

Notepad:
Run, notepad.exe
Return
Explorer:
Run, explorer.exe
Return
Calculator:
Run, calc.exe
Return
ControlPanel:
Run, control.exe
Return
Display:
Run, desk.cpl
Return
Windows:
Run, explorer.exe "C:\Windows"
Return
ProgramFiles:
Run, explorer.exe "%A_ProgramFiles%"
Return
Desktop:
Run, explorer.exe "%A_Desktop%"
Return

RapidHotkey(keystroke, times="", delay=0.15, 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 = 1
	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, "[\*\~\$\#\+\!\^]")
	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
}	
Morse(timeout = 400) { ;by Laszo -> http://www.autohotkey.com/forum/viewtopic.php?t=16951 (Modified to return: KeyWait %key%, T%tout%)
   static lastkey
   tout := timeout/1000
   key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^]")
   Loop {
      t := A_TickCount
      KeyWait %key%, T%tout%
	  Pattern .= A_TickCount-t > timeout
	  If(ErrorLevel)
		Return Pattern
	  KeyWait %key%,DT%tout%
      If (ErrorLevel)
         Return Pattern
   }
}


EricCartman
  • Members
  • 60 posts
  • Last active: Oct 26 2010 12:50 PM
  • Joined: 13 Oct 2008
You guys are my new heroes.

With all of the heavy lifting done by HotKeyIt, Laszlo, and ManaUser, the following script disables the Caps Lock key on one press, brings up ManaUser's super handy capitalization utility on two presses, and toggles the CapsLock state on three presses. I get credit only for hating the Caps Lock key and being able to copy and paste.

; RapidHotkey:
; http://www.autohotkey.com/forum/viewtopic.php?t=38795
; by HotKeyIt
;
; Morse:
; http://www.autohotkey.com/forum/viewtopic.php?t=16951
; by Laszlo
;
; Cap Menu and string manipulation:
; http://www.autohotkey.com/forum/topic27797.html
; by ManaUser

Menu Case, Add, &UPPERCASE, CCase
Menu Case, Add, &lowercase, CCase
Menu Case, Add, &Title Case, CCase
Menu Case, Add, &Sentence case, CCase
Menu Case, Add
Menu Case, Add, &Fix Linebreaks, CCase
Menu Case, Add, &Reverse, CCase

CapsLock::RapidHotkey("CapMenu" Chr(4) "ToggleCapsLock", 2, 0.1,1)

CapMenu:
GetText(TempText)
If NOT ERRORLEVEL
   Menu Case, Show
Return

CCase:
If (A_ThisMenuItemPos = 1)
   StringUpper, TempText, TempText
Else If (A_ThisMenuItemPos = 2)
   StringLower, TempText, TempText
Else If (A_ThisMenuItemPos = 3)
   StringLower, TempText, TempText, T
Else If (A_ThisMenuItemPos = 4)
{
   StringLower, TempText, TempText
   TempText := RegExReplace(TempText, "((?:^|[.!?]\s+)[a-z])", "$u1")
} ;Seperator, no 5
Else If (A_ThisMenuItemPos = 6)
{
   TempText := RegExReplace(TempText, "\R", "`r`n")
}
Else If (A_ThisMenuItemPos = 7)
{
   Temp2 =
   StringReplace, TempText, TempText, `r`n, % Chr(29), All
   Loop Parse, TempText
      Temp2 := A_LoopField . Temp2
   StringReplace, TempText, Temp2, % Chr(29), `r`n, All
}
PutText(TempText)
Return

;******************************************************************************
; Copies the selected text to a variable while PRESERVING the clipboard.
;******************************************************************************

GetText(ByRef MyText = "")
{
   SavedClip := ClipboardAll
   Clipboard =
   Send ^c
   ClipWait 0.5
   If ERRORLEVEL
   {
      Clipboard := SavedClip
      MyText =
      Return
   }
   MyText := Clipboard
   Clipboard := SavedClip
   Return MyText
}

; Pastes text from a variable while preserving the clipboard.
PutText(MyText)
{
   SavedClip := ClipboardAll 
   Clipboard =              ; For better compatability
   Sleep 20                 ; with Clipboard History
   Clipboard := MyText
   Send ^v
   Sleep 100
   Clipboard := SavedClip
   Return
}

;This makes sure sure the same window stays active after showing the InputBox.
;Otherwise you might get the text pasted into another window unexpectedly.
SafeInput(Title, Prompt, Default = "")
{
   ActiveWin := WinExist("A")
   InputBox OutPut, %Title%, %Prompt%,,, 120,,,,, %Default%
   WinActivate ahk_id %ActiveWin%
   Return OutPut
}

;This checks if a window is, in fact a window.
;As opposed to the desktop or a menu, etc.
IsWindow(hwnd) 
{
   WinGet, s, Style, ahk_id %hwnd% 
   return s & 0xC00000 ? (s & 0x80000000 ? 0 : 1) : 0
   ;WS_CAPTION AND !WS_POPUP(for tooltips etc) 
}

RapidHotkey(keystroke, times="", delay=0.1, IsLabel=0)
{
   ;Suspend, On
   Pattern := Morse(delay*1000)
   If (StrLen(Pattern) < 2 and Chr(Asc(times)) != "1")
      Return
   If (times = "" and InStr(keystroke, Chr(4)))
   {
      Loop, Parse, keystroke,% Chr(4)   
         If (StrLen(Pattern) = A_Index+1)
            continue := A_Index, times := StrLen(Pattern)
   }
   Else if (RegExMatch(times, "^\d+$") and InStr(keystroke, Chr(4)))
   {
      Loop, Parse, keystroke,% Chr(4)   
         If (StrLen(Pattern) = A_Index+times-1)
            times := StrLen(Pattern), continue := A_Index
   }
   Else if InStr(times, Chr(4))
   {
      Loop, Parse, times,% Chr(4)
         If (StrLen(Pattern) = A_LoopField)
            continue := A_Index, times := A_LoopField
   }
   Else if (times = "")
      continue := 1, times := 1
   Else if (times = StrLen(Pattern))
      continue := 1
   If !continue
      Return
   Loop, Parse, keystroke,% Chr(4)
      If (continue = A_Index)
         keystr := A_LoopField
   Loop, Parse, IsLabel,% Chr(4)
      If (continue = A_Index)
         IsLabel := A_LoopField
   hotkey := RegExReplace(A_ThisHotkey, "[\*\~\$\#\+\!\^]")
   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
   , "\^[^\!\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
}   
Morse(timeout = 400) { ;by Laszo -> http://www.autohotkey.com/forum/viewtopic.php?t=16951
   tout := timeout/1000
   key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^]")
   Loop {
      t := A_TickCount
      KeyWait %key%
      Pattern .= A_TickCount-t > timeout
      KeyWait %key%,DT%tout%
      If (ErrorLevel)
         Return Pattern
   }
}

ToggleCapsLock:
state := GetKeyState("Capslock", "T")
If (state = False)
SetCapsLockState, On
Else
SetCapsLockState, Off


lilalurl.T32
  • Members
  • 391 posts
  • Last active: Jul 05 2011 03:39 PM
  • Joined: 17 May 2007
Nice work Hotkeyit.
________
MICHIGAN MEDICAL MARIJUANA DISPENSARY

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008

Nice work Hotkeyit.

Thanks, I'm glad you find it useful. :)

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
I've written a NumBoard for fun so you can type one handed using numpad

NumBoard

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
RapidHotkey can now be triggered on a combination of hotkeys as well. For example:
~LButton & RButton::RapidHotkey("Menu1""Menu2""Menu3",1,0.3,1)
Menu1:
Menu2:
Menu3:
MsgBox % A_ThisLabel
Return

;#####################################
;RapidHotkey() and Morse() functions #
;#####################################
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)
   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
}   
Morse(timeout = 400) { ;by Laszo -> http://www.autohotkey.com/forum/viewtopic.php?t=16951 (Modified to return: KeyWait %key%, T%tout%)
   tout := timeout/1000
   key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^]")
   IfInString, key, %A_Space%
   StringTrimLeft, key, key,% InStr(key,A_Space,1,0)
   Loop {
      t := A_TickCount
      KeyWait %key%, T%tout%
     Pattern .= A_TickCount-t > timeout
     If(ErrorLevel)
      Return Pattern
     KeyWait %key%,DT%tout%
      If (ErrorLevel)
         Return Pattern
   }
}


meep.com
  • Members
  • 5 posts
  • Last active: Jun 06 2009 01:52 AM
  • Joined: 11 Feb 2009
When I copied your code into a script it gave me an error:


Error at line 1.
Invalid Hotkey


How do I fix it?

Thanks

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
You have to include both functions in your script or include RapidHotkey.ahk and Morse.ahk in your Lib directories or:
#include RapidHotkey.ahk
#include Morse.ahk


DAT
  • Members
  • 58 posts
  • Last active: Jul 17 2014 08:55 AM
  • Joined: 01 Dec 2008
Belated thanks to Hotkeyit for an extremely useful script. I've been using it a lot for making double or triple clicks of Shift, LCtrl, RCtrl, CapsLock do useful things like opening and closing various search windows (http://www.autohotke...topic46083.html)

With the original version I get occasional false activations. It happens when I'm typing fast and happen to press a normal key in between two presses of the special key.

I've solved this by slightly modifying Morse() so that it detects an intervening alien key and aborts the multiple key press. To make it work I have to define all the possible alien keys as hotkeys. I can then use A_ThisHotkey inside the Morse loop to see if the last hotkey is an alien.

To use it all you have to do is call subroutine DefHotkeys near the beginning of your application program and replace Morse in your RapidHotkey function with the modified version here. The modified RapidHotkey works like the unmodified version with main programs that don't run DefHotkeys, but of course you lose the protection against false alarms.

It seems a bit crude to have to make all these 'do-nothing' hotkey assigments but since they don't conflict with any assignments in other scripts, it appears to be only an aesthetic issue. Thanks to Laszlo for the guts of DefHotkeys. References are in the script below.

There's also a test section that demonstrates it working. To use it run the script and try rapidly tapping the Right-Control key once, twice, thrice, etc.

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

Gosub , DefHotkeys
~RControl::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
}
; end of test section
;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;; 
; Subroutine to define lots of keys as 'do-nothing' hotkeys.
; As described by Laszlo's at http://www.autohotkey.com/forum/topic17614.html 
; and http://www.autohotkey.com/forum/topic7081.html. 
;
DefHotkeys:
keys = ``1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./ 
Loop Parse, keys 
   HotKey , ~*%A_LoopField%, DoNothing
SpecialKeys =LShift RShift LCtrl RCtrl LAlt RAlt LWin RWin  ; Add as required
Loop Parse, SpecialKeys , %A_Space%
   HotKey , ~*%A_LoopField%, DoNothing

DoNothing:
; SoundBeep , 3000, 10
Return
;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;
/*RapidHotkey() is unchanged from the 24.02.2009 version posted by
Hotkeyit at http://www.autohotkey.com/forum/viewtopic.php?t=38795 
but Morse is modified slightly from Laszlo's original at 
http://www.autohotkey.com/forum/viewtopic.php?t=16951.  The morse 
code aspect has gone - it just detects multiple hits of the same key 
that are separated by less than 'timeout'. It returns 0 for one hit, 
00 for two, and so on. It now returns blank if any other key is 
detected during what would otherwise count as a multiple key-press.  
*/

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

;;;;;;;
; Based on Morse by Laszlo at http://www.autohotkey.com/forum/viewtopic.php?t=16951.  
; Modified by DAT 14-09-2009 to avoid false alarms 
; (Modified lines are marked DAT).
; 
Morse(timeout = 400) {  
   tout := timeout/1000 
   CurrentKey = %A_ThisHotKey%  ; DAT: Stored so can trap wrong hotkey within the loop
   key := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^]") 
   IfInString, key, %A_Space% 
   StringTrimLeft, key, key,% InStr(key,A_Space,1,0) 
   Loop 
   {                         ; Loops until KeyWait test fails, or 'alien' hotkey spotted.
      t := A_TickCount 
      KeyWait %key%, T%tout%                ; Wait for key to be released.
      Pattern .= 0                          ; DAT: 'Morse' aspect not being used anyway...
      If(ErrorLevel)                        
         Return Pattern                     ; It timed out, so finish.
      KeyWait %key%,DT%tout%    ; Wait for next key-down...
      If (ErrorLevel)                       ; and allow to loop if key repeats,
         Return Pattern                     ; otherwise finish if it times out.
      IfNotEqual, A_ThisHotkey, %CurrentKey%  ; DAT: Abort if another key has intervened
      {
         Pattern = 
         Return Pattern 
      }
   }
}
;;;;;;;;;;;;;;;;;;;;;;;;


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
If you don't type too fast, that Input misses keystrokes, the following version of Morse also brakes out at foreign keys, but it is simpler than DAT's solution.
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" || k != key) ; Break at long no-press time or foreign keys

         Return Pattern

   }

}
If the key we watch is in the list of the endkeys, some more work is needed.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
... e.g. this version works with the Backspace key. (Don't use the BS shorthand!)
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

   }

}



+Backspace::MsgBox % "Morse press pattern " Morse()

#!z::MsgBox % "Morse press pattern " Morse()