summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorKevin <chenkevinh11@gmail.com>2020-09-02 17:37:39 -0700
committerKevin <chenkevinh11@gmail.com>2020-09-02 17:37:39 -0700
commit5b493ef334c32a7074e29b68f779f81cbdf6596c (patch)
tree193a6ebf7e0820a3fbb54f2f42487991b8321144 /app
parent068626d1a74d3883a8ccb2cd514a217098e99420 (diff)
parentd53a8e36ca17f988b7b1df754478baf2e87597e6 (diff)
Merge branch 'main' into docs/encoders
Sync with upstream
Diffstat (limited to 'app')
-rw-r--r--app/CMakeLists.txt6
-rw-r--r--app/boards/shields/iris/Kconfig.defconfig16
-rw-r--r--app/boards/shields/iris/Kconfig.shield8
-rw-r--r--app/boards/shields/iris/iris.conf0
-rw-r--r--app/boards/shields/iris/iris.dtsi51
-rw-r--r--app/boards/shields/iris/iris.keymap59
-rw-r--r--app/boards/shields/iris/iris_left.conf2
-rw-r--r--app/boards/shields/iris/iris_left.overlay22
-rw-r--r--app/boards/shields/iris/iris_right.conf2
-rw-r--r--app/boards/shields/iris/iris_right.overlay26
-rw-r--r--app/boards/shields/sofle/Kconfig.defconfig54
-rw-r--r--app/boards/shields/sofle/Kconfig.shield8
-rw-r--r--app/boards/shields/sofle/sofle.conf9
-rw-r--r--app/boards/shields/sofle/sofle.dtsi91
-rw-r--r--app/boards/shields/sofle/sofle.keymap63
-rw-r--r--app/boards/shields/sofle/sofle_left.conf5
-rw-r--r--app/boards/shields/sofle/sofle_left.overlay26
-rw-r--r--app/boards/shields/sofle/sofle_right.conf5
-rw-r--r--app/boards/shields/sofle/sofle_right.overlay30
-rw-r--r--app/drivers/zephyr/kscan_gpio_direct.c4
-rw-r--r--app/dts/behaviors.dtsi1
-rw-r--r--app/dts/behaviors/layer_tap.dtsi12
-rw-r--r--app/dts/behaviors/mod_tap.dtsi5
-rw-r--r--app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml23
-rw-r--r--app/dts/bindings/behaviors/zmk,behavior-mod-tap.yaml8
-rw-r--r--app/include/dt-bindings/zmk/keys.h5
-rw-r--r--app/include/zmk/event-manager.h5
-rw-r--r--app/src/behaviors/behavior_hold_tap.c502
-rw-r--r--app/src/behaviors/behavior_mod_tap.c252
-rw-r--r--app/src/event_manager.c18
-rw-r--r--app/src/keymap.c56
-rw-r--r--app/tests/hold-tap/README.md1
-rw-r--r--app/tests/hold-tap/balanced/1-dn-up/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/1-dn-up/keycode_events.snapshot5
-rw-r--r--app/tests/hold-tap/balanced/1-dn-up/native_posix.keymap11
-rw-r--r--app/tests/hold-tap/balanced/2-dn-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/2-dn-timer-up/keycode_events.snapshot5
-rw-r--r--app/tests/hold-tap/balanced/2-dn-timer-up/native_posix.keymap11
-rw-r--r--app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/native_posix.keymap13
-rw-r--r--app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/native_posix.keymap13
-rw-r--r--app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/native_posix.keymap13
-rw-r--r--app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/keycode_events.snapshot10
-rw-r--r--app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/events.patterns4
-rw-r--r--app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/balanced/behavior_keymap.dtsi27
-rw-r--r--app/tests/hold-tap/hold-preferred/1-dn-up/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/1-dn-up/keycode_events.snapshot5
-rw-r--r--app/tests/hold-tap/hold-preferred/1-dn-up/native_posix.keymap11
-rw-r--r--app/tests/hold-tap/hold-preferred/2-dn-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/2-dn-timer-up/keycode_events.snapshot5
-rw-r--r--app/tests/hold-tap/hold-preferred/2-dn-timer-up/native_posix.keymap11
-rw-r--r--app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/native_posix.keymap13
-rw-r--r--app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/native_posix.keymap13
-rw-r--r--app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/native_posix.keymap13
-rw-r--r--app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot10
-rw-r--r--app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/events.patterns4
-rw-r--r--app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi29
-rw-r--r--app/tests/hold-tap/tap-preferred/1-dn-up/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/1-dn-up/keycode_events.snapshot5
-rw-r--r--app/tests/hold-tap/tap-preferred/1-dn-up/native_posix.keymap11
-rw-r--r--app/tests/hold-tap/tap-preferred/2-dn-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/2-dn-timer-up/keycode_events.snapshot5
-rw-r--r--app/tests/hold-tap/tap-preferred/2-dn-timer-up/native_posix.keymap11
-rw-r--r--app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/native_posix.keymap13
-rw-r--r--app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/native_posix.keymap13
-rw-r--r--app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/native_posix.keymap13
-rw-r--r--app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot10
-rw-r--r--app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/events.patterns4
-rw-r--r--app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot7
-rw-r--r--app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/native_posix.keymap14
-rw-r--r--app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi27
-rw-r--r--app/tests/hold-tap/zmk-modtap-proposal.odgbin0 -> 23148 bytes
-rw-r--r--app/tests/hold-tap/zmk-modtap-proposal.pdfbin0 -> 25493 bytes
136 files changed, 1961 insertions, 292 deletions
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 9fce3b6..2e24fdc 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -25,7 +25,6 @@ zephyr_linker_sources(RODATA include/linker/zmk-events.ld)
target_include_directories(app PRIVATE include)
target_sources(app PRIVATE src/kscan.c)
target_sources(app PRIVATE src/matrix_transform.c)
-target_sources(app PRIVATE src/keymap.c)
target_sources(app PRIVATE src/hid.c)
target_sources(app PRIVATE src/sensors.c)
target_sources_ifdef(CONFIG_ZMK_DISPLAY app PRIVATE src/display.c)
@@ -36,12 +35,13 @@ target_sources(app PRIVATE src/events/modifiers_state_changed.c)
target_sources(app PRIVATE src/events/sensor_event.c)
target_sources(app PRIVATE src/behaviors/behavior_key_press.c)
target_sources(app PRIVATE src/behaviors/behavior_reset.c)
-target_sources(app PRIVATE src/behaviors/behavior_mod_tap.c)
+target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c)
target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_transparent.c)
target_sources(app PRIVATE src/behaviors/behavior_none.c)
target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c)
+target_sources(app PRIVATE src/keymap.c)
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble_unpair_combo.c)
@@ -56,3 +56,5 @@ target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c)
target_sources(app PRIVATE src/endpoints.c)
target_sources(app PRIVATE src/hid_listener.c)
target_sources(app PRIVATE src/main.c)
+
+zephyr_cc_option(-Wfatal-errors) \ No newline at end of file
diff --git a/app/boards/shields/iris/Kconfig.defconfig b/app/boards/shields/iris/Kconfig.defconfig
new file mode 100644
index 0000000..6439780
--- /dev/null
+++ b/app/boards/shields/iris/Kconfig.defconfig
@@ -0,0 +1,16 @@
+# Copyright (c) 2020 Pete Johanson, Kurtis Lew
+# SPDX-License-Identifier: MIT
+
+if SHIELD_IRIS_LEFT
+
+config ZMK_KEYBOARD_NAME
+ default "Iris Left"
+
+endif
+
+if SHIELD_IRIS_RIGHT
+
+config ZMK_KEYBOARD_NAME
+ default "Iris Right"
+
+endif
diff --git a/app/boards/shields/iris/Kconfig.shield b/app/boards/shields/iris/Kconfig.shield
new file mode 100644
index 0000000..370bd22
--- /dev/null
+++ b/app/boards/shields/iris/Kconfig.shield
@@ -0,0 +1,8 @@
+# Copyright (c) 2020 Pete Johanson, Kurtis Lew
+# SPDX-License-Identifier: MIT
+
+config SHIELD_IRIS_LEFT
+ def_bool $(shields_list_contains,iris_left)
+
+config SHIELD_IRIS_RIGHT
+ def_bool $(shields_list_contains,iris_right)
diff --git a/app/boards/shields/iris/iris.conf b/app/boards/shields/iris/iris.conf
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/boards/shields/iris/iris.conf
diff --git a/app/boards/shields/iris/iris.dtsi b/app/boards/shields/iris/iris.dtsi
new file mode 100644
index 0000000..f6e32c4
--- /dev/null
+++ b/app/boards/shields/iris/iris.dtsi
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020 Pete Johanson, Kurtis Lew
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <dt-bindings/zmk/matrix-transform.h>
+
+/ {
+ chosen {
+ zmk,kscan = &kscan0;
+ zmk,matrix_transform = &default_transform;
+ };
+
+ default_transform: keymap_transform_0 {
+ compatible = "zmk,matrix-transform";
+ columns = <16>;
+ rows = <4>;
+// | SW6 | SW5 | SW4 | SW3 | SW2 | SW1 | | SW1 | SW2 | SW3 | SW4 | SW5 | SW6 |
+// | SW12 | SW11 | SW10 | SW9 | SW8 | SW7 | | SW7 | SW8 | SW9 | SW10 | SW11 | SW12 |
+// | SW18 | SW17 | SW16 | SW15 | SW14 | SW13 | | SW13 | SW14 | SW15 | SW16 | SW17 | SW18 |
+// | SW24 | SW23 | SW22 | SW21 | SW20 | SW19 | SW25 | | SW25 | SW19 | SW20 | SW21 | SW22 | SW23 | SW24 |
+// | SW29 | SW28 | SW27 | SW26 | | SW26 | SW27 | SW28 | SW29 |
+ map = <
+RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10) RC(0,11)
+RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,6) RC(1,7) RC(1,8) RC(1,9) RC(1,10) RC(1,11)
+RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,6) RC(2,7) RC(2,8) RC(2,9) RC(2,10) RC(2,11)
+RC(3,0) RC(3,1) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(4,2) RC(4,9) RC(3,6) RC(3,7) RC(3,8) RC(3,9) RC(3,10) RC(3,11)
+ RC(4,3) RC(4,4) RC(4,5) RC(4,6) RC(4,7) RC(4,8)
+ >;
+ };
+
+ kscan0: kscan {
+ compatible = "zmk,kscan-gpio-matrix";
+ label = "KSCAN";
+
+ diode-direction = "col2row";
+ row-gpios
+ = <&pro_micro_d 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
+ , <&pro_micro_d 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
+ , <&pro_micro_d 8 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
+ , <&pro_micro_d 0 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
+ , <&pro_micro_d 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
+ ;
+
+ };
+
+ bt_unpair_combo: bt_unpair_combo {
+ compatible = "zmk,bt-unpair-combo";
+ };
+}; \ No newline at end of file
diff --git a/app/boards/shields/iris/iris.keymap b/app/boards/shields/iris/iris.keymap
new file mode 100644
index 0000000..46b0817
--- /dev/null
+++ b/app/boards/shields/iris/iris.keymap
@@ -0,0 +1,59 @@
+# Copyright (c) 2020 Pete Johanson, Kurtis Lew
+# SPDX-License-Identifier: MIT
+
+#include <behaviors.dtsi>
+#include <dt-bindings/zmk/keys.h>
+
+/ {
+ keymap {
+ compatible = "zmk,keymap";
+
+ default_layer {
+// ------------------------------------------------------------------------------------------------------------
+// | ESC | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | ` |
+// | TAB | Q | W | E | R | T | | Y | U | I | O | P | - |
+// | CTRL | A | S | D | F | G | | H | J | K | L | ; | ' |
+// | SHIFT | Z | X | C | V | B | "[" | | "]" | N | M | , | . | / | SHIFT |
+// | GUI | LOWER| SPACE | | ENTER | RAISE| ALT |
+ bindings = <
+&kp ESC &kp NUM_1 &kp NUM_2 &kp NUM_3 &kp NUM_4 &kp NUM_5 &kp NUM_6 &kp NUM_7 &kp NUM_8 &kp NUM_9 &kp NUM_0 &kp GRAV
+&kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp MINUS
+&kp LCTL &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT
+&kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LBKT &kp RBKT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RSFT
+ &kp LGUI &mo 1 &kp SPC &kp RET &mo 2 &kp RALT
+ >;
+ };
+
+ lower_layer {
+// ------------------------------------------------------------------------------------------------------------
+// | | | | | | | | | | | | | |
+// | F1 | F2 | F3 | F4 | F5 | F6 | | F7 | F8 | F9 | F10 | F11 | F12 |
+// | ` | ! | @ | # | $ | % | | ^ | & | * | ( | ) | ~ |
+// | | | | | | | | | | | _ | + | { | } | "|" |
+// | | | | | | | |
+ bindings = <
+&trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans
+&kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &kp F7 &kp F8 &kp F9 &kp F10 &kp F11 &kp F12
+&kp GRAV &kp BANG &kp ATSN &kp HASH &kp CURU &kp PRCT &kp CRRT &kp AMPS &kp KMLT &kp LPRN &kp RPRN &kp TILD
+&trans &trans &trans &trans &trans &trans &trans &trans &trans &kp MINUS &kp KPLS &kp LCUR &kp RCUR &kp PIPE
+ &trans &trans &trans &trans &trans &trans
+ >;
+ };
+
+ raise_layer {
+// ------------------------------------------------------------------------------------------------------------
+// | | | | | | | | | | | | | |
+// | ` | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | |
+// | F1 | F2 | F3 | F4 | F5 | F6 | | | <- | ^ | v | -> | |
+// | F7 | F8 | F9 | F10 | F11 | F12 | | | | + | - | = | [ | ] | \ |
+// | | | | | | | |
+ bindings = <
+&trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans
+&kp GRAV &kp NUM_1 &kp NUM_2 &kp NUM_3 &kp NUM_4 &kp NUM_5 &kp NUM_6 &kp NUM_7 &kp NUM_8 &kp NUM_9 &kp NUM_0 &trans
+&kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &trans &kp LARW &kp DARW &kp UARW &kp RARW &trans
+&kp F7 &kp F8 &kp F9 &kp F10 &kp F11 &kp F12 &trans &trans &kp KPLS &kp MINUS &kp EQL &kp LBKT &kp RBKT &kp BSLH
+ &trans &trans &trans &trans &trans &trans
+ >;
+ };
+ };
+};
diff --git a/app/boards/shields/iris/iris_left.conf b/app/boards/shields/iris/iris_left.conf
new file mode 100644
index 0000000..1e028a7
--- /dev/null
+++ b/app/boards/shields/iris/iris_left.conf
@@ -0,0 +1,2 @@
+CONFIG_ZMK_SPLIT=y
+CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL=y
diff --git a/app/boards/shields/iris/iris_left.overlay b/app/boards/shields/iris/iris_left.overlay
new file mode 100644
index 0000000..e04638e
--- /dev/null
+++ b/app/boards/shields/iris/iris_left.overlay
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2020 Pete Johanson, Kurtis Lew
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "iris.dtsi"
+
+&kscan0 {
+ col-gpios
+ = <&pro_micro_a 1 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_a 0 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 15 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 14 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 16 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 10 GPIO_ACTIVE_HIGH>
+ ;
+};
+
+&bt_unpair_combo {
+ key-positions = <0 42>;
+};
diff --git a/app/boards/shields/iris/iris_right.conf b/app/boards/shields/iris/iris_right.conf
new file mode 100644
index 0000000..990cf7c
--- /dev/null
+++ b/app/boards/shields/iris/iris_right.conf
@@ -0,0 +1,2 @@
+CONFIG_ZMK_SPLIT=y
+CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL=y
diff --git a/app/boards/shields/iris/iris_right.overlay b/app/boards/shields/iris/iris_right.overlay
new file mode 100644
index 0000000..3f3dcb8
--- /dev/null
+++ b/app/boards/shields/iris/iris_right.overlay
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 Pete Johanson, Kurtis Lew
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "iris.dtsi"
+
+&default_transform {
+ col-offset = <6>;
+};
+
+&kscan0 {
+ col-gpios
+ = <&pro_micro_a 1 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_a 0 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 15 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 14 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 16 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 10 GPIO_ACTIVE_HIGH>
+ ;
+};
+
+&bt_unpair_combo {
+ key-positions = <11 43>;
+};
diff --git a/app/boards/shields/sofle/Kconfig.defconfig b/app/boards/shields/sofle/Kconfig.defconfig
new file mode 100644
index 0000000..58d8217
--- /dev/null
+++ b/app/boards/shields/sofle/Kconfig.defconfig
@@ -0,0 +1,54 @@
+# Copyright (c) 2020 Ryan Cross
+# SPDX-License-Identifier: MIT
+
+if SHIELD_SOFLE_LEFT
+
+config ZMK_KEYBOARD_NAME
+ default "Sofle Left"
+endif
+
+if SHIELD_SOFLE_RIGHT
+
+config ZMK_KEYBOARD_NAME
+ default "Sofle Right"
+endif
+
+if SHIELD_SOFLE_LEFT || SHIELD_SOFLE_RIGHT
+
+if ZMK_DISPLAY
+
+config I2C
+ default y
+
+config SSD1306
+ default y
+
+config SSD1306_REVERSE_MODE
+ default y
+
+endif # ZMK_DISPLAY
+
+if LVGL
+
+config LVGL_HOR_RES
+ default 128
+
+config LVGL_VER_RES
+ default 32
+
+config LVGL_VDB_SIZE
+ default 64
+
+config LVGL_DPI
+ default 148
+
+config LVGL_BITS_PER_PIXEL
+ default 1
+
+choice LVGL_COLOR_DEPTH
+ default LVGL_COLOR_DEPTH_1
+endchoice
+
+endif # LVGL
+
+endif
diff --git a/app/boards/shields/sofle/Kconfig.shield b/app/boards/shields/sofle/Kconfig.shield
new file mode 100644
index 0000000..e23a97a
--- /dev/null
+++ b/app/boards/shields/sofle/Kconfig.shield
@@ -0,0 +1,8 @@
+# Copyright (c) 2020 Ryan Cross
+# SPDX-License-Identifier: MIT
+
+config SHIELD_SOFLE_LEFT
+ def_bool $(shields_list_contains,sofle_left)
+
+config SHIELD_SOFLE_RIGHT
+ def_bool $(shields_list_contains,sofle_right)
diff --git a/app/boards/shields/sofle/sofle.conf b/app/boards/shields/sofle/sofle.conf
new file mode 100644
index 0000000..fe3f0f4
--- /dev/null
+++ b/app/boards/shields/sofle/sofle.conf
@@ -0,0 +1,9 @@
+# Copyright (c) 2020 Ryan Cross
+# SPDX-License-Identifier: MIT
+
+# Uncomment the following line to enable the Sofle OLED Display
+# CONFIG_ZMK_DISPLAY=y
+
+# Uncomment these two lines to add support for encoders
+# CONFIG_EC11=y
+# CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y
diff --git a/app/boards/shields/sofle/sofle.dtsi b/app/boards/shields/sofle/sofle.dtsi
new file mode 100644
index 0000000..653a772
--- /dev/null
+++ b/app/boards/shields/sofle/sofle.dtsi
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2020 Pete Johanson, Ryan Cross
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <dt-bindings/zmk/matrix-transform.h>
+
+/ {
+ chosen {
+ zmk,kscan = &kscan0;
+ zmk,matrix_transform = &default_transform;
+ };
+
+ default_transform: keymap_transform_0 {
+ compatible = "zmk,matrix-transform";
+ columns = <16>;
+ rows = <4>;
+// | SW6 | SW5 | SW4 | SW3 | SW2 | SW1 | | SW1 | SW2 | SW3 | SW4 | SW5 | SW6 |
+// | SW12 | SW11 | SW10 | SW9 | SW8 | SW7 | | SW7 | SW8 | SW9 | SW10 | SW11 | SW12 |
+// | SW18 | SW17 | SW16 | SW15 | SW14 | SW13 | | SW13 | SW14 | SW15 | SW16 | SW17 | SW18 |
+// | SW24 | SW23 | SW22 | SW21 | SW20 | SW19 | SW25 | | SW25 | SW19 | SW20 | SW21 | SW22 | SW23 | SW24 |
+// | SW30 | SW29 | SW28 | SW27 | SW26 | | SW26 | SW27 | SW28 | SW29 | SW30 |
+ map = <
+RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10) RC(0,11)
+RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,6) RC(1,7) RC(1,8) RC(1,9) RC(1,10) RC(1,11)
+RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,6) RC(2,7) RC(2,8) RC(2,9) RC(2,10) RC(2,11)
+RC(3,0) RC(3,1) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(4,5) RC(4,6) RC(3,6) RC(3,7) RC(3,8) RC(3,9) RC(3,10) RC(3,11)
+ RC(4,0) RC(4,1) RC(4,2) RC(4,3) RC(4,4) RC(4,7) RC(4,8) RC(4,9) RC(4,10) RC(4,11)
+ >;
+ };
+
+ kscan0: kscan {
+ compatible = "zmk,kscan-gpio-matrix";
+ label = "KSCAN";
+
+ diode-direction = "col2row";
+ row-gpios
+ = <&pro_micro_d 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
+ , <&pro_micro_d 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
+ , <&pro_micro_d 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
+ , <&pro_micro_d 8 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
+ , <&pro_micro_d 9 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
+ ;
+ };
+
+ left_encoder: encoder_left {
+ compatible = "alps,ec11";
+ label = "LEFT_ENCODER";
+ a-gpios = <&pro_micro_a 3 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>;
+ b-gpios = <&pro_micro_a 2 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>;
+ resolution = <4>;
+ };
+
+ right_encoder: encoder_right {
+ compatible = "alps,ec11";
+ label = "RIGHT_ENCODER";
+ a-gpios = <&pro_micro_a 2 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>;
+ b-gpios = <&pro_micro_a 3 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>;
+ resolution = <4>;
+ };
+
+ sensors {
+ compatible = "zmk,keymap-sensors";
+ sensors = <&left_encoder &right_encoder>;
+ };
+
+ bt_unpair_combo: bt_unpair_combo {
+ compatible = "zmk,bt-unpair-combo";
+ };
+};
+
+&pro_micro_i2c {
+ status = "okay";
+
+ ssd1306@3c {
+ compatible = "solomon,ssd1306fb";
+ reg = <0x3c>;
+ label = "DISPLAY";
+ width = <128>;
+ height = <32>;
+ segment-offset = <0>;
+ page-offset = <0>;
+ display-offset = <0>;
+ multiplex-ratio = <31>;
+ segment-remap;
+ com-invdir;
+ com-sequential;
+ prechargep = <0x22>;
+ };
+};
diff --git a/app/boards/shields/sofle/sofle.keymap b/app/boards/shields/sofle/sofle.keymap
new file mode 100644
index 0000000..aadffa1
--- /dev/null
+++ b/app/boards/shields/sofle/sofle.keymap
@@ -0,0 +1,63 @@
+#include <behaviors.dtsi>
+#include <dt-bindings/zmk/keys.h>
+
+/ {
+ keymap {
+ compatible = "zmk,keymap";
+
+ default_layer {
+// ------------------------------------------------------------------------------------------------------------
+// | ` | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | |
+// | ESC | Q | W | E | R | T | | Y | U | I | O | P | BKSPC |
+// | TAB | A | S | D | F | G | | H | J | K | L | ; | ' |
+// | SHIFT | Z | X | C | V | B | MUTE | | | N | M | , | . | / | SHIFT |
+// | GUI | ALT | CTRL | LOWER| ENTER | | SPACE | RAISE| CTRL | ALT | GUI |
+ bindings = <
+&kp GRAV &kp NUM_1 &kp NUM_2 &kp NUM_3 &kp NUM_4 &kp NUM_5 &kp NUM_6 &kp NUM_7 &kp NUM_8 &kp NUM_9 &kp NUM_0 &none
+&kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BKSP
+&kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT
+&kp LSFT &kp Z &kp X &kp C &kp V &kp B &cp M_MUTE &none &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RSFT
+ &kp LGUI &kp LALT &kp LCTL &mo 1 &kp RET &kp SPC &mo 2 &kp RCTL &kp RALT &kp RGUI
+ >;
+
+ sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>;
+ };
+
+ lower_layer {
+// TODO: Some binds are waiting for shifted keycode support.
+// ------------------------------------------------------------------------------------------------------------
+// | | F1 | F2 | F3 | F4 | F5 | | F6 | F7 | F8 | F9 | F10 | F11 |
+// | ` | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | F12 |
+// | | ! | @ | # | $ | % | | ^ | & | * | ( | ) | | |
+// | | = | - | + | { | } | | | | [ | ] | ; | : | \ | |
+// | | | | | | | | | | | |
+ bindings = <
+&trans &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &kp F7 &kp F8 &kp F9 &kp F10 &kp F11
+&kp GRAV &kp NUM_1 &kp NUM_2 &kp NUM_3 &kp NUM_4 &kp NUM_5 &kp NUM_6 &kp NUM_7 &kp NUM_8 &kp NUM_9 &kp NUM_0 &kp F12
+&trans &kp BANG &kp ATSN &kp HASH &kp CURU &kp PRCT &kp CRRT &kp AMPS &kp KMLT &kp LPRN &kp RPRN &kp PIPE
+&trans &kp EQL &kp MINUS &kp KPLS &kp LCUR &kp RCUR &trans &trans &kp LBKT &kp RBKT &kp SCLN &kp COLN &kp BSLH &trans
+ &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans
+ >;
+
+ sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>;
+ };
+
+ raise_layer {
+// ------------------------------------------------------------------------------------------------------------
+// | | | | | | | | | | | | | |
+// | | INS | PSCR | GUI | | | | PGUP | | ^ | | | |
+// | | ALT | CTRL | SHIFT | | CAPS | | PGDN | <- | v | -> | DEL | BKSPC |
+// | | UNDO | CUT | COPY | PASTE | | | | | | | | | | |
+// | | | | | | | | | | | |
+ bindings = <
+&trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans
+&trans &kp INS &kp PRSC &kp GUI &trans &trans &kp PGUP &trans &kp UARW &trans &kp NUM_0 &trans
+&trans &kp LALT &kp LCTL &kp LSFT &trans &kp CLCK &kp PGDN &kp LARW &kp DARW &kp RARW &kp DEL &kp BKSP
+&trans &kp UNDO &kp CUT &kp COPY &kp PSTE &trans &trans &trans &trans &trans &trans &trans &trans &trans
+ &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans
+ >;
+
+ sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>;
+ };
+ };
+};
diff --git a/app/boards/shields/sofle/sofle_left.conf b/app/boards/shields/sofle/sofle_left.conf
new file mode 100644
index 0000000..bbac735
--- /dev/null
+++ b/app/boards/shields/sofle/sofle_left.conf
@@ -0,0 +1,5 @@
+# Copyright (c) 2020 Ryan Cross
+# SPDX-License-Identifier: MIT
+
+CONFIG_ZMK_SPLIT=y
+CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL=y
diff --git a/app/boards/shields/sofle/sofle_left.overlay b/app/boards/shields/sofle/sofle_left.overlay
new file mode 100644
index 0000000..0dfb753
--- /dev/null
+++ b/app/boards/shields/sofle/sofle_left.overlay
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 Ryan Cross
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "sofle.dtsi"
+
+&kscan0 {
+ col-gpios
+ = <&pro_micro_a 1 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_a 0 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 15 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 14 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 16 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 10 GPIO_ACTIVE_HIGH>
+ ;
+};
+
+&left_encoder {
+ status = "okay";
+};
+
+&bt_unpair_combo {
+ key-positions = <0 54>;
+};
diff --git a/app/boards/shields/sofle/sofle_right.conf b/app/boards/shields/sofle/sofle_right.conf
new file mode 100644
index 0000000..ca5de38
--- /dev/null
+++ b/app/boards/shields/sofle/sofle_right.conf
@@ -0,0 +1,5 @@
+# Copyright (c) 2020 Ryan Cross
+# SPDX-License-Identifier: MIT
+
+CONFIG_ZMK_SPLIT=y
+CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL=y
diff --git a/app/boards/shields/sofle/sofle_right.overlay b/app/boards/shields/sofle/sofle_right.overlay
new file mode 100644
index 0000000..8eaf076
--- /dev/null
+++ b/app/boards/shields/sofle/sofle_right.overlay
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020 Ryan Cross
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "sofle.dtsi"
+
+&default_transform {
+ col-offset = <6>;
+};
+
+&kscan0 {
+ col-gpios
+ = <&pro_micro_d 10 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 16 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 14 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_d 15 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_a 0 GPIO_ACTIVE_HIGH>
+ , <&pro_micro_a 1 GPIO_ACTIVE_HIGH>
+ ;
+};
+
+&right_encoder {
+ status = "okay";
+};
+
+&bt_unpair_combo {
+ key-positions = <11 55>;
+};
diff --git a/app/drivers/zephyr/kscan_gpio_direct.c b/app/drivers/zephyr/kscan_gpio_direct.c
index 6e0beb0..a3aa8c4 100644
--- a/app/drivers/zephyr/kscan_gpio_direct.c
+++ b/app/drivers/zephyr/kscan_gpio_direct.c
@@ -156,7 +156,6 @@ static int kscan_gpio_read(struct device *dev)
struct kscan_gpio_data *data = dev->driver_data;
const struct kscan_gpio_config *cfg = dev->config_info;
u32_t read_state = data->pin_state;
- LOG_DBG("Scanning the pins for updated state");
for (int i = 0; i < cfg->num_of_inputs; i++)
{
struct device *in_dev = kscan_gpio_input_devices(dev)[i];
@@ -165,8 +164,9 @@ static int kscan_gpio_read(struct device *dev)
}
for (int i = 0; i < cfg->num_of_inputs; i++)
{
+ bool prev_pressed = BIT(i) & data->pin_state;
bool pressed = BIT(i) & read_state;
- if (pressed != (BIT(i) & data->pin_state))
+ if (pressed != prev_pressed)
{
LOG_DBG("Sending event at %d,%d state %s",
0, i, (pressed ? "on" : "off"));
diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi
index 4cfb7a0..ab70bcc 100644
--- a/app/dts/behaviors.dtsi
+++ b/app/dts/behaviors.dtsi
@@ -2,6 +2,7 @@
#include <behaviors/transparent.dtsi>
#include <behaviors/none.dtsi>
#include <behaviors/mod_tap.dtsi>
+#include <behaviors/layer_tap.dtsi>
#include <behaviors/momentary_layer.dtsi>
#include <behaviors/toggle_layer.dtsi>
#include <behaviors/reset.dtsi>
diff --git a/app/dts/behaviors/layer_tap.dtsi b/app/dts/behaviors/layer_tap.dtsi
new file mode 100644
index 0000000..af7319b
--- /dev/null
+++ b/app/dts/behaviors/layer_tap.dtsi
@@ -0,0 +1,12 @@
+/ {
+ behaviors {
+ lt: behavior_layer_tap {
+ compatible = "zmk,behavior-hold-tap";
+ label = "LAYER_TAP";
+ #binding-cells = <2>;
+ flavor = "tap-preferred";
+ tapping_term_ms = <200>;
+ bindings = <&mo>, <&kp>;
+ };
+ };
+};
diff --git a/app/dts/behaviors/mod_tap.dtsi b/app/dts/behaviors/mod_tap.dtsi
index 8e3b4e9..4ce732b 100644
--- a/app/dts/behaviors/mod_tap.dtsi
+++ b/app/dts/behaviors/mod_tap.dtsi
@@ -1,9 +1,12 @@
/ {
behaviors {
mt: behavior_mod_tap {
- compatible = "zmk,behavior-mod-tap";
+ compatible = "zmk,behavior-hold-tap";
label = "MOD_TAP";
#binding-cells = <2>;
+ flavor = "hold-preferred";
+ tapping_term_ms = <200>;
+ bindings = <&kp>, <&kp>;
};
};
};
diff --git a/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml b/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml
new file mode 100644
index 0000000..a20578f
--- /dev/null
+++ b/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml
@@ -0,0 +1,23 @@
+# Copyright (c) 2020, Cody McGinnis; Okke Formsma
+# SPDX-License-Identifier: MIT
+
+description: Hold or Tap behavior
+
+compatible: "zmk,behavior-hold-tap"
+
+include: two_param.yaml
+
+properties:
+ bindings:
+ type: phandles
+ required: true
+ tapping_term_ms:
+ type: int
+ flavor:
+ type: string
+ required: false
+ default: "hold-preferred"
+ enum:
+ - "hold-preferred"
+ - "balanced"
+ - "tap-preferred" \ No newline at end of file
diff --git a/app/dts/bindings/behaviors/zmk,behavior-mod-tap.yaml b/app/dts/bindings/behaviors/zmk,behavior-mod-tap.yaml
deleted file mode 100644
index 7911082..0000000
--- a/app/dts/bindings/behaviors/zmk,behavior-mod-tap.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2020, Pete Johanson
-# SPDX-License-Identifier: MIT
-
-description: Mod-Tap Beavhior
-
-compatible: "zmk,behavior-mod-tap"
-
-include: two_param.yaml
diff --git a/app/include/dt-bindings/zmk/keys.h b/app/include/dt-bindings/zmk/keys.h
index 0a52857..cd9261d 100644
--- a/app/include/dt-bindings/zmk/keys.h
+++ b/app/include/dt-bindings/zmk/keys.h
@@ -92,6 +92,11 @@
#define GUI 0x65
+#define UNDO 0x7A
+#define CUT 0x7B
+#define COPY 0x7C
+#define PSTE 0x7D
+
#define CURU 0xB4
#define LPRN 0xB6
diff --git a/app/include/zmk/event-manager.h b/app/include/zmk/event-manager.h
index 43d3f29..d9a56a4 100644
--- a/app/include/zmk/event-manager.h
+++ b/app/include/zmk/event-manager.h
@@ -75,9 +75,14 @@ struct zmk_event_subscription {
#define ZMK_EVENT_RAISE_AFTER(ev, mod) \
zmk_event_manager_raise_after((struct zmk_event_header *)ev, &zmk_listener_##mod);
+
+#define ZMK_EVENT_RAISE_AT(ev, mod) \
+ zmk_event_manager_raise_at((struct zmk_event_header *)ev, &zmk_listener_##mod);
+
#define ZMK_EVENT_RELEASE(ev) \
zmk_event_manager_release((struct zmk_event_header *)ev);
int zmk_event_manager_raise(struct zmk_event_header *event);
int zmk_event_manager_raise_after(struct zmk_event_header *event, const struct zmk_listener *listener);
+int zmk_event_manager_raise_at(struct zmk_event_header *event, const struct zmk_listener *listener);
int zmk_event_manager_release(struct zmk_event_header *event);
diff --git a/app/src/behaviors/behavior_hold_tap.c b/app/src/behaviors/behavior_hold_tap.c
new file mode 100644
index 0000000..2c6d996
--- /dev/null
+++ b/app/src/behaviors/behavior_hold_tap.c
@@ -0,0 +1,502 @@
+/*
+ * Copyright (c) 2020 Cody McGinnis, Okke Formsma
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define DT_DRV_COMPAT zmk_behavior_hold_tap
+
+#include <device.h>
+#include <drivers/behavior.h>
+#include <logging/log.h>
+#include <zmk/behavior.h>
+
+#include <zmk/matrix.h>
+#include <zmk/endpoints.h>
+#include <zmk/event-manager.h>
+#include <zmk/events/position-state-changed.h>
+#include <zmk/events/keycode-state-changed.h>
+#include <zmk/events/modifiers-state-changed.h>
+#include <zmk/hid.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#if DT_NODE_EXISTS(DT_DRV_INST(0))
+
+#define ZMK_BHV_HOLD_TAP_MAX_HELD 10
+#define ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS 40
+
+// increase if you have keyboard with more keys.
+#define ZMK_BHV_HOLD_TAP_POSITION_NOT_USED 9999
+
+
+enum flavor {
+ ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED = 0,
+ ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED = 1,
+ ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED = 2,
+};
+
+struct behavior_hold_tap_behaviors {
+ struct zmk_behavior_binding tap;
+ struct zmk_behavior_binding hold;
+};
+
+typedef k_timeout_t (*timer_func)();
+
+struct behavior_hold_tap_config {
+ timer_func tapping_term_ms;
+ struct behavior_hold_tap_behaviors *behaviors;
+ enum flavor flavor;
+};
+
+// this data is specific for each hold-tap
+struct active_hold_tap {
+ s32_t position;
+ u32_t param_hold;
+ u32_t param_tap;
+ bool is_decided;
+ bool is_hold;
+ const struct behavior_hold_tap_config *config;
+ struct k_delayed_work work;
+ bool work_is_cancelled;
+};
+
+// The undecided hold tap is the hold tap that needs to be decided before
+// other keypress events can be released. While the undecided_hold_tap is
+// not NULL, most events are captured in captured_events.
+// After the hold_tap is decided, it will stay in the active_hold_taps until
+// its key-up has been processed and the delayed work is cleaned up.
+struct active_hold_tap *undecided_hold_tap = NULL;
+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 struct zmk_event_header *captured_events[ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS] = {};
+
+static int capture_event(const struct zmk_event_header *event)
+{
+ for (int i = 0; i < ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS; i++) {
+ if (captured_events[i] == NULL) {
+ captured_events[i] = event;
+ return 0;
+ }
+ }
+ return -ENOMEM;
+}
+
+static struct position_state_changed *find_captured_keydown_event(u32_t position)
+{
+ struct position_state_changed *last_match = NULL;
+ for (int i = 0; i < ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS; i++) {
+ const struct zmk_event_header *eh = captured_events[i];
+ if (eh == NULL) {
+ return last_match;
+ }
+ if (!is_position_state_changed(eh)) {
+ continue;
+ }
+ struct position_state_changed *position_event = cast_position_state_changed(eh);
+ if (position_event->position == position && position_event->state) {
+ last_match = position_event;
+ }
+ }
+ return last_match;
+}
+
+const struct zmk_listener zmk_listener_behavior_hold_tap;
+
+static void release_captured_events()
+{
+ if (undecided_hold_tap != NULL) {
+ return;
+ }
+
+ // We use a trick to prevent copying the captured_events array.
+ //
+ // Events for different mod-tap instances are separated by a NULL pointer.
+ //
+ // The first event popped will never be catched by the next active hold-tap
+ // because to start capturing a mod-tap-key-down event must first completely
+ // go through the events queue.
+ //
+ // Example of this release process;
+ // [mt2_down, k1_down, k1_up, mt2_up, null, ...]
+ // ^
+ // mt2_down position event isn't captured because no hold-tap is active.
+ // mt2_down behavior event is handled, now we have an undecided hold-tap
+ // [null, k1_down, k1_up, mt2_up, null, ...]
+ // ^
+ // k1_down is captured by the mt2 mod-tap
+ // !note that searches for find_captured_keydown_event by the mt2 behavior will stop at the first null encountered
+ // [mt1_down, null, k1_up, mt2_up, null, ...]
+ // ^
+ // k1_up event is captured by the new hold-tap:
+ // [k1_down, k1_up, null, mt2_up, null, ...]
+ // ^
+ // mt2_up event is not captured but causes release of mt2 behavior
+ // [k1_down, k1_up, null, null, null, ...]
+ // now mt2 will start releasing it's own captured positions.
+ for (int i = 0; i < ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS; i++) {
+ const struct zmk_event_header *captured_event = captured_events[i];
+ if (captured_event == NULL) {
+ return;
+ }
+ captured_events[i] = NULL;
+ if (undecided_hold_tap != NULL) {
+ k_msleep(10);
+ }
+ if (is_position_state_changed(captured_event)) {
+ struct position_state_changed *position_event = cast_position_state_changed(captured_event);
+ LOG_DBG("Releasing key position event for position %d %s", position_event->position, (position_event->state ? "pressed" : "released"));
+ } else {
+ struct keycode_state_changed *modifier_event = cast_keycode_state_changed(captured_event);
+ LOG_DBG("Releasing mods changed event 0x%02X %s", modifier_event->keycode, (modifier_event->state ? "pressed" : "released"));
+ }
+ ZMK_EVENT_RAISE_AT(captured_event, behavior_hold_tap);
+ }
+}
+
+static struct active_hold_tap *find_hold_tap(u32_t position)
+{
+ for (int i = 0; i < ZMK_BHV_HOLD_TAP_MAX_HELD; i++) {
+ if (active_hold_taps[i].position == position) {
+ return &active_hold_taps[i];
+ }
+ }
+ return NULL;
+}
+
+static struct active_hold_tap *store_hold_tap(u32_t position, u32_t param_hold, u32_t param_tap, const struct behavior_hold_tap_config *config)
+{
+ for (int i = 0; i < ZMK_BHV_HOLD_TAP_MAX_HELD; i++) {
+ if (active_hold_taps[i].position != ZMK_BHV_HOLD_TAP_POSITION_NOT_USED) {
+ continue;
+ }
+ active_hold_taps[i].position = position;
+ active_hold_taps[i].is_decided = false;
+ active_hold_taps[i].is_hold = false;
+ active_hold_taps[i].config = config;
+ active_hold_taps[i].param_hold = param_hold;
+ active_hold_taps[i].param_tap = param_tap;
+ return &active_hold_taps[i];
+ }
+ return NULL;
+}
+
+static void clear_hold_tap(struct active_hold_tap *hold_tap)
+{
+ hold_tap->position = ZMK_BHV_HOLD_TAP_POSITION_NOT_USED;
+ hold_tap->is_decided = false;
+ hold_tap->is_hold = false;
+ hold_tap->work_is_cancelled = false;
+}
+
+enum decision_moment {
+ HT_KEY_UP = 0,
+ HT_OTHER_KEY_DOWN = 1,
+ HT_OTHER_KEY_UP = 2,
+ HT_TIMER_EVENT = 3,
+};
+
+static void decide_balanced(struct active_hold_tap *hold_tap, enum decision_moment event)
+{
+ switch (event) {
+ case HT_KEY_UP:
+ hold_tap->is_hold = 0;
+ hold_tap->is_decided = true;
+ break;
+ case HT_OTHER_KEY_UP:
+ case HT_TIMER_EVENT:
+ hold_tap->is_hold = 1;
+ hold_tap->is_decided = true;
+ break;
+ default: return;
+ }
+}
+
+static void decide_tap_preferred(struct active_hold_tap *hold_tap, enum decision_moment event)
+{
+ switch (event) {
+ case HT_KEY_UP:
+ hold_tap->is_hold = 0;
+ hold_tap->is_decided = true;
+ break;
+ case HT_TIMER_EVENT:
+ hold_tap->is_hold = 1;
+ hold_tap->is_decided = true;
+ break;
+ default: return;
+ }
+}
+
+static void decide_hold_preferred(struct active_hold_tap *hold_tap, enum decision_moment event)
+{
+ switch (event) {
+ case HT_KEY_UP:
+ hold_tap->is_hold = 0;
+ hold_tap->is_decided = true;
+ break;
+ case HT_OTHER_KEY_DOWN:
+ case HT_TIMER_EVENT:
+ hold_tap->is_hold = 1;
+ hold_tap->is_decided = true;
+ break;
+ default: return;
+ }
+}
+
+static inline char* flavor_str(enum flavor flavor) {
+ switch(flavor) {
+ case ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED:
+ return "hold-preferred";
+ case ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED:
+ return "balanced";
+ case ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED:
+ return "tap-preferred";
+ }
+ return "UNKNOWN FLAVOR";
+}
+
+static void decide_hold_tap(struct active_hold_tap *hold_tap, enum decision_moment event)
+{
+ if (hold_tap->is_decided) {
+ return;
+ }
+
+ if (hold_tap != undecided_hold_tap) {
+ LOG_DBG("ERROR found undecided tap hold that is not the active tap hold");
+ return;
+ }
+
+ switch(hold_tap->config->flavor) {
+ case ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED:
+ decide_hold_preferred(hold_tap, event);
+ case ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED:
+ decide_balanced(hold_tap, event);
+ case ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED:
+ decide_tap_preferred(hold_tap, event);
+ }
+
+ if (!hold_tap->is_decided) {
+ return;
+ }
+
+ LOG_DBG("%d decided %s (%s event %d)",
+ hold_tap->position,
+ hold_tap->is_hold ? "hold" : "tap",
+ flavor_str(hold_tap->config->flavor),
+ event);
+ undecided_hold_tap = NULL;
+
+ struct zmk_behavior_binding *behavior;
+ if (hold_tap->is_hold) {
+ behavior = &hold_tap->config->behaviors->hold;
+ struct device *behavior_device = device_get_binding(behavior->behavior_dev);
+ behavior_keymap_binding_pressed(behavior_device, hold_tap->position, hold_tap->param_hold, 0);
+ } else {
+ behavior = &hold_tap->config->behaviors->tap;
+ struct device *behavior_device = device_get_binding(behavior->behavior_dev);
+ behavior_keymap_binding_pressed(behavior_device, hold_tap->position, hold_tap->param_tap, 0);
+ }
+ release_captured_events();
+}
+
+static int on_hold_tap_binding_pressed(struct device *dev, u32_t position, u32_t param_hold, u32_t param_tap)
+{
+ const struct behavior_hold_tap_config *cfg = dev->config_info;
+
+ if (undecided_hold_tap != NULL) {
+ LOG_DBG("ERROR another hold-tap behavior is undecided.");
+ // if this happens, make sure the behavior events occur AFTER other position events.
+ return 0;
+ }
+
+ struct active_hold_tap *hold_tap = store_hold_tap(position, param_hold, param_tap, cfg);
+ if (hold_tap == NULL) {
+ LOG_ERR("unable to store hold-tap info, did you press more than %d hold-taps?", ZMK_BHV_HOLD_TAP_MAX_HELD);
+ return 0;
+ }
+
+ LOG_DBG("%d new undecided hold_tap", position);
+ undecided_hold_tap = hold_tap;
+ k_delayed_work_submit(&hold_tap->work, cfg->tapping_term_ms());
+
+ // todo: once we get timing info for keypresses, start the timer relative to the original keypress
+ // don't forget to simulate a timer-event before the event after that time was handled.
+
+ return 0;
+}
+
+static int on_hold_tap_binding_released(struct device *dev, u32_t position, u32_t _, u32_t __)
+{
+ struct active_hold_tap *hold_tap = find_hold_tap(position);
+
+ if (hold_tap == NULL) {
+ LOG_ERR("ACTIVE_HOLD_TAP_CLEANED_UP_TOO_EARLY");
+ return 0;
+ }
+
+ int work_cancel_result = k_delayed_work_cancel(&hold_tap->work);
+ decide_hold_tap(hold_tap, HT_KEY_UP);
+
+ struct zmk_behavior_binding *behavior;
+ if (hold_tap->is_hold) {
+ behavior = &hold_tap->config->behaviors->hold;
+ struct device *behavior_device = device_get_binding(behavior->behavior_dev);
+ behavior_keymap_binding_released(behavior_device, hold_tap->position, hold_tap->param_hold, 0);
+ } else {
+ behavior = &hold_tap->config->behaviors->tap;
+ struct device *behavior_device = device_get_binding(behavior->behavior_dev);
+ behavior_keymap_binding_released(behavior_device, hold_tap->position, hold_tap->param_tap, 0);
+ }
+
+
+ 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.
+ LOG_DBG("%d hold-tap timer work in event queue", position);
+ hold_tap->work_is_cancelled = true;
+ } else {
+ LOG_DBG("%d cleaning up hold-tap", position);
+ clear_hold_tap(hold_tap);
+ }
+
+ return 0;
+}
+
+static const struct behavior_driver_api behavior_hold_tap_driver_api = {
+ .binding_pressed = on_hold_tap_binding_pressed,
+ .binding_released = on_hold_tap_binding_released,
+};
+
+
+static int position_state_changed_listener(const struct zmk_event_header *eh)
+{
+ struct position_state_changed *ev = cast_position_state_changed(eh);
+
+ if (undecided_hold_tap == NULL) {
+ LOG_DBG("%d bubble (no undecided hold_tap active)", ev->position);
+ return 0;
+ }
+
+ if (undecided_hold_tap->position == ev->position) {
+ if (ev->state) { // keydown
+ LOG_ERR("hold-tap listener should be called before before most other listeners!");
+ return 0;
+ } else { // keyup
+ LOG_DBG("%d bubble undecided hold-tap keyrelease event", undecided_hold_tap->position);
+ return 0;
+ }
+ }
+
+ if (!ev->state && find_captured_keydown_event(ev->position) == NULL) {
+ // no keydown event has been captured, let it bubble.
+ // we'll catch modifiers later in modifier_state_changed_listener
+ LOG_DBG("%d bubbling %d %s event", undecided_hold_tap->position, ev->position, ev->state ? "down" : "up");
+ return 0;
+ }
+
+ LOG_DBG("%d capturing %d %s event", undecided_hold_tap->position, ev->position, ev->state ? "down" : "up");
+ capture_event(eh);
+ decide_hold_tap(undecided_hold_tap, ev->state ? HT_OTHER_KEY_DOWN : HT_OTHER_KEY_UP);
+ return ZMK_EV_EVENT_CAPTURED;
+}
+
+static bool is_mod(struct keycode_state_changed *ev)
+{
+ return ev->usage_page == USAGE_KEYPAD && ev->keycode >= LCTL && ev->keycode <= RGUI;
+}
+
+static int keycode_state_changed_listener(const struct zmk_event_header *eh)
+{
+ // we want to catch layer-up events too... how?
+ struct keycode_state_changed *ev = cast_keycode_state_changed(eh);
+
+ if (undecided_hold_tap == NULL) {
+ // LOG_DBG("0x%02X bubble (no undecided hold_tap active)", ev->keycode);
+ return 0;
+ }
+
+ if (!is_mod(ev)) {
+ // LOG_DBG("0x%02X bubble (not a mod)", ev->keycode);
+ return 0;
+ }
+
+ // 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, ev->state ? "down" : "up");
+ capture_event(eh);
+ return ZMK_EV_EVENT_CAPTURED;
+}
+
+
+int behavior_hold_tap_listener(const struct zmk_event_header *eh)
+{
+ if (is_position_state_changed(eh)) {
+ return position_state_changed_listener(eh);
+ } else if (is_keycode_state_changed(eh)) {
+ return keycode_state_changed_listener(eh);
+ }
+ return 0;
+}
+
+ZMK_LISTENER(behavior_hold_tap, behavior_hold_tap_listener);
+ZMK_SUBSCRIPTION(behavior_hold_tap, position_state_changed);
+// this should be modifiers_state_changed, but unfrotunately that's not implemented yet.
+ZMK_SUBSCRIPTION(behavior_hold_tap, keycode_state_changed);
+
+void behavior_hold_tap_timer_work_handler(struct k_work *item)
+{
+ struct active_hold_tap *hold_tap = CONTAINER_OF(item, struct active_hold_tap, work);
+
+ if (hold_tap->work_is_cancelled) {
+ clear_hold_tap(hold_tap);
+ } else {
+ decide_hold_tap(hold_tap, HT_TIMER_EVENT);
+ }
+}
+
+static int behavior_hold_tap_init(struct device *dev)
+{
+ static bool init_first_run = true;
+
+ if (init_first_run) {
+ for (int i = 0; i < ZMK_BHV_HOLD_TAP_MAX_HELD; i++) {
+ k_delayed_work_init(&active_hold_taps[i].work, behavior_hold_tap_timer_work_handler);
+ active_hold_taps[i].position = ZMK_BHV_HOLD_TAP_POSITION_NOT_USED;
+ }
+ }
+ init_first_run = false;
+ return 0;
+}
+
+struct behavior_hold_tap_data {};
+static struct behavior_hold_tap_data behavior_hold_tap_data;
+
+#define _TRANSFORM_ENTRY(idx, node) \
+ { \
+ .behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(node, bindings, idx)), \
+ .param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), (DT_INST_PHA_BY_IDX(node, bindings, idx, param1))), \
+ .param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), (DT_INST_PHA_BY_IDX(node, bindings, idx, param2))), \
+ },
+
+#define KP_INST(n) \
+ static k_timeout_t behavior_hold_tap_config_##n##_gettime() { return K_MSEC(DT_INST_PROP(n, tapping_term_ms)); } \
+ static struct behavior_hold_tap_behaviors behavior_hold_tap_behaviors_##n = { \
+ .hold = _TRANSFORM_ENTRY(0, n) \
+ .tap = _TRANSFORM_ENTRY(1, n) \
+ }; \
+ static struct behavior_hold_tap_config behavior_hold_tap_config_##n = { \
+ .behaviors = &behavior_hold_tap_behaviors_##n, \
+ .tapping_term_ms = &behavior_hold_tap_config_##n##_gettime, \
+ .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, \
+ &behavior_hold_tap_data, \
+ &behavior_hold_tap_config_##n, \
+ APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
+ &behavior_hold_tap_driver_api);
+
+DT_INST_FOREACH_STATUS_OKAY(KP_INST)
+
+
+#endif \ No newline at end of file
diff --git a/app/src/behaviors/behavior_mod_tap.c b/app/src/behaviors/behavior_mod_tap.c
deleted file mode 100644
index 5a2f60e..0000000
--- a/app/src/behaviors/behavior_mod_tap.c
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (c) 2020 Peter Johanson <peter@peterjohanson.com>
- *
- * SPDX-License-Identifier: MIT
- */
-
-#define DT_DRV_COMPAT zmk_behavior_mod_tap
-
-#include <device.h>
-#include <drivers/behavior.h>
-#include <logging/log.h>
-
-#include <zmk/matrix.h>
-#include <zmk/endpoints.h>
-#include <zmk/event-manager.h>
-#include <zmk/events/keycode-state-changed.h>
-#include <zmk/events/modifiers-state-changed.h>
-#include <zmk/hid.h>
-
-LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
-
-#define ZMK_BHV_MOD_TAP_MAX_HELD 4
-#define ZMK_BHV_MOD_TAP_MAX_PENDING_KC 4
-
-struct active_mod_tap_item {
- u32_t keycode;
- u8_t mods;
- bool pending;
- zmk_mod_flags active_mods;
-};
-
-struct captured_keycode_state_change_item {
- struct keycode_state_changed* event;
- zmk_mod_flags active_mods;
-};
-
-struct behavior_mod_tap_config { };
-struct behavior_mod_tap_data {
- struct active_mod_tap_item active_mod_taps[ZMK_BHV_MOD_TAP_MAX_HELD];
- struct captured_keycode_state_change_item captured_keycode_events[ZMK_BHV_MOD_TAP_MAX_PENDING_KC];
-};
-
-bool have_pending_mods(char *label) {
- struct device *dev = device_get_binding(label);
- struct behavior_mod_tap_data *data = dev->driver_data;
-
- for (int i = 0; i < ZMK_BHV_MOD_TAP_MAX_HELD; i++) {
- if (data->active_mod_taps[i].mods) {
- LOG_DBG("Found pending mods for %d and keycode 0x%02X", data->active_mod_taps[i].mods, data->active_mod_taps[i].keycode);
- return true;
- }
- }
-
- return false;
-}
-
-struct captured_keycode_state_change_item* find_pending_keycode(struct behavior_mod_tap_data *data, u32_t keycode)
-{
- for (int i = 0; i < ZMK_BHV_MOD_TAP_MAX_PENDING_KC; i++) {
- if (data->captured_keycode_events[i].event == NULL) {
- continue;
- }
-
- if (data->captured_keycode_events[i].event->keycode == keycode) {
- return &data->captured_keycode_events[i];
- }
- }
-
- return NULL;
-}
-
-zmk_mod_flags behavior_mod_tap_active_mods(struct behavior_mod_tap_data *data)
-{
- zmk_mod_flags mods = 0;
-
- for (int i = 0; i < ZMK_BHV_MOD_TAP_MAX_HELD; i++) {
- mods |= data->active_mod_taps[i].mods;
- }
-
- return mods;
-}
-
-int behavior_mod_tap_capture_keycode_event(struct behavior_mod_tap_data *data, struct keycode_state_changed *ev)
-{
- for (int i = 0; i < ZMK_BHV_MOD_TAP_MAX_PENDING_KC; i++) {
- if (data->captured_keycode_events[i].event != NULL) {
- continue;
- }
-
- data->captured_keycode_events[i].event = ev;
- data->captured_keycode_events[i].active_mods = behavior_mod_tap_active_mods(data);
- return 0;
- }
-
- return -ENOMEM;
-}
-
-void behavior_mod_tap_update_active_mods_state(struct behavior_mod_tap_data *data, zmk_mod_flags used_flags)
-{
- for (int i = 0; i < ZMK_BHV_MOD_TAP_MAX_HELD; i++) {
- if ((data->active_mod_taps[i].mods & used_flags) == data->active_mod_taps[i].mods) {
- data->active_mod_taps[i].pending = false;
- }
- }
-}
-
-// How to pass context to subscription?!
-int behavior_mod_tap_listener(const struct zmk_event_header *eh)
-{
- if (is_keycode_state_changed(eh) && have_pending_mods(DT_INST_LABEL(0))) {
- struct device *dev = device_get_binding(DT_INST_LABEL(0));
- struct keycode_state_changed *ev = cast_keycode_state_changed(eh);
- struct behavior_mod_tap_data *data = dev->driver_data;
- struct captured_keycode_state_change_item* pending_keycode;
- if (ev->state) {
- LOG_DBG("Have pending mods, capturing keycode 0x%02X event to ressend later", ev->keycode);
- behavior_mod_tap_capture_keycode_event(data, ev);
- return ZMK_EV_EVENT_CAPTURED;
- } else if ((pending_keycode = find_pending_keycode(data, ev->keycode)) != NULL) {
- LOG_DBG("Key released, going to activate mods 0x%02X then send pending key press for keycode 0x%02X",
- pending_keycode->active_mods, pending_keycode->event->keycode);
-
- zmk_hid_register_mods(pending_keycode->active_mods);
- behavior_mod_tap_update_active_mods_state(data, pending_keycode->active_mods);
-
- ZMK_EVENT_RELEASE(pending_keycode->event);
- k_msleep(10);
-
- pending_keycode->event = NULL;
- pending_keycode->active_mods = 0;
- }
- }
- return 0;
-}
-
-ZMK_LISTENER(behavior_mod_tap, behavior_mod_tap_listener);
-ZMK_SUBSCRIPTION(behavior_mod_tap, keycode_state_changed);
-
-static int behavior_mod_tap_init(struct device *dev)
-{
- return 0;
-};
-
-
-static int on_keymap_binding_pressed(struct device *dev, u32_t position, u32_t mods, u32_t keycode)
-{
- struct behavior_mod_tap_data *data = dev->driver_data;
- LOG_DBG("mods: %d, keycode: 0x%02X", mods, keycode);
- for (int i = 0; i < ZMK_BHV_MOD_TAP_MAX_HELD; i++) {
- if (data->active_mod_taps[i].mods != 0) {
- continue;
- }
-
- zmk_mod_flags active_mods = behavior_mod_tap_active_mods(data);
-
- data->active_mod_taps[i].active_mods = active_mods;
- data->active_mod_taps[i].mods = mods;
- data->active_mod_taps[i].keycode = keycode;
- data->active_mod_taps[i].pending = true;
-
- return 0;
- }
-
- LOG_WRN("Failed to record mod-tap activation, at maximum concurrent mod-tap activations");
-
- return -ENOMEM;
-}
-
-static int on_keymap_binding_released(struct device *dev, u32_t position, u32_t mods, u32_t keycode)
-{
- struct behavior_mod_tap_data *data = dev->driver_data;
- LOG_DBG("mods: %d, keycode: %d", mods, keycode);
-
- for (int i = 0; i < ZMK_BHV_MOD_TAP_MAX_HELD; i++) {
- struct active_mod_tap_item *item = &data->active_mod_taps[i];
- if (item->mods == mods && item->keycode == keycode) {
- if (item->pending) {
- LOG_DBG("Sending un-triggered mod-tap for keycode: 0x%02X", keycode);
-
- if (item->active_mods) {
- LOG_DBG("Registering recorded active mods captured when mod-tap initially activated: 0x%02X", item->active_mods);
- behavior_mod_tap_update_active_mods_state(data, item->active_mods);
- zmk_hid_register_mods(item->active_mods);
- }
-
- struct keycode_state_changed *key_press = create_keycode_state_changed(USAGE_KEYPAD, item->keycode, true);
- ZMK_EVENT_RAISE_AFTER(key_press, behavior_mod_tap);
- k_msleep(10);
-
- for (int j = 0; j < ZMK_BHV_MOD_TAP_MAX_PENDING_KC; j++) {
- if (data->captured_keycode_events[j].event == NULL) {
- continue;
- }
-
- struct keycode_state_changed *ev = data->captured_keycode_events[j].event;
- data->captured_keycode_events[j].event = NULL;
- data->captured_keycode_events[j].active_mods = 0;
- LOG_DBG("Re-sending latched key press for usage page 0x%02X keycode 0x%02X state %s", ev->usage_page, ev->keycode, (ev->state ? "pressed" : "released"));
- ZMK_EVENT_RELEASE(ev);
- k_msleep(10);
- }
-
- struct keycode_state_changed *key_release = create_keycode_state_changed(USAGE_KEYPAD, keycode, false);
- LOG_DBG("Sending un-triggered mod-tap release for keycode: 0x%02X", keycode);
- ZMK_EVENT_RAISE_AFTER(key_release, behavior_mod_tap);
- k_msleep(10);
-
- if (item->active_mods) {
- LOG_DBG("Unregistering recorded active mods captured when mod-tap initially activated: 0x%02X", item->active_mods);
- zmk_hid_unregister_mods(item->active_mods);
- zmk_endpoints_send_report(USAGE_KEYPAD);
- }
-
-
- } else {
- LOG_DBG("Releasing triggered mods: %d", mods);
- zmk_hid_unregister_mods(mods);
- zmk_endpoints_send_report(USAGE_KEYPAD);
- }
-
- item->mods = 0;
- item->keycode = 0;
- item->active_mods = 0;
-
- LOG_DBG("Removing mods %d from active_mods for other held mod-taps", mods);
- for (int j = 0; j < ZMK_BHV_MOD_TAP_MAX_HELD; j++) {
- if (data->active_mod_taps[j].active_mods & mods) {
- LOG_DBG("Removing 0x%02X from active mod tap mods 0x%02X keycode 0x%02X", mods, data->active_mod_taps[j].mods, data->active_mod_taps[j].keycode);
- data->active_mod_taps[j].active_mods &= ~mods;
- }
- }
- break;
- }
- }
-
- return 0;
-}
-
-static const struct behavior_driver_api behavior_mod_tap_driver_api = {
- .binding_pressed = on_keymap_binding_pressed,
- .binding_released = on_keymap_binding_released,
-};
-
-static const struct behavior_mod_tap_config behavior_mod_tap_config = {};
-
-static struct behavior_mod_tap_data behavior_mod_tap_data;
-
-DEVICE_AND_API_INIT(behavior_mod_tap, DT_INST_LABEL(0), behavior_mod_tap_init,
- &behavior_mod_tap_data,
- &behavior_mod_tap_config,
- APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
- &behavior_mod_tap_driver_api);
diff --git a/app/src/event_manager.c b/app/src/event_manager.c
index c405176..47ad6b7 100644
--- a/app/src/event_manager.c
+++ b/app/src/event_manager.c
@@ -71,7 +71,23 @@ int zmk_event_manager_raise_after(struct zmk_event_header *event, const struct z
return -EINVAL;
}
+int zmk_event_manager_raise_at(struct zmk_event_header *event, const struct zmk_listener *listener)
+{
+ u8_t len = __event_subscriptions_end - __event_subscriptions_start;
+ for (int i = 0; i < len; i++) {
+ struct zmk_event_subscription *ev_sub = __event_subscriptions_start + i;
+
+ if (ev_sub->event_type == event->event && ev_sub->listener == listener) {
+ return zmk_event_manager_handle_from(event, i);
+ }
+ }
+
+ LOG_WRN("Unable to find where to raise this event");
+
+ return -EINVAL;
+}
+
int zmk_event_manager_release(struct zmk_event_header *event)
{
return zmk_event_manager_handle_from(event, event->last_listener_index + 1);
-} \ No newline at end of file
+}
diff --git a/app/src/keymap.c b/app/src/keymap.c
index ee6e370..57cdad6 100644
--- a/app/src/keymap.c
+++ b/app/src/keymap.c
@@ -51,10 +51,10 @@ static u8_t zmk_keymap_layer_default = 0;
// State
-// When a behavior handles a key position "down" event, we record that layer
+// When a behavior handles a key position "down" event, we record the layer state
// here so that even if that layer is deactivated before the "up", event, we
// still send the release event to the behavior in that layer also.
-static u8_t zmk_keymap_active_behavior_layer[ZMK_KEYMAP_LEN];
+static u32_t zmk_keymap_active_behavior_layer[ZMK_KEYMAP_LEN];
static struct zmk_behavior_binding zmk_keymap[ZMK_KEYMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = {
DT_INST_FOREACH_CHILD(0, TRANSFORMED_LAYER)
@@ -101,47 +101,51 @@ int zmk_keymap_layer_toggle(u8_t layer)
return zmk_keymap_layer_activate(layer);
};
-bool is_active_position(u32_t position, u8_t layer)
+bool is_active_layer(u8_t layer, u32_t layer_state)
{
- return (zmk_keymap_layer_state & BIT(layer)) == BIT(layer)
- || layer == zmk_keymap_layer_default
- || zmk_keymap_active_behavior_layer[position] == layer;
+ return (layer_state & BIT(layer)) == BIT(layer)
+ || layer == zmk_keymap_layer_default;
}
+int zmk_keymap_apply_position_state(int layer, u32_t position, bool pressed)
+{
+ struct zmk_behavior_binding *binding = &zmk_keymap[layer][position];
+ struct device *behavior;
+
+ LOG_DBG("layer: %d position: %d, binding name: %s", layer, position, log_strdup(binding->behavior_dev));
+
+ behavior = device_get_binding(binding->behavior_dev);
+
+ if (!behavior) {
+ LOG_DBG("No behavior assigned to %d on layer %d", position, layer);
+ return 1;
+ }
+
+ if (pressed) {
+ return behavior_keymap_binding_pressed(behavior, position, binding->param1, binding->param2);
+ } else {
+ return behavior_keymap_binding_released(behavior, position, binding->param1, binding->param2);
+ }
+}
+
int zmk_keymap_position_state_changed(u32_t position, bool pressed)
{
for (int layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer >= zmk_keymap_layer_default; layer--)
{
- if (is_active_position(position, layer))
+ u32_t layer_state = pressed ? zmk_keymap_layer_state : zmk_keymap_active_behavior_layer[position];
+ if (is_active_layer(layer, layer_state))
{
- struct zmk_behavior_binding *binding = &zmk_keymap[layer][position];
- struct device *behavior;
- int ret;
-
- LOG_DBG("layer: %d position: %d, binding name: %s", layer, position, log_strdup(binding->behavior_dev));
+ int ret = zmk_keymap_apply_position_state(layer, position, pressed);
- behavior = device_get_binding(binding->behavior_dev);
-
- if (!behavior) {
- LOG_DBG("No behavior assigned to %d on layer %d", position, layer);
- continue;
- }
- if (pressed) {
- ret = behavior_keymap_binding_pressed(behavior, position, binding->param1, binding->param2);
- } else {
- ret = behavior_keymap_binding_released(behavior, position, binding->param1, binding->param2);
- }
-
+ zmk_keymap_active_behavior_layer[position] = zmk_keymap_layer_state;
if (ret > 0) {
LOG_DBG("behavior processing to continue to next layer");
continue;
} else if (ret < 0) {
LOG_DBG("Behavior returned error: %d", ret);
- zmk_keymap_active_behavior_layer[position] = 0;
return ret;
} else {
- zmk_keymap_active_behavior_layer[position] = pressed ? layer : 0;
return ret;
}
}
diff --git a/app/tests/hold-tap/README.md b/app/tests/hold-tap/README.md
new file mode 100644
index 0000000..0630132
--- /dev/null
+++ b/app/tests/hold-tap/README.md
@@ -0,0 +1 @@
+Refer to the pdf/open document "zmk-modtap-proposal.{pdf,odt}" in this directory for a visual representation of the numbered tests for hold-tap.
diff --git a/app/tests/hold-tap/balanced/1-dn-up/events.patterns b/app/tests/hold-tap/balanced/1-dn-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/1-dn-up/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/1-dn-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/1-dn-up/keycode_events.snapshot
new file mode 100644
index 0000000..5f6a266
--- /dev/null
+++ b/app/tests/hold-tap/balanced/1-dn-up/keycode_events.snapshot
@@ -0,0 +1,5 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided tap (balanced event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/balanced/1-dn-up/native_posix.keymap b/app/tests/hold-tap/balanced/1-dn-up/native_posix.keymap
new file mode 100644
index 0000000..10336ef
--- /dev/null
+++ b/app/tests/hold-tap/balanced/1-dn-up/native_posix.keymap
@@ -0,0 +1,11 @@
+#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)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/2-dn-timer-up/events.patterns b/app/tests/hold-tap/balanced/2-dn-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/2-dn-timer-up/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/2-dn-timer-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/2-dn-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..ddda1ae
--- /dev/null
+++ b/app/tests/hold-tap/balanced/2-dn-timer-up/keycode_events.snapshot
@@ -0,0 +1,5 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (balanced event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/balanced/2-dn-timer-up/native_posix.keymap b/app/tests/hold-tap/balanced/2-dn-timer-up/native_posix.keymap
new file mode 100644
index 0000000..aa93b86
--- /dev/null
+++ b/app/tests/hold-tap/balanced/2-dn-timer-up/native_posix.keymap
@@ -0,0 +1,11 @@
+#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,500)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/events.patterns b/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/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/3a-moddn-dn-modup-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/keycode_events.snapshot
new file mode 100644
index 0000000..a435103
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0xe4
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided tap (balanced event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_released: usage_page 0x07 keycode 0xe4
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/native_posix.keymap b/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/native_posix.keymap
new file mode 100644
index 0000000..6f08689
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/native_posix.keymap
@@ -0,0 +1,13 @@
+#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(1,1,10) /*ctrl*/
+ ZMK_MOCK_PRESS(0,0,100) /*mt f-shift */
+ ZMK_MOCK_RELEASE(1,1,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/events.patterns b/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/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/3b-moddn-dn-modup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..c0da94f
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0xe4
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (balanced event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_released: usage_page 0x07 keycode 0xe4
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/native_posix.keymap b/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/native_posix.keymap
new file mode 100644
index 0000000..392d328
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/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(1,1,10) /*ctrl*/
+ ZMK_MOCK_PRESS(0,0,50) /*mt f-shift */
+ ZMK_MOCK_RELEASE(1,1,300)
+ /*timer*/
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/events.patterns b/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/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/3c-kcdn-dn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/keycode_events.snapshot
new file mode 100644
index 0000000..ce6e7b7
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0x07
+ht_binding_pressed: 0 new undecided hold_tap
+kp_released: usage_page 0x07 keycode 0x07
+ht_decide: 0 decided tap (balanced event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/native_posix.keymap b/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/native_posix.keymap
new file mode 100644
index 0000000..77306cd
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/native_posix.keymap
@@ -0,0 +1,13 @@
+#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(1,0,10) /*d*/
+ ZMK_MOCK_PRESS(0,0,100) /*mt f-shift */
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/events.patterns b/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/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/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..1ec384a
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0x07
+ht_binding_pressed: 0 new undecided hold_tap
+kp_released: usage_page 0x07 keycode 0x07
+ht_decide: 0 decided hold (balanced event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/native_posix.keymap b/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/native_posix.keymap
new file mode 100644
index 0000000..1441331
--- /dev/null
+++ b/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/native_posix.keymap
@@ -0,0 +1,13 @@
+#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(1,0,10) /* d */
+ ZMK_MOCK_PRESS(0,0,100) /* mt f-shift */
+ ZMK_MOCK_RELEASE(1,0,400)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/events.patterns b/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/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/4a-dn-htdn-timer-htup-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/keycode_events.snapshot
new file mode 100644
index 0000000..8a1980b
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/keycode_events.snapshot
@@ -0,0 +1,10 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (balanced event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+ht_binding_pressed: 1 new undecided hold_tap
+ht_decide: 1 decided tap (balanced event 0)
+kp_pressed: usage_page 0x07 keycode 0x0d
+kp_released: usage_page 0x07 keycode 0x0d
+ht_binding_released: 1 cleaning up hold-tap
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/native_posix.keymap b/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/native_posix.keymap
new file mode 100644
index 0000000..c10c6d6
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/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,200)
+ ZMK_MOCK_PRESS(0,1,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(0,1,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/events.patterns b/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/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/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot
new file mode 100644
index 0000000..b89b21d
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (balanced event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/native_posix.keymap b/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/native_posix.keymap
new file mode 100644
index 0000000..ce163f5
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/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,200)
+ ZMK_MOCK_PRESS(1,0,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/events.patterns b/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/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/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..798e2ee
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (balanced event 2)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/native_posix.keymap b/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/native_posix.keymap
new file mode 100644
index 0000000..7abda41
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/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,100)
+ ZMK_MOCK_PRESS(1,0,100)
+ ZMK_MOCK_RELEASE(1,0,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/events.patterns b/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/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/4c-dn-kcdn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/keycode_events.snapshot
new file mode 100644
index 0000000..798e2ee
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (balanced event 2)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/native_posix.keymap b/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/native_posix.keymap
new file mode 100644
index 0000000..ce030af
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/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_PRESS(1,0,10)
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ /* timer */
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/events.patterns b/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/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/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot b/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot
new file mode 100644
index 0000000..5c9f4e3
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided tap (balanced event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
+kp_released: usage_page 0x07 keycode 0x07
diff --git a/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/native_posix.keymap b/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/native_posix.keymap
new file mode 100644
index 0000000..5467660
--- /dev/null
+++ b/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/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,100)
+ ZMK_MOCK_PRESS(1,0,100)
+ ZMK_MOCK_RELEASE(0,0,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(1,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
new file mode 100644
index 0000000..df56fb5
--- /dev/null
+++ b/app/tests/hold-tap/balanced/behavior_keymap.dtsi
@@ -0,0 +1,27 @@
+#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>;
+ bindings = <&kp>, <&kp>;
+ };
+ };
+
+ keymap {
+ compatible = "zmk,keymap";
+ label ="Default keymap";
+
+ default_layer {
+ bindings = <
+ &ht_bal LSFT F &ht_bal LCTL J
+ &kp D &kp RCTL>;
+ };
+ };
+};
diff --git a/app/tests/hold-tap/hold-preferred/1-dn-up/events.patterns b/app/tests/hold-tap/hold-preferred/1-dn-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/1-dn-up/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/1-dn-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/1-dn-up/keycode_events.snapshot
new file mode 100644
index 0000000..cf787d8
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/1-dn-up/keycode_events.snapshot
@@ -0,0 +1,5 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided tap (hold-preferred event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/hold-preferred/1-dn-up/native_posix.keymap b/app/tests/hold-tap/hold-preferred/1-dn-up/native_posix.keymap
new file mode 100644
index 0000000..10336ef
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/1-dn-up/native_posix.keymap
@@ -0,0 +1,11 @@
+#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)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/2-dn-timer-up/events.patterns b/app/tests/hold-tap/hold-preferred/2-dn-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/2-dn-timer-up/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/2-dn-timer-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/2-dn-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..03329d5
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/2-dn-timer-up/keycode_events.snapshot
@@ -0,0 +1,5 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (hold-preferred event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/hold-preferred/2-dn-timer-up/native_posix.keymap b/app/tests/hold-tap/hold-preferred/2-dn-timer-up/native_posix.keymap
new file mode 100644
index 0000000..aa93b86
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/2-dn-timer-up/native_posix.keymap
@@ -0,0 +1,11 @@
+#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,500)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/events.patterns b/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/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/3a-moddn-dn-modup-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot
new file mode 100644
index 0000000..adf4fe2
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0xe4
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided tap (hold-preferred event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_released: usage_page 0x07 keycode 0xe4
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/native_posix.keymap b/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/native_posix.keymap
new file mode 100644
index 0000000..6f08689
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/native_posix.keymap
@@ -0,0 +1,13 @@
+#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(1,1,10) /*ctrl*/
+ ZMK_MOCK_PRESS(0,0,100) /*mt f-shift */
+ ZMK_MOCK_RELEASE(1,1,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/events.patterns b/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/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/3b-moddn-dn-modup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..69b64a9
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0xe4
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (hold-preferred event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_released: usage_page 0x07 keycode 0xe4
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/native_posix.keymap b/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/native_posix.keymap
new file mode 100644
index 0000000..392d328
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/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(1,1,10) /*ctrl*/
+ ZMK_MOCK_PRESS(0,0,50) /*mt f-shift */
+ ZMK_MOCK_RELEASE(1,1,300)
+ /*timer*/
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/events.patterns b/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/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/3c-kcdn-dn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot
new file mode 100644
index 0000000..b06a1d7
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0x07
+ht_binding_pressed: 0 new undecided hold_tap
+kp_released: usage_page 0x07 keycode 0x07
+ht_decide: 0 decided tap (hold-preferred event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/native_posix.keymap b/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/native_posix.keymap
new file mode 100644
index 0000000..77306cd
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/native_posix.keymap
@@ -0,0 +1,13 @@
+#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(1,0,10) /*d*/
+ ZMK_MOCK_PRESS(0,0,100) /*mt f-shift */
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/events.patterns b/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/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/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..bf31955
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0x07
+ht_binding_pressed: 0 new undecided hold_tap
+kp_released: usage_page 0x07 keycode 0x07
+ht_decide: 0 decided hold (hold-preferred event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/native_posix.keymap b/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/native_posix.keymap
new file mode 100644
index 0000000..1441331
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/native_posix.keymap
@@ -0,0 +1,13 @@
+#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(1,0,10) /* d */
+ ZMK_MOCK_PRESS(0,0,100) /* mt f-shift */
+ ZMK_MOCK_RELEASE(1,0,400)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/events.patterns b/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/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/4a-dn-htdn-timer-htup-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot
new file mode 100644
index 0000000..3ed7de0
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot
@@ -0,0 +1,10 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (hold-preferred event 1)
+kp_pressed: usage_page 0x07 keycode 0xe1
+ht_binding_pressed: 1 new undecided hold_tap
+ht_decide: 1 decided tap (hold-preferred event 0)
+kp_pressed: usage_page 0x07 keycode 0x0d
+kp_released: usage_page 0x07 keycode 0x0d
+ht_binding_released: 1 cleaning up hold-tap
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/native_posix.keymap b/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/native_posix.keymap
new file mode 100644
index 0000000..c10c6d6
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/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,200)
+ ZMK_MOCK_PRESS(0,1,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(0,1,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/events.patterns b/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/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/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot
new file mode 100644
index 0000000..e0b57fd
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (hold-preferred event 1)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/native_posix.keymap b/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/native_posix.keymap
new file mode 100644
index 0000000..ce163f5
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/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,200)
+ ZMK_MOCK_PRESS(1,0,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/events.patterns b/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/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/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..e0b57fd
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (hold-preferred event 1)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/native_posix.keymap b/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/native_posix.keymap
new file mode 100644
index 0000000..7abda41
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/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,100)
+ ZMK_MOCK_PRESS(1,0,100)
+ ZMK_MOCK_RELEASE(1,0,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/events.patterns b/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/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/4c-dn-kcdn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot
new file mode 100644
index 0000000..e0b57fd
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (hold-preferred event 1)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/native_posix.keymap b/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/native_posix.keymap
new file mode 100644
index 0000000..ce030af
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/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_PRESS(1,0,10)
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ /* timer */
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/events.patterns b/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/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/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot
new file mode 100644
index 0000000..cac579d
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (hold-preferred event 1)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
+kp_released: usage_page 0x07 keycode 0x07
diff --git a/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/native_posix.keymap b/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/native_posix.keymap
new file mode 100644
index 0000000..5467660
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/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,100)
+ ZMK_MOCK_PRESS(1,0,100)
+ ZMK_MOCK_RELEASE(0,0,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(1,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
new file mode 100644
index 0000000..375ffd9
--- /dev/null
+++ b/app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi
@@ -0,0 +1,29 @@
+#include <dt-bindings/zmk/keys.h>
+#include <behaviors.dtsi>
+#include <dt-bindings/zmk/kscan-mock.h>
+
+
+
+/ {
+ behaviors {
+ ht_hold: behavior_hold_hold_tap {
+ compatible = "zmk,behavior-hold-tap";
+ label = "hold_hold_tap";
+ #binding-cells = <2>;
+ flavor = "hold-preferred";
+ tapping_term_ms = <300>;
+ bindings = <&kp>, <&kp>;
+ };
+ };
+
+ keymap {
+ compatible = "zmk,keymap";
+ label ="Default keymap";
+
+ default_layer {
+ bindings = <
+ &ht_hold LSFT F &ht_hold LCTL J
+ &kp D &kp RCTL>;
+ };
+ };
+};
diff --git a/app/tests/hold-tap/tap-preferred/1-dn-up/events.patterns b/app/tests/hold-tap/tap-preferred/1-dn-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/1-dn-up/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/1-dn-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/1-dn-up/keycode_events.snapshot
new file mode 100644
index 0000000..2a250fb
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/1-dn-up/keycode_events.snapshot
@@ -0,0 +1,5 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided tap (tap-preferred event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/tap-preferred/1-dn-up/native_posix.keymap b/app/tests/hold-tap/tap-preferred/1-dn-up/native_posix.keymap
new file mode 100644
index 0000000..10336ef
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/1-dn-up/native_posix.keymap
@@ -0,0 +1,11 @@
+#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)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/2-dn-timer-up/events.patterns b/app/tests/hold-tap/tap-preferred/2-dn-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/2-dn-timer-up/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/2-dn-timer-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/2-dn-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..4f1ee63
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/2-dn-timer-up/keycode_events.snapshot
@@ -0,0 +1,5 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (tap-preferred event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/tap-preferred/2-dn-timer-up/native_posix.keymap b/app/tests/hold-tap/tap-preferred/2-dn-timer-up/native_posix.keymap
new file mode 100644
index 0000000..aa93b86
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/2-dn-timer-up/native_posix.keymap
@@ -0,0 +1,11 @@
+#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,500)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/events.patterns b/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/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/3a-moddn-dn-modup-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot
new file mode 100644
index 0000000..87d1406
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0xe4
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided tap (tap-preferred event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_released: usage_page 0x07 keycode 0xe4
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/native_posix.keymap b/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/native_posix.keymap
new file mode 100644
index 0000000..6f08689
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/native_posix.keymap
@@ -0,0 +1,13 @@
+#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(1,1,10) /*ctrl*/
+ ZMK_MOCK_PRESS(0,0,100) /*mt f-shift */
+ ZMK_MOCK_RELEASE(1,1,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/events.patterns b/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/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/3b-moddn-dn-modup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..7455d2a
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0xe4
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (tap-preferred event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_released: usage_page 0x07 keycode 0xe4
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/native_posix.keymap b/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/native_posix.keymap
new file mode 100644
index 0000000..392d328
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/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(1,1,10) /*ctrl*/
+ ZMK_MOCK_PRESS(0,0,50) /*mt f-shift */
+ ZMK_MOCK_RELEASE(1,1,300)
+ /*timer*/
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/events.patterns b/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/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/3c-kcdn-dn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot
new file mode 100644
index 0000000..3d7eaf1
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0x07
+ht_binding_pressed: 0 new undecided hold_tap
+kp_released: usage_page 0x07 keycode 0x07
+ht_decide: 0 decided tap (tap-preferred event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/native_posix.keymap b/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/native_posix.keymap
new file mode 100644
index 0000000..77306cd
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/native_posix.keymap
@@ -0,0 +1,13 @@
+#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(1,0,10) /*d*/
+ ZMK_MOCK_PRESS(0,0,100) /*mt f-shift */
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/events.patterns b/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/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/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..059d99c
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+kp_pressed: usage_page 0x07 keycode 0x07
+ht_binding_pressed: 0 new undecided hold_tap
+kp_released: usage_page 0x07 keycode 0x07
+ht_decide: 0 decided hold (tap-preferred event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/native_posix.keymap b/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/native_posix.keymap
new file mode 100644
index 0000000..1441331
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/native_posix.keymap
@@ -0,0 +1,13 @@
+#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(1,0,10) /* d */
+ ZMK_MOCK_PRESS(0,0,100) /* mt f-shift */
+ ZMK_MOCK_RELEASE(1,0,400)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/events.patterns b/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/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/4a-dn-htdn-timer-htup-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot
new file mode 100644
index 0000000..a8cf490
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot
@@ -0,0 +1,10 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (tap-preferred event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+ht_binding_pressed: 1 new undecided hold_tap
+ht_decide: 1 decided tap (tap-preferred event 0)
+kp_pressed: usage_page 0x07 keycode 0x0d
+kp_released: usage_page 0x07 keycode 0x0d
+ht_binding_released: 1 cleaning up hold-tap
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/native_posix.keymap b/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/native_posix.keymap
new file mode 100644
index 0000000..c10c6d6
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/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,200)
+ ZMK_MOCK_PRESS(0,1,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(0,1,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/events.patterns b/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/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/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot
new file mode 100644
index 0000000..92a3569
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (tap-preferred event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/native_posix.keymap b/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/native_posix.keymap
new file mode 100644
index 0000000..ce163f5
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/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,200)
+ ZMK_MOCK_PRESS(1,0,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/events.patterns b/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/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/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot
new file mode 100644
index 0000000..92a3569
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided hold (tap-preferred event 3)
+kp_pressed: usage_page 0x07 keycode 0xe1
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0xe1
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/native_posix.keymap b/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/native_posix.keymap
new file mode 100644
index 0000000..7abda41
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/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,100)
+ ZMK_MOCK_PRESS(1,0,100)
+ ZMK_MOCK_RELEASE(1,0,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/events.patterns b/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/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/4c-dn-kcdn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot
new file mode 100644
index 0000000..bc8aa8e
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided tap (tap-preferred event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
diff --git a/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/native_posix.keymap b/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/native_posix.keymap
new file mode 100644
index 0000000..ce030af
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/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_PRESS(1,0,10)
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ /* timer */
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/events.patterns b/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/events.patterns
new file mode 100644
index 0000000..fdf2b15
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/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/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot
new file mode 100644
index 0000000..b106f13
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot
@@ -0,0 +1,7 @@
+ht_binding_pressed: 0 new undecided hold_tap
+ht_decide: 0 decided tap (tap-preferred event 0)
+kp_pressed: usage_page 0x07 keycode 0x09
+kp_pressed: usage_page 0x07 keycode 0x07
+kp_released: usage_page 0x07 keycode 0x09
+ht_binding_released: 0 cleaning up hold-tap
+kp_released: usage_page 0x07 keycode 0x07
diff --git a/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/native_posix.keymap b/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/native_posix.keymap
new file mode 100644
index 0000000..5467660
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/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,100)
+ ZMK_MOCK_PRESS(1,0,100)
+ ZMK_MOCK_RELEASE(0,0,200)
+ /* timer fires */
+ ZMK_MOCK_RELEASE(1,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
new file mode 100644
index 0000000..e514fa6
--- /dev/null
+++ b/app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi
@@ -0,0 +1,27 @@
+#include <dt-bindings/zmk/keys.h>
+#include <behaviors.dtsi>
+#include <dt-bindings/zmk/kscan-mock.h>
+
+/ {
+ behaviors {
+ tp: behavior_tap_preferred {
+ compatible = "zmk,behavior-hold-tap";
+ label = "MOD_TAP";
+ #binding-cells = <2>;
+ flavor = "tap-preferred";
+ tapping_term_ms = <300>;
+ bindings = <&kp>, <&kp>;
+ };
+ };
+
+ keymap {
+ compatible = "zmk,keymap";
+ label ="Default keymap";
+
+ default_layer {
+ bindings = <
+ &tp LSFT F &tp LCTL J
+ &kp D &kp RCTL>;
+ };
+ };
+};
diff --git a/app/tests/hold-tap/zmk-modtap-proposal.odg b/app/tests/hold-tap/zmk-modtap-proposal.odg
new file mode 100644
index 0000000..82f8436
--- /dev/null
+++ b/app/tests/hold-tap/zmk-modtap-proposal.odg
Binary files differ
diff --git a/app/tests/hold-tap/zmk-modtap-proposal.pdf b/app/tests/hold-tap/zmk-modtap-proposal.pdf
new file mode 100644
index 0000000..33048ca
--- /dev/null
+++ b/app/tests/hold-tap/zmk-modtap-proposal.pdf
Binary files differ