Dynamic macros

This commit is contained in:
Nick Conway 2023-06-02 20:53:59 -04:00
parent b735a051ce
commit a94192c65d
No known key found for this signature in database
GPG key ID: AA850592E4C1D453
22 changed files with 679 additions and 0 deletions

View file

@ -52,6 +52,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources(app PRIVATE src/behaviors/behavior_caps_word.c)
target_sources(app PRIVATE src/behaviors/behavior_key_repeat.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_MACRO app PRIVATE src/behaviors/behavior_macro.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_DYNAMIC_MACRO app PRIVATE src/behaviors/behavior_dynamic_macro.c)
target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_mod_morph.c)
target_sources(app PRIVATE src/behaviors/behavior_outputs.c)

View file

@ -478,6 +478,11 @@ config ZMK_BEHAVIORS_QUEUE_SIZE
int "Maximum number of behaviors to allow queueing from a macro or other complex behavior"
default 64
config ZMK_DYNAMIC_MACRO_MAX_ACTIONS
int "Maximum number of keystrokes to be recorded in a dynamic macro"
default 64
DT_COMPAT_ZMK_BEHAVIOR_KEY_TOGGLE := zmk,behavior-key-toggle
rsource "Kconfig.behaviors"
config ZMK_MACRO_DEFAULT_WAIT_MS
@ -715,6 +720,12 @@ choice CBPRINTF_IMPLEMENTATION
endchoice
DT_COMPAT_ZMK_BEHAVIOR_DYNAMIC_MACRO := zmk,behavior-dynamic-macro
config ZMK_BEHAVIOR_DYNAMIC_MACRO
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_BEHAVIOR_DYNAMIC_MACRO))
module = ZMK
module-str = zmk
source "subsys/logging/Kconfig.template.log_config"

View file

@ -0,0 +1,16 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Dynamic Macro Behavior
compatible: "zmk,behavior-dynamic-macro"
include: one_param.yaml
properties:
wait-ms:
type: int
default: -1
description: The default time to wait (in milliseconds) before triggering the next behavior in the macro bindings list.
no-output:
type: boolean

View file

@ -0,0 +1,8 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define PLAY 0
#define RECORD 1

View file

