summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml3
-rw-r--r--app/src/behaviors/behavior_hold_tap.c38
-rw-r--r--app/tests/hold-tap/balanced/5-quick-tap/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/5-quick-tap/keycode_events.snapshot10
-rw-r--r--app/tests/hold-tap/balanced/5-quick-tap/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/balanced/behavior_keymap.dtsi1
-rw-r--r--app/tests/hold-tap/hold-preferred/5-quick-tap/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/5-quick-tap/keycode_events.snapshot10
-rw-r--r--app/tests/hold-tap/hold-preferred/5-quick-tap/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi1
-rw-r--r--app/tests/hold-tap/tap-preferred/5-quick-tap/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/5-quick-tap/keycode_events.snapshot10
-rw-r--r--app/tests/hold-tap/tap-preferred/5-quick-tap/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi1
-rw-r--r--docs/docs/behaviors/hold-tap.md15
15 files changed, 142 insertions, 1 deletions
diff --git a/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml b/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml
index 5f74e9a..56f0cc2 100644
--- a/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml
+++ b/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml
@@ -13,6 +13,9 @@ properties:
required: true
tapping_term_ms:
type: int
+ quick_tap_ms:
+ type: int
+ default: -1
flavor:
type: string
required: false
diff --git a/app/src/behaviors/behavior_hold_tap.c b/app/src/behaviors/behavior_hold_tap.c
index 4e91846..8f239dc 100644
--- a/app/src/behaviors/behavior_hold_tap.c
+++ b/app/src/behaviors/behavior_hold_tap.c
@@ -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;
};
@@ -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) {
@@ -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) {
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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();
@@ -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();
@@ -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, \
diff --git a/app/tests/hold-tap/balanced/5-quick-tap/events.patterns b/app/tests/hold-tap/balanced/5-quick-tap/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/5-quick-tap/events.patterns
@@ -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 \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/5-quick-tap/keycode_events.snapshot b/app/tests/hold-tap/balanced/5-quick-tap/keycode_events.snapshot
new file mode 100644
index 0000000..a1b18d1
--- /dev/null
+++ b/app/tests/hold-tap/balanced/5-quick-tap/keycode_events.snapshot
@@ -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
diff --git a/app/tests/hold-tap/balanced/5-quick-tap/native_posix.keymap b/app/tests/hold-tap/balanced/5-quick-tap/native_posix.keymap
new file mode 100644
index 0000000..8f90ffa
--- /dev/null
+++ b/app/tests/hold-tap/balanced/5-quick-tap/native_posix.keymap
@@ -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)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/behavior_keymap.dtsi b/app/tests/hold-tap/balanced/behavior_keymap.dtsi
index 34a4d45..972b606 100644
--- a/app/tests/hold-tap/balanced/behavior_keymap.dtsi
+++ b/app/tests/hold-tap/balanced/behavior_keymap.dtsi
@@ -10,6 +10,7 @@
#binding-cells = <2>;
flavor = "balanced";
tapping_term_ms = <300>;
+ quick_tap_ms = <200>;
bindings = <&kp>, <&kp>;
};
};
diff --git a/app/tests/hold-tap/hold-preferred/5-quick-tap/events.patterns b/app/tests/hold-tap/hold-preferred/5-quick-tap/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/5-quick-tap/events.patterns
@@ -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 \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/5-quick-tap/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/5-quick-tap/keycode_events.snapshot
new file mode 100644
index 0000000..c3caf87
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/5-quick-tap/keycode_events.snapshot
@@ -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
diff --git a/app/tests/hold-tap/hold-preferred/5-quick-tap/native_posix.keymap b/app/tests/hold-tap/hold-preferred/5-quick-tap/native_posix.keymap
new file mode 100644
index 0000000..8f90ffa
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/5-quick-tap/native_posix.keymap
@@ -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)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi b/app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi
index e614319..2b35f89 100644
--- a/app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi
+++ b/app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi
@@ -12,6 +12,7 @@
#binding-cells = <2>;
flavor = "hold-preferred";
tapping_term_ms = <300>;
+ quick_tap_ms = <200>;
bindings = <&kp>, <&kp>;
};
};
diff --git a/app/tests/hold-tap/tap-preferred/5-quick-tap/events.patterns b/app/tests/hold-tap/tap-preferred/5-quick-tap/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/5-quick-tap/events.patterns
@@ -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 \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/5-quick-tap/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/5-quick-tap/keycode_events.snapshot
new file mode 100644
index 0000000..e89ccf3
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/5-quick-tap/keycode_events.snapshot
@@ -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
diff --git a/app/tests/hold-tap/tap-preferred/5-quick-tap/native_posix.keymap b/app/tests/hold-tap/tap-preferred/5-quick-tap/native_posix.keymap
new file mode 100644
index 0000000..8f90ffa
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/5-quick-tap/native_posix.keymap
@@ -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)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi b/app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi
index e6d33c0..80d6b0a 100644
--- a/app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi
+++ b/app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi
@@ -10,6 +10,7 @@
#binding-cells = <2>;
flavor = "tap-preferred";
tapping_term_ms = <300>;
+ quick_tap_ms = <200>;
bindings = <&kp>, <&kp>;
};
};
diff --git a/docs/docs/behaviors/hold-tap.md b/docs/docs/behaviors/hold-tap.md
index 0cf4888..c148fa3 100644
--- a/docs/docs/behaviors/hold-tap.md
+++ b/docs/docs/behaviors/hold-tap.md
@@ -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)
@@ -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:
```
@@ -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>;
};