Skip to content

Commit

Permalink
feat: Add parameterised tap dance
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick-Munnich committed Jan 3, 2025
1 parent 9650e40 commit ae249be
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 39 deletions.
3 changes: 2 additions & 1 deletion app/dts/behaviors.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
#include <behaviors/transparent.dtsi>
#include <behaviors/none.dtsi>
#include <behaviors/mod_tap.dtsi>
#include <behaviors/mod_morph.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
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
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
7 changes: 1 addition & 6 deletions app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,4 @@ description: Mod Morph Behavior

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

include: [two_param.yaml, mod_morph_base.yaml]

properties:
binding-params:
type: array
required: true
include: [two_param.yaml, mod_morph_base.yaml, binding_params.yaml]
8 changes: 8 additions & 0 deletions app/dts/bindings/behaviors/zmk,behavior-tap-dance-param.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]
74 changes: 51 additions & 23 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,40 @@ 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 KP_INST(n) \
#define BREAK_ITEM(i, inst) DT_PROP_BY_IDX(inst, binding_params, i)
#define BREAK_ZERO(i, inst) 0

#define VALIDATE_BINDING_PARAMS(inst, prop, idx) \
BUILD_ASSERT((COND_CODE_0(DT_NODE_HAS_PROP(inst, prop), (0), \
((((DT_PROP_BY_IDX(inst, prop, idx) >> 2) & 3) <= 2) && \
((DT_PROP_BY_IDX(inst, prop, idx) & 3) <= 2)))), \
"##inst##@index##idx##: Invalid binding parameters. Use the BINDING_PARAM(x,y) " \
"macro with valid arguments being 0, 1, or 2.");

#define TD_INST(inst) \
DT_FOREACH_PROP_ELEM(inst, binding_params, VALIDATE_BINDING_PARAMS) \
BUILD_ASSERT( \
(COND_CODE_0(DT_NODE_HAS_PROP(inst, binding_params), (0), \
((DT_PROP_LEN(inst, bindings)) == DT_PROP_LEN(inst, binding_params)))), \
"##inst##: The number of binding parameters must match the number of bindings"); \
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);

DT_INST_FOREACH_STATUS_OKAY(KP_INST)
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_FOREACH_STATUS_OKAY(zmk_behavior_tap_dance, TD_INST)
DT_FOREACH_STATUS_OKAY(zmk_behavior_tap_dance_param, TD_INST)

#endif
2 changes: 2 additions & 0 deletions app/tests/tap-dance/7-params/events.patterns
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
s/.*hid_listener_keycode/kp/p
s/.*on_tap_dance_binding/td_binding/p
63 changes: 63 additions & 0 deletions app/tests/tap-dance/7-params/keycode_events.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
td_binding_pressed: 0 created new tap dance
td_binding_pressed: 0 tap dance pressed
td_binding_released: 0 tap dance keybind released
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
td_binding_pressed: 0 created new tap dance
td_binding_pressed: 0 tap dance pressed
td_binding_released: 0 tap dance keybind released
td_binding_pressed: 0 tap dance pressed
td_binding_released: 0 tap dance keybind released
kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
td_binding_pressed: 0 created new tap dance
td_binding_pressed: 0 tap dance pressed
td_binding_released: 0 tap dance keybind released
td_binding_pressed: 0 tap dance pressed
td_binding_released: 0 tap dance keybind released
td_binding_pressed: 0 tap dance pressed
kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
td_binding_released: 0 tap dance keybind released
kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
td_binding_pressed: 1 created new tap dance
td_binding_pressed: 1 tap dance pressed
td_binding_released: 1 tap dance keybind released
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
td_binding_pressed: 1 created new tap dance
td_binding_pressed: 1 tap dance pressed
td_binding_released: 1 tap dance keybind released
td_binding_pressed: 1 tap dance pressed
td_binding_released: 1 tap dance keybind released
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
td_binding_pressed: 1 created new tap dance
td_binding_pressed: 1 tap dance pressed
td_binding_released: 1 tap dance keybind released
td_binding_pressed: 1 tap dance pressed
td_binding_released: 1 tap dance keybind released
td_binding_pressed: 1 tap dance pressed
td_binding_released: 1 tap dance keybind released
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
td_binding_pressed: 1 created new tap dance
td_binding_pressed: 1 tap dance pressed
kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
td_binding_released: 1 tap dance keybind released
kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
td_binding_pressed: 1 created new tap dance
td_binding_pressed: 1 tap dance pressed
td_binding_released: 1 tap dance keybind released
td_binding_pressed: 1 tap dance pressed
kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
td_binding_released: 1 tap dance keybind released
kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
td_binding_pressed: 1 created new tap dance
td_binding_pressed: 1 tap dance pressed
td_binding_released: 1 tap dance keybind released
td_binding_pressed: 1 tap dance pressed
td_binding_released: 1 tap dance keybind released
td_binding_pressed: 1 tap dance pressed
kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
td_binding_released: 1 tap dance keybind released
kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
89 changes: 89 additions & 0 deletions app/tests/tap-dance/7-params/native_posix_64.keymap
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>

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

/ {
behaviors {
ht: hold_tap {
compatible = "zmk,behavior-hold-tap";
#binding-cells = <2>;
tapping-term-ms = <200>;
quick-tap-ms = <0>;
flavor = "tap-preferred";
bindings = <&kp>, <&kp>;
};

tdm: tap_dance_mixed {
compatible = "zmk,behavior-tap-dance-param";
#binding-cells = <2>;
tapping-term-ms = <100>;
bindings = <&ht PLACEHOLDER PLACEHOLDER>, <&ht C PLACEHOLDER>, <&ht PLACEHOLDER D>;
binding-params = <BINDING_PARAM(2,1) BINDING_PARAM(0,1) BINDING_PARAM(2,0)>;
};

td2: tap_dance_basic {
compatible = "zmk,behavior-tap-dance-param";
#binding-cells = <2>;
tapping-term-ms = <100>;
bindings = <&kp PLACEHOLDER>, <&kp B>, <&kp PLACEHOLDER>;
binding-params = <BINDING_PARAM(1,0) BINDING_PARAM(0,0) BINDING_PARAM(2,0)>;
};
};

keymap {
compatible = "zmk,keymap";

default_layer {
bindings = <
&td2 A C &tdm A B
&trans &trans >;
};
};
};

&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,110)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,110)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,110)

ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,110)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,110)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,110)

ZMK_MOCK_PRESS(0,1,410)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,1,410)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,1,410)
ZMK_MOCK_RELEASE(0,1,10)
>;
};

0 comments on commit ae249be

Please sign in to comment.