Handling single, double, triple, quadruple... infinite key presses.

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
GollyJer
Posts: 61
Joined: 19 Sep 2015, 19:33
Contact:

Handling single, double, triple, quadruple... infinite key presses.

18 Nov 2017, 13:43

I wanted an easy way to handle multiple presses of a hotkey. As far as I could Google there weren't any good solutions, so I dug in created this one. :P

It took a lot of reading and learning along the way and uses a couple techniques I don't see much on the forums. It also relies on the ErrorLevel returned from KeyWait, which is useful to understand.

Without further ado... here is...
HandleMultiplePresses()

Code: Select all

HandleMultiplePresses(pressHandlerNames*) {
  Recurse:

  keyPresses += 1
  strippedHotkey := StripHotkeyModifiers(A_ThisHotkey)
  KeyWait, %strippedHotkey%         ; Wait for KeyUp. 
  KeyWait, %strippedHotkey%, D T.12 ; Wait for same KeyDown or .12 seconds to elapse.
  keyPressedBeforeTimeout := (ErrorLevel = 0)

  If (keyPressedBeforeTimeout) {
    Goto, Recurse
  }
  
  Return pressHandlerNames[keyPresses]()
}

StripHotkeyModifiers(hotkeyToStrip) {
  ; from https://autohotkey.com/board/topic/32973-func-waitthishotkey/
  RegExMatch(hotkeyToStrip, "i)(?:[~#!<>\*\+\^\$]*([^ ]+)( UP)?)$", Key)
  Return Key1, ErrorLevel := (Key2 ? "Down" : "Up")
}
How do you use it?
After a hotkey, call HandleMultiplePresses with the number of function names matched to the number of key press counts you want to handle. The order defines the handler.
2 presses? Handled by the second parameter. 3? The third. etc.
An example will help.

Code: Select all

F1:: HandleMultiplePresses("SayGo", "SayFight", "SayWin")
^F1:: HandleMultiplePresses("SayGo", "SayFight", "SayWin")
F2:: HandleMultiplePresses("SayWin", "SayGo")

SayGo() {
  MsgBox, %A_ThisHotkey% says Go!
}

SayFight() {
  MsgBox, %A_ThisHotkey% says Fight!
}

SayWin() {
  MsgBox, %A_ThisHotkey% says Win!
}
I'm certain I'm missing something and this can be improved. But, it works pretty great.
I'll keep the post up to date with any improvements. Feedback welcome!
Last edited by GollyJer on 25 Apr 2018, 19:37, edited 3 times in total.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Handling single, double, triple, quadruple... infinite key presses.

18 Nov 2017, 14:53

Very nice, well done :thumbup: .
Suggestion, on the last press, call the function without waiting, eg

Code: Select all

keyPresses += 1
if (keyPresses == pressHandlerNames.length())
	return pressHandlerNames[keyPresses]()
You might aswell use return, if the function returns something.

Cheers, thanks for sharing.
User avatar
GollyJer
Posts: 61
Joined: 19 Sep 2015, 19:33
Contact:

Re: Handling single, double, triple, quadruple... infinite key presses.

18 Nov 2017, 15:17

Very nice, well done :thumbup: .
Hi Helgef... thanks.
Suggestion, on the last press, call the function without waiting?
I would consider this unexpected behavior. What if some game needs me to press the key 20 times but I only want to do something special up to 3 times?
You might aswell use return, if the function returns something.
I don't see how it can hurt... code updated.
Cheers, thanks for sharing.
You're welcome!
User avatar
rommmcek
Posts: 1470
Joined: 15 Aug 2014, 15:18

Re: Handling single, double, triple, quadruple... infinite key presses.

21 Nov 2017, 21:17

Exorbitant! Specially I like this behavior:

Code: Select all

$F1:: HandleMultiplePresses("F1", "SayFight", "SayWin", "", "", "", "", "SayGo")
F1(){
	Send, {F1}
}
preserving native key response and not throwing an error for empty function "" !
I modified to:

Code: Select all

KeyWait, %strippedHotkey%, D T.3
Works fine for me, but have you any concerns about it?

Thanks!
User avatar
GollyJer
Posts: 61
Joined: 19 Sep 2015, 19:33
Contact:

Re: Handling single, double, triple, quadruple... infinite key presses.

22 Nov 2017, 15:58

Exorbitant! Specially I like this behavior:

Code: Select all

$F1:: HandleMultiplePresses("F1", "SayFight", "SayWin", "", "", "", "", "SayGo")
F1(){
	Send, {F1}
}
preserving native key response and not throwing an error for empty function "" !
Happy to know you like it!
And yes. That behavior is exactly what I was going for... the flexibility to handle (or not) any press sequence you desire.
I modified to:

Code: Select all

KeyWait, %strippedHotkey%, D T.3
Works fine for me, but have you any concerns about it?
No concerns. I chose a wait time that didn't feel long after a single press and I could achieve multiple presses without making a mistake.
fenchai
Posts: 292
Joined: 28 Mar 2016, 07:57

