Compare commits

...

16 Commits

Author SHA1 Message Date
Nick Conway bf37575267
Merge a388b4113b into ad7fbfef92 2025-12-05 16:40:32 +01:00
Artem ad7fbfef92
feat(ble): Use appearance set in the BT_DEVICE_APPEARANCE config (#3115)
Properly use the BT_DEVICE_APPEARANCE Zephyr symbol to advertise
the correct appearance, allowing overrides, for e.g. mice.

Co-authored-by: Cem Aksoylar <caksoylar@users.noreply.github.com>
2025-12-04 13:53:47 -05:00
Nick Conway a388b4113b
fix(leader-key): fix tests 2025-06-16 14:03:25 -04:00
Nick Conway 96f1537a0e
docs(leader-key): update leader key docs for overlap timeout 2025-06-16 14:03:00 -04:00
Nick Conway f117af5769
feat(leader-key): add overlap timeout property 2025-06-16 14:02:26 -04:00
Nick Conway ab5125a098
feat(leader-key): initialize dt config values 2025-06-16 13:27:04 -04:00
Nick Conway ee4a63be8f
feat(leader-key): Update docs 2025-06-16 12:32:37 -04:00
Nick Conway 9bed79a543
feat(leader-key): refactor for child nodes, multiple instances 2025-06-16 11:54:38 -04:00
Nick Conway f2db79460d
satisfy pre-commit checks 2025-06-16 11:54:38 -04:00
Nick Conway 18a636cdbf
feat(leader): default to timerless 2025-06-16 11:54:38 -04:00
Nick Conway ee2e6e5a4a
feat(leader): update kconfig 2025-06-16 11:54:38 -04:00
marleydepew ec7e38685c
config link in leader.md
I fixed the link to the advanced config documentation
2025-06-16 11:54:37 -04:00
marleydepew 390743a164
Added "don't" in leader-key.md
logically this should say to add `timerless` if you don't want the timeout
2025-06-16 11:54:37 -04:00
Nick Conway 3e5025712a
Update for 3.5 and refactor 2025-06-16 11:54:37 -04:00
Nick Conway b4a57566b0
Fix leader overlapping with timerless 2025-06-16 11:54:37 -04:00
Nick Conway 83bb1c23e2
Leader key 2025-06-16 11:54:37 -04:00
62 changed files with 1021 additions and 1 deletions

View File

@ -53,6 +53,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources(app PRIVATE src/behaviors/behavior_caps_word.c)
target_sources(app PRIVATE src/behaviors/behavior_key_repeat.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_MACRO app PRIVATE src/behaviors/behavior_macro.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_LEADER_KEY app PRIVATE src/behaviors/behavior_leader_key.c)
target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_mod_morph.c)
target_sources(app PRIVATE src/behaviors/behavior_outputs.c)

View File

@ -25,6 +25,9 @@ config USB_DEVICE_PID
config USB_DEVICE_MANUFACTURER
default "ZMK Project"
config BT_DEVICE_APPEARANCE
default 961
config BT_DIS_PNP_VID
default 0x1D50
@ -458,6 +461,19 @@ config ZMK_COMBO_MAX_KEYS_PER_COMBO
# Combo options
endmenu
menu "Leader Options"
config ZMK_LEADER_MAX_KEYS_PER_SEQUENCE
int "Maximum number of key presses in a leader sequence"
default 4
config ZMK_LEADER_MAX_SEQUENCES_PER_KEY
int "Maximum number of leader sequences that a key can belong to"
default 5
#Leader options
endmenu
menu "Behavior Options"
config ZMK_BEHAVIORS_QUEUE_SIZE

View File

@ -134,3 +134,8 @@ config ZMK_BEHAVIOR_MACRO
bool
default y
depends on DT_HAS_ZMK_BEHAVIOR_MACRO_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_TWO_PARAM_ENABLED
config ZMK_BEHAVIOR_LEADER_KEY
bool
default y
depends on DT_HAS_ZMK_BEHAVIOR_LEADER_KEY_ENABLED

View File

@ -0,0 +1,29 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Leader key behavior
compatible: "zmk,behavior-leader-key"
include: zero_param.yaml
properties:
timeout-ms:
type: int
default: -1
overlap-timeout-ms:
type: int
default: 200
child-binding:
description: "A leader sequence"
properties:
bindings:
type: phandle-array
required: true
key-positions:
type: array
required: true
immediate-trigger:
type: boolean

