Skip to content

Commit

Permalink
Merge pull request #13 from evilC/AHK-v2
Browse files Browse the repository at this point in the history
Ahk v2
  • Loading branch information
evilC authored Apr 9, 2024
2 parents a6d0676 + e77ac1d commit 7a65441
Show file tree
Hide file tree
Showing 15 changed files with 565 additions and 75 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
; Demonstrates Interception Subscription Mode with TapHoldManager
#SingleInstance force
; Use these includes if you placed the contents of the TapHoldManager and AutoHotInterception Lib folders in the AHK lib folder (My Documents\AutoHotkey\Lib)
#include <AutoHotInterception>
#include <InterceptionTapHold>
#include <TapHoldManager>
;#include <AutoHotInterception>
;#include <InterceptionTapHold>
;#include <TapHoldManager>

; Use these includes if you placed the contents of the TapHoldManager and AutoHotInterception Lib folders in a lib folder next to this script
; ie copy the AutoHotInterception Lib folder into the TapHoldManager Lib folder
;#include Lib\AutoHotInterception.ahk
;#include Lib\InterceptionTapHold.ahk
;#include Lib\TapHoldManager.ahk
#include Lib\AutoHotInterception.ahk
#include Lib\InterceptionTapHold.ahk
#include Lib\TapHoldManager.ahk

AHI := new AutoHotInterception()
; keyboard1Id := AHI.GetDeviceIdFromHandle(false, "HID\VID_03EB&PID_FF02&REV_0008&MI_03")
keyboard1Id := AHI.GetKeyboardId(0x03EB, 0xFF02)
;keyboard1Id := AHI.GetKeyboardId(0x03EB, 0xFF02)
;keyboard1Id := AHI.GetDeviceIdFromHandle(false, "HID\VID_03EB&PID_FF02&REV_0008&MI_03")
keyboard1Id := 3
ITH1 := new InterceptionTapHold(AHI, keyboard1Id)

ITH1.Add("1", Func("Func1"))
Expand Down
2 changes: 1 addition & 1 deletion KeyboardToMouse.ahk → AHK v1/KeyboardToMouse.ahk
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ HandleInput(axis, dir, isHold, taps, state := -1){
; Tap
x := 0
y := 0
%axis% := (dir * taps) * MoveMultiplier * MoveMultiplier
%axis% := (dir * taps) * MoveMultiplier
MouseMove, % x, % y, 0, R
}
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
32 changes: 32 additions & 0 deletions AHK v2/Context Example.ahk
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#Requires AutoHotkey v2.0
; Exmaple of Context Specific hotkeys (Only applies to a certain window)
; 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>

thm := TapHoldManager(,,,,"ahk_exe notepad.exe") ; with window parameter set here, default window criteria that will be set for all sub-created hotkeys under this manager object is notepad
thm.Add("1", MyFunc1)
thm.Add("2", MyFunc2)
thm.Add("3", MyFunc3,,,,,"ahk_class WordPadClass") ; this hotkey's window criteria will be WordPad (instead of manager object's previously passed-in notepad default)

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

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

MyFunc3(isHold, taps, state){
ToolTip("3`n" (isHold ? "HOLD" : "TAP") "`nTaps: " taps "`nState: " state)
SetTimer(RemoveTooltip, -1000)
}

RemoveTooltip(){
ToolTip
}

^Esc::ExitApp
37 changes: 37 additions & 0 deletions AHK v2/Interception Subscription Mode Example.ahk
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#Requires AutoHotkey v2.0
#SingleInstance force

; Demonstrates Interception Subscription Mode with TapHoldManager

; Use these includes if you placed the contents of the TapHoldManager and AutoHotInterception Lib folders in the AHK lib folder (My Documents\AutoHotkey\Lib)
; #include <AutoHotInterception>
; #include <InterceptionTapHold>
; #include <TapHoldManager>

; Use these includes if you placed the contents of the TapHoldManager and AutoHotInterception Lib folders in a lib folder next to this script
; ie copy the AutoHotInterception Lib folder into the TapHoldManager Lib folder
#include Lib\AutoHotInterception.ahk
#include Lib\InterceptionTapHold.ahk
#include Lib\TapHoldManager.ahk

