background/related:
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:
Code:
[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 You
This 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.
Code:
;------------------------------------------------
; 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: now has that capability].
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.