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

Post your working scripts, libraries and tools
Avastgard
Posts: 49
Joined: 30 Sep 2016, 21:54

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

27 Jun 2020, 16:53

Thank you for your answer.

Now, the way I understand it, TapHoldManager only works for tapping/holding the same key, is that right? Or is it possible to have it working by, say, tapping E twice and then holding Spacebar?
User avatar
evilC
Posts: 4780
Joined: 27 Feb 2014, 12:30

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

27 Jun 2020, 20:11

Only support for the same key is built into the library, but no reason you could not implement that functionality yourself by just subscribing to e and space, and only enabling the space subscription once e was tapped twice

Code: Select all

#include Lib\TapHoldManager.ahk

thm := new TapHoldManager()

thm.add("e", func("eFunc")) 
thm.add("Space", func("spaceFunc"))
thm.PauseHotkey("Space")

eFunc(isHold, taps, state){
    global thm
    if (taps == 2 && !isHold){
        thm.ResumeHotkey("Space")
    }
}

spaceFunc(isHold, taps, state){
    global thm
    if (taps == 1 && isHold && state){
        thm.PauseHotkey("Space")
        Msgbox You tapped e twice then held space
    }
}
Avastgard
Posts: 49
Joined: 30 Sep 2016, 21:54

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

28 Jun 2020, 20:24

Thank you very much for this code! Tell me if I understand it correctly (please bear with my noobness):

Code: Select all

#include Lib\TapHoldManager.ahk

thm := new TapHoldManager()

thm.add("e", func("eFunc")) ; creates the hotkey E
thm.add("Space", func("spaceFunc")) ; creates the hotkey Spacebar
thm.PauseHotkey("Space") ; Keeps the Space hotkey paused until it is resumed

eFunc(isHold, taps, state){
    global thm ; What does "global" do?
    if (taps == 2 && !isHold){ ; if the E key is tapped twice AND is not held down
        thm.ResumeHotkey("Space") ; then put the space hotkey in action? Is that it?
    }
}

spaceFunc(isHold, taps, state){
    global thm
    if (taps == 1 && isHold && state){ ; if Spacebar is pressed once and held (why do we need "state"?)
        thm.PauseHotkey("Space") ; Spacebar hotkey is paused untill the function above (E) is executed.
        Msgbox You tapped e twice then held space
    }
}
And just another question: is it possible to put the "~" symbol only when the function is executed and not when setting up the hotkey? I'm asking this because I have the following code:

Code: Select all

; If the library files are in a subfolder called Lib next to the script, use this
#include Lib\TapHoldManager.ahk
; If you placed all the library files in your My Documents\AutoHotkey\lib folder, use this
;#include <TapHoldManager>

#SingleInstance force

thm := new TapHoldManager()	; TapTime / Prefix can now be set here
thm.Add("~'", Func("FuncChromeTabPrevious"), , , , , "ahk_exe chrome.exe")			; TapFunc / HoldFunc now always one function
thm.Add("~´", Func("FuncChromeTabNext"), , , , , "ahk_exe chrome.exe")
thm.Add("~Alt", Func("FuncAltTab"))

FuncChromeTabPrevious(isHold, taps, state)
{
  if (taps == 2)
    Send, ^+{Tab}
}

FuncChromeTabNext(isHold, taps, state)
{
  if (taps == 2)
    Send, ^{Tab}
}

