Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

PatternHotKey : Map short/long keypress patterns to anything


  • Please log in to reply
10 replies to this topic
ins0mniaque
  • Members
  • 3 posts
  • Last active: May 19 2011 08:00 AM
  • Joined: 13 May 2011
PatternHotKey

PatternHotKey allows you to map multiple pattern combination of short or long press of a hotkey to any combination of keys, labels or functions. I needed to map multiple long key press from my Boxee Remote because it has so few buttons, something like RapidHotkey, but that detected long key press. And frankly, I was not thrilled by the parameter hell of RapidHotkey (no offense intended), so... voilà!


Download:

[*:2qu8sump] PatternHotKey.ahk
Last updated: May 13th, 2011
[*:2qu8sump] PatternHotKey Test.ahk
Last updated: May 13th, 2011
Example (contents of the test file):
#Include PatternHotKey.ahk

; Simple hotkey: Make capslock toggle only on double press
;     '.' (short press) eats the Capslock key (sends no keys)
;     '..' (double short press) toggles capslock (GoSub ToggleCapsLock)
Capslock::PatternHotKey(".:","..->ToggleCapsLock")

ToggleCapsLock:
    if ( GetKeyState("Capslock", "T") )
        SetCapsLockState, Off
    else
        SetCapsLockState, On
return

; Test slow patterns (up to 10 presses)
F9::MsgBox % KeyPressPattern(10, 0.5)

; Test default patterns (up to 10 presses)
F10::MsgBox % KeyPressPattern(10)

; Test fast patterns (up to 10 presses)
F11::MsgBox % KeyPressPattern(10, 0.1)

; Test pattern hotkey
;
;     ':'  separator means Send
;     '->' separator means GoSub or function call
;          Note: Functions always receive everything as one
;                parameter: there is no parameter parsing.
F12::PatternHotKey(".:single-click"                            ; single-"click" (equivalent to '0')
                  ,"..:double-click"                           ; double-"click" (equivalent to '00')
                  ,"...:triple-click"                          ; triple-"click" (equivalent to '000')
                  ,"007:Bond, James Bond"                      ; double short then a '7' long press
                                                               ;     i.e. at 0.2 period, between 1.4 and
                                                               ;          1.6 seconds; it is hard to trigger.
                  ,"._->mylabel"                               ; short then long
                  ,"__->myfunction()"                          ; double long
                  ,"_.->myfunction(12345)"                     ; long then short
                  ,"~^[1-5]$->myfunction(long press, mate)"    ; long press between '1' and '5'
                  ,"~^[6-9A-Z]$->myfunction(very long press)"  ; long press between '6' and 'Z'
                  ,3                                           ; length (because it is integer)
                  ,0.2)                                        ; delay (because it is float)

mylabel:
    MsgBox % "gosub mylabel called"
return

myfunction(myparameter)
{
    MsgBox % "myfunction(" . myparameter . ") called"
}

Acknowledgements:
[*:2qu8sump]Laszlo for the lovely idea that is Morse()
[*:2qu8sump]HotKeyIt for RapidHotkey and messing with Morse
[*:2qu8sump]TheGood for his post design, hope it's not copyrighted ;)
Thank you all for sharing!

As always, comments and suggestions are welcome.
Enjoy!

Changelog

May 13th, 2011

- First version


  • Guests
  • Last active:
  • Joined: --
Error at line 38 of include file patterhotkey.ahk

The following variable name contains and illegal character:
"arguments*"

the program will exit.


Running winxp sp3.

genmce
  • Members
  • 144 posts
  • Last active: May 21 2015 03:09 PM
  • Joined: 10 Jan 2009
Sorry - this was from running the patterhotkey test.ahk file

and the text above was copied from the dialog that I see.

I am looking forward to trying this idea.

KeyMce/GenMce - mackie emulator for pc keyboard/Convert your controller to mackie.
Midi I/O - Want to play with midi/ahk? links dead.. pm me


  • Guests
  • Last active:
  • Joined: --

