Jump to content

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

Chording Script


  • Please log in to reply
18 replies to this topic
ManaUser
  • Members
  • 1121 posts
  • Last active: Dec 07 2016 04:24 PM
  • Joined: 24 May 2007
A couple people have asked about this recently, so I think I'd see what I could make, and it turned out rather nice, if I do say so myself. It's still experimental though, so be sure and let me know if you find any problems.

See the comment at the top for a quick explanation of what "chording" is.

;Chord.ahk by Paul Pliska (ManaUser) - Version 2.0

#SingleInstance Force
#NoEnv
SendMode Input

; Chording can mean a few different things but what I mean by it here
; is pressing two buttons at the same time and getting a different
; result than when you press them alone. This isn't the same as just
; holding one button and pressing the other one, you have to press
; them down together.

; With that out of the way, let's take a look at MakeChord:

;       MakeChord(Note1, Note2, Result[, Delay, Context])

; Note1 and Note2 are the two keys or mouse buttons that combine
; to make the chord. Due to the delay, I don't recommend keys that
; are frequently used in typing, like letters or shift, unless you
; limit it to a specific application. (More on that in a moment.)

; Result can be one of three things:

;    1. A label. This will be executed when the chord is pressed.
;       When the chord is released, another label with the same
;       name followed by "_Up" will be executed, if it exists.
;    2. A single key or mouse button. This will be "pressed" for
;       as long as the chord is held.
;    3. A string for the Send command. This will be sent once
;       when the chord is pressed.

; The script will attempt to interpret Result as one of those three
; things in that order, so a string for Send is the default.

; Delay, if present, is how close together the key presses have to
; happen to count as a chord, in milliseconds. When you press one of the
; chord keys, its normal function will be delayed by that much to give
; you a chance to make the chord. A longer delay makes it easier to
; perform a chord, but if its too long, the delay will be noticeable,
; which is annoying.

; Context, if present, controls which applications the chord applies to.
; This works exactly the same as the Title parameter of #IfWinActive.
; If you omit the Context parameter, the chord applies everywhere. You
; can create a chord in multiple contexts, with different results. If
; more than one apply, the one that was created first will activate,
; except the "everywhere" context, which has the lowest priority.
; If you want to specify a context, but use the defelt delay, use zero
; for the delay.

; You can also change the delay using another function: SetKeyDelay()

;       SetKeyDelay(20, "LButton RButton MButton XButton1 XButton2")
;       SetKeyDelay(75)

; The first example sets the delay for all mouse buttons to 20 ms.
; The second sets the default delay to 75, it will be 50 otherwise.

; Some Examples:

MakeChord("LButton", "RButton", "MButton", 30)
MakeChord("RShift", "Enter", "NumpadEnter", 0, "ahk_class Photoshop")
MakeChord("F11", "F12", "UserName{tab}Password{enter}")
MakeChord("XButton1", "XButton2", "CtrlDrag")
MakeChord("#1", "#2", "You pressed the Windows Key plus 1 and 2.")

Return

CtrlDrag:
Send {Ctrl Down}{LButton Down}
Return

CtrlDrag_Up:
Send {LButton Up}{Ctrl Up}
Return

; You can delete everything above the line and either put in
; you one code here, or #Include this in your script and use
; MakeChord from there.

;-------------------------------------------------------------

;Chord.ahk by Paul Pliska (ManaUser) - Version 2.0
MakeChord(Note1, Note2, Result, Delay = 0, Context = "")
{
   Local NewChord
   If Context
      HotKey IfWinActive, %Context%
   If Note1 In Ctrl,Alt,Shift
   {
      MakeChord("L" Note1, Note2, Result)
      MakeChord("R" Note1, Note2, Result)
      If Context
         HotKey IfWinActive
      Return
   }
   If Note2 In Ctrl,Alt,Shift
   {
      MakeChord(Note1, "L" Note2, Result)
      MakeChord(Note1, "R" Note2, Result)
      If Context
         HotKey IfWinActive
      Return
   }
   KeyWait % StripMods(Note1)
   KeyWait % StripMods(Note2)
   HotKey *$%Note1%, NoteDown
   HotKey *$%Note2%, NoteDown
   HotKey *$%Note1% Up, NoteUp
   HotKey *$%Note2% Up, NoteUp
   If Context
      HotKey IfWinActive
   NewChord := GetChordName(EscapeNote(Note1), EscapeNote(Note2))
   If NOT InStr(ChordList, NewChord)
   {
      If (ChordList != "")
         ChordList .= "|"
      ChordList .= NewChord
   }
   If NOT RegExMatch(Result, "^[a-zA-Z]\|")
   {
      If IsLabel(Result)
         Result := "L|" Result
      Else If (GetKeyState(Result) != "")
         Result := "K|" Result
      Else
         Result := "S|" Result
   }
   If (ConText = "")
      %NewChord% := Result
   else
   {
      Loop
      {
         Existing := %NewChord%_%A_Index%_Context
         If (Existing = "" OR Existing = Context)
         {
            %NewChord%_%A_Index%_Context := Context
            %NewChord%_%A_Index% := Result
            Break
         }
      }
   }
   If (Delay > 0)
      SetKeyDelay(Delay, Note1 " " Note2)
   If (DefaultKeyDelay = "")
      DefaultKeyDelay := 50
}

