.. | ||
international_chars | ||
keypos_def | ||
.gitattributes | ||
example.keymap | ||
helper.h | ||
README.md |
zmk-nodefree-config
ZMK lets user customize their keyboard layout by providing a Devicetree file
(.keymap
). The specific syntax requirements of the Devicetree file format can,
however, make this process a bit daunting for new users.
This repository provides simple convenience macros that simplify the configuration for many common use cases. It results in a "node-free" user configuration with a more streamlined syntax. Check out example.keymap to see it in action.
See changelog for latest changes.
Overview
The following convenience macros are provided:
ZMK_BEHAVIOR
creates behaviors such as hold-taps, tap-dances or ZMK macros [doc]ZMK_LAYER
adds layers to the keymap [doc]ZMK_COMBO
defines combos [doc]ZMK_CONDITIONAL_LAYER
sets up "tri-layer" conditions [doc]ZMK_UNICODE_SINGLE
andZMK_UNICODE_PAIR
create unicode characters [doc]international_chars
provides character definitions for some non-English languages [doc]keypos_def
provides human-readable key position shortcuts for some popular keyboards that simplify the configuration of combos and positional hold-taps [doc]
Quickstart
- Copy this repository into the root folder of your zmk-config. The
folder structure should look as follows:
zmk-config ├── config │ ├── your.keyboard.conf │ ├── your_keyboard.keymap │ └── ... ├── zmk-nodefree-config │ ├── helper.h │ ├── ...
- Source
helper.h
near the top of your.keymap
file:#include "../zmk-nodefree-config/helper.h"
- Customize your keyboard's
.keymap
file. See example.keymap or my personal zmk-config for a complete configuration, and read the documentation below for details.
Configuration details
ZMK_BEHAVIOR
ZMK_BEHAVIOR
can be used to create any of the following ZMK behaviors: caps-word,
hold-tap, key-repeat, macro, mod-morph, sticky-key or tap-dance
Syntax: ZMK_BEHAVIOR(name, type, specification)
name
: a unique string chosen by the user (e.g.,my_behavior
). The new behavior can be added to the keymap using&name
(e.g.,&my_behavior
).type
: the behavior to be created. It must be one of the following:caps_word
,hold_tap
,key_repeat
,macro
,mod_morph
,sticky_key
ortap_dance
. Note that two-word types are separated by underscores (_
).specification
: the custom behavior code. It should contain the body of the corresponding ZMK behavior configuration without thelabel
,#binding-cells
andcompatible
properties and without the surrounding node-specification.
Example 1: Creating a custom "homerow mod" tap-hold behavior
ZMK_BEHAVIOR(hrm, hold_tap,
flavor = "balanced";
tapping-term-ms = <280>;
quick-tap-ms = <125>;
global-quick-tap;
bindings = <&kp>, <&kp>;
)
This creates a custom "homerow mod" that can be added to the keymap using &hrm
. For example,
&hrm LSHIFT T
creates a key that yields T
on tap and LSHIFT
on hold.
Example 2: Creating a custom tap-dance key
ZMK_BEHAVIOR(ss_cw, tap_dance,
tapping-term-ms = <200>;
bindings = <&sk LSHFT>, <&caps_word>;
)
This behavior yields sticky-shift on tap and caps-word on double tap. It can be added to
the keymap using &ss_cw
.
Example 3: Creating a custom "win-sleep" macro
ZMK_BEHAVIOR(win_sleep, macro,
wait-ms = <100>;
tap-ms = <5>;
bindings = <&kp LG(X) &kp U &kp S>;
)
This creates a "Windows sleep macro" that can be added to the keymap using &win_sleep
.
ZMK_LAYER
ZMK_LAYER
adds new keymap layers to the configuration.
Syntax: ZMK_LAYER(name, layout)
name
: a unique identifier string chosen by the user (usually there is no reason to reference this elsewhere)layout
: the layout specification using the same syntax as thebindings
property of the ZMK keymap configuration
Multiple layers can be added with repeated calls of ZMK_LAYER
. They will be ordered
in the same order in which they are created, with the first-specified layer being
the "lowest" one (see here for details).
Example usage
ZMK_KEYMAP(default_layer,
// ╭─────────────┬─────────────┬─────────────┬─────────────┬─────────────╮ ╭─────────────┬─────────────┬─────────────┬─────────────┬─────────────╮
&kp Q &kp W &kp F &kp P &kp B &kp J &kp L &kp U &kp Y &kp SQT
// ├─────────────┼─────────────┼─────────────┼─────────────┼─────────────┤ ├─────────────┼─────────────┼─────────────┼─────────────┼─────────────┤
&hrm LGUI A &hrm LALT R &hrm LCTRL S &hrm LSHFT T &kp G &kp M &hrm RSHFT N &hrm LCTRL E &hrm LALT I &hrm LGUI O
// ├─────────────┼─────────────┼─────────────┼─────────────┼─────────────┤ ├─────────────┼─────────────┼─────────────┼─────────────┼─────────────┤
&kp Z &kp X &kp C &kp D &kp V &kp K &kp H &kp COMMA &kp DOT &kp SEMI
// ╰─────────────┼─────────────┼─────────────┼─────────────┼─────────────┤ ├─────────────┼─────────────┼─────────────┼─────────────┼─────────────┤
&kp ESC < NAV SPACE &kp TAB &kp RET &ss_cw &bs_del_num
// ╰─────────────┴──── ────────┴─────────────╯ ╰─────────────┴─────────────┴─────────────╯
)
ZMK_COMBO
ZMK_COMBO
defines new combos.
Syntax: ZMK_COMBO(name, bindings, keypos, layers)
name
: a unique identifier string chosen by the user (usually there is not reason to reference this elsewhere)binding
: the binding triggered by the combo (this can be any stock or previously defined behavior)keypos
: a list of 2 or more key positions that trigger the combo (e.g.,12 13
). Note that the mapping from key positions to keys depends on your keyboard. To facilitate the combo setup and increase portability, one can use key-position helpers instead. See below on how to use them.layers
: a list of layers for which the combo is active (e.g.,0 1
for the first two layers). If set toALL
the combo is active on all layers.
By default, the timeout for combos created with ZMK_COMBO
is 30ms. If COMBO_TERM
is
reset prior to calling ZMK_COMBO
, the new value of COMBO_TERM
is used instead.
Alternatively, one can use ZMK_COMBO_ADV
which allows to specify the combo-timeout directly
as 5th argument.
Note: with older ZMK versions, using different combo-timeouts would result in keys getting stuck. If this is an issue, try updating to the latest ZMK version.
Example: copy and paste combos
#undef COMBO_TERM
#define COMBO_TERM 50
ZMK_COMBO(copy, &kp LC(C), 12 13, ALL)
ZMK_COMBO(paste, &kp LC(V), 13 14, ALL)
This sets the combo timeout to 50ms, and then creates two combos which both are
active on all layers. The first combo is triggered when the
12th and 13th keys are pressed jointly within the COMBO_TERM
, sending Ctrl + C. The
second combo is triggered when the 13th and 14th keys are pressed jointly, sending
Ctrl + V.
ZMK_CONDITIONAL_LAYER
This sets up tri-layer conditions.
Syntax: ZMK_CONDITIONAL_LAYER(if_layers, then_layers)
if_layers
: a list of layers which trigger thethen_layer
if simultaneously activethen_layer
: the layer which is activated when the if-condition is met. Due to ZMK's layering model, it should generally have a higher number than theif_layers
For instance, this triggers "layer 3" if layers "1" and "2" are simultaneously active.
ZMK_CONDITIONAL_LAYER(1 2, 3)
Mind that ZMK's layer numbering starts at 0. One can use layer definitions, as demonstrated in example.keymap, to simplify life.
ZMK_UNICODE
There are two macros that create new unicode characters that
can be added to the keymap. ZMK_UNICODE_SINGLE
creates single unicode characters such
as €, and ZMK_UNICODE_PAIR
creates pairs of shifted/unshifted unicode
characters that are useful for specifying international characters such as
ä/Ä or δ/Δ.
Syntax: ZMK_UNICODE_SINGLE(name, L0, L1, L2, L3)
name:
a unique string chosen by the user (e.g.,my_char
). The unicode character can be added to the keymap using&name
(e.g.,&my_char
)L0
toL3
: a 4-digit sequence defining the unicode string using standard ZMK key codes
Syntax: ZMK_UNICODE_PAIR(name, L0, L1, L2, L3, U0, U1, U2, U3)
name:
a unique string chosen by the user (e.g.,my_char
). The unicode character can be added to the keymap using&name
(e.g.,&my_char
)L0
toL3
: a 4-digit sequence defining the unshifted unicode stringU0
toU3
: a 4-digit sequence defining the shifted unicode string (which is send when holding Shift while pressing &name)
Note: 5-digit unicode characters are currently not supported (but would be easy to add if there is interest).
Example 1: Euro sign (€)
ZMK_UNICODE_SINGLE(euro_sign, N2, N0, A, C)
This creates a Euro character that can be added to the keymap using &euro_sign
.
Example 2: German umlauts (ä/Ä, ö/Ö, ü/Ü)
// name unshifted shifted
ZMK_UNICODE_PAIR( de_ae, N0, N0, E, N4, N0, N0, C, N4 )
ZMK_UNICODE_PAIR( de_oe, N0, N0, F, N6, N0, N0, D, N6 )
ZMK_UNICODE_PAIR( de_ue, N0, N0, F, C, N0, N0, D, C )
The creates "umlaut" pairs that can be added to the keymap using &de_ae
, &de_oe
and &de_ue
.
Dependencies for unicode
-
ZMK_UNICODE_PAIR
requires ZMK patched with PR #1412. If you need help maintaining your own ZMK repository, check out this guide. If you don't want to maintain your own ZMK repository, you can use ZMK's beta testing feature to configure Github Actions to build against a patched remote branch of ZMK. To do so, replace the contents ofwest.yml
in yourzmk-config/config
directory with the following contents, which adds the required patch:manifest: remotes: - name: urob url-base: https://github.com/urob projects: - name: zmk remote: urob revision: fix-mod-morph import: app/west.yml self: path: config
-
The input of unicode characters differs across operating systems. By default,
ZMK_UNICODE
is configured for Windows (using WinCompose). To set it up for another OS, set the variableHOST_OS
before sourcinghelper.h
.For Linux use:
#define HOST_OS 1 // set to 1 for Linux, default is 0 (Windows) #include helper.h
For macOS/Windows-Alt-Codes use:
#define HOST_OS 2 // set to 2 for macOS/Windows-Alt-Codes, default is 0 (Windows) #include helper.h
This will send unicode characters using the OS's default input channels. For non-default input channels or for other operating systems, one can instead set the variables
OS_UNICODE_LEAD
andOS_UNICODE_TRAIL
to the character sequences that initialize/terminate the unicode input.1 -
On Windows and macOS there are additional requirements for unicode input to work. On Windows, one must install WinCompose for full support (or use Win-Alt-Codes for limited support in select software). On macOS one must enable unicode input in the system preferences.
International characters
There are pre-defined definitions for international characters for a few languages --- currently German, Greek and Swedish (contributions are welcome)2. These can be loaded by sourcing the corresponding files; e.g.:
#include "../zmk-nodefree-config/international_chars/german.dtsi"
#include "../zmk-nodefree-config/international_chars/greek.dtsi"
#include "../zmk-nodefree-config/international_chars/swedish.dtsi"
Once sourced, international characters can be added to the
keymap using, e.g., &de_ae
, &el_alpha
or &sv_ao
(each language has its own prefix; see the language files for a complete list of available characters).
Dependencies: These definitions make use of unicode in the background, see the unicode section above for prerequisites.
Note: Windows-Alt-Codes use different keycode sequences than the usual unicode sequences, requiring different definitions. Currently, they are pre-defined for German:
#include "../zmk-nodefree-config/international_chars/german_alt.dtsi"
Key-position helpers
Certain configuration options such as combos and positional hold-taps are based on the physical position of keys on the keyboard. This can be cumbersome and reduces portability of configuration files across keyboards with different layouts.
To increase portability and ease of use, this repo provides optional key-position helpers for some popular keyboard layouts (48-key boards such as Planck, 42-key boards such as Corne, 36-key boards and 34-key boards).
These key-position helpers provide a map from the physical key positions to human-readable shortcuts. All shortcuts are of the following form:
L/R
for Left/Right handT/M/B/H
for Top/Middle/Bottom and tHumb row.0/1/2/3/4
for the finger position starting from the inside (0
is the inner index-finger column,1
is the home position of the index finger, ...,4
is the home position of the pinkie)
For instance, the shortcuts layout for a 36-key board looks 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 ╭───────╯
╰─────────────┴─────────────╯
Schematics for all supported keyboards can be found in the corresponding
definition files in the keypos_def
folder.
To use these key-position helpers, source the definition file for your keyboard
into your .keymap
file. E.g., for a 36-key board, use:
#include "../zmk-nodefree-config/keypos_def/keypos_36keys.h"
Example 1: Defining combos using key-position helpers
ZMK_COMBO(copy, &kp LC(C), LB2 LB3, ALL)
ZMK_COMBO(paste, &kp LC(V), LB1 LB2, ALL)
This defines a "copy"-combo for the middle + ring finger on the left bottom row, and a "paste"-combo for the index + middle finger on the left bottom row. Both combos are active on all layers.
Example 2: Home-row mods with positional hold-taps
Here we use ZMK's positional hold-tap feature to make home-row mods only trigger with "opposite hand" keys. Using key-position helpers makes this straightforward:
#define KEYS_L LT0 LT1 LT2 LT3 LT4 LM0 LM1 LM2 LM3 LM4 LB0 LB1 LB2 LB3 LB4 // left-hand keys
#define KEYS_R RT0 RT1 RT2 RT3 RT4 RM0 RM1 RM2 RM3 RM4 RB0 RB1 RB2 RB3 RB4 // right-hand keys
#define THUMBS LH2 LH1 LH0 RH0 RH1 RH2 // thumb keys
ZMK_BEHAVIOR(hml, hold_tap, // left-hand HRMs
flavor = "balanced";
tapping-term-ms = <280>;
quick-tap-ms = <125>;
global-quick-tap;
bindings = <&kp>, <&kp>;
hold-trigger-key-positions = <KEYS_R THUMBS>;
)
ZMK_BEHAVIOR(hmr, hold_tap, // right-hand HRMs
flavor = "balanced";
tapping-term-ms = <280>;
quick-tap-ms = <125>;
global-quick-tap;
bindings = <&kp>, <&kp>;
hold-trigger-key-positions = <KEYS_L THUMBS>;
)
Changelog
- 9/11/2022: Support for Windows-Alt-Codes
- 8/05/2022: New combo macro
ZMK_COMBO_ADV
for "advanced" combo setups. Compared to the regularZMK_COMBO
macro, it takes the combo-timeout as fifth argument. Moreover, ifCOMBO_HOOK
is defined, it includes its definition as additional options. For example, to use the new global-quick-tap for combos option introduced in PR #1387, one would set#define COMBO_HOOK global-quick-tap-ms = <125>;
before callingZMK_COMBO_ADV
. See my personal combo setup for examples. - 7/31/2022: Switch unicode dependency from PR #1114 to PR #1412
-
The default for Windows is
OS_UNICODE_LEAD
set to tap Right Alt followed by U andOS_UNICODE_TRAIL
set to tap Return. The default for Linux isOS_UNICODE_LEAD
set to tap Shift + Ctrl + U andOS_UNICODE_TRAIL
set to tap Space. The default for macOS isOS_UNICODE_LEAD
set to hold Left Alt andOS_UNICODE_TRAIL
set to release Left Alt. ↩︎ -
Swedish language support was added by discord user "captainwoot". ↩︎