Error at line 38 of include file patterhotkey.ahk

The following variable name contains and illegal character:
"arguments*"

the program will exit.


Running winxp sp3.


I forgot to mention it is only for AutoHotkey_L, it uses avariadic function for that nice interface. I could make it work with AutoHotkey too. Is there a reason not to use Autohotkey_L ?

I have a new version I will be able to upload tomorrow that also caches the parsed arguments, and allows mapping repetitive actions for long keypresses (like increasing volume when a key is kept down).

ins0mniaque
  • Members
  • 3 posts
  • Last active: May 19 2011 08:00 AM
  • Joined: 13 May 2011
Forgot to log in, ^^^ that was me.

bitflow
  • Members
  • 5 posts
  • Last active: Dec 04 2011 07:48 PM
  • Joined: 08 Jan 2010
I was looking for a technique to allow a single keypress to trigger one action and multiple keypresses of the same key to trigger an alternate action. PatternHotkey delivers. Thank you!

Before walking away from computer, I hit Pause-Pause to lock it and turn off displays, or just Pause once to turn off the displays.
; Pause single keypress: Turn off all monitors
; Pause double keypress: Turn off all monitors and Lock Workstation
Pause::PatternHotKey(".->MonitorsOff()"
                   , "..->MonitorsOffLockWorkstation()")

MonitorsOff() {
	SendMessage, 0x112, 0xF170, 2,, Program Manager  ; Turn monitors off
}

LockWorkstation() {
	DllCall("LockWorkStation")
}

MonitorsOffLockWorkstation() {
	MonitorsOff()
	LockWorkstation()
}



Similarly, ScrollLock toggles between audio outputs, directing sound to a particular device. Once for Bluetooth, twice for Speakers or a third time for the USB headset.

; Activate sound device endpoint
;     '.'   (sinle short press)  activates Bluetooth headset
;     '..'  (double short press) activates Speakers
;     '...' (triple short press) activates USB headset
ScrollLock::PatternHotKey(".->ActivateSoundDeviceEndpoint(Bluetooth)"
                        , "..->ActivateSoundDeviceEndpoint(Klipsh)"
                        , "...->ActivateSoundDeviceEndpoint(Logitech)")

