Leader Key+ sequence

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
mlnggg
Posts: 2
Joined: 30 Mar 2018, 09:21

Leader Key+ sequence

30 Mar 2018, 09:51

I'd like to create a "leader key", kind of like in Vim/emacs so I can trigger macros using key sequences. In other words, you press <KeyCombination> and then type a hotstring to trigger a macro.

Ideally:

Code: Select all


#IfWinActive, ahk_class Notepad
^a::MsgBox "..." ;this is the leader key (press and release) that brings up a menu of further hotstrings (g, h, j)

if key = g {MsgBox "You typed 'g'"}
else if  key = h {do something else}
else if key = j {..}
else if key - Esc {close MsgBox and return}
/some code 
Is this possible to do?
gregster
Posts: 9111
Joined: 30 Sep 2013, 06:48

Re: Leader Key+ sequence

30 Mar 2018, 10:04

Welcome to the forum!
I would look into triggering Input (I guess, the second example in the docs seems to go in your direction) or possibly InputBox with your hotkey.
mlnggg
Posts: 2
Joined: 30 Mar 2018, 09:21

Re: Leader Key+ sequence

30 Mar 2018, 15:30

Thanks. Is there a way to close input boxes/msg boxes without hitting "enter". I am basically looking for "any key" to close the message box and proceed.
MaxAstro
Posts: 557
Joined: 05 Oct 2016, 13:00

Re: Leader Key+ sequence

30 Mar 2018, 15:59

Actually, there is a much better solution. EvilC wrote a piece of code specifically for triggering a function with a sequence of keys. His code, including an example, is reproduced below. You don't really need to know how it works entirely; just drop everything except the examples into an otherwise blank ahk file and #include it in your script. Then follow the examples to create sequences. I've added a second example myself to show how a keyboard sequence would work, since his example is a joystick sequence.

Note that sequences you create need to be in the autoexecute section of the code unless you want to have to trigger a function to activate them. Once you have defined a sequence, you can call Start() and Stop() to start or stop watching for it.

Code: Select all

;; EXAMPLE
dpr := new SeqWatcher(Func("DragonPunchRight"), 0, 100) ; Dragon punch keys must be at most 100ms apart by default
dpr.AddKey("pov", new SeqPov("1JoyPov"))	; Add entry for POV
dpr.AddKey("a", new SeqButton("1Joy1"))		; Add entry for Key A

dpr.AddSeq("pov", povRight)
dpr.AddSeq("pov", povCenter)
dpr.AddSeq("pov", povDown)
dpr.AddSeq("pov", povDownRight)
dpr.AddSeq("pov", povRight)
dpr.AddSeq("a", true, 100, 300)				; Timing exception for this button - min 100ms, max 300ms after Right
dpr.Start()

DragonPunchRight()
{
	MsgBox % "You did a dragon punch!"
	return
}

;; EXAMPLE 2

seq1 := new SeqWatcher(Func("TestSequence"), 0, 100)	; Note that with the default timing, you have to type fast; change 100 to 250 for more lax timing
seq1.AddKey("S", new SeqButton("s"))		; Add entry for keyboard "s"
seq1.AddKey("E", new SeqButton("e"))		; Add entry for keyboard "e"
seq1.AddKey("Q", new SeqButton("q"))		; Add entry for keyboard "q"

seq1.AddSeq("S", true)	; True means check for key pressed
seq1.AddSeq("S", false)	; False means check for key released
seq1.AddSeq("E", true)
seq1.AddSeq("E", false)
seq1.AddSeq("Q", true)
seq1.AddSeq("Q", false)
seq1.Start()			; Start watching for the sequence

TestSequence()
{
	MsgBox % "You pressed S, E, Q!"
	return
}

;; END EXAMPLE

; All types (Key, Axis, POV) share this common base class
class SeqObj {
	__New(keyName){
		this.GetKeyStateName := keyName
	}
}

; A Button input
class SeqButton extends SeqObj {
	__New(keyName){
		base.__New(keyName)
		this.value := 0		; Keyboard default ("Unpressed") value is 0
	}
}

