What Limits >2 Key Combos?

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
abcjme
Posts: 65
Joined: 08 Mar 2018, 10:48

What Limits >2 Key Combos?

25 Mar 2018, 16:58

In general, non-modifier key combos are limited to 2 keys. From what I could gather, this is true for all OSs. With that noted, what causes this limitation? Is it micro-coded into all mainstream/common mobos?
Rohwedder
Posts: 7771
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: What Limits >2 Key Combos?

26 Mar 2018, 03:51

Hallo,
try:

Code: Select all

!a:: ;3 Key Combo: Alt+a b c  (or  Alta + b c)
    Keys = a
    Loop
    {
        Input, Key, ML1T.2
        Keys .= Key
    }
    Until (A_TimeSinceThisHotkey > 3000) Or !InStr("ab",Keys)
	;max. timespan 3 seconds
    If Keys = abc
        MsgBox, True
    Else
        MsgBox, False
Return
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: What Limits >2 Key Combos?

26 Mar 2018, 05:29

It is a limitation of many, but not all, keyboards, see for example this topic: Key Rollover.

Cheers.
abcjme
Posts: 65
Joined: 08 Mar 2018, 10:48

Re: What Limits >2 Key Combos?

03 Apr 2018, 23:48

Helgef wrote:It is a limitation of many, but not all, keyboards, see for example this topic: Key Rollover.

Cheers.
I'm talking about making a hotkey out of multi-keys. Even if my keyboard can handle 5 key combos, I can't make a key combo out of it.

For example, I can't do:
  • a & s & d & f & g::testing
Even with more complicated coding, you can only output x>2 key combos to 1 character. You can't do x>2 key combos to multi-characters.

For example, you can do:
  • 1 & 2 & 3 = f
But you can't do:
  • 1 & 2 & 3 = fg
The code that allows x>2 key-combos to a single character.

Code: Select all

keys =  asd
Braille =
(
a:1 b:2 c:3 d:12 e:23 f:123 ...
)
StringSplit k,keys
Loop % k0
   Hotkey % "*" k%A_Index%, scan
return

scan:
   critical
   prev:= new, new:= ""
   Loop % k0
      If GetKeyState(k%A_Index%,"P")
         new.= A_Index
   If (new >= prev)
      valid:= ":" new " ", char:= SubStr(Braille,Instr(Braille,valid)-1,1)
   ToolTip % char valid
   If new
      SetTimer scan, -100
   else SendRaw % char
return
abcjme
Posts: 65
Joined: 08 Mar 2018, 10:48

Re: What Limits >2 Key Combos?

03 Apr 2018, 23:49

Rohwedder wrote:Hallo,
try:

Code: Select all

!a:: ;3 Key Combo: Alt+a b c  (or  Alta + b c)
    Keys = a
    Loop
    {
        Input, Key, ML1T.2
        Keys .= Key
    }
    Until (A_TimeSinceThisHotkey > 3000) Or !InStr("ab",Keys)
	;max. timespan 3 seconds
    If Keys = abc
        MsgBox, True
    Else
        MsgBox, False
Return

I couldn't get this to work. It's return false every time, despite trying all sorts of combo pressing orders of Alt, a, b, and c; and my keyboard has extreme NKRO.
User avatar
Masonjar13
Posts: 1555
Joined: 20 Jul 2014, 10:16
Location: Не Россия
Contact:

Re: What Limits >2 Key Combos?

04 Apr 2018, 02:45

I'm not sure why the limitation is present, but an easy way around it is to use directives. Though, doing so will only (optionally) block the last key in the sequence.

Code: Select all

#if getKeyState("a","P") && getKeyState("b","P")
c::msgbox hotkey pressed
#if
Specifically, hotkey c is only ever checked once c is pressed. When c is pressed, it will evaluate the #if. If it resolves true, the hotkey sub will run. If it resolves false, the sub is not ran and the key acts normally. This means that you can press the keys specified in the directive in any order, but the hotkey must always be the last.
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
abcjme
Posts: 65
Joined: 08 Mar 2018, 10:48

Re: What Limits >2 Key Combos?

14 Apr 2018, 02:22

Helgef wrote:You can try this if you like, [Class] LongHotkeys (a & b & c & ...)
Amazing! I've been playing around with this script, and it comes close to what I had idealized. However, having a key-combo will conflict with any other key-combos that use the same keys but are of lesser quantity.

For example:

Code: Select all

#Include longhotkey.ahk
new longhotkey("a & s", "1111")
new longhotkey("s & a", "1111")
myLH:= new longhotkey("a & s & d", "2222")
myLh.enableFirstUp(enable:=1)
myLH:= new longhotkey("s & a & d", "2222")
myLh.enableFirstUp(enable:=1)
myLH:= new longhotkey("d & a & s", "2222")
myLh.enableFirstUp(enable:=1)
myLH:= new longhotkey("d & s & a", "2222")
myLh.enableFirstUp(enable:=1)
^esc::ExitApp
I can't press a & s & d, nor s & a & d; without a & s, or s & a, also triggering.

Is there a good work-around for preventing a &s, or s & a, from triggering if any other keys are being pressed; while still allowing for the 3-key-combos to still trigger?
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: What Limits >2 Key Combos?

15 Apr 2018, 05:55

Hello, when you have pressed a & s, there is no way to know if you intend to press d, a work around would require timers to delay the hotkey a & s to give you a chance to press d, which would then cancel the hotkey a & s and trigger a & s & d. Such functionality is not provided by LongHotkeys.ahk. The easy way out is to define sequences which only overlaps at the end of the sequence, like this,

Code: Select all

; example:
#singleinstance force
; This option is not available in the original longhotkey script,
LongHotkey.allowOnlyOneTrigger := true ; Allow only one hotkey to trigger per key press

; Note that the order is important here, that is, longest first
longKeys := [ 	 ["a & s & d & f",	"asdf"]
				,["a & s & d & g",	"asdg"]
				,["a & s & d & h",	"asdh"]
				,["s & d & f",		"sdf"]
				,["s & d & g",		"sdg"]
				,["s & d & h",		"sdh"]
				,["d & f",			"df"]
				,["d & g",			"dg"] 
				,["d & h",			"dh"] ]	; note the trailing ']'

; Optional context example, hotkeys only work when capslock is on.			
;LongHotkey.setGlobalContext("getKeystate", "capslock", "T")	; Enable this line to set a global context.
			
for k, longKey in longKeys {
	lh := new LongHotkey(longKey[1], longKey[2])
	lh.enableFirstUp( true )	; If using the global context, you probably want to disable this line.
}


return ; end auto execute section
esc::exitapp


; You can put this in its own file and use #include, like:
;#include longhotkeys.ahk
class LongHotkey
{
	;	;	;	; 	; 	;	;	;	;
	; Author: Helgef
	; Date: 2016-10-22
	; Instructions: https://autohotkey.com/boards/viewtopic.php?f=6&t=24145
	;
	
