Hold while undecided

This commit is contained in:
Nick Conway 2023-06-02 20:54:24 -04:00
parent b276a3bfb0
commit c3f74bbcd1
7 changed files with 108 additions and 0 deletions

View file

@ -31,6 +31,8 @@ properties:
- "balanced"
- "tap-preferred"
- "tap-unless-interrupted"
hold-while-undecided:
type: boolean
retro-tap:
type: boolean
hold-trigger-key-positions:

View file

@ -59,6 +59,7 @@ struct behavior_hold_tap_config {
int quick_tap_ms;
bool global_quick_tap;
enum flavor flavor;
bool hold_while_undecided;
bool retro_tap;
bool hold_trigger_on_release;
int32_t hold_trigger_key_positions_len;
@ -67,10 +68,12 @@ struct behavior_hold_tap_config {
// this data is specific for each hold-tap
struct active_hold_tap {
bool first_press;
int32_t position;
uint32_t param_hold;
uint32_t param_tap;
int64_t timestamp;
bool hold_released;
enum status status;
const struct behavior_hold_tap_config *config;
struct k_work_delayable work;
@ -219,11 +222,13 @@ static struct active_hold_tap *store_hold_tap(uint32_t position, uint32_t param_
if (active_hold_taps[i].position != ZMK_BHV_HOLD_TAP_POSITION_NOT_USED) {
continue;
}
active_hold_taps[i].first_press = true;
active_hold_taps[i].position = position;
active_hold_taps[i].status = STATUS_UNDECIDED;
active_hold_taps[i].config = config;
active_hold_taps[i].param_hold = param_hold;
active_hold_taps[i].param_tap = param_tap;
active_hold_taps[i].hold_released = false;
active_hold_taps[i].timestamp = timestamp;
active_hold_taps[i].position_of_first_other_key_pressed = -1;
return &active_hold_taps[i];
@ -434,6 +439,28 @@ static void decide_positional_hold(struct active_hold_tap *hold_tap) {
hold_tap->status = STATUS_TAP;
}
static void release_hold_binding(struct active_hold_tap *hold_tap,
enum decision_moment decision_moment) {
bool keyTap =
!(hold_tap->status == STATUS_HOLD_TIMER || hold_tap->status == STATUS_HOLD_INTERRUPT);
bool holdRelease =
(hold_tap->status == STATUS_HOLD_TIMER || hold_tap->status == STATUS_HOLD_INTERRUPT) &&
decision_moment == HT_KEY_UP;
if (holdRelease || keyTap) {
LOG_DBG("Releasing hold behavior");
struct zmk_behavior_binding_event event = {
.position = hold_tap->position,
.timestamp = hold_tap->timestamp,
};
struct zmk_behavior_binding binding = {0};
binding.behavior_dev = hold_tap->config->hold_behavior_dev;
binding.param1 = hold_tap->param_hold;
behavior_keymap_binding_released(&binding, event);
hold_tap->hold_released = true;
}
}
static void decide_hold_tap(struct active_hold_tap *hold_tap,
enum decision_moment decision_moment) {
if (hold_tap->status != STATUS_UNDECIDED) {
@ -473,6 +500,10 @@ static void decide_hold_tap(struct active_hold_tap *hold_tap,
status_str(hold_tap->status), flavor_str(hold_tap->config->flavor),
decision_moment_str(decision_moment));
undecided_hold_tap = NULL;
if (hold_tap->config->hold_while_undecided && !hold_tap->hold_released) {
release_hold_binding(hold_tap, decision_moment);
}
press_binding(hold_tap);
release_captured_events();
}
@ -530,6 +561,12 @@ static int on_hold_tap_binding_pressed(struct zmk_behavior_binding *binding,
if (is_quick_tap(hold_tap)) {
decide_hold_tap(hold_tap, HT_QUICK_TAP);
} else if (cfg->hold_while_undecided) {
struct zmk_behavior_binding hold_binding = {0};
hold_binding.behavior_dev = cfg->hold_behavior_dev;
hold_binding.param1 = binding->param1;
LOG_DBG("%d hold behavior pressed while undecided", event.position);
behavior_keymap_binding_pressed(&hold_binding, event);
}
// if this behavior was queued we have to adjust the timer to only
@ -556,6 +593,9 @@ static int on_hold_tap_binding_released(struct zmk_behavior_binding *binding,
}
decide_hold_tap(hold_tap, HT_KEY_UP);
if (hold_tap->config->hold_while_undecided && !hold_tap->hold_released) {
release_hold_binding(hold_tap, HT_KEY_UP);
}
decide_retro_tap(hold_tap);
release_binding(hold_tap);
@ -652,6 +692,11 @@ static int keycode_state_changed_listener(const zmk_event_t *eh) {
return ZMK_EV_EVENT_BUBBLE;
}
if (undecided_hold_tap->first_press && undecided_hold_tap->config->hold_while_undecided) {
undecided_hold_tap->first_press = false;
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,
@ -705,6 +750,7 @@ static int behavior_hold_tap_init(const struct device *dev) {
.quick_tap_ms = DT_INST_PROP(n, quick_tap_ms), \
.global_quick_tap = DT_INST_PROP(n, global_quick_tap), \
.flavor = DT_ENUM_IDX(DT_DRV_INST(n), flavor), \
.hold_while_undecided = DT_INST_PROP(n, hold_while_undecided), \
.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), \

View file

@ -0,0 +1,29 @@
#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";
label = "HOLD_TAP_BALANCED";
#binding-cells = <2>;
flavor = "balanced";
tapping-term-ms = <300>;
quick-tap-ms = <200>;
bindings = <&kp>, <&kp>;
hold-while-undecided;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&ht_bal LEFT_SHIFT A &ht_bal LEFT_CONTROL B
&kp D &kp RIGHT_CONTROL>;
};
};
};

View file

@ -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

View file

@ -0,0 +1,8 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_binding_pressed: 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

View file

@ -0,0 +1,11 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap_hwu.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};

View file

@ -84,6 +84,14 @@ For example, if you press `&mt LEFT_SHIFT A` and then release it without pressin
};
```
#### `hold-while-undecided`
If enabled, the hold behavior will immediately be held, and will release before the tap behavior is sent. With modifiers (excluding the windows key) this will not affect typing, and is useful for mod + clicking.
:::note Alt behavior
In some applications, pressing the alt key by itself will have its own behavior like activate a menu.
:::
#### Positional hold-tap and `hold-trigger-key-positions`
Including `hold-trigger-key-positions` in your hold-tap definition turns on the positional hold-tap feature. With positional hold-tap enabled, if you press any key **NOT** listed in `hold-trigger-key-positions` before `tapping-term-ms` expires, it will produce a tap.