Skip to content

Commit

Permalink
Add macro-pause-for-layer behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronkollasch committed Jun 21, 2023
1 parent b259d5a commit db1de28
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 4 deletions.
6 changes: 6 additions & 0 deletions app/dts/behaviors/macros.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
};
};
};
8 changes: 8 additions & 0 deletions app/dts/bindings/macros/zmk,macro-pause-for-layer.yaml
Original file line number Diff line number Diff line change
@@ -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
103 changes: 99 additions & 4 deletions app/src/behaviors/behavior_macro.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <zmk/behavior.h>
#include <zmk/behavior_queue.h>
#include <zmk/keymap.h>
#include <zmk/event_manager.h>
#include <zmk/events/layer_state_changed.h>

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

Expand All @@ -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;
Expand All @@ -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;
};
Expand All @@ -44,13 +52,24 @@ 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)

#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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
}
Expand All @@ -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,
Expand Down
24 changes: 24 additions & 0 deletions docs/docs/behaviors/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `&macro_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
= <&macro_press &kp LGUI>
, <&macro_tap &kp TAB>
, <&macro_pause_for_layer>
, <&macro_release &kp LGUI>
;
```

Notes:

- `&macro_pause_for_layer` cannot currently be used in the same macro as `&macro_pause_for_release`.
- Because `&macro_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.
- `&macro_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 `&macro_press` to `&macro_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
Expand Down

0 comments on commit db1de28

Please sign in to comment.