ActivateSoundDeviceEndpoint(DeviceName) {
	; close sound panel if it's open
	IfWinExist Sound ahk_class #32770
		WinClose

	; open sound panel
	Run rundll32.exe shell32.dll`,Control_RunDLL mmsys.cpl`,`,0  ; similar to Run mmsys.cpl
	WinWait Sound ahk_class #32770

	; fails if pointer is over window, get window out of the way
	WinMove, 0,8000

	; iterate over list of device endpoints
	ControlGet, len, List, Count, SysListView321
	Loop %len%
	{
		; select first or next endpoint
		ControlSend,SysListView321,{Down}

		; get its info
		ControlGet, Device_Info, List, Selected, SysListView321
		IfInString, Device_Info, %DeviceName%
		{
			; Extract endpoint title and device it's on
			 RegExMatch(Device_Info, "(?P<Title>.+)\t(?P<Device>.+)\t", Matched)

			; set default
			ControlClick, &Set Default
			ControlClick, OK
			WinWaitClose

			; play a sound on the newly activated device
			SoundPlay, *64  ; Asterisk (info)

			; show a tray tip with the new endpoint
			TrayTip %MatchedTitle%, %MatchedDevice% is now active, 2, 17 ; 2 seconds, info icon (1) without sound (+16)
			; TrayTip durations under 10 seconds don't work.  Forcibly remove after 2 seconds
			SetTimer, RemoveTrayTip, 2000

			; break loop
			return
		}
	}
	return
}


RemoveTrayTip() {
	SetTimer, RemoveTrayTip, Off
	TrayTip  ; without parameters, removes displayed traytip
}


ahk_alvin
  • Members
  • 12 posts
  • Last active: Dec 24 2011 09:44 AM
  • Joined: 19 Feb 2011
it is very nice functionality, I want to have to a try, but Links seems be invailid, I am not able to download it.

TheGreatSwami Woo
  • Members
  • 237 posts
  • Last active: Jan 22 2012 03:31 PM
  • Joined: 26 May 2011

it is very nice functionality, I want to have to a try, but Links seems be invailid, I am not able to download it.


I didnt have a problem but here is the code:
; PatternHotKey / KeyPressPattern

; PatternHotKey registers one or multiple patterns of a hotkey to either sending keys, going to a
;               label or calling a function with zero or one parameter.
;
; Usage : hotkey::PatternHotKey("command1", ["command2", "command3", length(integer), period(float)])
;
;     where commands match one of the following formats:
;         "pattern:keys"                  ; Maps pattern to send keys
;         "pattern->label"                ; Maps pattern to label (GoSub)
;         "pattern->function()"           ; Maps pattern to function myfunction with
;                                           no parameter
;         "pattern->function(value)"      ; Maps pattern to function myfunction with
;                                           the first parameter equal to 'value'
;
;         and patterns match the following formats:
;             '.' or '0' represents a short press
;             '1' to '9' and 'A' to 'Z' represents a long press of
;                                       the specified length (base 36)
;             '-' or '_' represents a long press of any length
;             '?' represents any press
;             '~' as prefix marks the following string as a
;                 regular expression for the pattern
;
;     length : Maximum length of returned pattern. Automatically detected unless
;              using custom regular expression patterns. Keeping this value to the
;              minimum will speed up keypress detection.
;     period : Amount of time in seconds to wait for additional keypresses.
;
;     e.g. "01->mylabel" maps a short press followed by a 0.2 to 0.4 seconds press to
;                        the 'mylabel' label.
;          "_:{Esc}" maps a long press to sending the Esc key.
;          ".?-_0->myfunction(1)" maps a short press followed by any press followed by
;                                 2 long press to calling 'myfunction(1)'.
;          "~^[6-9A-Z]$->myfunction()" maps the regular expression '^[6-9A-Z]$' (exact
;                                      length match a long press of length '6' to 'Z')
;                                      to calling 'myfunction()'.
PatternHotKey(arguments*)
{
    period = 0.2
    length = 1

    ; Parse input
    for index, argument in arguments
    {
        ; Use any float as period
        if argument is float
            period := argument, continue

        ; Use any integer as length. Automatically calculated
        ; unless using custom patterns ('~' prefix).
        if argument is integer
            length := argument, continue

        ; Check for Send command (':')
        separator := InStr(argument, ":", 1) - 1
        if ( separator >= 0 )
        {
            pattern   := SubStr(argument, 1, separator)
            command    = Send
            parameter := SubStr(argument, separator + 2)
        }
        else
        {
            ; Check for Function or GoSub command ('->')
            separator := InStr(argument, "->", 1) - 1
            if ( separator >= 0 )
            {
                pattern := SubStr(argument, 1, separator)

                call := Trim(SubStr(argument, separator + 3))
                parenthesis := InStr(call, "(", 1, separator) - 1
                if ( parenthesis >= 0 )
                {
                    ; Parse function name and single parameter
                    command   := SubStr(call, 1, parenthesis)
                    parameter := Trim(SubStr(call, parenthesis + 1), "()"" `t")
                }
                else
                {
                    command    = GoSub
                    parameter := call
                }
            }
            else
                continue
        }

        ; Convert pattern to regular expression
        ;
        ; Note: Treat '~' as an escape character for custom regular expressions.
        ;       Custom regular expressions can't have the pattern length
        ;       automatically calculated, so the length parameter might be
        ;       necessary.
        if ( Asc(pattern) = Asc("~") )
            pattern := SubStr(pattern, 2)
        else
        {
            ; Short press
            StringReplace, pattern, pattern, ., 0, All

            ; Long press
            StringReplace, pattern, pattern, -, [1-9A-Z], All
            StringReplace, pattern, pattern, _, [1-9A-Z], All

            ; Any press
            StringReplace, pattern, pattern, ?, [0-9A-Z], All

            ; Exact length match
            pattern := "^" . pattern . "$"

            ; Record max pattern length
            if ( length < separator )
                length := separator
        }

        patterns%index%   := pattern
        commands%index%   := command
        parameters%index% := parameter
    }

    ; Record key press pattern
    keypress := KeyPressPattern(length, period)

    ; Try to find matching pattern
    Loop %index%
    {
        pattern   := patterns%A_Index%
        command   := commands%A_Index%
        parameter := parameters%A_Index%

        if ( pattern && RegExMatch(keypress, pattern) )
        {
            if ( command = "Send" )
                Send % parameter
            else if ( command = "GoSub" and IsLabel(parameter) )
                gosub, %parameter%
            else if ( IsFunc(command) )
                %command%(parameter)
        }
    }
}