; An Axis input
class SeqAxis extends SeqObj {
	__New(keyName){
		base.__New(keyName)
		this.value := 50	; Axis default ("Centered") value is 50
	}
}

; A POV input
class SeqPov extends SeqObj {
	__New(keyName){
		base.__New(keyName)	; POV default ("Centered") value is -1
		this.value := -1
	}
}

; The Watcher class
class SeqWatcher {
	keys := {}
	seq := []
	max := 0
	pos := 1
	
	__New(callback, defaultMinTime := 0, defaultMaxTime := 0){
		this.Callback := callback
		this.defaultMinTime := defaultMinTime
		this.defaultMaxTime := defaultMaxTime
		this.WatcherFn := this.WatchKeys.Bind(this)
	}
	
	; Add a new key to the watch list
	AddKey(keyName, keyObj){
		this.Keys[keyName] := keyObj
	}
	
	; Add a KNOWN key to the Sequence
	AddSeq(keyName, keyValue, minTime := -1, maxTime := -1){
		if (!this.keys.HasKey(keyName)){
			MsgBox % "Unknown Key name " keyName
			ExitApp
		}
		if (this.max == 0){
			; Min/Max Time for first item is always 0
			minTime := -1, maxTime := -1
		}
		minTime := (minTime == -1 ? this.defaultMinTime : minTime)
		maxTime := (maxTime == -1 ? this.defaultMaxTime : maxTime)

		this.seq.Push({Name: keyName, Value: keyValue, MinTime: minTime, MaxTime: maxTime})
		this.max := this.seq.Length()
	}
	
	; Start watching
	Start(){
		this.pos := 1
		fn := this.WatcherFn
		SetTimer, % fn, 10
	}
	
	; Stop watching
	Stop(){
		fn := this.WatcherFn
		SetTimer, % fn, Off
	}
	
	; Called repeatedly when watching, to see if the next key in sequence was hit
	WatchKeys(){
		; Iterate through all the keys we are watching
		for i, keyObj in this.keys {
			nextSeq := this.seq[this.pos]
			nextKey := this.keys[nextSeq.name]
			; The KeyName (eg "1Joy1") that we are expecting to see next in the sequence
			expectedKeyStateName := nextKey.GetKeyStateName
			; The Value for the KeyName that we are expecting to see next in the sequence
			expectedKeyStateValue := nextSeq.value

;			ToolTip % "NextKey: " expectedKeyStateName ", NextValue: " expectedKeyStateValue	; Uncomment to monitor sequences via tooltip

			isWithinMin := 1
			isWithinMax := 1
			
			if (this.lastKeyTime == 0){
				isWithinMin := 1, isWithinMax := 1
			} else {
				timeSinceLastKey := A_TickCount - this.lastKeyTime
				isWithinMin := ( nextSeq.MinTime == 0 ? true : timeSinceLastKey > nextSeq.MinTime )
				isWithinMax := ( nextSeq.MaxTime == 0 ? true : timeSinceLastKey < nextSeq.MaxTime )
			}
			
			; The Actual value we saw for this key
			value := GetKeyState(keyObj.GetKeyStateName)
			
			; Did this key change?
			if (keyObj.value != value){
				; This key changed
				keyObj.value := value
				; Is this key the next in seqence, and is it the value we are expecting?
				if (keyObj.GetKeyStateName == expectedKeyStateName && keyObj.value == expectedKeyStateValue && isWithinMin && isWithinMax){
					; Yes - move position on
					this.pos++
					this.lastKeyTime := A_TickCount
				} else {
					; No - reset position
					this.ResetSequence()
				}
				; Debugging - show current position
				;~ ToolTip % "POS: " this.pos
			}
			
			; Check if sequence complete
			if (this.pos > this.max){
				; Reset to beginning of sequence
				this.ResetSequence()
				; Fire the Callback
				this.callback.Call()
			}
		}
	}
	
	ResetSequence(){
		this.pos := 1
		this.lastKeyTime := 0
	}
}

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot] and 402 guests