SetKeyDelay(Delay, Keys = "")
{
   Global
   If (Keys = "")
      DefaultKeyDelay := Delay
   Loop Parse, Keys, %A_Space%
   {
      Escaped := EscapeNote(A_LoopField)
      %Escaped%_Delay := Delay
   }
}

NoteDown:
   Critical
   ThisKey := GetThisKey()
   If ChordUsingNote(ThisKey)
      Return
   If (LastNote != "")
   {
      ChordName := GetChordName(LastNote, ThisKey)
      If (NoCreate(ChordName) OR NoCreate(ChordName "_1"))
      {
         Loop
         {
            ThisContext := %ChordName%_%A_Index%_Context
            If (ThisContext = "")
            {
               ThisContextNum = x
               MatchingChord := %ChordName%
               Break
            }
            If WinActive(ThisContext)
            {
               ThisContextNum := A_Index
               MatchingChord := %ChordName%_%A_Index%
               Break
            }
         }
         RegExMatch(MatchingChord, "^([a-zA-Z])\|(.*)$", ChordPart)
         If (ChordPart1 = "L")
            SetTimer %ChordPart2%, -1
         If (ChordPart1 = "S")
            Send %ChordPart2%
         If (ChordPart1 = "K")
            Send % "{blind}{" Ctrl2AltFix(ChordName, ChordPart2) " DownTemp}"
         %ChordName%_Down := ThisContextNum
         SetTimer PressIt, Off
         LastNote =

         Return
      }
      GoSub PressIt
   }
   LastNote := ThisKey
   If (NoCreate(ThisKey "_Delay"))
      SetTimer PressIt, % %ThisKey%_Delay
   else
      SetTimer PressIt, % DefaultKeyDelay
Return

NoteUp:
   Critical
   ThisKey := GetThisKey()
   ChordName := ChordUsingNote(ThisKey)
   If ChordName
   {
      ThisContext := %ChordName%_Down
      If (ThisContext)
      {
         If (ThisContext = "x")
            MatchingChord := %ChordName%
         Else
            MatchingChord := %ChordName%_%ThisContext%

         If (ChordPart1 = "L" AND IsLabel(ChordPart2 "_Up"))
            SetTimer %ChordPart2%_Up, -1
         If (ChordPart1 = "K")
            Send % "{blind}{" ChordPart2 " Up}"
         %ChordName%_Down =
      }
   }
   If (LastNote != "")
      GoSub PressIt
   If GetKeyState(UnescapeNote(ThisKey, 1))
      Send % "{blind}{" UnescapeNote(ThisKey, 1) " Up}"
Return

PressIt:
   Critical
   Send % "{blind}{" UnescapeNote(LastNote, 1) " Down}"
   SetTimer PressIt, Off
   LastNote =
Return

GetThisKey()
{
   Return EscapeNote(RegExReplace(A_ThisHotkey, "i)[~*$]*(\S+)( Up)?", "$1"))
}

GetChordName(Note1, Note2)
{
   If (Note2 < Note1)
      Return Note2 "_" Note1
   Else
      Return Note1 "_" Note2
}

StripMods(Note)
{
   Return RegExReplace(Note, "^[+^!#]*")
}

