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
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

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

Post by evilC » 28 Aug 2022, 15:08

@Epoch #IfWinActive is a context mode settings for mappings in the form of a::b (ie ones that are effectively directives, and not part of the flow of code.

you need to use Hotkey, IfWinActive, ahk_exe explorer.exe on a line BEFORE a THM subscription, like so:

Code: Select all

thm := new TapHoldManager(150, 300, , "$*") ; 
Hotkey, IfWinActive, ahk_exe explorer.exe ; All subsequent thm.Add subscriptions will only fire for explorer.
thm.Add("a", Func("DefaultFunction"))

DefaultFunction(isHold, taps, state){
	if (!state)
		return ; Only take action on key press, not key release (prevents sending actions twice)
	if (isHold){
		; Holds
		if (taps == 1){
			msgbox You have tapped and  held "a".
		} else if (taps == 2){
			msgbox You have tapped "a" 2 times and held the second tap.
		}
	} else {
		; Taps
		if (taps == 1){
			msgbox You have tapped "a" 1 time.
		} else if (taps == 2){
			msgbox You have tapped "a" 2 times.
		}
	}
}


User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

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

Post by evilC » 28 Aug 2022, 15:20

@Jasonosaj
Everything you mentioned is as I would expect the code to work.
There is no such thing as "Pressing two keys at the same time" insofar as it is impossible to hold two keys EXACTLY at the same time.
It sounds to me that what you want is something like Holding Caps to enter a mode, then when you hit another key (p, w, c, s etc), some special action is taken.
In which case, that's a normal AHK hotkey surely, not a job for THM? You could just use THM to detect the double-tap, but to do the normal combinations, use normal AHK hotkeys?

Or you could just do something like this:

Code: Select all

	; INITIALIZATION
	; ...
	CapsHeld := 0
	thm := new TapHoldManager(,,, "$")
	thm.Add("Capslock", Func("CapsHotkeyFunc"))
	Return

	; ... [

#If CapsHeld

p::
	if WinExist("ahk_class dopus.lister") {
		WinActivate
		Run, G:\Downloads
		}
	else {
		Run, G:\Downloads
		}
	}
	return

w::
	if WinExist("ahk_class dopus.lister") {
		WinActivate
		Run, C:\Users\jason\OneDrive - Work\8 - Work Downloads
		}
	else {
		Run, C:\Users\jason\OneDrive - Work\8 - Work Downloads
		}
	}
#If

	capsHotkeyFunc(isHold, taps, state) {
		global CapsHeld
		If (isHold) {
			CapsHeld := state
			}
		Else If (taps == 2) {
			QuickClip(0.5)
			F1HotkeyFunction(Stringtemp)
			}
		Return

		; ...

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 » 28 Aug 2022, 19:09

Epoch wrote:
28 Aug 2022, 11:48
1. Having a context-sensitive TapHoldManager instance, for example having its hotkey taking effect only on windows explorer. Simply starting with #if Winactive, as in the following example doesn't seem to work.
I can help you with your 1st question.
Context sensitivity has always been tricky with THM. But that's why the genius EvilC made it easy for us.
Just add the program to this part :arrow: thm := new TapHoldManager(150, 300, , "$*","ahk_exe explorer.exe") )

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 » 28 Aug 2022, 20:58

evilC wrote:
28 Aug 2022, 15:08

@Epoch #IfWinActive is a context mode settings for mappings in the form of a::b (ie ones that are effectively directives, and not part of the flow of code.

you need to use Hotkey, IfWinActive, ahk_exe explorer.exe on a line BEFORE a THM subscription, like so:

Code: Select all

thm := new TapHoldManager(150, 300, , "$*") ; 
Hotkey, IfWinActive, ahk_exe explorer.exe ; All subsequent thm.Add subscriptions will only fire for explorer.
thm.Add("a", Func("DefaultFunction"))