	; Class variables
	static instanceArray:=Object()										; Holds references to all objects derived from this class.
	static allHotkeys:=Object()											; Holds all registred hotkeys.
	static doNothingFunc:=ObjBindMethod(LongHotkey,"doNothing")			; All hotkeys are bound to this function.
	static globalContextFunc:=""										; Global context function, set via setGlobalContext()
	static allSuspended:=0												; Used for pseudo-suspending all hotkeys.
	static PriorLhk:=""													; Stores the long hotkey that was completed prior to the most recent completed lhk.
	static mostRecentLhk:=""											; Stores the most recent lhk.
	static TimeOfPriorLongHotkey:=""									; Stores the A_TickCount for the PriorLongHotkey.
	static TimeOfMostRecentLongHotkey:=""								; Stores the A_TickCount for the most recently completed LongHotkey.
	static hasFirstUps:=0												; Keeps track of whether any lhk has specified FirstUp option.
	static LatestFirstUp:=""											; For the first up option.
	; Instance variables
	hits:=0																; Tracks the progress of the long hotkey.
	contextFunc:=""														; Specified through setContext() method, to make the long hotkey context sensitive.
	suspended:=0														; Set by suspend() method, for pseudo-suspending hotkey.
	FirstUpAllowed:=1													; Used when enableFirstUp(true) has been called, to determine if upFunc should be called.
	upFunc:=""															; See comment on FirstUpAllowed.
	TimeOfThisLongHotkey:=""											; Stores the time of  the last completion for this lhk.
	
	;
	; Callable instance methods
	;
	
	setContext(function:="",params*)
	{
		; Provide a function with an optional number of parameters to act as context function.
		; This function should return true if the hotkey should be considered as in context, else false.
		; Call setContext() without passing any parameters to remove context.
		if IsFunc(function)
			this.contextFunc:=Func(function).Bind(params*)
		else if (function="")
			this.contextFunc:=""
		return
	}
	
	suspend(bool:=1)
	{
		; Pseudo-supend this hotkey. The registred hotkeys will remain, but the evaluation will terminate quick and no triggering is possible.
		; Call with bool:=1 or do not pass a parameter at all, to invoke suspension.
		; Call with bool:=0 to cancel suspension.
		; Call with bool:=-1 to toggle suspension.
		; Returns the current suspension state, ie., 1 for being suspended, 0 for not suspended.
		return this.suspended:=bool=-1?!this.suspended:bool
	}
	
	setFunction(function,params*)
	{
		; Specify the name of the function that will be called when the long hotkey is completed, along with any number of parameters.
		; Can be a label.
		; If 'function' is not a function nor label, it is considered to be a string to send, it will be passed to LongHotkey.send()
		; By default a reference to this hotkey is pushed into the params* array.
		if	RegExMatch(function,"@$")															; Mark the function name with an @ (at) at the end to omit the reference to this instance in the params* array.	Eg, "myFunc@"
			function:=SubStr(function,1,-1)
		else		
			params.push(this)
			
		if IsFunc(function)
			this.function:=Func(function).Bind(params*)											; Function to call when sequence is completed, with params.
		else if IsLabel(function)
			this.function:=function																; Label to call when sequence is completed.
		else if (function!="")																	
			this.function:=ObjBindMethod(LongHotkey,"Send",function)							; The parameter 'function' was not function nor label, send it as string instead.
		else
			this.function:=""	
	}
	
	enableFirstUp(enable:=1)
	{
		; Enable the first key in the long hotkey to trigger its normal press event on its release, in the case when no other keys in the long hotkey was pressed.
		; Call this function with parameter 0 to disable this behaviour.
		if enable
		{
			LongHotkey.hasFirstUps:=this.upFunc?LongHotkey.hasFirstUps:++LongHotkey.hasFirstUps ; Only increment hasFirstUps counter if upFunc doesn't exist.
			_downKey:=RegExReplace(this.keyList[1].down,"(?:\*|)(\w+)","{$1}") ; Encloses key name in "{ }"
			this.upFunc:=ObjBindMethod(LongHotkey,"SendFirstUp",_downKey)
		}
		else if (this.UpFunc!="")	; This check is to avoid decrement LongHotkey.hasFirstUps unless it has an upFunc
		{
			this.upFunc:=""
			LongHotkey.hasFirstUps--
		}
		return
	}
	
	ThisLongHotkey()
	{
		; Similar to A_ThisHotkey.
		; Call this method on the reference passed to the success function, to get back the string that defined the hotkey.
		; Eg, A_ThisLongHotkey:=lh.ThisLongHotkey(), where lh is the last parameter of the success function, eg, f(x1,...,xn,lh)
		return this.keys
	}
	
	TimeSinceThisLongHotkey()
	{
		; Similar to A_TimeSinceThisHotkey.
		; Returns the time (in ms) since this lhk was triggered.
		; If the long hotkey has never been triggered, this method returns -1.
		return this.TimeOfThisLongHotkey?A_TickCount-this.TimeOfThisLongHotkey:-1
	}
	
	getKeyList()
	{
		; Similar to the ThisLongHotkey() method, but here an array is returned. Note that modifers doesn't get their own spots in the array, eg,
		; keys:="^ & a & b ! & c" transforms to keyList:=[^a, ^b, ^!c]
		; This description is not correct any more                  																		< - - NOTE
		return this.keyList
	}
	
	unregister()
	{
		; Unregister this long hotkey. To free the object, do lh:="" afterwards, if you wish. Here, lh is an instance of the class LongHotkey.
		; To reregister, use reregister() method (not if lh:="" was done, obviously).
		_clonedList:=this.keyList.clone()
		LongHotkey.instanceArray.delete(this.instanceNumber)
		Hotkey, If, LongHotkey.Press()
		For _k, _key in _clonedList  ; For each key(.down) in this long hotkey, check if it is registred in any of the other hotkeys, if it is, do not unregister it, else, do.
		{
			_deleteThis:=1
			For _l, _lh in LongHotkey.instanceArray						; Search for the key in another long hotkey, if it is found, do not delete it.
			{
				For _m, _dndKey in _lh.keyList				
				{
					if (_key.down=_dndKey.down)	
					{				
						_deleteThis:=0 ; do not delete key.
						break,2
					}
				}
			}
			if _deleteThis
			{
				Hotkey, % _key.down, Off									; Turn off the hotkey, and remove it from the allHotkeys list.
				LongHotkey.allHotkeys.delete(_key.down)
			}
		}
		this.keyList:=_clonedList									    ; The clone lives.
		this.registred:=0												; This long hotkey is not registred any more.
		Hotkey, If
		return 
	}
	
	reregister()
	{
		; Reregister a long hotkey, return 1 on success, 0 otherwise.
		if this.registred
			return 0
		For _k, _key in this.keyList 										; If we get here, this is the clone.
			LongHotkey.RegisterHotkey(_key.down)
		this.instanceNumber:=LongHotkey.instanceArray.push(this)
		this.registred:=1
		return 1
	}
	
	;
	;	Callable class methods
	;
	
	suspendAll(bool:=1)
	{
		; Pseudo-supend all long hotkeys.
		; The registred hotkeys will remain, but the evaluation will terminate quick and no triggering is possible.
		; To truly suspend, use the built in Supend command.
		; Call with bool:=1 or do not pass a parameter at all, to invoke suspension.
		; Call with bool:=0 to cancel suspension.
		; Call with bool:=-1 to toggle suspension.
		; Returns the current suspension state, ie., 1 for all is suspended, 0 for not suspended
		return LongHotkey.allSuspended:=bool=-1?!LongHotkey.allSuspended:bool
	}
	