EscapeNote(Note)
{
   Scaw = +^!#scaw
   Symbols = !EX@AT#NM$DS`%PC^CT&ND*AS(OP)CP``BT~TL_US+PL-MN=EQ|VB\BK/FW?QM[OS]CS{OC}CC:CN;SC"DQ'SQ,CM.PD<LT>GT
   ModKeys := RegExReplace(Note, "(([#^!+])(?=.)|.+$)", "$2")
   MainKey := SubStr(Note, StrLen(ModKeys) + 1)
   Loop 4
      If InStr(ModKeys, SubStr(Scaw, A_Index, 1))
         Escaped .= SubStr(Scaw, A_Index + 4, 1)
   Match := InStr(Symbols, MainKey, 1)
   If Mod(Match, 3) = 1
      MainKey := SubStr(Symbols, Match + 1, 2)
   Escaped .= "$" MainKey
   Return Escaped
}

UnescapeNote(Note, StripMods = 0)
{
   Scaw = +^!#scaw
   Symbols = !EX@AT#NM$DS`%PC^CT&ND*AS(OP)CP``BT~TL_US+PL-MN=EQ|VB\BK/FW?QM[OS]CS{OC}CC:CN;SC"DQ'SQ,CM.PD<LT>GT
   StringSplit Keys, Note, $
   Match := InStr(Symbols, Keys2, 1)
   If Mod(Match, 3) = 2
      Keys2 := SubStr(Symbols, Match - 1, 1)
   If NOT StripMods
   {
      Loop 4
         If InStr(Keys1, SubStr(Scaw, A_Index + 4, 1))
            Unescaped .= SubStr(Scaw, A_Index, 1)
   }
   Unescaped .= Keys2
   Return Unescaped
}

ChordUsingNote(Note)
{
   Global
   Loop Parse, ChordList, |
   {
      If SubStr("_" A_LoopField "_", "_" Note "_") AND %A_LoopField%_Down
         Return A_LoopField
   }
   Return ""
}

Ctrl2AltFix(Chord, Key)
{
   Local Fix
   Fix =
   If SubStr(Key, "Alt")
   {
      If InStr(Chord, "LCtrl")
         Fix .= "LCtrl Up}{"
      If InStr(Chord, "RCtrl")
         Fix .= "RCtrl Up}{"
   }
   Fix .= Key
   Return Fix
}

NoCreate(TestVar)
{
   Global
   If %TestVar% =
      Return ""
   Else
      Return (%TestVar%)
}
Update:
1.1: You can now set the delay for each key individually. Also added an example of how to combine this with a modifier key.
1.11: Improved handling of modifier keys.
2.0: You can now the same chord in multiple contexts with different functions. Also a new method for setting key delay.

Tynan
  • Members
  • 28 posts
  • Last active: Nov 06 2009 03:32 AM
  • Joined: 18 May 2009
I like it. It works really well. For the two mouse buttons, because I was pressing them both with a single finger, I could get away with a time of 20.
I really like how it's actually pressing both buttons at once, not my method of holding one mouse button and pressing the other.

ManaUser
  • Members
  • 1121 posts
  • Last active: Dec 07 2016 04:24 PM
  • Joined: 24 May 2007
Thanks for the feedback.

purefusion
  • Guests
  • Last active:
  • Joined: --
Damn near perfect timing! I was just looking for this solution this morning. Great work!

JDN
  • Members
  • 313 posts
  • Last active: Sep 03 2016 06:51 AM
  • Joined: 24 Mar 2004
One thing about which to be careful.

Different keyboard manufacturers seem to allow certain chording combination of keys while they do not allow others.

I have not done a lot of investigation into this. But I was horrified to learn that certain combinations of 3 or 4 keys will not generate a recognizable signal.

Fortunately, this problem seems to usually appear when you are trying to press many keys at once. But it is worth taking note in any event.

Chording is a fantastic technique. And if it were reliable, I would expect that it would be in much greater use.

There has to be some reason why hardly no one uses this technique. I always figured it was because the hardware was not reliable above a certan number of keys. But I've never taken the time to do enough testing to figure it out.

But, bottom line, this is an extremely important technique ManaUser and I think you could use this to do many different applications. - lilke producing a generalized Program Launching app and distributing it to users - sort of a "custom - made" Launcer that the user could create and modify themselves.

ManaUser
  • Members
  • 1121 posts
  • Last active: Dec 07 2016 04:24 PM
  • Joined: 24 May 2007
That's true about keyboards not supporting chording well. But in general I think most keyboards can handle any two keys plus standard modifier keys, which is all this script deals with anyway. More than that and it's iffy.

