Type with the numpad! by jonny
Chording keyboard: strings sent at key combinations by Laszlo
chording/chorded input: input using simultaneous press of 2 or more keys.
Although i had already used the chording method for my Configurable One-Hand Typing script, i didn't realize the advantage that chording has over hotstrings, until it struck me during reading the above discussion threads. The main advantage is this: chording frees up the key-sequence to be typed normally, which is not possible with hotstrings which auto-replace whether or not the keys are held pressed. Chording is actually a sort of combination between hotkeys and hotstrings, resembling hotstrings by auto-replace (although it could be extended beyond this to perform commands other than Send, as well as use non-alphanumeric keys) and resembling hotkeys by the method of simultaneous-press of keys. The difference is almost cosmetic in that the prefix of hotkeys is usually a traditional modifier-key (Shift/Alt/Ctrl/Win), while in chording there isn't this restriction.
AutoHotkey already allows chording of any two keys, but the ability to maintain the native function becomes tricky and is not immediate to achieve (challenging actually, with the various parameter considerations). And a generalized solution for this has remained elusive, until now.
Usage
^#k toggle chording on/off (starts out Off)
^#k if chording is on, holding the hotkey ~1 sec shows the Chord List
It is easy to configure, with 3 settings:
ch_th = chord threshold (millisec), time to allow chords as normal input; hold the keys pressed this long to transform the chord.
ch_ini = filePathName of the .ini file.
ch_delimiter = the separator used between the chord and its expansion in the settings file. Do not use it within the expanded string.
These may be changed, directly below the hotkey label.
The .ini file is recognized as Chord settings.ini (which you may change) and should be done in the following fashion, by default:
[preferences] total = 20 1 = yt.Yours truly, 2 = oh.on the other hand 3 = iet.in the event that 4 = wo.without 5 = tpi.the patient is 6 = tpw.the patient was 7 = et.exceptional typing speed 8 = YT.yuletide 9 = OH.ornaments and heart-warming gatherings 10 = IET.It's an excellent time of year 11 = 135.153 12 = 123.1234567890 13 = a1.abcdefghijklmnopqrstuvwxyz0123456789 14 = ahk.AutoHotkey is an excellent program 15 = mcl.my chord list 16 = xc.extend chords to actions other than Send 17 = ct.Christmas time 18 = ny.New Year's 19 = mc.Merry Christmas 20 = ty.thank YouThis is just for example. The total should be accurately hard-coded, and there is no limit to the number of entries (i've not yet tested for the practical-speed limit-range). A period is used as the delimiter (this can be changed using the ch_delimiter setting, below the hotkey label). Place the .ini file in the same folder as the script.
;------------------------------------------------ ; Chording Keyboard Input 1.1.4 ; Copyright (c) 2005 by Decarlo L. SetKeyDelay 0 return ; this is necessary or the Keywait function does not execute properly (perhaps a function-loading bug. Win2000) *#!x:: ;Thread, Priority, 2147483647 msgbox,,, exiting Ahk, .5 EXITAPP ^#k:: ; change to preference ch_th = 100 ; threshold/window of time (millisec) to allow chords as normal input ; keep the keys pressed for this long to transform the chord ch_ini = %A_ScriptDir%\Chord settings.ini ; filePathName of .ini file ch_delimiter = . ; separates chord abbreviation from expansion in settings file. do not use it within the expanded string. change to preference ch_k := StringR(A_ThisHotkey, 1) if chording_On { Loop 14 ; if hotkey is held for ~1 sec, show the chord list { sleep 30 if !GetKeyState(ch_k, "p") BREAK if A_Index = 14 { Gui, 30: Destroy Gui, 30: +AlwaysOnTop +VScroll Gui, 30: Font, 0x183CE7 ; blue Gui, 30: Color, 0xD6D3D6 ; light grey Gui, 30: Add, Text, ReadOnly, %ch_index% Gui, 30: Show, AutoSize, Chord List Keywait % ch_k RETURN } } Process, Priority,, Normal ; optional, normal priority when chording is off Keywait, %ch_k% SetTimer, ChordInfo, off SplashText("chording off") Tooltip,,,,17 } else { Process, Priority,, High ; increase responsiveness during higher cpu load ch_str = ch_index = StringCaseSense on IniRead ch_#chords, %ch_ini%, preferences, total Loop % ch_#chords IniRead ch_%A_Index%, %ch_ini%, preferences, %A_Index% Loop % ch_#chords { A_Index1 := A_Index Loop Parse, ch_%A_Index%, %ch_delimiter% { if A_Index = 1 ch_%A_Index1% = %A_LoopField% else { ch_%A_Index1%_ = %A_LoopField% ch_index := ch_index . ch_%A_Index1% . "`t" . ch_%A_Index1%_ . "`r`n" } } } SplashText("chording ON", 1000) SetTimer, Chord, 1 ;SetTimer, ChordInfo, 200 } chording_On := !chording_On return SplashText(title="", timeout="", width="", height="", text="") ; default timeout 700 { SplashTextOn, %Width%, %Height%, %Title%, %Text% if timeout = sleep 700 else if timeout = 0 ; leave on until replaced or turned off later RETURN else sleep %timeout% SplashTextOff return } StringR(in, charsToKeep, charsToTrim="", trimTrailingWhitespace="") { StringRight, out, in, %charsToKeep% if charsToTrim StringTrimRight, out, out, %charsToTrim% return out } In( a, b, eachChar =0 ) ; If a in b list. For b, use varName or "var1 [, var2, ...]" { if eachChar { Loop, Parse, b if a = %A_LoopField% Return 1 } else Loop, Parse, b, `,, %A_Space%%A_Tab% if a = %A_LoopField% Return 1 } GetKS(in, eachChar =0) { global ch_str if eachChar { Loop, Parse, in if not GetKeyState(A_LoopField, "p") RETURN return 1 } else { Loop, Parse, in, `,, %A_Space%%A_Tab% if not GetKeyState(A_LoopField, "p") RETURN return 1 } } KeyWait(str, hotkey =0) ; default is string, not hotkey label ; this accepts strings and hotkey labels. do not use commas; spaces/tabs optional { if hotkey ; parse hotkey label { StringReplace, str, str, ^, Ctrl`, StringReplace, str, str, #, LWin`,RWin`, StringReplace, str, str, !, Alt`, StringReplace, str, str, +, Shift`, StringReplace, str, str, %A_Space%&%A_Space%, `, if StringR(str, 2) = ",," { StringTrimRight, str, str, 1 str := str . "`," } Loop, Parse, str, `,, %A_Space%%A_Tab% Keywait % A_LoopField } else Loop, Parse, str,, %A_Space%%A_Tab% Keywait % A_LoopField } Chord: SetKeyDelay 0 StringCaseSense on keys = abcdefghijklmnopqrstuvwxyz0123456789`,./;'[]\-=`` keys1 = %keys% keys2 = ABCDEFGHIJKLMNOPQRSTUVWXYZ)!@#$`%^&*(<>?:"{}|_+~ Loop { sleep 10 if !chording_On { SetTimer, Chord, off Keywait %ch_k% EXIT } if GetKeyState("Space", "p") OR GetKeyState("Enter", "p") OR GetKeyState("bs", "p") OR GetKeyState("Esc", "p") { ch_str = ch_Len = 0 keys = %keys1% } else if ch_Len > 5 CONTINUE if GetKeyState("Shift", "p") keys = %keys2% Loop, Parse, keys { if GetKeyState(A_LoopField, "p") AND !In(A_LoopField, ch_str, 1) { ch_str = %ch_str%%A_LoopField% ch_Len ++ ; for backspacing & performance reasons ch_Time = %A_TickCount% ; for threshold calculation sleep 10 } } if (ch_Len > 1) & (A_TickCount - ch_Time > ch_th) AND GetKS(ch_str, 1) Loop %ch_#chords% { ifEqual, ch_%A_Index%, % ch_str { if !ch_%A_Index%_ GoSub ch_%ch_str% else { BlockInput on ch_ := ch_%A_Index%_ send {bs %ch_Len%}%ch_% Keywait(ch_str) Loop Parse, ch_str ; handle BlockInput side-effect if GetKeyState(A_LoopField) ; retrieve logical state send {%A_LoopField% up} BlockInput off } BREAK } } } return ChordInfo: Tooltip in: %ch_str%,, ,17 return 30GuiClose: 30GuiEscape: Gui, 30:Destroy return ;-------------------------------------------------
I'll add Shifted counterparts later, probably after Christmas [EDIT].
There are two known issues. One seems to be when the first two letters are pressed within 20 millisec of each other, which may result in the script seeing the first two keys in switched position and not acknowledging the chord; however, this is relatively rare. This can be worked around by removing the sequentialness requirement, but that would introduce a few other, more unwanted side-effects. i may resolve this in the future but it is currently not a priority. The other issue is when a word/string begins with two consecutive identical strings, like pompom, or tyty. If you have ty chorded to Thanksgiving, year-round, and you type tyty, it may sometimes auto-replace on the second instance even without simultaneous pressing.