From 832f490ca21573da73df6f0ba3d7f784895a7e66 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Thu, 2 Nov 2023 13:11:57 -0700 Subject: [PATCH 1/8] feat: lock a momentary layer --- app/CMakeLists.txt | 1 + app/dts/behaviors.dtsi | 1 + app/dts/behaviors/momentary_layer_lock.dtsi | 15 +++++++ .../zmk,behavior-momentary-layer-lock.yaml | 8 ++++ app/include/zmk/momentary_layer.h | 15 +++++++ app/src/behaviors/behavior_momentary_layer.c | 37 ++++++++++++----- .../behaviors/behavior_momentary_layer_lock.c | 40 +++++++++++++++++++ 7 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 app/dts/behaviors/momentary_layer_lock.dtsi create mode 100644 app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml create mode 100644 app/include/zmk/momentary_layer.h create mode 100644 app/src/behaviors/behavior_momentary_layer_lock.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 6ef00311..f0b5d82a 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -49,6 +49,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) 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(app PRIVATE src/behaviors/behavior_momentary_layer.c) + target_sources(app PRIVATE src/behaviors/behavior_momentary_layer_lock.c) target_sources(app PRIVATE src/behaviors/behavior_mod_morph.c) target_sources(app PRIVATE src/behaviors/behavior_outputs.c) target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c) diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index 23f2fee2..2409cf1e 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include diff --git a/app/dts/behaviors/momentary_layer_lock.dtsi b/app/dts/behaviors/momentary_layer_lock.dtsi new file mode 100644 index 00000000..792c2195 --- /dev/null +++ b/app/dts/behaviors/momentary_layer_lock.dtsi @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + behaviors { + /omit-if-no-ref/ molock: behavior_momentary_layer_lock { + compatible = "zmk,behavior-momentary-layer-lock"; + label = "MO_LOCK"; + #binding-cells = <1>; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml b/app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml new file mode 100644 index 00000000..9d32afc1 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2023 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Momentary layer on press/release behavior + +compatible: "zmk,behavior-momentary-layer-lock" + +include: one_param.yaml diff --git a/app/include/zmk/momentary_layer.h b/app/include/zmk/momentary_layer.h new file mode 100644 index 00000000..7511c0b1 --- /dev/null +++ b/app/include/zmk/momentary_layer.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +// Locks all active momentary layers so they are not disabled when the key is +// released. +// +// Returns a set of the layers that were locked. +zmk_keymap_layers_state_t zmk_lock_active_momentary_layers(); \ No newline at end of file diff --git a/app/src/behaviors/behavior_momentary_layer.c b/app/src/behaviors/behavior_momentary_layer.c index 0c86e605..9f708f7b 100644 --- a/app/src/behaviors/behavior_momentary_layer.c +++ b/app/src/behaviors/behavior_momentary_layer.c @@ -12,32 +12,51 @@ #include #include +#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); struct behavior_mo_config {}; -struct behavior_mo_data {}; +struct behavior_mo_data { + zmk_keymap_layers_state_t active_momentary_layers; + zmk_keymap_layers_state_t ignore_on_release; +}; + +static const struct behavior_mo_config behavior_mo_config = {}; +static struct behavior_mo_data behavior_mo_data; + +zmk_keymap_layers_state_t zmk_lock_active_momentary_layers() { + return behavior_mo_data.ignore_on_release = behavior_mo_data.active_momentary_layers; +} static int behavior_mo_init(const struct device *dev) { return 0; }; static int mo_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { - LOG_DBG("position %d layer %d", event.position, binding->param1); - return zmk_keymap_layer_activate(binding->param1); + int layer = binding->param1; + LOG_DBG("position %d layer %d", event.position, layer); + WRITE_BIT(behavior_mo_data.active_momentary_layers, layer, true); + return zmk_keymap_layer_activate(layer); } static int mo_keymap_binding_released(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { - LOG_DBG("position %d layer %d", event.position, binding->param1); + int layer = binding->param1; + LOG_DBG("position %d layer %d", event.position, layer); + WRITE_BIT(behavior_mo_data.active_momentary_layers, layer, false); + + // If the layer is locked, don't deactivate it. Instead clear the + // ignore_on_release flag so the next press/release will. + if (behavior_mo_data.ignore_on_release & BIT(layer)) { + WRITE_BIT(behavior_mo_data.ignore_on_release, layer, false); + return 0; + } + return zmk_keymap_layer_deactivate(binding->param1); } static const struct behavior_driver_api behavior_mo_driver_api = { .binding_pressed = mo_keymap_binding_pressed, .binding_released = mo_keymap_binding_released}; -static const struct behavior_mo_config behavior_mo_config = {}; - -static struct behavior_mo_data behavior_mo_data; - BEHAVIOR_DT_INST_DEFINE(0, behavior_mo_init, NULL, &behavior_mo_data, &behavior_mo_config, - POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_driver_api); + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_driver_api); \ No newline at end of file diff --git a/app/src/behaviors/behavior_momentary_layer_lock.c b/app/src/behaviors/behavior_momentary_layer_lock.c new file mode 100644 index 00000000..b2d03792 --- /dev/null +++ b/app/src/behaviors/behavior_momentary_layer_lock.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_momentary_layer_lock + +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +static int behavior_mo_lock_init(const struct device *dev) { return 0; }; + +static int mo_lock_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + int fallback_layer = binding->param1; + zmk_keymap_layers_state_t locked = zmk_lock_active_momentary_layers(); + if (!locked && fallback_layer >= 0) { + return zmk_keymap_layer_to(fallback_layer); + } + + return 0; +} + +static const struct behavior_driver_api behavior_mo_lock_driver_api = { + .binding_pressed = mo_lock_keymap_binding_pressed}; + +DEVICE_DT_INST_DEFINE(0, behavior_mo_lock_init, NULL, NULL, NULL, APPLICATION, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_lock_driver_api); + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ \ No newline at end of file From 7cf4c429fa07d7ea7d1e787596c99c5811507828 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 6 Nov 2023 12:58:20 -0800 Subject: [PATCH 2/8] allow custom &molock fallback behaviors --- app/dts/behaviors/momentary_layer_lock.dtsi | 3 +- .../zmk,behavior-momentary-layer-lock.yaml | 7 +- .../behaviors/behavior_momentary_layer_lock.c | 72 ++++++++++++++++--- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/app/dts/behaviors/momentary_layer_lock.dtsi b/app/dts/behaviors/momentary_layer_lock.dtsi index 792c2195..e1d49201 100644 --- a/app/dts/behaviors/momentary_layer_lock.dtsi +++ b/app/dts/behaviors/momentary_layer_lock.dtsi @@ -9,7 +9,8 @@ /omit-if-no-ref/ molock: behavior_momentary_layer_lock { compatible = "zmk,behavior-momentary-layer-lock"; label = "MO_LOCK"; - #binding-cells = <1>; + #binding-cells = <0>; + bindings = <&to 0>; }; }; }; diff --git a/app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml b/app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml index 9d32afc1..dc7138fb 100644 --- a/app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml +++ b/app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml @@ -5,4 +5,9 @@ description: Momentary layer on press/release behavior compatible: "zmk,behavior-momentary-layer-lock" -include: one_param.yaml +include: zero_param.yaml + +properties: + bindings: + type: phandle-array + required: true diff --git a/app/src/behaviors/behavior_momentary_layer_lock.c b/app/src/behaviors/behavior_momentary_layer_lock.c index b2d03792..cca22397 100644 --- a/app/src/behaviors/behavior_momentary_layer_lock.c +++ b/app/src/behaviors/behavior_momentary_layer_lock.c @@ -18,23 +18,79 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +struct behavior_mo_lock_config { + struct zmk_behavior_binding fallback_binding; +}; + +struct behavior_mo_lock_data { + bool is_fallback_binding_pressed; +}; + static int behavior_mo_lock_init(const struct device *dev) { return 0; }; static int mo_lock_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { - int fallback_layer = binding->param1; - zmk_keymap_layers_state_t locked = zmk_lock_active_momentary_layers(); - if (!locked && fallback_layer >= 0) { - return zmk_keymap_layer_to(fallback_layer); + LOG_DBG("%d molock pressed", event.position); + + zmk_keymap_layers_state_t locked_layers = zmk_lock_active_momentary_layers(); + + if (!locked_layers) { + LOG_DBG("no layers locked, falling back to %s", binding->behavior_dev); + + const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev); + const struct behavior_mo_lock_config *config = dev->config; + struct zmk_behavior_binding fallback_binding = config->fallback_binding; + struct behavior_mo_lock_data *data = dev->data; + + data->is_fallback_binding_pressed = true; + return behavior_keymap_binding_pressed(&fallback_binding, event); + } else { + LOG_DBG("locked layers: %#08x", locked_layers); } return 0; } -static const struct behavior_driver_api behavior_mo_lock_driver_api = { - .binding_pressed = mo_lock_keymap_binding_pressed}; +static int mo_lock_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("%d molock released", event.position); -DEVICE_DT_INST_DEFINE(0, behavior_mo_lock_init, NULL, NULL, NULL, APPLICATION, - CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_lock_driver_api); + const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev); + struct behavior_mo_lock_data *data = dev->data; + + if (data->is_fallback_binding_pressed) { + const struct behavior_mo_lock_config *config = dev->config; + struct zmk_behavior_binding fallback_binding = config->fallback_binding; + + data->is_fallback_binding_pressed = false; + return behavior_keymap_binding_released(&fallback_binding, event); + } + + return 0; +} + +#define KP_INST(n) \ + static struct behavior_mo_lock_config behavior_mo_lock_config_##n = { \ + .fallback_binding = \ + { \ + .behavior_dev = DT_PROP(DT_INST_PHANDLE_BY_IDX(n, bindings, 0), label), \ + .param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(n, bindings, 0, param1), (0), \ + (DT_INST_PHA_BY_IDX(n, bindings, 0, param1))), \ + .param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(n, bindings, 0, param2), (0), \ + (DT_INST_PHA_BY_IDX(n, bindings, 0, param))), \ + }, \ + }; \ + static struct behavior_mo_lock_data behavior_mo_lock_data_##n; \ + \ + DEVICE_DT_INST_DEFINE(n, behavior_mo_lock_init, NULL, &behavior_mo_lock_data_##n, \ + &behavior_mo_lock_config_##n, APPLICATION, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_lock_driver_api); + +static const struct behavior_driver_api behavior_mo_lock_driver_api = { + .binding_pressed = mo_lock_keymap_binding_pressed, + .binding_released = mo_lock_keymap_binding_released, +}; + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) #endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ \ No newline at end of file From 54a9fdca5ab28853bd857d7e629d9b9c08fccccc Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 6 Nov 2023 14:04:06 -0800 Subject: [PATCH 3/8] add tests for molock --- .../1-normal/events.patterns | 3 ++ .../1-normal/keycode_events.snapshot | 5 ++++ .../1-normal/native_posix_64.keymap | 22 ++++++++++++++ .../2-deactivate-locked/events.patterns | 3 ++ .../keycode_events.snapshot | 10 +++++++ .../native_posix_64.keymap | 28 +++++++++++++++++ .../3-default-fallback/events.patterns | 3 ++ .../keycode_events.snapshot | 9 ++++++ .../3-default-fallback/native_posix_64.keymap | 28 +++++++++++++++++ .../4-relock/events.patterns | 3 ++ .../4-relock/keycode_events.snapshot | 9 ++++++ .../4-relock/native_posix_64.keymap | 30 +++++++++++++++++++ .../5-custom/events.patterns | 3 ++ .../5-custom/keycode_events.snapshot | 5 ++++ .../5-custom/native_posix_64.keymap | 22 ++++++++++++++ .../6-custom-fallback/events.patterns | 3 ++ .../6-custom-fallback/keycode_events.snapshot | 2 ++ .../6-custom-fallback/native_posix_64.keymap | 17 +++++++++++ .../momentary-layer-lock/behavior_keymap.dtsi | 27 +++++++++++++++++ 19 files changed, 232 insertions(+) create mode 100644 app/tests/momentary-layer-lock/1-normal/events.patterns create mode 100644 app/tests/momentary-layer-lock/1-normal/keycode_events.snapshot create mode 100644 app/tests/momentary-layer-lock/1-normal/native_posix_64.keymap create mode 100644 app/tests/momentary-layer-lock/2-deactivate-locked/events.patterns create mode 100644 app/tests/momentary-layer-lock/2-deactivate-locked/keycode_events.snapshot create mode 100644 app/tests/momentary-layer-lock/2-deactivate-locked/native_posix_64.keymap create mode 100644 app/tests/momentary-layer-lock/3-default-fallback/events.patterns create mode 100644 app/tests/momentary-layer-lock/3-default-fallback/keycode_events.snapshot create mode 100644 app/tests/momentary-layer-lock/3-default-fallback/native_posix_64.keymap create mode 100644 app/tests/momentary-layer-lock/4-relock/events.patterns create mode 100644 app/tests/momentary-layer-lock/4-relock/keycode_events.snapshot create mode 100644 app/tests/momentary-layer-lock/4-relock/native_posix_64.keymap create mode 100644 app/tests/momentary-layer-lock/5-custom/events.patterns create mode 100644 app/tests/momentary-layer-lock/5-custom/keycode_events.snapshot create mode 100644 app/tests/momentary-layer-lock/5-custom/native_posix_64.keymap create mode 100644 app/tests/momentary-layer-lock/6-custom-fallback/events.patterns create mode 100644 app/tests/momentary-layer-lock/6-custom-fallback/keycode_events.snapshot create mode 100644 app/tests/momentary-layer-lock/6-custom-fallback/native_posix_64.keymap create mode 100644 app/tests/momentary-layer-lock/behavior_keymap.dtsi diff --git a/app/tests/momentary-layer-lock/1-normal/events.patterns b/app/tests/momentary-layer-lock/1-normal/events.patterns new file mode 100644 index 00000000..a7191624 --- /dev/null +++ b/app/tests/momentary-layer-lock/1-normal/events.patterns @@ -0,0 +1,3 @@ +s/.*hid_listener_keycode/kp/p +s/.*set_layer_state: layer_changed:/layer_changed:/p +s/.*mo_keymap_binding/mo/p \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/1-normal/keycode_events.snapshot b/app/tests/momentary-layer-lock/1-normal/keycode_events.snapshot new file mode 100644 index 00000000..4f7655be --- /dev/null +++ b/app/tests/momentary-layer-lock/1-normal/keycode_events.snapshot @@ -0,0 +1,5 @@ +mo_pressed: position 1 layer 1 +layer_changed: layer 1 state 1 +mo_released: position 1 layer 1 +kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/momentary-layer-lock/1-normal/native_posix_64.keymap b/app/tests/momentary-layer-lock/1-normal/native_posix_64.keymap new file mode 100644 index 00000000..d53ebd7d --- /dev/null +++ b/app/tests/momentary-layer-lock/1-normal/native_posix_64.keymap @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,1,10) + + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/2-deactivate-locked/events.patterns b/app/tests/momentary-layer-lock/2-deactivate-locked/events.patterns new file mode 100644 index 00000000..a7191624 --- /dev/null +++ b/app/tests/momentary-layer-lock/2-deactivate-locked/events.patterns @@ -0,0 +1,3 @@ +s/.*hid_listener_keycode/kp/p +s/.*set_layer_state: layer_changed:/layer_changed:/p +s/.*mo_keymap_binding/mo/p \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/2-deactivate-locked/keycode_events.snapshot b/app/tests/momentary-layer-lock/2-deactivate-locked/keycode_events.snapshot new file mode 100644 index 00000000..f204ceaf --- /dev/null +++ b/app/tests/momentary-layer-lock/2-deactivate-locked/keycode_events.snapshot @@ -0,0 +1,10 @@ +mo_pressed: position 1 layer 1 +layer_changed: layer 1 state 1 +mo_released: position 1 layer 1 +kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 +mo_pressed: position 1 layer 1 +mo_released: position 1 layer 1 +layer_changed: layer 1 state 0 +kp_pressed: usage_page 0x07 keycode 0x27 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x27 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/momentary-layer-lock/2-deactivate-locked/native_posix_64.keymap b/app/tests/momentary-layer-lock/2-deactivate-locked/native_posix_64.keymap new file mode 100644 index 00000000..b9146f78 --- /dev/null +++ b/app/tests/momentary-layer-lock/2-deactivate-locked/native_posix_64.keymap @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,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,10) + + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/3-default-fallback/events.patterns b/app/tests/momentary-layer-lock/3-default-fallback/events.patterns new file mode 100644 index 00000000..a7191624 --- /dev/null +++ b/app/tests/momentary-layer-lock/3-default-fallback/events.patterns @@ -0,0 +1,3 @@ +s/.*hid_listener_keycode/kp/p +s/.*set_layer_state: layer_changed:/layer_changed:/p +s/.*mo_keymap_binding/mo/p \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/3-default-fallback/keycode_events.snapshot b/app/tests/momentary-layer-lock/3-default-fallback/keycode_events.snapshot new file mode 100644 index 00000000..196fd278 --- /dev/null +++ b/app/tests/momentary-layer-lock/3-default-fallback/keycode_events.snapshot @@ -0,0 +1,9 @@ +mo_pressed: position 1 layer 1 +layer_changed: layer 1 state 1 +mo_released: position 1 layer 1 +kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 +layer_changed: layer 1 state 0 +layer_changed: layer 0 state 1 +kp_pressed: usage_page 0x07 keycode 0x27 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x27 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/momentary-layer-lock/3-default-fallback/native_posix_64.keymap b/app/tests/momentary-layer-lock/3-default-fallback/native_posix_64.keymap new file mode 100644 index 00000000..c134cd19 --- /dev/null +++ b/app/tests/momentary-layer-lock/3-default-fallback/native_posix_64.keymap @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,1,10) + + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/4-relock/events.patterns b/app/tests/momentary-layer-lock/4-relock/events.patterns new file mode 100644 index 00000000..a7191624 --- /dev/null +++ b/app/tests/momentary-layer-lock/4-relock/events.patterns @@ -0,0 +1,3 @@ +s/.*hid_listener_keycode/kp/p +s/.*set_layer_state: layer_changed:/layer_changed:/p +s/.*mo_keymap_binding/mo/p \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/4-relock/keycode_events.snapshot b/app/tests/momentary-layer-lock/4-relock/keycode_events.snapshot new file mode 100644 index 00000000..50a99eb8 --- /dev/null +++ b/app/tests/momentary-layer-lock/4-relock/keycode_events.snapshot @@ -0,0 +1,9 @@ +mo_pressed: position 1 layer 1 +layer_changed: layer 1 state 1 +mo_released: position 1 layer 1 +kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 +mo_pressed: position 1 layer 1 +mo_released: position 1 layer 1 +kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/momentary-layer-lock/4-relock/native_posix_64.keymap b/app/tests/momentary-layer-lock/4-relock/native_posix_64.keymap new file mode 100644 index 00000000..6575ddc8 --- /dev/null +++ b/app/tests/momentary-layer-lock/4-relock/native_posix_64.keymap @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,1,10) + + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,1,10) + + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/5-custom/events.patterns b/app/tests/momentary-layer-lock/5-custom/events.patterns new file mode 100644 index 00000000..a7191624 --- /dev/null +++ b/app/tests/momentary-layer-lock/5-custom/events.patterns @@ -0,0 +1,3 @@ +s/.*hid_listener_keycode/kp/p +s/.*set_layer_state: layer_changed:/layer_changed:/p +s/.*mo_keymap_binding/mo/p \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/5-custom/keycode_events.snapshot b/app/tests/momentary-layer-lock/5-custom/keycode_events.snapshot new file mode 100644 index 00000000..4f7655be --- /dev/null +++ b/app/tests/momentary-layer-lock/5-custom/keycode_events.snapshot @@ -0,0 +1,5 @@ +mo_pressed: position 1 layer 1 +layer_changed: layer 1 state 1 +mo_released: position 1 layer 1 +kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/momentary-layer-lock/5-custom/native_posix_64.keymap b/app/tests/momentary-layer-lock/5-custom/native_posix_64.keymap new file mode 100644 index 00000000..bf942116 --- /dev/null +++ b/app/tests/momentary-layer-lock/5-custom/native_posix_64.keymap @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(1,1,10) + ZMK_MOCK_RELEASE(0,1,10) + + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/6-custom-fallback/events.patterns b/app/tests/momentary-layer-lock/6-custom-fallback/events.patterns new file mode 100644 index 00000000..a7191624 --- /dev/null +++ b/app/tests/momentary-layer-lock/6-custom-fallback/events.patterns @@ -0,0 +1,3 @@ +s/.*hid_listener_keycode/kp/p +s/.*set_layer_state: layer_changed:/layer_changed:/p +s/.*mo_keymap_binding/mo/p \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/6-custom-fallback/keycode_events.snapshot b/app/tests/momentary-layer-lock/6-custom-fallback/keycode_events.snapshot new file mode 100644 index 00000000..c96d1b4c --- /dev/null +++ b/app/tests/momentary-layer-lock/6-custom-fallback/keycode_events.snapshot @@ -0,0 +1,2 @@ +kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/momentary-layer-lock/6-custom-fallback/native_posix_64.keymap b/app/tests/momentary-layer-lock/6-custom-fallback/native_posix_64.keymap new file mode 100644 index 00000000..f5e698bb --- /dev/null +++ b/app/tests/momentary-layer-lock/6-custom-fallback/native_posix_64.keymap @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(1,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/momentary-layer-lock/behavior_keymap.dtsi b/app/tests/momentary-layer-lock/behavior_keymap.dtsi new file mode 100644 index 00000000..a3f6cf84 --- /dev/null +++ b/app/tests/momentary-layer-lock/behavior_keymap.dtsi @@ -0,0 +1,27 @@ +/ { + behaviors{ + kp_molock: kp_molock { + compatible = "zmk,behavior-momentary-layer-lock"; + label = "KP_MOLOCK"; + #binding-cells = <0>; + bindings = <&kp F>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &kp N0 &mo 1 + &molock &kp_molock>; + }; + + layer_1 { + bindings = < + &kp N1 &trans + &trans &trans>; + }; + }; +}; From 53d6c82e54aa60b7cdc7720f7b2ba04d6f6e042e Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 6 Nov 2023 14:04:44 -0800 Subject: [PATCH 4/8] add docs for molock --- docs/docs/behaviors/layers.md | 37 +++++++++++++++++++++++++++++++++++ docs/docs/config/behaviors.md | 24 +++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/docs/docs/behaviors/layers.md b/docs/docs/behaviors/layers.md index 7cfb4df7..525cd692 100644 --- a/docs/docs/behaviors/layers.md +++ b/docs/docs/behaviors/layers.md @@ -149,6 +149,43 @@ Example: It is possible to use "toggle layer" to have keys that raise and lower the layers as well. +## Momentary Layer Lock + +Even if you mostly use momentary layers, it's occasionally useful to permanently enable a layer without needing to hold anything down. Instead of creating a separate `&tog` or `&to` binding for each layer, you can use `&molock`. + +`&molock` causes all currently active momentary layers not to be deactivated when `&mo` is released. If no momentary layers are active, `&molock` triggers a fallback behavior, which by default returns to the base layer (`&to 0`), deactivating any locked momentary layers in the process. Alternatively, the user can deactivate a single locked momentary layer by pressing and releasing the corresponding `&mo` binding again. + +### Behavior Binding + +- Reference: `&molock` + +Example: + +```dts +&molock +``` + +### Configuration + +You can configure a different fallback behavior by overriding the `bindings` property of the built-in `&molock` behavior. For example, to return to layer 1 (instead of layer 0): + +```dts +&molock { + bindings = <&to 1>; +}; +``` + +You can also create any number of custom `&molock` behaviors by using `compatible = "zmk,behavior-momentary-layer-lock"` like so: + +```dts +// Presses F if triggered while no momentary layers are active +kp_molock: kp_molock { + compatible = "zmk,behavior-momentary-layer-lock"; + label = "KP_MOLOCK"; + bindings = <&kp F>; +}; +``` + ## Conditional Layers The "conditional layers" feature enables a particular layer when all layers in a specified set are active. diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index c31de5dd..afd9dd64 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -210,6 +210,30 @@ Definition files: With `compatible = "zmk,behavior-sensor-rotate-var"`, this behavior forwards the first parameter it receives to the parameter of the first behavior specified in `bindings`, and second parameter to the parameter of the second behavior. +## Momentary Layer Lock + +Creates a custom behavior that locks any active momentary layers or—if none are active—triggers the fallback behavior specified in `bindings`. + +See the [momentary layer lock](../behaviors/layers.md#momentary-layer-lock) documentation for more details and examples. + +### Devicetree + +Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-momentary-layer-lock.yaml) + +Applies to: `compatible = "zmk,behavior-momentary-layer-lock"` + +| Property | Type | Description | +| ---------------- | ------------- | ------------------------------------------------------- | +| `label` | string | Unique label for the node | +| `#binding-cells` | int | Must be `0` | +| `bindings` | phandle array | A behavior to trigger if no momentary layers are active | + +You can use the following nodes to tweak the default behaviors: + +| Node | Behavior | +| --------- | ------------------------------------------------------------------- | +| `&molock` | [Momentary Layer Lock](../behaviors/layers.md#momentary-layer-lock) | + ## Sticky Key Creates a custom behavior that triggers a behavior and keeps it pressed it until another key is pressed and released. From b9e4522331560b26d6310e09bf9e9ff6417ea039 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Tue, 7 Nov 2023 11:10:25 -0800 Subject: [PATCH 5/8] emdash too powerful Co-authored-by: Cem Aksoylar --- docs/docs/config/behaviors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index afd9dd64..fcbbbc89 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -212,7 +212,7 @@ With `compatible = "zmk,behavior-sensor-rotate-var"`, this behavior forwards the ## Momentary Layer Lock -Creates a custom behavior that locks any active momentary layers or—if none are active—triggers the fallback behavior specified in `bindings`. +Creates a custom behavior that locks any active momentary layers or triggers the fallback behavior specified in `bindings` if none are active. See the [momentary layer lock](../behaviors/layers.md#momentary-layer-lock) documentation for more details and examples. From 16791aa0a3c6c61d9aacbcb0da399dc57b7f031f Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Sun, 11 Feb 2024 17:31:47 -0800 Subject: [PATCH 6/8] rebase to work with Zephyr 3.5 --- app/dts/behaviors/momentary_layer_lock.dtsi | 1 - app/src/behaviors/behavior_momentary_layer_lock.c | 8 ++++---- docs/docs/config/behaviors.md | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/dts/behaviors/momentary_layer_lock.dtsi b/app/dts/behaviors/momentary_layer_lock.dtsi index e1d49201..539d23ce 100644 --- a/app/dts/behaviors/momentary_layer_lock.dtsi +++ b/app/dts/behaviors/momentary_layer_lock.dtsi @@ -8,7 +8,6 @@ behaviors { /omit-if-no-ref/ molock: behavior_momentary_layer_lock { compatible = "zmk,behavior-momentary-layer-lock"; - label = "MO_LOCK"; #binding-cells = <0>; bindings = <&to 0>; }; diff --git a/app/src/behaviors/behavior_momentary_layer_lock.c b/app/src/behaviors/behavior_momentary_layer_lock.c index cca22397..1147c266 100644 --- a/app/src/behaviors/behavior_momentary_layer_lock.c +++ b/app/src/behaviors/behavior_momentary_layer_lock.c @@ -73,7 +73,7 @@ static int mo_lock_keymap_binding_released(struct zmk_behavior_binding *binding, static struct behavior_mo_lock_config behavior_mo_lock_config_##n = { \ .fallback_binding = \ { \ - .behavior_dev = DT_PROP(DT_INST_PHANDLE_BY_IDX(n, bindings, 0), label), \ + .behavior_dev = DEVICE_DT_NAME(DT_INST_PHANDLE_BY_IDX(n, bindings, 0)), \ .param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(n, bindings, 0, param1), (0), \ (DT_INST_PHA_BY_IDX(n, bindings, 0, param1))), \ .param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(n, bindings, 0, param2), (0), \ @@ -82,9 +82,9 @@ static int mo_lock_keymap_binding_released(struct zmk_behavior_binding *binding, }; \ static struct behavior_mo_lock_data behavior_mo_lock_data_##n; \ \ - DEVICE_DT_INST_DEFINE(n, behavior_mo_lock_init, NULL, &behavior_mo_lock_data_##n, \ - &behavior_mo_lock_config_##n, APPLICATION, \ - CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_lock_driver_api); + BEHAVIOR_DT_INST_DEFINE(n, behavior_mo_lock_init, NULL, &behavior_mo_lock_data_##n, \ + &behavior_mo_lock_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_lock_driver_api); static const struct behavior_driver_api behavior_mo_lock_driver_api = { .binding_pressed = mo_lock_keymap_binding_pressed, diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index fcbbbc89..0338574c 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -224,7 +224,6 @@ Applies to: `compatible = "zmk,behavior-momentary-layer-lock"` | Property | Type | Description | | ---------------- | ------------- | ------------------------------------------------------- | -| `label` | string | Unique label for the node | | `#binding-cells` | int | Must be `0` | | `bindings` | phandle array | A behavior to trigger if no momentary layers are active | From df496393b2f6d654905b3215c00d515937ddc913 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 12 Feb 2024 17:31:39 -0800 Subject: [PATCH 7/8] expand docs and give example --- docs/docs/behaviors/layers.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/docs/behaviors/layers.md b/docs/docs/behaviors/layers.md index 525cd692..71aee959 100644 --- a/docs/docs/behaviors/layers.md +++ b/docs/docs/behaviors/layers.md @@ -151,9 +151,11 @@ It is possible to use "toggle layer" to have keys that raise and lower the layer ## Momentary Layer Lock -Even if you mostly use momentary layers, it's occasionally useful to permanently enable a layer without needing to hold anything down. Instead of creating a separate `&tog` or `&to` binding for each layer, you can use `&molock`. +Even if you mostly use [momentary layers](#momentary-layer) instead of `&to` or `&tog`, it's occasionally useful to permanently enable a layer without needing to hold anything down. Instead of creating an additional `&tog` or `&to` binding for each such layer, you can use `&molock`. -`&molock` causes all currently active momentary layers not to be deactivated when `&mo` is released. If no momentary layers are active, `&molock` triggers a fallback behavior, which by default returns to the base layer (`&to 0`), deactivating any locked momentary layers in the process. Alternatively, the user can deactivate a single locked momentary layer by pressing and releasing the corresponding `&mo` binding again. +If `&molock` is pressed while any number of `&mo` bindings are being held, those momentary layers will not be deactivated when the corresponding `&mo` key is released. As a result, those momentary layers become "locked" until that `&mo` key is pressed and released a second time or the layer becomes deactivated by some other means (e.g. a `&tog` binding for that layer or a `&to` binding for a lower one). + +If `&molock` is pressed while no `&mo` bindings are being held, it triggers a user-configurable fallback behavior. The default fallback behavior returns to the base layer (`&to 0`), deactivating any locked momentary layers in the process. ### Behavior Binding @@ -165,6 +167,33 @@ Example: &molock ``` +Lock a symbol layer: + +```dts +#define BASE 0 +#define SYMS 1 + +/ { + keymap { + compatible = "zmk,keymap"; + + base_layer { + bindings = <&mo SYMS &kp Z &kp M &kp K >; + }; + symbol_layer { + bindings = <&trans &kp PLUS &kp MINUS &molock>; + }; + }; +}; +``` + +:::info +Holding down the leftmost key (`&mo SYMS`), then pressing and releasing the rightmost key (`&molock`), will lock the symbol layer. Even after releasing the leftmost key, the symbol layer remains active. + +To return to the base layer, press and release either the leftmost key (triggering the `&mo SYMS` behavior a second time) or the rightmost key (triggering the default fallback behavior for `&molock`). + +::: + ### Configuration You can configure a different fallback behavior by overriding the `bindings` property of the built-in `&molock` behavior. For example, to return to layer 1 (instead of layer 0): From b73904c624655c9ab385afdb6a3cb1b0a7a36df2 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 12 Feb 2024 18:05:37 -0800 Subject: [PATCH 8/8] apply Cem's suggestions Co-authored-by: Cem Aksoylar --- docs/docs/behaviors/layers.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/docs/behaviors/layers.md b/docs/docs/behaviors/layers.md index 71aee959..182972c8 100644 --- a/docs/docs/behaviors/layers.md +++ b/docs/docs/behaviors/layers.md @@ -153,7 +153,7 @@ It is possible to use "toggle layer" to have keys that raise and lower the layer Even if you mostly use [momentary layers](#momentary-layer) instead of `&to` or `&tog`, it's occasionally useful to permanently enable a layer without needing to hold anything down. Instead of creating an additional `&tog` or `&to` binding for each such layer, you can use `&molock`. -If `&molock` is pressed while any number of `&mo` bindings are being held, those momentary layers will not be deactivated when the corresponding `&mo` key is released. As a result, those momentary layers become "locked" until that `&mo` key is pressed and released a second time or the layer becomes deactivated by some other means (e.g. a `&tog` binding for that layer or a `&to` binding for a lower one). +If `&molock` is pressed while any number of `&mo` bindings are being held, those momentary layers will not be deactivated when the corresponding `&mo` key is released. As a result, those momentary layers become "locked" until that `&mo` key is pressed and released a second time or the layer becomes deactivated by some other means (e.g. a `&tog` binding for that layer or a `&to` binding for any other one). If `&molock` is pressed while no `&mo` bindings are being held, it triggers a user-configurable fallback behavior. The default fallback behavior returns to the base layer (`&to 0`), deactivating any locked momentary layers in the process. @@ -187,13 +187,10 @@ Lock a symbol layer: }; ``` -:::info Holding down the leftmost key (`&mo SYMS`), then pressing and releasing the rightmost key (`&molock`), will lock the symbol layer. Even after releasing the leftmost key, the symbol layer remains active. To return to the base layer, press and release either the leftmost key (triggering the `&mo SYMS` behavior a second time) or the rightmost key (triggering the default fallback behavior for `&molock`). -::: - ### Configuration You can configure a different fallback behavior by overriding the `bindings` property of the built-in `&molock` behavior. For example, to return to layer 1 (instead of layer 0): @@ -210,7 +207,6 @@ You can also create any number of custom `&molock` behaviors by using `compatibl // Presses F if triggered while no momentary layers are active kp_molock: kp_molock { compatible = "zmk,behavior-momentary-layer-lock"; - label = "KP_MOLOCK"; bindings = <&kp F>; }; ```