I made an attempt to configure a chorded Escape. (Chord meaning several keys pressed together. In UHK smart macros, this is expressed with ifShortcut
and ifNotShortcut
commands.)
In my case, pressing q
+w
together results in Esc
.
I created this macro, chordscape:
ifKeyActive $keyId.q {
ifShortcut timeoutIn 100 $keyId.w final holdKey escape
else final holdKey q
}
ifKeyActive $keyId.w {
ifShortcut timeoutIn 100 $keyId.q final holdKey escape
else final holdKey w
}
Note the use of holdKey
instead of tapKey
, so autorepeat will still work for q
, w
and also, for the chorded Escape.
I then assigned it to the q
and w
keys:
You could have separate macros for the q
and for the w
key, but I would like to maintain just a single place for this functionality. And I donât want to blow up the amount of macros needed for one function.
My wishes for the UHK firmware:
I really think chords should be easier to configure. It is weird that you have to map them to each of the possible starting keys, and that I had to make two separate parts in the macro, one for each starting key. Chords should be specified independent of the order of press, and especially independent of the starting key.
The Kanata software solves this by having a separate section for chords. (It is labelled â-experimentalâ because it is fairly new, but already works well.) This section will be processed before any layer processing:
(defcfg chords-v2-min-idle-experimental 50)
(defchordsv2-experimental
(q w) esc 100 all-released (maxtend)
)
(q w)
is the list of input keys that make up the chord.
esc
is the action taken when the chord triggers.
100
is the maximum wait time in ms for the input keys to be pressed together.
This makes it much easier to define the chords you want, and what actions they should have. It does not matter in which order you press them: as long as the keys are pressed down within the interval defined (100 ms in the above example), and only after a minimum idle time has elapsed with no keys depressed (to prevent accidential activation of chords during rapid typing, 50 ms in the above example), then the corresponding action is activated.
Finally, you can also define exclusion layers (maxtend in the above example) where the chord will never trigger.
Here is an idea how this could work for the UHK:
First, imagine in Agent you can click a checkbox on each key on the base layer
This key can trigger a chord.
I would just click this checkbox for the q
and w
keys. The rest of primary / secondary configuration would stay the same. Agent would still show the keys as q
and w
, maybe with a small symbol indicating that these keys can also trigger chords.
In a primitive implementation, whenever a key is pressed that has the âchord triggerâ checked, the firmware would execute a specific macro ($onChordTrigger
) that can do all sorts of ifShortcut
checks. That processing would be made simpler with two new commands checkChords
and ifChord
, iterating over potential chords, and executing matching actions. Finally, a third new command, continueProcessingLayers
would continue with regular layer mapping and actions if none of the chords mapped (or that is done automatically on return of the $onChordTrigger
macro).
$onChordTrigger:
checkChords timeoutIn 100 {
ifChord $keyId.q $keyId.w final holdKey escape
ifChord $keyId.graveAccentAndTilde $keyId.1 final exec tripleGraveComment
}
continueProcessingLayers
checkChords
works as a timed loop around the ifChord statements. ifChord
checks for activation of all the keys and executes the corresponding statement if all the keys have been activated. Finally, continueProcessingLayers
runs normal actions for the keys depressed during the checkChords loop.
Note: I donât fully understand the postponer in the current firmware, so maybe this is already scriptable in macros today. But I didnât see it. (@kareltucek ?)
Simpler and easier chord configuration:
In a better implementation, you would not have to write that $onChordTrigger
macro. In Agent, there would be a section âChordsâ (for each keymap?) where you can list all the keys that together make a chord. (Only keys that had the trigger box checked can be used here.) You would see a list of chords, and [Add], [Configure] and [Remove] buttons.
chord | action | |
---|---|---|
q w | chordscape | [Configure] [Remove] |
[Add another chord] |
Each chord definition lists all the keys for that chord, and a macro to be executed when the chord triggers. Or, instead of always referring to a macro, it would offer the same actions as for any key:
Global parameters for chords that would be configured are:
- minimum chord idle time before chords can be active, suggested default: 50 ms
- maximum chord wait time until normal key processing continues, suggested default: 100 ms
Keys that are marked as chord trigger keys will see a delay of up to maximum chord wait time before they trigger a press (i.e. like the timeoutIn
parameter of ifShortcut
). If such a key gets pressed and released again before that maximum chord wait time, then itâs tap will activate immediately. As a result, you will not experience any visible delay during normal typing (short taps).
Now you donât need the âthis key can trigger a chordâ checkbox anymore. It can be calculated from the list of keys that were added to the chord list. In this variant, you would just add your chords to the chord list, and Agent would then show the chord trigger symbol on the relevant keys of the base layer. (I guess only the base layerâŚ?)
The firmware would be extended to automatically run those chord checks before regular key handling.
A further enhancement (optional) would allow configuration of the layers on which a chord is active:
chord | action | layers | ⌠|
---|---|---|---|
q w | chordscape | base, fn, mod | [Configure] [Remove] |
[Add another chord] |
In this variant, chords would only be recognised if one of the defined layers is active. This would even allow different actions for the same chord on different layers. Probably overkill, but comes out of this implementation almost automatically.