diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 6ef00311027..f0b5d82ab48 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 23f2fee2806..2409cf1e370 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 00000000000..539d23ceba1 --- /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"; + #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 new file mode 100644 index 00000000000..dc7138fb7ea --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml @@ -0,0 +1,13 @@ +# Copyright (c) 2023 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Momentary layer on press/release behavior + +compatible: "zmk,behavior-momentary-layer-lock" + +include: zero_param.yaml + +properties: + bindings: + type: phandle-array + required: true diff --git a/app/include/zmk/momentary_layer.h b/app/include/zmk/momentary_layer.h new file mode 100644 index 00000000000..7511c0b1d3a --- /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 0c86e605b55..9f708f7b2d6 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 00000000000..1147c266796 --- /dev/null +++ b/app/src/behaviors/behavior_momentary_layer_lock.c @@ -0,0 +1,96 @@ +/* + * 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) + +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) { + 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 int mo_lock_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("%d molock released", event.position); + + 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 = 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), \ + (DT_INST_PHA_BY_IDX(n, bindings, 0, param))), \ + }, \ + }; \ + static struct behavior_mo_lock_data behavior_mo_lock_data_##n; \ + \ + 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, + .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 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 00000000000..a7191624e53 --- /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 00000000000..4f7655be0e2 --- /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 00000000000..d53ebd7df46 --- /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 00000000000..a7191624e53 --- /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 00000000000..f204ceaf1f4 --- /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 00000000000..b9146f7844d --- /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 00000000000..a7191624e53 --- /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 00000000000..196fd278543 --- /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 00000000000..c134cd195f9 --- /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 00000000000..a7191624e53 --- /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 00000000000..50a99eb8173 --- /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 00000000000..6575ddc8805 --- /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 00000000000..a7191624e53 --- /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 00000000000..4f7655be0e2 --- /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 00000000000..bf942116292 --- /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 00000000000..a7191624e53 --- /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 00000000000..c96d1b4cd9a --- /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 00000000000..f5e698bbf93 --- /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 00000000000..a3f6cf84a1e --- /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>; + }; + }; +}; diff --git a/docs/docs/behaviors/layers.md b/docs/docs/behaviors/layers.md index 7cfb4df7e00..182972c825b 100644 --- a/docs/docs/behaviors/layers.md +++ b/docs/docs/behaviors/layers.md @@ -149,6 +149,68 @@ 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](#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 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. + +### Behavior Binding + +- Reference: `&molock` + +Example: + +```dts +&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>; + }; + }; +}; +``` + +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): + +```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"; + 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 c31de5dd0a4..0338574c639 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -210,6 +210,29 @@ 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 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. + +### 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 | +| ---------------- | ------------- | ------------------------------------------------------- | +| `#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.