Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Parameterised Mod Morph and Tap Dance #2724

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/dts/behaviors.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <behaviors/none.dtsi>
#include <behaviors/mod_tap.dtsi>
#include <behaviors/layer_tap.dtsi>
#include <behaviors/tap_dance.dtsi>
#include <behaviors/mod_morph.dtsi>
#include <behaviors/gresc.dtsi>
#include <behaviors/sticky_key.dtsi>
#include <behaviors/momentary_layer.dtsi>
Expand Down
24 changes: 24 additions & 0 deletions app/dts/behaviors/mod_morph.dtsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <dt-bindings/zmk/behaviors.h>
#include <dt-bindings/zmk/keys.h>

/ {
behaviors {
#if ZMK_BEHAVIOR_OMIT(MODMORPH)
/omit-if-no-ref/
#endif
mm: mod_morph_kp {
compatible = "zmk,behavior-mod-morph-param";
#binding-cells = <2>;
bindings = <&kp PLACEHOLDER>, <&kp PLACEHOLDER>;
display-name = "Mod-Morph";
mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>;
binding-params = <BINDING_PARAM(1,0) BINDING_PARAM(2,0)>;
};
};
};
23 changes: 23 additions & 0 deletions app/dts/behaviors/tap_dance.dtsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <dt-bindings/zmk/behaviors.h>

/ {
behaviors {
#if ZMK_BEHAVIOR_OMIT(TAPDANCE)
/omit-if-no-ref/
#endif
td: tap_dance_kp {
compatible = "zmk,behavior-tap-dance-param";
#binding-cells = <2>;
bindings = <&kp PLACEHOLDER>, <&kp PLACEHOLDER>;
display-name = "Tap-Dance";
tapping-term-ms = <200>;
binding-params = <BINDING_PARAM(1,0) BINDING_PARAM(2,0)>;
};
};
};
6 changes: 6 additions & 0 deletions app/dts/bindings/behaviors/binding_params.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT

properties:
binding-params:
type: array
13 changes: 13 additions & 0 deletions app/dts/bindings/behaviors/mod_morph_base.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT

properties:
bindings:
type: phandle-array
required: true
mods:
type: int
required: true
keep-mods:
type: int
required: false
10 changes: 10 additions & 0 deletions app/dts/bindings/behaviors/tap_dance_base.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT

properties:
bindings:
type: phandle-array
required: true
tapping-term-ms:
type: int
default: 200
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT

description: Mod Morph Behavior

compatible: "zmk,behavior-mod-morph-param"

include: [two_param.yaml, mod_morph_base.yaml, binding_params.yaml]
13 changes: 1 addition & 12 deletions app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,4 @@ description: Mod Morph Behavior

compatible: "zmk,behavior-mod-morph"

include: zero_param.yaml

properties:
bindings:
type: phandle-array
required: true
mods:
type: int
required: true
keep-mods:
type: int
required: false
include: [zero_param.yaml, mod_morph_base.yaml]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT

description: Tap Dance Behavior

compatible: "zmk,behavior-tap-dance-param"

include: [two_param.yaml, tap_dance_base.yaml, binding_params.yaml]
10 changes: 1 addition & 9 deletions app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,4 @@ description: Tap Dance Behavior

compatible: "zmk,behavior-tap-dance"

include: zero_param.yaml

properties:
bindings:
type: phandle-array
required: true
tapping-term-ms:
type: int
default: 200
include: [zero_param.yaml, tap_dance_base.yaml]
5 changes: 4 additions & 1 deletion app/include/dt-bindings/zmk/behaviors.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@