@ -0,0 +1,206 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/behavior.h>
#include <zmk/behavior_queue.h>
#include <zmk/keymap.h>
#include <zmk/events/keycode_state_changed.h>
#include <dt-bindings/zmk/dynamic-macros.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define DT_DRV_COMPAT zmk_behavior_dynamic_macro
#define HID_KEY_USAGE_PAGE 0x70000
int8_t total_recorded_actions = 0;
struct behavior_dynamic_macro_bind {
uint32_t wait_ms;
bool pressed;
struct zmk_behavior_binding binding;
};
struct behavior_dynamic_macro_state {
bool recording;
uint32_t lastEventTime;
uint32_t count;
struct behavior_dynamic_macro_bind bindings[CONFIG_ZMK_DYNAMIC_MACRO_MAX_ACTIONS];
};
struct behavior_dynamic_macro_config {
uint32_t wait_ms;
bool no_output;
};
#define ZMK_BHV_RECORDING_MACRO_MAX 10
struct recording_macro {
uint32_t count;
uint32_t position;
bool recording;
const struct behavior_dynamic_macro_config *config;
struct behavior_dynamic_macro_state *state;
};
struct recording_macro recording_macros[ZMK_BHV_RECORDING_MACRO_MAX] = {};
static struct recording_macro *find_recording_macro(uint32_t position) {
for (int i = 0; i < ZMK_BHV_RECORDING_MACRO_MAX; i++) {
if (recording_macros[i].position == position && recording_macros[i].recording) {
return &recording_macros[i];
}
}
return NULL;
}
static int new_recording_macro(uint32_t position,
const struct behavior_dynamic_macro_config *config,
struct behavior_dynamic_macro_state *state,
struct recording_macro **macro) {
for (int i = 0; i < ZMK_BHV_RECORDING_MACRO_MAX; i++) {
struct recording_macro *const ref_macro = &recording_macros[i];
if (!ref_macro->recording) {
ref_macro->recording = true;
ref_macro->count = 0;
ref_macro->position = position;
ref_macro->config = config;
ref_macro->state = state;
*macro = ref_macro;
return 0;
}
}
return -ENOMEM;
}
static void queue_dynamic_macro(uint32_t position, uint32_t time,
struct behavior_dynamic_macro_state *state) {
LOG_DBG("Iterating dynamic macro bindings - count: %d", state->count);
for (int i = 0; i < state->count; i++) {
uint32_t wait_ms = time;
if (time == -1) {
wait_ms = state->bindings[i].wait_ms;
}
zmk_behavior_queue_add(position, state->bindings[i].binding, state->bindings[i].pressed,
wait_ms);
}
}
static int on_dynamic_macro_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
const struct device *dev = device_get_binding(binding->behavior_dev);
const struct behavior_dynamic_macro_config *cfg = dev->config;
struct behavior_dynamic_macro_state *state = dev->data;
if (binding->param1 == PLAY) {
if (state->recording) {
LOG_ERR("Macro is currently recording, can't play");
} else {
LOG_DBG("Playing Dynamic Macro");
queue_dynamic_macro(event.position, cfg->wait_ms, state);
}
} else if (binding->param1 == RECORD) {
state->recording = !state->recording;
LOG_DBG("Recording Status: %d", state->recording);
if (state->recording) {
struct recording_macro *macro;
macro = find_recording_macro(event.position);
if (new_recording_macro(event.position, cfg, state, &macro) == -ENOMEM) {
LOG_ERR("Unable to record new macro. Insufficient space in recording_macros[]");
return ZMK_BEHAVIOR_OPAQUE;
}
LOG_DBG("Recording new macro: %d", event.position);
if (macro) {
total_recorded_actions -= macro->count;
}
macro->count = 0;
} else {
struct recording_macro *macro;
macro = find_recording_macro(event.position);
macro->recording = false;
macro->state->count = macro->count;
}
}
return ZMK_BEHAVIOR_OPAQUE;
}
static int on_dynamic_macro_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return ZMK_BEHAVIOR_OPAQUE;
}
static int behavior_dynamic_macro_init(const struct device *dev) { return 0; };
static const struct behavior_driver_api behavior_dynamic_macro_driver_api = {
.binding_pressed = on_dynamic_macro_binding_pressed,
.binding_released = on_dynamic_macro_binding_released,
};
static int dynamic_macro_keycode_state_changed_listener(const zmk_event_t *eh);
ZMK_LISTENER(behavior_dynamic_macro, dynamic_macro_keycode_state_changed_listener);
ZMK_SUBSCRIPTION(behavior_dynamic_macro, zmk_keycode_state_changed);
static int dynamic_macro_keycode_state_changed_listener(const zmk_event_t *eh) {
struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh);
if (ev == NULL) {
return ZMK_EV_EVENT_BUBBLE;
}
for (int i = 0; i < ZMK_BHV_RECORDING_MACRO_MAX; i++) {
struct recording_macro *macro = &recording_macros[i];
if (macro->recording && total_recorded_actions < CONFIG_ZMK_DYNAMIC_MACRO_MAX_ACTIONS) {
uint32_t eventTime = k_uptime_get();
uint32_t elapsedTime = eventTime - macro->state->lastEventTime;
macro->state->lastEventTime = eventTime;
if (ev->state) {
macro->state->bindings[macro->count].pressed = true;
} else {
macro->state->bindings[macro->count].pressed = false;
}
macro->state->bindings[macro->count].binding.behavior_dev = "KEY_PRESS";
macro->state->bindings[macro->count].binding.param1 = HID_KEY_USAGE_PAGE + ev->keycode;
macro->state->bindings[macro->count].binding.param2 = 0;
if (macro->count > 0) {
macro->state->bindings[macro->count - 1].wait_ms = elapsedTime;
}
macro->count++;
total_recorded_actions++;
if (macro->config->no_output) {
return ZMK_EV_EVENT_HANDLED;
}
return ZMK_EV_EVENT_BUBBLE;
} else if (total_recorded_actions >= CONFIG_ZMK_DYNAMIC_MACRO_MAX_ACTIONS) {
LOG_ERR(
"Action not recorded, not enough space, CONFIG_ZMK_DYNAMIC_MACRO_MAX_ACTIONS %d",
CONFIG_ZMK_DYNAMIC_MACRO_MAX_ACTIONS);
if (macro->config->no_output) {
return ZMK_EV_EVENT_HANDLED;
}
return ZMK_EV_EVENT_BUBBLE;
}
}
return ZMK_EV_EVENT_BUBBLE;
}
#define DYNAMIC_MACRO_INST(n) \
static struct behavior_dynamic_macro_state behavior_dynamic_macro_state_##n = { \
.recording = false, .count = 0}; \
static struct behavior_dynamic_macro_config behavior_dynamic_macro_config_##n = { \
.wait_ms = DT_INST_PROP_OR(n, wait_ms, -1), .no_output = DT_INST_PROP(n, no_output)}; \
DEVICE_DT_INST_DEFINE(n, behavior_dynamic_macro_init, NULL, &behavior_dynamic_macro_state_##n, \
&behavior_dynamic_macro_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&behavior_dynamic_macro_driver_api);
DT_INST_FOREACH_STATUS_OKAY(DYNAMIC_MACRO_INST)