	setGlobalContext(function:="",params*)
	{
		; Provide a function with an optional number of parameters to act as global context function.
		; If this function is set and returns 0 no hotkey is active.
		; This function should return true if the hotkey should be considered as in context, else false.
		; Call with setGlobalContext() without any parameters to remove context.
		if IsFunc(function)
			LongHotkey.globalContextFunc:=Func(function).Bind(params*)
		else if (function="")
			LongHotkey.globalContextFunc:=""
		return
	}	
	unregisterAll(onOff:="Off")
	{
		; Unregisters all hotkeys.
		; Do not pass a parameter
		Hotkey, If, LongHotkey.Press()
		For _key in LongHotkey.allHotkeys
			Hotkey, % _key, % onOff
		Hotkey, If,
		return
	}
	
	reregisterAll()
	{
		; Reregisters all hotkeys.
		return LongHotkey.unregisterAll("On")
	}
	
	MostRecentLongHotkey(ref:=0)
	{
		; This is returns the most recently completed long hotkey.
		; Call with ref:=0 or without any parameter, to get the key string that defined the hotkey that triggered most recently, eg,
		; "a & b & c".
		; Call with ref:=1 to recieve a reference to the most recent long hotkey instead.
		return !ref?LongHotkey.MostRecentLhk.keys:LongHotkey.MostRecentLhk
	}
	
	TimeSinceMostRecentLongHotkey()
	{
		; Returns the time since (in ms.) the the most recently lhk was triggered.
		; If no long hotkey has been triggered, this method returns -1.
		return LongHotkey.TimeOfMostRecentLongHotkey?A_TickCount-LongHotkey.TimeOfMostRecentLongHotkey:-1
	}
	
	PriorLongHotkey(ref:=0)
	{
		; Similar to A_PriorHotkey.
		; Call with ref:=0 or without any parameter, to get the key string that defined the hotkey that triggered prior to the most recent one, eg,
		; "a & b & c".
		; Call with ref:=1 to recieve a reference to the prior long hotkey instead.
		; returns blank if no PriorLongHotkey exists.
		return !ref?LongHotkey.Priorlhk.keys:LongHotkey.Priorlhk
	}
	
	TimeSincePriorLongHotkey()
	{
		; Similar to A_TimeSincePriorHotkey
		; Returns the time since (in ms.) the prior lhk was last triggered. That is, time since the lhk prior to the most recent one was triggered.
		; If there is no prior long hotkey, this method returns -1.
		return LongHotkey.TimeOfPriorLongHotkey?A_TickCount-LongHotkey.TimeOfPriorLongHotkey:-1
	}
	;
	; End callable methods.
	;
	
	__New(keys,function,params*)
	{
		this.instanceNumber:=LongHotkey.instanceArray.Push(this)	
		this.length:=this.processKeys(keys)							; Creates a "keyList" for this instance, and returns the appropriate length.
		this.registred:=1											; Indicates that the hotkeys are registred.
		this.keys:=keys
		this.setFunction(function,params*)
	}
	
	processKeys(str)
	{
		; Pre-process routine, runs once per new long hotkey.
		; Converts the key string (str) to an array, keyList[n]:={down:modifier key_n, up: "*key_n up", tilde:true/false}
		; Eg, "^ & ~a & b & ! & c" -> 	keyList[1]:={down: "^a",  up: "*a up", tilde: 1}
		;								keyList[2]:={down: "^b",  up: "*b up", tilde: 0}
		;								keyList[3]:={down: "^!c", up: "*c up", tilde: 0}
		; Also makes a slightly redunant array: keyUpList[keyList[n].up]:=n. It is used in the Release() function, to quickly determine which part of the hotkey sequnce was released.
		;
		
		this.keyList:=Object()
		this.keyUpList:=Object()
		_modifiers:=""
		; Adjust key string (str) to fit pre-process routine
		str:=RegExReplace(str,"\s","") 									; Remove spaces.
		; Transfrom modifiers given by name to symbol styled modifier, eg, "LCtrl" --> "<^"
		_ModifierList := { LControl:"<^",LCtrl:"<^",RControl:">^",RCtrl:">^",Ctrl:"^",Control:"^"
						,LAlt:"<!",RAlt:">!",Alt:"!"
						,LShift:"<+",RShift:">+",Shift:"+"
						,LWin:"<#",RWin:">#",Win:"#"					; "Win" is not a supported key, but it works here.
						,AltGr:"<^>!"}
		
		; prepend @0 to last key if it is a modifier. Cheap way to make modifiers work as last key. This is an after-constructionm, due to some oversight. 
		if _ModifierList.HasKey(RegExReplace(str,".*&(.*)","$1"))
			str:=RegExReplace(str,"(.*&)","$1@0")
		For _name, _symbol in _ModifierList
			str:=RegExReplace(str,"i)\b" _name "\b", _symbol)			; Swap names for symbols.
		; Parse 1, tilde ~
		_ctr:=0															; For correct indexing of tilde ~.
		Loop, Parse, str,&
		{
			if RegExMatch(A_LoopField,"[\^!#+]+")
				continue
			_ctr++
			this.keyList[_ctr]:={}										; Create an empty "sub-object" at index _ctr, this will have three key-value pairs: down,up,tilde.
			if RegExMatch(A_LoopField,"~")
				this.keyList[_ctr].tilde:=1								; If key has tilde, 0 should be returned from Press(), then the key is not suppressed.
			else
				this.keyList[_ctr].tilde:=0								; Keys without tilde, should be suppressed.
		}
		str:=RegExReplace(str,"~","") 									; Remove all ~
		; Parse 2, set up key list and register hotkeys.
		_ctr:=0															; For correct indexing.
		Loop, Parse, str,&
		{
			if RegExMatch(A_LoopField,"[\^!#+]+") && !InStr(A_LoopField, "@0")						; Check if modifers. @0 is to allow last key as modifier.
			{
				_modifiers:=LongHotkey.sortModifiers(_modifiers A_LoopField)
				continue
			}
			_ctr++
			_key:=RegExReplace(A_LoopField,"@0")				; This is a cheap way to make modifiers work as last key
			LongHotkey.RegisterHotkey(_modifiers _key)			; Register this hotkey, i.e, modifier+key.
			this.keyList[_ctr].down:=_modifiers _key 			; Down events will trigger Press()					
			this.keyList[_ctr].up:=(InStr(_key,"*")?"":"*") _key " up" 	; Using this format for the up event is due to that there seemed to be problems with modifiers. That is good english.					
			; Test
			this.keyUpList[this.keyList[_ctr].up]:=_ctr					; This is slightly redundant, but it should improve performance.
			LongHotkey.allHotkeys[_modifiers _key]:=""			; This is used for un/re-registerAll().
		}
		return _ctr	; Return the length of the sequence.
	}
	
	sortModifiers(unsorted)
	{
		; Helper function for process keys. Sorts modifiers, to aviod, eg, ^!b and !^b, this enables user to instanciate
		; long hotkeys like, "ctrl & a & alt & b" and "alt & a & ctrl & b", simultaneously.
		_ModifierList := [{"<^>!":4},{"<^":2},{">^":2},{"<!":2},{">!":2},{"<+":2}
						 ,{">+":2},{"<#":2},{">#":2},{"+":1},{"^":1},{"!":1},{"#":1}]
		_sorted:=""
		For _k, _mod in _ModifierList				; This is 13 iterations.
			For _symbol, _len in _mod				; This loop is one iteration.
				if (_p:=InStr(unsorted,_symbol))
					_sorted.=SubStr(unsorted,_p,_len), unsorted:=StrReplace(unsorted,_symbol,"")
		
		return _sorted
	}
	