View File

@ -30,3 +30,9 @@
#define ZMK_VIRTUAL_KEY_POSITION_BEHAVIOR_INPUT_PROCESSOR(listener_index, processor_index) \
(ZMK_VIRTUAL_KEY_POSITION_COMBO(ZMK_COMBOS_LEN) + \
(ZMK_INPUT_LISTENERS_LEN * (processor_index)) + (listener_index))
/**
* Gets the virtual key position to use for the leader sequence with the given index.
*/
#define ZMK_VIRTUAL_KEY_POSITION_LEADER(index) \
(ZMK_VIRTUAL_KEY_POSITION_SENSOR(ZMK_KEYMAP_SENSORS_LEN) + (index))

View File

@ -0,0 +1,382 @@
/*
* Copyright (c) 2025 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_leader_key
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/hid.h>
#include <zmk/event_manager.h>
#include <zmk/events/keycode_state_changed.h>
#include <zmk/events/position_state_changed.h>
#include <zmk/keymap.h>
#include <zmk/behavior.h>
#include <zmk/matrix.h>
#include <zmk/virtual_key_position.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
static bool leader_status;
static int32_t press_count;
static int32_t release_count;
static int32_t timeout_ms;
static int32_t overlap_timeout_ms;
static int32_t active_leader_position;
static bool first_release;
static struct k_work_delayable release_timer;
static int64_t release_at;
// static bool timer_started;
static bool timer_cancelled;
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
static uint8_t source;
#endif
struct leader_seq_cfg {
int32_t key_positions[CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE];
int32_t key_position_len;
bool immediate_trigger;
bool is_pressed;
// the virtual key position is a key position outside the range used by the keyboard.
// it is necessary so hold-taps can uniquely identify a behavior.
int32_t virtual_key_position;
struct zmk_behavior_binding behavior;
};
struct behavior_leader_key_config {
int32_t timeout_ms;
int32_t overlap_timeout_ms;
struct leader_seq_cfg *sequences;
size_t sequences_len;
};
// leader_pressed_keys is filled with an event when a key is pressed.
// The keys are removed from this array when they are released.
// Once this array is empty, the behavior is released.
static const struct zmk_position_state_changed
*leader_pressed_keys[CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE] = {NULL};
static uint32_t current_sequence[CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE] = {-1};
// the set of candidate leader based on the currently leader_pressed_keys
static int num_candidates;
static struct leader_seq_cfg *sequence_candidates[CONFIG_ZMK_LEADER_MAX_SEQUENCES_PER_KEY];
static int num_comp_candidates;
static struct leader_seq_cfg
*completed_sequence_candidates[CONFIG_ZMK_LEADER_MAX_SEQUENCES_PER_KEY];
const static struct behavior_leader_key_config *active_leader_cfg;
static bool has_current_sequence(struct leader_seq_cfg *sequence, int count) {
for (int i = 0; i < count; i++) {
if (sequence->key_positions[i] != current_sequence[i]) {
return false;
}
}
return true;
}
static bool is_in_current_sequence(int32_t position) {
for (int i = 0; i < CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE; i++) {
if (position == current_sequence[i]) {
return true;
}
}
return false;
}
static bool is_duplicate(struct leader_seq_cfg *seq) {
for (int i = 0; i < CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE; i++) {
if (sequence_candidates[i] == seq) {
return true;
}
}
return false;
}
static bool release_key_in_sequence(int32_t position) {
for (int i = 0; i < release_count; i++) {
if (leader_pressed_keys[i] && position == leader_pressed_keys[i]->position) {
leader_pressed_keys[i] = NULL;
return true;
}
}
return false;
}
static bool all_keys_released() {
for (int i = 0; i < press_count; i++) {
if (leader_pressed_keys[i] != NULL) {
return false;
}
}
return true;
}
static void clear_candidates() {
for (int i = 0; i < CONFIG_ZMK_LEADER_MAX_SEQUENCES_PER_KEY; i++) {
sequence_candidates[i] = NULL;
completed_sequence_candidates[i] = NULL;
}
}
static void leader_find_candidates(int32_t position, int count) {
clear_candidates();
num_candidates = 0;
num_comp_candidates = 0;
for (int i = 0; i < active_leader_cfg->sequences_len; i++) {
struct leader_seq_cfg *sequence = &(active_leader_cfg->sequences[i]);
if (sequence == NULL) {
continue;
}
if (sequence->key_positions[count] == position && has_current_sequence(sequence, count) &&
!is_duplicate(sequence)) {
sequence_candidates[num_candidates] = sequence;
num_candidates++;
if (sequence->key_position_len == count + 1) {
completed_sequence_candidates[num_comp_candidates] = sequence;
num_comp_candidates++;
}
}
}
}
const struct zmk_listener zmk_listener_leader;
static inline int press_leader_behavior(struct leader_seq_cfg *sequence, int32_t timestamp) {
struct zmk_behavior_binding_event event = {
.position = sequence->virtual_key_position,
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
.source = source,
#endif
.timestamp = timestamp,
};
sequence->is_pressed = true;
return behavior_keymap_binding_pressed(&sequence->behavior, event);
}
static inline int release_leader_behavior(struct leader_seq_cfg *sequence, int32_t timestamp) {
struct zmk_behavior_binding_event event = {
.position = sequence->virtual_key_position,
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
.source = source,
#endif
.timestamp = timestamp,
};
sequence->is_pressed = false;
return behavior_keymap_binding_released(&sequence->behavior, event);
}
static int stop_timer() {
int timer_cancel_result = k_work_cancel_delayable(&release_timer);
if (timer_cancel_result == -EINPROGRESS) {
// too late to cancel, we'll let the timer handler clear up.
timer_cancelled = true;
}
return timer_cancel_result;
}
static void reset_timer(int32_t timestamp, bool is_overlap) {
int32_t wait_time_ms = timeout_ms;
if (is_overlap) {
wait_time_ms = overlap_timeout_ms;
}
release_at = timestamp + wait_time_ms;
int32_t ms_left = release_at - k_uptime_get();
if (ms_left > 0) {
k_work_schedule(&release_timer, K_MSEC(ms_left));
LOG_DBG("Successfully reset leader timer");
}
}
static void activate_leader_key(const struct behavior_leader_key_config *cfg, uint32_t position) {
LOG_DBG("leader key activated");
leader_status = true;
press_count = 0;
release_count = 0;
timeout_ms = cfg->timeout_ms;
overlap_timeout_ms = cfg->overlap_timeout_ms;
active_leader_position = position;
first_release = false;
active_leader_cfg = cfg;
if (timeout_ms > 0) {
reset_timer(k_uptime_get(), false);
}
for (int i = 0; i < CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE; i++) {
leader_pressed_keys[i] = NULL;
}
};
static void deactivate_leader_key() {
LOG_DBG("leader key deactivated");
leader_status = false;
clear_candidates();
};
static void behavior_leader_key_timer_handler(struct k_work *item) {
if (!leader_status) {
return;
}
if (timer_cancelled) {
return;
}
LOG_DBG("deactivating leader due to timeout");
for (int i = 0; i < num_comp_candidates; i++) {
if (!completed_sequence_candidates[i]->is_pressed) {
press_leader_behavior(completed_sequence_candidates[i], k_uptime_get());
release_leader_behavior(completed_sequence_candidates[i], k_uptime_get());
}
}
deactivate_leader_key();
}
static int position_state_changed_listener(const zmk_event_t *ev) {
struct zmk_position_state_changed *data = as_zmk_position_state_changed(ev);
if (data == NULL) {
return 0;
}
if (!leader_status && !data->state && !all_keys_released()) {
if (release_key_in_sequence(data->position)) {
return ZMK_EV_EVENT_HANDLED;
}
return 0;
}
if (leader_status) {
if (data->state) { // keydown
leader_find_candidates(data->position, press_count);
LOG_DBG("leader cands: %d comp: %d", num_candidates, num_comp_candidates);
stop_timer();
current_sequence[press_count] = data->position;
leader_pressed_keys[press_count] = data;
press_count++;
for (int i = 0; i < num_comp_candidates; i++) {
LOG_DBG("leader i is %d", i);
struct leader_seq_cfg *seq = completed_sequence_candidates[i];
if (seq->immediate_trigger || (num_candidates == 1 && num_comp_candidates == 1)) {
press_leader_behavior(seq, data->timestamp);
}
}
} else { // keyup
if (data->position == active_leader_position && !first_release) {
first_release = true;
return 0;
}
if (!is_in_current_sequence(data->position)) {
return 0;
}
if (num_candidates == 0) {
deactivate_leader_key();
return ZMK_EV_EVENT_HANDLED;
}
release_count++;
release_key_in_sequence(data->position);
for (int i = 0; i < num_comp_candidates; i++) {
struct leader_seq_cfg *seq = completed_sequence_candidates[i];
if (seq->is_pressed && all_keys_released()) {
release_leader_behavior(seq, data->timestamp);
num_comp_candidates--;
}
if (num_candidates == 1 && num_comp_candidates == 0) {
deactivate_leader_key();
}
}
if (timeout_ms > 0) {
reset_timer(data->timestamp, false);
}
if (num_comp_candidates < num_candidates) {
reset_timer(data->timestamp, true);
}
}
return ZMK_EV_EVENT_HANDLED;
}
return 0;
}
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
const struct device *dev = device_get_binding(binding->behavior_dev);
const struct behavior_leader_key_config *cfg = dev->config;
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
source = event.source;
#endif
activate_leader_key(cfg, event.position);
return ZMK_BEHAVIOR_OPAQUE;
}
static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return ZMK_BEHAVIOR_OPAQUE;
}
static const struct behavior_driver_api behavior_leader_key_driver_api = {
.binding_pressed = on_keymap_binding_pressed,
.binding_released = on_keymap_binding_released,
};
static int behavior_leader_key_init(const struct device *dev) {
k_work_init_delayable(&release_timer, behavior_leader_key_timer_handler);
return 0;
}
ZMK_LISTENER(leader, position_state_changed_listener);
ZMK_SUBSCRIPTION(leader, zmk_position_state_changed);
#define SEQUENCE_ITEM(i, n, prop) DT_PROP_BY_IDX(n, prop, i)
#define PROP_SEQUENCES(n, prop) \
{ \
.virtual_key_position = ZMK_VIRTUAL_KEY_POSITION_LEADER(__COUNTER__), \
.is_pressed = false, \
.immediate_trigger = DT_PROP(n, immediate_trigger), \
.key_position_len = DT_PROP_LEN(n, prop), \
.key_positions = {LISTIFY(DT_PROP_LEN(n, prop), SEQUENCE_ITEM, (, ), n, prop)}, \
.behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, n), \
}
#define LEAD_INST(n) \
static struct leader_seq_cfg leader_sequences_##n[] = { \
DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP_VARGS(n, PROP_SEQUENCES, (, ), key_positions)}; \
static struct behavior_leader_key_config behavior_leader_key_config_##n = { \
.timeout_ms = DT_INST_PROP(n, timeout_ms), \
.overlap_timeout_ms = DT_INST_PROP(n, overlap_timeout_ms), \
.sequences = leader_sequences_##n, \
.sequences_len = ARRAY_SIZE(leader_sequences_##n)}; \
BEHAVIOR_DT_INST_DEFINE(n, behavior_leader_key_init, NULL, NULL, \
&behavior_leader_key_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_leader_key_driver_api);
DT_INST_FOREACH_STATUS_OKAY(LEAD_INST)

View File

@ -65,13 +65,15 @@ static uint8_t active_profile;
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
#define DEVICE_APPEARANCE \
(uint8_t) CONFIG_BT_DEVICE_APPEARANCE, (uint8_t)(CONFIG_BT_DEVICE_APPEARANCE >> 8)
BUILD_ASSERT(
DEVICE_NAME_LEN <= CONFIG_BT_DEVICE_NAME_MAX,
"ERROR: BLE device name is too long. Max length: " STRINGIFY(CONFIG_BT_DEVICE_NAME_MAX));
static struct bt_data zmk_ble_ad[] = {
BT_DATA_BYTES(BT_DATA_GAP_APPEARANCE, 0xC1, 0x03),
BT_DATA_BYTES(BT_DATA_GAP_APPEARANCE, DEVICE_APPEARANCE),
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_SOME, 0x12, 0x18, /* HID Service */
0x0f, 0x18 /* Battery Service */

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,4 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,10 @@
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,2000)
>;
};