; KeyPressPattern returns a base-36 string pattern representing the recorded
;                 keypresses for the current hotkey. Each digit represents a
;                 keypress, indicating how much "pressed down time" (in amount
;                 of period) a key was down, with 0 representing a short press.
;
;     length       : Maximum length of returned pattern. Keeping this value to
;                    the minimum will speed up keypress detection.
;     period       : Amount of time in seconds to wait for additional keypresses.
;
;     e.g. (Using the default period of 0.2 seconds)
;          "01" is a short press followed by a 0.2 to 0.4 seconds press
;          "30" is a 0.6 to 0.8 seconds press followed by a short press
;          "000" is triple-click (3 short press)
KeyPressPattern(length = 2, period = 0.2)
{
    ; Find pressed key
    key := RegExReplace(A_ThisHotKey, "[\*\~\$\#\+\!\^]")
    IfInString, key, %A_Space%
        StringTrimLeft, key, key, % InStr(key, A_Space, 1)

    ; Find modifiers
    if key in Alt,Ctrl,Shift,Win
        modifiers := "{L" key "}{R" key "}"

    current = 0
    loop
    {
        ; Wait for key up
        KeyWait %key%, T%period%
        ; If key up was received...
        if ( ! ErrorLevel )
        {
            ; Append code in base 36 to pattern and reset current code
            pattern .= current < 10
                       ? current
                       : Chr(55 + ( current > 36 ? 36 : current ))
            current = 0
        }
        else
            current++

        ; Return pattern if it is of desired length
        if ( StrLen(pattern) >= length )
            return pattern

        ; If key up was received...
        if ( ! ErrorLevel )
        {
            ; Wait for next key down of the same key
            ;
            ; Capslock, mouse buttons and hotkeys using the no reentry ('$' prefix) cannot use
            ; Input. KeyWait is used instead, but it cannot detect cancelled patterns (a different
            ; key is pressed before timeout) or modifier keys.
            if ( key in Capslock, LButton, MButton, RButton or Asc(A_ThisHotkey) = Asc("$") )
            {
                KeyWait, %key%, T%period% D

                ; If key down timed out, return pattern
                if ( ErrorLevel )
                    return pattern
            }
            else
            {
                Input,, T%period% L1 V,{%key%}%modifiers%

                ; If key down timed out, return pattern
                if ( ErrorLevel = "Timeout" )
                    return pattern
                ; If a different key is pressed, cancel pattern
                else if ( ErrorLevel = "Max" )
                    return
                ; If input is cancelled, cancel pattern
                else if ( ErrorLevel = "NewInput" )
                    return
            }
        }
    }
}


ahk_alvin
  • Members
  • 12 posts
  • Last active: Dec 24 2011 09:44 AM
  • Joined: 19 Feb 2011
thanks, because <!-- m -->http://www.dropbox.com/<!-- m --> can not open in china. I cound't download the code.

it is very nice functionality, I want to have to a try, but Links seems be invailid, I am not able to download it.


I didnt have a problem but here is the code:
; PatternHotKey / KeyPressPattern