DefaultFunction(isHold, taps, state){
	if (!state)
		return ; Only take action on key press, not key release (prevents sending actions twice)
	if (isHold){
		; Holds
		if (taps == 1){
			msgbox You have tapped and  held "a".
		} else if (taps == 2){
			msgbox You have tapped "a" 2 times and held the second tap.
		}
	} else {
		; Taps
		if (taps == 1){
			msgbox You have tapped "a" 1 time.
		} else if (taps == 2){
			msgbox You have tapped "a" 2 times.
		}
	}
}

SteveMylo wrote:
Epoch wrote:
28 Aug 2022, 11:48
1. Having a context-sensitive TapHoldManager instance, for example having its hotkey taking effect only on windows explorer. Simply starting with #if Winactive, as in the following example doesn't seem to work.
I can help you with your 1st question.
Context sensitivity has always been tricky with THM. But that's why the genius EvilC made it easy for us.
Just add the program to this part :arrow: thm := new TapHoldManager(150, 300, , "$*","ahk_exe explorer.exe") )
Yeah, I've figured there must be a way!
Thanks guys, I understand both of these is basically the same approach? Both of these did the trick, expect the double tap + hold part:

Code: Select all

 else if (taps == 2){
msgbox You have tapped "a" 2 times and held the second tap.
Tapping "a" 2 times and holding the second tap produces the correct messagebox only the first time it is executed, if I add explorer.exe in the equation in the ways you've mentioned. Unless I reload the script, all the subsequent times show the "You have tapped and held "a"." message.
In order to get the "You have tapped "a" 2 times and held the second tap" message I now have to tap 3 times and hold the last one.

User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

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

Post by evilC » 29 Aug 2022, 03:06

SteveMylo wrote:
28 Aug 2022, 19:09
I can help you with your 1st question.
Context sensitivity has always been tricky with THM. But that's why the genius EvilC made it easy for us.
Just add the program to this part :arrow: thm := new TapHoldManager(150, 300, , "$*","ahk_exe explorer.exe") )
D'oh, I completely forgot about that - it was actually added by someone else and merged in as a pull request.
Epoch wrote:
28 Aug 2022, 20:58
Tapping "a" 2 times and holding the second tap produces the correct messagebox only the first time it is executed, if I add explorer.exe in the equation in the ways you've mentioned. Unless I reload the script, all the subsequent times show the "You have tapped and held "a"." message.
In order to get the "You have tapped "a" 2 times and held the second tap" message I now have to tap 3 times and hold the last one.
It's the msgbox that is breaking it. You show the msgbox on A DOWN, which takes away focus from explorer, so when A UP happens, explorer is not in focus, so AHK does not fire the hotkey, and so THM does not reset some of it's internal counters.
If you are going to be using THM to change focus, then you would probably need to do it on key up? (isHold == 1 && state = 0)
I am not sure I could work around this in the THM code myself. One idea may be to subscribe all up event hotkeys without the context, but then if you had a blocking hotkey, that could break stuff, so I don't really see any way to gracefully work around this issue

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 » 29 Aug 2022, 10:14