View File

@ -0,0 +1,39 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/ {
leader: leader {
compatible = "zmk,behavior-leader-key";
#binding-cells = <0>;
timeout-ms = <200>;
leader_seq_one {
key-positions = <0>;
bindings = <&kp A>;
};
leader_seq_two {
key-positions = <1>;
bindings = <&kp B>;
};
leader_seq_three {
key-positions = <3>;
bindings = <&kp N1>;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&kp N1 &kp N2
&kp N3 &leader
>;
};
};
};

View File

@ -0,0 +1,39 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/ {
leader: leader {
compatible = "zmk,behavior-leader-key";
#binding-cells = <0>;
timeout-ms = <200>;
leader_seq_one {
key-positions = <0>;
bindings = <&kp A>;
};
leader_seq_two {
key-positions = <1>;
bindings = <&kp B>;
};
leader_seq_three {
key-positions = <0 1>;
bindings = <&kp C>;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&kp N1 &kp N2
&kp N3 &leader
>;
};
};
};

View File

@ -0,0 +1,40 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/ {
leader: leader {
compatible = "zmk,behavior-leader-key";
#binding-cells = <0>;
timeout-ms = <200>;
leader_seq_one {
key-positions = <0>;
bindings = <&kp A>;
immediate-trigger;
};
leader_seq_two {
key-positions = <1>;
bindings = <&kp B>;
};
leader_seq_three {
key-positions = <0 1>;
bindings = <&kp C>;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&kp N1 &kp N2
&kp N3 &leader
>;
};
};
};