; PatternHotKey registers one or multiple patterns of a hotkey to either sending keys, going to a
;               label or calling a function with zero or one parameter.
;
; Usage : hotkey::PatternHotKey("command1", ["command2", "command3", length(integer), period(float)])
;
;     where commands match one of the following formats:
;         "pattern:keys"                  ; Maps pattern to send keys
;         "pattern->label"                ; Maps pattern to label (GoSub)
;         "pattern->function()"           ; Maps pattern to function myfunction with
;                                           no parameter
;         "pattern->function(value)"      ; Maps pattern to function myfunction with
;                                           the first parameter equal to 'value'
;
;         and patterns match the following formats:
;             '.' or '0' represents a short press
;             '1' to '9' and 'A' to 'Z' represents a long press of
;                                       the specified length (base 36)
;             '-' or '_' represents a long press of any length
;             '?' represents any press
;             '~' as prefix marks the following string as a
;                 regular expression for the pattern
;
;     length : Maximum length of returned pattern. Automatically detected unless
;              using custom regular expression patterns. Keeping this value to the
;              minimum will speed up keypress detection.
;     period : Amount of time in seconds to wait for additional keypresses.
;
;     e.g. "01->mylabel" maps a short press followed by a 0.2 to 0.4 seconds press to
;                        the 'mylabel' label.
;          "_:{Esc}" maps a long press to sending the Esc key.
;          ".?-_0->myfunction(1)" maps a short press followed by any press followed by
;                                 2 long press to calling 'myfunction(1)'.
;          "~^[6-9A-Z]$->myfunction()" maps the regular expression '^[6-9A-Z]$' (exact
;                                      length match a long press of length '6' to 'Z')
;                                      to calling 'myfunction()'.
PatternHotKey(arguments*)
{
    period = 0.2
    length = 1

    ; Parse input
    for index, argument in arguments
    {
        ; Use any float as period
        if argument is float
            period := argument, continue

        ; Use any integer as length. Automatically calculated
        ; unless using custom patterns ('~' prefix).
        if argument is integer
            length := argument, continue

        ; Check for Send command (':')
        separator := InStr(argument, ":", 1) - 1
        if ( separator >= 0 )
        {
            pattern   := SubStr(argument, 1, separator)
            command    = Send
            parameter := SubStr(argument, separator + 2)
        }
        else
        {
            ; Check for Function or GoSub command ('->')
            separator := InStr(argument, "->", 1) - 1
            if ( separator >= 0 )
            {
                pattern := SubStr(argument, 1, separator)

                call := Trim(SubStr(argument, separator + 3))
                parenthesis := InStr(call, "(", 1, separator) - 1
                if ( parenthesis >= 0 )
                {
                    ; Parse function name and single parameter
                    command   := SubStr(call, 1, parenthesis)
                    parameter := Trim(SubStr(call, parenthesis + 1), "()"" `t")
                }
                else
                {
                    command    = GoSub
                    parameter := call
                }
            }
            else
                continue
        }

        ; Convert pattern to regular expression
        ;
        ; Note: Treat '~' as an escape character for custom regular expressions.
        ;       Custom regular expressions can't have the pattern length
        ;       automatically calculated, so the length parameter might be
        ;       necessary.
        if ( Asc(pattern) = Asc("~") )
            pattern := SubStr(pattern, 2)
        else
        {
            ; Short press
            StringReplace, pattern, pattern, ., 0, All

            ; Long press
            StringReplace, pattern, pattern, -, [1-9A-Z], All
            StringReplace, pattern, pattern, _, [1-9A-Z], All

            ; Any press
            StringReplace, pattern, pattern, ?, [0-9A-Z], All

            ; Exact length match
            pattern := "^" . pattern . "$"

            ; Record max pattern length
            if ( length < separator )
                length := separator
        }

        patterns%index%   := pattern
        commands%index%   := command
        parameters%index% := parameter
    }

    ; Record key press pattern
    keypress := KeyPressPattern(length, period)

    ; Try to find matching pattern
    Loop %index%
    {
        pattern   := patterns%A_Index%
        command   := commands%A_Index%
        parameter := parameters%A_Index%

        if ( pattern && RegExMatch(keypress, pattern) )
        {
            if ( command = "Send" )
                Send % parameter
            else if ( command = "GoSub" and IsLabel(parameter) )
                gosub, %parameter%
            else if ( IsFunc(command) )
                %command%(parameter)
        }
    }
}

