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

behaviors(hold-tap): Implement quick_tap_ms (TAPPING_FORCE_HOLD) #655

Merged
merged 1 commit into from
Feb 2, 2021
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
3 changes: 3 additions & 0 deletions app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ properties:
required: true
tapping_term_ms:
type: int
quick_tap_ms:
type: int
default: -1
flavor:
type: string
required: false
Expand Down
38 changes: 38 additions & 0 deletions app/src/behaviors/behavior_hold_tap.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct behavior_hold_tap_config {
int tapping_term_ms;
char *hold_behavior_dev;
char *tap_behavior_dev;
int quick_tap_ms;
enum flavor flavor;
};

Expand Down Expand Up @@ -67,6 +68,24 @@ struct active_hold_tap active_hold_taps[ZMK_BHV_HOLD_TAP_MAX_HELD] = {};
// We capture most position_state_changed events and some modifiers_state_changed events.
const zmk_event_t *captured_events[ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS] = {};

// Keep track of which key was tapped most recently for 'quick_tap_ms'
struct last_tapped {
int32_t position;
int64_t tap_deadline;
};

struct last_tapped last_tapped;

static void store_last_tapped(struct active_hold_tap *hold_tap) {
last_tapped.position = hold_tap->position;
last_tapped.tap_deadline = hold_tap->timestamp + hold_tap->config->quick_tap_ms;
}

static bool is_quick_tap(struct active_hold_tap *hold_tap) {
return last_tapped.position == hold_tap->position &&
last_tapped.tap_deadline > hold_tap->timestamp;
}

static int capture_event(const zmk_event_t *event) {
for (int i = 0; i < ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS; i++) {
if (captured_events[i] == NULL) {
Expand Down Expand Up @@ -191,6 +210,7 @@ enum decision_moment {
HT_OTHER_KEY_DOWN = 1,
HT_OTHER_KEY_UP = 2,
HT_TIMER_EVENT = 3,
HT_QUICK_TAP = 4,
};

static void decide_balanced(struct active_hold_tap *hold_tap, enum decision_moment event) {
Expand All @@ -204,6 +224,10 @@ static void decide_balanced(struct active_hold_tap *hold_tap, enum decision_mome
hold_tap->is_hold = 1;
hold_tap->is_decided = true;
break;
case HT_QUICK_TAP:
hold_tap->is_hold = 0;
hold_tap->is_decided = true;
break;
default:
return;
}
Expand All @@ -219,6 +243,10 @@ static void decide_tap_preferred(struct active_hold_tap *hold_tap, enum decision
hold_tap->is_hold = 1;
hold_tap->is_decided = true;
break;
case HT_QUICK_TAP:
hold_tap->is_hold = 0;
hold_tap->is_decided = true;
break;
default:
return;
}
Expand All @@ -235,6 +263,10 @@ static void decide_hold_preferred(struct active_hold_tap *hold_tap, enum decisio
hold_tap->is_hold = 1;
hold_tap->is_decided = true;
break;
case HT_QUICK_TAP:
hold_tap->is_hold = 0;
hold_tap->is_decided = true;
break;
default:
return;
}
Expand Down Expand Up @@ -293,6 +325,7 @@ static void decide_hold_tap(struct active_hold_tap *hold_tap, enum decision_mome
binding.behavior_dev = hold_tap->config->tap_behavior_dev;
binding.param1 = hold_tap->param_tap;
binding.param2 = 0;
store_last_tapped(hold_tap);
}
behavior_keymap_binding_pressed(&binding, event);
release_captured_events();
Expand Down Expand Up @@ -320,6 +353,10 @@ static int on_hold_tap_binding_pressed(struct zmk_behavior_binding *binding,
LOG_DBG("%d new undecided hold_tap", event.position);
undecided_hold_tap = hold_tap;

if (is_quick_tap(hold_tap)) {
decide_hold_tap(hold_tap, HT_QUICK_TAP);
}

// 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 @@ -492,6 +529,7 @@ static struct behavior_hold_tap_data behavior_hold_tap_data;
.tapping_term_ms = DT_INST_PROP(n, tapping_term_ms), \
.hold_behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(n, bindings, 0)), \
.tap_behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(n, bindings, 1)), \
.quick_tap_ms = DT_INST_PROP(n, quick_tap_ms), \
.flavor = DT_ENUM_IDX(DT_DRV_INST(n), flavor), \
}; \
DEVICE_AND_API_INIT(behavior_hold_tap_##n, DT_INST_LABEL(n), behavior_hold_tap_init, \
Expand Down
4 changes: 4 additions & 0 deletions app/tests/hold-tap/balanced/5-quick-tap/events.patterns
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
10 changes: 10 additions & 0 deletions app/tests/hold-tap/balanced/5-quick-tap/keycode_events.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided tap (balanced event 0)
kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided tap (balanced event 4)
kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
14 changes: 14 additions & 0 deletions app/tests/hold-tap/balanced/5-quick-tap/native_posix.keymap
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"


&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,0,400)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
1 change: 1 addition & 0 deletions app/tests/hold-tap/balanced/behavior_keymap.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#binding-cells = <2>;
flavor = "balanced";
tapping_term_ms = <300>;
quick_tap_ms = <200>;
bindings = <&kp>, <&kp>;
};
};
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,10 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided tap (hold-preferred event 0)
kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided tap (hold-preferred event 4)
kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x09 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,14 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"


&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,0,400)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
1 change: 1 addition & 0 deletions app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#binding-cells = <2>;
flavor = "hold-preferred";
tapping_term_ms = <300>;
quick_tap_ms = <200>;
bindings = <&kp>, <&kp>;
};
};
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,10 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided tap (tap-preferred event 0)
kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided tap (tap-preferred event 4)
kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x09 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,14 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"


&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,0,400)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
1 change: 1 addition & 0 deletions app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#binding-cells = <2>;
flavor = "tap-preferred";
tapping_term_ms = <300>;
quick_tap_ms = <200>;
bindings = <&kp>, <&kp>;
};
};
Expand Down
15 changes: 14 additions & 1 deletion docs/docs/behaviors/hold-tap.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Simply put, the hold-tap key will output the 'hold' behavior if it's held for a

### Hold-Tap

The `tapping_term_ms` parameter decides between a 'tap' and a 'hold'.
The graph below shows how the hold-tap decides between a 'tap' and a 'hold'.

![Simple behavior](../assets/hold-tap/case1_2.png)

Expand All @@ -37,6 +37,18 @@ For basic usage, please see [mod-tap](./mod-tap.md) and [layer-tap](./layers.md)

### Advanced Configuration

#### `tapping_term_ms`

Defines how long a key must be pressed to trigger Hold behavior.

#### `quick_tap_ms`

If you press a tapped hold-tap again within `quick_tap_ms` milliseconds, it will always trigger the tap behavior. This is useful for things like a backspace, where a quick tap+hold holds backspace pressed. Set this to a negative value to disable. The default is -1 (disabled).

In QMK, unlike ZMK, this functionality is enabled by default, and you turn it off using `TAPPING_FORCE_HOLD`.

#### Home row mods

This example configures a hold-tap that works well for homerow mods:

```
Expand All @@ -50,6 +62,7 @@ This example configures a hold-tap that works well for homerow mods:
label = "HOMEROW_MODS";
#binding-cells = <2>;
tapping_term_ms = <150>;
quick_tap_ms = <0>;
flavor = "tap-preferred";
bindings = <&kp>, <&kp>;
};
Expand Down