Re: Handling single, double, triple, quadruple... infinite key presses.

22 Nov 2017, 17:51

Same as RapidHotkey() ?
I think RapidHotkey even allows sending texts, subs, actions.

but still pretty awesome work
User avatar
GollyJer
Posts: 61
Joined: 19 Sep 2015, 19:33
Contact:

Re: Handling single, double, triple, quadruple... infinite key presses.

27 Nov 2017, 20:51

fenchai wrote:Same as RapidHotkey() ?
I think RapidHotkey even allows sending texts, subs, actions.

but still pretty awesome work
HandleMultiplePresses allows you to define ANY function. So it can send texts, subs, actions... or am I missing what you mean?

It seems RapidHotkey does the same thing but is significantly more complicated. Also it doesn't, as far as I can tell, allow for different functions to be executed depending on the press count.
For those interested RapidHotkey is here.
fenchai
Posts: 292
Joined: 28 Mar 2016, 07:57

Re: Handling single, double, triple, quadruple... infinite key presses.

28 Nov 2017, 13:48

GollyJer wrote:
fenchai wrote:Same as RapidHotkey() ?
I think RapidHotkey even allows sending texts, subs, actions.

but still pretty awesome work
HandleMultiplePresses allows you to define ANY function. So it can send texts, subs, actions... or am I missing what you mean?

It seems RapidHotkey does the same thing but is significantly more complicated. Also it doesn't, as far as I can tell, allow for different functions to be executed depending on the press count.
For those interested RapidHotkey is here.
I think RapidHotkey is more complicated but allows more customization while yours is simpler but has no way to change the timing of how long is double press and so on.

But I still would use yours for simplicity.
temoridao
Posts: 5
Joined: 28 Jun 2020, 14:21
Contact:

Re: Handling single, double, triple, quadruple... infinite key presses.

16 Nov 2020, 09:55

For those who interested, I have refactored the function. Added customizable delay between hotkeys and some optimizations ;)
It may be called almost in the same way as in the first post, but accepts linear array, or key-value object as first parameter instead of variadic parameters.
Self-contained example:

Code: Select all

   ;Try press Ctrl+T from 1 to 6 times.
   ^t::MsgBox % "Handler's return value: "
              . HandleMultiPressHotkey({1: FSend("^t") ;Single-press: retain original hotkey action (Ctrl+T)
       , 2: "MyMsgBox"               ;2-press. result: ""
       , 3: _F("MyMsgBox", 3)        ;3-press. result: 8
       , 4: _F("Run", "notepad.exe") ;4-press: launch Notepad. result: process ID (PID) of newly launched notepad instance
                                     ;5-press: skip  intentionally, do nothing. result: ""
       , 6: _F("ExitApp", 43)})      ;6-press: exit script with code 43
       ;, 150: _F("MyMsgBox", "Can you do this?!")}) ;150-press: Can you complete this? :)

   _F(funcName, params*) {
   	return Func(funcName).Bind(params*)
   }
   MyMsgBox(pressCount := "some") {
   	MsgBox % A_ThisHotkey " hotkey pressed " pressCount " times!"
   	return pressCount + 5
   }
   ExitApp(exitCode := 0) {
   	ExitApp exitCode
   }
   Run(Target, WorkingDir := "", Mode := "") {
   	Run %Target%, %WorkingDir%, %Mode%, v
   	Return v
   }
   Send(keys) {
   	Send % keys
   }
   FSend(keys) {
   	return Func("Send").Bind(keys)
   }
   
HandleMultiPressHotkey(pressHandlers, keyWaitDelay := 150) {
	strippedHotkey := RegExReplace(A_ThisHotkey, "i)(?:[~#!<>\*\+\^\$]*([^ ]+)(?: UP)?)$", "$1")
	keyPresses := 0
	keyPressedBeforeTimeout := false
	options := "DT" keyWaitDelay / 1000
	Loop {
		++keyPresses
		KeyWait, %strippedHotkey%            ; Wait for KeyUp.
		KeyWait, %strippedHotkey%, %options% ; Wait for same KeyDown or `keyWaitDelay` to elapse.
		keyPressedBeforeTimeout := (ErrorLevel = 0)
	} Until !keyPressedBeforeTimeout

	if (!pressHandlers.HasKey(keyPresses)) {
		return ""
	}

	f := pressHandlers[keyPresses]
	if (!IsObject(f)) { ;If not Func or BoundFunc object (i.e. just a string containing function name)
		f := Func(f)
	}
	return f ? f.Call() : "" ;Test Func object for validity/existence before calling
}
The most up-to-date version (with detailed documentation at the top of function) you can find here: https://github.com/temoridao/ahk/blob/master/Lib/Funcs.ahk
User avatar
rommmcek
Posts: 1470
Joined: 15 Aug 2014, 15:18

Re: Handling single, double, triple, quadruple... infinite key presses.

28 Feb 2024, 15:59

I love HandleMultiplePresses. Here is my v2 version. Not one to one translation, but should have the same functionality.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 147 guests