View File

@ -0,0 +1,33 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/ {
leader: leader {
compatible = "zmk,behavior-leader-key";
#binding-cells = <0>;
leader_seq_one {
key-positions = <0>;
bindings = <&kp A>;
};
leader_seq_two {
key-positions = <0 0>;
bindings = <&kp B>;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&kp N1 &kp N2
&kp N3 &leader
>;
};
};
};

View File

@ -0,0 +1,32 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/ {
leader: leader {
compatible = "zmk,behavior-leader-key";
#binding-cells = <0>;
leader_seq_one {
key-positions = <0 1 2>;
bindings = <&kp A>;
};
leader_seq_two {
key-positions = <3 3>;
bindings = <&kp B>;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&kp N1 &kp N2
&kp N3 &leader
>;
};
};
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,4 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,10 @@
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,4 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,12 @@
#include "../behavior_keymap_three.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,2 @@
leader: leader key activated
leader: leader key deactivated

View File

@ -0,0 +1,10 @@
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,6 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,12 @@
#include "../behavior_keymap_overlap_immediate.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,5 @@
leader: leader key activated
leader: deactivating leader due to timeout
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,10 @@
#include "../behavior_keymap_overlap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,5 @@
leader: leader key activated
leader: deactivating leader due to timeout
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,10 @@
#include "../behavior_keymap_overlap_timerless.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,4 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,12 @@
#include "../behavior_keymap_overlap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,4 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,12 @@
#include "../behavior_keymap_overlap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_RELEASE(0,0,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,4 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,14 @@
#include "../behavior_keymap_three.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,2,10)
ZMK_MOCK_RELEASE(0,2,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,4 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,14 @@
#include "../behavior_keymap_three.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_PRESS(0,2,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_RELEASE(0,2,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,4 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,14 @@
#include "../behavior_keymap_three.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_PRESS(0,2,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_RELEASE(0,2,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,4 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,12 @@
#include "../behavior_keymap_overlap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_RELEASE(0,1,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,3 @@
leader: leader key activated
leader: deactivating leader due to timeout
leader: leader key deactivated

View File

@ -0,0 +1,8 @@
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,2000)
>;
};