; KeyPressPattern returns a base-36 string pattern representing the recorded
;                 keypresses for the current hotkey. Each digit represents a
;                 keypress, indicating how much "pressed down time" (in amount
;                 of period) a key was down, with 0 representing a short press.
;
;     length       : Maximum length of returned pattern. Keeping this value to
;                    the minimum will speed up keypress detection.
;     period       : Amount of time in seconds to wait for additional keypresses.
;
;     e.g. (Using the default period of 0.2 seconds)
;          "01" is a short press followed by a 0.2 to 0.4 seconds press
;          "30" is a 0.6 to 0.8 seconds press followed by a short press
;          "000" is triple-click (3 short press)
KeyPressPattern(length = 2, period = 0.2)
{
    ; Find pressed key
    key := RegExReplace(A_ThisHotKey, "[\*\~\$\#\+\!\^]")
    IfInString, key, %A_Space%
        StringTrimLeft, key, key, % InStr(key, A_Space, 1)

    ; Find modifiers
    if key in Alt,Ctrl,Shift,Win
        modifiers := "{L" key "}{R" key "}"

    current = 0
    loop
    {
        ; Wait for key up
        KeyWait %key%, T%period%
        ; If key up was received...
        if ( ! ErrorLevel )
        {
            ; Append code in base 36 to pattern and reset current code
            pattern .= current < 10
                       ? current
                       : Chr(55 + ( current > 36 ? 36 : current ))
            current = 0
        }
        else
            current++

        ; Return pattern if it is of desired length
        if ( StrLen(pattern) >= length )
            return pattern

        ; If key up was received...
        if ( ! ErrorLevel )
        {
            ; Wait for next key down of the same key
            ;
            ; Capslock, mouse buttons and hotkeys using the no reentry ('$' prefix) cannot use
            ; Input. KeyWait is used instead, but it cannot detect cancelled patterns (a different
            ; key is pressed before timeout) or modifier keys.
            if ( key in Capslock, LButton, MButton, RButton or Asc(A_ThisHotkey) = Asc("$") )
            {
                KeyWait, %key%, T%period% D

                ; If key down timed out, return pattern
                if ( ErrorLevel )
                    return pattern
            }
            else
            {
                Input,, T%period% L1 V,{%key%}%modifiers%

                ; If key down timed out, return pattern
                if ( ErrorLevel = "Timeout" )
                    return pattern
                ; If a different key is pressed, cancel pattern
                else if ( ErrorLevel = "Max" )
                    return
                ; If input is cancelled, cancel pattern
                else if ( ErrorLevel = "NewInput" )
                    return
            }
        }
    }
}



Bennomoehlman
  • Members
  • 9 posts
  • Last active: May 10 2017 06:55 PM
  • Joined: 12 Dec 2011
Hi,
I know this thread is old, but hopefully someone can help me.

I would like to have access to German Umlauts, i.e. ä, ü, etc. And I would like to access them by long-pressing "a" or "u". I don't care about the double- or triple-click in this case (though I really like it's implementation).

The problem is that with the current script, if I set up the longpress, then the single-click is too slow for regular typing. Is there any way to set it up that the script only kicks in if the key is pressed longer than a certain amount of time and therefore the letter "a" can be used for regular typing?

Thanks.

Bargav
  • Members
  • 17 posts
  • Last active: Dec 18 2015 09:30 PM
  • Joined: 04 Mar 2013

Pattern Hotkey script is amazing. But Multiple Hotkeys are not working. Only Single Hotkeys are working. I hope you will soon solve this issue.