	;;
	;;	Hotkey evaluation methods.
	;;
	
	Press()
	{
		Critical,On
		if (LongHotkey.allSuspended || (LongHotkey.LatestFirstUp!="" && ((LongHotkey.LatestFirstUp:="") || 1)))	; If pseudo-suspended, return 0 immediately, or if first up is needed to be suppressed.
			return 0
		if (LongHotkey.globalContextFunc!="" && !LongHotkey.globalContextFunc.Call())			; Global context check
			return 0
		_upEventRegistred:=0																	; To aviod registring the up event more than once
		_oneHit:=0, _tilde:=0																	; These values will togheter determine if the output should be suppressed or not, it is an unfortunate solution, w.r.t. maintainabillity. Hopefully it works and need not be changed.
		_priority:=0, _dp:=0	; In case a lot of hotkeys are triggered at the same time, or any other reason, one can set dp:=1 to make sure the timers for the success functions is set with decreaseing priority, hence they will not interupt eachother, but execute in the order their settimer is created. If you want the timers not to be interupted by other timers and such, set priority to a high enough value.
		For _k, _lh in LongHotkey.instanceArray
		{
			if (_lh.hits=0 && (_lh.suspended || (_lh.contextFunc!="" && !_lh.contextFunc.Call()))) 	; Check if suspended, and check context only when first key is pressed.
				continue
			if (_lh.hits>0 && _lh.keyList[_lh.hits].down=A_ThisHotkey)							; Key is same as last, suppress and continue.
			{
				_oneHit:=1
				_tilde+=_lh.keyList[_lh.hits].tilde
				continue
			}
			if (_lh.keyList[_lh.hits+1].down=A_ThisHotkey)										; Check if advanced.
			{				
				_oneHit:=1
				_lh.hits+=1																		; Update hit count.
				_tilde+=_lh.keyList[_lh.hits].tilde
				if (_lh.hits=_lh.length)														; Hotkey completed.
				{
					_timerFunction:=_lh.function												; Set up function call.
					SetTimer, % _timerFunction,-1,% _priority - _dp								; priority and dp is explaind a few lines up.			
					_lh.hits-=1																	; Decrement the hit count to enable auto-repeat of the hotkey-
					_lh.FirstUpAllowed:=0														; No "first up" if hotkey completed.
					; Manage TimeSince, and PriorLongHotkey stuff.
					_lh.TimeOfThisLongHotkey:=A_TickCount
					LongHotkey.PriorLhk:=LongHotkey.mostRecentLhk								; Manage prior long hotkey.
					LongHotkey.mostRecentLhk:=_lh
					LongHotkey.TimeOfPriorLongHotkey:=LongHotkey.TimeOfMostRecentLongHotkey		; Time stamps.
					LongHotkey.TimeOfMostRecentLongHotkey:=A_TickCount
					if LongHotkey.allowOnlyOneTrigger											; Break if only one trigger allowed
						break
					continue 																	; No need to bind up-event for last key.
				}
				if !_upEventRegistred
				{
					_doNothingFunc:=LongHotkey.doNothingFunc									; Hotkey has advanced, but not compeleted.
					Hotkey, If, LongHotkey.Release()											; Bind up-event.
					Hotkey, % _lh.keyList[_lh.hits].up, % _doNothingFunc, On
					Hotkey, If,
					_upEventRegistred:=1
				}
			}
		}
		return _oneHit*(_tilde=0)  ; If there is no hit, no suppress, if there is a tilde but no hotkey advanced/completed, no suppress.
	}
	
	Release()
	{
		; Every time a key is released, all long hotkeys are set to zero hits.
		Critical, On
		Hotkey, If, LongHotkey.Release()			; Unbind this up-event.
		Hotkey, % A_ThisHotkey, Off
		Hotkey, If
		; Determine if this up event should reset or decrease the hit count for any long hotkey. Also, manages the "first_up" option
		_oneTimerSet:=0												; For first up option
		if LongHotkey.hasFirstUps									; Do this check to avoid calling noMultiHits() unless necessary.
			_noMultiHits:=LongHotkey.noMultiHits() 					; For first up option
		
		For _k, _lh in LongHotkey.instanceArray
		{
			if (_lh.hits=0)
				continue
			if (_noMultiHits && !_oneTimerSet && _lh.upFunc!="" && _lh.hits=1 && _lh.FirstUpAllowed && _lh.keyList[1].up=A_ThisHotkey)
			{
				_timerFunction:=_lh.upFunc
				SetTimer,% _timerFunction,-1
				_oneTimerSet:=1										; Only send one time, in case more than one hotkey has this as first key.
				LongHotkey.LatestFirstUp:=_lh.keyList[1].down		; This is needed to disable Press() when the first up is triggered.
			}
			; Determine new hit count for this long hotkey.
			_n:=_lh.keyUpList[A_ThisHotkey]
			if (_n!="" && _n<=_lh.hits)
				_lh.hits:=_n-1
			_lh.FirstUpAllowed:=_lh.hits?_lh.FirstUpAllowed:1
		}
		return 0
	}
	
	noMultiHits()
	{
		; Helper function for FirstUp option, called from Release()
		For _k, _lh in LongHotkey.instanceArray
			if !_lh.FirstUpAllowed
				return 0
		return 1
	}
	
	RegisterHotkey(key)
	{
		; Register key to function doNothing(), under context LongHotkey.Press()
		_doNothingFunc:=LongHotkey.doNothingFunc
		Hotkey, If, LongHotkey.Press()
		if !LongHotkey.allHotkeys.HasKey(key)	; Make sure key not already registred.
		{
			Hotkey,% key,% _doNothingFunc, On
			LongHotkey.allHotkeys[key]:=""
		}
		Hotkey, If
		return
	}
	
	doNothing(){
		return		; All hotkeys are bound to this, serves two purposes:
	}				; 1. The hotkey command require a function/label, 2. calling this function will suppress the "usual" output of the hotkey, when needed.
	
	SendFirstUp(key)
	{
		; Function to send first key in case enableFirstUp(1) has been called
		SendLevel,1 ; If there is problems with the first up function, try to increase/remove this. 
		Send, % key
		return
	}
	
	Send(str)
	{
		; Mostly for testing, but works if wanted.
		SendInput, % str
		return
	}
	
	; For the hotkey command.
	#If LongHotkey.Press()
	#If LongHotkey.Release()
	#If
}
The code above uses a slightly modified version of longhotkeys.ahk. I would recommend to either use a modifier key for each sequence, eg, alt & a & s ... or a global context to avoid interfering with normal typing, instead of using the first up option. There is an example of a global context in the code above, see comments in code.

Thanks for your feedback, cheers :wave:
abcjme
Posts: 65
Joined: 08 Mar 2018, 10:48

Re: What Limits >2 Key Combos?

16 Apr 2018, 05:32

Helgef wrote:...
Hmmm, that doesn't seem to be quite what I had in mind. The outputs of a 2-key combo seem to require relation and dependence to any 3-key combo that the same 2 keys are in. I was looking for something that could give completely different outputs for a 2-key combo and a 3-key combo, even if they contained some of the same keys.