View File

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*zmk_leader.*:/leader:/p
s/.*leader_key.*:/leader:/p

View File

@ -0,0 +1,8 @@
leader: leader key activated
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated
leader: leader key activated
pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
leader: leader key deactivated

View File

@ -0,0 +1,14 @@
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,2000)
>;
};

View File

@ -16,3 +16,4 @@ See [Configuration Overview](index.md) for instructions on how to change these s
| `CONFIG_ZMK_BLE_EXPERIMENTAL_FEATURES` | bool | Aggregate config that enables both `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN` and `CONFIG_ZMK_BLE_EXPERIMENTAL_SEC`. | n |
| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Enable passkey entry during pairing for enhanced security. (Note: After enabling this, you will need to re-pair all previously paired hosts.) | n |
| `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION` | bool | Low level setting for GATT subscriptions. Set to `n` to work around an annoying Windows bug with battery notifications. | y |
| `CONFIG_BT_DEVICE_APPEARANCE` | int | Bluetooth device [appearance value](https://bluetooth.com/specifications/assigned-numbers) (should be converted from hexadecimal to decimal). | 961 |

View File

@ -0,0 +1,21 @@
---
title: Leader Configuration
sidebar_label: Leader
---
See the [Leader key behavior page](../keymaps/behaviors/leader-key.md) for more details and examples.
See [Configuration Overview](index.md) for instructions on how to change these settings.
## Kconfig
Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)
| Config | Type | Description | Default |
| ----------------------------------------- | ---- | --------------------------------------------------- | ------- |
| `CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE` | int | Maximum number of key positions per leader sequence | 4 |
| `CONFIG_ZMK_LEADER_MAX_SEQUENCES_PER_KEY` | int | Maximum number of sequences allowed per leader key | 5 |
`CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE` sets the maximum length of a leader sequence.
If `CONFIG_ZMK_LEADER_MAX_SEQUENCES_PER_KEY` is 5, you can have up to 5 separate leader sequences each for each leader key.

