TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick buttons

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
SteveMylo
Posts: 233
Joined: 22 Jun 2021, 00:50
Location: Australia
Contact:

Re: TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick button

Post by SteveMylo » 17 Mar 2023, 18:35

@Ralf_Reddings200244 a simple please would be nice. :crazy:


User avatar
SteveMylo
Posts: 233
Joined: 22 Jun 2021, 00:50
Location: Australia
Contact:

Re: TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick button

Post by SteveMylo » 19 Mar 2023, 15:32

@Ralf_Reddings200244 oh. It said “pease” & I mustn’t have realised sorry

gaberiel__2
Posts: 14
Joined: 18 Jan 2023, 14:18

Re: TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick button

Post by gaberiel__2 » 08 Apr 2023, 05:24

Any news on TapHoldManager for AutoHotkey V2?? We really neeed an update on this!! :D

Epoch
Posts: 41
Joined: 04 Jun 2021, 11:09

Re: TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick button

Post by Epoch » 19 Apr 2023, 19:19

SteveMylo wrote:
03 Feb 2023, 19:59
@Epoch Wow big question.
Firstly it took me months to figure out this stuff :lol:

Right, 1st thing you need to do:
During testing I would only use standard keyboard keys & not F13.
that way when everything works you can then use F13 and you will know if F13 is the issue or not.

2nd:
Definitely do not put multiple scripts in a mega script.
I used to do that with THM and it's a very bad idea.
And you are suffering from that now. If you miss just one curly bracket anywhere "{" , it'll come up as an error or it says functions can't be in functions and it WON'T tell you which line the ERROR is in, if it does it is wrong. It's a nightmare.
You will learn faster by having separate scripts for every single key.

3rd:
You have to put all other #include files underneath all the THM functions.
That way FindText function can still work: See below

Code: Select all

#Include <TapHoldManager>

thm := new TapHoldManager(,,,,"ahk_group MyGroup") 
thm.Add("z", Func("Z_test"),,,2) 

#Include <Vector>
#Include <FindText>
#Include <SoundBeep>
#include <UIA_Interface>
#Include <ToolTipFM>

; Now start the rest of your script below:
4th:
Have a look at my context sensitivity script below:
I made it complex on purpose to cover everything you might need.
If you are using more than one window title, then make it a group.
Here I used the "z" key twice just to show you the power of context sensitivity.
The Double Tap and Hold only works in "myGroup" Windows
and single Tap "z" only works in "ahk_exe chrome.exe"

Code: Select all

GroupAdd, MyGroup, ahk_exe Notepad.exe
GroupAdd, MyGroup, ahk_exe Evernote.exe

#Include <TapHoldManager>
thm := new TapHoldManager(,,,,"ahk_group MyGroup") 
thm.Add("z", Func("Z_test"),,,2) ; ; Douple tap "z" only takes effect in "MyGroup" Active windows

thm2 := new TapHoldManager(,,,,"ahk_exe chrome.exe") 
thm2.Add("z", Func("Z_test2"),,,1) ; Single tap funciton "z" only takes effect in "ahk_exe chrome.exe" Active window
; I put the number '2' in 3 different spots above to make sure the second 'z' funciton/command have different names so there is no conflict, e.g... thm2 / thm2.Add / Z_test2

; below is where you HAVE TO put the REST of the #include files, BELOW the THM stuff.
#Include <HideCursor>
#Include <Vector>
#Include <FindText>
#Include <SoundBeep>
#include <UIA_Interface>
#Include <ToolTipFM>

; the THM commands can start here:

Z_test(isHold, taps, state){     ; Z_test Function is associated with "ahk_group MyGroup" context sensitivity
if (isHold=1) & (taps=2) & (state=1){ ; double press and hold
soundbeep
return
}
}
return

Z_test2(isHold, taps, state){    ; Z_test2 Function is associated with "ahk_exe chrome.exe" context sensitivity
if (isHold=0) & (taps=1) & (state){ ; single Tap
soundbeep
soundbeep
return
}
}

return
~Esc::ExitApp
5th:
As for how the tap and Hold works: It's easy once you know.
* For just TAPS and no HOLD's , then HOLD must always = "0" and (state) should have nothing at all ===> if (isHold=0) & (taps=3) & (state)
* If you are adding a HOLD, then HOLD has to =1 and STATE has to =1 or 0
(state=0) means the hotkey triggers straight away:
And (state=1) means hotkey triggers after releasing the hotkey.
Which I love which is like a Keywait. ===> if (isHold=1) & (taps=3) & (state=1) ; this line means tap 3times but on the 3rd just leave your finger held down and it'll fire after 150ms default

