diff --git a/app/Kconfig b/app/Kconfig index 2cde34cc..81637336 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -414,7 +414,7 @@ menu "Behavior Options" config ZMK_BEHAVIORS_QUEUE_SIZE int "Maximum number of behaviors to allow queueing from a macro or other complex behavior" - default 20 + default 64 endmenu diff --git a/app/dts/behaviors/macros.dtsi b/app/dts/behaviors/macros.dtsi index 37b364de..a455a706 100644 --- a/app/dts/behaviors/macros.dtsi +++ b/app/dts/behaviors/macros.dtsi @@ -4,6 +4,15 @@ * SPDX-License-Identifier: MIT */ +#define ZMK_MACRO_STRINGIFY(x) #x +#define ZMK_MACRO(name,...) \ + name: name { \ + label = ZMK_MACRO_STRINGIFY(ZM_ ## name); \ + compatible = "zmk,behavior-macro"; \ + #binding-cells = <0>; \ + __VA_ARGS__ \ + }; + / { behaviors { macro_tap: macro_control_mode_tap { diff --git a/app/src/behavior_queue.c b/app/src/behavior_queue.c index bba98d45..1c4a5a88 100644 --- a/app/src/behavior_queue.c +++ b/app/src/behavior_queue.c @@ -26,7 +26,6 @@ static K_DELAYED_WORK_DEFINE(queue_work, behavior_queue_process_next); static void behavior_queue_process_next(struct k_work *work) { struct q_item item = {.wait = 0}; - int ret; while (k_msgq_get(&zmk_behavior_queue_msgq, &item, K_NO_WAIT) == 0) { LOG_DBG("Invoking %s: 0x%02x 0x%02x", log_strdup(item.binding.behavior_dev), @@ -53,17 +52,14 @@ static void behavior_queue_process_next(struct k_work *work) { int zmk_behavior_queue_add(uint32_t position, const struct zmk_behavior_binding binding, bool press, uint32_t wait) { struct q_item item = {.press = press, .binding = binding, .wait = wait}; - int ret; - LOG_DBG(""); - - ret = k_msgq_put(&zmk_behavior_queue_msgq, &item, K_NO_WAIT); + const int ret = k_msgq_put(&zmk_behavior_queue_msgq, &item, K_NO_WAIT); if (ret < 0) { return ret; } if (!k_delayed_work_pending(&queue_work)) { - k_delayed_work_submit(&queue_work, K_NO_WAIT); + behavior_queue_process_next(&queue_work); } return 0; diff --git a/app/src/behaviors/behavior_macro.c b/app/src/behaviors/behavior_macro.c index 12a2be15..37d0fb3b 100644 --- a/app/src/behaviors/behavior_macro.c +++ b/app/src/behaviors/behavior_macro.c @@ -98,7 +98,7 @@ static int behavior_macro_init(const struct device *dev) { // Updated state used for initial state on release. } else if (IS_PAUSE(cfg->bindings[i].behavior_dev)) { state->release_state.start_index = i + 1; - state->release_state.count = cfg->count - i - 1; + state->release_state.count = cfg->count - state->release_state.start_index; state->press_bindings_count = i; LOG_DBG("Release will resume at %d", state->release_state.start_index); break; @@ -112,7 +112,7 @@ static int behavior_macro_init(const struct device *dev) { static void queue_macro(uint32_t position, const struct zmk_behavior_binding bindings[], struct behavior_macro_trigger_state state) { - LOG_DBG("Iterating macro bindings from %d-%d", state.start_index, state.count); + LOG_DBG("Iterating macro bindings - starting: %d, count: %d", state.start_index, state.count); for (int i = state.start_index; i < state.start_index + state.count; i++) { if (!handle_control_binding(&state, &bindings[i])) { switch (state.mode) { @@ -174,8 +174,8 @@ static const struct behavior_driver_api behavior_macro_driver_api = { #define MACRO_INST(n) \ static struct behavior_macro_state behavior_macro_state_##n = {}; \ static struct behavior_macro_config behavior_macro_config_##n = { \ - .default_wait_ms = DT_INST_PROP_OR(drv_inst, wait_ms, 100), \ - .default_tap_ms = DT_INST_PROP_OR(drv_inst, tap_ms, 100), \ + .default_wait_ms = DT_INST_PROP_OR(n, wait_ms, 100), \ + .default_tap_ms = DT_INST_PROP_OR(n, tap_ms, 100), \ .count = DT_INST_PROP_LEN(n, bindings), \ .bindings = TRANSFORMED_BEHAVIORS(n)}; \ DEVICE_DT_INST_DEFINE(n, behavior_macro_init, device_pm_control_nop, \ diff --git a/app/tests/macros/basic/events.patterns b/app/tests/macros/basic/events.patterns index 3c9d3f83..0a5f25ca 100644 --- a/app/tests/macros/basic/events.patterns +++ b/app/tests/macros/basic/events.patterns @@ -1 +1,2 @@ -s/.*hid_listener_keycode/kp/p \ No newline at end of file +s/.*hid_listener_keycode/kp/p +s/.*behavior_queue_process_next/queue_process_next/p \ No newline at end of file diff --git a/app/tests/macros/basic/keycode_events.snapshot b/app/tests/macros/basic/keycode_events.snapshot index 9fe52e6d..b238a2ff 100644 --- a/app/tests/macros/basic/keycode_events.snapshot +++ b/app/tests/macros/basic/keycode_events.snapshot @@ -1,6 +1,18 @@ +queue_process_next: Invoking KEY_PRESS: 0x70004 0x00 kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 50ms +queue_process_next: Invoking KEY_PRESS: 0x70004 0x00 kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 10ms +queue_process_next: Invoking KEY_PRESS: 0x70005 0x00 kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 50ms +queue_process_next: Invoking KEY_PRESS: 0x70005 0x00 kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 10ms +queue_process_next: Invoking KEY_PRESS: 0x70006 0x00 kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 50ms +queue_process_next: Invoking KEY_PRESS: 0x70006 0x00 kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 10ms diff --git a/app/tests/macros/behavior_keymap.dtsi b/app/tests/macros/behavior_keymap.dtsi index 6860f2d5..a9815159 100644 --- a/app/tests/macros/behavior_keymap.dtsi +++ b/app/tests/macros/behavior_keymap.dtsi @@ -10,42 +10,37 @@ / { macros { - abc_macro: abc_macro { - label = "ABCs"; - compatible = "zmk,behavior-macro"; - #binding-cells = <0>; + ZMK_MACRO( + abc_macro, + wait-ms = <10>; + tap-ms = <50>; bindings = <&kp A &kp B &kp C>; - }; + ) - hold_shift_macro: hold_shift_macro { - label = "HOLD_SHFT"; - compatible = "zmk,behavior-macro"; - #binding-cells = <0>; + ZMK_MACRO( + hold_shift_macro, bindings = <¯o_press &kp LSHFT> , <¯o_tap> , <&kp D &kp O &kp G> , <¯o_release &kp LSHFT> ; - }; + ) - custom_timing: custom_timing_macro { - label = "ABC_TIMING"; - compatible = "zmk,behavior-macro"; - #binding-cells = <0>; + ZMK_MACRO( + custom_timing, bindings = <¯o_wait_time 50> , <&kp A> , <¯o_tap_time 20> , <&kp B &kp C> ; - }; + ) - dual_sequence_macro: dual_sequence_macro { - label = "DUAL_SEQ"; - compatible = "zmk,behavior-macro"; - #binding-cells = <0>; + ZMK_MACRO( + dual_sequence_macro, wait-ms = <10>; + tap-ms = <40>; bindings = <¯o_press &kp LALT> , <¯o_tap> @@ -53,7 +48,7 @@ , <¯o_pause_for_release> , <¯o_release &kp LALT> ; - }; + ) }; keymap { diff --git a/app/tests/macros/mo-plus-modifier-from-hold-tap/events.patterns b/app/tests/macros/mo-plus-modifier-from-hold-tap/events.patterns new file mode 100644 index 00000000..3c9d3f83 --- /dev/null +++ b/app/tests/macros/mo-plus-modifier-from-hold-tap/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode/kp/p \ No newline at end of file diff --git a/app/tests/macros/mo-plus-modifier-from-hold-tap/keycode_events.snapshot b/app/tests/macros/mo-plus-modifier-from-hold-tap/keycode_events.snapshot new file mode 100644 index 00000000..9f47e064 --- /dev/null +++ b/app/tests/macros/mo-plus-modifier-from-hold-tap/keycode_events.snapshot @@ -0,0 +1,4 @@ +kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/macros/mo-plus-modifier-from-hold-tap/native_posix.keymap b/app/tests/macros/mo-plus-modifier-from-hold-tap/native_posix.keymap new file mode 100644 index 00000000..2f3b943a --- /dev/null +++ b/app/tests/macros/mo-plus-modifier-from-hold-tap/native_posix.keymap @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include + +/ { + macros { + ZMK_MACRO( + mo_mod_macro, + wait-ms = <0>; + tap-ms = <20>; + bindings + = <¯o_press &mo 1 &kp LSHFT> + , <¯o_pause_for_release> + , <¯o_release &mo 1 &kp LSHFT>; + ) + }; + + behaviors { + mth: macro_tap_hold { + compatible = "zmk,behavior-hold-tap"; + label = "MACRO_TAP_HOLD"; + #binding-cells = <2>; + flavor = "tap-unless-interrupted"; + tapping-term-ms = <200>; + bindings = <&mo_mod_macro>, <&kp>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mth 0 TAB &kp A + &kp B &kp C>; + }; + + extra_layer { + bindings = < + &kp D &kp E + &kp F &kp G>; + + }; + + }; +}; + +&kscan { + events = ; +}; \ No newline at end of file diff --git a/app/tests/macros/mo-plus-modifier-macro/events.patterns b/app/tests/macros/mo-plus-modifier-macro/events.patterns new file mode 100644 index 00000000..3c9d3f83 --- /dev/null +++ b/app/tests/macros/mo-plus-modifier-macro/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode/kp/p \ No newline at end of file diff --git a/app/tests/macros/mo-plus-modifier-macro/keycode_events.snapshot b/app/tests/macros/mo-plus-modifier-macro/keycode_events.snapshot new file mode 100644 index 00000000..9f47e064 --- /dev/null +++ b/app/tests/macros/mo-plus-modifier-macro/keycode_events.snapshot @@ -0,0 +1,4 @@ +kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/macros/mo-plus-modifier-macro/native_posix.keymap b/app/tests/macros/mo-plus-modifier-macro/native_posix.keymap new file mode 100644 index 00000000..c264a9ab --- /dev/null +++ b/app/tests/macros/mo-plus-modifier-macro/native_posix.keymap @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include + +/ { + macros { + ZMK_MACRO( + mo_mod_macro, + wait-ms = <0>; + tap-ms = <20>; + bindings + = <¯o_press &mo 1 &kp LSHFT> + , <¯o_pause_for_release> + , <¯o_release &mo 1 &kp LSHFT>; + ) + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mo_mod_macro &kp A + &kp B &kp C>; + }; + + extra_layer { + bindings = < + &kp D &kp E + &kp F &kp G>; + + }; + + }; +}; + +&kscan { + events = ; +}; \ No newline at end of file diff --git a/app/tests/macros/press-mid-macro/keycode_events.snapshot b/app/tests/macros/press-mid-macro/keycode_events.snapshot index 4fad9154..22393a3a 100644 --- a/app/tests/macros/press-mid-macro/keycode_events.snapshot +++ b/app/tests/macros/press-mid-macro/keycode_events.snapshot @@ -1,6 +1,6 @@ -pos_state: layer: 0 position: 0, binding name: ABCs +pos_state: layer: 0 position: 0, binding name: ZM_abc_macro kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 -pos_state: layer: 0 position: 0, binding name: ABCs +pos_state: layer: 0 position: 0, binding name: ZM_abc_macro pos_state: layer: 0 position: 1, binding name: MO pos_state: layer: 0 position: 1, binding name: MO kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/macros/wait-macro-release/events.patterns b/app/tests/macros/wait-macro-release/events.patterns index 3c9d3f83..02e0c505 100644 --- a/app/tests/macros/wait-macro-release/events.patterns +++ b/app/tests/macros/wait-macro-release/events.patterns @@ -1 +1,3 @@ -s/.*hid_listener_keycode/kp/p \ No newline at end of file +s/.*hid_listener_keycode/kp/p +s/.*behavior_queue_process_next/queue_process_next/p +s/.*queue_macro/qm/p \ No newline at end of file diff --git a/app/tests/macros/wait-macro-release/keycode_events.snapshot b/app/tests/macros/wait-macro-release/keycode_events.snapshot index f8e7bd2f..509a1c48 100644 --- a/app/tests/macros/wait-macro-release/keycode_events.snapshot +++ b/app/tests/macros/wait-macro-release/keycode_events.snapshot @@ -1,6 +1,16 @@ +qm: Iterating macro bindings - starting: 0, count: 4 +queue_process_next: Invoking KEY_PRESS: 0x700e2 0x00 kp_pressed: usage_page 0x07 keycode 0xe2 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 10ms +queue_process_next: Invoking KEY_PRESS: 0x7002b 0x00 +kp_pressed: usage_page 0x07 keycode 0x2b implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 40ms +queue_process_next: Invoking KEY_PRESS: 0x7002b 0x00 +kp_released: usage_page 0x07 keycode 0x2b implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 10ms kp_pressed: usage_page 0x07 keycode 0x2b implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x2b implicit_mods 0x00 explicit_mods 0x00 -kp_pressed: usage_page 0x07 keycode 0x2b implicit_mods 0x00 explicit_mods 0x00 -kp_released: usage_page 0x07 keycode 0x2b implicit_mods 0x00 explicit_mods 0x00 +qm: Iterating macro bindings - starting: 5, count: 2 +queue_process_next: Invoking KEY_PRESS: 0x700e2 0x00 kp_released: usage_page 0x07 keycode 0xe2 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 0ms diff --git a/docs/docs/behaviors/macros.md b/docs/docs/behaviors/macros.md index 04e393ba..59bdd70a 100644 --- a/docs/docs/behaviors/macros.md +++ b/docs/docs/behaviors/macros.md @@ -40,9 +40,9 @@ used to reference the macro in your keymap The macro can then be bound in your keymap by referencing it by the label `&zed_em_kay`, e.g.: ``` - raise_layer { - bindings = <&zed_em_kay>; - }; + raise_layer { + bindings = <&zed_em_kay>; + }; ``` ### Bindings @@ -99,10 +99,10 @@ To pause the macro until release, use `¯o_pause_for_release`, like in this e ``` bindings - = <¯o_press &mo 1 &kp LSHFT> - , <¯o_wait_for_release> - , <¯o_release &mo 1 &kp LSHFT> - ; + = <¯o_press &mo 1 &kp LSHFT> + , <¯o_pause_for_release> + , <¯o_release &mo 1 &kp LSHFT> + ; ``` ### Wait Time @@ -129,8 +129,54 @@ point in a macro bindings list, use `¯o_tap_time`, e.g. `¯o_tap_time 30` ``` bindings = <¯o_tap_time 10> - , <&kp S &kp H &kp O &kp R &kp T> + , <&kp S &kp H &kp O &kp R &kp T> , <¯o_tap_time 500> , <&kp L &kp O &kp N &kp G> ; ``` + +## Common Patterns + +### Layer Activation + More + +Macros make it easy to combine a [layer behavior](/docs/behaviors/layers), e.g. `&mo` with another behavior at the same time. +Common examples are enabling one or more modifiers when the layer is active, or changing the RBG underglow color. + +To achieve this, a combination of a 0ms wait time and splitting the press and release between a `¯o_pause_for_release` is used: + +#### Layer + Modifier + +``` +wait-ms = <0>; +bindings + = <¯o_press &mo 1 &kp LSHFT> + , <¯o_pause_for_release> + , <¯o_release &mo 1 &kp LSHFT>; +``` + +#### Layer + Underglow Color + +To trigged a different underglow when the macro is pressed, and when it is released, we use the macro "press" activation mode whenever triggering the `&rgb_ug` behavior: + +``` +wait-ms = <0>; +bindings + = <¯o_press &mo 1 &rgb_ug RGB_COLOR_HSB(128,100,100)> + , <¯o_pause_for_release> + , <¯o_release &mo 1 ¯o_press &rgb_ug RGB_COLOR_HSB(300,100,50)>; +``` + +## Keycode Sequences + +The other common use case for macros is to sending sequences of keycodes to the connected host. Here, a wait and tap time of at least 30ms is recommended to +avoid having HID notifications grouped at the BLE protocol level and then processed out of order: + +``` +wait-ms = <40>; +tap-ms = <40>; +bindings + = <&kp Z &kp M &kp K> + , <&kp SPACE> + , <&kp R &kp O &kp C &kp K &kp S> + ; +``` \ No newline at end of file