diff --git a/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml b/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml index ed40f9368c8a..da2c45d9ab0a 100644 --- a/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml +++ b/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml @@ -14,3 +14,6 @@ properties: mods: type: int required: true + masked_mods: + type: int + required: false diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index 83762b81fe01..22190a86e3aa 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -227,6 +227,9 @@ int zmk_hid_unregister_mods(zmk_mod_flags_t explicit_modifiers); int zmk_hid_implicit_modifiers_press(zmk_mod_flags_t implicit_modifiers); int zmk_hid_implicit_modifiers_release(); +int zmk_hid_masked_modifiers_set(zmk_mod_flags_t masked_modifiers); +int zmk_hid_masked_modifiers_clear(); + int zmk_hid_keyboard_press(zmk_key_t key); int zmk_hid_keyboard_release(zmk_key_t key); void zmk_hid_keyboard_clear(); diff --git a/app/src/behaviors/behavior_mod_morph.c b/app/src/behaviors/behavior_mod_morph.c index a40bd3651a24..97534ffc0946 100644 --- a/app/src/behaviors/behavior_mod_morph.c +++ b/app/src/behaviors/behavior_mod_morph.c @@ -27,6 +27,7 @@ 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; }; struct behavior_mod_morph_data { @@ -45,6 +46,7 @@ static int on_mod_morph_binding_pressed(struct zmk_behavior_binding *binding, } if (zmk_hid_get_explicit_mods() & cfg->mods) { + zmk_hid_masked_modifiers_set(cfg->masked_mods); data->pressed_binding = (struct zmk_behavior_binding *)&cfg->morph_binding; } else { data->pressed_binding = (struct zmk_behavior_binding *)&cfg->normal_binding; @@ -64,6 +66,7 @@ static int on_mod_morph_binding_released(struct zmk_behavior_binding *binding, struct zmk_behavior_binding *pressed_binding = data->pressed_binding; data->pressed_binding = NULL; + zmk_hid_masked_modifiers_clear(); return behavior_keymap_binding_released(pressed_binding, event); } @@ -88,6 +91,8 @@ static int behavior_mod_morph_init(const struct device *dev) { return 0; } .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, masked_mods), (DT_INST_PROP(n, mods)), \ + (DT_INST_PROP(n, masked_mods))), \ }; \ static struct behavior_mod_morph_data behavior_mod_morph_data_##n = {}; \ DEVICE_DT_INST_DEFINE(n, behavior_mod_morph_init, NULL, &behavior_mod_morph_data_##n, \ diff --git a/app/src/hid.c b/app/src/hid.c index 9e7451b7f439..3cb08b19d046 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -23,10 +23,12 @@ static struct zmk_hid_mouse_report mouse_report = { // Only release the modifier if the count is 0. static int explicit_modifier_counts[8] = {0, 0, 0, 0, 0, 0, 0, 0}; static zmk_mod_flags_t explicit_modifiers = 0; +static zmk_mod_flags_t implicit_modifiers = 0; +static zmk_mod_flags_t masked_modifiers = 0; #define SET_MODIFIERS(mods) \ { \ - keyboard_report.body.modifiers = mods; \ + keyboard_report.body.modifiers = (mods & ~masked_modifiers) | implicit_modifiers; \ LOG_DBG("Modifiers set to 0x%02X", keyboard_report.body.modifiers); \ } @@ -161,13 +163,29 @@ static inline int check_keyboard_usage(zmk_key_t usage) { } \ } -int zmk_hid_implicit_modifiers_press(zmk_mod_flags_t implicit_modifiers) { +int zmk_hid_implicit_modifiers_press(zmk_mod_flags_t new_implicit_modifiers) { + implicit_modifiers = new_implicit_modifiers; zmk_mod_flags_t current = GET_MODIFIERS; - SET_MODIFIERS(explicit_modifiers | implicit_modifiers); + SET_MODIFIERS(explicit_modifiers); return current == GET_MODIFIERS ? 0 : 1; } int zmk_hid_implicit_modifiers_release() { + implicit_modifiers = 0; + zmk_mod_flags_t current = GET_MODIFIERS; + SET_MODIFIERS(explicit_modifiers); + return current == GET_MODIFIERS ? 0 : 1; +} + +int zmk_hid_masked_modifiers_set(zmk_mod_flags_t new_masked_modifiers) { + masked_modifiers = new_masked_modifiers; + zmk_mod_flags_t current = GET_MODIFIERS; + SET_MODIFIERS(explicit_modifiers); + return current == GET_MODIFIERS ? 0 : 1; +} + +int zmk_hid_masked_modifiers_clear() { + masked_modifiers = 0; zmk_mod_flags_t current = GET_MODIFIERS; SET_MODIFIERS(explicit_modifiers); return current == GET_MODIFIERS ? 0 : 1; diff --git a/app/tests/mod-morph/default_mask_no_implicit/events.patterns b/app/tests/mod-morph/default_mask_no_implicit/events.patterns new file mode 100644 index 000000000000..75740ac1e968 --- /dev/null +++ b/app/tests/mod-morph/default_mask_no_implicit/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/default_mask_no_implicit/keycode_events.snapshot b/app/tests/mod-morph/default_mask_no_implicit/keycode_events.snapshot new file mode 100644 index 000000000000..fdf5674ec99a --- /dev/null +++ b/app/tests/mod-morph/default_mask_no_implicit/keycode_events.snapshot @@ -0,0 +1,46 @@ +--- pressed: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x00 +--- released: keycode 0x04 implicit_mods 0x00 explicit_mods 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 0x05 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x02 +--- released: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +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 0xE2 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x04 +reg implicit: Modifiers set to 0x04 +--- pressed: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x04 +unmask mods: Modifiers set to 0x04 +--- released: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x04 +--- released: keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x00 +unreg implicit: Modifiers set to 0x00 +--- pressed: keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x04 +reg implicit: Modifiers set to 0x04 +--- pressed: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x06 +reg implicit: Modifiers set to 0x06 +mask mods: Modifiers set to 0x04 +--- pressed: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x04 +unmask mods: Modifiers set to 0x06 +--- released: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x06 +--- released: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x04 +unreg implicit: Modifiers set to 0x04 +--- released: keycode 0xE2 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/default_mask_no_implicit/native_posix_64.keymap b/app/tests/mod-morph/default_mask_no_implicit/native_posix_64.keymap new file mode 100644 index 000000000000..30b96f015a2a --- /dev/null +++ b/app/tests/mod-morph/default_mask_no_implicit/native_posix_64.keymap @@ -0,0 +1,56 @@ +#include +#include +#include + + +&kscan { + events = < + /* A */ + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + + /* B */ + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(1,0,10) + + /* LALT + A */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(0,0,10) + + /* LALT + B */ + ZMK_MOCK_PRESS(0,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) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; + +/ { + behaviors { + mod_morph: mod_morph { + compatible = "zmk,behavior-mod-morph"; + label = "MOD_MORPH_TEST"; + #binding-cells = <0>; + bindings = <&kp A>, <&kp B>; + mods = <(MOD_LSFT|MOD_RSFT)>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &kp LEFT_ALT &mod_morph + &kp LEFT_SHIFT &kp RIGHT_SHIFT + >; + }; + }; +}; diff --git a/app/tests/mod-morph/default_mask_yes_implicit/events.patterns b/app/tests/mod-morph/default_mask_yes_implicit/events.patterns new file mode 100644 index 000000000000..75740ac1e968 --- /dev/null +++ b/app/tests/mod-morph/default_mask_yes_implicit/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/default_mask_yes_implicit/keycode_events.snapshot b/app/tests/mod-morph/default_mask_yes_implicit/keycode_events.snapshot new file mode 100644 index 000000000000..400a11ae5083 --- /dev/null +++ b/app/tests/mod-morph/default_mask_yes_implicit/keycode_events.snapshot @@ -0,0 +1,46 @@ +--- pressed: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x00 +--- released: keycode 0x04 implicit_mods 0x00 explicit_mods 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 0x05 implicit_mods 0x02 explicit_mods 0x00 +reg implicit: Modifiers set to 0x02 +unmask mods: Modifiers set to 0x02 +--- released: keycode 0x05 implicit_mods 0x02 explicit_mods 0x00 +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 0xE2 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x04 +reg implicit: Modifiers set to 0x04 +--- pressed: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x04 +unmask mods: Modifiers set to 0x04 +--- released: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x04 +--- released: keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x00 +unreg implicit: Modifiers set to 0x00 +--- pressed: keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x04 +reg implicit: Modifiers set to 0x04 +--- pressed: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x06 +reg implicit: Modifiers set to 0x06 +mask mods: Modifiers set to 0x04 +--- pressed: keycode 0x05 implicit_mods 0x02 explicit_mods 0x00 +reg implicit: Modifiers set to 0x06 +unmask mods: Modifiers set to 0x06 +--- released: keycode 0x05 implicit_mods 0x02 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x06 +--- released: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x04 +unreg implicit: Modifiers set to 0x04 +--- released: keycode 0xE2 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/default_mask_yes_implicit/native_posix_64.keymap b/app/tests/mod-morph/default_mask_yes_implicit/native_posix_64.keymap new file mode 100644 index 000000000000..f754638c95ab --- /dev/null +++ b/app/tests/mod-morph/default_mask_yes_implicit/native_posix_64.keymap @@ -0,0 +1,56 @@ +#include +#include +#include + + +&kscan { + events = < + /* A */ + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + + /* LSFT + B */ + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(1,0,10) + + /* LALT + A */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(0,0,10) + + /* LALT + LSFT + B */ + ZMK_MOCK_PRESS(0,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) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; + +/ { + behaviors { + mod_morph: mod_morph { + compatible = "zmk,behavior-mod-morph"; + label = "MOD_MORPH_TEST"; + #binding-cells = <0>; + bindings = <&kp A>, <&kp LS(B)>; + mods = <(MOD_LSFT|MOD_RSFT)>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &kp LEFT_ALT &mod_morph + &kp LEFT_SHIFT &kp RIGHT_SHIFT + >; + }; + }; +}; diff --git a/app/tests/mod-morph/morph-into-hold-tap/events.patterns b/app/tests/mod-morph/morph-into-hold-tap/events.patterns new file mode 100644 index 000000000000..75740ac1e968 --- /dev/null +++ b/app/tests/mod-morph/morph-into-hold-tap/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/morph-into-hold-tap/keycode_events.snapshot b/app/tests/mod-morph/morph-into-hold-tap/keycode_events.snapshot new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/app/tests/mod-morph/morph-into-hold-tap/native_posix_64.keymap b/app/tests/mod-morph/morph-into-hold-tap/native_posix_64.keymap new file mode 100644 index 000000000000..7c8b5b2d49ad --- /dev/null +++ b/app/tests/mod-morph/morph-into-hold-tap/native_posix_64.keymap @@ -0,0 +1,59 @@ +#include +#include +#include + +/* +This test fails when the hold-tap resolves as tap, because &mod_morph is then released +when the hold-tap decision is made but before the keypress is registered. +*/ + +&kscan { + events = < + /* Shift + tap &mod_morph --> expect B (but get Shift + B) */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(0,0,10) + + /* Shift + hold &mod_morph --> expect and get D (no shift) */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,200) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; + +/ { + behaviors { + mod_morph: mod_morph { + compatible = "zmk,behavior-mod-morph"; + label = "MOD_MORPH_TEST"; + #binding-cells = <0>; + bindings = <&kp A>, << 1 B>; + mods = <(MOD_LSFT|MOD_RSFT)>; + masked_mods = <(MOD_LSFT|MOD_RSFT)>; // same as default, added in case default gets changed + }; + + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &kp LEFT_SHIFT &mod_morph + &kp C &none + >; + }; + + second_layer { + bindings = < + &trans &trans + &kp D &none + >; + }; + }; +}; diff --git a/app/tests/mod-morph/morph-into-hold-tap/pending b/app/tests/mod-morph/morph-into-hold-tap/pending new file mode 100644 index 000000000000..f1a159d2dc5f --- /dev/null +++ b/app/tests/mod-morph/morph-into-hold-tap/pending @@ -0,0 +1,7 @@ +This test fails when the hold-tap is decided to resolve as tap. In this case, as soon as +the hold-tap decision is made, the mod-morph is released. This triggers the +`.binding_released` function of the `mod-morph` behavior, clearing the `masked_mods` +before the actual tap-binding of the `hold-tap` gets registered. + +If the hold-tap is decided to resolve as hold, the mod-morph isn't released, and +`masked_mods` are cleared correctly. diff --git a/app/tests/mod-morph/no_mask_no_implicit/events.patterns b/app/tests/mod-morph/no_mask_no_implicit/events.patterns new file mode 100644 index 000000000000..75740ac1e968 --- /dev/null +++ b/app/tests/mod-morph/no_mask_no_implicit/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/no_mask_no_implicit/keycode_events.snapshot b/app/tests/mod-morph/no_mask_no_implicit/keycode_events.snapshot new file mode 100644 index 000000000000..1297f1348575 --- /dev/null +++ b/app/tests/mod-morph/no_mask_no_implicit/keycode_events.snapshot @@ -0,0 +1,46 @@ +--- pressed: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x00 +--- released: keycode 0x04 implicit_mods 0x00 explicit_mods 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 0x02 +--- pressed: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x02 +unmask mods: Modifiers set to 0x02 +--- released: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +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 0xE2 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x04 +reg implicit: Modifiers set to 0x04 +--- pressed: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x04 +unmask mods: Modifiers set to 0x04 +--- released: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x04 +--- released: keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x00 +unreg implicit: Modifiers set to 0x00 +--- pressed: keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x04 +reg implicit: Modifiers set to 0x04 +--- pressed: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x06 +reg implicit: Modifiers set to 0x06 +mask mods: Modifiers set to 0x06 +--- pressed: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x06 +unmask mods: Modifiers set to 0x06 +--- released: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x06 +--- released: keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +unreg explicit: Modifiers set to 0x04 +unreg implicit: Modifiers set to 0x04 +--- released: keycode 0xE2 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/no_mask_no_implicit/native_posix_64.keymap b/app/tests/mod-morph/no_mask_no_implicit/native_posix_64.keymap new file mode 100644 index 000000000000..13857c6ef292 --- /dev/null +++ b/app/tests/mod-morph/no_mask_no_implicit/native_posix_64.keymap @@ -0,0 +1,57 @@ +#include +#include +#include + + +&kscan { + events = < + /* A */ + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + + /* LSFT + B */ + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(1,0,10) + + /* LALT + A */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(0,0,10) + + /* LALT + LSFT + B */ + ZMK_MOCK_PRESS(0,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) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; + +/ { + behaviors { + mod_morph: mod_morph { + compatible = "zmk,behavior-mod-morph"; + label = "MOD_MORPH_TEST"; + #binding-cells = <0>; + bindings = <&kp A>, <&kp B>; + mods = <(MOD_LSFT|MOD_RSFT)>; + masked_mods = <0>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &kp LEFT_ALT &mod_morph + &kp LEFT_SHIFT &kp RIGHT_SHIFT + >; + }; + }; +}; diff --git a/app/tests/mod-morph/partial_mask_no_implicit/events.patterns b/app/tests/mod-morph/partial_mask_no_implicit/events.patterns new file mode 100644 index 000000000000..75740ac1e968 --- /dev/null +++ b/app/tests/mod-morph/partial_mask_no_implicit/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/partial_mask_no_implicit/keycode_events.snapshot b/app/tests/mod-morph/partial_mask_no_implicit/keycode_events.snapshot new file mode 100644 index 000000000000..0febe25d7e77 --- /dev/null +++ b/app/tests/mod-morph/partial_mask_no_implicit/keycode_events.snapshot @@ -0,0 +1,29 @@ +--- pressed: keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x00 +--- released: keycode 0x04 implicit_mods 0x00 explicit_mods 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 0x05 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x00 +unmask mods: Modifiers set to 0x02 +--- released: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +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 0xE5 implicit_mods 0x00 explicit_mods 0x00 +reg explicit: Modifiers set to 0x20 +reg implicit: Modifiers set to 0x20 +mask mods: Modifiers set to 0x20 +--- pressed: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +reg implicit: Modifiers set to 0x20 +unmask mods: Modifiers set to 0x20 +--- released: keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +unreg implicit: Modifiers set to 0x20 +--- released: keycode 0xE5 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/partial_mask_no_implicit/native_posix_64.keymap b/app/tests/mod-morph/partial_mask_no_implicit/native_posix_64.keymap new file mode 100644 index 000000000000..5dc08b801abf --- /dev/null +++ b/app/tests/mod-morph/partial_mask_no_implicit/native_posix_64.keymap @@ -0,0 +1,49 @@ +#include +#include +#include + + +&kscan { + events = < + /* A */ + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + + /* B */ + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(1,0,10) + + /* RSFT + B */ + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(1,1,10) + >; +}; + +/ { + behaviors { + mod_morph: mod_morph { + compatible = "zmk,behavior-mod-morph"; + label = "MOD_MORPH_TEST"; + #binding-cells = <0>; + bindings = <&kp A>, <&kp B>; + mods = <(MOD_LSFT|MOD_RSFT)>; + masked_mods = <(MOD_LSFT)>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &kp LEFT_ALT &mod_morph + &kp LEFT_SHIFT &kp RIGHT_SHIFT + >; + }; + }; +}; diff --git a/app/tests/modifiers/mixed/kp-lctl-dn-mod-dn-lctl-up-mod-up/keycode_events.snapshot b/app/tests/modifiers/mixed/kp-lctl-dn-mod-dn-lctl-up-mod-up/keycode_events.snapshot index 43edb9612225..45719679dfb9 100644 --- a/app/tests/modifiers/mixed/kp-lctl-dn-mod-dn-lctl-up-mod-up/keycode_events.snapshot +++ b/app/tests/modifiers/mixed/kp-lctl-dn-mod-dn-lctl-up-mod-up/keycode_events.snapshot @@ -7,7 +7,7 @@ mods: Modifiers set to 0x03 released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 unreg: Modifier 0 count: 0 unreg: Modifier 0 released -unreg: Modifiers set to 0x00 +unreg: Modifiers set to 0x02 mods: Modifiers set to 0x00 released: usage_page 0x07 keycode 0x05 implicit_mods 0x02 explicit_mods 0x00 mods: Modifiers set to 0x00 diff --git a/docs/docs/behaviors/mod-morph.md b/docs/docs/behaviors/mod-morph.md index 2606aaf8bf95..8a7c0523a0c9 100644 --- a/docs/docs/behaviors/mod-morph.md +++ b/docs/docs/behaviors/mod-morph.md @@ -14,8 +14,6 @@ The Mod-Morph behavior sends a different keypress, depending on whether a specif The Mod-Morph behavior acts as one of two keycodes, depending on if the required modifier is being held during the keypress. -When the modifier is being held it is sent along with the morphed keycode. This can cause problems when the morphed keycode and modifier have an existing relationship (such as `shift-delete` or `ctrl-v` on many operating systems). - ### Configuration An example of how to implement the mod-morph "Grave Escape": @@ -31,10 +29,6 @@ An example of how to implement the mod-morph "Grave Escape": mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>; }; }; - - keymap { - ... - }; }; ``` @@ -71,3 +65,30 @@ Example: ``` mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>; ``` + +### Advanced configuration + +`masked_mods` + +When a modifier specified in `mods` is being held, it won't be sent along with the morphed keycode if it is also part of `masked_mods`. To sent all modifiers along with the morphed keycode, set `masked_mods` to `<0>`. By default, `masked_mods` equals `mods`. + +For example, the following configuration morphs `LEFT_SHIFT` + `BACKSPACE` into `DELETE`, and morphs `RIGHT_SHIFT` + `BACKSPACE` into `RIGHT_SHIFT` + `DELETE`. + +``` +/ { + behaviors { + bspc_del: backspace_delete { + compatible = "zmk,behavior-mod-morph"; + label = "BACKSPACE_DELETE"; + #binding-cells = <0>; + bindings = <&kp BACKSPACE>, <&kp DELETE>; + mods = <(MOD_LSFT|MOD_RSFT)>; + masked_mods = <(MOD_LSFT)>; + }; + }; +}; +``` + +### Limitations + +In some circumstance, when the morphed keycode is a hold-tap, the modifier gets sent along with the hold-tap regardless of the specification of `masked_mods`.