So I notice a mistake in your code in the Space Funciton. state has to be state=0 OR state=1 whenever this is a HOLD

Code: Select all

if (isHold=1) & (taps=1) & (state)  ; <===  state has to be state=0   OR   state=1
{
ToolTip One Hold!
Sleep 500
ToolTip
Return
}

6th:
you have to make sure if you don't know already, that the Curley brackets are all there
And have two on the end, otherwise you will get an error saying " you can't have functions inside functions" .

Code: Select all

Z_test(isHold, taps, state){     ;  Curly bracket here
if (isHold=1) & (taps=2) & (state=1){  ; another Curly bracket here
soundbeep
return
} ; this curly bracket closes off the this line  ===> if (isHold=1) & (taps=2) & (state=1)

; this is where you add anymore Tap functions like if (isHold=0) & (taps=1) & (state)

}  ; This last curly bracket closes of the particular Hotkey Funciton. 

return
What a glorious reply, this helped me getting a better understanding of how THM works which led in putting together a script from scratch, just for THM stuff.
I wanted to thank you once again for all your assistance, it's something I don't take for granted and you’ve been one of the most helpful and nice blokes around here. And even if it is supposedly getting better and better in writing code, that’s something you just don’t get with ChatGPT! :)

I remember trying to get over some issues I’ve encountered on my own, refraining from asking questions as much as I can before I get back to you.
Then, as we all know, life has the habit of getting in the way... So, I feel bad for not doing so earlier, but I wanted to sincerely thank you once more!

User avatar
SteveMylo
Posts: 233
Joined: 22 Jun 2021, 00:50
Location: Australia
Contact:

Re: TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick button

Post by SteveMylo » 06 Jun 2023, 15:49

@Epoch Hey no problem, so many people helped me on here too :-)

ntepa
Posts: 406
Joined: 19 Oct 2022, 20:52

Re: TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick button

Post by ntepa » 10 Jun 2023, 04:27

Here's v2 conversion:

Code: Select all

#Requires AutoHotkey v2.0
thm := TapHoldManager()
thm.Add("1", MyFunc1)
thm.Add("2", MyFunc2, 250, 500, 2,, "ahk_exe notepad.exe")

MyFunc1(isHold, taps, state){
	ToolTip "key: 1`n" (isHold ? "HOLD" : "TAP") "`nTaps: " taps "`nState: " state
}

MyFunc2(isHold, taps, state){
	ToolTip "key: 2`n" (isHold ? "HOLD" : "TAP") "`nTaps: " taps "`nState: " state
}

$F1:: ; pause hotkey "2"
{
	thm.PauseHotkey("2")
}

$F2:: ; resume hotkey "2"
{
	thm.ResumeHotkey("2")
}

$F3:: ; remove hotkey "2"
{
	thm.RemoveHotkey("2")
}

Code: Select all

class TapHoldManager {
	Bindings := Map(), Bindings.CaseSense := "Off"

	__New(tapTime := 150, holdTime := tapTime, maxTaps := -1, prefixes := "$", window := ""){
		this.tapTime := tapTime
		this.holdTime := holdTime
		this.maxTaps := maxTaps
		this.prefixes := prefixes
		this.window := window
	}

	Add(keyName, callback, tapTime?, holdTime?, maxTaps?, prefixes?, window?){    ; Add hotkey
		if this.Bindings.Has(keyName)
			this.RemoveHotkey(keyName)
		this.Bindings[keyName] := TapHoldManager.KeyManager(keyName, callback, tapTime ?? this.tapTime, holdTime ?? this.holdTime, maxTaps ?? this.maxTaps, prefixes ?? this.prefixes, window ?? this.window)
	}

	RemoveHotkey(keyName){ ; to remove hotkey
		this.Bindings.Delete(keyName).SetState(0)
	}

	PauseHotkey(keyName){ ; to pause hotkey temprarily
		this.Bindings[keyName].SetState(0)
	}

	ResumeHotkey(keyName){ ; resume previously deactivated hotkey
		this.Bindings[keyName].SetState(1)
	}

	class KeyManager {
		state := 0					; Current state of the key
		sequence := 0				; Number of taps so far
		holdActive := 0				; A hold was activated and we are waiting for the release

		__New(keyName, Callback, tapTime, holdTime, maxTaps, prefixes, window){
			this.keyName := keyName
			this.Callback := Callback
			this.tapTime := tapTime
			this.holdTime := holdTime
			this.maxTaps := maxTaps
			this.prefixes := prefixes
			this.window := window

			this.HoldWatcherFn := this.HoldWatcher.Bind(this)
			this.TapWatcherFn := this.TapWatcher.Bind(this)
			this.JoyWatcherFn := this.JoyButtonWatcher.Bind(this)
			this.DeclareHotkeys()
		}

