Repository: zmkfirmware/zmk (2025-05-15T06:32:15Z)

Issue:
[bug] parameterized macros do not function properly with layers
hi, some members and i figured this out today in the discord server

if you use a parameterized macro which contains any form of layer interaction, the value of the last used (we only tested with 1) parameter gets passed to the layer interaction instead of the number specified

so, using the macro:
```
kp_l1: kp_l1 {
    compatible = "zmk,behavior-macro-one-param";
    #binding-cells = <1>;
    bindings
    = <&macro_param_1to1>
    , <&macro_tap &kp MACRO_PLACEHOLDER>
    , <&macro_press &mo 1>
    , <&macro_pause_for_release>
    , <&macro_release &mo 1>;
};
```
and any mention of this macro, like `&kp_l1 W` in your keymap (or literally anywhere)

... will result in getting stuck in layer 1
this is because (in our example) `W` gets passed to `&mo` instead of `1`, thus never leaving layer 1, as evident from usb logging:

```
[00:00:16.775,482] <dbg> zmk: behavior_queue_process_next: Invoking momentary_layer: 0x7001a 0x00
[00:00:16.775,512] <dbg> zmk: mo_keymap_binding_released: position 1 layer 458778
```
(lmao)

if the full logs are needed, ill be happy to provide them

workaround: avoid parameterized macros and just write one for every key you need

thank you for reading!


Discussion:
Here is a test that reproduces it and fails: https://github.com/caksoylar/zmk/commit/ce66de1e75437e9daef414cbfac37cce5668ba95

Also shows the wrong layer index for the macro release in logs:
```diff
--- tests/macros/param-then-no-param/keycode_events.snapshot    2025-04-27 17:23:07.032002780 -0700
+++ ./build/tests/macros/param-then-no-param/keycode_events.log 2025-04-27 17:24:32.932002687 -0700
@@ -3,6 +3,6 @@
 mo_pressed: position 0 layer 1
 kp_pressed: usage_page 0x07 keycode 0x1C implicit_mods 0x00 explicit_mods 0x00
 kp_released: usage_page 0x07 keycode 0x1C implicit_mods 0x00 explicit_mods 0x00
-mo_released: position 0 layer 1
-kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
-kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
+mo_released: position 0 layer 458756
+kp_pressed: usage_page 0x07 keycode 0x1D implicit_mods 0x00 explicit_mods 0x00
+kp_released: usage_page 0x07 keycode 0x1D implicit_mods 0x00 explicit_mods 0x00
FAILED: macros/param-then-no-param
```

I'll take a look at the code if I get a chance, the test should make it easier to figure out the issue if anyone else investigates.



Patch:
diff --git a/app/src/behaviors/behavior_macro.c b/app/src/behaviors/behavior_macro.c
index c16fb69a9e9..3e4d17321fa 100644
--- a/app/src/behaviors/behavior_macro.c
+++ b/app/src/behaviors/behavior_macro.c
@@ -129,7 +129,9 @@ static int behavior_macro_init(const struct device *dev) {
             LOG_DBG("Release will resume at %d", state->release_state.start_index);
             break;
         } else {
-            // Ignore regular invokable bindings
+            // Mostly ignore regular invokable bindings, except they will consume macro parameters
+            state->release_state.param1_source = PARAM_SOURCE_BINDING;
+            state->release_state.param2_source = PARAM_SOURCE_BINDING;
         }
     }
 


Test patch:
diff --git a/app/tests/macros/param-then-no-param/events.patterns b/app/tests/macros/param-then-no-param/events.patterns
new file mode 100644
index 00000000000..a23e05630c9
--- /dev/null
+++ b/app/tests/macros/param-then-no-param/events.patterns
@@ -0,0 +1,2 @@
+s/.*hid_listener_keycode/kp/p
+s/.*mo_keymap_binding/mo/p
diff --git a/app/tests/macros/param-then-no-param/keycode_events.snapshot b/app/tests/macros/param-then-no-param/keycode_events.snapshot
new file mode 100644
index 00000000000..aee0e1e6bdb
--- /dev/null
+++ b/app/tests/macros/param-then-no-param/keycode_events.snapshot
@@ -0,0 +1,8 @@
+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
+mo_pressed: position 0 layer 1
+kp_pressed: usage_page 0x07 keycode 0x1C implicit_mods 0x00 explicit_mods 0x00
+kp_released: usage_page 0x07 keycode 0x1C implicit_mods 0x00 explicit_mods 0x00
+mo_released: position 0 layer 1
+kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
+kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
diff --git a/app/tests/macros/param-then-no-param/native_posix_64.keymap b/app/tests/macros/param-then-no-param/native_posix_64.keymap
new file mode 100644
index 00000000000..e6c42901cab
--- /dev/null
+++ b/app/tests/macros/param-then-no-param/native_posix_64.keymap
@@ -0,0 +1,47 @@
+#include <dt-bindings/zmk/keys.h>
+#include <behaviors.dtsi>
+#include <dt-bindings/zmk/kscan_mock.h>
+
+/ {
+    macros {
+        kp_l1: kp_l1 {
+            compatible = "zmk,behavior-macro-one-param";
+            #binding-cells = <1>;
+            wait-ms = <5>;
+            tap-ms = <5>;
+            bindings
+            = <&macro_param_1to1>
+            , <&macro_tap &kp MACRO_PLACEHOLDER>
+            , <&macro_press &mo 1>
+            , <&macro_pause_for_release>
+            , <&macro_release &mo 1>;
+        };
+    };
+
+    keymap {
+        compatible = "zmk,keymap";
+
+        default_layer {
+            bindings = <
+            &kp_l1 A &kp B
+            &kp C    &none>;
+        };
+
+        other_layer {
+            bindings = <
+            &kp X    &kp Y
+            &kp Z    &none>;
+        };
+    };
+};
+
+&kscan {
+    events = <
+        ZMK_MOCK_PRESS(0,0,20)   // press macro (taps A)
+        ZMK_MOCK_PRESS(0,1,20)   // press Y
+        ZMK_MOCK_RELEASE(0,1,20) // release Y
+        ZMK_MOCK_RELEASE(0,0,20) // release macro
+        ZMK_MOCK_PRESS(1,0,10)   // press C
+        ZMK_MOCK_RELEASE(1,0,10) // release C
+    >;
+};