View File

@ -63,6 +63,7 @@ Below table lists major features/capabilities currently supported in ZMK, as wel
| [Sticky (One Shot) Keys](keymaps/behaviors/sticky-key.md) | ✅ |
| [Combos](keymaps/combos.md) | ✅ |
| [Macros](keymaps/behaviors/macros.md) | ✅ |
| [Leader Key](keymaps/behaviors/leader-key.md) | ✅ |
| [Mouse Keys](keymaps/behaviors/mouse-emulation.md) | ✅ |
| [Realtime Keymap Updating](features/studio.md) | 🚧 |

View File

@ -0,0 +1,87 @@
---
title: Leader Key Behavior
sidebar_label: Leader Key
---
## Summary
The leader key behavior when triggered will capture all following key presses and trigger a leader sequence's behavior if pressed.
### Configuration
#### `timeout-ms`
Defines the amount of time to wait to trigger a completed leader sequence. Defaults to no timeout and will wait indefinitely.
#### `overlap-timeout-ms`
Defines the amount of time to wait to trigger a completed leader sequence after an overlapping sequence is completed. Defaults to 200ms.
### Leader Sequences
#### Summary
Leader sequences are a way to have a sequence of key presses to output a different key. For example, you can hit the leader key followed by Q, then W to output escape.
#### Configuration
Leader sequences are configured as child nodes of your leader key behavior(s). They are specified like this:
```
leader: leader {
compatible = "zmk,behavior-leader-key";
#binding-cells = <0>;
seq_esc {
key-positions = <0 1>;
bindings = <&kp ESC>;
};
};
```
Each sequence can have the following properties:
- `key-positions` is an array of key positions. See the info section below about how to figure out the positions on your board.
- `bindings` is the behavior that is activated when all the key positions are pressed.
- (advanced) you can specify `immediate-trigger` if you want the sequence to be triggered as soon as all key positions are pressed. The default is to wait for the leader key's timeout to trigger the sequence if it overlaps another.
The `key-positions` array must not be longer than the `CONFIG_ZMK_LEADER_MAX_KEYS_PER_SEQUENCE` setting, which defaults to 4. If you want a leader sequence that triggers when pressing 5 keys, then you must change the setting to 5.
:::info
Key positions are numbered like the keys in your keymap, starting at 0. So, if the first key in your keymap is `Q`, this key is in position `0`. The next key (possibly `W`) will have position 1, etcetera.
:::
#### Locality
Sequence behaviors inherit their locality from the position of the leader key. For example, on split keyboards, a sequence using the `&bootloader` behavior will invoke the bootloader on the side on which the leader key is bound.
::::tip
Add the same leader key to both sides to be able to reset either side.
::::
### Example Usage
```
leader: leader {
compatible = "zmk,behavior-leader-key";
binding-cells = <0>;
seq_b {
key-positions = <1 1>;
bindings = <&kp B>;
};
seq_c {
key-positions = <1>;
bindings = <&kp C>;
};
};
```
### Advanced usage
See [leader configuration](../../config/leader.md) for advanced configuration options.

View File

@ -69,6 +69,7 @@ module.exports = {
"keymaps/behaviors/hold-tap",
"keymaps/behaviors/mod-morph",
"keymaps/behaviors/macros",
"keymaps/behaviors/leader-key",
"keymaps/behaviors/key-toggle",
"keymaps/behaviors/sticky-key",
"keymaps/behaviors/sticky-layer",
@ -126,6 +127,7 @@ module.exports = {
"config/encoders",
"config/lighting",
"config/pointing",
"config/leader",
"config/keymap",
"config/layout",
"config/kscan",