SteveMylo wrote:
28 Aug 2022, 19:09
I can help you with your 1st question.
Context sensitivity has always been tricky with THM. But that's why the genius EvilC made it easy for us.
Just add the program to this part :arrow: thm := new TapHoldManager(150, 300, , "$*","ahk_exe explorer.exe") )
evilC wrote: D'oh, I completely forgot about that - it was actually added by someone else and merged in as a pull request.
Would you say one way is preferred over the other if I plan to have various TapHoldManager instances , all in one big script I use for various actions, for several programs? (which is why I wanted to know how I associate an instance with a specific program)
Any things I should take into account? For example, I've noticed that for some reason, the code I've initially posted doesn't work unless I put it on the top/beginning of my script, is that a prerequisite for TabHoldManager to work?
Epoch wrote:
28 Aug 2022, 20:58
Tapping "a" 2 times and holding the second tap produces the correct messagebox only the first time it is executed, if I add explorer.exe in the equation in the ways you've mentioned. Unless I reload the script, all the subsequent times show the "You have tapped and held "a"." message.
In order to get the "You have tapped "a" 2 times and held the second tap" message I now have to tap 3 times and hold the last one.
evilC wrote:
29 Aug 2022, 03:06
It's the msgbox that is breaking it. You show the msgbox on A DOWN, which takes away focus from explorer, so when A UP happens, explorer is not in focus, so AHK does not fire the hotkey, and so THM does not reset some of it's internal counters.
If you are going to be using THM to change focus, then you would probably need to do it on key up? (isHold == 1 && state = 0)
I am not sure I could work around this in the THM code myself. One idea may be to subscribe all up event hotkeys without the context, but then if you had a blocking hotkey, that could break stuff, so I don't really see any way to gracefully work around this issue

If you mean

Code: Select all

} else if (isHold == 1 && state = 0){
msgbox You have tapped "a" 2 times and held the second tap.
instead of

Code: Select all

} else if (taps == 2){
msgbox You have tapped "a" 2 times and held the second tap.
it doesn't seem to be working on my end. In any case, since the messagebox triggering was for testing reasons only and I don't really plan to use the code to change focus in this case, but for launching a program, I wouldn't want to bother you too much with this.
Messagebox breaking stuff is something that probably has happened before in other scripts though, so definitely something to keep in my mind when troubleshooting stuff with AHK, in general, so cheers. :)
Do you think the 2). part in my original post (holding a F key then tapping/double-tapping/holding another key to trigger different actions) is something too complicated to pull off?

User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

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

Post by evilC » 29 Aug 2022, 14:11

Would you say one way is preferred over the other if I plan to have various TapHoldManager instances
Using the hotkey, if method will take effect for all subsequently declared sequences (ie thm.Add) calls, regardless of which THM instance is used.
For that reason, it would probably result in shorter code I guess?
it doesn't seem to be working on my end. In any case

Code: Select all

} else if (isHold == 1 && taps == 2 && state = 0){
msgbox You have tapped "a" 2 times and held the second tap.
}
This detects double-tap-and hold, and will trigger on key release, not key press.


But as I said, this all depends on what you end up doing in response to the event. You are using msgbox as a debugging tool in this example script, but you are actually altering how the script works with this debugging tool.
If, in normal operation, in response to a tap sequence, you would not switch which window has the focus, then you can just take your action on key down.
To illustrate:

Code: Select all

	if (isHold){
		; Holds
		if (taps == 1){
			tooltip You have tapped and  held "a". %A_TickCount%
		} else if (taps == 2){
			tooltip You have tapped "a" 2 times and held the second tap. %A_TickCount%
		}
Now the code does not break after the first run, because a tooltip does not change which window has focus. I added A_TickCount to the tooltip so that you can see the number changed and know it has triggered for the second time, because the number changes.

If you are going to be launching an application, then I dunno, because I suppose it depends when the launching application grabs focus.
Another way to maybe handle it would be to launch the new application after the current thread ends.
If memory serves, a SetTimer value of -0 will launch whenever the current thread ends.

eg

Code: Select all

	if (isHold){
		; Holds
		if (taps == 1){
			ScheduleApp("notepad.exe")
		}

; ...

ScheduleApp(app, delay := -0){
	fn := Func("RunApp").Bind(app)
	SetTimer, % fn, % delay
}

RunApp(app){
	Run, % app
}
But maybe having it run when the thread ends will still be before key up, so you may want to try setting to delay to -100 or something to give the key up a chance to fire? But then it would probably still only work intermittently, depending on how long you held the key for, so maybe just simplest to run the app on key up anyway

Jasonosaj
Posts: 46
Joined: 02 Feb 2022, 15:02
Location: California

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

Post by Jasonosaj » 29 Aug 2022, 16:56

evilC wrote:
28 Aug 2022, 15:20
@Jasonosaj
Everything you mentioned is as I would expect the code to work.
There is no such thing as "Pressing two keys at the same time" insofar as it is impossible to hold two keys EXACTLY at the same time.
It sounds to me that what you want is something like Holding Caps to enter a mode, then when you hit another key (p, w, c, s etc), some special action is taken.
In which case, that's a normal AHK hotkey surely, not a job for THM? You could just use THM to detect the double-tap, but to do the normal combinations, use normal AHK hotkeys?

Or you could just do something like this:

Code: Select all

	; INITIALIZATION
	; ...
	CapsHeld := 0
	thm := new TapHoldManager(,,, "$")
	thm.Add("Capslock", Func("CapsHotkeyFunc"))
	Return

	; ... [

#If CapsHeld

p::
	if WinExist("ahk_class dopus.lister") {
		WinActivate
		Run, G:\Downloads
		}
	else {
		Run, G:\Downloads
		}
	}
	return

w::
	if WinExist("ahk_class dopus.lister") {
		WinActivate
		Run, C:\Users\jason\OneDrive - Work\8 - Work Downloads
		}
	else {
		Run, C:\Users\jason\OneDrive - Work\8 - Work Downloads
		}
	}
#If

	capsHotkeyFunc(isHold, taps, state) {
		global CapsHeld
		If (isHold) {
			CapsHeld := state
			}
		Else If (taps == 2) {
			QuickClip(0.5)
			F1HotkeyFunction(Stringtemp)
			}
		Return

		; ...
You're awesome and I'm negligent. That's pretty much the conclusion that I came to as well. Should have come back here and posted a big "nevermind, I figured out why I was screwing this one up. Ignore me." Thanks for getting back to me, @evilC, you're a rockstar.

spiderzsoft
Posts: 2
Joined: 19 Oct 2022, 10:26

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

Post by spiderzsoft » 19 Oct 2022, 10:44

evilC wrote:
31 Jul 2019, 04:56
"there is no such dll in the download"
The copy step (Step 3) uses a file that you used in step 1 (Install interception), so if you are on step 3, this cannot possibly be true
please contact me on discord chat server:
spiderzsoft#1666

Your early response will be highly appreciated for Us. Thanks

YlLO
Posts: 1
Joined: 24 Oct 2022, 07:05

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

Post by YlLO » 24 Oct 2022, 07:15

@evilC

I use your code in combination with a layer. It works perfect except when I add the #if statement below, the code breaks. Tapholdmanager no longer requires the layer to be activated (and activates when pushing). Any suggestions? thanks for advice (& creating)

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
 #Warn  ; Enable warnings to assist with detecting common errors. ;*[Try-out PC]
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
#include Lib\TapHoldManager.ahk
#SingleInstance force ; runt maar 1 script 
	
; layers activation------------------------------
#If GetKeyState("Xbutton1", "P") ;*[Try-out PC]
thm := new TapHoldManager()
	
	
	
	thm.Add("1", Func("MyFunc1"))
	MyFunc1(isHold, taps, state){
		if (!state)
			return ; Only take action on key press, not key release
		if (isHold){
				; Holds
			if (taps == 1){
				msgbox 1 hold
			} else if (taps == 2){
				msgbox 2 holds
			}
		} else {
				; Taps
			if (taps == 1){
				msgbox TEST 11
			} else if (taps == 2){
				msgbox TEST 12
			}
		}
		
}
	r::
	if WinExist("ahk_exe ONENOTE.EXE")
		WinActivate % "ahk_exe ONENOTE.EXE"
	else
		Run % "ONENOTE"
	Return
	
#if
	


Bachelar
Posts: 28
Joined: 26 Aug 2020, 12:30

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

Post by Bachelar » 05 Nov 2022, 03:40

@evilC Love your THM! I have a question, though. Is it necessary to put code using THM in a separate .ahk file? I'm asking because if THM is instantiated and used within an already existing script of mine, it's no longer working.

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 » 28 Jan 2023, 12:09

Is it possible to have a key combination containing an F key as my tapholdmanager hotkey? I've set a mouse button as "F13", allowing me to combine it with various keyboard keys for different actions i.e

Code: Select all

F13 & H:: Do this
So I'd like to hold F13 (the mouse button) while tapping, double tapping, holding etc. the H key in order to get different actions. Is this possible?

Also, is there a reason THM doesn't work unless it's on the very top of my script on my end? If it's not in the beginning it won't work which wouldn't be much of a problem if that didn't prevent me from using Timers in the same script (since these should on the top in order to run too). Any workarounds or is it just the way THM works?
Here's my THM-related code, please let me know if you see anything else that doesn't seem quite right too. Cheers!

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
SetTitleMatchMode, 2
#SingleInstance Ignore

thm := new TapHoldManager(100, 250, , "$*") ; 
Hotkey, IfWinActive, ahk_exe AutoHotkeyU32.exe ; All subsequent thm.Add subscriptions will only fire for AHK Studio.
thm.Add("^!6", Func("AHK_Commenting"), 5, 250, , "$*")
Hotkey, IfWinActive, ahk_exe waterfox.exe ; All subsequent thm.Add subscriptions will only fire for Waterfox.
thm.Add("^+!q", Func("Waterfox_ContextSearch"))
thm.Add("^+!b", Func("Waterfox_Bookmarks"), 200, 300, , "$*")

...followed by my THM hotkeys and then all the other non-THM stuff.
 

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 » 28 Jan 2023, 16:44

@Epoch Hi Epoch.
This code works while Holding the "F3" key on my laptop as I don't have F13... and "H" key will perform a SoundBeep.
I'm utilizing the thm.PauseHotkey("h") & thm.ResumeHotkey("h") functions in THM.
BTW This function is the Perfect Keywait, and I no longer use Keywait anymore, I just copy and paste THM's function. Keywait often has issues.

When "F3' is released, H won't sound beep anymore. Which is what you want! :clap:
I believe this is what you want, although I don't have a F13 button and since you are mapping it, you may need the other THM function that maps keys. I'm not sure.
Try this with F3 anyways and see if it works:

Code: Select all

#Include <TapHoldManager>

thm := new TapHoldManager()
thm.Add("F3", Func("F3_Func"),,,1) ; increase "1" to suit if you are tapping more than once
thm.Add("h", Func("hButton"),,,1)
thm.PauseHotkey("h")


F3_Func(isHold, taps, state){
	 global thm
if (isHold=1) & (taps=1) & (state=1){  ; allows "h" to work while holding down because (state=1)
thm.ResumeHotkey("h")
return
}


if (isHold=1) & (taps=1) & (state=0){  ; Pauses "h" when F3_Func is released because (state=0)
thm.PauseHotkey("h")
return
}
}


hButton(isHold, taps, state){
	 global thm
if (isHold=0) & (taps=1) & (state){

soundbeep
return
}
}

return
~Esc::ExitApp

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 » 28 Jan 2023, 16:52

@Bachelar I would, because THM includes calls to function's that have to be up the TOP of the script generally

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 » 31 Jan 2023, 22:26

evilC wrote:
22 Jun 2022, 03:41
Yes, it's totally possible - at the end of the day, which hotkey is used is ultimately goverened by a call such as thm.Add("1", Func("MyFunc1")) - so in this example, you just need to read a value from the ini file into a variable and instead call thm.Add(valueFromIniFile, Func("MyFunc1"))
Being able to reassign the key from within the app, however (As opposed to just editing the INI file manually prior to launching the script) is another matter entirely - I have an example of how to do this (Albeit with regular hotkeys) here: https://github.com/evilC/AppFactory
Hey evilC, just adding to this:
  • What if a script uses THM and the hotkey is (x)
  • I then make a .exe compiled and compress/encrypted file of that
  • then sell that to a user who wants to change the THM hotkey to (z)
  • is that possible for an .ini file to change the encrypted .exe file?
Or is that what you are saying? the script will be prebuilt to look for the .ini file to read? even after encryption?

Also does referring to an .ini file slow down the time from when the hotkey is pressed to when the action is performed? compared to a regular THM script?

I'm trying to find freelancers who can do it for me but no luck yet. Cheers

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 » 01 Feb 2023, 11:02

Hi again Steve, thanks for helping out! I've been testing your code (along with my older THM functions) for the past few days and I've got a few things to say.

1.
SteveMylo wrote:
28 Jan 2023, 16:44
@Epoch Hi Epoch.
This code works while Holding the "F3" key on my laptop as I don't have F13... and "H" key will perform a SoundBeep.
I believe this is what you want, although I don't have a F13 button and since you are mapping it, you may need the other THM function that maps keys. I'm not sure.
I've tried your code and of course it's working and yes that's pretty much what I am after, except I want it for F13 & Space and I also want double tap and hold actions (maybe triple tap too). Whereas your script works perfectly though, when I adapt it on my needs it's "only" kinda working, meaning it's a bit inconsistent.

Code: Select all

#Include <TapHoldManager>

thm := new TapHoldManager()
thm.Add("F13", Func("F13_Func"),,,1) ; increase "1" to suit if you are tapping more than once
thm.Add("Space", Func("Space"),350,500,3)
thm.PauseHotkey("Space")


F13_Func(isHold, taps, state){
	 global thm
if (isHold=1) & (taps=1) & (state=1){  ; allows "Space" to work while holding down because (state=1)
thm.ResumeHotkey("Space")
return
}


if (isHold=1) & (taps=1) & (state=0){  ; Pauses "Space" when F13_Func is released because (state=0)
thm.PauseHotkey("Space")
return
}
}


Space(isHold, taps, state){
	 global thm
if (isHold=0) & (taps=1) & (state)

{
ToolTip One Tap!
Sleep 500
ToolTip
Return
}

if (isHold=0) & (taps=2) & (state)

{
ToolTip Two Taps!
Sleep 500
ToolTip
Return
}

if (isHold=0) & (taps=3) & (state)

{
ToolTip Three Taps!
Sleep 500
ToolTip
Return
}

if (isHold=1) & (taps=1) & (state)

{
ToolTip One Hold!
Sleep 500
ToolTip
Return
}



}

return
~Esc::ExitApp

The problem is that while it's working properly 75% of the time, sometimes it's missing taps, I'll tap three times and the tooltip will report only two etc. Also, when trying the "hold" action I'll receive a message saying:
"71 hotkeys have been received in the last X seconds" asking me if I want to terminate the script and Space stops working. Searching in the forum, I've found out this usually means a hotkey sending itself causing a loop and suggested solutions are to add "$" before the hotkey (didn't work) or #MaxHotkeysPerInterval 500 on the top of the script (it seems to get rid of the warning popup but not of the missing taps inconsistency). But it sounds like the proper way to deal with this is fixing a (possible) mistake on the code itself...
Either I am adapting something wrong or the F13 is the culprit. Btw, I am using the Logitech G600 mouse which has a third mouse button on the right of the classic right button which acts like a shift button to have twice as many actions (I think it's still the only one in the market having that, so useful!). Now, this is a Logitech proprietary thing and it's not seen by AHK on its own. Using Logitech software though with a Lua script, I can set it to act as "F13" and AHK sees it. Here is the Lua script btw, I found it online, didn't come up with it myself:

Code: Select all

function OnEvent(event, arg, family)
if event == "MOUSE_BUTTON_PRESSED" and arg == 6 then -- Change 3 to whatever Gkey you want to use.
PressKey("F13"); else
ReleaseKey("F13");
end
end
I've been using F13 combined with several other other keys for a while and never had a problem, probably there's something wrong in my adaptation?

2). Judging from your answer to @Bachelar in the post right above mine, it seems like having a dedicated script for tabholdmanager on its own is necessary, right? I mostly work with one large, megascript doing stuff in several programs, but as I’ve said it seems it doesn'to play well with Timers, seeing they both need to be on the top.

3). With that in mind, I put together a new Tabholdmanager script but when trying to combine this new hotkey with my older THM functions, several issues seem to appear. Messages that say something along the lines “functions can’t contain other functions” or some functions seemingly breaking others.
Rather than troubling you with posting everything, I’ll try to put things in the right place myself for a little longer so I gotta ask… the order things are layered seems very important, right? As a reminder, except the thm.Add and the (isHold, taps, state) parts, I also use the context sensitive lines EvilC told me about (you've also posted an alternative way yourself, I'll try this one too, but I am not sure if you can have multiple lines initiating THM, one for each program) the other day, in order to limit the programs the hotkeys have an effect on. I've got these for example:

