Skip to content

feat(behaviors): hold-tap hold while undecided #1811

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

Merged
merged 3 commits into from
Feb 20, 2024
Merged
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
4 changes: 4 additions & 0 deletions app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ properties:
- "balanced"
- "tap-preferred"
- "tap-unless-interrupted"
hold-while-undecided:
type: boolean
hold-while-undecided-linger:
type: boolean
retro-tap:
type: boolean
hold-trigger-key-positions:
Expand Down
103 changes: 81 additions & 22 deletions app/src/behaviors/behavior_hold_tap.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ enum status {
};

enum decision_moment {
HT_KEY_DOWN,
HT_KEY_UP,
HT_OTHER_KEY_DOWN,
HT_OTHER_KEY_UP,
Expand All @@ -59,6 +60,8 @@ struct behavior_hold_tap_config {
int quick_tap_ms;
int require_prior_idle_ms;
enum flavor flavor;
bool hold_while_undecided;
bool hold_while_undecided_linger;
bool retro_tap;
bool hold_trigger_on_release;
int32_t hold_trigger_key_positions_len;
Expand Down Expand Up @@ -387,47 +390,83 @@ static inline const char *decision_moment_str(enum decision_moment decision_mome
}
}

static int press_binding(struct active_hold_tap *hold_tap) {
if (hold_tap->config->retro_tap && hold_tap->status == STATUS_HOLD_TIMER) {
return 0;
}
static int press_hold_binding(struct active_hold_tap *hold_tap) {
struct zmk_behavior_binding_event event = {
.position = hold_tap->position,
.timestamp = hold_tap->timestamp,
};

struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->hold_behavior_dev,
.param1 = hold_tap->param_hold};
return behavior_keymap_binding_pressed(&binding, event);
}

static int press_tap_binding(struct active_hold_tap *hold_tap) {
struct zmk_behavior_binding_event event = {
.position = hold_tap->position,
.timestamp = hold_tap->timestamp,
};

struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->tap_behavior_dev,
.param1 = hold_tap->param_tap};
store_last_hold_tapped(hold_tap);
return behavior_keymap_binding_pressed(&binding, event);
}

static int release_hold_binding(struct active_hold_tap *hold_tap) {
struct zmk_behavior_binding_event event = {
.position = hold_tap->position,
.timestamp = hold_tap->timestamp,
};

struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->hold_behavior_dev,
.param1 = hold_tap->param_hold};
return behavior_keymap_binding_released(&binding, event);
}

static int release_tap_binding(struct active_hold_tap *hold_tap) {
struct zmk_behavior_binding_event event = {
.position = hold_tap->position,
.timestamp = hold_tap->timestamp,
};

struct zmk_behavior_binding binding = {0};
struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->tap_behavior_dev,
.param1 = hold_tap->param_tap};
return behavior_keymap_binding_released(&binding, event);
}

static int press_binding(struct active_hold_tap *hold_tap) {
if (hold_tap->config->retro_tap && hold_tap->status == STATUS_HOLD_TIMER) {
return 0;
}

if (hold_tap->status == STATUS_HOLD_TIMER || hold_tap->status == STATUS_HOLD_INTERRUPT) {
binding.behavior_dev = hold_tap->config->hold_behavior_dev;
binding.param1 = hold_tap->param_hold;
if (hold_tap->config->hold_while_undecided) {
// the hold is already active, so we don't need to press it again
return 0;
} else {
return press_hold_binding(hold_tap);
}
} else {
binding.behavior_dev = hold_tap->config->tap_behavior_dev;
binding.param1 = hold_tap->param_tap;
store_last_hold_tapped(hold_tap);
if (hold_tap->config->hold_while_undecided &&
!hold_tap->config->hold_while_undecided_linger) {
// time to release the hold before pressing the tap
release_hold_binding(hold_tap);
}
return press_tap_binding(hold_tap);
}
return behavior_keymap_binding_pressed(&binding, event);
}