		DeclareHotkeys(){
			if (this.window)
				HotIfWinactive this.window ; sets the hotkey window context if window option is passed-in

			Hotkey this.prefixes this.keyName, this.KeyEvent.Bind(this, 1), "On" ; On option is important in case hotkey previously defined and turned off.
			if (this.keyName ~= "i)^\d*Joy"){
				Hotkey this.keyName " up", (*) => SetTimer(this.JoyWatcherFn, 10), "On"
			} else {
				Hotkey this.prefixes this.keyName " up", this.KeyEvent.Bind(this, 0), "On"
			}

			if (this.window)
				HotIfWinactive ; restores hotkey window context to default
		}

		SetState(state){ ; turns On/Off hotkeys (should be previously declared) // state is either "1: On" or "0: Off"
			; "state" under this method context refers to whether the hotkey will be turned on or off, while in other methods context "state" refers to the current activity on the hotkey (whether it's pressed or released (after a tap or hold))
			if (this.window)
				HotIfWinactive this.window

			state := (state ? "On" : "Off")
			Hotkey this.prefixes this.keyName, state
			if (this.keyName ~= "i)^\d*Joy"){
				Hotkey this.keyName " up", state
			} else {
				Hotkey this.prefixes this.keyName " up", state
			}

			if (this.window)
				HotIfWinactive
		}

		JoyButtonWatcher(){
			if GetKeyState(this.keyName)
				return
			SetTimer this.JoyWatcherFn, 0
			this.KeyEvent(0)
		}

		KeyEvent(state, *){
			if (state == this.state)
				return	; Suppress Repeats
			this.state := state
			if (state){
				; Key went down
				this.sequence++
				SetTimer this.HoldWatcherFn, -this.holdTime
			} else {
				; Key went up
				SetTimer this.holdWatcherFn, 0
				if (this.holdActive){
					this.holdActive := 0
					SetTimer this.FireCallback.Bind(this, this.sequence, 0), -1
					this.sequence := 0
					return
				}
				if (this.maxTaps > 0 && this.Sequence == this.maxTaps){
					SetTimer this.tapWatcherFn, 0
					SetTimer this.FireCallback.Bind(this, this.sequence, -1), -1
					this.sequence := 0
				} else {
					SetTimer this.tapWatcherFn, -this.tapTime
				}
			}
		}

		; If this function fires, a key was held for longer than the tap timeout, so engage hold mode
		HoldWatcher(){
			if (this.sequence > 0 && this.state == 1){
				; Got to end of tapTime after first press, and still held.
				; HOLD PRESS
				SetTimer this.FireCallback.Bind(this, this.sequence, 1), -1
				this.holdActive := 1
			}
		}

		; If this function fires, a key was released and we got to the end of the tap timeout, but no press was seen
		TapWatcher(){
			if (this.sequence > 0 && this.state == 0){
				; TAP
				SetTimer this.FireCallback.Bind(this, this.sequence), -1
				this.sequence := 0
			}
		}

		FireCallback(seq, state := -1){
			this.Callback.Call(state != -1, seq, state)
		}
	}
}

Ralf_Reddings200244
Posts: 85
Joined: 11 Mar 2023, 14:16

Re: TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick button

Post by Ralf_Reddings200244 » 11 Jun 2023, 07:10

@ntepa
Wow, I was not expecting this at all. Seems to be working fine too!
Thank you so much man! Unbalievable.

By the way you should consider releasing this on the V2 section! This is a much needed port

LAPIII
Posts: 667
Joined: 01 Aug 2021, 06:01

Re: TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick button

Post by LAPIII » 11 Jun 2023, 09:50

Yeah and make a repository on Github. Thanks for this.

gaberiel__2
Posts: 14
Joined: 18 Jan 2023, 14:18

Re: TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick button

Post by gaberiel__2 » 14 Jun 2023, 07:59

ntepa wrote:
10 Jun 2023, 04:27
Here's v2 conversion:
Wow, Wow. I cannot thank you enough for this. V2 here I come.

milliard
Posts: 8
Joined: 26 Feb 2022, 21:11

Re: TapHoldManager - Long Press / Multi Tap / Multi Tap and Hold / Any number of Taps / Multi-Keyboard / Joystick button

Post by milliard » 21 Aug 2023, 21:25

What about porting the InterceptionTapHold.ahk file to v2? :)

Post Reply

Return to “Scripts and Functions (v1)”