AHI := AutoHotInterception()
; keyboard1Id := AHI.GetDeviceIdFromHandle(false, "HID\VID_03EB&PID_FF02&REV_0008&MI_03")
; keyboard1Id := AHI.GetKeyboardId(0x03EB, 0xFF02)
keyboard1Id := 3
ITH1 := InterceptionTapHold(AHI, keyboard1Id)

ITH1.Add("1", Func1)
ITH1.Add("2", Func2)
return

Func1(isHold, taps, state){
ToolTip("KB 1 Key 1`n" (isHold ? "HOLD" : "TAP") "`nTaps: " taps "`nState: " state)
}

Func2(isHold, taps, state){
ToolTip("KB 1 Key 2`n" (isHold ? "HOLD" : "TAP") "`nTaps: " taps "`nState: " state)
}

^Esc::{
ExitApp
}
49 changes: 49 additions & 0 deletions AHK v2/KeyboardToMouse.ahk
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#Requires AutoHotkey v2.0
/*
Remaps Keyboard Numpad to Mouse cursor movement
Press and hold a key to move
Double-tap and hold to move quicker
Triple-tap and hold to move even quicker
etc...
*/
#include Lib\TapHoldManager.ahk
MoveMultiplier := 5
InitMoveVectors()

thm := TapHoldManager()
thm.Add("NumpadLeft", HandleInput.Bind("x", "-1"))
thm.Add("NumpadRight", HandleInput.Bind("x", "1"))

thm.Add("NumpadUp", HandleInput.Bind("y", "-1"))
thm.Add("NumpadDown", HandleInput.Bind("y", "1"))

HandleInput(axis, dir, isHold, taps, state := -1){
global MoveVectors, MoveMultiplier
ToolTip("isHold: " isHold ", Axis: " axis ", Dir: " dir ", Taps: " taps ", State: " state)
if (state == 1){
MoveVectors[axis] := (dir * taps) * MoveMultiplier
SetTimer(DoMove, 10)
} else if (state == 0){
MoveVectors[axis] := 0
if (MoveVectors["x"] == 0 && MoveVectors["y"] == 0){
SetTimer(DoMove, 0)
}
} else {
; Tap
InitMoveVectors()
MoveVectors[axis] := (dir * taps) * MoveMultiplier
MouseMove(MoveVectors["x"], MoveVectors["y"], 0, "R")
}
}

InitMoveVectors(){
global MoveVectors
MoveVectors := Map("x", 0, "y", 0)
}

DoMove(){
global MoveVectors
MouseMove(MoveVectors["x"], MoveVectors["y"] , 0, "R")
}

^Esc::ExitApp
52 changes: 52 additions & 0 deletions AHK v2/Lib/InterceptionTapHold.ahk
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#Requires AutoHotkey v2.0

; Patch for TapAndHoldManager to convert it to use Interception
class InterceptionTapHold extends TapHoldManager {
__New(ahi, id, tapTime?, holdTime?, maxTaps?, block := true){
this.AHI := ahi
this.isMouse := (id > 10)
this.block := block
this.id := id
super.__New(tapTime?, holdTime?, maxTaps?)
}

Add(keyName, callback, tapTime?, holdTime?, maxTaps?, block?){
this.Bindings[keyName] := InterceptionKeyManager(this, keyName, callback, tapTime ?? this.tapTime, holdTime ?? this.holdTime, maxTaps ?? this.maxTaps, block ?? this.block)
}

GetKeyboardList(){
return this.AHI.GetDeviceList()
}
}

class InterceptionKeyManager extends TapHoldManager.KeyManager {
mouseButtonIds := {LButton: 0, RButton: 1, MButton: 2, XButton1: 3, XButton2: 4}
__New(manager, keyName, Callback, tapTime, holdTime, maxTaps, block){
this.block := block ?? manager.block
super.__New(manager, keyName, Callback, tapTime, holdTime, maxTaps)
}

DeclareHotkeys(){
this.SetState(1)
}

SetState(state){
if (this.manager.isMouse){
if (!this.mouseButtonIds.HasKey(this.keyName)){
MsgBox("Unknown Mouse Button name " this.keyName)
ExitApp
}
keyName := this.mouseButtonIds[this.keyName]
if (state)
result := this.manager.AHI.SubscribeMouseButton(this.manager.id, keyName, this.block, this.KeyEvent.Bind(this))
else
result := this.manager.AHI.UnsubscribeMouseButton(this.manager.id, keyName)

} else {
if (state)
result := this.manager.AHI.SubscribeKey(this.manager.id, GetKeySC(this.keyName), this.block, this.KeyEvent.Bind(this))
else
result := this.manager.AHI.UnsubscribeKey(this.manager.id, GetKeySC(this.keyName))
}
}
}
180 changes: 180 additions & 0 deletions AHK v2/Lib/TapHoldManager.ahk
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#Requires AutoHotkey v2.0

