https://github.com/urob/zmk-adaptive-key
A ZMK module for adaptive-key behavior
https://github.com/urob/zmk-adaptive-key
zmk zmk-module
Last synced: about 1 year ago
JSON representation
A ZMK module for adaptive-key behavior
- Host: GitHub
- URL: https://github.com/urob/zmk-adaptive-key
- Owner: urob
- License: mit
- Created: 2024-12-14T19:32:29.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-04-01T15:10:10.000Z (about 1 year ago)
- Last Synced: 2025-04-01T16:29:09.016Z (about 1 year ago)
- Topics: zmk, zmk-module
- Language: C
- Homepage:
- Size: 28.3 KB
- Stars: 11
- Watchers: 1
- Forks: 2
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-zmk - urob/zmk-adaptive-key - A ZMK module adding a adaptive-key behavior. (Community firmware Modules and Behaviors / Custom Behaviors)
- awesome-zmk - urob/zmk-adaptive-key - A ZMK module adding a adaptive-key behavior. (Behaviors)
README
# ZMK-ADAPTIVE-KEY
This module adds a `adaptive-key` behavior to ZMK. Some highlights compared to
existing alternatives:
- Works as a module without the need to patch ZMK.
- Configurable `dead-keys` property to turn any keycode into a dead key.
- Simple "inline" macro specification to bind behavior sequences.
- `min-prior-idle-ms` and `max-prior-idle` timeout properties that can vary by
trigger.
- Correct handling of explicit modifiers.
## Usage
To load the module, add the following entries to `remotes` and `projects` in
`config/west.yml`.
```yaml
manifest:
remotes:
- name: zmkfirmware
url-base: https://github.com/zmkfirmware
- name: urob
url-base: https://github.com/urob
projects:
- name: zmk
remote: urob # or zmkfirmware, see comment below
revision: v0.2+fix-child-nodes
import: app/west.yml
- name: zmk-adaptive-key
remote: urob
revision: v0.2 # set to same as ZMK version above
self:
path: config
```
**Important:** The `zephyr` remote used by upstream ZMK currently contains a bug
that under certain circumstances causes the build to fail. You will need to
patch yours if your build fails with an error message like:
```
ERROR: /behavior/leader-key POST_KERNEL 31 < /behaviors/foo POST_KERNEL 49
```
The simplest way to getting the patch is to use my `zmk` remote, as configured
in above manifest. This will automatically build against a patched version of
Zephyr. Alternatively, you can use upstream ZMK and directly overwrite Zephyr by
adding
[these lines](https://github.com/urob/zmk-adaptive-key/blob/433dab18883462f6f5b34baa30f1991345d864e8/tests/west.yml#L12-L36)
to your `west.yml` manifest. In either case, if you are building using Github
Actions, you may need to clear your cache (in the left sidebar on the `Actions`
tab) for the changes to take effect.
## Configuration
An `adaptive-key` defines "trigger" conditions on the _last_ keycode pressed
prior to pressing the behavior. If any trigger condition matches, a behavior
bound to that trigger is invoked. If no trigger condition matches, a default
behavior is invoked.
### `trigger` properties
Triggers are defined as child nodes of an adapative-key instance and are checked
in order of their definition. Triggers have two _required properties_:
- **`trigger-keys`**: A list of keycodes that trigger the bindings.
- **`bindings`**: Behaviors bound to the trigger. If set to multiple behaviors
they are invoked in sequence.
Additional conditions can be configured via _optional properties_:
- **`min-prior-idle-ms`**: Minimum time that must be elapsed since the last key
press. Defaults to none.
- **`max-prior-idle-ms`**: Maximum time that must be elapsed since the last key
press. Defaults to none.
- **`strict-modifiers`**: If true, modifiers must _exactly_ match the
`trigger-keys`. Otherwise it suffices to _contain_ the `trigger-keys` (useful
for case-sensitive bindings). Defaults to false.
### `adaptive-key` properties
Besides `trigger` child nodes, `adaptive-key` instances have the following
properties:
- **`bindings`** (required): The default behavior to invoke if no trigger
condition is met. Can be `&none` to do nothing.
- **`dead-keys`**: A list of key codes that are converted to dead keys. Dead
keys don't send a keycode when pressed the first time but are still considered
as trigger condition. If pressed again, dead keys send their normal keycode.
## Examples
### Hands-down adaptive keys
```c
/ {
behaviors {
ak_h: ak_h {
compatible = "zmk,behavior-adaptive-key";
#binding-cells = <0>;
bindings = <&kp H>;
akt_ah { trigger-keys = ; max-prior-idle-ms = <300>; bindings = <&kp U>; };
akt_uh { trigger-keys = ; max-prior-idle-ms = <300>; bindings = <&kp A>; };
akt_eh { trigger-keys = ; max-prior-idle-ms = <300>; bindings = <&kp O>; };
};
ak_m: ak_m {
compatible = "zmk,behavior-adaptive-key";
#binding-cells = <0>;
bindings = <&kp M>;
akt_gm { trigger-keys = ; max-prior-idle-ms = <300>; bindings = <&kp L>; };
akt_pm { trigger-keys =
; max-prior-idle-ms = <300>; bindings = <&kp L>; };
};
// And similarly for VP->VL, PV->LV, BT->BL, TB->LB
ak_g: ak_g {
compatible = "zmk,behavior-adaptive-key";
#binding-cells = <0>;
bindings = <&kp G>;
// Binding two behaviors: JG->JPG
akt_jg { trigger-keys = ; max-prior-idle-ms = <300>; bindings = <&kp P &kp G>; };
};
};
};
```
### Dead keys
```c
/ {
behaviors {
ak_e: ak_e {
compatible = "zmk,behavior-adaptive-key";
#binding-cells = <0>;
bindings = <&kp E>;
dead-keys = ;
grave { trigger-keys = ; bindings = <&fr_e_grave>; };
acute { trigger-keys = ; bindings = <&fr_e_acute>; };
circumflex { trigger-keys = ; bindings = <&fr_e_circumflex>; };
diaeresis { trigger-keys = ; bindings = <&fr_e_diaeresis>; };
};
};
};
```
Note: the behavior bindings `&fr_e_grave` etc must be defined elsewhere (e.g.,
using the French
[language header](https://github.com/urob/zmk-helpers/tree/main#unicode-characters-and-language-collection)
from the `zmk-helpers` module).
Alternatively, "new" dead keycodes can be "created" by cannibalizing unused
keycode. For instance:
```c
#define DEAD1 F21
#define DEAD2 F22
#define DEAD3 F23
#define DEAD4 F24
/ {
behaviors {
ak_e: ak_e {
compatible = "zmk,behavior-adaptive-key";
#binding-cells = <0>;
bindings = <&kp E>;
dead-keys = ;
grave { trigger-keys = ; bindings = <&fr_e_grave>; };
acute { trigger-keys = ; bindings = <&fr_e_acute>; };
circumflex { trigger-keys = ; bindings = <&fr_e_circumflex>; };
diaeresis { trigger-keys = ; bindings = <&fr_e_diaeresis>; };
};
};
};
```
Note: While the keycodes used in this example are typically unused, they are
still [defined](https://zmk.dev/docs/keymaps/list-of-keycodes#f-keys). Making up
new _undefined_ keycodes is unsupported as their working hinges on the execution
order of this module, which cannot be configured by any supported means.
### Shift-repeat
```c
/ {
behaviors {
shift-repeat: shift-repeat {
compatible = "zmk,behavior-adaptive-key";
#binding-cells = <0>;
bindings = <&sk LSHFT>;
repeat {
trigger-keys = ;
bindings = <&key_repeat>;
max-prior-idle-ms = <350>;
strict-modifiers;
};
};
};
};
```
This sets up a `shift-repeat` behavior that sends `&sk LSHFT` unless when
pressed within 0.35 seconds of any alpha key, in which case it sends
`&key_repeat`. Great for your homing thumb key!
## `Kconfig` settings
- `CONFIG_ZMK_ADAPTIVE_KEY_MAX_TRIGGER_CONDITIONS`: Maximum number of trigger
conditions per `adaptive-key` behavior. Defaults to 32.
- `CONFIG_ZMK_ADAPTIVE_KEY_MAX_BINDINGS`: Maximum number of behaviors bound to a
trigger (i.e., length of macro sequence). Defaults to 4.
- `CONFIG_ZMK_ADAPTIVE_KEY_WAIT_MS`: Wait time in milliseconds between key
presses when binding a macro sequence. Defaults to 5ms.
- `CONFIG_ZMK_ADAPTIVE_KEY_TAP_MS`: Hold time per key tap when binding a macro
sequence. Defaults to 5ms.
## References
- The behavior idea is inspired by the
[Hands Down](https://sites.google.com/alanreiser.com/handsdown/home#h.3fq4ywspvw1g)
keyboard layout. The original ZMK
[feature request](https://github.com/zmkfirmware/zmk/issues/1624) provides
some further discussion.
- PR [#2042](https://github.com/zmkfirmware/zmk/pull/2042) provides an
alternative implementation.
- My personal [zmk-config](https://github.com/urob/zmk-config) contains advanced
usage examples.