#define ZMK_BEHAVIOR_OMIT(_name) \
!(defined(ZMK_BEHAVIORS_KEEP_##_name) || \
(defined(ZMK_BEHAVIORS_KEEP_ALL) && !defined(ZMK_BEHAVIORS_OMIT_##_name)))
(defined(ZMK_BEHAVIORS_KEEP_ALL) && !defined(ZMK_BEHAVIORS_OMIT_##_name)))

#define PLACEHOLDER 0
#define BINDING_PARAM(arg1, arg2) ((arg1 << 2) | arg2)
56 changes: 36 additions & 20 deletions app/src/behaviors/behavior_mod_morph.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
* SPDX-License-Identifier: MIT
*/

#define DT_DRV_COMPAT zmk_behavior_mod_morph

#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
Expand All @@ -21,13 +19,15 @@

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
#if DT_HAS_COMPAT_STATUS_OKAY(zmk_behavior_mod_morph) || \
DT_HAS_COMPAT_STATUS_OKAY(zmk_behavior_mod_morph_param)

struct behavior_mod_morph_config {
struct zmk_behavior_binding normal_binding;
struct zmk_behavior_binding morph_binding;
zmk_mod_flags_t mods;
zmk_mod_flags_t masked_mods;
uint8_t binding_params;
};

struct behavior_mod_morph_data {
Expand All @@ -39,6 +39,7 @@ static int on_mod_morph_binding_pressed(struct zmk_behavior_binding *binding,
const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev);
const struct behavior_mod_morph_config *cfg = dev->config;
struct behavior_mod_morph_data *data = dev->data;
uint8_t map = cfg->binding_params;

if (data->pressed_binding != NULL) {
LOG_ERR("Can't press the same mod-morph twice");
Expand All @@ -50,7 +51,12 @@ static int on_mod_morph_binding_pressed(struct zmk_behavior_binding *binding,
data->pressed_binding = (struct zmk_behavior_binding *)&cfg->morph_binding;
} else {
data->pressed_binding = (struct zmk_behavior_binding *)&cfg->normal_binding;
map = map >> 4;
}
if (map & 0b1100)
data->pressed_binding->param1 = (map & 0b0100) ? binding->param1 : binding->param2;
if (map & 0b0011)
data->pressed_binding->param2 = (map & 0b0001) ? binding->param1 : binding->param2;
return zmk_behavior_invoke_binding(data->pressed_binding, event, true);
}

Expand Down Expand Up @@ -84,26 +90,36 @@ static int behavior_mod_morph_init(const struct device *dev) { return 0; }

#define _TRANSFORM_ENTRY(idx, node) \
{ \
.behavior_dev = DEVICE_DT_NAME(DT_INST_PHANDLE_BY_IDX(node, bindings, idx)), \
.param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), \
(DT_INST_PHA_BY_IDX(node, bindings, idx, param1))), \
.param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), \
(DT_INST_PHA_BY_IDX(node, bindings, idx, param2))), \
.behavior_dev = DEVICE_DT_NAME(DT_PHANDLE_BY_IDX(node, bindings, idx)), \
.param1 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), \
(DT_PHA_BY_IDX(node, bindings, idx, param1))), \
.param2 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), \
(DT_PHA_BY_IDX(node, bindings, idx, param2))), \
}

#define KP_INST(n) \
static struct behavior_mod_morph_config behavior_mod_morph_config_##n = { \
.normal_binding = _TRANSFORM_ENTRY(0, n), \
.morph_binding = _TRANSFORM_ENTRY(1, n), \
.mods = DT_INST_PROP(n, mods), \
.masked_mods = COND_CODE_0(DT_INST_NODE_HAS_PROP(n, keep_mods), (DT_INST_PROP(n, mods)), \
(DT_INST_PROP(n, mods) & ~DT_INST_PROP(n, keep_mods))), \
#define KP_INST(inst) \
BUILD_ASSERT((COND_CODE_0(DT_NODE_HAS_PROP(inst, binding_params), (0), \
((((DT_PROP_BY_IDX(inst, binding_params, 0) >> 2) & \
(DT_PROP_BY_IDX(inst, binding_params, 0))) | \
((DT_PROP_BY_IDX(inst, binding_params, 1) >> 2) & \
(DT_PROP_BY_IDX(inst, binding_params, 1)))))) == 0), \
"Invalid binding parameters"); \
static struct behavior_mod_morph_config behavior_mod_morph_config_##inst = { \
.normal_binding = _TRANSFORM_ENTRY(0, inst), \
.morph_binding = _TRANSFORM_ENTRY(1, inst), \
.mods = DT_PROP(inst, mods), \
.masked_mods = COND_CODE_0(DT_NODE_HAS_PROP(inst, keep_mods), (DT_PROP(inst, mods)), \
(DT_PROP(inst, mods) & ~DT_PROP(inst, keep_mods))), \
.binding_params = (COND_CODE_0(DT_NODE_HAS_PROP(inst, binding_params), (0), \
((DT_PROP_BY_IDX(inst, binding_params, 0) << 4) | \
(DT_PROP_BY_IDX(inst, binding_params, 1))))), \
}; \
static struct behavior_mod_morph_data behavior_mod_morph_data_##n = {}; \
BEHAVIOR_DT_INST_DEFINE(n, behavior_mod_morph_init, NULL, &behavior_mod_morph_data_##n, \
&behavior_mod_morph_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mod_morph_driver_api);
static struct behavior_mod_morph_data behavior_mod_morph_data_##inst = {}; \
BEHAVIOR_DT_DEFINE(inst, behavior_mod_morph_init, NULL, &behavior_mod_morph_data_##inst, \
&behavior_mod_morph_config_##inst, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mod_morph_driver_api);

DT_INST_FOREACH_STATUS_OKAY(KP_INST)
DT_FOREACH_STATUS_OKAY(zmk_behavior_mod_morph, KP_INST)
DT_FOREACH_STATUS_OKAY(zmk_behavior_mod_morph_param, KP_INST)

#endif
60 changes: 38 additions & 22 deletions app/src/behaviors/behavior_tap_dance.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
* SPDX-License-Identifier: MIT
*/

#define DT_DRV_COMPAT zmk_behavior_tap_dance

#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
Expand All @@ -19,7 +17,8 @@

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
#if (DT_HAS_COMPAT_STATUS_OKAY(zmk_behavior_tap_dance) || \
DT_HAS_COMPAT_STATUS_OKAY(zmk_behavior_tap_dance_param))