For example:
a & s = 83632
a & s & d = mlpphubji
*those characters were essentially chosen at random.

Is such a thing possible???
User avatar
BriHecato
Posts: 124
Joined: 18 Jul 2017, 07:17

Re: What Limits >2 Key Combos?

16 Apr 2018, 05:52

It's possible - depend only on order of keys.
Pressing first, keywait, pressing second, keywait, pressing third?
It would be independed to any others.

About hardware - some keyboards are limited some are not (i think gamer keyboards has "wider badnwidth")
Why are you so into 3+ key combos, while you can do d+e d+r d+f d+v d+c chordings easily, you can use all 3 modifiers at once, long hold key, double and triple keypress... :)
abcjme
Posts: 65
Joined: 08 Mar 2018, 10:48

Re: What Limits >2 Key Combos?

16 Apr 2018, 06:58

BriHecato wrote:About hardware - some keyboards are limited some are not (i think gamer keyboards has "wider badnwidth")
My keyboard has top-of-the-line NKRO.
BriHecato wrote:Why are you so into 3+ key combos, while you can do d+e d+r d+f d+v d+c chordings easily, you can use all 3 modifiers at once, long hold key, double and triple keypress... :)
I'm interested for creating a custom stenotyper with a normal keyboard.
User avatar
BriHecato
Posts: 124
Joined: 18 Jul 2017, 07:17

Re: What Limits >2 Key Combos?

16 Apr 2018, 08:13

My girl works previously in court as stenotypist so maybe hotstrings would be a faster solution ? (and already built in AHK)
abcjme
Posts: 65
Joined: 08 Mar 2018, 10:48

Re: What Limits >2 Key Combos?

16 Apr 2018, 08:29

BriHecato wrote:My girl works previously in court as stenotypist so maybe hotstrings would be a faster solution ? (and already built in AHK)
Hotstrings are a possibility, and I do use them. However, it'd be nice to at least have the option of 3-key combos, and their 2-key combos counterparts; without the previously mentioned conflicts.
MaxAstro
Posts: 557
Joined: 05 Oct 2016, 13:00

Re: What Limits >2 Key Combos?

16 Apr 2018, 08:56

Switch the order around. For example:

a & s = 83632
a & d & s = mlpphubji

You'll have to remember the correct order to press them in, but they shouldn't conflict.
abcjme
Posts: 65
Joined: 08 Mar 2018, 10:48

Re: What Limits >2 Key Combos?

16 Apr 2018, 09:31

MaxAstro wrote:Switch the order around. For example:

a & s = 83632
a & d & s = mlpphubji

You'll have to remember the correct order to press them in, but they shouldn't conflict.
For steno use, it's no good to do it that way though. Key presses go too fast. It's best to have all orders of any key-combo equal the same thing.

E.g. (notice that the following hotkeys only involves 3 characters, but all 2-key and 3-key combos of them):

a & s = 1234
s & a = 1234

s & d = mlpokn
d & s = mlpokn

a & d = ghghgh
d & a = ghghgh

a & s & d = 5678
a & d & s = 5678
d & a & s = 5678
d & s & a = 5678
s & a & d = 5678
s & d & a = 5678
User avatar
evilC
Posts: 4824
Joined: 27 Feb 2014, 12:30

Re: What Limits >2 Key Combos?

16 Apr 2018, 09:40

BriHecato wrote:Pressing first, keywait, pressing second, keywait, pressing third?
It would be independed to any others.
KeyWait is a blocking wait. Only one KeyWait in a script can ever be active at once.
If one hotkey is sat at a KeyWait, and another hotkey is triggered with a KeyWait, the 1st hotkey's KeyWait will not be watching for release of the key while the 2nd hotkey's KeyWait is active.
You really, really do not want to be using KeyWait for this. Timers are the way to go.

@OP - BTW, I am not sure of what your use-case is, but just to let you know that I have a new library that makes using AHK with multiple keyboards nice and easy.
You could have a 2nd keyboard (Or a number pad or something) whose keys only respond to hotstrings and hotkeys, then just use your main keyboard for typing.
AutoHotInterception

Not sure if Helgef's library could also be made to work with it, I would think it would be possible.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: What Limits >2 Key Combos?

16 Apr 2018, 10:31

If you just want to send text, you can just backspace the previous string if there is an overlap, simple example,

Code: Select all

; example:
#singleinstance force

; This option is not available in the original longhotkey script,
LongHotkey.allowOnlyOneTrigger := true ; Allow only one hotkey to trigger per key press

; Note that the order is important here, that is, longest first
longKeys := [ 	 ["a & s & d", "5678"]
				,["a & d & s", "5678"]
				,["d & a & s", "5678"]
				,["d & s & a", "5678"]
				,["s & a & d", "5678"]
				,["s & d & a", "5678"]
                ,["a & s", "1234"]
                ,["s & a", "1234"]
                ,["s & d", "mlpokn"]
                ,["d & s", "mlpokn"]
                ,["a & d", "ghghgh"]
			    ,["d & a", "ghghgh"]	]
				
; Optional context example, hotkeys only work when capslock is on.			
;LongHotkey.setGlobalContext("getKeystate", "capslock", "T")	; Enable this line to set a global context.
			
for k, longKey in longKeys 
	lh := new LongHotkey( strreplace(longKey[1], " "), "mySend", longKey[2])
	

mySend(txt, ref){
	static ptxt := ""
	local
	global LongHotkey
	sendmode event
	setkeydelay -1,-1
	if (phk := LongHotkey.PriorLongHotkey()) && strlen(phk) < strlen(mrlk:=LongHotkey.MostRecentLongHotkey()) && (instr(mrlk, phk) == 1)
		send % "{BS " . strlen(ptxt) . "}"
	ptxt := txt
	send % txt
}

return ; end auto execute section
esc::exitapp


; You can put this in its own file and use #include, like:
;#include longhotkeys.ahk
class LongHotkey
{
	;	;	;	; 	; 	;	;	;	;
	; Author: Helgef
	; Date: 2016-10-22
	; Instructions: https://autohotkey.com/boards/viewtopic.php?f=6&t=24145
	;
	
	; Class variables
	static instanceArray:=Object()										; Holds references to all objects derived from this class.
	static allHotkeys:=Object()											; Holds all registred hotkeys.
	static doNothingFunc:=ObjBindMethod(LongHotkey,"doNothing")			; All hotkeys are bound to this function.
	static globalContextFunc:=""										; Global context function, set via setGlobalContext()
	static allSuspended:=0												; Used for pseudo-suspending all hotkeys.
	static PriorLhk:=""													; Stores the long hotkey that was completed prior to the most recent completed lhk.
	static mostRecentLhk:=""											; Stores the most recent lhk.
	static TimeOfPriorLongHotkey:=""									; Stores the A_TickCount for the PriorLongHotkey.
	static TimeOfMostRecentLongHotkey:=""								; Stores the A_TickCount for the most recently completed LongHotkey.
	static hasFirstUps:=0												; Keeps track of whether any lhk has specified FirstUp option.
	static LatestFirstUp:=""											; For the first up option.
	; Instance variables
	hits:=0																; Tracks the progress of the long hotkey.
	contextFunc:=""														; Specified through setContext() method, to make the long hotkey context sensitive.
	suspended:=0														; Set by suspend() method, for pseudo-suspending hotkey.
	FirstUpAllowed:=1													; Used when enableFirstUp(true) has been called, to determine if upFunc should be called.
	upFunc:=""															; See comment on FirstUpAllowed.
	TimeOfThisLongHotkey:=""											; Stores the time of  the last completion for this lhk.
	