One of the few times I've ever run into a problem is with the combination Ctrl, Space, Up, and Left. That comes up in games ever once in a while.

JDN
  • Members
  • 313 posts
  • Last active: Sep 03 2016 06:51 AM
  • Joined: 24 Mar 2004
Hey there Mana Man.

You are certainly correct about two keys. I have never had a problem trying to press two keys simultaneiously.

As a matter of fact, the only time I've had problems is with 4 or 5 keys and usually it's when they are from widely different rows and columns of the keyboard.

I just can't believe that people don't use chording more than they do. It has the potential to be one of the most powerful capabilities on the PC.

Actually, if you just consider the way courtroom reporters record text, it is very much like chording but on a highly specialized keyboard.

AHK makes it possible to use chording on a cheap $5 keyboard with no real problem and it lets you do some fantastic things - for one thing, if you can press 3 or 4 keys simultaneously, it's almost like getting a time warp where instead of using 4 strokes to type the word "fads", if you press all four keys at the same time, it's like working four times as fast.

If you think of this like a "time warp" where you compress the time taken to type four keystrokes into one stroke, there are some pretty amazing possibilities.

ManaUser
  • Members
  • 1121 posts
  • Last active: Dec 07 2016 04:24 PM
  • Joined: 24 May 2007
New version. I regret that this has gotten quite complicated, but I think it finally does everything I wanted.

It should work with any key (previously there was a problem with keys that are not valid names, like "/") and you can use the same chord to do different things depending on context.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
A similar idea was perused 3.5 years ago: http://www.autohotke...opic.php?t=6939. You could reuse some of the ideas there.

ManaUser
  • Members
  • 1121 posts
  • Last active: Dec 07 2016 04:24 PM
  • Joined: 24 May 2007
Wow, yours is certainly alot shorter.

But I think they're too different to really be combined effectively. The main thing that made mine so complicated is that I set it up so you could dynamically create chords... which admittedly may not really have been necessary. Still, interesting to read a different take on the idea. Thanks.

flowz
  • Guests
  • Last active:
  • Joined: --
Hi you seem to know alot about autohk well i have been try to make a simple script to put a scroll delay on my wheelup & wheeldown ( on my mouse )

but can quite seem to get it to work :S can you tell me where i am going wrong


here is my VERY simple script,

----------------------------------------------------------

; SetKeyDelay(137, = "WheelUp WheelDown")
; SetKeyDelay(137)

----------------------------------------------------------

As you can see i am trying to put a scroll delay of 137 milliseconds,
My reasons for this are really very simple, so i wont bother boring you with the reasons why i need this ;P

i am new to any kind of scripting and autoHK so please help if you can :)

Appreciative Guest...
  • Guests
  • Last active:
  • Joined: --
Well done. Script works great.
Took me a while, there's a bit of a learning curve, but once understood, the possibilities are limitless.

Thanks so much everyone.

Vitruvius
  • Members
  • 10 posts
  • Last active: Sep 29 2015 06:34 PM
  • Joined: 11 Nov 2010
First, I want to express my greatest appreciation for this script and this program. They truly work extremely well. One of the best and fastest I've seen around.

I only have one issue with this chording script.

The script needs to have the Caps Lock key not engaged in order to work. If Caps Lock is on, it will not trigger the chord.

Is there a way to make it ignore the Caps Lock status?

I tried, but my programming skills aren't half this sophisticated. I'm a newbie at AHK and can only manage the simple things.

Thank you all for your time and attention.

Vitruvius
  • Members
  • 10 posts
  • Last active: Sep 29 2015 06:34 PM
  • Joined: 11 Nov 2010
IMHO - I think the fact that you can dynamically create your own chords is its most attractive feature. It makes it simple and fast to do so.

Wow, yours is certainly alot shorter.

But I think they're too different to really be combined effectively. The main thing that made mine so complicated is that I set it up so you could dynamically create chords... which admittedly may not really have been necessary. Still, interesting to read a different take on the idea. Thanks.



Vitruvius
  • Members
  • 10 posts
  • Last active: Sep 29 2015 06:34 PM
  • Joined: 11 Nov 2010
Found a way to make it ignore the CapsLock status today.

Add {blind} before the key that the chord is to trigger.

MakeChord("LButton", "RButton", "{blind}{del}", 50)

hope it helps someone else.

Saludos

:)