View file

@ -0,0 +1,3 @@
s/.*hid_listener_keycode/kp/p
s/.*behavior_queue_process_next/queue_process_next/p
s/.*macro_binding/mac/p

View file

@ -0,0 +1,28 @@
mac_pressed: Recording Status: 1
mac_pressed: Recording new macro: 0
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
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
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
mac_pressed: Recording Status: 0
mac_pressed: Playing Dynamic Macro
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 10ms
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: 0x70004 0x00
kp_pressed: 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: 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: 0x70004 0x00
kp_pressed: 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: 0x70004 0x00
kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
queue_process_next: Processing next queued behavior in 10ms

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,100)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,100)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,100)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,100)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,100)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,100)
>;
};

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include <dt-bindings/zmk/dynamic-macros.h>
/ {
macros {
dm1: dm1 {
compatible = "zmk,behavior-dynamic-macro";
label = "dynamic-macro-1";
wait-ms = <10>;
#binding-cells = <1>;
};
dm2: dm2 {
compatible = "zmk,behavior-dynamic-macro";
label = "dynamic-macro-2";
wait-ms = <10>;
#binding-cells = <1>;
};
rm1: rm1 {
compatible = "zmk,behavior-macro";
label = "macro-1";
wait-ms = <10>;
tap-ms = <10>;
#binding-cells = <0>;
bindings = <&kp A &kp A &kp A>;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&dm1 RECORD &dm1 PLAY
&kp A &tog 1>;
};
extra_layer {
bindings = <
&kp N1 &rm1
&tog 2 &trans>;
};
extra_layer_2 {
bindings = <
&dm2 RECORD &dm2 PLAY
&trans &kp N1>;
};
};
};

View file

@ -0,0 +1,3 @@
s/.*hid_listener_keycode/kp/p
s/.*behavior_queue_process_next/queue_process_next/p
s/.*macro_binding/mac/p

View file

@ -0,0 +1,40 @@
mac_pressed: Recording Status: 1
mac_pressed: Recording new macro: 0
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 10ms
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: 0x70004 0x00
kp_pressed: 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: 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: 0x70004 0x00
kp_pressed: 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: 0x70004 0x00
kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
queue_process_next: Processing next queued behavior in 10ms
mac_pressed: Recording Status: 0
mac_pressed: Playing Dynamic Macro
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 10ms
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: 0x70004 0x00
kp_pressed: 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: 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: 0x70004 0x00
kp_pressed: 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: 0x70004 0x00
kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
queue_process_next: Processing next queued behavior in 10ms

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,100)
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,100)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,100)
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,100)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,100)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,100)
>;
};

View file

@ -0,0 +1,3 @@
s/.*hid_listener_keycode/kp/p
s/.*behavior_queue_process_next/queue_process_next/p
s/.*macro_binding/mac/p

View file

@ -0,0 +1,28 @@
mac_pressed: Recording Status: 1
mac_pressed: Recording new macro: 0
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
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
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
mac_pressed: Recording Status: 0
mac_pressed: Playing Dynamic Macro
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 10ms
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: 0x70004 0x00
kp_pressed: 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: 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: 0x70004 0x00
kp_pressed: 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: 0x70004 0x00
kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
queue_process_next: Processing next queued behavior in 10ms

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,100)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,100)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,100)
ZMK_MOCK_RELEASE(0,0,100)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,100)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,100)
>;
};

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include <dt-bindings/zmk/dynamic-macros.h>
/ {
macros {
dm1: dm1 {
compatible = "zmk,behavior-dynamic-macro";
label = "dynamic-macro-1";
#binding-cells = <1>;
};
dm2: dm2 {
compatible = "zmk,behavior-dynamic-macro";
label = "dynamic-macro-2";
wait-ms = <10>;
#binding-cells = <1>;
};
rm1: rm1 {
compatible = "zmk,behavior-macro";
label = "macro-1";
wait-ms = <10>;
tap-ms = <10>;
#binding-cells = <0>;
bindings = <&kp A &kp A &kp A>;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&dm1 RECORD &dm1 PLAY
&kp A &tog 1>;
};
extra_layer {
bindings = <
&kp N1 &rm1
&tog 2 &trans>;
};
extra_layer_2 {
bindings = <
&dm2 RECORD &dm2 PLAY
&trans &kp N1>;
};
};
};

