Open gaberiel44 opened 1 year ago
Someone did it on the forums: https://www.autohotkey.com/boards/viewtopic.php?p=525416#p525416
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)
}
}
}
I have multiple scripts for THM and they are all #included in a single .ahk script. frustratingly, i discovered i can't run multiple scripts at the same time....if i want to run a particular script i have to either locate its .ahk file right click and Run Script or go to the tray icon and Reload This Script. How do I fix this
v2 doesn't work for me.
#requires AutoHotkey v2.0
#include <TapHoldManager>
thm := new TapHoldManager(100, 200, 1)
thm.Add("1" , Send("!"))
On running this AHK displays this error:
Warning: This variable appears to never be assigned a value. Specifically: global new
Removing new
hides this error. But a new error is displayed when I press 1
:
Error: This value of type "String" has no method named "Call".
Edit: Updated the code to this:
thm := TapHoldManager()
thm.Add("1" , MyFunc1 , 100 , 200 , 1)
MyFunc1() {
Send("!")
}
New error is displayed by AHK:
Error: Too many parameters passed to function.
@iamMG this is because your callback function does not accept the parameters which tell you whether it was a tap or a hold Even if you don't use them, it must declare them
MyFunc1(isHold, taps, state) { ; <- Function MUST declare isHold, taps, and state!
Send("!")
}
@trACEroam not really sure what you mean. If you supply some example scripts that exhibit the issue I could maybe help
Update: I am currently working on a release of THM for AHK v2
A WIP version can be found here
It should be identical in usage (Apart from obviously not having to wrap your function names in Func()
), except for one minor change.
In v1, if you wanted to skip an optional parameter, you used -1
(eg thm.Add("1" , Func("MyFunc1"), -1, -1 , 2
).
In v2, you just omit the parameter (eg thm.Add("1" , MyFunc1 ,,, 2)
)
I could have kept using -1, but with v2's support for unset parameters and coalescing operators, it just felt a lot cleaner (And consistent with AHK's normal syntax) to make this change
I am currently trying to move over to V2,
TapHoldManager
is core to my Autohotkey code base. So I am stuck with V1.I am not much of a programmer, I find V1 to be a pain to use. I tested V2 and it solves many of my gripes with V1.
Can we please get a V2 port. It does not have to be any time soon. But I would appreciate to know that its on your road map.
Thanks for this library.