diff --git a/app/dts/behaviors/macros.dtsi b/app/dts/behaviors/macros.dtsi index 44bc7ab7..ddd76832 100644 --- a/app/dts/behaviors/macros.dtsi +++ b/app/dts/behaviors/macros.dtsi @@ -77,5 +77,10 @@ name: name { \ compatible = "zmk,macro-param-2to2"; #binding-cells = <0>; }; + + macro_pause_for_layer: macro_pause_for_layer { + compatible = "zmk,macro-pause-for-layer"; + #binding-cells = <0>; + }; }; }; diff --git a/app/dts/bindings/macros/zmk,macro-pause-for-layer.yaml b/app/dts/bindings/macros/zmk,macro-pause-for-layer.yaml new file mode 100644 index 00000000..0c06fca6 --- /dev/null +++ b/app/dts/bindings/macros/zmk,macro-pause-for-layer.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Macro Pause Until Layer Change + +compatible: "zmk,macro-pause-for-layer" + +include: zero_param.yaml diff --git a/app/src/behaviors/behavior_macro.c b/app/src/behaviors/behavior_macro.c index acffe3d8..1a615583 100644 --- a/app/src/behaviors/behavior_macro.c +++ b/app/src/behaviors/behavior_macro.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -21,6 +23,11 @@ enum behavior_macro_mode { enum param_source { PARAM_SOURCE_BINDING, PARAM_SOURCE_MACRO_1ST, PARAM_SOURCE_MACRO_2ND }; +enum behavior_macro_release_trigger { + MACRO_RELEASE_KEY, + MACRO_RELEASE_LAYER, +}; + struct behavior_macro_trigger_state { uint32_t wait_ms; uint32_t tap_ms; @@ -33,6 +40,7 @@ struct behavior_macro_trigger_state { struct behavior_macro_state { struct behavior_macro_trigger_state release_state; + enum behavior_macro_release_trigger release_trigger; uint32_t press_bindings_count; }; @@ -44,6 +52,16 @@ struct behavior_macro_config { struct zmk_behavior_binding bindings[]; }; +#define MACRO_SCHEDULED_QUEUE_SIZE 16 +struct behavior_macro_scheduled_state { + int layer; + uint32_t position; + struct zmk_behavior_binding binding; +}; +static struct behavior_macro_scheduled_state + behavior_macro_scheduled_states[MACRO_SCHEDULED_QUEUE_SIZE]; +static int behavior_macro_scheduled_states_count = 0; + #define TAP_MODE DEVICE_DT_NAME(DT_INST(0, zmk_macro_control_mode_tap)) #define PRESS_MODE DEVICE_DT_NAME(DT_INST(0, zmk_macro_control_mode_press)) #define REL_MODE DEVICE_DT_NAME(DT_INST(0, zmk_macro_control_mode_release)) @@ -51,6 +69,7 @@ struct behavior_macro_config { #define TAP_TIME DEVICE_DT_NAME(DT_INST(0, zmk_macro_control_tap_time)) #define WAIT_TIME DEVICE_DT_NAME(DT_INST(0, zmk_macro_control_wait_time)) #define WAIT_REL DEVICE_DT_NAME(DT_INST(0, zmk_macro_pause_for_release)) +#define WAIT_LAYER DEVICE_DT_NAME(DT_INST(0, zmk_macro_pause_for_layer)) #define P1TO1 DEVICE_DT_NAME(DT_INST(0, zmk_macro_param_1to1)) #define P1TO2 DEVICE_DT_NAME(DT_INST(0, zmk_macro_param_1to2)) @@ -65,6 +84,7 @@ struct behavior_macro_config { #define IS_TAP_TIME(dev) ZM_IS_NODE_MATCH(dev, TAP_TIME) #define IS_WAIT_TIME(dev) ZM_IS_NODE_MATCH(dev, WAIT_TIME) #define IS_PAUSE(dev) ZM_IS_NODE_MATCH(dev, WAIT_REL) +#define IS_LAYER_PAUSE(dev) ZM_IS_NODE_MATCH(dev, WAIT_LAYER) #define IS_P1TO1(dev) ZM_IS_NODE_MATCH(dev, P1TO1) #define IS_P1TO2(dev) ZM_IS_NODE_MATCH(dev, P1TO2) @@ -124,6 +144,13 @@ static int behavior_macro_init(const struct device *dev) { state->press_bindings_count = i; LOG_DBG("Release will resume at %d", state->release_state.start_index); break; + } else if (IS_LAYER_PAUSE(cfg->bindings[i].behavior_dev)) { + state->release_state.start_index = i + 1; + state->release_state.count = cfg->count - state->release_state.start_index; + state->release_trigger = MACRO_RELEASE_LAYER; + state->press_bindings_count = i + 1; + LOG_DBG("Layer change will resume at %d", state->release_state.start_index); + break; } else { // Ignore regular invokable bindings } @@ -156,10 +183,31 @@ static void replace_params(struct behavior_macro_trigger_state *state, static void queue_macro(uint32_t position, const struct zmk_behavior_binding bindings[], struct behavior_macro_trigger_state state, - const struct zmk_behavior_binding *macro_binding) { + const struct zmk_behavior_binding *macro_binding, int layer) { 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])) { + if (IS_LAYER_PAUSE(bindings[i].behavior_dev)) { + char *behavior_dev = macro_binding->behavior_dev; + if (behavior_macro_scheduled_states_count < MACRO_SCHEDULED_QUEUE_SIZE) { + behavior_macro_scheduled_states[behavior_macro_scheduled_states_count].position = + position; + behavior_macro_scheduled_states[behavior_macro_scheduled_states_count].layer = + layer; + behavior_macro_scheduled_states[behavior_macro_scheduled_states_count].binding = + *macro_binding; + LOG_DBG("macro_layer: scheduling resume behavior %s on layer %d deactivation", + behavior_dev, layer); + behavior_macro_scheduled_states_count++; + } else { + LOG_ERR("macro_layer: queue size %d exceeded; running behavior %s immediately", + MACRO_SCHEDULED_QUEUE_SIZE, behavior_dev); + const struct device *dev = device_get_binding(behavior_dev); + const struct behavior_macro_config *cfg = dev->config; + struct behavior_macro_state *dev_state = dev->data; + queue_macro(position, cfg->bindings, dev_state->release_state, macro_binding, + layer); + } + } else if (!handle_control_binding(&state, &bindings[i])) { struct zmk_behavior_binding binding = bindings[i]; replace_params(&state, &binding, macro_binding); @@ -193,7 +241,7 @@ static int on_macro_binding_pressed(struct zmk_behavior_binding *binding, .start_index = 0, .count = state->press_bindings_count}; - queue_macro(event.position, cfg->bindings, trigger_state, binding); + queue_macro(event.position, cfg->bindings, trigger_state, binding, event.layer); return ZMK_BEHAVIOR_OPAQUE; } @@ -204,11 +252,58 @@ static int on_macro_binding_released(struct zmk_behavior_binding *binding, const struct behavior_macro_config *cfg = dev->config; struct behavior_macro_state *state = dev->data; - queue_macro(event.position, cfg->bindings, state->release_state, binding); + if (state->release_trigger == MACRO_RELEASE_KEY) { + queue_macro(event.position, cfg->bindings, state->release_state, binding, event.layer); + } else { + LOG_DBG("skipping macro release with trigger %d", state->release_trigger); + } return ZMK_BEHAVIOR_OPAQUE; } +static int macro_layer_state_changed_listener(const zmk_event_t *eh); + +ZMK_LISTENER(behavior_macro, macro_layer_state_changed_listener); +ZMK_SUBSCRIPTION(behavior_macro, zmk_layer_state_changed); + +static int macro_layer_state_changed_listener(const zmk_event_t *eh) { + struct zmk_layer_state_changed *ev = as_zmk_layer_state_changed(eh); + if (ev == NULL || behavior_macro_scheduled_states_count == 0) { + return ZMK_EV_EVENT_BUBBLE; + } + for (int i = 0; i < behavior_macro_scheduled_states_count; i++) { + const struct zmk_behavior_binding *binding = &behavior_macro_scheduled_states[i].binding; + char *behavior_dev = binding->behavior_dev; + int layer = behavior_macro_scheduled_states[i].layer; + if (behavior_dev == NULL || ev->layer != layer || ev->state == true) { + continue; + } + behavior_macro_scheduled_states[i].binding.behavior_dev = NULL; + const struct device *dev = device_get_binding(behavior_dev); + if (dev == NULL) { + LOG_ERR("unable to find device for behavior %s", behavior_dev); + continue; + } + const struct behavior_macro_config *cfg = dev->config; + struct behavior_macro_state *state = dev->data; + if (state->release_trigger == MACRO_RELEASE_LAYER) { + LOG_DBG("macro_layer: running scheduled behavior %s from index %d with %d behaviors", + behavior_dev, state->release_state.start_index, state->release_state.count); + queue_macro(behavior_macro_scheduled_states[i].position, cfg->bindings, + state->release_state, binding, behavior_macro_scheduled_states[i].layer); + } + } + int new_count = 0; + for (int i = 0; i < behavior_macro_scheduled_states_count; i++) { + if (behavior_macro_scheduled_states[i].binding.behavior_dev != NULL) { + behavior_macro_scheduled_states[new_count] = behavior_macro_scheduled_states[i]; + new_count++; + } + } + behavior_macro_scheduled_states_count = new_count; + return ZMK_EV_EVENT_BUBBLE; +} + static const struct behavior_driver_api behavior_macro_driver_api = { .binding_pressed = on_macro_binding_pressed, .binding_released = on_macro_binding_released, diff --git a/docs/docs/behaviors/macros.md b/docs/docs/behaviors/macros.md index 50c8945e..6a6d875f 100644 --- a/docs/docs/behaviors/macros.md +++ b/docs/docs/behaviors/macros.md @@ -108,6 +108,30 @@ bindings ; ``` +### Processing Continuation on Layer Change + +The macro can also be paused so that the remaining part of the `bindings` list is processed when the layer containing the macro is turned off. + +To pause the macro until layer change, use `¯o_pause_for_layer`. For example, this macro will press a modifier and tap a key, then hold that modifier until the current layer is deactivated. + +``` +bindings + = <¯o_press &kp LGUI> + , <¯o_tap &kp TAB> + , <¯o_pause_for_layer> + , <¯o_release &kp LGUI> + ; +``` + +Notes: + +- `¯o_pause_for_layer` cannot currently be used in the same macro as `¯o_pause_for_release`. +- Because `¯o_pause_for_layer` waits for the layer containing the macro to be deactivated, it will not work if defined on the base default layer. + This includes all combo keys. +- `¯o_pause_for_layer` adds an entry to a queue of scheduled macro states to resume, and if the queue is full, subsequent macros are resumed immediately. + This is to prevent stuck keys due to an uneven ratio of `¯o_press` to `¯o_release` calls. + In the example above, LGUI will remain held even if the queue is already full, until the layer changes. The queue size is 16. + ### Wait Time The wait time setting controls how long of a delay is introduced between behaviors in the `bindings` list. The initial wait time for a macro,