	;
	; Callable instance methods
	;
	
	setContext(function:="",params*)
	{
		; Provide a function with an optional number of parameters to act as context function.
		; This function should return true if the hotkey should be considered as in context, else false.
		; Call setContext() without passing any parameters to remove context.
		if IsFunc(function)
			this.contextFunc:=Func(function).Bind(params*)
		else if (function="")
			this.contextFunc:=""
		return
	}
	
	suspend(bool:=1)
	{
		; Pseudo-supend this hotkey. The registred hotkeys will remain, but the evaluation will terminate quick and no triggering is possible.
		; Call with bool:=1 or do not pass a parameter at all, to invoke suspension.
		; Call with bool:=0 to cancel suspension.
		; Call with bool:=-1 to toggle suspension.
		; Returns the current suspension state, ie., 1 for being suspended, 0 for not suspended.
		return this.suspended:=bool=-1?!this.suspended:bool
	}
	
	setFunction(function,params*)
	{
		; Specify the name of the function that will be called when the long hotkey is completed, along with any number of parameters.
		; Can be a label.
		; If 'function' is not a function nor label, it is considered to be a string to send, it will be passed to LongHotkey.send()
		; By default a reference to this hotkey is pushed into the params* array.
		if	RegExMatch(function,"@$")															; Mark the function name with an @ (at) at the end to omit the reference to this instance in the params* array.	Eg, "myFunc@"
			function:=SubStr(function,1,-1)
		else		
			params.push(this)
			
		if IsFunc(function)
			this.function:=Func(function).Bind(params*)											; Function to call when sequence is completed, with params.
		else if IsLabel(function)
			this.function:=function																; Label to call when sequence is completed.
		else if (function!="")																	
			this.function:=ObjBindMethod(LongHotkey,"Send",function)							; The parameter 'function' was not function nor label, send it as string instead.
		else
			this.function:=""	
	}
	
	enableFirstUp(enable:=1)
	{
		; Enable the first key in the long hotkey to trigger its normal press event on its release, in the case when no other keys in the long hotkey was pressed.
		; Call this function with parameter 0 to disable this behaviour.
		if enable
		{
			LongHotkey.hasFirstUps:=this.upFunc?LongHotkey.hasFirstUps:++LongHotkey.hasFirstUps ; Only increment hasFirstUps counter if upFunc doesn't exist.
			_downKey:=RegExReplace(this.keyList[1].down,"(?:\*|)(\w+)","{$1}") ; Encloses key name in "{ }"
			this.upFunc:=ObjBindMethod(LongHotkey,"SendFirstUp",_downKey)
		}
		else if (this.UpFunc!="")	; This check is to avoid decrement LongHotkey.hasFirstUps unless it has an upFunc
		{
			this.upFunc:=""
			LongHotkey.hasFirstUps--
		}
		return
	}
	
	ThisLongHotkey()
	{
		; Similar to A_ThisHotkey.
		; Call this method on the reference passed to the success function, to get back the string that defined the hotkey.
		; Eg, A_ThisLongHotkey:=lh.ThisLongHotkey(), where lh is the last parameter of the success function, eg, f(x1,...,xn,lh)
		return this.keys
	}
	
	TimeSinceThisLongHotkey()
	{
		; Similar to A_TimeSinceThisHotkey.
		; Returns the time (in ms) since this lhk was triggered.
		; If the long hotkey has never been triggered, this method returns -1.
		return this.TimeOfThisLongHotkey?A_TickCount-this.TimeOfThisLongHotkey:-1
	}
	
	getKeyList()
	{
		; Similar to the ThisLongHotkey() method, but here an array is returned. Note that modifers doesn't get their own spots in the array, eg,
		; keys:="^ & a & b ! & c" transforms to keyList:=[^a, ^b, ^!c]
		; This description is not correct any more                  																		< - - NOTE
		return this.keyList
	}
	
	unregister()
	{
		; Unregister this long hotkey. To free the object, do lh:="" afterwards, if you wish. Here, lh is an instance of the class LongHotkey.
		; To reregister, use reregister() method (not if lh:="" was done, obviously).
		_clonedList:=this.keyList.clone()
		LongHotkey.instanceArray.delete(this.instanceNumber)
		Hotkey, If, LongHotkey.Press()
		For _k, _key in _clonedList  ; For each key(.down) in this long hotkey, check if it is registred in any of the other hotkeys, if it is, do not unregister it, else, do.
		{
			_deleteThis:=1
			For _l, _lh in LongHotkey.instanceArray						; Search for the key in another long hotkey, if it is found, do not delete it.
			{
				For _m, _dndKey in _lh.keyList				
				{
					if (_key.down=_dndKey.down)	
					{				
						_deleteThis:=0 ; do not delete key.
						break,2
					}
				}
			}
			if _deleteThis
			{
				Hotkey, % _key.down, Off									; Turn off the hotkey, and remove it from the allHotkeys list.
				LongHotkey.allHotkeys.delete(_key.down)
			}
		}
		this.keyList:=_clonedList									    ; The clone lives.
		this.registred:=0												; This long hotkey is not registred any more.
		Hotkey, If
		return 
	}
	
	reregister()
	{
		; Reregister a long hotkey, return 1 on success, 0 otherwise.
		if this.registred
			return 0
		For _k, _key in this.keyList 										; If we get here, this is the clone.
			LongHotkey.RegisterHotkey(_key.down)
		this.instanceNumber:=LongHotkey.instanceArray.push(this)
		this.registred:=1
		return 1
	}
	
	;
	;	Callable class methods
	;
	
	suspendAll(bool:=1)
	{
		; Pseudo-supend all long hotkeys.
		; The registred hotkeys will remain, but the evaluation will terminate quick and no triggering is possible.
		; To truly suspend, use the built in Supend command.
		; Call with bool:=1 or do not pass a parameter at all, to invoke suspension.
		; Call with bool:=0 to cancel suspension.
		; Call with bool:=-1 to toggle suspension.
		; Returns the current suspension state, ie., 1 for all is suspended, 0 for not suspended
		return LongHotkey.allSuspended:=bool=-1?!LongHotkey.allSuspended:bool
	}
	
	setGlobalContext(function:="",params*)
	{
		; Provide a function with an optional number of parameters to act as global context function.
		; If this function is set and returns 0 no hotkey is active.
		; This function should return true if the hotkey should be considered as in context, else false.
		; Call with setGlobalContext() without any parameters to remove context.
		if IsFunc(function)
			LongHotkey.globalContextFunc:=Func(function).Bind(params*)
		else if (function="")
			LongHotkey.globalContextFunc:=""
		return
	}	
	unregisterAll(onOff:="Off")
	{
		; Unregisters all hotkeys.
		; Do not pass a parameter
		Hotkey, If, LongHotkey.Press()
		For _key in LongHotkey.allHotkeys
			Hotkey, % _key, % onOff
		Hotkey, If,
		return
	}
	
