From c8f9927ecaa1a3b434a6d8c5bd23424bfbe6912c Mon Sep 17 00:00:00 2001 From: Nicolas Munnich Date: Wed, 18 Dec 2024 13:52:32 +0100 Subject: [PATCH 1/3] feat: Add parameterised mod morph --- app/dts/behaviors.dtsi | 1 + app/dts/behaviors/mod_morph.dtsi | 24 +++++++ .../bindings/behaviors/mod_morph_base.yaml | 13 ++++ .../zmk,behavior-mod-morph-param.yaml | 13 ++++ .../behaviors/zmk,behavior-mod-morph.yaml | 13 +--- app/include/dt-bindings/zmk/behaviors.h | 5 +- app/src/behaviors/behavior_mod_morph.c | 56 +++++++++++------ .../4a-morph-params-singles/events.patterns | 8 +++ .../keycode_events.snapshot | 63 +++++++++++++++++++ .../native_posix_64.keymap | 51 +++++++++++++++ .../4b-morph-params-doubles/events.patterns | 8 +++ .../keycode_events.snapshot | 59 +++++++++++++++++ .../native_posix_64.keymap | 49 +++++++++++++++ 13 files changed, 330 insertions(+), 33 deletions(-) create mode 100644 app/dts/behaviors/mod_morph.dtsi create mode 100644 app/dts/bindings/behaviors/mod_morph_base.yaml create mode 100644 app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml create mode 100644 app/tests/mod-morph/4a-morph-params-singles/events.patterns create mode 100644 app/tests/mod-morph/4a-morph-params-singles/keycode_events.snapshot create mode 100644 app/tests/mod-morph/4a-morph-params-singles/native_posix_64.keymap create mode 100644 app/tests/mod-morph/4b-morph-params-doubles/events.patterns create mode 100644 app/tests/mod-morph/4b-morph-params-doubles/keycode_events.snapshot create mode 100644 app/tests/mod-morph/4b-morph-params-doubles/native_posix_64.keymap diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index 653b085d5c5..a25572cb63d 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/app/dts/behaviors/mod_morph.dtsi b/app/dts/behaviors/mod_morph.dtsi new file mode 100644 index 00000000000..ad131f4b6ff --- /dev/null +++ b/app/dts/behaviors/mod_morph.dtsi @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +/ { + behaviors { +#if ZMK_BEHAVIOR_OMIT(MODMORPH) + /omit-if-no-ref/ +#endif + mm: mod_morph_kp { + compatible = "zmk,behavior-mod-morph-param"; + #binding-cells = <2>; + bindings = <&kp PLACEHOLDER>, <&kp PLACEHOLDER>; + display-name = "Mod-Morph"; + mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>; + binding-params = ; + }; + }; +}; \ No newline at end of file diff --git a/app/dts/bindings/behaviors/mod_morph_base.yaml b/app/dts/bindings/behaviors/mod_morph_base.yaml new file mode 100644 index 00000000000..e60e5ec67e5 --- /dev/null +++ b/app/dts/bindings/behaviors/mod_morph_base.yaml @@ -0,0 +1,13 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +properties: + bindings: + type: phandle-array + required: true + mods: + type: int + required: true + keep-mods: + type: int + required: false diff --git a/app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml b/app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml new file mode 100644 index 00000000000..ad506a3583d --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml @@ -0,0 +1,13 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Mod Morph Behavior + +compatible: "zmk,behavior-mod-morph-param" + +include: [two_param.yaml, mod_morph_base.yaml] + +properties: + binding-params: + type: array + required: true diff --git a/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml b/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml index 20235d045f7..5a8c4ded5fe 100644 --- a/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml +++ b/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml @@ -5,15 +5,4 @@ description: Mod Morph Behavior compatible: "zmk,behavior-mod-morph" -include: zero_param.yaml - -properties: - bindings: - type: phandle-array - required: true - mods: - type: int - required: true - keep-mods: - type: int - required: false +include: [zero_param.yaml, mod_morph_base.yaml] diff --git a/app/include/dt-bindings/zmk/behaviors.h b/app/include/dt-bindings/zmk/behaviors.h index abfb91f48c3..e2fc9b0cf8e 100644 --- a/app/include/dt-bindings/zmk/behaviors.h +++ b/app/include/dt-bindings/zmk/behaviors.h @@ -6,4 +6,7 @@ #define ZMK_BEHAVIOR_OMIT(_name) \ !(defined(ZMK_BEHAVIORS_KEEP_##_name) || \ - (defined(ZMK_BEHAVIORS_KEEP_ALL) && !defined(ZMK_BEHAVIORS_OMIT_##_name))) \ No newline at end of file + (defined(ZMK_BEHAVIORS_KEEP_ALL) && !defined(ZMK_BEHAVIORS_OMIT_##_name))) + +#define PLACEHOLDER 0 +#define BINDING_PARAM(arg1, arg2) ((arg1 << 2) | arg2) \ No newline at end of file diff --git a/app/src/behaviors/behavior_mod_morph.c b/app/src/behaviors/behavior_mod_morph.c index 6698f24886f..a9561468238 100644 --- a/app/src/behaviors/behavior_mod_morph.c +++ b/app/src/behaviors/behavior_mod_morph.c @@ -4,8 +4,6 @@ * SPDX-License-Identifier: MIT */ -#define DT_DRV_COMPAT zmk_behavior_mod_morph - #include #include #include @@ -21,13 +19,15 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); -#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +#if DT_HAS_COMPAT_STATUS_OKAY(zmk_behavior_mod_morph) || \ + DT_HAS_COMPAT_STATUS_OKAY(zmk_behavior_mod_morph_param) struct behavior_mod_morph_config { struct zmk_behavior_binding normal_binding; struct zmk_behavior_binding morph_binding; zmk_mod_flags_t mods; zmk_mod_flags_t masked_mods; + uint8_t binding_params; }; struct behavior_mod_morph_data { @@ -39,6 +39,7 @@ static int on_mod_morph_binding_pressed(struct zmk_behavior_binding *binding, const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev); const struct behavior_mod_morph_config *cfg = dev->config; struct behavior_mod_morph_data *data = dev->data; + uint8_t map = cfg->binding_params; if (data->pressed_binding != NULL) { LOG_ERR("Can't press the same mod-morph twice"); @@ -50,7 +51,12 @@ static int on_mod_morph_binding_pressed(struct zmk_behavior_binding *binding, data->pressed_binding = (struct zmk_behavior_binding *)&cfg->morph_binding; } else { data->pressed_binding = (struct zmk_behavior_binding *)&cfg->normal_binding; + map = map >> 4; } + if (map & 0b1100) + data->pressed_binding->param1 = (map & 0b0100) ? binding->param1 : binding->param2; + if (map & 0b0011) + data->pressed_binding->param2 = (map & 0b0001) ? binding->param1 : binding->param2; return zmk_behavior_invoke_binding(data->pressed_binding, event, true); } @@ -84,26 +90,36 @@ static int behavior_mod_morph_init(const struct device *dev) { return 0; } #define _TRANSFORM_ENTRY(idx, node) \ { \ - .behavior_dev = DEVICE_DT_NAME(DT_INST_PHANDLE_BY_IDX(node, bindings, idx)), \ - .param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), \ - (DT_INST_PHA_BY_IDX(node, bindings, idx, param1))), \ - .param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), \ - (DT_INST_PHA_BY_IDX(node, bindings, idx, param2))), \ + .behavior_dev = DEVICE_DT_NAME(DT_PHANDLE_BY_IDX(node, bindings, idx)), \ + .param1 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), \ + (DT_PHA_BY_IDX(node, bindings, idx, param1))), \ + .param2 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), \ + (DT_PHA_BY_IDX(node, bindings, idx, param2))), \ } -#define KP_INST(n) \ - static struct behavior_mod_morph_config behavior_mod_morph_config_##n = { \ - .normal_binding = _TRANSFORM_ENTRY(0, n), \ - .morph_binding = _TRANSFORM_ENTRY(1, n), \ - .mods = DT_INST_PROP(n, mods), \ - .masked_mods = COND_CODE_0(DT_INST_NODE_HAS_PROP(n, keep_mods), (DT_INST_PROP(n, mods)), \ - (DT_INST_PROP(n, mods) & ~DT_INST_PROP(n, keep_mods))), \ +#define KP_INST(inst) \ + BUILD_ASSERT((COND_CODE_0(DT_NODE_HAS_PROP(inst, binding_params), (0), \ + ((((DT_PROP_BY_IDX(inst, binding_params, 0) >> 2) & \ + (DT_PROP_BY_IDX(inst, binding_params, 0))) | \ + ((DT_PROP_BY_IDX(inst, binding_params, 1) >> 2) & \ + (DT_PROP_BY_IDX(inst, binding_params, 1)))))) == 0), \ + "Invalid binding parameters"); \ + static struct behavior_mod_morph_config behavior_mod_morph_config_##inst = { \ + .normal_binding = _TRANSFORM_ENTRY(0, inst), \ + .morph_binding = _TRANSFORM_ENTRY(1, inst), \ + .mods = DT_PROP(inst, mods), \ + .masked_mods = COND_CODE_0(DT_NODE_HAS_PROP(inst, keep_mods), (DT_PROP(inst, mods)), \ + (DT_PROP(inst, mods) & ~DT_PROP(inst, keep_mods))), \ + .binding_params = (COND_CODE_0(DT_NODE_HAS_PROP(inst, binding_params), (0), \ + ((DT_PROP_BY_IDX(inst, binding_params, 0) << 4) | \ + (DT_PROP_BY_IDX(inst, binding_params, 1))))), \ }; \ - static struct behavior_mod_morph_data behavior_mod_morph_data_##n = {}; \ - BEHAVIOR_DT_INST_DEFINE(n, behavior_mod_morph_init, NULL, &behavior_mod_morph_data_##n, \ - &behavior_mod_morph_config_##n, POST_KERNEL, \ - CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mod_morph_driver_api); + static struct behavior_mod_morph_data behavior_mod_morph_data_##inst = {}; \ + BEHAVIOR_DT_DEFINE(inst, behavior_mod_morph_init, NULL, &behavior_mod_morph_data_##inst, \ + &behavior_mod_morph_config_##inst, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mod_morph_driver_api); -DT_INST_FOREACH_STATUS_OKAY(KP_INST) +DT_FOREACH_STATUS_OKAY(zmk_behavior_mod_morph, KP_INST) +DT_FOREACH_STATUS_OKAY(zmk_behavior_mod_morph_param, KP_INST) #endif diff --git a/app/tests/mod-morph/4a-morph-params-singles/events.patterns b/app/tests/mod-morph/4a-morph-params-singles/events.patterns new file mode 100644 index 00000000000..f1a41fcf2c3 --- /dev/null +++ b/app/tests/mod-morph/4a-morph-params-singles/events.patterns @@ -0,0 +1,8 @@ +s/.*hid_listener_keycode_pressed.*keycode/pressed: keycode/p +s/.*hid_listener_keycode_released.*keycode/released: keycode/p +s/.*hid_register_mod.*Modifiers set to /reg explicit: Modifiers set to /p +s/.*hid_unregister_mod.*Modifiers set to /unreg explicit: Modifiers set to /p +s/.*hid_implicit_modifiers_press.*Modifiers set to /reg implicit: Modifiers set to /p +s/.*hid_implicit_modifiers_release.*Modifiers set to /unreg implicit: Modifiers set to /p +s/.*hid_masked_modifiers_set.*Modifiers set to /mask mods: Modifiers set to /p +s/.*hid_masked_modifiers_clear.*Modifiers set to /unmask mods: Modifiers set to /p diff --git a/app/tests/mod-morph/4a-morph-params-singles/keycode_events.snapshot b/app/tests/mod-morph/4a-morph-params-singles/keycode_events.snapshot new file mode 100644 index 00000000000..43a571392de --- /dev/null +++ b/app/tests/mod-morph/4a-morph-params-singles/keycode_events.snapshot @@ -0,0 +1,63 @@ +pressed: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +released: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x00 +pressed: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x02 +reg implicit: Modifiers set to 0x02 +mask mods: Modifiers set to 0x00 +pressed: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +released: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x02 +released: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x00 +unreg implicit: Modifiers set to 0x00 +pressed: keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +released: keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x00 +pressed: keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x01 +reg implicit: Modifiers set to 0x01 +mask mods: Modifiers set to 0x00 +pressed: keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +released: keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x01 +unmask mods: Modifiers set to 0x01 +released: keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x00 +unreg implicit: Modifiers set to 0x00 +pressed: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x02 +reg implicit: Modifiers set to 0x02 +mask mods: Modifiers set to 0x00 +pressed: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +released: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x02 +unmask mods: Modifiers set to 0x02 +pressed: keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x03 +reg implicit: Modifiers set to 0x03 +mask mods: Modifiers set to 0x02 +mask mods: Modifiers set to 0x01 +pressed: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x01 +released: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x01 +unmask mods: Modifiers set to 0x03 +unmask mods: Modifiers set to 0x03 +released: keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x02 +unreg implicit: Modifiers set to 0x02 +released: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x00 +unreg implicit: Modifiers set to 0x00 diff --git a/app/tests/mod-morph/4a-morph-params-singles/native_posix_64.keymap b/app/tests/mod-morph/4a-morph-params-singles/native_posix_64.keymap new file mode 100644 index 00000000000..04c0d6ced23 --- /dev/null +++ b/app/tests/mod-morph/4a-morph-params-singles/native_posix_64.keymap @@ -0,0 +1,51 @@ +#include +#include +#include + +/ { + behaviors { + mm1: mod_morph_param1 { + compatible = "zmk,behavior-mod-morph-param"; + #binding-cells = <2>; + bindings = <&mm C PLACEHOLDER>, <&mm D PLACEHOLDER>; + mods = <(MOD_LCTL|MOD_RCTL)>; + binding-params = ; + }; + }; + + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + &kp LEFT_CONTROL &mm1 A B + &kp LEFT_SHIFT &mm A B + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(1,1,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(1,1,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,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,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,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,10) + ZMK_MOCK_RELEASE(1,0,10) + >; +}; diff --git a/app/tests/mod-morph/4b-morph-params-doubles/events.patterns b/app/tests/mod-morph/4b-morph-params-doubles/events.patterns new file mode 100644 index 00000000000..f1a41fcf2c3 --- /dev/null +++ b/app/tests/mod-morph/4b-morph-params-doubles/events.patterns @@ -0,0 +1,8 @@ +s/.*hid_listener_keycode_pressed.*keycode/pressed: keycode/p +s/.*hid_listener_keycode_released.*keycode/released: keycode/p +s/.*hid_register_mod.*Modifiers set to /reg explicit: Modifiers set to /p +s/.*hid_unregister_mod.*Modifiers set to /unreg explicit: Modifiers set to /p +s/.*hid_implicit_modifiers_press.*Modifiers set to /reg implicit: Modifiers set to /p +s/.*hid_implicit_modifiers_release.*Modifiers set to /unreg implicit: Modifiers set to /p +s/.*hid_masked_modifiers_set.*Modifiers set to /mask mods: Modifiers set to /p +s/.*hid_masked_modifiers_clear.*Modifiers set to /unmask mods: Modifiers set to /p diff --git a/app/tests/mod-morph/4b-morph-params-doubles/keycode_events.snapshot b/app/tests/mod-morph/4b-morph-params-doubles/keycode_events.snapshot new file mode 100644 index 00000000000..885f1850e84 --- /dev/null +++ b/app/tests/mod-morph/4b-morph-params-doubles/keycode_events.snapshot @@ -0,0 +1,59 @@ +pressed: keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +released: keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x00 +pressed: keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x01 +reg implicit: Modifiers set to 0x01 +mask mods: Modifiers set to 0x00 +pressed: keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +released: keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x01 +unmask mods: Modifiers set to 0x01 +released: keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x00 +unreg implicit: Modifiers set to 0x00 +pressed: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x02 +reg implicit: Modifiers set to 0x02 +mask mods: Modifiers set to 0x00 +pressed: keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +released: keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x02 +unmask mods: Modifiers set to 0x02 +pressed: keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x03 +reg implicit: Modifiers set to 0x03 +mask mods: Modifiers set to 0x02 +mask mods: Modifiers set to 0x01 +pressed: keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x01 +released: keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x01 +unmask mods: Modifiers set to 0x03 +unmask mods: Modifiers set to 0x03 +released: keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x02 +unreg implicit: Modifiers set to 0x02 +released: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x00 +unreg implicit: Modifiers set to 0x00 +pressed: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x02 +reg implicit: Modifiers set to 0x02 +mask mods: Modifiers set to 0x00 +pressed: keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +released: keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x02 +unmask mods: Modifiers set to 0x02 +released: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x00 +unreg implicit: Modifiers set to 0x00 diff --git a/app/tests/mod-morph/4b-morph-params-doubles/native_posix_64.keymap b/app/tests/mod-morph/4b-morph-params-doubles/native_posix_64.keymap new file mode 100644 index 00000000000..3cdef0aaa4a --- /dev/null +++ b/app/tests/mod-morph/4b-morph-params-doubles/native_posix_64.keymap @@ -0,0 +1,49 @@ +#include +#include +#include + +/ { + behaviors { + mm1: mod_morph_param1 { + compatible = "zmk,behavior-mod-morph-param"; + #binding-cells = <2>; + bindings = <&mm PLACEHOLDER PLACEHOLDER>, <&mm PLACEHOLDER PLACEHOLDER>; + mods = <(MOD_LCTL|MOD_RCTL)>; + binding-params = ; + }; + }; + + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + &kp LEFT_CONTROL &mm1 E F + &kp LEFT_SHIFT &mm A B + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,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,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,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,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(1,0,10) + >; +}; From 05e797cd01d3477101042f3c7b08c5bed9bd72fa Mon Sep 17 00:00:00 2001 From: Nicolas Munnich Date: Wed, 18 Dec 2024 22:35:18 +0100 Subject: [PATCH 2/3] feat: Add parameterised tap dance --- app/dts/behaviors.dtsi | 3 +- app/dts/behaviors/tap_dance.dtsi | 23 +++++ .../bindings/behaviors/binding_params.yaml | 6 ++ .../bindings/behaviors/tap_dance_base.yaml | 10 +++ .../zmk,behavior-mod-morph-param.yaml | 7 +- .../zmk,behavior-tap-dance-param.yaml | 8 ++ .../behaviors/zmk,behavior-tap-dance.yaml | 10 +-- app/src/behaviors/behavior_tap_dance.c | 60 ++++++++----- app/tests/tap-dance/7-params/events.patterns | 2 + .../7-params/keycode_events.snapshot | 63 +++++++++++++ .../tap-dance/7-params/native_posix_64.keymap | 89 +++++++++++++++++++ 11 files changed, 243 insertions(+), 38 deletions(-) create mode 100644 app/dts/behaviors/tap_dance.dtsi create mode 100644 app/dts/bindings/behaviors/binding_params.yaml create mode 100644 app/dts/bindings/behaviors/tap_dance_base.yaml create mode 100644 app/dts/bindings/behaviors/zmk,behavior-tap-dance-param.yaml create mode 100644 app/tests/tap-dance/7-params/events.patterns create mode 100644 app/tests/tap-dance/7-params/keycode_events.snapshot create mode 100644 app/tests/tap-dance/7-params/native_posix_64.keymap diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index a25572cb63d..47b771757bf 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -9,8 +9,9 @@ #include #include #include -#include #include +#include +#include #include #include #include diff --git a/app/dts/behaviors/tap_dance.dtsi b/app/dts/behaviors/tap_dance.dtsi new file mode 100644 index 00000000000..285b3ab5365 --- /dev/null +++ b/app/dts/behaviors/tap_dance.dtsi @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + behaviors { +#if ZMK_BEHAVIOR_OMIT(TAPDANCE) + /omit-if-no-ref/ +#endif + td: tap_dance_kp { + compatible = "zmk,behavior-tap-dance-param"; + #binding-cells = <2>; + bindings = <&kp PLACEHOLDER>, <&kp PLACEHOLDER>; + display-name = "Tap-Dance"; + tapping-term-ms = <200>; + binding-params = ; + }; + }; +}; \ No newline at end of file diff --git a/app/dts/bindings/behaviors/binding_params.yaml b/app/dts/bindings/behaviors/binding_params.yaml new file mode 100644 index 00000000000..b7e81a40328 --- /dev/null +++ b/app/dts/bindings/behaviors/binding_params.yaml @@ -0,0 +1,6 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +properties: + binding-params: + type: array diff --git a/app/dts/bindings/behaviors/tap_dance_base.yaml b/app/dts/bindings/behaviors/tap_dance_base.yaml new file mode 100644 index 00000000000..26a1db48efe --- /dev/null +++ b/app/dts/bindings/behaviors/tap_dance_base.yaml @@ -0,0 +1,10 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +properties: + bindings: + type: phandle-array + required: true + tapping-term-ms: + type: int + default: 200 diff --git a/app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml b/app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml index ad506a3583d..a046a023168 100644 --- a/app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml +++ b/app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml @@ -5,9 +5,4 @@ description: Mod Morph Behavior compatible: "zmk,behavior-mod-morph-param" -include: [two_param.yaml, mod_morph_base.yaml] - -properties: - binding-params: - type: array - required: true +include: [two_param.yaml, mod_morph_base.yaml, binding_params.yaml] diff --git a/app/dts/bindings/behaviors/zmk,behavior-tap-dance-param.yaml b/app/dts/bindings/behaviors/zmk,behavior-tap-dance-param.yaml new file mode 100644 index 00000000000..666080d66b8 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-tap-dance-param.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Tap Dance Behavior + +compatible: "zmk,behavior-tap-dance-param" + +include: [two_param.yaml, tap_dance_base.yaml, binding_params.yaml] diff --git a/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml b/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml index 82e1517dac9..02559579554 100644 --- a/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml +++ b/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml @@ -5,12 +5,4 @@ description: Tap Dance Behavior compatible: "zmk,behavior-tap-dance" -include: zero_param.yaml - -properties: - bindings: - type: phandle-array - required: true - tapping-term-ms: - type: int - default: 200 +include: [zero_param.yaml, tap_dance_base.yaml] diff --git a/app/src/behaviors/behavior_tap_dance.c b/app/src/behaviors/behavior_tap_dance.c index 5423f93f7dc..0fe2e04a697 100644 --- a/app/src/behaviors/behavior_tap_dance.c +++ b/app/src/behaviors/behavior_tap_dance.c @@ -4,8 +4,6 @@ * SPDX-License-Identifier: MIT */ -#define DT_DRV_COMPAT zmk_behavior_tap_dance - #include #include #include @@ -19,7 +17,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); -#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +#if (DT_HAS_COMPAT_STATUS_OKAY(zmk_behavior_tap_dance) || \ + DT_HAS_COMPAT_STATUS_OKAY(zmk_behavior_tap_dance_param)) #define ZMK_BHV_TAP_DANCE_MAX_HELD 10 @@ -29,6 +28,7 @@ struct behavior_tap_dance_config { uint32_t tapping_term_ms; size_t behavior_count; struct zmk_behavior_binding *behaviors; + uint8_t binding_params[]; }; struct active_tap_dance { @@ -64,7 +64,7 @@ static struct active_tap_dance *find_tap_dance(uint32_t position) { static int new_tap_dance(struct zmk_behavior_binding_event *event, const struct behavior_tap_dance_config *config, - struct active_tap_dance **tap_dance) { + struct active_tap_dance **tap_dance, uint32_t param1, uint32_t param2) { for (int i = 0; i < ZMK_BHV_TAP_DANCE_MAX_HELD; i++) { struct active_tap_dance *const ref_dance = &active_tap_dances[i]; if (ref_dance->position == ZMK_BHV_TAP_DANCE_POSITION_FREE) { @@ -73,9 +73,12 @@ static int new_tap_dance(struct zmk_behavior_binding_event *event, #if IS_ENABLED(CONFIG_ZMK_SPLIT) ref_dance->source = event->source; #endif + ref_dance->param1 = param1; + ref_dance->param2 = param2; + ref_dance->is_pressed = true; ref_dance->config = config; + ref_dance->release_at = 0; - ref_dance->is_pressed = true; ref_dance->timer_started = true; ref_dance->timer_cancelled = false; ref_dance->tap_dance_decided = false; @@ -111,7 +114,8 @@ static void reset_timer(struct active_tap_dance *tap_dance, static inline int press_tap_dance_behavior(struct active_tap_dance *tap_dance, int64_t timestamp) { tap_dance->tap_dance_decided = true; - struct zmk_behavior_binding binding = tap_dance->config->behaviors[tap_dance->counter - 1]; + struct zmk_behavior_binding *binding = &(tap_dance->config->behaviors[tap_dance->counter - 1]); + uint8_t param_map = tap_dance->config->binding_params[tap_dance->counter - 1]; struct zmk_behavior_binding_event event = { .position = tap_dance->position, .timestamp = timestamp, @@ -119,12 +123,16 @@ static inline int press_tap_dance_behavior(struct active_tap_dance *tap_dance, i .source = tap_dance->source, #endif }; - return zmk_behavior_invoke_binding(&binding, event, true); + if (param_map & 0b1100) + binding->param1 = (param_map & 0b0100) ? tap_dance->param1 : tap_dance->param2; + if (param_map & 0b0011) + binding->param2 = (param_map & 0b0001) ? tap_dance->param1 : tap_dance->param2; + return zmk_behavior_invoke_binding(binding, event, true); } static inline int release_tap_dance_behavior(struct active_tap_dance *tap_dance, int64_t timestamp) { - struct zmk_behavior_binding binding = tap_dance->config->behaviors[tap_dance->counter - 1]; + struct zmk_behavior_binding *binding = &(tap_dance->config->behaviors[tap_dance->counter - 1]); struct zmk_behavior_binding_event event = { .position = tap_dance->position, .timestamp = timestamp, @@ -133,7 +141,7 @@ static inline int release_tap_dance_behavior(struct active_tap_dance *tap_dance, #endif }; clear_tap_dance(tap_dance); - return zmk_behavior_invoke_binding(&binding, event, false); + return zmk_behavior_invoke_binding(binding, event, false); } static int on_tap_dance_binding_pressed(struct zmk_behavior_binding *binding, @@ -143,7 +151,7 @@ static int on_tap_dance_binding_pressed(struct zmk_behavior_binding *binding, struct active_tap_dance *tap_dance; tap_dance = find_tap_dance(event.position); if (tap_dance == NULL) { - if (new_tap_dance(&event, cfg, &tap_dance) == -ENOMEM) { + if (new_tap_dance(&event, cfg, &tap_dance, binding->param1, binding->param2) == -ENOMEM) { LOG_ERR("Unable to create new tap dance. Insufficient space in active_tap_dances[]."); return ZMK_BEHAVIOR_OPAQUE; } @@ -258,20 +266,28 @@ static int behavior_tap_dance_init(const struct device *dev) { #define _TRANSFORM_ENTRY(idx, node) ZMK_KEYMAP_EXTRACT_BINDING(idx, node) #define TRANSFORMED_BINDINGS(node) \ - {LISTIFY(DT_INST_PROP_LEN(node, bindings), _TRANSFORM_ENTRY, (, ), DT_DRV_INST(node))} + {LISTIFY(DT_PROP_LEN(node, bindings), _TRANSFORM_ENTRY, (, ), node)} + +#define BREAK_ITEM(i, inst) DT_PROP_BY_IDX(inst, binding_params, i) +#define BREAK_ZERO(i, inst) 0 -#define KP_INST(n) \ +#define KP_INST(inst) \ static struct zmk_behavior_binding \ - behavior_tap_dance_config_##n##_bindings[DT_INST_PROP_LEN(n, bindings)] = \ - TRANSFORMED_BINDINGS(n); \ - static struct behavior_tap_dance_config behavior_tap_dance_config_##n = { \ - .tapping_term_ms = DT_INST_PROP(n, tapping_term_ms), \ - .behaviors = behavior_tap_dance_config_##n##_bindings, \ - .behavior_count = DT_INST_PROP_LEN(n, bindings)}; \ - BEHAVIOR_DT_INST_DEFINE(n, behavior_tap_dance_init, NULL, NULL, \ - &behavior_tap_dance_config_##n, POST_KERNEL, \ - CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_tap_dance_driver_api); + behavior_tap_dance_config_##inst##_bindings[DT_PROP_LEN(inst, bindings)] = \ + TRANSFORMED_BINDINGS(inst); \ + static struct behavior_tap_dance_config behavior_tap_dance_config_##inst = { \ + .tapping_term_ms = DT_PROP(inst, tapping_term_ms), \ + .behaviors = behavior_tap_dance_config_##inst##_bindings, \ + .behavior_count = DT_PROP_LEN(inst, bindings), \ + .binding_params = \ + COND_CODE_0(DT_NODE_HAS_PROP(inst, binding_params), \ + ({LISTIFY(DT_PROP_LEN(inst, bindings), BREAK_ZERO, (, ), inst)}), \ + ({LISTIFY(DT_PROP_LEN(inst, bindings), BREAK_ITEM, (, ), inst)}))}; \ + BEHAVIOR_DT_DEFINE(inst, behavior_tap_dance_init, NULL, NULL, \ + &behavior_tap_dance_config_##inst, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_tap_dance_driver_api); -DT_INST_FOREACH_STATUS_OKAY(KP_INST) +DT_FOREACH_STATUS_OKAY(zmk_behavior_tap_dance, KP_INST) +DT_FOREACH_STATUS_OKAY(zmk_behavior_tap_dance_param, KP_INST) #endif diff --git a/app/tests/tap-dance/7-params/events.patterns b/app/tests/tap-dance/7-params/events.patterns new file mode 100644 index 00000000000..1768fc21062 --- /dev/null +++ b/app/tests/tap-dance/7-params/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/7-params/keycode_events.snapshot b/app/tests/tap-dance/7-params/keycode_events.snapshot new file mode 100644 index 00000000000..4777075a87e --- /dev/null +++ b/app/tests/tap-dance/7-params/keycode_events.snapshot @@ -0,0 +1,63 @@ +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +td_binding_pressed: 0 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 0 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +td_binding_released: 1 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +td_binding_released: 1 tap dance keybind released +td_binding_pressed: 1 tap dance pressed +td_binding_released: 1 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +td_binding_released: 1 tap dance keybind released +td_binding_pressed: 1 tap dance pressed +td_binding_released: 1 tap dance keybind released +td_binding_pressed: 1 tap dance pressed +td_binding_released: 1 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 1 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +td_binding_released: 1 tap dance keybind released +td_binding_pressed: 1 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 1 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +td_binding_released: 1 tap dance keybind released +td_binding_pressed: 1 tap dance pressed +td_binding_released: 1 tap dance keybind released +td_binding_pressed: 1 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 1 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 \ No newline at end of file diff --git a/app/tests/tap-dance/7-params/native_posix_64.keymap b/app/tests/tap-dance/7-params/native_posix_64.keymap new file mode 100644 index 00000000000..da963c19a23 --- /dev/null +++ b/app/tests/tap-dance/7-params/native_posix_64.keymap @@ -0,0 +1,89 @@ +#include +#include +#include + +#include +#include +#include + +/ { + behaviors { + ht: hold_tap { + compatible = "zmk,behavior-hold-tap"; + #binding-cells = <2>; + tapping-term-ms = <200>; + quick-tap-ms = <0>; + flavor = "tap-preferred"; + bindings = <&kp>, <&kp>; + }; + + tdm: tap_dance_mixed { + compatible = "zmk,behavior-tap-dance-param"; + #binding-cells = <2>; + tapping-term-ms = <100>; + bindings = <&ht PLACEHOLDER PLACEHOLDER>, <&ht C PLACEHOLDER>, <&ht PLACEHOLDER D>; + binding-params = ; + }; + + td2: tap_dance_basic { + compatible = "zmk,behavior-tap-dance-param"; + #binding-cells = <2>; + tapping-term-ms = <100>; + bindings = <&kp PLACEHOLDER>, <&kp B>, <&kp PLACEHOLDER>; + binding-params = ; + }; + }; + + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &td2 A C &tdm A B + &trans &trans >; + }; + }; +}; + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,110) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,110) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,110) + + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,110) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,110) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,110) + + ZMK_MOCK_PRESS(0,1,410) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_PRESS(0,1,410) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_PRESS(0,1,410) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file From f595f4a4805602102418b0dbe9def1bb8ae1ec9c Mon Sep 17 00:00:00 2001 From: Nicolas Munnich Date: Thu, 19 Dec 2024 01:17:03 +0100 Subject: [PATCH 3/3] docs: Documented parameterised tap dance and mod morph --- docs/docs/config/behaviors.md | 52 +++++++---- docs/docs/keymaps/behaviors/mod-morph.md | 108 ++++++++++++++++------ docs/docs/keymaps/behaviors/tap-dance.mdx | 95 ++++++++++++++++--- 3 files changed, 199 insertions(+), 56 deletions(-) diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index 7df6d176608..49864ad9d70 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -233,23 +233,28 @@ See the [mod-morph behavior](../keymaps/behaviors/mod-morph.md) documentation fo ### Devicetree -Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-mod-morph.yaml) +Definition files: -Applies to: `compatible = "zmk,behavior-mod-morph"` +- [zmk/app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-mod-morph.yaml) +- [zmk/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-mod-morph.yaml) -| Property | Type | Description | -| ---------------- | ------------- | --------------------------------------------------------------------------------- | -| `#binding-cells` | int | Must be `<0>` | -| `bindings` | phandle array | A list of two behaviors: one for normal press and one for mod morphed press | -| `mods` | int | A bit field of modifiers. The morph behavior is used if any of these are pressed. | +| Property | Type | Description | +| ---------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `compatible` | string | Mod-Morph variant, **must be _one_ of**:
  • `"zmk,behavior-mod-morph-param"`
  • `"zmk,behavior-mod-morph"`
| +| `#binding-cells` | int | Must be
  • `<2>` if `compatible = "zmk,behavior-mod-morph-param"`
  • `<0>` if `compatible = "zmk,behavior-mod-morph"`
| +| `bindings` | phandle array | A list of two behaviors: one for normal press and one for mod morphed press | +| `binding-params` | array | A list of two param assignment maps. Only applies to `compatible = "zmk,behavior-mod-morph-param"` | +| `mods` | int | A bit field of modifiers. The morph behavior is used if any of these are pressed. | See [dt-bindings/zmk/modifiers.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/modifiers.h) for a list of modifiers. +The `binding-params` array should be constructed using the `BINDING_PARAM` macro. +See [here](../keymaps/behaviors/mod-morph.md#binding-parameters) for more details. You can use the following nodes to tweak the default behaviors: -| Node | Behavior | -| -------- | ------------------------------------------------- | -| `&gresc` | [Grave escape](../keymaps/behaviors/mod-morph.md) | +| Node | Behavior | +| ----- | ------------------------------------------------------- | +| `&mm` | [Mod Morph Keypress](../keymaps/behaviors/mod-morph.md) | ## Sensor Rotation @@ -327,15 +332,28 @@ See the [tap dance behavior](../keymaps/behaviors/tap-dance.mdx) documentation f ### Devicetree -Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-tap-dance.yaml) +Definition files: + +- [zmk/app/dts/bindings/behaviors/zmk,behavior-tap-dance-param.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-tap-dance.yaml) +- [zmk/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-tap-dance.yaml) + +| Property | Type | Description | Default | +| ----------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `compatible` | string | Tap-Dance variant, **must be _one_ of**:
  • `"zmk,behavior-tap-dance-param"`
  • `"zmk,behavior-tap-dance"`
| | +| `#binding-cells` | int | Must be
  • `<2>` if `compatible = "zmk,behavior-tap-dance-param"`
  • `<0>` if `compatible = "zmk,behavior-tap-dance"`
| | +| `bindings` | phandle array | A list of behaviors from which to select | | +| `binding-params` | array | A list of two param assignment maps. Only applies to `compatible = "zmk,behavior-tap-dance-param"` | | +| `tapping-term-ms` | int | The maximum time (in milliseconds) between taps before an item from `bindings` is triggered. | 200 | -Applies to: `compatible = "zmk,behavior-tap-dance"` +See [dt-bindings/zmk/modifiers.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/modifiers.h) for a list of modifiers. +The `binding-params` array should be constructed using the `BINDING_PARAM` macro. +See [here](../keymaps/behaviors/tap-dance.mdx#binding-params) for more details. + +You can use the following nodes to tweak the default behaviors: -| Property | Type | Description | Default | -| ----------------- | ------------- | -------------------------------------------------------------------------------------------- | ------- | -| `#binding-cells` | int | Must be `<0>` | | -| `bindings` | phandle array | A list of behaviors from which to select | | -| `tapping-term-ms` | int | The maximum time (in milliseconds) between taps before an item from `bindings` is triggered. | 200 | +| Node | Behavior | +| ----- | -------------------------------------------------------- | +| `&td` | [Tap Dance Keypress](../keymaps/behaviors/tap-dance.mdx) | ## Two Axis Input diff --git a/docs/docs/keymaps/behaviors/mod-morph.md b/docs/docs/keymaps/behaviors/mod-morph.md index 879db998484..17fb51104b8 100644 --- a/docs/docs/keymaps/behaviors/mod-morph.md +++ b/docs/docs/keymaps/behaviors/mod-morph.md @@ -12,35 +12,41 @@ The mod-morph behavior invokes a different behavior depending on whether any of ## Mod-Morph -### Configuration +ZMK provides a simple build-in mod-morph behavior. +When pressed with one of `LEFT_SHIFT`, `LEFT_GUI`, `RIGHT_SHIFT`, `RIGHT_GUI` active, a [key press](key-press.md) will be triggered using the second parameter. +Otherwise, a key press with the first parameter will be triggered. -Below is an example of how to implement the mod-morph "Grave Escape". When assigned to a key, pressing the key on its own will send an -Escape keycode but pressing it while a shift or GUI modifier is held sends the grave `` ` `` keycode instead: +### Behavior Binding + +- Reference: `&mm` +- Parameters: The keycode usage IDs from the usage page, e.g. `N4` or `A` + +Example: ```dts -/ { - behaviors { - gresc: grave_escape { - compatible = "zmk,behavior-mod-morph"; - #binding-cells = <0>; - bindings = <&kp ESC>, <&kp GRAVE>; - mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>; - }; - }; -}; +&mm A B ``` -Note that this specific mod-morph exists in ZMK by default using the binding `&gresc`. +## Custom Mod-Morph -### Behavior Binding +If you want to trigger other behaviors or morph based on other combinations of modifiers, you can create a new mod-morph behavior. -- Reference: `&gresc` -- Parameter: None - -Example: +Below is an example of how to implement a mod-morph that selects which behavior to trigger based on whether a `CTRL` modifier is active. +When `CTRL` is not active, a `&kp` with the first parameter passed to the behavior is triggered. +Otherwise, the layer whose number corresponds to the second parameter passed to the behavior is triggered. ```dts -&gresc +/ { + behaviors { + mm_ctrl: mod_morph_control { + compatible = "zmk,behavior-mod-morph-param"; + #binding-cells = <2>; + mods = <(MOD_LCTL|MOD_RCTL)>; + bindings = <&kp PLACEHOLDER>, <&mo PLACEHOLDER>; + binding-params = ; + }; + }; +}; ``` ### Mods @@ -64,6 +70,37 @@ Example: mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>; ``` +### Binding Parameters + +The `binding-params` property determines how the parameters passed to the mod-morph behavior are passed on to the behaviors listed in `bindings`. +It is an array which always has two `BINDING_PARAM(arg1,arg2)` elements - the first corresponds to the first behavior listed in `bindings`, the second corresponding to the second. The number chosen for `argX` determines what the Xth parameter passed to the behavior will be: + +- `1`: The Xth parameter passed will be the first parameter that the mod-morph behavior received +- `2`: The Xth parameter passed will be the second parameter that the mod-morph behavior received +- `0`: The Xth parameter will be that which is written in the `bindings` array. + +Note that any parameters in `bindings` behaviors which are to be replaced should be set to `PLACEHOLDER`. + +Examples: + +```dts +binding-params = ; +``` + +The first behavior receives both input parameters, the second receives neither. + +```dts +binding-params = ; +``` + +The first behavior receives the second input parameter as its first parameter, the second behavior receives the first input parameter as its first parameter. + +```dts +binding-params = ; +``` + +Neither behavior receives any input parameter. Note that mod-morph always requires two parameters to be passed, so you will still need to write e.g. `&my_mm 0 0` in your keymap - the parameters passed will be ignored[^1]. + ### Advanced Configuration #### `keep-mods` @@ -76,9 +113,10 @@ For example, the following configuration morphs `LEFT_SHIFT` + `BACKSPACE` into / { behaviors { bspc_del: backspace_delete { - compatible = "zmk,behavior-mod-morph"; - #binding-cells = <0>; + compatible = "zmk,behavior-mod-morph-param"; + #binding-cells = <2>; bindings = <&kp BACKSPACE>, <&kp DELETE>; + binding-params = ; mods = <(MOD_LSFT|MOD_RSFT)>; keep-mods = <(MOD_RSFT)>; }; @@ -97,15 +135,17 @@ As an example, consider the following two mod-morphs: / { behaviors { morph_BC: morph_BC { - compatible = "zmk,behavior-mod-morph"; - #binding-cells = <0>; + compatible = "zmk,behavior-mod-morph-param"; + #binding-cells = <2>; bindings = <&kp B>, <&kp C>; + binding-params = ; mods = <(MOD_LCTL|MOD_RCTL)>; }; morph_ABC: morph_ABC { - compatible = "zmk,behavior-mod-morph"; - #binding-cells = <0>; + compatible = "zmk,behavior-mod-morph-param"; + #binding-cells = <2>; bindings = <&kp A>, <&morph_BC>; + binding-params = ; mods = <(MOD_LSFT|MOD_RSFT)>; }; }; @@ -119,3 +159,19 @@ When you assign `&morph_ABC` to a key position and press it, it will output `A` If the first modified key press sends the modifier along with the morphed keycode and [Karabiner-Elements](https://karabiner-elements.pqrs.org/) is running, disable the "Modify Events" toggle from Karabiner's "Devices" settings page for the keyboard running ZMK. ::: + +[^1]: There exists a simplified version of mod-morph without any input parameters, `compatible="zmk,behavior-mod-morph"`: + +```dts +/ { + behaviors { + bspc_del: backspace_delete { + compatible = "zmk,behavior-mod-morph"; + #binding-cells = <0>; + bindings = <&kp BACKSPACE>, <&kp DELETE>; + mods = <(MOD_LSFT|MOD_RSFT)>; + keep-mods = <(MOD_RSFT)>; + }; + }; +}; +``` diff --git a/docs/docs/keymaps/behaviors/tap-dance.mdx b/docs/docs/keymaps/behaviors/tap-dance.mdx index 166d7813226..19abe1591af 100644 --- a/docs/docs/keymaps/behaviors/tap-dance.mdx +++ b/docs/docs/keymaps/behaviors/tap-dance.mdx @@ -8,23 +8,75 @@ import TabItem from "@theme/TabItem"; ## Summary -A tap-dance key invokes a different behavior (e.g. `kp`) corresponding to how many times it is pressed. For example, you could configure a tap-dance key that acts as `LSHIFT` if tapped once, or Caps _Lock_ if tapped twice. The expandability of the number of [`bindings`](#bindings) attached to a particular tap-dance is a great way to add more functionality to a single key, especially for keyboards with a limited number of keys. Tap-dances are completely custom, so for every unique tap-dance key,a new tap-dance must be defined in your keymap's `behaviors`. +A tap-dance key invokes a different behavior (e.g. `kp`) corresponding to how many times it is pressed. +For example, you could configure a tap-dance key that acts as `LSHIFT` if tapped once, or Caps _Lock_ if tapped twice. +The expandability of the number of [`bindings`](#bindings) attached to a particular tap-dance is a great way to add more functionality to a single key, especially for keyboards with a limited number of keys. +Tap-dances accept two parameters from your keymap, which you can map to the behaviors within as you please. Tap-dances are designed to resolve immediately when interrupted by another keypress. Meaning, when a keybind is pressed other than any active tap-dances, the tap-dance will activate according to the current value of its counter before the interrupting keybind is registered. -### Configuration +## Keypress Tap Dance -#### `tapping-term-ms` +ZMK provides a simple two-tap tap dance for you to use. Press it once, and it will output a key press with the first parameter. +Press it twice within 200ms, and it will output a key press with the second parameter. + +### Behavior Binding + +- Reference: `&td` +- Parameters: The keycode usage IDs from the usage page, e.g. `N4` or `A` + +Example: + +```dts +&td A B +``` + +## Configuration + +### `tapping-term-ms` Defines the maximum elapsed time after the last tap-dance keybind press before a binding is selected from [`bindings`](#bindings). Default value is `200`ms. -#### `bindings` +### `bindings` An array of one or more keybinds. This list can include [any ZMK keycode](../list-of-keycodes.mdx) and any listed ZMK behavior, like [hold-taps](hold-tap.mdx), or [sticky keys](sticky-key.md). The index of a keybind in the `bindings` array corresponds to the number of times the tap-dance binding is pressed. For example, in the basic tap-dance counter shown below, `&kp N2` is the second binding in the array of `bindings`: we then see an output of `2` when the `td0` binding is pressed twice. The number of bindings in this array also determines the tap-dance's maximum number of keypresses. When a tap-dance reaches its maximum number of keypresses, it will immediately invoke the last behavior in its list of `bindings`, rather than waiting for [`tapping-term-ms`](#tapping-term-ms) to expire before the output is displayed. -### Example Usage +### `binding-params` + +The `binding-params` property determines how the parameters passed to the tap-dance behavior are passed on to the behaviors listed in `bindings`. +It is an array of `BINDING_PARAM(arg1,arg2)` elements, one for each behavior in `bindings`. +The Nth `BINDING_PARAM(arg1,arg2)` corresponds to the Nth behavior listed in `bindings`. +The number chosen for `argX` determines what the Xth parameter passed to the behavior will be: + +- `1`: The Xth parameter passed will be the first parameter that the tap-dance behavior received +- `2`: The Xth parameter passed will be the second parameter that the tap-dance behavior received +- `0`: The Xth parameter will be that which is written in the `bindings` array. + +Note that any parameters in `bindings` behaviors which are to be replaced should be set to `PLACEHOLDER`. + +Examples: + +```dts +binding-params = ; +``` + +The first behavior receives both input parameters, the second receives neither. + +```dts +binding-params = ; +``` + +The first behavior receives the second input parameter as its first parameter, the second behavior receives the first input parameter as its first parameter, the third receives neither. + +```dts +binding-params = ; +``` + +No behavior receives any input parameter. Note that tap-dance always requires two parameters to be passed, so you will still need to write e.g. `&my_td 0 0` in your keymap - the parameters passed will be ignored[^1]. + +## Example Usage ; + compatible = "zmk,behavior-tap-dance-param"; + #binding-cells = <2>; tapping-term-ms = <200>; bindings = <&kp N1>, <&kp N2>, <&kp N3>; + binding-params = ; }; }; @@ -56,7 +109,7 @@ This example configures a tap-dance named `td0` that outputs the number of times default_layer { bindings = < - &td0 + &td0 0 0 >; }; }; @@ -75,7 +128,7 @@ Alphanumeric [`key press`](key-press.md) bindings, like those used for `td0`, wi -This example configures a mod-tap inside a tap-dance named `td_mt` that outputs `CAPSLOCK` on a single tap, `LSHIFT` on a single press and hold, and `LCTRL` when the tap-dance is pressed twice. +This example configures a mod-tap inside a tap-dance named `td_mt` that outputs its first parameter on a single tap, `LSHIFT` on a single press and hold, and its second parameter when the tap-dance is pressed twice. ```dts title="Advanced Tap-Dance Example: Nested Mod-Tap" #include @@ -84,10 +137,11 @@ This example configures a mod-tap inside a tap-dance named `td_mt` that outputs / { behaviors { td_mt: tap_dance_mod_tap { - compatible = "zmk,behavior-tap-dance"; - #binding-cells = <0>; + compatible = "zmk,behavior-tap-dance-param"; + #binding-cells = <2>; tapping-term-ms = <200>; - bindings = <&mt LSHIFT CAPSLOCK>, <&kp LCTRL>; + bindings = <&mt LSHIFT PLACEHOLDER>, <&kp PLACEHOLDER>; + binding-params = ; }; }; @@ -96,7 +150,7 @@ This example configures a mod-tap inside a tap-dance named `td_mt` that outputs default_layer { bindings = < - &td_mt + &td_mt CAPSLOCK LCTRL >; }; }; @@ -105,3 +159,18 @@ This example configures a mod-tap inside a tap-dance named `td_mt` that outputs + +[^1]: There exists a simplified version of tap-dance without any input parameters, `compatible="zmk,behavior-tap-dance"`: + +```dts +/ { + behaviors { + td0: tap_dance_0 { + compatible = "zmk,behavior-tap-dance"; + #binding-cells = <0>; + tapping-term-ms = <200>; + bindings = <&kp N1>, <&kp N2>, <&kp N3>; + }; + }; +}; +```