Dynamic macros
This commit is contained in:
parent
b735a051ce
commit
a94192c65d
22 changed files with 679 additions and 0 deletions
|
@ -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)
|
||||
|
|
11
app/Kconfig
11
app/Kconfig
|
@ -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"
|
||||
|
|
16
app/dts/bindings/behaviors/zmk,behavior-dynamic-macro.yaml
Normal file
16
app/dts/bindings/behaviors/zmk,behavior-dynamic-macro.yaml
Normal 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
|
8
app/include/dt-bindings/zmk/dynamic-macros.h
Normal file
8
app/include/dt-bindings/zmk/dynamic-macros.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#define PLAY 0
|
||||
#define RECORD 1
|
206
app/src/behaviors/behavior_dynamic_macro.c
Normal file
206
app/src/behaviors/behavior_dynamic_macro.c
Normal 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, ¯o) == -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)
|
3
app/tests/dynamic-macros/basic/events.patterns
Normal file
3
app/tests/dynamic-macros/basic/events.patterns
Normal 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
|
28
app/tests/dynamic-macros/basic/keycode_events.snapshot
Normal file
28
app/tests/dynamic-macros/basic/keycode_events.snapshot
Normal 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
|
27
app/tests/dynamic-macros/basic/native_posix_64.keymap
Normal file
27
app/tests/dynamic-macros/basic/native_posix_64.keymap
Normal 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)
|
||||
>;
|
||||
};
|
60
app/tests/dynamic-macros/behavior_keymap.dtsi
Normal file
60
app/tests/dynamic-macros/behavior_keymap.dtsi
Normal 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>;
|
||||
};
|
||||
|
||||
};
|
||||
};
|
3
app/tests/dynamic-macros/call-macro/events.patterns
Normal file
3
app/tests/dynamic-macros/call-macro/events.patterns
Normal 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
|
40
app/tests/dynamic-macros/call-macro/keycode_events.snapshot
Normal file
40
app/tests/dynamic-macros/call-macro/keycode_events.snapshot
Normal 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
|
27
app/tests/dynamic-macros/call-macro/native_posix_64.keymap
Normal file
27
app/tests/dynamic-macros/call-macro/native_posix_64.keymap
Normal 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)
|
||||
>;
|
||||
};
|
3
app/tests/dynamic-macros/interrupt/events.patterns
Normal file
3
app/tests/dynamic-macros/interrupt/events.patterns
Normal 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
|
28
app/tests/dynamic-macros/interrupt/keycode_events.snapshot
Normal file
28
app/tests/dynamic-macros/interrupt/keycode_events.snapshot
Normal 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
|
27
app/tests/dynamic-macros/interrupt/native_posix_64.keymap
Normal file
27
app/tests/dynamic-macros/interrupt/native_posix_64.keymap
Normal 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)
|
||||
>;
|
||||
};
|
59
app/tests/dynamic-macros/recorded-time/behavior_keymap.dtsi
Normal file
59
app/tests/dynamic-macros/recorded-time/behavior_keymap.dtsi
Normal 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>;
|
||||
};
|
||||
|
||||
};
|
||||
};
|
3
app/tests/dynamic-macros/recorded-time/events.patterns
Normal file
3
app/tests/dynamic-macros/recorded-time/events.patterns
Normal 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
|
|
@ -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
|
|
@ -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)
|
||||
>;
|
||||
};
|
72
docs/docs/behaviors/dynamic-macros.md
Normal file
72
docs/docs/behaviors/dynamic-macros.md
Normal 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.
|
|
@ -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 | ✅ | ✅ | |
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue