feat(blog): Add nodefree-config post for spotlight series
Co-authored-by: Robert U <978080+urob@users.noreply.github.com>
This commit is contained in:
parent
1b8b6b4a0e
commit
78fa1e77c4
1 changed files with 189 additions and 0 deletions
189
docs/blog/2023-12-17-nodefree-config.md
Normal file
189
docs/blog/2023-12-17-nodefree-config.md
Normal file
|
@ -0,0 +1,189 @@
|
|||
---
|
||||
title: "Community Spotlight Series #2: Node-free Config"
|
||||
author: Cem Aksoylar
|
||||
author_title: Documentation maintainer
|
||||
author_url: https://github.com/caksoylar
|
||||
author_image_url: https://avatars.githubusercontent.com/u/7876996
|
||||
tags: [keyboards, firmware, community]
|
||||
---
|
||||
|
||||
This blog continues our series of posts where we highlight projects within the ZMK ecosystem
|
||||
that we think are interesting and that the users might benefit from knowing about them. You might
|
||||
be aware that ZMK configurations in the [Devicetree format](/docs/config#devicetree-files)
|
||||
use the [C preprocessor](https://en.wikipedia.org/wiki/C_preprocessor) so that directives like
|
||||
`#define RAISE 2` or `#include <behaviors.dtsi>` can be used in them. In this installment we are
|
||||
highlighting the [`zmk-nodefree-config` project](https://github.com/urob/zmk-nodefree-config)
|
||||
by [urob](https://github.com/urob) that contains helper methods that utilizes this fact
|
||||
for users who prefer editing and maintaining their ZMK config directly using the Devicetree
|
||||
syntax format.
|
||||
|
||||
In the rest of the post we leave it to urob to introduce and explain the motivations of the
|
||||
project, and various ways it can be used to help maintain ZMK keymaps. Stay tuned for future
|
||||
installments in the series!
|
||||
|
||||
## Overview
|
||||
|
||||
Loosely speaking the _nodefree_ repo -- more on the name later -- is a
|
||||
collection of helper functions that simplify configuring keymap files. Unlike
|
||||
the graphical keymap editor covered in the [previous spotlight
|
||||
post](https://zmk.dev/blog/2023/11/09/keymap-editor), it is aimed at users who
|
||||
edit and maintain directly the source code of their keymap files.
|
||||
|
||||
The provided helpers fall into roughly one of three categories:
|
||||
|
||||
1. Helpers that eliminate boilerplate, reduce the complexity of keymaps, and improve readability.
|
||||
2. Helpers that improve portability of "position-based" properties such as combos.
|
||||
3. Helpers that define international and other unicode characters.
|
||||
|
||||
The reminder of this post details each of these three categories.
|
||||
|
||||
## Eliminating Boilerplate
|
||||
|
||||
In ZMK, keymaps are configured using so-called _Devicetree_ files. Devicetree files
|
||||
define a collection of nested _nodes_, whereas each node in turn specifies a variety of
|
||||
_properties_ through which one can customize the keymap.
|
||||
|
||||
For example, the following snippet sets up a
|
||||
[mod-morph](https://zmk.dev/docs/behaviors/mod-morph) behavior that sends <kbd>.</kbd>
|
||||
("dot") when pressed by itself and sends <kbd>:</kbd> ("colon") when shifted:
|
||||
|
||||
```dts {6-7} showLineNumbers
|
||||
/ {
|
||||
behaviors {
|
||||
dot_colon: dot_colon_behavior {
|
||||
compatible = "zmk,behavior-mod-morph";
|
||||
#binding-cells = <0>;
|
||||
bindings = <&kp DOT>, <&kp COLON>;
|
||||
mods = <(MOD_LSFT|MOD_RSFT)>;
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Adding this snippet to the keymap will create a new node `dot_colon_behavior`
|
||||
(nested underneath the `behaviors` and root `/` nodes), and assigns it four
|
||||
properties (`compatible`, `#binding-cells`, etc). Here, the crucial properties are `bindings`
|
||||
and `mods`, which spell out the actual functionality of the new behavior. The rest
|
||||
of the snippet (including the nested node-structure) is boilerplate.
|
||||
|
||||
The idea of the _nodefree_ repo is to use C preprocessor macros to improve
|
||||
readability by eliminating as much boilerplate as possible. Besides hiding
|
||||
redundant behavior properties from the user, it also automatically creates and
|
||||
nests all required behavior nodes, making for a "node-free" and less
|
||||
error-prone user experience (hence the name of the repo).
|
||||
|
||||
For example, using `ZMK_BEHAVIOR`, one of the repo's helper functions, the
|
||||
above snippet simplifies to:
|
||||
|
||||
```dts showLineNumbers
|
||||
ZMK_BEHAVIOR(dot_colon, mod_morph,
|
||||
bindings = <&kp DOT>, <&kp COLON>;
|
||||
mods = <(MOD_LSFT|MOD_RSFT)>;
|
||||
)
|
||||
```
|
||||
|
||||
For complex keymap files, the gains from eliminating boilerplate can be
|
||||
enormous. To provide a benchmark, consider my [personal
|
||||
config](https://github.com/urob/zmk-config), which uses the _nodefree_ repo to
|
||||
create various behaviors, set up combos, and add layers to the keymap. Without
|
||||
the _nodefree_ helpers, the total size of my keymap would have been 41 kB. Using
|
||||
the helper macros, the actual size is instead reduced to a more sane 12 kB.[^1]
|
||||
|
||||
[^1]:
|
||||
To compute the impact on file size, I ran `pcpp
|
||||
--passthru-unfound-includes` on the `base.keymap` file, comparing two
|
||||
variants. First, I ran the pre-processor on the actual file. Second, I ran
|
||||
it on a version where I commented out all the _nodefree_ headers,
|
||||
preventing any of the helper functions from getting expanded. The
|
||||
difference isolates precisely the size gains from eliminating boilerplate,
|
||||
which in my ZMK config are especially large due to a vast number of
|
||||
behaviors used to add various Unicode characters to my keymap.
|
||||
|
||||
## Simplifying "Position-based" Behaviors
|
||||
|
||||
In ZMK, there are several features that are position-based. As of today, these
|
||||
are [combos](/docs/features/combos) and [positional
|
||||
hold-taps](/docs/behaviors/hold-tap#positional-hold-tap-and-hold-trigger-key-positions),
|
||||
with behaviors like the ["Swapper"](https://github.com/zmkfirmware/zmk/pull/1366) and [Leader
|
||||
key](https://github.com/zmkfirmware/zmk/pull/1380) currently
|
||||
developed by [Nick Conway](https://github.com/nickconway) in pull requests also utilizing them.
|
||||
|
||||
Configuring these behaviors involves lots of key counting, which can be
|
||||
cumbersome and error-prone, especially on larger keyboards. It also reduces the
|
||||
portability of configuration files across keyboards with different layouts.
|
||||
|
||||
To facilitate configuring position-based behaviors, the _nodefree_ repo comes
|
||||
with a community-maintained library of "key-position labels" for a variety of
|
||||
popular layouts. The idea is to provide a standardized naming convention that
|
||||
is consistent across different keyboards. For instance, the labels for a 36-key
|
||||
layout are as follows:
|
||||
|
||||
```
|
||||
╭─────────────────────┬─────────────────────╮
|
||||
│ LT4 LT3 LT2 LT1 LT0 │ RT0 RT1 RT2 RT3 RT4 │
|
||||
│ LM4 LM3 LM2 LM1 LM0 │ RM0 RM1 RM2 RM3 RM4 │
|
||||
│ LB4 LB3 LB2 LB1 LB0 │ RB0 RB1 RB2 RB3 RB4 │
|
||||
╰───────╮ LH2 LH1 LH0 │ RH0 RH1 RH2 ╭───────╯
|
||||
╰─────────────┴─────────────╯
|
||||
```
|
||||
|
||||
The labels are all of the following form:
|
||||
|
||||
- `L/R` for **L**eft/**R**ight side
|
||||
- `T/M/B/H` for **T**op/**M**iddle/**B**ottom and t**H**umb row.
|
||||
- `0/1/2/3/4` for the finger position, counting from the inside to the outside
|
||||
|
||||
The library currently contains definitions for 17 physical
|
||||
layouts, ranging from the tiny [Osprette](https://github.com/smores56/osprette) to the large-ish
|
||||
[Glove80](https://www.moergo.com/collections/glove80-keyboards).
|
||||
While some of these layouts contain more keys than others, the idea behind the
|
||||
library is that keys that for all practical purposes are in the "same" location
|
||||
share the same label. That is, the 3 rows containing the alpha keys are
|
||||
always labeled `T/M/B` with `LM1` and `RM1` defining the home position of
|
||||
the index fingers. For larger boards, the numbers row is always labeled
|
||||
`N`. For even larger boards, the function key row and the row below `B` are
|
||||
labeled `C` and `F` (mnemonics for **C**eiling and **F**loor), etc.
|
||||
|
||||
Besides sparing the user from counting keys, the library also makes it easy to
|
||||
port an entire, say, combo configuration from one keyboard to the next by simply
|
||||
switching layout headers.
|
||||
|
||||
## Unicode and International Keycodes
|
||||
|
||||
The final category of helpers is targeted at people who wish to type international characters
|
||||
without switching the input language of their operation system. To do so, the repo comes with
|
||||
helper functions that can be used to define Unicode behaviors.
|
||||
|
||||
In addition, the repo also ships with a community-maintained library of
|
||||
language-files that define Unicode behaviors for all relevant characters in a
|
||||
given language. For instance, after loading the German language file, one can
|
||||
add `&de_ae` to the keymap, which will send <kbd>ä</kbd>/<kbd>Ä</kbd> when pressed or shifted.
|
||||
|
||||
## About Me
|
||||
|
||||
My path to ZMK and programmable keyboards started in the early pandemic, when I
|
||||
built a [Katana60](https://geekhack.org/index.php?topic=88719.0) and learned
|
||||
how to touch-type Colemak. Soon after I purchased a Planck, which turned out
|
||||
to be the real gateway drug for me.
|
||||
|
||||
Committed to making the best out of the Planck's 48 keys, I have since
|
||||
discovered my love for tinkering with tiny layouts and finding new ways of
|
||||
[squeezing out](https://xkcd.com/2583/) a bit more ergonomics. Along the way, I
|
||||
also made the switch from QMK to ZMK, whose "object-oriented" approach to
|
||||
behaviors I found more appealing for complex keymaps.[^2]
|
||||
|
||||
[^2]:
|
||||
I am using the term object-oriented somewhat loosely here. What I mean by
|
||||
that is the differentiation between abstract behavior classes (such as
|
||||
hold-taps) and specific behavior instances that are added to the keymap.
|
||||
Allowing to set up multiple, reusable instances of each behavior has been a
|
||||
_huge_ time-saver compared to QMK's more limited behavior settings that are
|
||||
either global or key-specific.
|
||||
|
||||
These days I mostly type on a Corne-ish Zen and are waiting for the day when I
|
||||
will finally put together the
|
||||
[Hypergolic](https://github.com/davidphilipbarr/hypergolic) that's been sitting
|
||||
on my desk for months. My current keymap is designed for 34 keys, making
|
||||
liberal use of combos and [timerless homerow
|
||||
mods](https://github.com/urob/zmk-config#timeless-homerow-mods) to make up for
|
||||
a lack of keys.
|
Loading…
Add table
Reference in a new issue