Polish timer-less writeup

This commit is contained in:
urob 2023-02-12 22:07:39 -05:00
parent 016b1f84db
commit 637def5655

254
readme.md
View file

@ -4,85 +4,100 @@ This is my personal [ZMK firmware](https://github.com/zmkfirmware/zmk/) configur
It consists of a 34-keys base layout that is re-used for various boards, including my It consists of a 34-keys base layout that is re-used for various boards, including my
Corneish Zen and an Advantage 360 pro. Corneish Zen and an Advantage 360 pro.
## Key features ## *Key* features
- clean keymap + unicode setup using helper macros from - clean keymap + unicode setup using helper macros from
[zmk-nodefree-config](https://github.com/urob/zmk-nodefree-config) [zmk-nodefree-config](https://github.com/urob/zmk-nodefree-config)
- modified Github Actions workflow that recognizes git-submodules - the base keymap and combo setup are independent of the physical location of
- the base keymap and combo setup are independent of the physical location of keys and keys and are re-used for multiple keyboards. The configuration is fit onto
are re-used for multiple keyboards. The configuration is fit onto larger boards by larger boards by padding it via a modular structure of "extra keys"
padding it via a modular structure of "extra keys" - ["timer-less" homerow mods](#timeless-homerow-mods)
- ["timeless" homerow mods](#timeless-homerow-mods) on the base layer; sticky mods on
other layers
- num-word: a zmk version of smart-layers that automatically de-activate for non-numbers
- combos replacing the symbol layer - combos replacing the symbol layer
- arrow-cluster doubles as home/end/etc on long-press, - a smart-layer implementation for ZMK that automatically toggles the numbers
bspc/del delete words on long-press layer
- sticky shift on right thumb, double-tap (or shift + tap)[^1] activates caps-word - long-pressing the arrow-cluster yields home, end, begin/end of document, and
- <kbd>shift</kbd> + <kbd>,</kbd> morphs into <kbd>;</kbd> and <kbd>shift</kbd> + fwd/bwd-delete words
<kbd>.</kbd> morphs into <kbd>;</kbd> (freeing up the right pinky for - sticky shift on right thumb, double-tap (or shift + tap)[^1] activates
<kbd>repeat</kbd>) caps-word
- <kbd>shift</kbd> + <kbd>ctrl</kbd> + - <kbd>shift</kbd> + <kbd>,</kbd> morphs into <kbd>;</kbd> and
<kbd>,</kbd> morphs into <kbd><</kbd> and <kbd>shift</kbd> + <kbd>ctrl</kbd> + <kbd>shift</kbd> + <kbd>.</kbd> morphs into <kbd>;</kbd> (freeing up the
<kbd>.</kbd> morphs into <kbd>></kbd> right pinky for <kbd>? / !</kbd>)
- <kbd>shift</kbd> + <kbd>space</kbd> morphs into <kbd>dot</kbd><kbd>space</kbd> - <kbd>shift</kbd> + <kbd>ctrl</kbd> + <kbd>,</kbd> morphs into <kbd><</kbd>
<kbd>sticky-shift</kbd> and <kbd>shift</kbd> + <kbd>ctrl</kbd> + <kbd>.</kbd> morphs into
<kbd>></kbd>
- <kbd>shift</kbd> + <kbd>space</kbd> morphs into <kbd>dot</kbd>
<kbd>space</kbd><kbd>sticky-shift</kbd>
- "Greek" layer for mathematical typesetting (activated via sticky-layer combo) - "Greek" layer for mathematical typesetting (activated via sticky-layer combo)
- modified Github Actions workflow that recognizes git-submodules
- automated
[build-scripts](https://github.com/urob/zmk-config/tree/main/scripts#readme)
for local and Docker-based building (independently of VS Code)
![](img/keymap.png) ![](img/keymap.png)
## Timeless homerow mods ## Timeless homerow mods
Homerow mods [are great](https://precondition.github.io/home-row-mods). But they can [Homerow mods](https://precondition.github.io/home-row-mods) (aka "HRMs") can
require some finicky timing: In its most naive implementation, in order to produce a "mod", be a game changer -- at least in theory. In practice, they require some finicky
they must be held *longer* than `tapping-term-ms`. In order to produce timing: In its most naive implementation, in order to produce a "mod", they
a "tap", they must be held *less* than `tapping-term-ms`. This requires very consistent must be held *longer* than `tapping-term-ms`. In order to produce a "tap", they
typing speeds that, alas, I do not possess. Hence my quest for a "timeless" HRM must be held *less* than `tapping-term-ms`. This requires very consistent
setup.[^2] typing speeds that, alas, I do not possess. Hence my quest for a "timer-less"
HRM setup.[^2]
Here's what I have ended up with: A "timeless" HRM setup with virtually no misfires and After months of tweaking, I eventually ended up with a HRM setup that is
yet a fluent typing experience with mostly no delays. essentially timer-less, resulting in virtually no misfires. Yet it provides a
fluent typing experience with mostly no delays.
Let's suppose for a moment we set `tapping-term-ms` to something ridiculously large, say Let's suppose for a moment we set `tapping-term-ms` to something ridiculously
5 seconds. This makes the configuration "timeless". But it also creates two large, say 5 seconds. This makes the configuration timer-less of sorts. But it
problems: (1) In order to get a "mod" we now have to hold the HRM keys for has two problems: (1) To activate a mod we will have to hold the HRM keys for
what feels like eternity. (2) In normal typing, when tapping keys, there can be what feels like eternity. (2) During regular typing, there are delays between
long delays between the press of a key and the time it appears on the screen. Enter my the press of a key and the time it appears on the screen.[^3] Enter two of
two favorite configuration options: ZMK's best configuration options:
* To address the first problem, I use ZMK's `balanced` flavor, which produces * To address the first problem, I use ZMK's `balanced` flavor, which produces a
a "hold" if another key is both pressed and released within the tapping-term. Because "hold" if another key is both pressed and released within the tapping-term.
that is exactly what I normally do with HRMs, there is virtually never a need to wait Because that is exactly what I normally do with HRMs, there is virtually
past my long tapping term (see below for two exceptions). never a need to wait past my long tapping term (see below for two
exceptions).
* To address the typing delay, I use ZMK's `global-quick-tap` property, which * To address the typing delay, I use ZMK's `global-quick-tap` property, which
immediately resolves a HRM as "tap" when it is pressed shortly *after* another key immediately resolves a HRM as "tap" when it is pressed shortly *after*
has been tapped.[^3] This all but completely eliminates the delay when typing. another key has been tapped. This all but completely eliminates the delay.
This is almost perfect, but there's still a few rough edges: This is great but there are still a few rough edges:
* When rolling keys, I sometimes unintentionally end up with "nested" key * When rolling keys, I sometimes unintentionally end up with "nested" key
sequences: `key 1` down, `key 2` down and up, `key 1` up. Given the `balanced` flavor, sequences: `key 1` down, `key 2` down and up, `key 1` up. Because of the
this would falsely register `key 1` as a mod. To prevent this, I use ZMK's "positional `balanced` flavor, this would falsely register `key 1` as a mod. As a remedy,
hold-tap" feature to force HRMs to always resolve as "tap" when the *next* key is on I use ZMK's "positional hold-tap" feature to force HRMs to always resolve as
the same side of the keyboard. Problem solved. "tap" when the *next* key is on the same side of the keyboard. Problem
* ... or at least almost. The official ZMK version for positional-hold-taps performs the solved.
positional check when the next key is *pressed*. This is not ideal, because it * ... or at least almost. The official ZMK version for positional-hold-taps
prevents combining multiple modifiers on the same hand. To fix this, I use a small performs the positional check when the next key is *pressed*. This is not
patch that delays the positional-hold-tap decision until the next key's *release* ([PR ideal, because it prevents combining multiple modifiers on the same hand. To
#1423](https://github.com/zmkfirmware/zmk/pull/1423)). With the patch, multiple mods fix this, I use a small patch that delays the positional-hold-tap decision
can be combined when held, while I still get the benefit from positional-hold-taps until the next key's *release* ([PR
when keys are tapped. #1423](https://github.com/zmkfirmware/zmk/pull/1423)). With the patch,
* So far, nothing of the configuration depends on the duration of `tapping-term-ms`. In multiple mods can be combined when held, while I still get the benefit from
practice, there are two reasons why I don't set it to infinity: positional-hold-taps when keys are tapped.
1. Sometimes, in rare circumstances, I want to combine a mod with a alpha-key *on * So far, nothing of the configuration depends on the duration of
the same hand* (e.g., when using the mouse with the other hand). My positional `tapping-term-ms`. In practice, there are two reasons why I don't set it to
hold-tap configuration prevents this *within* the tapping term. By setting the infinity:
tapping term to something large but not crazy large (I use 280ms), I can still 1. Sometimes, in rare circumstances, I want to combine a mod with a
use same-hand `mod` + `alpha` shortcuts by holding the mod for just a little while alpha-key *on the same hand* (e.g., when using the mouse with the other
before tapping the alpha-key. hand). My positional hold-tap configuration prevents this *within* the
2. Sometimes, I want to press a modifier without another key (e.g., on Windows, tapping term. By setting the tapping term to something large but not crazy
tapping `Win` opens the search menu). Because the `balanced` flavour only large (I use 280ms), I can still use same-hand `mod` + `alpha` shortcuts by
kicks in when another key is pressed, this also requires waiting past holding the mod for just a little while before tapping the alpha-key.
`tapping-term-ms`. 2. Sometimes, I want to press a modifier without another key (e.g., on
Windows, tapping `Win` opens the search menu). Because the `balanced`
flavour only kicks in when another key is pressed, this also requires
waiting past `tapping-term-ms`.
* Finally, it is worth noting that this setup works best in combination with a
dedicated shift for capitalization during normal typing (I am a big fan of
sticky-shift on a home-thumb). This is because shifting alphas is the
one scenario where pressing a mod may conflict with `global-quick-tap`, which
may result in false negatives when typing fast.
Here's my configuration (I use a bunch of [helper Here's my configuration (I use a bunch of [helper
macros](https://github.com/urob/zmk-nodefree-config) to simplify the syntax, but they macros](https://github.com/urob/zmk-nodefree-config) to simplify the syntax, but they
@ -117,79 +132,85 @@ ZMK_BEHAVIOR(hmr, hold_tap,
hold-trigger-on-release; // requires PR #1423 hold-trigger-on-release; // requires PR #1423
) )
``` ```
One last note, the configuration above uses some syntactic sugar introduced in [PR
Final note: the config above uses syntax introduced in [PR
#1387](https://github.com/zmkfirmware/zmk/pull/1387), which decouples the #1387](https://github.com/zmkfirmware/zmk/pull/1387), which decouples the
`quick-tap-ms` timeout from the `global-quick-tap-ms` timeout. Without the PR, one `quick-tap-ms` timeout from the `global-quick-tap-ms` timeout. Without the PR,
can replace `global-quick-tap-ms = <150>` with `global-quick-tap` for a one can replace `global-quick-tap-ms = <150>` with `global-quick-tap` for a
similar effect (`global-quick-tap` will use the regular `quick-tap-ms` timeout in this similar effect (`global-quick-tap` will use the regular `quick-tap-ms` timeout
case). in this case).
My personal [ZMK fork](https://github.com/urob/zmk) includes both the My personal [ZMK fork](https://github.com/urob/zmk) includes both the
global-quick-tap-ms PR and the hold-trigger-on-release PR (along with a few other PRs). global-quick-tap-ms PR and the hold-trigger-on-release PR (along with a few
If you are looking for a ZMK-centric introduction to maintaining your own fork with a other PRs). If you prefer to maintain your own fork with a custom selection of
custom selection of PRs, you might find my ["cookbook PRs, you might find this [ZMK-centric introduction to
approach"](https://gist.github.com/urob/68a1e206b2356a01b876ed02d3f542c7) helpful. Git](https://gist.github.com/urob/68a1e206b2356a01b876ed02d3f542c7) helpful.
## Combo setup ## Combo setup
I make heavy use of combos to replace the usual symbol layer. The combo layout aims to My layout makes heavy use of combos. Thanks to `global-quick-tap` for combos
put the most used symbols in easy-to-access locations and also make them easy to (introduced in above mentioned PR #1387), combo misfires are rare, even when
remember. Specifically: rolling keys. Most of my combos are bind to symbols, replacing the usual
symbols layer seen on many sub-40 keyboard layouts. The combos are designed so
as to put the most used symbols in easy-to-access locations while also making
them easy to remember. Specifically:
- the top vertical-combo row matches the symbols on a standard numbers row (except `+` - the top vertical-combo row matches the symbols on a standard numbers row
and `&` being swapped) (except `+` and `&` being swapped)
- the bottom vertical-combo row aims for symmetry with the top row (subscript `_` aligns - the bottom vertical-combo row is symmetric to the top row (subscript `_`
with superscript `^`; minus `-` aligns with `+`; division `/` aligns with aligns with superscript `^`; minus `-` aligns with `+`; division `/` aligns
multiplication `*`; logical-or `|` aligns with logical-and `&`) with multiplication `*`; logical-or `|` aligns with logical-and `&`)
- parenthesis, braces, brackets, `!` and `?` are set up symmetrically in prime locations - parenthesis, braces, brackets are set up symmetrically as horizontal combos
- numlock (on `W + P`), cut (on `X + D`), copy, and paste are on the left side for - cut (on `X + D`), copy, and paste are on the left side for one-handed mouse
one-handed mouse use use
- `L + Y` activates Greek layer for next key, `L + U + Y` activates shifted Greek layer - `L + Y` activates Greek layer for the next key press, `L + U + Y` activates the shifted
for next key Greek layer the next key
- `tap`, `esc`, `enter` are on horizontal combos
## Experimental changes ## Experimental changes
- I recently reduced my core layout to 34 keys. Backspace, Delete and Tap are now all on - I recently reduced my core layout to 34 keys. Backspace and Delete are now on
my Navigation-layer. To make room for these keys, I have added hold-taps to the arrow my Navigation-layer. To make room for these keys, I have added hold-taps to
cluster, which now double as Home/End and Beginning/End of document. I really like the the arrow cluster, which now double as Home/End and Beginning/End of
new navigation cluster and will likely keep it in one way or another document. I really like the new navigation cluster and will likely keep it in
one way or another
- Inspired by Jonas Hietala's - Inspired by Jonas Hietala's
[Numword](https://www.jonashietala.se/blog/2021/06/03/the-t-34-keyboard-layout/#where-are-the-digits) [Numword](https://www.jonashietala.se/blog/2021/06/03/the-t-34-keyboard-layout/#where-are-the-digits)
for QMK, I implemented my own version of [Smart-layers for for QMK, I implemented my own version of [Smart-layers for
ZMK](https://github.com/zmkfirmware/zmk/pull/1451). It is triggered via a single tap ZMK](https://github.com/zmkfirmware/zmk/pull/1451). It is triggered via a
on my Num-key (holding the key will activate the num layer as usual without single tap on my Num-key (holding the key will activate the num layer as
triggering Numword). Similar to Capsword, Numword continues to be activated as long usual without triggering Numword). Similar to Capsword, Numword continues
as I type numbers, and deactivates automatically on any other keypress. I found that to be activated as long as I type numbers, and deactivates automatically on
I use Numword for most of my numbers typing. For single digits, it effectively is a any other keypress. I found that I use Numword for most of my numbers
sticky-layer, but importantly I can also use it for multiple digits. The only case typing. For single digits, it effectively is a sticky-layer, but
where it doesn't deactivate automatically is where immediately after a digit I would importantly I can also use it for multiple digits. The only case where it
type any of the letters on which my numpad is located (WFPRSTXCD), which is rare, doesn't deactivate automatically is where immediately after a digit I would
but does happen. For these cases I have a CANCEL key on my Nav layer that cancels type any of the letters on which my numpad is located (WFPRSTXCD), which is
both Numword and Capsword. rare, but does happen. For these cases I have a CANCEL key on my Nav layer
- Since the switch to 34 keys, I freed up the tap-position on my left-most thumb key. that cancels both Numword and Capsword.
For now I added a secondary Bspc, but I am still searching for a better use. (I tried - Since the switch to 34 keys, I freed up the tap-position on my left-most
adding Repeat here but I found that it doesn't work well adjacent to space) thumb key. For now I added a secondary Bspc, but I am still searching for a
better use. (I tried adding Repeat here but I found that it doesn't work well
adjacent to space, which requires to much lateral thumb-movements)
## Issues and workarounds ## Issues and workarounds
Since I switched from QMK to ZMK I have been very impressed with how easy it is to set Since I switched from QMK to ZMK I have been very impressed with how easy it is
up relatively complex layouts in ZMK. For the most parts I don't miss any functionality to set up relatively complex layouts in ZMK. For the most parts I don't miss
(to the contrary, I found that ZMK supports many features natively that would complex any functionality (to the contrary, I found that ZMK supports many features
user-space implementations in QMK). Below are a few remaining issues: natively that would require complex user-space implementations in QMK). Below
are a few remaining issues:
- ZMK does not yet support tap-only combos - ZMK does not yet support tap-only combos
([#544](https://github.com/zmkfirmware/zmk/issues/544)). Workaround: pause ([#544](https://github.com/zmkfirmware/zmk/issues/544)). Workaround: pause
briefly when chording multiple HRMs together on positions that otherwise would trigger briefly when chording multiple HRMs together on positions that otherwise would trigger
a combo. a combo.
- OS sleep is not yet implemented ([#1077](https://github.com/zmkfirmware/zmk/issues/1077)).
Workaround: use sleep-macro instead.
- `&bootloader` doesn't work with Planck_rev6 - `&bootloader` doesn't work with Planck_rev6
([#1086](https://github.com/zmkfirmware/zmk/issues/1086)). Workaround: Manually press ([#1086](https://github.com/zmkfirmware/zmk/issues/1086)). Workaround: Manually press
reset-button. reset-button.
- "sticky-hold" swallows OS shift when typing quickly. Workaround: use sticky-tap for now.
- Sleep is not yet implemented ([#1077](https://github.com/zmkfirmware/zmk/issues/1077)).
Workaround: use sleep-macro instead.
- Invalid DFU suffix signature warning when flashing with dfu-util. No problem for now
but may cause issues with future versions of dfu-util.
[^1]: Really what's happening is that `Shift` + my right home-thumb morph into [^1]: Really what's happening is that `Shift` + my right home-thumb morph into
caps-word. This gives me two separate ways of activating it: (1) Holding the caps-word. This gives me two separate ways of activating it: (1) Holding the
@ -198,7 +219,7 @@ user-space implementations in QMK). Below are a few remaining issues:
because the first tap yields sticky-shift, activating the mod-morph upon the second because the first tap yields sticky-shift, activating the mod-morph upon the second
tap. tap.
[^2]: I call it "timeless", because the large tapping-term makes the behavior [^2]: I call it "timer-less", because the large tapping-term makes the behavior
insensitive to the precise timings. One may say that there is still the insensitive to the precise timings. One may say that there is still the
`global-quick-tap` timeout. However, with both a large tapping-term and `global-quick-tap` timeout. However, with both a large tapping-term and
positional-hold-taps, the behavior is *not* actually sensitive to the positional-hold-taps, the behavior is *not* actually sensitive to the
@ -206,8 +227,7 @@ user-space implementations in QMK). Below are a few remaining issues:
in typing speed won't affect *what* is being typed but merly *how fast* it appears on in typing speed won't affect *what* is being typed but merly *how fast* it appears on
the screen. the screen.
[^3]: One potential downside of `global-quick-tap` is that it prevents using modifiers [^3]: The delay is determined by how quickly a key is released and is not
*immediately* after another key press. Arguably, this is only problematic for shift, directly related to the tapping-term. But regardless of its length, most
which is not a problem for me, because I have a dedicated "sticky shift" on my right people still find it noticable and disruptive.
thumb. If you rely on homerow mods for regular capitalization, you may want to reduce
the `global-quick-tap` term for just the two shift-mods to about 75-100ms.