	reregisterAll()
	{
		; Reregisters all hotkeys.
		return LongHotkey.unregisterAll("On")
	}
	
	MostRecentLongHotkey(ref:=0)
	{
		; This is returns the most recently completed long hotkey.
		; Call with ref:=0 or without any parameter, to get the key string that defined the hotkey that triggered most recently, eg,
		; "a & b & c".
		; Call with ref:=1 to recieve a reference to the most recent long hotkey instead.
		return !ref?LongHotkey.MostRecentLhk.keys:LongHotkey.MostRecentLhk
	}
	
	TimeSinceMostRecentLongHotkey()
	{
		; Returns the time since (in ms.) the the most recently lhk was triggered.
		; If no long hotkey has been triggered, this method returns -1.
		return LongHotkey.TimeOfMostRecentLongHotkey?A_TickCount-LongHotkey.TimeOfMostRecentLongHotkey:-1
	}
	
	PriorLongHotkey(ref:=0)
	{
		; Similar to A_PriorHotkey.
		; Call with ref:=0 or without any parameter, to get the key string that defined the hotkey that triggered prior to the most recent one, eg,
		; "a & b & c".
		; Call with ref:=1 to recieve a reference to the prior long hotkey instead.
		; returns blank if no PriorLongHotkey exists.
		return !ref?LongHotkey.Priorlhk.keys:LongHotkey.Priorlhk
	}
	
	TimeSincePriorLongHotkey()
	{
		; Similar to A_TimeSincePriorHotkey
		; Returns the time since (in ms.) the prior lhk was last triggered. That is, time since the lhk prior to the most recent one was triggered.
		; If there is no prior long hotkey, this method returns -1.
		return LongHotkey.TimeOfPriorLongHotkey?A_TickCount-LongHotkey.TimeOfPriorLongHotkey:-1
	}
	;
	; End callable methods.
	;
	
	__New(keys,function,params*)
	{
		this.instanceNumber:=LongHotkey.instanceArray.Push(this)	
		this.length:=this.processKeys(keys)							; Creates a "keyList" for this instance, and returns the appropriate length.
		this.registred:=1											; Indicates that the hotkeys are registred.
		this.keys:=keys
		this.setFunction(function,params*)
	}
	
	processKeys(str)
	{
		; Pre-process routine, runs once per new long hotkey.
		; Converts the key string (str) to an array, keyList[n]:={down:modifier key_n, up: "*key_n up", tilde:true/false}
		; Eg, "^ & ~a & b & ! & c" -> 	keyList[1]:={down: "^a",  up: "*a up", tilde: 1}
		;								keyList[2]:={down: "^b",  up: "*b up", tilde: 0}
		;								keyList[3]:={down: "^!c", up: "*c up", tilde: 0}
		; Also makes a slightly redunant array: keyUpList[keyList[n].up]:=n. It is used in the Release() function, to quickly determine which part of the hotkey sequnce was released.
		;
		
		this.keyList:=Object()
		this.keyUpList:=Object()
		_modifiers:=""
		; Adjust key string (str) to fit pre-process routine
		str:=RegExReplace(str,"\s","") 									; Remove spaces.
		; Transfrom modifiers given by name to symbol styled modifier, eg, "LCtrl" --> "<^"
		_ModifierList := { LControl:"<^",LCtrl:"<^",RControl:">^",RCtrl:">^",Ctrl:"^",Control:"^"
						,LAlt:"<!",RAlt:">!",Alt:"!"
						,LShift:"<+",RShift:">+",Shift:"+"
						,LWin:"<#",RWin:">#",Win:"#"					; "Win" is not a supported key, but it works here.
						,AltGr:"<^>!"}
		
		; prepend @0 to last key if it is a modifier. Cheap way to make modifiers work as last key. This is an after-constructionm, due to some oversight. 
		if _ModifierList.HasKey(RegExReplace(str,".*&(.*)","$1"))
			str:=RegExReplace(str,"(.*&)","$1@0")
		For _name, _symbol in _ModifierList
			str:=RegExReplace(str,"i)\b" _name "\b", _symbol)			; Swap names for symbols.
		; Parse 1, tilde ~
		_ctr:=0															; For correct indexing of tilde ~.
		Loop, Parse, str,&
		{
			if RegExMatch(A_LoopField,"[\^!#+]+")
				continue
			_ctr++
			this.keyList[_ctr]:={}										; Create an empty "sub-object" at index _ctr, this will have three key-value pairs: down,up,tilde.
			if RegExMatch(A_LoopField,"~")
				this.keyList[_ctr].tilde:=1								; If key has tilde, 0 should be returned from Press(), then the key is not suppressed.
			else
				this.keyList[_ctr].tilde:=0								; Keys without tilde, should be suppressed.
		}
		str:=RegExReplace(str,"~","") 									; Remove all ~
		; Parse 2, set up key list and register hotkeys.
		_ctr:=0															; For correct indexing.
		Loop, Parse, str,&
		{
			if RegExMatch(A_LoopField,"[\^!#+]+") && !InStr(A_LoopField, "@0")						; Check if modifers. @0 is to allow last key as modifier.
			{
				_modifiers:=LongHotkey.sortModifiers(_modifiers A_LoopField)
				continue
			}
			_ctr++
			_key:=RegExReplace(A_LoopField,"@0")				; This is a cheap way to make modifiers work as last key
			LongHotkey.RegisterHotkey(_modifiers _key)			; Register this hotkey, i.e, modifier+key.
			this.keyList[_ctr].down:=_modifiers _key 			; Down events will trigger Press()					
			this.keyList[_ctr].up:=(InStr(_key,"*")?"":"*") _key " up" 	; Using this format for the up event is due to that there seemed to be problems with modifiers. That is good english.					
			; Test
			this.keyUpList[this.keyList[_ctr].up]:=_ctr					; This is slightly redundant, but it should improve performance.
			LongHotkey.allHotkeys[_modifiers _key]:=""			; This is used for un/re-registerAll().
		}
		return _ctr	; Return the length of the sequence.
	}
	
	sortModifiers(unsorted)
	{
		; Helper function for process keys. Sorts modifiers, to aviod, eg, ^!b and !^b, this enables user to instanciate
		; long hotkeys like, "ctrl & a & alt & b" and "alt & a & ctrl & b", simultaneously.
		_ModifierList := [{"<^>!":4},{"<^":2},{">^":2},{"<!":2},{">!":2},{"<+":2}
						 ,{">+":2},{"<#":2},{">#":2},{"+":1},{"^":1},{"!":1},{"#":1}]
		_sorted:=""
		For _k, _mod in _ModifierList				; This is 13 iterations.
			For _symbol, _len in _mod				; This loop is one iteration.
				if (_p:=InStr(unsorted,_symbol))
					_sorted.=SubStr(unsorted,_p,_len), unsorted:=StrReplace(unsorted,_symbol,"")
		
		return _sorted
	}
	
	;;
	;;	Hotkey evaluation methods.
	;;
	