Code: Select all

Hotkey, IfWinActive, ahk_exe AutoHotkeyU32.exe
Hotkey, IfWinActive, ahk_exe waterfox.exe
I think am doing something wrong in the order I put things, I've tried putting

Code: Select all

Hotkey, IfWinActive
in order to stop context-sensitivity, but I am not sure whether this line is correct or if that's the only problem.
Also, can I group the thm.Add parts together with their (isHold, taps, state) counterparts or should I place all the thm.Add parts together first and THEN the functions? The former looks more tidy but I get the feeling it causes some of the issues mentioned. Or perhaps it has to do with some Gosub / Goto commands I've got in my older THM hotkeys. As I've said, up until now I was using THM on top of my megascript and I've found that necessary to get around the "functions can’t contain other functions” message I was also getting in cases like having a THM hotkey execute something that includes a function combined with Findtext stuff, or simply to reference some other code snippet found later in the script and not have to repeat it.
...Or maybe, again, it's my adaption of your new code that causes a mess...

4.
I'm utilizing the thm.PauseHotkey("h") & thm.ResumeHotkey("h") functions in THM.
BTW This function is the Perfect Keywait, and I no longer use Keywait anymore, I just copy and paste THM's function. Keywait often has issues.
That's quite interesting, I'd like to know more about this. To be honest, as with a lot advanced AHK stuff, I don't fully understand the way THM works. Studying this thread I've ended up with a working "template" to perform tap, double tap, triple tap and hold actions and I adapt it each time changing actions and hotkeys.
But I can't say I fully comprehend what happens under the hood, why for example you need to put taps=1, when it's not a tap but a hold (otherwise it won't work) on:

Code: Select all

if (isHold=1) & (taps=1) & (state)
and what exactly you do with "states". I mean understand you use it to allow space ("h" in your example) working normally when I don't use the combination, but not how this line/states working.

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

dostroll
Posts: 40
Joined: 03 Nov 2021, 08:56

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

Post by dostroll » 23 Feb 2023, 12:50

Thanks for the great library!

"tooltip,single" fires when the held 1 key is released.
What should I do to make it so that I didn't press the 1 key even though I released it?

Code: Select all

thm := new TapHoldManager()
thm.Add("1", Func("MyFunc1"))

MyFunc1(isHold, taps, state){
  if (taps = 1)
    {
      ToolTip, single
    }
  if (taps = 2)
    {
      ToolTip, doubble
    }
  if (state = 1)
    {
      ToolTip, hold
      taps := 0
      state := 0
      isHold := 0
    }
}

Ralf_Reddings200244
Posts: 84
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 » 17 Mar 2023, 13:12

Can we pease get a AutoHotkey V2 version of this Library? I really want to start using Autohotkey V2 but this library is the only thing holding me back.

All of my programs and scripts really on this library, so I am stuck :/

Post Reply

Return to “Scripts and Functions (v1)”