View file

@ -0,0 +1,3 @@
s/.*hid_listener_keycode/kp/p
s/.*behavior_queue_process_next/queue_process_next/p
s/.*macro_binding/mac/p

View file

@ -0,0 +1,28 @@
mac_pressed: Recording Status: 1
mac_pressed: Recording new macro: 0
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
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
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
mac_pressed: Recording Status: 0
mac_pressed: Playing Dynamic Macro
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 11ms
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 1001ms
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 11ms
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 1001ms
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 11ms
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 0ms

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,100)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,1000)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,1000)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,1000)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,100)
ZMK_MOCK_PRESS(0,1,10000)
ZMK_MOCK_RELEASE(0,1,100)
>;
};

View file

@ -0,0 +1,72 @@
---
title: Dynamic Macro Behavior
sidebar_label: Dynamic Macros
---
## Summary
The dynamic macro behavior allows creating macros and replaying them by recording key presses. While recording a macro, you can also play another macro.
:::note
Dynamic macros are cleared on reboot.
:::
:::warning
Dynamic macros are memory intensive, and may cause the firmware to crash. It is recommended to use only 1 dynamic macro in your keymap and re-record it if it needs changed. The maximum amount of actions to be recorded can be set with `CONFIG_ZMK_DYNAMIC_MACRO_MAX_ACTIONS` (default 64).
:::
## Dynamic Macro Action Defines
Dynamic macro action defines are provided through the [`dt-bindings/zmk/dynamic-macros.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/dynamic-macros.h) header,
which is added at the top of the keymap file:
```
#include <dt-bindings/zmk/dynamic-macros.h>
```
This will allow you to reference the actions defined in this header such as `PLAY`.
Here is a table describing the action for each define:
| Define | Action |
| -------- | --------------------------- |
| `PLAY` | Play back a recorded macro |
| `RECORD` | Toggle recording of a macro |
## Macro Definition
A dynamic macro definition looks like:
```
/ {
macros {
dyn-macro: dyn-macro {
label = "ZM_dynamic-macro";
compatible = "zmk,behavior-dynamic-macro";
#binding-cells = <1>;
};
};
};
```
The macro can then be bound in your keymap by referencing it by the label `dyn-macro` followed by PLAY or RECORD, e.g.:
```
/ {
keymap {
&dyn-macro PLAY &dyn-macro RECORD
...
};
};
```
## Configuration
### Wait Time
The wait time setting controls how long of a delay is introduced between behaviors. By default, a macro will play back at the speed it
was recorded, but it can be overwritten by assigning a value to the `wait-ms` property of the macro, e.g. `wait-ms = <20>;`.
### No Output
By default, keystrokes will still be sent to the host while a dynamic macro is recording. Setting `no-output` will change this and will not send keystrokes to the host while recording.

View file

@ -33,6 +33,7 @@ ZMK is currently missing some features found in other popular firmware. This tab
| One Shot Keys | ✅ | ✅ | ✅ |
| [Combo Keys](features/combos.md) | ✅ | | ✅ |
| [Macros](behaviors/macros.md) | ✅ | ✅ | ✅ |
| [Dynamic Macros](behaviors/dynamic-macros.md) | ✅ | | ✅ |
| Mouse Keys | 🚧 | ✅ | ✅ |
| Low Active Power Usage | ✅ | | |
| Low Power Sleep States | ✅ | ✅ | |

View file

@ -61,6 +61,7 @@ module.exports = {
"behaviors/hold-tap",
"behaviors/mod-tap",
"behaviors/mod-morph",
"behaviors/dynamic-macros",
"behaviors/macros",
"behaviors/key-toggle",
"behaviors/sticky-key",