	Press()
	{
		Critical,On
		if (LongHotkey.allSuspended || (LongHotkey.LatestFirstUp!="" && ((LongHotkey.LatestFirstUp:="") || 1)))	; If pseudo-suspended, return 0 immediately, or if first up is needed to be suppressed.
			return 0
		if (LongHotkey.globalContextFunc!="" && !LongHotkey.globalContextFunc.Call())			; Global context check
			return 0
		_upEventRegistred:=0																	; To aviod registring the up event more than once
		_oneHit:=0, _tilde:=0																	; These values will togheter determine if the output should be suppressed or not, it is an unfortunate solution, w.r.t. maintainabillity. Hopefully it works and need not be changed.
		_priority:=0, _dp:=0	; In case a lot of hotkeys are triggered at the same time, or any other reason, one can set dp:=1 to make sure the timers for the success functions is set with decreaseing priority, hence they will not interupt eachother, but execute in the order their settimer is created. If you want the timers not to be interupted by other timers and such, set priority to a high enough value.
		For _k, _lh in LongHotkey.instanceArray
		{
			if (_lh.hits=0 && (_lh.suspended || (_lh.contextFunc!="" && !_lh.contextFunc.Call()))) 	; Check if suspended, and check context only when first key is pressed.
				continue
			if (_lh.hits>0 && _lh.keyList[_lh.hits].down=A_ThisHotkey)							; Key is same as last, suppress and continue.
			{
				_oneHit:=1
				_tilde+=_lh.keyList[_lh.hits].tilde
				continue
			}
			if (_lh.keyList[_lh.hits+1].down=A_ThisHotkey)										; Check if advanced.
			{				
				_oneHit:=1
				_lh.hits+=1																		; Update hit count.
				_tilde+=_lh.keyList[_lh.hits].tilde
				if (_lh.hits=_lh.length)														; Hotkey completed.
				{
					_timerFunction:=_lh.function												; Set up function call.
					SetTimer, % _timerFunction,-1,% _priority - _dp								; priority and dp is explaind a few lines up.			
					_lh.hits-=1																	; Decrement the hit count to enable auto-repeat of the hotkey-
					_lh.FirstUpAllowed:=0														; No "first up" if hotkey completed.
					; Manage TimeSince, and PriorLongHotkey stuff.
					_lh.TimeOfThisLongHotkey:=A_TickCount
					LongHotkey.PriorLhk:=LongHotkey.mostRecentLhk								; Manage prior long hotkey.
					LongHotkey.mostRecentLhk:=_lh
					LongHotkey.TimeOfPriorLongHotkey:=LongHotkey.TimeOfMostRecentLongHotkey		; Time stamps.
					LongHotkey.TimeOfMostRecentLongHotkey:=A_TickCount
					if LongHotkey.allowOnlyOneTrigger											; Break if only one trigger allowed
						break
					continue 																	; No need to bind up-event for last key.
				}
				if !_upEventRegistred
				{
					_doNothingFunc:=LongHotkey.doNothingFunc									; Hotkey has advanced, but not compeleted.
					Hotkey, If, LongHotkey.Release()											; Bind up-event.
					Hotkey, % _lh.keyList[_lh.hits].up, % _doNothingFunc, On
					Hotkey, If,
					_upEventRegistred:=1
				}
			}
		}
		return _oneHit*(_tilde=0)  ; If there is no hit, no suppress, if there is a tilde but no hotkey advanced/completed, no suppress.
	}
	
	Release()
	{
		; Every time a key is released, all long hotkeys are set to zero hits.
		Critical, On
		Hotkey, If, LongHotkey.Release()			; Unbind this up-event.
		Hotkey, % A_ThisHotkey, Off
		Hotkey, If
		; Determine if this up event should reset or decrease the hit count for any long hotkey. Also, manages the "first_up" option
		_oneTimerSet:=0												; For first up option
		if LongHotkey.hasFirstUps									; Do this check to avoid calling noMultiHits() unless necessary.
			_noMultiHits:=LongHotkey.noMultiHits() 					; For first up option
		
		For _k, _lh in LongHotkey.instanceArray
		{
			if (_lh.hits=0)
				continue
			if (_noMultiHits && !_oneTimerSet && _lh.upFunc!="" && _lh.hits=1 && _lh.FirstUpAllowed && _lh.keyList[1].up=A_ThisHotkey)
			{
				_timerFunction:=_lh.upFunc
				SetTimer,% _timerFunction,-1
				_oneTimerSet:=1										; Only send one time, in case more than one hotkey has this as first key.
				LongHotkey.LatestFirstUp:=_lh.keyList[1].down		; This is needed to disable Press() when the first up is triggered.
			}
			; Determine new hit count for this long hotkey.
			_n:=_lh.keyUpList[A_ThisHotkey]
			if (_n!="" && _n<=_lh.hits)
				_lh.hits:=_n-1
			_lh.FirstUpAllowed:=_lh.hits?_lh.FirstUpAllowed:1
		}
		return 0
	}
	
	noMultiHits()
	{
		; Helper function for FirstUp option, called from Release()
		For _k, _lh in LongHotkey.instanceArray
			if !_lh.FirstUpAllowed
				return 0
		return 1
	}
	
	RegisterHotkey(key)
	{
		; Register key to function doNothing(), under context LongHotkey.Press()
		_doNothingFunc:=LongHotkey.doNothingFunc
		Hotkey, If, LongHotkey.Press()
		if !LongHotkey.allHotkeys.HasKey(key)	; Make sure key not already registred.
		{
			Hotkey,% key,% _doNothingFunc, On
			LongHotkey.allHotkeys[key]:=""
		}
		Hotkey, If
		return
	}
	
	doNothing(){
		return		; All hotkeys are bound to this, serves two purposes:
	}				; 1. The hotkey command require a function/label, 2. calling this function will suppress the "usual" output of the hotkey, when needed.
	
	SendFirstUp(key)
	{
		; Function to send first key in case enableFirstUp(1) has been called
		SendLevel,1 ; If there is problems with the first up function, try to increase/remove this. 
		Send, % key
		return
	}
	
	Send(str)
	{
		; Mostly for testing, but works if wanted.
		SendInput, % str
		return
	}
	
	; For the hotkey command.
	#If LongHotkey.Press()
	#If LongHotkey.Release()
	#If
}
You can chose any strings you want, it should work, but the order must be longest first in the array.

Cheers-
abcjme
Posts: 65
Joined: 08 Mar 2018, 10:48

Re: What Limits >2 Key Combos?

18 Apr 2018, 02:39

Helgef wrote:...

You can chose any strings you want, it should work, but the order must be longest first in the array.

Cheers-
This is almost perfect for steno! The only problem seems to a bug when pressing 3-key combos really quickly. It'll replace previous characters, sometimes even backspacing first.

Demonstrated here:
https://www.dropbox.com/s/6o43w70o3oaoe ... g.mp4?dl=0
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: What Limits >2 Key Combos?

18 Apr 2018, 13:20

Hello, try this

Code: Select all

mySend(txt, ref){
	static ptxt := ""
	static phk := ""
	local
	global LongHotkey
	critical
	sendmode event
	setkeydelay -1, -1
	mrlk := ref.keys
	if phk && strlen(phk) < strlen(mrlk) && (instr(mrlk, phk) == 1)
		send % "{BS " . strlen(ptxt) . "}"
	ptxt := txt
	phk := mrlk
	send % txt
}
Note that if you do not use any other send command in your script you can move

Code: Select all

sendmode event
setkeydelay -1, -1
to the top of the script to avoid executing those lines on every hotkey trigger. It is a very insignificant detail.

Cheers.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Bing [Bot], MrHue, Rohwedder, songdg, trane18 and 343 guests