class TapHoldManager {
Bindings := Map()

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

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

; Remove a key
RemoveHotkey(keyName){
this.Bindings.Delete(keyName).SetState(0)
}

; Pause a key
PauseHotkey(keyName){
this.Bindings[keyName].SetState(0)
}

; Unpause a key
ResumeHotkey(keyName){
this.Bindings[keyName].SetState(1)
}

class KeyManager {
; AutoHotInterception mod does not use prefixes or window, so these parameters must be optional
__New(manager, keyName, callback, tapTime, holdTime, maxTaps, prefixes?, window?){
this.state := 0 ; Current state of the key
this.sequence := 0 ; Number of taps so far

this.holdWatcherState := 0 ; Are we potentially in a hold state?
this.tapWatcherState := 0 ; Has a tap release occurred and another could possibly happen?

this.holdActive := 0 ; A hold was activated and we are waiting for the release

this.manager := manager
this.keyName := keyName
this.callback := callback
this.tapTime := tapTime
this.holdTime := holdTime
this.maxTaps := maxTaps
this.prefixes := prefixes ?? manager.prefixes
this.window := window ?? manager.window

this.HoldWatcherFn := this.HoldWatcher.Bind(this)
this.TapWatcherFn := this.TapWatcher.Bind(this)
this.JoyReleaseFn := this.JoyButtonRelease.Bind(this)
this.DeclareHotkeys()
}

; Internal use only - declares hotkeys
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.JoyReleaseFn, 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
}

; Turns On/Off hotkeys (should be previously declared) // state is either "1: On" or "0: Off"
SetState(state){
; "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
}

; For correcting a bug in AHK
; A joystick button hotkey such as "1Joy1 up::" will fire on button down, and not on release up
; So when the button is pressed, we start a timer which checks the actual state of the button using GetKeyState...
; ... and when it is actually released, we fire the up event
JoyButtonRelease(){
if (GetKeyState(this.keyName))
return
SetTimer this.JoyReleaseFn, 0
this.KeyEvent(0)
}

; Called when key events (down / up) occur
KeyEvent(state, *){
if (state == this.state)
return ; Suppress Repeats
this.state := state
if (state){
; Key went down
this.sequence++
this.SetHoldWatcherState(1)
} else {
; Key went up
this.SetHoldWatcherState(0)
if (this.holdActive){
this.holdActive := 0
SetTimer this.FireCallback.Bind(this, this.sequence, 0), -1
this.ResetSequence()
return
} else {
if (this.maxTaps && this.Sequence == this.maxTaps){
SetTimer this.FireCallback.Bind(this, this.sequence, -1), -1
this.ResetSequence()
} else {
this.SetTapWatcherState(1)
}
}
}
}

; Resets everything once a sequence completes
ResetSequence(){
this.SetHoldWatcherState(0)
this.SetTapWatcherState(0)
this.sequence := 0
this.holdActive := 0
}

; When a key is pressed, if it is not released within tapTime, then it is considered a hold
SetHoldWatcherState(state){
this.holdWatcherState := state
SetTimer this.HoldWatcherFn, (state ? "-" this.holdTime : 0)
}

; When a key is released, if it is re-pressed within tapTime, the sequence increments
SetTapWatcherState(state){
this.tapWatcherState := state
; SetTimer this.TapWatcherFn, (state ? "-" this.tapTime : 0)
SetTimer this.TapWatcherFn, (state ? "-" this.tapTime : 0)
}

; 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.ResetSequence()
}
}

; Fires the user-defined callback
FireCallback(seq, state := -1){
this.Callback.Call(state != -1, seq, state)
}
}
}
Loading

0 comments on commit 7a65441

Please sign in to comment.