static int release_binding(struct active_hold_tap *hold_tap) {
if (hold_tap->config->retro_tap && hold_tap->status == STATUS_HOLD_TIMER) {
return 0;
}

struct zmk_behavior_binding_event event = {
.position = hold_tap->position,
.timestamp = hold_tap->timestamp,
};

struct zmk_behavior_binding binding = {0};
if (hold_tap->status == STATUS_HOLD_TIMER || hold_tap->status == STATUS_HOLD_INTERRUPT) {
binding.behavior_dev = hold_tap->config->hold_behavior_dev;
binding.param1 = hold_tap->param_hold;
return release_hold_binding(hold_tap);
} else {
binding.behavior_dev = hold_tap->config->tap_behavior_dev;
binding.param1 = hold_tap->param_tap;
return release_tap_binding(hold_tap);
}
return behavior_keymap_binding_released(&binding, event);
}

static bool is_first_other_key_pressed_trigger_key(struct active_hold_tap *hold_tap) {
Expand Down Expand Up @@ -474,6 +513,12 @@ static void decide_hold_tap(struct active_hold_tap *hold_tap,
return;
}

if (hold_tap->config->hold_while_undecided && decision_moment == HT_KEY_DOWN) {
LOG_DBG("%d hold behavior pressed while undecided", hold_tap->position);
press_hold_binding(hold_tap);
return;
}

// If the hold-tap behavior is still undecided, attempt to decide it.
switch (hold_tap->config->flavor) {
case FLAVOR_HOLD_PREFERRED:
Expand Down Expand Up @@ -561,6 +606,8 @@ static int on_hold_tap_binding_pressed(struct zmk_behavior_binding *binding,
decide_hold_tap(hold_tap, HT_QUICK_TAP);
}

decide_hold_tap(hold_tap, HT_KEY_DOWN);

// if this behavior was queued we have to adjust the timer to only
// wait for the remaining time.
int32_t tapping_term_ms_left = (hold_tap->timestamp + cfg->tapping_term_ms) - k_uptime_get();
Expand Down Expand Up @@ -588,6 +635,10 @@ static int on_hold_tap_binding_released(struct zmk_behavior_binding *binding,
decide_retro_tap(hold_tap);
release_binding(hold_tap);

if (hold_tap->config->hold_while_undecided && hold_tap->config->hold_while_undecided_linger) {
release_hold_binding(hold_tap);
}

if (work_cancel_result == -EINPROGRESS) {
// let the timer handler clean up
// if we'd clear now, the timer may call back for an uninitialized active_hold_tap.
Expand Down Expand Up @@ -685,6 +736,12 @@ static int keycode_state_changed_listener(const zmk_event_t *eh) {
return ZMK_EV_EVENT_BUBBLE;
}

// hold-while-undecided can produce a mod, but we don't want to capture it.
if (undecided_hold_tap->config->hold_while_undecided &&
undecided_hold_tap->status == STATUS_UNDECIDED) {
return ZMK_EV_EVENT_BUBBLE;
}

// only key-up events will bubble through position_state_changed_listener
// if a undecided_hold_tap is active.
LOG_DBG("%d capturing 0x%02X %s event", undecided_hold_tap->position, ev->keycode,
Expand Down Expand Up @@ -742,6 +799,8 @@ static int behavior_hold_tap_init(const struct device *dev) {
? DT_INST_PROP(n, quick_tap_ms) \
: DT_INST_PROP(n, require_prior_idle_ms), \
.flavor = DT_ENUM_IDX(DT_DRV_INST(n), flavor), \
.hold_while_undecided = DT_INST_PROP(n, hold_while_undecided), \
.hold_while_undecided_linger = DT_INST_PROP(n, hold_while_undecided_linger), \
.retro_tap = DT_INST_PROP(n, retro_tap), \
.hold_trigger_on_release = DT_INST_PROP(n, hold_trigger_on_release), \
.hold_trigger_key_positions = DT_INST_PROP(n, hold_trigger_key_positions), \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
s/.*hid_listener_keycode/kp/p
s/.*mo_keymap_binding/mo/p
s/.*on_hold_tap_binding/ht_binding/p
s/.*decide_hold_tap/ht_decide/p
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 hold behavior pressed while undecided
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_decide: 0 decided tap (balanced decision moment key-up)
kp_released: usage_page 0x07 keycode 0xE1 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
ht_binding_released: 0 cleaning up hold-tap
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>

/ {
behaviors {
ht_bal: behavior_hold_tap_balanced {
compatible = "zmk,behavior-hold-tap";
#binding-cells = <2>;
flavor = "balanced";
tapping-term-ms = <300>;
quick-tap-ms = <200>;
bindings = <&kp>, <&kp>;
hold-while-undecided;
};
};

keymap {
compatible = "zmk,keymap";

default_layer {
bindings = <
&ht_bal LEFT_SHIFT A &ht_bal LEFT_CONTROL B
&kp D &kp RIGHT_CONTROL>;
};
};
};

&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
s/.*hid_listener_keycode/kp/p
s/.*mo_keymap_binding/mo/p
s/.*on_hold_tap_binding/ht_binding/p
s/.*decide_hold_tap/ht_decide/p
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 hold behavior pressed while undecided
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_decide: 0 decided hold-timer (balanced decision moment timer)
kp_released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>

/ {
behaviors {
ht_bal: behavior_hold_tap_balanced {
compatible = "zmk,behavior-hold-tap";
#binding-cells = <2>;
flavor = "balanced";
tapping-term-ms = <100>;
quick-tap-ms = <200>;
bindings = <&kp>, <&kp>;
hold-while-undecided;
};
};

keymap {
compatible = "zmk,keymap";

default_layer {
bindings = <
&ht_bal LEFT_SHIFT A &ht_bal LEFT_CONTROL B
&kp D &kp RIGHT_CONTROL>;
};
};
};

&kscan {
events = <
ZMK_MOCK_PRESS(0,0,150)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
s/.*hid_listener_keycode/kp/p
s/.*mo_keymap_binding/mo/p
s/.*on_hold_tap_binding/ht_binding/p
s/.*decide_hold_tap/ht_decide/p
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 hold behavior pressed while undecided
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_decide: 0 decided tap (balanced decision moment key-up)
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_released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>

/ {
behaviors {
ht_bal: behavior_hold_tap_balanced {
compatible = "zmk,behavior-hold-tap";
#binding-cells = <2>;
flavor = "balanced";
tapping-term-ms = <100>;
quick-tap-ms = <300>;
bindings = <&kp>, <&kp>;
hold-while-undecided;
hold-while-undecided-linger;
};
};

keymap {
compatible = "zmk,keymap";

default_layer {
bindings = <
&ht_bal LEFT_SHIFT A &ht_bal LEFT_CONTROL B
&kp D &kp RIGHT_CONTROL>;
};
};
};

&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
s/.*hid_listener_keycode/kp/p
s/.*mo_keymap_binding/mo/p
s/.*on_hold_tap_binding/ht_binding/p
s/.*decide_hold_tap/ht_decide/p
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 hold behavior pressed while undecided
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_decide: 0 decided tap (balanced decision moment key-up)
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>

/ {
behaviors {
ht_bal: behavior_hold_tap_balanced {
compatible = "zmk,behavior-hold-tap";
#binding-cells = <2>;
flavor = "balanced";
tapping-term-ms = <100>;
quick-tap-ms = <200>;
bindings = <&kp>, <&sk>;
hold-while-undecided;
hold-while-undecided-linger;
};
};

keymap {
compatible = "zmk,keymap";

default_layer {
bindings = <
&ht_bal LEFT_SHIFT LEFT_SHIFT &ht_bal LEFT_SHIFT LEFT_CONTROL
&kp D &kp RIGHT_CONTROL>;
};
};
};

&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
Loading