zmk/app/src/behaviors/behavior_dynamic_macro.c
2024-08-17 21:28:40 -04:00

206 lines
7.7 KiB
C

/*
* 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)