#define ZMK_BHV_TAP_DANCE_MAX_HELD 10

Expand All @@ -29,6 +28,7 @@ struct behavior_tap_dance_config {
uint32_t tapping_term_ms;
size_t behavior_count;
struct zmk_behavior_binding *behaviors;
uint8_t binding_params[];
};

struct active_tap_dance {
Expand Down Expand Up @@ -64,7 +64,7 @@ static struct active_tap_dance *find_tap_dance(uint32_t position) {

static int new_tap_dance(struct zmk_behavior_binding_event *event,
const struct behavior_tap_dance_config *config,
struct active_tap_dance **tap_dance) {
struct active_tap_dance **tap_dance, uint32_t param1, uint32_t param2) {
for (int i = 0; i < ZMK_BHV_TAP_DANCE_MAX_HELD; i++) {
struct active_tap_dance *const ref_dance = &active_tap_dances[i];
if (ref_dance->position == ZMK_BHV_TAP_DANCE_POSITION_FREE) {
Expand All @@ -73,9 +73,12 @@ static int new_tap_dance(struct zmk_behavior_binding_event *event,
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
ref_dance->source = event->source;
#endif
ref_dance->param1 = param1;
ref_dance->param2 = param2;
ref_dance->is_pressed = true;
ref_dance->config = config;

ref_dance->release_at = 0;
ref_dance->is_pressed = true;
ref_dance->timer_started = true;
ref_dance->timer_cancelled = false;
ref_dance->tap_dance_decided = false;
Expand Down Expand Up @@ -111,20 +114,25 @@ static void reset_timer(struct active_tap_dance *tap_dance,

static inline int press_tap_dance_behavior(struct active_tap_dance *tap_dance, int64_t timestamp) {
tap_dance->tap_dance_decided = true;
struct zmk_behavior_binding binding = tap_dance->config->behaviors[tap_dance->counter - 1];
struct zmk_behavior_binding *binding = &(tap_dance->config->behaviors[tap_dance->counter - 1]);
uint8_t param_map = tap_dance->config->binding_params[tap_dance->counter - 1];
struct zmk_behavior_binding_event event = {
.position = tap_dance->position,
.timestamp = timestamp,
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
.source = tap_dance->source,
#endif
};
return zmk_behavior_invoke_binding(&binding, event, true);
if (param_map & 0b1100)
binding->param1 = (param_map & 0b0100) ? tap_dance->param1 : tap_dance->param2;
if (param_map & 0b0011)
binding->param2 = (param_map & 0b0001) ? tap_dance->param1 : tap_dance->param2;
return zmk_behavior_invoke_binding(binding, event, true);
}

static inline int release_tap_dance_behavior(struct active_tap_dance *tap_dance,
int64_t timestamp) {
struct zmk_behavior_binding binding = tap_dance->config->behaviors[tap_dance->counter - 1];
struct zmk_behavior_binding *binding = &(tap_dance->config->behaviors[tap_dance->counter - 1]);
struct zmk_behavior_binding_event event = {
.position = tap_dance->position,
.timestamp = timestamp,
Expand All @@ -133,7 +141,7 @@ static inline int release_tap_dance_behavior(struct active_tap_dance *tap_dance,
#endif
};
clear_tap_dance(tap_dance);
return zmk_behavior_invoke_binding(&binding, event, false);
return zmk_behavior_invoke_binding(binding, event, false);
}

static int on_tap_dance_binding_pressed(struct zmk_behavior_binding *binding,
Expand All @@ -143,7 +151,7 @@ static int on_tap_dance_binding_pressed(struct zmk_behavior_binding *binding,
struct active_tap_dance *tap_dance;
tap_dance = find_tap_dance(event.position);
if (tap_dance == NULL) {
if (new_tap_dance(&event, cfg, &tap_dance) == -ENOMEM) {
if (new_tap_dance(&event, cfg, &tap_dance, binding->param1, binding->param2) == -ENOMEM) {
LOG_ERR("Unable to create new tap dance. Insufficient space in active_tap_dances[].");
return ZMK_BEHAVIOR_OPAQUE;
}
Expand Down Expand Up @@ -258,20 +266,28 @@ static int behavior_tap_dance_init(const struct device *dev) {
#define _TRANSFORM_ENTRY(idx, node) ZMK_KEYMAP_EXTRACT_BINDING(idx, node)

#define TRANSFORMED_BINDINGS(node) \
{LISTIFY(DT_INST_PROP_LEN(node, bindings), _TRANSFORM_ENTRY, (, ), DT_DRV_INST(node))}
{LISTIFY(DT_PROP_LEN(node, bindings), _TRANSFORM_ENTRY, (, ), node)}

#define BREAK_ITEM(i, inst) DT_PROP_BY_IDX(inst, binding_params, i)
#define BREAK_ZERO(i, inst) 0

#define KP_INST(n) \
#define KP_INST(inst) \
static struct zmk_behavior_binding \
behavior_tap_dance_config_##n##_bindings[DT_INST_PROP_LEN(n, bindings)] = \
TRANSFORMED_BINDINGS(n); \
static struct behavior_tap_dance_config behavior_tap_dance_config_##n = { \
.tapping_term_ms = DT_INST_PROP(n, tapping_term_ms), \
.behaviors = behavior_tap_dance_config_##n##_bindings, \
.behavior_count = DT_INST_PROP_LEN(n, bindings)}; \
BEHAVIOR_DT_INST_DEFINE(n, behavior_tap_dance_init, NULL, NULL, \
&behavior_tap_dance_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_tap_dance_driver_api);
behavior_tap_dance_config_##inst##_bindings[DT_PROP_LEN(inst, bindings)] = \
TRANSFORMED_BINDINGS(inst); \
static struct behavior_tap_dance_config behavior_tap_dance_config_##inst = { \
.tapping_term_ms = DT_PROP(inst, tapping_term_ms), \
.behaviors = behavior_tap_dance_config_##inst##_bindings, \
.behavior_count = DT_PROP_LEN(inst, bindings), \
.binding_params = \
COND_CODE_0(DT_NODE_HAS_PROP(inst, binding_params), \
({LISTIFY(DT_PROP_LEN(inst, bindings), BREAK_ZERO, (, ), inst)}), \
({LISTIFY(DT_PROP_LEN(inst, bindings), BREAK_ITEM, (, ), inst)}))}; \
BEHAVIOR_DT_DEFINE(inst, behavior_tap_dance_init, NULL, NULL, \
&behavior_tap_dance_config_##inst, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_tap_dance_driver_api);

DT_INST_FOREACH_STATUS_OKAY(KP_INST)
DT_FOREACH_STATUS_OKAY(zmk_behavior_tap_dance, KP_INST)
DT_FOREACH_STATUS_OKAY(zmk_behavior_tap_dance_param, KP_INST)

#endif
8 changes: 8 additions & 0 deletions app/tests/mod-morph/4a-morph-params-singles/events.patterns
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
s/.*hid_listener_keycode_pressed.*keycode/pressed: keycode/p
s/.*hid_listener_keycode_released.*keycode/released: keycode/p
s/.*hid_register_mod.*Modifiers set to /reg explicit: Modifiers set to /p
s/.*hid_unregister_mod.*Modifiers set to /unreg explicit: Modifiers set to /p
s/.*hid_implicit_modifiers_press.*Modifiers set to /reg implicit: Modifiers set to /p
s/.*hid_implicit_modifiers_release.*Modifiers set to /unreg implicit: Modifiers set to /p
s/.*hid_masked_modifiers_set.*Modifiers set to /mask mods: Modifiers set to /p
s/.*hid_masked_modifiers_clear.*Modifiers set to /unmask mods: Modifiers set to /p
Loading
Loading