From db1de28a6e68217a8cd57959a457ddb94f2312c4 Mon Sep 17 00:00:00 2001 From: Aaron Kollasch Date: Mon, 8 May 2023 23:29:50 -0400 Subject: [PATCH] Add macro-pause-for-layer behavior --- app/dts/behaviors/macros.dtsi | 6 + .../macros/zmk,macro-pause-for-layer.yaml | 8 ++ app/src/behaviors/behavior_macro.c | 103 +++++++++++++++++- docs/docs/behaviors/macros.md | 24 ++++ 4 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 app/dts/bindings/macros/zmk,macro-pause-for-layer.yaml diff --git a/app/dts/behaviors/macros.dtsi b/app/dts/behaviors/macros.dtsi index 36b4a8d33f3..afae3bd5361 100644 --- a/app/dts/behaviors/macros.dtsi +++ b/app/dts/behaviors/macros.dtsi @@ -91,5 +91,11 @@ name: name { \ label = "MAC_PARAM_2TO2"; #binding-cells = <0>; }; + + macro_pause_for_layer: macro_pause_for_layer { + compatible = "zmk,macro-pause-for-layer"; + label = "MAC_WAIT_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 00000000000..0c06fca65c9 --- /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 c47284531a7..6d0d309364a 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 DT_PROP(DT_INST(0, zmk_macro_control_mode_tap), label) #define PRESS_MODE DT_PROP(DT_INST(0, zmk_macro_control_mode_press), label) #define REL_MODE DT_PROP(DT_INST(0, zmk_macro_control_mode_release), label) @@ -51,6 +69,7 @@ struct behavior_macro_config { #define TAP_TIME DT_PROP(DT_INST(0, zmk_macro_control_tap_time), label) #define WAIT_TIME DT_PROP(DT_INST(0, zmk_macro_control_wait_time), label) #define WAIT_REL DT_PROP(DT_INST(0, zmk_macro_pause_for_release), label) +#define WAIT_LAYER DT_PROP(DT_INST(0, zmk_macro_pause_for_layer), label) #define P1TO1 DT_PROP(DT_INST(0, zmk_macro_param_1to1), label) #define P1TO2 DT_PROP(DT_INST(0, zmk_macro_param_1to2), label) @@ -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 40c333a9066..a8fe31b9e88 100644 --- a/docs/docs/behaviors/macros.md +++ b/docs/docs/behaviors/macros.md @@ -149,6 +149,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, 100ms by default, can