From c33931c72c179d19028b2d70ec043cbc3d786137 Mon Sep 17 00:00:00 2001 From: Okke Formsma Date: Tue, 1 Sep 2020 14:37:37 +0200 Subject: [PATCH] Initial implementation of hold-tap --- app/CMakeLists.txt | 4 +- app/dts/behaviors.dtsi | 2 + app/dts/behaviors/homerow_tap.dtsi | 12 + app/dts/behaviors/layer_tap.dtsi | 12 + app/dts/behaviors/mod_tap.dtsi | 5 +- .../behaviors/zmk,behavior-mod-tap.yaml | 8 - .../behaviors/zmk,behavior-tap-hold.yaml | 23 + app/include/zmk/event-manager.h | 4 + app/run-test-debug.sh | 44 ++ app/src/behaviors/behavior_hold_tap.c | 491 ++++++++++++++++++ app/src/behaviors/behavior_mod_tap.c | 252 --------- app/src/event_manager.c | 6 + app/tests/hold-tap/balanced/1/events.patterns | 4 + .../balanced/1/keycode_events.snapshot | 5 + .../hold-tap/balanced/1/native_posix.keymap | 11 + app/tests/hold-tap/balanced/2/events.patterns | 4 + .../balanced/2/keycode_events.snapshot | 5 + .../hold-tap/balanced/2/native_posix.keymap | 11 + .../hold-tap/balanced/3a/events.patterns | 4 + .../balanced/3a/keycode_events.snapshot | 7 + .../hold-tap/balanced/3a/native_posix.keymap | 13 + .../hold-tap/balanced/3b/events.patterns | 4 + .../balanced/3b/keycode_events.snapshot | 7 + .../hold-tap/balanced/3b/native_posix.keymap | 14 + .../hold-tap/balanced/3c/events.patterns | 4 + .../balanced/3c/keycode_events.snapshot | 7 + .../hold-tap/balanced/3c/native_posix.keymap | 13 + .../hold-tap/balanced/3d/events.patterns | 4 + .../balanced/3d/keycode_events.snapshot | 7 + .../hold-tap/balanced/3d/native_posix.keymap | 13 + .../balanced/4a-nested/events.patterns | 4 + .../4a-nested/keycode_events.snapshot | 10 + .../balanced/4a-nested/native_posix.keymap | 14 + .../hold-tap/balanced/4a/events.patterns | 4 + .../balanced/4a/keycode_events.snapshot | 7 + .../hold-tap/balanced/4a/native_posix.keymap | 14 + .../hold-tap/balanced/4b/events.patterns | 4 + .../balanced/4b/keycode_events.snapshot | 7 + .../hold-tap/balanced/4b/native_posix.keymap | 14 + .../hold-tap/balanced/4c/events.patterns | 4 + .../balanced/4c/keycode_events.snapshot | 7 + .../hold-tap/balanced/4c/native_posix.keymap | 14 + .../hold-tap/balanced/4d/events.patterns | 4 + .../balanced/4d/keycode_events.snapshot | 7 + .../hold-tap/balanced/4d/native_posix.keymap | 14 + .../hold-tap/balanced/behavior_keymap.dtsi | 27 + .../hold-tap/hold-preferred/1/events.patterns | 4 + .../hold-preferred/1/keycode_events.snapshot | 5 + .../hold-preferred/1/native_posix.keymap | 11 + .../hold-tap/hold-preferred/2/events.patterns | 4 + .../hold-preferred/2/keycode_events.snapshot | 5 + .../hold-preferred/2/native_posix.keymap | 11 + .../hold-preferred/3a/events.patterns | 4 + .../hold-preferred/3a/keycode_events.snapshot | 7 + .../hold-preferred/3a/native_posix.keymap | 13 + .../hold-preferred/3b/events.patterns | 4 + .../hold-preferred/3b/keycode_events.snapshot | 7 + .../hold-preferred/3b/native_posix.keymap | 14 + .../hold-preferred/3c/events.patterns | 4 + .../hold-preferred/3c/keycode_events.snapshot | 7 + .../hold-preferred/3c/native_posix.keymap | 13 + .../hold-preferred/3d/events.patterns | 4 + .../hold-preferred/3d/keycode_events.snapshot | 7 + .../hold-preferred/3d/native_posix.keymap | 13 + .../hold-preferred/4a-nested/events.patterns | 4 + .../4a-nested/keycode_events.snapshot | 10 + .../4a-nested/native_posix.keymap | 14 + .../hold-preferred/4a/events.patterns | 4 + .../hold-preferred/4a/keycode_events.snapshot | 7 + .../hold-preferred/4a/native_posix.keymap | 14 + .../hold-preferred/4b/events.patterns | 4 + .../hold-preferred/4b/keycode_events.snapshot | 7 + .../hold-preferred/4b/native_posix.keymap | 14 + .../hold-preferred/4c/events.patterns | 4 + .../hold-preferred/4c/keycode_events.snapshot | 7 + .../hold-preferred/4c/native_posix.keymap | 14 + .../hold-preferred/4d/events.patterns | 4 + .../hold-preferred/4d/keycode_events.snapshot | 7 + .../hold-preferred/4d/native_posix.keymap | 14 + .../hold-preferred/behavior_keymap.dtsi | 29 ++ .../hold-tap/tap-preferred/1/events.patterns | 4 + .../tap-preferred/1/keycode_events.snapshot | 5 + .../tap-preferred/1/native_posix.keymap | 11 + .../hold-tap/tap-preferred/2/events.patterns | 4 + .../tap-preferred/2/keycode_events.snapshot | 5 + .../tap-preferred/2/native_posix.keymap | 11 + .../hold-tap/tap-preferred/3a/events.patterns | 4 + .../tap-preferred/3a/keycode_events.snapshot | 7 + .../tap-preferred/3a/native_posix.keymap | 13 + .../hold-tap/tap-preferred/3b/events.patterns | 4 + .../tap-preferred/3b/keycode_events.snapshot | 7 + .../tap-preferred/3b/native_posix.keymap | 14 + .../hold-tap/tap-preferred/3c/events.patterns | 4 + .../tap-preferred/3c/keycode_events.snapshot | 7 + .../tap-preferred/3c/native_posix.keymap | 13 + .../hold-tap/tap-preferred/3d/events.patterns | 4 + .../tap-preferred/3d/keycode_events.snapshot | 7 + .../tap-preferred/3d/native_posix.keymap | 13 + .../tap-preferred/4a-nested/events.patterns | 4 + .../4a-nested/keycode_events.snapshot | 10 + .../4a-nested/native_posix.keymap | 14 + .../hold-tap/tap-preferred/4a/events.patterns | 4 + .../tap-preferred/4a/keycode_events.snapshot | 7 + .../tap-preferred/4a/native_posix.keymap | 14 + .../hold-tap/tap-preferred/4b/events.patterns | 4 + .../tap-preferred/4b/keycode_events.snapshot | 7 + .../tap-preferred/4b/native_posix.keymap | 14 + .../hold-tap/tap-preferred/4c/events.patterns | 4 + .../tap-preferred/4c/keycode_events.snapshot | 7 + .../tap-preferred/4c/native_posix.keymap | 14 + .../hold-tap/tap-preferred/4d/events.patterns | 4 + .../tap-preferred/4d/keycode_events.snapshot | 7 + .../tap-preferred/4d/native_posix.keymap | 14 + .../tap-preferred/behavior_keymap.dtsi | 27 + app/tests/hold-tap/zmk-modtap-proposal.odg | Bin 0 -> 23148 bytes app/tests/hold-tap/zmk-modtap-proposal.pdf | Bin 0 -> 25493 bytes .../tap-hold/balanced/behavior_keymap.dtsi | 27 + .../hold-preferred/behavior_keymap.dtsi | 29 ++ .../tap-preferred/behavior_keymap.dtsi | 27 + 119 files changed, 1561 insertions(+), 263 deletions(-) create mode 100644 app/dts/behaviors/homerow_tap.dtsi create mode 100644 app/dts/behaviors/layer_tap.dtsi delete mode 100644 app/dts/bindings/behaviors/zmk,behavior-mod-tap.yaml create mode 100644 app/dts/bindings/behaviors/zmk,behavior-tap-hold.yaml create mode 100755 app/run-test-debug.sh create mode 100644 app/src/behaviors/behavior_hold_tap.c delete mode 100644 app/src/behaviors/behavior_mod_tap.c create mode 100644 app/tests/hold-tap/balanced/1/events.patterns create mode 100644 app/tests/hold-tap/balanced/1/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/1/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/2/events.patterns create mode 100644 app/tests/hold-tap/balanced/2/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/2/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/3a/events.patterns create mode 100644 app/tests/hold-tap/balanced/3a/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/3a/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/3b/events.patterns create mode 100644 app/tests/hold-tap/balanced/3b/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/3b/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/3c/events.patterns create mode 100644 app/tests/hold-tap/balanced/3c/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/3c/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/3d/events.patterns create mode 100644 app/tests/hold-tap/balanced/3d/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/3d/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/4a-nested/events.patterns create mode 100644 app/tests/hold-tap/balanced/4a-nested/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/4a-nested/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/4a/events.patterns create mode 100644 app/tests/hold-tap/balanced/4a/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/4a/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/4b/events.patterns create mode 100644 app/tests/hold-tap/balanced/4b/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/4b/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/4c/events.patterns create mode 100644 app/tests/hold-tap/balanced/4c/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/4c/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/4d/events.patterns create mode 100644 app/tests/hold-tap/balanced/4d/keycode_events.snapshot create mode 100644 app/tests/hold-tap/balanced/4d/native_posix.keymap create mode 100644 app/tests/hold-tap/balanced/behavior_keymap.dtsi create mode 100644 app/tests/hold-tap/hold-preferred/1/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/1/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/1/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/2/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/2/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/2/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/3a/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/3a/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/3a/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/3b/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/3b/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/3b/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/3c/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/3c/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/3c/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/3d/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/3d/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/3d/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/4a-nested/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/4a-nested/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/4a-nested/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/4a/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/4a/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/4a/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/4b/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/4b/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/4b/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/4c/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/4c/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/4c/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/4d/events.patterns create mode 100644 app/tests/hold-tap/hold-preferred/4d/keycode_events.snapshot create mode 100644 app/tests/hold-tap/hold-preferred/4d/native_posix.keymap create mode 100644 app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi create mode 100644 app/tests/hold-tap/tap-preferred/1/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/1/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/1/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/2/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/2/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/2/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/3a/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/3a/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/3a/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/3b/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/3b/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/3b/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/3c/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/3c/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/3c/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/3d/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/3d/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/3d/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/4a-nested/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/4a-nested/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/4a-nested/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/4a/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/4a/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/4a/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/4b/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/4b/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/4b/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/4c/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/4c/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/4c/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/4d/events.patterns create mode 100644 app/tests/hold-tap/tap-preferred/4d/keycode_events.snapshot create mode 100644 app/tests/hold-tap/tap-preferred/4d/native_posix.keymap create mode 100644 app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi create mode 100644 app/tests/hold-tap/zmk-modtap-proposal.odg create mode 100644 app/tests/hold-tap/zmk-modtap-proposal.pdf create mode 100644 app/tests/tap-hold/balanced/behavior_keymap.dtsi create mode 100644 app/tests/tap-hold/hold-preferred/behavior_keymap.dtsi create mode 100644 app/tests/tap-hold/tap-preferred/behavior_keymap.dtsi diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index db56c538..2e24fdc2 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) diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index 4cfb7a0f..fdcf426b 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/app/dts/behaviors/homerow_tap.dtsi b/app/dts/behaviors/homerow_tap.dtsi new file mode 100644 index 00000000..21c1531f --- /dev/null +++ b/app/dts/behaviors/homerow_tap.dtsi @@ -0,0 +1,12 @@ +/ { + behaviors { + ht: behavior_homerow_mod { + compatible = "zmk,behavior-hold-tap"; + label = "homerow_mod"; + #binding-cells = <2>; + flavor = "balanced"; + tapping_term_ms = <200>; + bindings = <&kp>, <&kp>; + }; + }; +}; diff --git a/app/dts/behaviors/layer_tap.dtsi b/app/dts/behaviors/layer_tap.dtsi new file mode 100644 index 00000000..af7319b3 --- /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 8e3b4e91..4ce732b4 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-mod-tap.yaml b/app/dts/bindings/behaviors/zmk,behavior-mod-tap.yaml deleted file mode 100644 index 7911082f..00000000 --- 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/dts/bindings/behaviors/zmk,behavior-tap-hold.yaml b/app/dts/bindings/behaviors/zmk,behavior-tap-hold.yaml new file mode 100644 index 00000000..a20578fa --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-tap-hold.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/include/zmk/event-manager.h b/app/include/zmk/event-manager.h index 43d3f299..07c0aa98 100644 --- a/app/include/zmk/event-manager.h +++ b/app/include/zmk/event-manager.h @@ -78,6 +78,10 @@ struct zmk_event_subscription { #define ZMK_EVENT_RELEASE(ev) \ zmk_event_manager_release((struct zmk_event_header *)ev); +#define ZMK_EVENT_RELEASE_AGAIN(ev) \ + zmk_event_manager_release_again((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_release(struct zmk_event_header *event); +int zmk_event_manager_release_again(struct zmk_event_header *event); diff --git a/app/run-test-debug.sh b/app/run-test-debug.sh new file mode 100755 index 00000000..f6696a1d --- /dev/null +++ b/app/run-test-debug.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# +# Copyright (c) 2020 Peter Johanson; Cody McGinnis; Okke Formsma +# +# SPDX-License-Identifier: MIT +# +set -e +set -x + +if [ -z "$1" ]; then + echo "Usage: ./run-test.sh " + exit 1 +elif [ "$1" = "all" ]; then + echo "" > ./build/tests/pass-fail.log + find tests -name native_posix.keymap -exec dirname \{\} \; | xargs -l -P 4 ./run-test.sh + err=$? + sort -k2 ./build/tests/pass-fail.log + exit $err +fi + +testcase="$1" +echo "Running $testcase:" + +west build -d build/$testcase -b native_posix --pristine -- -DZMK_CONFIG=$testcase +if [ $? -gt 0 ]; then + echo "FAIL: $testcase did not build" +else + ./build/$testcase/zephyr/zmk.exe | sed -e "s/.*> //" | tee build/$testcase/keycode_events_full.log | sed -n -f $testcase/events.patterns > build/$testcase/keycode_events.log + cat build/$testcase/keycode_events_full.log + cat build/$testcase/keycode_events.log + diff -au $testcase/keycode_events.snapshot build/$testcase/keycode_events.log + if [ $? -gt 0 ]; then + if [ -f $testcase/pending ]; then + echo "PEND: $testcase" + exit 0 + else + echo "FAIL: $testcase" + exit 1 + fi + else + echo "PASS: $testcase" + exit 0 + fi +fi \ No newline at end of file diff --git a/app/src/behaviors/behavior_hold_tap.c b/app/src/behaviors/behavior_hold_tap.c new file mode 100644 index 00000000..08fa1397 --- /dev/null +++ b/app/src/behaviors/behavior_hold_tap.c @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2020 Cody McGinnis, Okke Formsma + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_hold_tap + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_NODE_EXISTS(DT_DRV_INST(0)) + +/************************************************************ DATA SETUP */ +#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 + +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; + char *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] = {}; + +/************************************************************ CAPTURED POSITION HELPER FUNCTIONS */ +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; +} + +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_RELEASE_AGAIN(captured_event); + } +} + + +/************************************************************ ACTIVE TAP HOLD HELPER FUNCTIONS */ + +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: + hold_tap->is_hold = 1; + 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_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: + hold_tap->is_hold = 1; + 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_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; + } + + char *flavor = hold_tap->config->flavor; + if (strcmp(flavor, "balanced") == 0) { + decide_balanced(hold_tap, event); + } else if (strcmp(flavor, "tap-preferred") == 0) { + decide_tap_preferred(hold_tap, event); + } else if (strcmp(flavor, "hold-preferred") == 0) { + decide_hold_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, 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(); +} + +/************************************************************ hold_tap_binding and key handlers */ +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); + +/************************************************************ TIMER FUNCTIONS */ +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; + +/************************************************************ NODE CONFIG */ +#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_INST_PROP(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 5a2f60e2..00000000 --- a/app/src/behaviors/behavior_mod_tap.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (c) 2020 Peter Johanson - * - * SPDX-License-Identifier: MIT - */ - -#define DT_DRV_COMPAT zmk_behavior_mod_tap - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -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 c405176f..2f423fc5 100644 --- a/app/src/event_manager.c +++ b/app/src/event_manager.c @@ -74,4 +74,10 @@ int zmk_event_manager_raise_after(struct zmk_event_header *event, const struct z int zmk_event_manager_release(struct zmk_event_header *event) { return zmk_event_manager_handle_from(event, event->last_listener_index + 1); +} + + +int zmk_event_manager_release_again(struct zmk_event_header *event) +{ + return zmk_event_manager_handle_from(event, event->last_listener_index); } \ No newline at end of file diff --git a/app/tests/hold-tap/balanced/1/events.patterns b/app/tests/hold-tap/balanced/1/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/1/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/keycode_events.snapshot b/app/tests/hold-tap/balanced/1/keycode_events.snapshot new file mode 100644 index 00000000..5f6a2668 --- /dev/null +++ b/app/tests/hold-tap/balanced/1/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/native_posix.keymap b/app/tests/hold-tap/balanced/1/native_posix.keymap new file mode 100644 index 00000000..10336ef3 --- /dev/null +++ b/app/tests/hold-tap/balanced/1/native_posix.keymap @@ -0,0 +1,11 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/balanced/2/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/2/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/keycode_events.snapshot b/app/tests/hold-tap/balanced/2/keycode_events.snapshot new file mode 100644 index 00000000..ddda1ae4 --- /dev/null +++ b/app/tests/hold-tap/balanced/2/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/native_posix.keymap b/app/tests/hold-tap/balanced/2/native_posix.keymap new file mode 100644 index 00000000..aa93b862 --- /dev/null +++ b/app/tests/hold-tap/balanced/2/native_posix.keymap @@ -0,0 +1,11 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/balanced/3a/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/3a/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/keycode_events.snapshot b/app/tests/hold-tap/balanced/3a/keycode_events.snapshot new file mode 100644 index 00000000..a4351030 --- /dev/null +++ b/app/tests/hold-tap/balanced/3a/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/native_posix.keymap b/app/tests/hold-tap/balanced/3a/native_posix.keymap new file mode 100644 index 00000000..6f08689b --- /dev/null +++ b/app/tests/hold-tap/balanced/3a/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/balanced/3b/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/3b/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/keycode_events.snapshot b/app/tests/hold-tap/balanced/3b/keycode_events.snapshot new file mode 100644 index 00000000..c0da94fd --- /dev/null +++ b/app/tests/hold-tap/balanced/3b/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/native_posix.keymap b/app/tests/hold-tap/balanced/3b/native_posix.keymap new file mode 100644 index 00000000..392d328b --- /dev/null +++ b/app/tests/hold-tap/balanced/3b/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/balanced/3c/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/3c/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/keycode_events.snapshot b/app/tests/hold-tap/balanced/3c/keycode_events.snapshot new file mode 100644 index 00000000..ce6e7b7c --- /dev/null +++ b/app/tests/hold-tap/balanced/3c/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/native_posix.keymap b/app/tests/hold-tap/balanced/3c/native_posix.keymap new file mode 100644 index 00000000..77306cd0 --- /dev/null +++ b/app/tests/hold-tap/balanced/3c/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/balanced/3d/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/3d/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/keycode_events.snapshot b/app/tests/hold-tap/balanced/3d/keycode_events.snapshot new file mode 100644 index 00000000..1ec384a6 --- /dev/null +++ b/app/tests/hold-tap/balanced/3d/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/native_posix.keymap b/app/tests/hold-tap/balanced/3d/native_posix.keymap new file mode 100644 index 00000000..14413311 --- /dev/null +++ b/app/tests/hold-tap/balanced/3d/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#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-nested/events.patterns b/app/tests/hold-tap/balanced/4a-nested/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/4a-nested/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-nested/keycode_events.snapshot b/app/tests/hold-tap/balanced/4a-nested/keycode_events.snapshot new file mode 100644 index 00000000..8a1980b8 --- /dev/null +++ b/app/tests/hold-tap/balanced/4a-nested/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-nested/native_posix.keymap b/app/tests/hold-tap/balanced/4a-nested/native_posix.keymap new file mode 100644 index 00000000..c10c6d64 --- /dev/null +++ b/app/tests/hold-tap/balanced/4a-nested/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/balanced/4a/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/4a/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/keycode_events.snapshot b/app/tests/hold-tap/balanced/4a/keycode_events.snapshot new file mode 100644 index 00000000..b89b21dc --- /dev/null +++ b/app/tests/hold-tap/balanced/4a/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/native_posix.keymap b/app/tests/hold-tap/balanced/4a/native_posix.keymap new file mode 100644 index 00000000..ce163f53 --- /dev/null +++ b/app/tests/hold-tap/balanced/4a/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/balanced/4b/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/4b/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/keycode_events.snapshot b/app/tests/hold-tap/balanced/4b/keycode_events.snapshot new file mode 100644 index 00000000..798e2eed --- /dev/null +++ b/app/tests/hold-tap/balanced/4b/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/native_posix.keymap b/app/tests/hold-tap/balanced/4b/native_posix.keymap new file mode 100644 index 00000000..7abda41a --- /dev/null +++ b/app/tests/hold-tap/balanced/4b/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/balanced/4c/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/4c/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/keycode_events.snapshot b/app/tests/hold-tap/balanced/4c/keycode_events.snapshot new file mode 100644 index 00000000..798e2eed --- /dev/null +++ b/app/tests/hold-tap/balanced/4c/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/native_posix.keymap b/app/tests/hold-tap/balanced/4c/native_posix.keymap new file mode 100644 index 00000000..ce030af3 --- /dev/null +++ b/app/tests/hold-tap/balanced/4c/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/balanced/4d/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/balanced/4d/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/keycode_events.snapshot b/app/tests/hold-tap/balanced/4d/keycode_events.snapshot new file mode 100644 index 00000000..5c9f4e33 --- /dev/null +++ b/app/tests/hold-tap/balanced/4d/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/native_posix.keymap b/app/tests/hold-tap/balanced/4d/native_posix.keymap new file mode 100644 index 00000000..54676600 --- /dev/null +++ b/app/tests/hold-tap/balanced/4d/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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 00000000..df56fb5b --- /dev/null +++ b/app/tests/hold-tap/balanced/behavior_keymap.dtsi @@ -0,0 +1,27 @@ +#include +#include +#include + +/ { + 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/events.patterns b/app/tests/hold-tap/hold-preferred/1/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/1/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/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/1/keycode_events.snapshot new file mode 100644 index 00000000..cf787d8d --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/1/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/native_posix.keymap b/app/tests/hold-tap/hold-preferred/1/native_posix.keymap new file mode 100644 index 00000000..10336ef3 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/1/native_posix.keymap @@ -0,0 +1,11 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/hold-preferred/2/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/2/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/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/2/keycode_events.snapshot new file mode 100644 index 00000000..03329d53 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/2/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/native_posix.keymap b/app/tests/hold-tap/hold-preferred/2/native_posix.keymap new file mode 100644 index 00000000..aa93b862 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/2/native_posix.keymap @@ -0,0 +1,11 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/hold-preferred/3a/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3a/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/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3a/keycode_events.snapshot new file mode 100644 index 00000000..adf4fe23 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3a/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/native_posix.keymap b/app/tests/hold-tap/hold-preferred/3a/native_posix.keymap new file mode 100644 index 00000000..6f08689b --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3a/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/hold-preferred/3b/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3b/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/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3b/keycode_events.snapshot new file mode 100644 index 00000000..69b64a96 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3b/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/native_posix.keymap b/app/tests/hold-tap/hold-preferred/3b/native_posix.keymap new file mode 100644 index 00000000..392d328b --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3b/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/hold-preferred/3c/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3c/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/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3c/keycode_events.snapshot new file mode 100644 index 00000000..b06a1d76 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3c/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/native_posix.keymap b/app/tests/hold-tap/hold-preferred/3c/native_posix.keymap new file mode 100644 index 00000000..77306cd0 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3c/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/hold-preferred/3d/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3d/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/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3d/keycode_events.snapshot new file mode 100644 index 00000000..bf31955b --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3d/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/native_posix.keymap b/app/tests/hold-tap/hold-preferred/3d/native_posix.keymap new file mode 100644 index 00000000..14413311 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/3d/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#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-nested/events.patterns b/app/tests/hold-tap/hold-preferred/4a-nested/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4a-nested/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-nested/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4a-nested/keycode_events.snapshot new file mode 100644 index 00000000..3ed7de0d --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4a-nested/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-nested/native_posix.keymap b/app/tests/hold-tap/hold-preferred/4a-nested/native_posix.keymap new file mode 100644 index 00000000..c10c6d64 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4a-nested/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/hold-preferred/4a/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4a/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/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4a/keycode_events.snapshot new file mode 100644 index 00000000..e0b57fd4 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4a/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/native_posix.keymap b/app/tests/hold-tap/hold-preferred/4a/native_posix.keymap new file mode 100644 index 00000000..ce163f53 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4a/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/hold-preferred/4b/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4b/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/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4b/keycode_events.snapshot new file mode 100644 index 00000000..e0b57fd4 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4b/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/native_posix.keymap b/app/tests/hold-tap/hold-preferred/4b/native_posix.keymap new file mode 100644 index 00000000..7abda41a --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4b/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/hold-preferred/4c/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4c/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/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4c/keycode_events.snapshot new file mode 100644 index 00000000..e0b57fd4 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4c/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/native_posix.keymap b/app/tests/hold-tap/hold-preferred/4c/native_posix.keymap new file mode 100644 index 00000000..ce030af3 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4c/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/hold-preferred/4d/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4d/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/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4d/keycode_events.snapshot new file mode 100644 index 00000000..cac579d2 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4d/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/native_posix.keymap b/app/tests/hold-tap/hold-preferred/4d/native_posix.keymap new file mode 100644 index 00000000..54676600 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/4d/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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 00000000..375ffd93 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi @@ -0,0 +1,29 @@ +#include +#include +#include + + + +/ { + 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/events.patterns b/app/tests/hold-tap/tap-preferred/1/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/1/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/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/1/keycode_events.snapshot new file mode 100644 index 00000000..2a250fb8 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/1/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/native_posix.keymap b/app/tests/hold-tap/tap-preferred/1/native_posix.keymap new file mode 100644 index 00000000..10336ef3 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/1/native_posix.keymap @@ -0,0 +1,11 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/tap-preferred/2/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/2/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/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/2/keycode_events.snapshot new file mode 100644 index 00000000..4f1ee635 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/2/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/native_posix.keymap b/app/tests/hold-tap/tap-preferred/2/native_posix.keymap new file mode 100644 index 00000000..aa93b862 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/2/native_posix.keymap @@ -0,0 +1,11 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/tap-preferred/3a/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3a/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/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3a/keycode_events.snapshot new file mode 100644 index 00000000..87d1406a --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3a/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/native_posix.keymap b/app/tests/hold-tap/tap-preferred/3a/native_posix.keymap new file mode 100644 index 00000000..6f08689b --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3a/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/tap-preferred/3b/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3b/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/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3b/keycode_events.snapshot new file mode 100644 index 00000000..7455d2a3 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3b/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/native_posix.keymap b/app/tests/hold-tap/tap-preferred/3b/native_posix.keymap new file mode 100644 index 00000000..392d328b --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3b/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/tap-preferred/3c/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3c/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/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3c/keycode_events.snapshot new file mode 100644 index 00000000..3d7eaf1a --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3c/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/native_posix.keymap b/app/tests/hold-tap/tap-preferred/3c/native_posix.keymap new file mode 100644 index 00000000..77306cd0 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3c/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/tap-preferred/3d/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3d/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/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3d/keycode_events.snapshot new file mode 100644 index 00000000..059d99c5 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3d/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/native_posix.keymap b/app/tests/hold-tap/tap-preferred/3d/native_posix.keymap new file mode 100644 index 00000000..14413311 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/3d/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#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-nested/events.patterns b/app/tests/hold-tap/tap-preferred/4a-nested/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4a-nested/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-nested/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4a-nested/keycode_events.snapshot new file mode 100644 index 00000000..a8cf4907 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4a-nested/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-nested/native_posix.keymap b/app/tests/hold-tap/tap-preferred/4a-nested/native_posix.keymap new file mode 100644 index 00000000..c10c6d64 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4a-nested/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/tap-preferred/4a/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4a/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/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4a/keycode_events.snapshot new file mode 100644 index 00000000..92a3569a --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4a/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/native_posix.keymap b/app/tests/hold-tap/tap-preferred/4a/native_posix.keymap new file mode 100644 index 00000000..ce163f53 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4a/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/tap-preferred/4b/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4b/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/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4b/keycode_events.snapshot new file mode 100644 index 00000000..92a3569a --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4b/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/native_posix.keymap b/app/tests/hold-tap/tap-preferred/4b/native_posix.keymap new file mode 100644 index 00000000..7abda41a --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4b/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/tap-preferred/4c/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4c/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/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4c/keycode_events.snapshot new file mode 100644 index 00000000..bc8aa8e3 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4c/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/native_posix.keymap b/app/tests/hold-tap/tap-preferred/4c/native_posix.keymap new file mode 100644 index 00000000..ce030af3 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4c/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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/events.patterns b/app/tests/hold-tap/tap-preferred/4d/events.patterns new file mode 100644 index 00000000..fdf2b15c --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4d/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/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4d/keycode_events.snapshot new file mode 100644 index 00000000..b106f136 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4d/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/native_posix.keymap b/app/tests/hold-tap/tap-preferred/4d/native_posix.keymap new file mode 100644 index 00000000..54676600 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/4d/native_posix.keymap @@ -0,0 +1,14 @@ +#include +#include +#include +#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 00000000..e514fa65 --- /dev/null +++ b/app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi @@ -0,0 +1,27 @@ +#include +#include +#include + +/ { + 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 0000000000000000000000000000000000000000..82f84369d4cba1d75c4dc16e3064fbdab1ed225b GIT binary patch literal 23148 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 JA(NlI{{!HqraS-u literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..33048ca1d64eda9816137c4a01f255ab64badbdd GIT binary patch literal 25493 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 X*2Dq(%k)r~h@c1=8=IUeg6w|)Oj +#include +#include + +/ { + 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/tap-hold/hold-preferred/behavior_keymap.dtsi b/app/tests/tap-hold/hold-preferred/behavior_keymap.dtsi new file mode 100644 index 00000000..375ffd93 --- /dev/null +++ b/app/tests/tap-hold/hold-preferred/behavior_keymap.dtsi @@ -0,0 +1,29 @@ +#include +#include +#include + + + +/ { + 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/tap-hold/tap-preferred/behavior_keymap.dtsi b/app/tests/tap-hold/tap-preferred/behavior_keymap.dtsi new file mode 100644 index 00000000..e514fa65 --- /dev/null +++ b/app/tests/tap-hold/tap-preferred/behavior_keymap.dtsi @@ -0,0 +1,27 @@ +#include +#include +#include + +/ { + 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>; + }; + }; +};