FuncAltTab(isHold, taps, state)
{
  if (taps == 2)
    Send, !{Tab}
return
}
The first two hotkeys (' and ´) are used to cycle through tabs in Google Chrome. The thing is, as the code is right now, even when successfully double-tapping the hotkeys, they will still send the respective keys (' and ´) because I added the ~ sign before them (otherwise I would not be able to type ' and ´ in Chrome, and I need to be able to do that). Would it be possible to "delay" the effect of "~", like, if I tap ´ once, it would wait for the next ´ to fire the ^{Tab} function, but if the second ´ was not tapped within tapTime, "´" would be sent, like specified in the script?
User avatar
evilC
Posts: 4780
Joined: 27 Feb 2014, 12:30

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

29 Jun 2020, 04:09

global is needed because functions have their own local variable space. Without global ahi, the function would not be able to use the ahi variable that was declared outside the function
state holds the state of the input key - for a tap, it is always -1, but on a hold, it is 1 when you press the key and 0 when you release the key. So this code just ensures that the messagebox only shows on press of space, but not release
; then put the space hotkey in action? Is that it? Correct
; Spacebar hotkey is paused untill the function above (E) is executed Correct
Would it be possible to "delay" the effect of "~", like...
Bear in mind that when you subscribe to a key, the function associated with that key is not called at all until it has decided what you have done - ie it will not call the function after the 1st tap until it has decided that too long has passed, and a 2nd tap is not going to come.
On a double-tap, the function is only called once, after the 2nd tap, it is not called after the 1st tap

It's also worth mentioning that the intended way of specifying a prefix is by using the appropriate parameter, either:
thm := new TapHoldManager(,,, "$~") ; All hotkeys have pass-through
or
thm.Add("´", Func("FuncChromeTabNext"), , , , "$~", "ahk_exe chrome.exe") ; This hotkey has pass-through
Be aware that when you have a subscribed key that sends itself (ie subscribing to ' and the function for ' potentially sending '), you must make sure the $ prefix is in effect, else you will get an infinite loop (you press ', the function sends ', which triggers the ' hotkey, which sends ', which triggers the ' hotkey, etc...)
THM defaults to a prefix of $, but if you override the prefix (eg you wish to add ~), you should also include the $ prefix to stop it self-triggering (ie $~ not ~)

So if you want to sometimes block, and sometimes not block the key, what you have to do is to block the key, and then use logic within your function to decide whether to send the key that was blocked, or take some other action:

Code: Select all

thm.Add("'", Func("FuncChromeTabPrevious"), , , , , "ahk_exe chrome.exe")

FuncChromeTabPrevious(isHold, taps, state)
{
  if (isHold)
  {
    if (taps == 1)
      Send ' ; on one tap, just pass through a tap of '
    else if (taps == 2)
      Send, ^+{Tab} ; on two taps, send previous tab
  }
}
This code is a bit overly-simplistic (ie it does not handle a hold of '), but hopefully you get the idea

Another thing worth mentioning is that if you only ever want to support double-taps, and not triple-taps or more, then set the "maxTaps" parameter to 2.
This will speed up response times - when you double-tap, it will not wait to see if a 3rd tap happens before firing the function
jbone1313
Posts: 8
Joined: 19 Feb 2015, 10:20

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

04 Jul 2020, 16:00

Thank you so much for this. I am rocking my Kinesis with tap holds!
eye_in_the_sky
Posts: 3
Joined: 08 Jul 2020, 13:50

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

08 Jul 2020, 13:58

Some help with the script would be appreciated.

1. Tap the Left Arrow key twice in quick succession -> cursor will jump to beginning of sentence like tapping the Home key
2. Tap the Right Arrow key twice in quick succession -> cursor will jump to end of sentence like tapping the End key
3. And, all other key actions like individual taps and long presses work the same as how these original arrow key actions
work
3.1 for example: (tapping Left key one at a time moves cursor one character at a time towards the left; pressing Left moves cursor character by character to the left continuously until Left key is released)
3.2 same as for Right arrow key.

This code for Left and Right keys do not have desired effect.

Code: Select all

#include Lib\TapHoldManager.ahk	

thm := new TapHoldManager() 
thm.add("Left", Func("goHome"),150, 150, 2)
thm.add("Right", Func("goEnd"),150, 150, 2)

goHome(isHold, taps, state)
{
    if (taps == 2)
      Send {Home} ; on two taps, send Home
}

goEnd(isHold, taps, state)
{
    if (taps == 2)
      Send {End} ; on two taps, send End
}
jbone1313
Posts: 8
Joined: 19 Feb 2015, 10:20

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

08 Jul 2020, 23:35

As mentioned in this issue in the GitHub (link below), there seems to be some unavoidable delay, such that setting tap and holds to "normal" keys like letters and numbers is not a good idea if one is a fast typer.

Just wanted to check to see if there are any ideas about how to fix that or work around it. I tried rolling my own simple tap and hold, and I ran into the same issue, because my script had to wait for the key release to know if it was a tap or hold, which caused the delay. I.e., the normal key press sends the keystroke on the key press, not the release.

It would be very useful to be able to setup tap and holds on "normal" keys.

https://github.com/evilC/TapHoldManager/issues/2
jbone1313
Posts: 8
Joined: 19 Feb 2015, 10:20

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

09 Jul 2020, 10:32

evilC wrote:
09 Jul 2020, 10:02
See the comment in the GitHub thread - this is what the maxTaps parameter is meant to solve
Thank you for the reply. Yeah, I am aware of that parameter, and indeed, I set it to 1. I think the issue here is that waiting for the release is the problem. In a normal typing scenario, the delay of waiting for the release causes issues. The people in the GitHub thread explain it well.

One person mentioned QMK's "permissive hold," which apparently means that if your tap overlaps with another key’s tap, it sends taps for both keys, which would probably "fix" this "issue." I have no idea if it is possible to implement that in AHK. It sounds complicated. But, you are smart people. :)
eye_in_the_sky
Posts: 3
Joined: 08 Jul 2020, 13:50

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

09 Jul 2020, 12:19

Hi evilC, not sure why my script is not working the way I wanted, would appreciate if you could take a look?
eye_in_the_sky
Posts: 3
Joined: 08 Jul 2020, 13:50

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

13 Jul 2020, 03:49

Thanks though it still not functional but I found a different method to do what I want.
Netmano
Posts: 48
Joined: 17 Jun 2020, 16:24

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

19 Jul 2020, 09:59

Hey evilC. I seem to be having an issue with THM and the order of declared keys.



In the code snippet below, I cant trigger "BackSpace" at all but as soon as I delete "XButton2" or move "BackSpace" to top or bottom of the key stack it goes back to working. I could work with this fix but then another key stops working.

I have much more keys declared in my actual script and so more than two keys dont work. This is just a condensed example.

I have Attached a copy of a condensed example script for you.
Any insight or help would be appreciated.
Example.ahk
(34.42 KiB) Downloaded 18 times

Code: Select all

#NoEnv
#Persistent
#singleinstance, force
#include <TapHoldManager>
 

Notepad:= new TapHoldManager( 200, 400, 2, "$*","ahk_exe notepad.exe" )

  Notepad.Add("XButton1", Func("Base_XButton1"))
  Notepad.Add("^XButton1", Func("Ctrl_XButton1"))
  Notepad.Add("+XButton1", Func("Shift_XButton1"))

  Notepad.Add("XButton2", Func("Base_XButton2"))
  Notepad.Add("^XButton2", Func("Ctrl_XButton2"))
  Notepad.Add("+XButton2", Func("Shift_XButton2"))

  Notepad.Add("BackSpace", Func("Base_BackSpace"))
  Notepad.Add("^BackSpace", Func("Ctrl_BackSpace"))
  Notepad.Add("+BackSpace", Func("Shift_BackSpace"))
 

  Notepad.Add("h", Func("Base_h"))
  Notepad.Add("^h", Func("Ctrl_h"))
  Notepad.Add("+h", Func("Shift_h"))

  Notepad.Add("l", Func("Base_l"))
  Notepad.Add("^l", Func("Ctrl_l"))
  Notepad.Add("+l", Func("Shift_l"))

  Notepad.Add("d", Func("Base_d"))
  Notepad.Add("^d", Func("Ctrl_d"))
  Notepad.Add("+d", Func("Shift_d"))

  Notepad.Add("i", Func("Base_i"))
  Notepad.Add("^i", Func("Ctrl_i"))
  Notepad.Add("+i", Func("Shift_i"))

  Notepad.Add("o", Func("Base_o"))
  Notepad.Add("^o", Func("Ctrl_o"))
  Notepad.Add("+o", Func("Shift_o"))

  Notepad.Add("u", Func("Base_u"))
  Notepad.Add("^u", Func("Ctrl_u"))
  Notepad.Add("+u", Func("Shift_u"))



  Notepad.Add("PgDn", Func("Base_PgDn"))
  Notepad.Add("^PgDn", Func("Ctrl_PgDn"))
  Notepad.Add("+PgDn", Func("Shift_PgDn"))

  Notepad.Add("PgUp", Func("Base_PgUp"))
  Notepad.Add("^PgUp", Func("Ctrl_PgUp"))
  Notepad.Add("+PgUp", Func("Shift_PgUp"))
User avatar
evilC
Posts: 4780
Joined: 27 Feb 2014, 12:30

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

20 Jul 2020, 13:05

Yeah, I used the following test code and there definitely seems to be something strange going on

Code: Select all

#NoEnv
#Persistent
#singleinstance, force
#include Lib\TapHoldManager.ahk


;~ Notepad:= new TapHoldManager( 200, 400, 2, "$*","ahk_exe notepad.exe" )
;~ Notepad:= new TapHoldManager( 200, 400, 2, "$*")
Notepad:= new TapHoldManager(200,400,2,"$")

Notepad.Add("XButton1", Func("KeyEvent").Bind("Base_XButton1"))
  Notepad.Add("^XButton1", Func("KeyEvent").Bind("Ctrl_XButton1"))
  Notepad.Add("+XButton1", Func("KeyEvent").Bind("Shift_XButton1"))

  Notepad.Add("XButton2", Func("KeyEvent").Bind("Base_XButton2"))
  Notepad.Add("^XButton2", Func("KeyEvent").Bind("Ctrl_XButton2"))
  Notepad.Add("+XButton2", Func("KeyEvent").Bind("Shift_XButton2"))

  Notepad.Add("BackSpace", Func("KeyEvent").Bind("Base_BackSpace"))
  Notepad.Add("^BackSpace", Func("KeyEvent").Bind("Ctrl_BackSpace"))
  Notepad.Add("+BackSpace", Func("KeyEvent").Bind("Shift_BackSpace"))
 

  Notepad.Add("h", Func("KeyEvent").Bind("Base_h"))
  Notepad.Add("^h", Func("KeyEvent").Bind("Ctrl_h"))
  Notepad.Add("+h", Func("KeyEvent").Bind("Shift_h"))

  Notepad.Add("l", Func("KeyEvent").Bind("Base_l"))
  Notepad.Add("^l", Func("KeyEvent").Bind("Ctrl_l"))
  Notepad.Add("+l", Func("KeyEvent").Bind("Shift_l"))

  Notepad.Add("d", Func("KeyEvent").Bind("Base_d"))
  Notepad.Add("^d", Func("KeyEvent").Bind("Ctrl_d"))
  Notepad.Add("+d", Func("KeyEvent").Bind("Shift_d"))

  Notepad.Add("i", Func("KeyEvent").Bind("Base_i"))
  Notepad.Add("^i", Func("KeyEvent").Bind("Ctrl_i"))
  Notepad.Add("+i", Func("KeyEvent").Bind("Shift_i"))

  Notepad.Add("o", Func("KeyEvent").Bind("Base_o"))
  Notepad.Add("^o", Func("KeyEvent").Bind("Ctrl_o"))
  Notepad.Add("+o", Func("KeyEvent").Bind("Shift_o"))

  Notepad.Add("u", Func("KeyEvent").Bind("Base_u"))
  Notepad.Add("^u", Func("KeyEvent").Bind("Ctrl_u"))
  Notepad.Add("+u", Func("KeyEvent").Bind("Shift_u"))



  Notepad.Add("PgDn", Func("KeyEvent").Bind("Base_PgDn"))
  Notepad.Add("^PgDn", Func("KeyEvent").Bind("Ctrl_PgDn"))
  Notepad.Add("+PgDn", Func("KeyEvent").Bind("Shift_PgDn"))

  Notepad.Add("PgUp", Func("KeyEvent").Bind("Base_PgUp"))
  Notepad.Add("^PgUp", Func("KeyEvent").Bind("Ctrl_PgUp"))
  Notepad.Add("+PgUp", Func("KeyEvent").Bind("Shift_PgUp"))
return

KeyEvent(keyName, isHold, taps, state){
    Tooltip % keyName ": isHold: " isHold ", taps: " taps ", state: " state
}
So when I use "$" as the prefix, all is fine.
As soon as I make it "$*", it breaks, but deleting a few of the other hotkeys also seems to fix it

I also wrote this test code that mimics what THM is doing

Code: Select all

#NoEnv
#Persistent
#singleinstance, force

keys := ["XButton1","XButton2", "BackSpace","h","l","d","i","o","u","pgdn","pgup"]
mods := [{mod: "", label: "base"},{mod: "^", label: "ctrl"},{mod: "+", label: "shift"}]
pfx := "$*"

for i, k in keys {
  for i, modobj in mods {
    keyName := modobj.mod k
    fn := Func("KeyEvent").Bind(1, modobj.label "-" k)
    hotkey, % pfx keyName, % fn
    
    fn := Func("KeyEvent").Bind(0, modobj.label "-" k)
    hotkey, % pfx keyName " up", % fn
  }
}
return

KeyEvent(state, keyName){
    Tooltip % keyName ": state: " state
}
This code just basically builds a load of hotkeys - no special processing for taps or holds, and it breaks in exactly the way that THM does (Change pfx from $* to $ and it works)
Backspace on press triggers backspace::, but release of backspace triggers ^backspace up::
So there may be a bug in AHK or something? Not sure what is going on here, may have to ask around
I take it the reason that you declare * for hotkeys in the first place is that you want them also to work when ALT is held?
Netmano
Posts: 48
Joined: 17 Jun 2020, 16:24

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

20 Jul 2020, 18:03

@evilC
That looks to reduce my code that run into hundreds of lines into 30 lines by the looks of it. Outsanding stuff.
So there may be a bug in AHK or something?
If you suspect it to be so, This here is beyond my comprehension to be honest. but am glad I brought this to your attention now.

I take it the reason that you declare * for hotkeys in the first place is that you want them also to work when ALT is held?
Correct, I've run out of single keys, Ctrl+ [some key], Shift+ [some key] and cant use keys like numpad1 as they are not accepted by most softwares that have hotkey editors in their prefrences.
I am forced to reserve Shift+Alt and other variations for another purpose. so I am in a tight bind (no pun intended :D )


I noticed you started asking around already, much appreciated.
maltby
Posts: 2
Joined: 27 Aug 2020, 00:48

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

27 Aug 2020, 01:02

@evilC - thanks for this library from another user / keyboard shortcut junkie. Some questions for you (or anyone who can answer):

1. With THM, can I define tap/hold functions for modifier keys - Control, Shift, Win, Alt - in exactly the same way as letter keys? Are there any limitations on this? I looked but didn't see anything in the GitHub docs about this.
2. On Reddit you recently posted an Autoshift script (https old.reddit.com /r/AutoHotkey/comments/gds0cc/keywait_misbehaving_when_implementing_autoshift/). Broken Link for safety I tried your script and it worked *except* that after typing a word and pressing `space` the space would be entered before the last letter like this: `exampl e`. I think it's related to the key delay mentioned in the GitHub issue #2: https://github.com/evilC/TapHoldManager/issues/2. In your script MaxTaps is set to 1, so I don't know how to fix the issue. Any suggestions?
3. Are you familiar with (Dual)[https://github.com/lydell/dual]? Unlike THM, it is aware of multi-key presses rather than having each key processed separately. It also has no maintainer and focuses on modifier keys only. I'm wondering if you might combine it with THM or be able to solve the delay problem by looking at its code.
4. I'm trying to set up additional keyboard layers but can't use TMK or QMK with my keyboard. Is THM the right tool for doing this type of thing? If not, are you aware of any that are?

Thanks!
User avatar
evilC
Posts: 4780
Joined: 27 Feb 2014, 12:30

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

01 Sep 2020, 09:25

1) Nothing stopping that, no
2) Hmm, not entirely sure why that happens, I suppose maybe if you hit the space very quickly after the e, then the callback does not have time to fire before the space is processed.
I guess you could add space as a THM key too?
3) Not familiar with it, no
4) Not really sure I am afraid

Return to “Scripts and Functions”

Who is online

Users browsing this forum: Gaia, jadams and 35 guests