From 745338dc10b1a4383f4c3205dfdf39d8a05a347c Mon Sep 17 00:00:00 2001 From: dnaq Date: Fri, 24 Sep 2021 13:24:56 +0200 Subject: [PATCH 01/10] feat(shield): Add Bat43 shield See https://kbd.dailycraft.jp/bat43/ for details. --- app/boards/shields/bat43/Kconfig.defconfig | 9 ++++ app/boards/shields/bat43/Kconfig.shield | 5 ++ app/boards/shields/bat43/bat43.keymap | 50 ++++++++++++++++++++ app/boards/shields/bat43/bat43.overlay | 54 ++++++++++++++++++++++ app/boards/shields/bat43/bat43.zmk.yml | 8 ++++ 5 files changed, 126 insertions(+) create mode 100644 app/boards/shields/bat43/Kconfig.defconfig create mode 100644 app/boards/shields/bat43/Kconfig.shield create mode 100644 app/boards/shields/bat43/bat43.keymap create mode 100644 app/boards/shields/bat43/bat43.overlay create mode 100644 app/boards/shields/bat43/bat43.zmk.yml diff --git a/app/boards/shields/bat43/Kconfig.defconfig b/app/boards/shields/bat43/Kconfig.defconfig new file mode 100644 index 00000000..43de1fa8 --- /dev/null +++ b/app/boards/shields/bat43/Kconfig.defconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +if SHIELD_BAT43 + +config ZMK_KEYBOARD_NAME + default "Bat43" + +endif diff --git a/app/boards/shields/bat43/Kconfig.shield b/app/boards/shields/bat43/Kconfig.shield new file mode 100644 index 00000000..10ee88ce --- /dev/null +++ b/app/boards/shields/bat43/Kconfig.shield @@ -0,0 +1,5 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config SHIELD_BAT43 + def_bool $(shields_list_contains,bat43) diff --git a/app/boards/shields/bat43/bat43.keymap b/app/boards/shields/bat43/bat43.keymap new file mode 100644 index 00000000..34670d37 --- /dev/null +++ b/app/boards/shields/bat43/bat43.keymap @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#define LOWER 1 +#define RAISE 2 + +#define L_SPC < LOWER SPACE +#define R_RET < RAISE RET + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < +&kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp MINUS +&kp TAB &kp A &kp S &kp D &kp F &kp G &kp BSPC &kp H &kp J &kp K &kp L &kp SEMI &kp RSHFT +&kp LCTL &kp Z &kp X &kp C &kp V &kp B &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp RCTL + &kp LGUI &kp LANG2 L_SPC R_RET &kp LANG1 &kp RALT + &bt BT_CLR &out OUT_TOG &bt BT_SEL 0 &bt BT_SEL 1 &bt BT_SEL 2 + >; + }; + lower_layer { + bindings = < +&trans &none &none &none &none &none &none &kp EQUAL &kp PLUS &kp STAR &kp PRCNT &trans +&trans &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &trans &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &trans +&trans &none &none &none &none &none &none &none &trans &trans &trans &trans + &trans &trans &trans &trans &trans &trans + &trans &trans &trans &trans &trans + >; + }; + raise_layer { + bindings = < +&trans &kp BSLH &kp EXCL &kp AMPS &kp PIPE &none &none &kp EQUAL &kp PLUS &kp STAR &kp PRCNT &trans +&trans &kp HASH &kp GRAVE &kp DQT &kp SQT &kp TILDE &trans &kp LEFT &kp DOWN &kp UP &kp RIGHT &kp DLLR &trans +&trans &none &none &kp LBRC &kp LBKT &kp LPAR &kp RPAR &kp RBKT &kp RBRC &kp AT &kp CARET &trans + &trans &trans &trans &trans &trans &trans + &trans &trans &trans &trans &trans + >; + }; + }; +}; diff --git a/app/boards/shields/bat43/bat43.overlay b/app/boards/shields/bat43/bat43.overlay new file mode 100644 index 00000000..fc906e0f --- /dev/null +++ b/app/boards/shields/bat43/bat43.overlay @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + }; + + default_transform: keymap_transform_0 { + compatible = "zmk,matrix-transform"; + columns = <6>; + rows = <7>; + + map = < +RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(4,0) RC(4,1) RC(4,2) RC(4,3) RC(4,4) RC(4,5) +RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(3,0) RC(5,0) RC(5,1) RC(5,2) RC(5,3) RC(5,4) RC(5,5) +RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(6,0) RC(6,1) RC(6,2) RC(6,3) RC(6,4) RC(6,5) + RC(3,3) RC(3,4) RC(3,5) RC(7,0) RC(7,1) RC(7,2) + RC(7,5) RC(7,4) RC(7,3) RC(3,1) RC(3,2) + >; + }; + + kscan0: kscan_0 { + compatible = "zmk,kscan-gpio-matrix"; + label = "KSCAN"; + diode-direction = "col2row"; + + col-gpios + = <&pro_micro 10 GPIO_ACTIVE_HIGH> + , <&pro_micro 16 GPIO_ACTIVE_HIGH> + , <&pro_micro 14 GPIO_ACTIVE_HIGH> + , <&pro_micro 15 GPIO_ACTIVE_HIGH> + , <&pro_micro 9 GPIO_ACTIVE_HIGH> + , <&pro_micro 8 GPIO_ACTIVE_HIGH> + ; + + row-gpios + = <&pro_micro 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 18 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 19 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 20 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 21 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + ; + }; +}; diff --git a/app/boards/shields/bat43/bat43.zmk.yml b/app/boards/shields/bat43/bat43.zmk.yml new file mode 100644 index 00000000..a84bf862 --- /dev/null +++ b/app/boards/shields/bat43/bat43.zmk.yml @@ -0,0 +1,8 @@ +file_format: "1" +id: bat43 +name: BAT43 +type: shield +url: https://kbd.dailycraft.jp/bat43/ +requires: [pro_micro] +features: + - keys From a1a8c30f7ff9f0b57ad9f278e6ccd47697855867 Mon Sep 17 00:00:00 2001 From: Nick Winans Date: Mon, 14 Mar 2022 00:36:29 -0500 Subject: [PATCH 02/10] Remove deprecated key codes from bat43 keymap --- app/boards/shields/bat43/bat43.keymap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/boards/shields/bat43/bat43.keymap b/app/boards/shields/bat43/bat43.keymap index 34670d37..0f7e2d55 100644 --- a/app/boards/shields/bat43/bat43.keymap +++ b/app/boards/shields/bat43/bat43.keymap @@ -23,7 +23,7 @@ bindings = < &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp MINUS &kp TAB &kp A &kp S &kp D &kp F &kp G &kp BSPC &kp H &kp J &kp K &kp L &kp SEMI &kp RSHFT -&kp LCTL &kp Z &kp X &kp C &kp V &kp B &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp RCTL +&kp LCTRL &kp Z &kp X &kp C &kp V &kp B &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp RCTRL &kp LGUI &kp LANG2 L_SPC R_RET &kp LANG1 &kp RALT &bt BT_CLR &out OUT_TOG &bt BT_SEL 0 &bt BT_SEL 1 &bt BT_SEL 2 >; From 2b4d5dd7d9271dd520d9143a9bc5d95db08921a3 Mon Sep 17 00:00:00 2001 From: Carlos Filoteo Date: Sun, 13 Mar 2022 23:51:55 -0600 Subject: [PATCH 03/10] feat(shields): Add elephant42 Shield Support (#1009) * Initial Elephant42 implementation * Add underglow feature * Fix keymap * Copy corne defconfig for OLED and LED * Fix matrix positions * Add nice_nano_v2.overlay * Usability improvements to keymap * Update LED length * Delete nice_nano v1 overlay * Remove unused conf files * Add copyright/license headers * PR feedback * Try fixing formatting again * Minor format * More missed tabs * Format --- .../shields/elephant42/Kconfig.defconfig | 55 +++++++++++++++++ app/boards/shields/elephant42/Kconfig.shield | 8 +++ .../elephant42/boards/nice_nano_v2.overlay | 28 +++++++++ app/boards/shields/elephant42/elephant42.conf | 6 ++ app/boards/shields/elephant42/elephant42.dtsi | 60 +++++++++++++++++++ .../shields/elephant42/elephant42.keymap | 55 +++++++++++++++++ .../shields/elephant42/elephant42.zmk.yml | 14 +++++ .../elephant42/elephant42_left.overlay | 18 ++++++ .../elephant42/elephant42_right.overlay | 22 +++++++ 9 files changed, 266 insertions(+) create mode 100644 app/boards/shields/elephant42/Kconfig.defconfig create mode 100644 app/boards/shields/elephant42/Kconfig.shield create mode 100644 app/boards/shields/elephant42/boards/nice_nano_v2.overlay create mode 100644 app/boards/shields/elephant42/elephant42.conf create mode 100644 app/boards/shields/elephant42/elephant42.dtsi create mode 100644 app/boards/shields/elephant42/elephant42.keymap create mode 100644 app/boards/shields/elephant42/elephant42.zmk.yml create mode 100644 app/boards/shields/elephant42/elephant42_left.overlay create mode 100644 app/boards/shields/elephant42/elephant42_right.overlay diff --git a/app/boards/shields/elephant42/Kconfig.defconfig b/app/boards/shields/elephant42/Kconfig.defconfig new file mode 100644 index 00000000..db2fa230 --- /dev/null +++ b/app/boards/shields/elephant42/Kconfig.defconfig @@ -0,0 +1,55 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +if SHIELD_ELEPHANT42_LEFT + +config ZMK_KEYBOARD_NAME + default "Elephant42" + +config ZMK_SPLIT_BLE_ROLE_CENTRAL + default y + +endif + +if SHIELD_ELEPHANT42_LEFT || SHIELD_ELEPHANT42_RIGHT + +config ZMK_SPLIT + default y + +if ZMK_DISPLAY + +config I2C + default y + +config SSD1306 + default y + +config SSD1306_REVERSE_MODE + default y + +endif # ZMK_DISPLAY + +if LVGL + +config LVGL_HOR_RES_MAX + default 128 + +config LVGL_VER_RES_MAX + default 32 + +config LVGL_VDB_SIZE + default 64 + +config LVGL_DPI + default 148 + +config LVGL_BITS_PER_PIXEL + default 1 + +choice LVGL_COLOR_DEPTH + default LVGL_COLOR_DEPTH_1 +endchoice + +endif # LVGL + +endif \ No newline at end of file diff --git a/app/boards/shields/elephant42/Kconfig.shield b/app/boards/shields/elephant42/Kconfig.shield new file mode 100644 index 00000000..25841868 --- /dev/null +++ b/app/boards/shields/elephant42/Kconfig.shield @@ -0,0 +1,8 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config SHIELD_ELEPHANT42_LEFT + def_bool $(shields_list_contains,elephant42_left) + +config SHIELD_ELEPHANT42_RIGHT + def_bool $(shields_list_contains,elephant42_right) \ No newline at end of file diff --git a/app/boards/shields/elephant42/boards/nice_nano_v2.overlay b/app/boards/shields/elephant42/boards/nice_nano_v2.overlay new file mode 100644 index 00000000..6cd5de8c --- /dev/null +++ b/app/boards/shields/elephant42/boards/nice_nano_v2.overlay @@ -0,0 +1,28 @@ +&spi1 { + compatible = "nordic,nrf-spim"; + /* Cannot be used together with i2c0. */ + status = "okay"; + mosi-pin = <6>; + // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. + sck-pin = <5>; + miso-pin = <7>; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + label = "WS2812"; + /* SPI */ + reg = <0>; /* ignored, but necessary for SPI bindings */ + spi-max-frequency = <4000000>; + + /* WS2812 */ + chain-length = <27>; /* There are per-key RGB and the LAST 6 are underglow */ + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + }; +}; + +/ { + chosen { + zmk,underglow = &led_strip; + }; +}; \ No newline at end of file diff --git a/app/boards/shields/elephant42/elephant42.conf b/app/boards/shields/elephant42/elephant42.conf new file mode 100644 index 00000000..1b41763f --- /dev/null +++ b/app/boards/shields/elephant42/elephant42.conf @@ -0,0 +1,6 @@ +# Uncomment the following lines to enable the Elephant42 RGB Underglow +# CONFIG_ZMK_RGB_UNDERGLOW=y +# CONFIG_WS2812_STRIP=y + +# Uncomment the following line to enable the Elephant42 OLED Display +# CONFIG_ZMK_DISPLAY=y \ No newline at end of file diff --git a/app/boards/shields/elephant42/elephant42.dtsi b/app/boards/shields/elephant42/elephant42.dtsi new file mode 100644 index 00000000..e2b708ca --- /dev/null +++ b/app/boards/shields/elephant42/elephant42.dtsi @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + }; + + default_transform: keymap_transform_0 { + compatible = "zmk,matrix-transform"; + columns = <12>; + rows = <4>; + map = < +RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10) RC(0,11) +RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,6) RC(1,7) RC(1,8) RC(1,9) RC(1,10) RC(1,11) + RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,6) RC(2,7) RC(2,8) RC(2,9) RC(2,10) + RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(3,6) RC(3,7) RC(3,8) RC(3,9) + >; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + label = "KSCAN"; + + diode-direction = "col2row"; + row-gpios + = <&pro_micro 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + ; + + }; +}; + +&pro_micro_i2c { + status = "okay"; + + oled: ssd1306@3c { + compatible = "solomon,ssd1306fb"; + reg = <0x3c>; + label = "DISPLAY"; + width = <128>; + height = <32>; + segment-offset = <0>; + page-offset = <0>; + display-offset = <0>; + multiplex-ratio = <31>; + segment-remap; + com-invdir; + com-sequential; + prechargep = <0x22>; + }; +}; \ No newline at end of file diff --git a/app/boards/shields/elephant42/elephant42.keymap b/app/boards/shields/elephant42/elephant42.keymap new file mode 100644 index 00000000..8594c117 --- /dev/null +++ b/app/boards/shields/elephant42/elephant42.keymap @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#define LOWR 1 +#define RAIS 2 +#define ADJT 3 + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + < ADJT ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp DEL + &mt LCTRL TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SEMI &kp SQT + &kp Z &kp X &kp C &kp V &kp B &kp N &kp M &kp COMMA &kp DOT &kp FSLH + &kp LSHFT &mo LOWR &kp LGUI &kp BSPC &kp SPACE &kp ENTER &mo RAIS &kp LALT + >; + }; + + lower { + bindings = < + &kp GRAVE &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp C_PLAY_PAUSE + &trans &trans &trans &trans &trans &trans &kp LEFT &kp DOWN &kp UP &kp RIGHT &trans &trans + &trans &trans &trans &trans &kp LBKT &kp RBKT &trans &trans &trans &trans + &trans &trans &trans &trans &trans &trans &trans &trans + >; + }; + raise { + bindings = < + &kp TILDE &trans &trans &trans &trans &trans &trans &trans &trans &kp MINUS &kp EQUAL &kp C_PLAY_PAUSE + &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &kp BSLH + &trans &trans &trans &trans &kp LBKT &kp RBKT &trans &trans &trans &trans + &trans &trans &trans &trans &trans &trans &trans &trans + >; + }; + + adjust { + bindings = < + &trans &bt BT_NXT &bt BT_PRV &trans &trans &bt BT_CLR &trans &trans &trans &trans &trans &trans + &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans + &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans + &out OUT_TOG &trans &trans &trans &trans &trans &trans &trans + >; + }; + }; +}; \ No newline at end of file diff --git a/app/boards/shields/elephant42/elephant42.zmk.yml b/app/boards/shields/elephant42/elephant42.zmk.yml new file mode 100644 index 00000000..60eb3777 --- /dev/null +++ b/app/boards/shields/elephant42/elephant42.zmk.yml @@ -0,0 +1,14 @@ +file_format: "1" +id: elephant42 +name: Elephant42 +type: shield +url: https://github.com/illness072/elephant42 +requires: [pro_micro] +exposes: [i2c_oled] +features: + - keys + - display + - underglow +siblings: + - elephant42_left + - elephant42_right diff --git a/app/boards/shields/elephant42/elephant42_left.overlay b/app/boards/shields/elephant42/elephant42_left.overlay new file mode 100644 index 00000000..72fe2251 --- /dev/null +++ b/app/boards/shields/elephant42/elephant42_left.overlay @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include "elephant42.dtsi" + +&kscan0 { + col-gpios + = <&pro_micro 21 GPIO_ACTIVE_HIGH> + , <&pro_micro 20 GPIO_ACTIVE_HIGH> + , <&pro_micro 19 GPIO_ACTIVE_HIGH> + , <&pro_micro 18 GPIO_ACTIVE_HIGH> + , <&pro_micro 15 GPIO_ACTIVE_HIGH> + , <&pro_micro 14 GPIO_ACTIVE_HIGH> + ; +}; \ No newline at end of file diff --git a/app/boards/shields/elephant42/elephant42_right.overlay b/app/boards/shields/elephant42/elephant42_right.overlay new file mode 100644 index 00000000..35bd5895 --- /dev/null +++ b/app/boards/shields/elephant42/elephant42_right.overlay @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include "elephant42.dtsi" + +&default_transform { + col-offset = <6>; +}; + +&kscan0 { + col-gpios + = <&pro_micro 14 GPIO_ACTIVE_HIGH> + , <&pro_micro 15 GPIO_ACTIVE_HIGH> + , <&pro_micro 18 GPIO_ACTIVE_HIGH> + , <&pro_micro 19 GPIO_ACTIVE_HIGH> + , <&pro_micro 20 GPIO_ACTIVE_HIGH> + , <&pro_micro 21 GPIO_ACTIVE_HIGH> + ; +}; From d6a2290d5ef5da561b590f9b1d236c40005940fe Mon Sep 17 00:00:00 2001 From: Aaron Nunley Date: Fri, 29 Jan 2021 17:52:33 -0800 Subject: [PATCH 04/10] Added support for 2% Milk (Updated with metadata) Update app/boards/shields/two_percent_milk/two_percent_milk.overlay Co-Authored-By: Nick Winans --- .../two_percent_milk/Kconfig.defconfig | 9 +++++++ .../shields/two_percent_milk/Kconfig.shield | 5 ++++ .../two_percent_milk/two_percent_milk.conf | 2 ++ .../two_percent_milk/two_percent_milk.keymap | 22 +++++++++++++++++ .../two_percent_milk/two_percent_milk.overlay | 24 +++++++++++++++++++ .../two_percent_milk/two_percent_milk.zmk.yml | 8 +++++++ 6 files changed, 70 insertions(+) create mode 100644 app/boards/shields/two_percent_milk/Kconfig.defconfig create mode 100644 app/boards/shields/two_percent_milk/Kconfig.shield create mode 100644 app/boards/shields/two_percent_milk/two_percent_milk.conf create mode 100644 app/boards/shields/two_percent_milk/two_percent_milk.keymap create mode 100644 app/boards/shields/two_percent_milk/two_percent_milk.overlay create mode 100644 app/boards/shields/two_percent_milk/two_percent_milk.zmk.yml diff --git a/app/boards/shields/two_percent_milk/Kconfig.defconfig b/app/boards/shields/two_percent_milk/Kconfig.defconfig new file mode 100644 index 00000000..1046f53c --- /dev/null +++ b/app/boards/shields/two_percent_milk/Kconfig.defconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +if SHIELD_TWO_PERCENT_MILK + +config ZMK_KEYBOARD_NAME + default "2% Milk" + +endif diff --git a/app/boards/shields/two_percent_milk/Kconfig.shield b/app/boards/shields/two_percent_milk/Kconfig.shield new file mode 100644 index 00000000..ec2c3b1f --- /dev/null +++ b/app/boards/shields/two_percent_milk/Kconfig.shield @@ -0,0 +1,5 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config SHIELD_TWO_PERCENT_MILK + def_bool $(shields_list_contains,two_percent_milk) diff --git a/app/boards/shields/two_percent_milk/two_percent_milk.conf b/app/boards/shields/two_percent_milk/two_percent_milk.conf new file mode 100644 index 00000000..fb23f20c --- /dev/null +++ b/app/boards/shields/two_percent_milk/two_percent_milk.conf @@ -0,0 +1,2 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT diff --git a/app/boards/shields/two_percent_milk/two_percent_milk.keymap b/app/boards/shields/two_percent_milk/two_percent_milk.keymap new file mode 100644 index 00000000..71152de2 --- /dev/null +++ b/app/boards/shields/two_percent_milk/two_percent_milk.keymap @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + + #include + #include + #include + + / { + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &kp X + &kp Z + >; + }; + }; + }; \ No newline at end of file diff --git a/app/boards/shields/two_percent_milk/two_percent_milk.overlay b/app/boards/shields/two_percent_milk/two_percent_milk.overlay new file mode 100644 index 00000000..d43ed321 --- /dev/null +++ b/app/boards/shields/two_percent_milk/two_percent_milk.overlay @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + chosen { + zmk,kscan = &kscan0; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-direct"; + + label = "KSCAN"; + + input-gpios + = <&pro_micro 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + ; + + }; + +}; \ No newline at end of file diff --git a/app/boards/shields/two_percent_milk/two_percent_milk.zmk.yml b/app/boards/shields/two_percent_milk/two_percent_milk.zmk.yml new file mode 100644 index 00000000..c53c2df2 --- /dev/null +++ b/app/boards/shields/two_percent_milk/two_percent_milk.zmk.yml @@ -0,0 +1,8 @@ +file_format: "1" +id: two_percent_milk +name: 2% Milk +type: shield +url: https://github.com/Spaceboards/SpaceboardsHardware/tree/master/Keyboards/2%25%20Milk +requires: [pro_micro] +features: + - keys From 459972fddd3d9e778b91b824888de9e0bbeffbce Mon Sep 17 00:00:00 2001 From: Krzysztof Gutkowski Date: Sat, 12 Mar 2022 19:18:25 +0000 Subject: [PATCH 05/10] fix(boards): Adjust matrix for BT60v1 to fix the broken right Shift key according to the diagram from the board creators, it should be RC(3,12) instead of RC(3,13) --- app/boards/arm/bt60/bt60_v1.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/boards/arm/bt60/bt60_v1.dts b/app/boards/arm/bt60/bt60_v1.dts index 83da2c07..8b2f0cb4 100644 --- a/app/boards/arm/bt60/bt60_v1.dts +++ b/app/boards/arm/bt60/bt60_v1.dts @@ -22,7 +22,7 @@ RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10) RC(0,11) RC(0,12) RC(0,13) RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,6) RC(1,7) RC(1,8) RC(1,9) RC(1,10) RC(1,11) RC(1,12) RC(1,13) RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,6) RC(2,7) RC(2,8) RC(2,9) RC(2,10) RC(2,11) RC(2,13) - RC(3,0) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(3,6) RC(3,7) RC(3,8) RC(3,9) RC(3,10) RC(3,11) RC(3,13) + RC(3,0) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(3,6) RC(3,7) RC(3,8) RC(3,9) RC(3,10) RC(3,11) RC(3,12) RC(4,0) RC(4,1) RC(4,2) RC(4,6) RC(4,10) RC(4,11) RC(4,12) RC(4,13) RC(4,14) >; }; From ad5a12a7bcdc7c0962fdef7c23eaa0cf14d0b7ed Mon Sep 17 00:00:00 2001 From: okke Date: Sun, 27 Feb 2022 14:11:36 +0100 Subject: [PATCH 06/10] fix(behaviors): Fix bug in nested sticky keys If multiple sticky keys with quick release were nested, only the first one was properly released. This fix makes sure all of them are released properly. Fixes https://github.com/zmkfirmware/zmk/issues/1149 --- app/src/behaviors/behavior_sticky_key.c | 14 +++- .../sticky-keys/10-sl-sl-kp/events.patterns | 1 + .../10-sl-sl-kp/keycode_events.snapshot | 8 +++ .../10-sl-sl-kp/native_posix.keymap | 65 +++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 app/tests/sticky-keys/10-sl-sl-kp/events.patterns create mode 100644 app/tests/sticky-keys/10-sl-sl-kp/keycode_events.snapshot create mode 100644 app/tests/sticky-keys/10-sl-sl-kp/native_posix.keymap diff --git a/app/src/behaviors/behavior_sticky_key.c b/app/src/behaviors/behavior_sticky_key.c index 3c75a7a3..7909e1af 100644 --- a/app/src/behaviors/behavior_sticky_key.c +++ b/app/src/behaviors/behavior_sticky_key.c @@ -188,6 +188,9 @@ static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) { if (ev == NULL) { return ZMK_EV_EVENT_BUBBLE; } + + // keep track whether the event has been reraised, so we only reraise it once + bool event_reraised = false; for (int i = 0; i < ZMK_BHV_STICKY_KEY_MAX_HELD; i++) { struct active_sticky_key *sticky_key = &active_sticky_keys[i]; if (sticky_key->position == ZMK_BHV_STICKY_KEY_POSITION_FREE) { @@ -223,10 +226,12 @@ static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) { if (sticky_key->timer_started) { stop_timer(sticky_key); if (sticky_key->config->quick_release) { - // continue processing the event. Release the sticky key afterwards. - ZMK_EVENT_RAISE_AFTER(eh, behavior_sticky_key); + // immediately release the sticky key after the key press is handled. + if (!event_reraised) { + ZMK_EVENT_RAISE_AFTER(eh, behavior_sticky_key); + event_reraised = true; + } release_sticky_key_behavior(sticky_key, ev->timestamp); - return ZMK_EV_EVENT_CAPTURED; } } sticky_key->modified_key_usage_page = ev->usage_page; @@ -240,6 +245,9 @@ static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) { } } } + if (event_reraised) { + return ZMK_EV_EVENT_CAPTURED; + } return ZMK_EV_EVENT_BUBBLE; } diff --git a/app/tests/sticky-keys/10-sl-sl-kp/events.patterns b/app/tests/sticky-keys/10-sl-sl-kp/events.patterns new file mode 100644 index 00000000..833100f6 --- /dev/null +++ b/app/tests/sticky-keys/10-sl-sl-kp/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/sticky-keys/10-sl-sl-kp/keycode_events.snapshot b/app/tests/sticky-keys/10-sl-sl-kp/keycode_events.snapshot new file mode 100644 index 00000000..fc0f29b9 --- /dev/null +++ b/app/tests/sticky-keys/10-sl-sl-kp/keycode_events.snapshot @@ -0,0 +1,8 @@ +pressed: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +pressed: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/sticky-keys/10-sl-sl-kp/native_posix.keymap b/app/tests/sticky-keys/10-sl-sl-kp/native_posix.keymap new file mode 100644 index 00000000..e9b87f42 --- /dev/null +++ b/app/tests/sticky-keys/10-sl-sl-kp/native_posix.keymap @@ -0,0 +1,65 @@ +#include +#include +#include + +/* + sticky layers should quick-release. + Thus, the second keypress should be on the default layer, not on the lower_layer. +*/ + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &sl 1 &kp A + &none &none>; + }; + + layer_1 { + bindings = < + &sl 2 &none + &none &none>; + }; + + layer_2 { + bindings = < + &none &kp NUM_1 + &none &none>; + }; + }; +}; + +&kscan { + events = < + /* press sl 1 */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + /* press sl 2 */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + /* press 1 */ + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + /* press A */ + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + + /* repeat test to check if cleanup is done correctly */ + /* press sl 1 */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + /* press sl 2 */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + /* press 1 */ + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + /* press A */ + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + + >; +}; \ No newline at end of file From 32ebe2cfb57be5881750b4296a8ece85921f3aa9 Mon Sep 17 00:00:00 2001 From: Kurtis Lew Date: Tue, 15 Mar 2022 22:08:42 -0700 Subject: [PATCH 07/10] feat(behaviors): Add Tap-Dance behavior --- app/CMakeLists.txt | 1 + .../behaviors/zmk,behavior-tap-dance.yaml | 16 ++ app/src/behaviors/behavior_tap_dance.c | 259 ++++++++++++++++++ app/tests/tap-dance/1a-tap1/events.patterns | 2 + .../tap-dance/1a-tap1/keycode_events.snapshot | 5 + .../tap-dance/1a-tap1/native_posix.keymap | 11 + app/tests/tap-dance/1b-tap2/events.patterns | 2 + .../tap-dance/1b-tap2/keycode_events.snapshot | 7 + .../tap-dance/1b-tap2/native_posix.keymap | 13 + app/tests/tap-dance/1c-tap3/events.patterns | 2 + .../tap-dance/1c-tap3/keycode_events.snapshot | 9 + .../tap-dance/1c-tap3/native_posix.keymap | 15 + app/tests/tap-dance/2a-hold1/events.patterns | 2 + .../2a-hold1/keycode_events.snapshot | 5 + .../tap-dance/2a-hold1/native_posix.keymap | 11 + app/tests/tap-dance/2b-hold2/events.patterns | 2 + .../2b-hold2/keycode_events.snapshot | 7 + .../tap-dance/2b-hold2/native_posix.keymap | 13 + app/tests/tap-dance/2c-hold3/events.patterns | 2 + .../2c-hold3/keycode_events.snapshot | 9 + .../tap-dance/2c-hold3/native_posix.keymap | 15 + .../tap-dance/3a-tap-int-mid/events.patterns | 2 + .../3a-tap-int-mid/keycode_events.snapshot | 10 + .../3a-tap-int-mid/native_posix.keymap | 13 + .../tap-dance/3b-tap-int-seq/events.patterns | 2 + .../3b-tap-int-seq/keycode_events.snapshot | 10 + .../3b-tap-int-seq/native_posix.keymap | 13 + .../3c-tap-int-after/events.patterns | 2 + .../3c-tap-int-after/keycode_events.snapshot | 10 + .../3c-tap-int-after/native_posix.keymap | 13 + .../tap-dance/3d-hold-int-mid/events.patterns | 2 + .../3d-hold-int-mid/keycode_events.snapshot | 10 + .../3d-hold-int-mid/native_posix.keymap | 13 + .../tap-dance/3e-hold-int-seq/events.patterns | 2 + .../3e-hold-int-seq/keycode_events.snapshot | 10 + .../3e-hold-int-seq/native_posix.keymap | 13 + .../3f-hold-int-after/events.patterns | 2 + .../3f-hold-int-after/keycode_events.snapshot | 10 + .../3f-hold-int-after/native_posix.keymap | 13 + app/tests/tap-dance/4a-single/events.patterns | 2 + .../4a-single/keycode_events.snapshot | 5 + .../tap-dance/4a-single/native_posix.keymap | 11 + .../tap-dance/5a-tdint-mid/events.patterns | 2 + .../5a-tdint-mid/keycode_events.snapshot | 10 + .../5a-tdint-mid/native_posix.keymap | 13 + .../tap-dance/5b-tdint-seq/events.patterns | 2 + .../5b-tdint-seq/keycode_events.snapshot | 10 + .../5b-tdint-seq/native_posix.keymap | 13 + .../tap-dance/5c-tdint-after/events.patterns | 2 + .../5c-tdint-after/keycode_events.snapshot | 10 + .../5c-tdint-after/native_posix.keymap | 13 + .../5d-tdint-multiple/events.patterns | 2 + .../5d-tdint-multiple/keycode_events.snapshot | 15 + .../5d-tdint-multiple/native_posix.keymap | 15 + app/tests/tap-dance/behavior_keymap.dtsi | 60 ++++ docs/docs/assets/tap-dance/timing_diagram.svg | 83 ++++++ docs/docs/behaviors/tap-dance.md | 76 +++++ docs/sidebars.js | 1 + 58 files changed, 903 insertions(+) create mode 100644 app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml create mode 100644 app/src/behaviors/behavior_tap_dance.c create mode 100644 app/tests/tap-dance/1a-tap1/events.patterns create mode 100644 app/tests/tap-dance/1a-tap1/keycode_events.snapshot create mode 100644 app/tests/tap-dance/1a-tap1/native_posix.keymap create mode 100644 app/tests/tap-dance/1b-tap2/events.patterns create mode 100644 app/tests/tap-dance/1b-tap2/keycode_events.snapshot create mode 100644 app/tests/tap-dance/1b-tap2/native_posix.keymap create mode 100644 app/tests/tap-dance/1c-tap3/events.patterns create mode 100644 app/tests/tap-dance/1c-tap3/keycode_events.snapshot create mode 100644 app/tests/tap-dance/1c-tap3/native_posix.keymap create mode 100644 app/tests/tap-dance/2a-hold1/events.patterns create mode 100644 app/tests/tap-dance/2a-hold1/keycode_events.snapshot create mode 100644 app/tests/tap-dance/2a-hold1/native_posix.keymap create mode 100644 app/tests/tap-dance/2b-hold2/events.patterns create mode 100644 app/tests/tap-dance/2b-hold2/keycode_events.snapshot create mode 100644 app/tests/tap-dance/2b-hold2/native_posix.keymap create mode 100644 app/tests/tap-dance/2c-hold3/events.patterns create mode 100644 app/tests/tap-dance/2c-hold3/keycode_events.snapshot create mode 100644 app/tests/tap-dance/2c-hold3/native_posix.keymap create mode 100644 app/tests/tap-dance/3a-tap-int-mid/events.patterns create mode 100644 app/tests/tap-dance/3a-tap-int-mid/keycode_events.snapshot create mode 100644 app/tests/tap-dance/3a-tap-int-mid/native_posix.keymap create mode 100644 app/tests/tap-dance/3b-tap-int-seq/events.patterns create mode 100644 app/tests/tap-dance/3b-tap-int-seq/keycode_events.snapshot create mode 100644 app/tests/tap-dance/3b-tap-int-seq/native_posix.keymap create mode 100644 app/tests/tap-dance/3c-tap-int-after/events.patterns create mode 100644 app/tests/tap-dance/3c-tap-int-after/keycode_events.snapshot create mode 100644 app/tests/tap-dance/3c-tap-int-after/native_posix.keymap create mode 100644 app/tests/tap-dance/3d-hold-int-mid/events.patterns create mode 100644 app/tests/tap-dance/3d-hold-int-mid/keycode_events.snapshot create mode 100644 app/tests/tap-dance/3d-hold-int-mid/native_posix.keymap create mode 100644 app/tests/tap-dance/3e-hold-int-seq/events.patterns create mode 100644 app/tests/tap-dance/3e-hold-int-seq/keycode_events.snapshot create mode 100644 app/tests/tap-dance/3e-hold-int-seq/native_posix.keymap create mode 100644 app/tests/tap-dance/3f-hold-int-after/events.patterns create mode 100644 app/tests/tap-dance/3f-hold-int-after/keycode_events.snapshot create mode 100644 app/tests/tap-dance/3f-hold-int-after/native_posix.keymap create mode 100644 app/tests/tap-dance/4a-single/events.patterns create mode 100644 app/tests/tap-dance/4a-single/keycode_events.snapshot create mode 100644 app/tests/tap-dance/4a-single/native_posix.keymap create mode 100644 app/tests/tap-dance/5a-tdint-mid/events.patterns create mode 100644 app/tests/tap-dance/5a-tdint-mid/keycode_events.snapshot create mode 100644 app/tests/tap-dance/5a-tdint-mid/native_posix.keymap create mode 100644 app/tests/tap-dance/5b-tdint-seq/events.patterns create mode 100644 app/tests/tap-dance/5b-tdint-seq/keycode_events.snapshot create mode 100644 app/tests/tap-dance/5b-tdint-seq/native_posix.keymap create mode 100644 app/tests/tap-dance/5c-tdint-after/events.patterns create mode 100644 app/tests/tap-dance/5c-tdint-after/keycode_events.snapshot create mode 100644 app/tests/tap-dance/5c-tdint-after/native_posix.keymap create mode 100644 app/tests/tap-dance/5d-tdint-multiple/events.patterns create mode 100644 app/tests/tap-dance/5d-tdint-multiple/keycode_events.snapshot create mode 100644 app/tests/tap-dance/5d-tdint-multiple/native_posix.keymap create mode 100644 app/tests/tap-dance/behavior_keymap.dtsi create mode 100644 docs/docs/assets/tap-dance/timing_diagram.svg create mode 100644 docs/docs/behaviors/tap-dance.md diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 25f6c6cd..d853255c 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -54,6 +54,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c) target_sources(app PRIVATE src/behaviors/behavior_mod_morph.c) target_sources(app PRIVATE src/behaviors/behavior_outputs.c) + target_sources(app PRIVATE src/behaviors/behavior_tap_dance.c) target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c) target_sources(app PRIVATE src/behaviors/behavior_to_layer.c) target_sources(app PRIVATE src/behaviors/behavior_transparent.c) diff --git a/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml b/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml new file mode 100644 index 00000000..8f01effc --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml @@ -0,0 +1,16 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Tap Dance Behavior + +compatible: "zmk,behavior-tap-dance" + +include: zero_param.yaml + +properties: + bindings: + type: phandle-array + required: true + tapping-term-ms: + type: int + default: 200 \ No newline at end of file diff --git a/app/src/behaviors/behavior_tap_dance.c b/app/src/behaviors/behavior_tap_dance.c new file mode 100644 index 00000000..4b35e4de --- /dev/null +++ b/app/src/behaviors/behavior_tap_dance.c @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_tap_dance + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +#define ZMK_BHV_TAP_DANCE_MAX_HELD 10 + +#define ZMK_BHV_TAP_DANCE_POSITION_FREE ULONG_MAX + +struct behavior_tap_dance_config { + uint32_t tapping_term_ms; + size_t behavior_count; + struct zmk_behavior_binding *behaviors; +}; + +struct active_tap_dance { + // Tap Dance Data + int counter; + uint32_t position; + uint32_t param1; + uint32_t param2; + bool is_pressed; + const struct behavior_tap_dance_config *config; + + // Timer Data + bool timer_started; + bool timer_cancelled; + bool tap_dance_decided; + int64_t release_at; + struct k_delayed_work release_timer; +}; + +struct active_tap_dance active_tap_dances[ZMK_BHV_TAP_DANCE_MAX_HELD] = {}; + +static struct active_tap_dance *find_tap_dance(uint32_t position) { + for (int i = 0; i < ZMK_BHV_TAP_DANCE_MAX_HELD; i++) { + if (active_tap_dances[i].position == position && !active_tap_dances[i].timer_cancelled) { + return &active_tap_dances[i]; + } + } + return NULL; +} + +static int new_tap_dance(uint32_t position, const struct behavior_tap_dance_config *config, + struct active_tap_dance **tap_dance) { + for (int i = 0; i < ZMK_BHV_TAP_DANCE_MAX_HELD; i++) { + struct active_tap_dance *const ref_dance = &active_tap_dances[i]; + if (ref_dance->position == ZMK_BHV_TAP_DANCE_POSITION_FREE) { + ref_dance->counter = 0; + ref_dance->position = position; + ref_dance->config = config; + ref_dance->release_at = 0; + ref_dance->is_pressed = true; + ref_dance->timer_started = true; + ref_dance->timer_cancelled = false; + ref_dance->tap_dance_decided = false; + *tap_dance = ref_dance; + return 0; + } + } + return -ENOMEM; +} + +static void clear_tap_dance(struct active_tap_dance *tap_dance) { + tap_dance->position = ZMK_BHV_TAP_DANCE_POSITION_FREE; +} + +static int stop_timer(struct active_tap_dance *tap_dance) { + int timer_cancel_result = k_delayed_work_cancel(&tap_dance->release_timer); + if (timer_cancel_result == -EINPROGRESS) { + // too late to cancel, we'll let the timer handler clear up. + tap_dance->timer_cancelled = true; + } + return timer_cancel_result; +} + +static void reset_timer(struct active_tap_dance *tap_dance, + struct zmk_behavior_binding_event event) { + tap_dance->release_at = event.timestamp + tap_dance->config->tapping_term_ms; + int32_t ms_left = tap_dance->release_at - k_uptime_get(); + if (ms_left > 0) { + k_delayed_work_submit(&tap_dance->release_timer, K_MSEC(ms_left)); + LOG_DBG("Successfully reset timer at position %d", tap_dance->position); + } +} + +static inline int press_tap_dance_behavior(struct active_tap_dance *tap_dance, int64_t timestamp) { + tap_dance->tap_dance_decided = true; + struct zmk_behavior_binding binding = tap_dance->config->behaviors[tap_dance->counter - 1]; + struct zmk_behavior_binding_event event = { + .position = tap_dance->position, + .timestamp = timestamp, + }; + return behavior_keymap_binding_pressed(&binding, event); +} + +static inline int release_tap_dance_behavior(struct active_tap_dance *tap_dance, + int64_t timestamp) { + struct zmk_behavior_binding binding = tap_dance->config->behaviors[tap_dance->counter - 1]; + struct zmk_behavior_binding_event event = { + .position = tap_dance->position, + .timestamp = timestamp, + }; + clear_tap_dance(tap_dance); + return behavior_keymap_binding_released(&binding, event); +} + +static int on_tap_dance_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct behavior_tap_dance_config *cfg = dev->config; + struct active_tap_dance *tap_dance; + tap_dance = find_tap_dance(event.position); + if (tap_dance == NULL) { + if (new_tap_dance(event.position, cfg, &tap_dance) == -ENOMEM) { + LOG_ERR("Unable to create new tap dance. Insufficient space in active_tap_dances[]."); + return ZMK_BEHAVIOR_OPAQUE; + } + LOG_DBG("%d created new tap dance", event.position); + } + tap_dance->is_pressed = true; + LOG_DBG("%d tap dance pressed", event.position); + stop_timer(tap_dance); + // Increment the counter on keypress. If the counter has reached its maximum + // value, invoke the last binding available. + if (tap_dance->counter < cfg->behavior_count) { + tap_dance->counter++; + } + if (tap_dance->counter == cfg->behavior_count) { + // LOG_DBG("Tap dance has been decided via maximum counter value"); + press_tap_dance_behavior(tap_dance, event.timestamp); + return ZMK_EV_EVENT_BUBBLE; + } + reset_timer(tap_dance, event); + return ZMK_BEHAVIOR_OPAQUE; +} + +static int on_tap_dance_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("%d tap dance keybind released", event.position); + struct active_tap_dance *tap_dance = find_tap_dance(event.position); + if (tap_dance == NULL) { + LOG_ERR("ACTIVE TAP DANCE CLEARED TOO EARLY"); + return ZMK_BEHAVIOR_OPAQUE; + } + tap_dance->is_pressed = false; + if (tap_dance->tap_dance_decided) { + release_tap_dance_behavior(tap_dance, event.timestamp); + } + return ZMK_BEHAVIOR_OPAQUE; +} + +void behavior_tap_dance_timer_handler(struct k_work *item) { + struct active_tap_dance *tap_dance = CONTAINER_OF(item, struct active_tap_dance, release_timer); + if (tap_dance->position == ZMK_BHV_TAP_DANCE_POSITION_FREE) { + return; + } + if (tap_dance->timer_cancelled) { + return; + } + LOG_DBG("Tap dance has been decided via timer. Counter reached: %d", tap_dance->counter); + press_tap_dance_behavior(tap_dance, tap_dance->release_at); + if (tap_dance->is_pressed) { + return; + } + release_tap_dance_behavior(tap_dance, tap_dance->release_at); +} + +static const struct behavior_driver_api behavior_tap_dance_driver_api = { + .binding_pressed = on_tap_dance_binding_pressed, + .binding_released = on_tap_dance_binding_released, +}; + +static int tap_dance_position_state_changed_listener(const zmk_event_t *eh); + +ZMK_LISTENER(behavior_tap_dance, tap_dance_position_state_changed_listener); +ZMK_SUBSCRIPTION(behavior_tap_dance, zmk_position_state_changed); + +static int tap_dance_position_state_changed_listener(const zmk_event_t *eh) { + struct zmk_position_state_changed *ev = as_zmk_position_state_changed(eh); + if (ev == NULL) { + return ZMK_EV_EVENT_BUBBLE; + } + if (!ev->state) { + LOG_DBG("Ignore upstroke at position %d.", ev->position); + return ZMK_EV_EVENT_BUBBLE; + } + for (int i = 0; i < ZMK_BHV_TAP_DANCE_MAX_HELD; i++) { + struct active_tap_dance *tap_dance = &active_tap_dances[i]; + if (tap_dance->position == ZMK_BHV_TAP_DANCE_POSITION_FREE) { + continue; + } + if (tap_dance->position == ev->position) { + continue; + } + stop_timer(tap_dance); + LOG_DBG("Tap dance interrupted, activating tap-dance at %d", tap_dance->position); + if (!tap_dance->tap_dance_decided) { + press_tap_dance_behavior(tap_dance, ev->timestamp); + if (!tap_dance->is_pressed) { + release_tap_dance_behavior(tap_dance, ev->timestamp); + } + return ZMK_EV_EVENT_BUBBLE; + } + } + return ZMK_EV_EVENT_BUBBLE; +} + +static int behavior_tap_dance_init(const struct device *dev) { + static bool init_first_run = true; + if (init_first_run) { + for (int i = 0; i < ZMK_BHV_TAP_DANCE_MAX_HELD; i++) { + k_delayed_work_init(&active_tap_dances[i].release_timer, + behavior_tap_dance_timer_handler); + clear_tap_dance(&active_tap_dances[i]); + } + } + init_first_run = false; + return 0; +} + +#define _TRANSFORM_ENTRY(idx, node) ZMK_KEYMAP_EXTRACT_BINDING(idx, node), + +#define TRANSFORMED_BINDINGS(node) \ + { UTIL_LISTIFY(DT_INST_PROP_LEN(node, bindings), _TRANSFORM_ENTRY, DT_DRV_INST(node)) } + +#define KP_INST(n) \ + static struct zmk_behavior_binding \ + behavior_tap_dance_config_##n##_bindings[DT_INST_PROP_LEN(n, bindings)] = \ + TRANSFORMED_BINDINGS(n); \ + static struct behavior_tap_dance_config behavior_tap_dance_config_##n = { \ + .tapping_term_ms = DT_INST_PROP(n, tapping_term_ms), \ + .behaviors = behavior_tap_dance_config_##n##_bindings, \ + .behavior_count = DT_INST_PROP_LEN(n, bindings)}; \ + DEVICE_AND_API_INIT(behavior_tap_dance_##n, DT_INST_LABEL(n), behavior_tap_dance_init, NULL, \ + &behavior_tap_dance_config_##n, APPLICATION, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_tap_dance_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + +#endif \ No newline at end of file diff --git a/app/tests/tap-dance/1a-tap1/events.patterns b/app/tests/tap-dance/1a-tap1/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/1a-tap1/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/1a-tap1/keycode_events.snapshot b/app/tests/tap-dance/1a-tap1/keycode_events.snapshot new file mode 100644 index 00000000..38bc54c3 --- /dev/null +++ b/app/tests/tap-dance/1a-tap1/keycode_events.snapshot @@ -0,0 +1,5 @@ +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/1a-tap1/native_posix.keymap b/app/tests/tap-dance/1a-tap1/native_posix.keymap new file mode 100644 index 00000000..1e5dff06 --- /dev/null +++ b/app/tests/tap-dance/1a-tap1/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,200) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/1b-tap2/events.patterns b/app/tests/tap-dance/1b-tap2/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/1b-tap2/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/1b-tap2/keycode_events.snapshot b/app/tests/tap-dance/1b-tap2/keycode_events.snapshot new file mode 100644 index 00000000..c23537b9 --- /dev/null +++ b/app/tests/tap-dance/1b-tap2/keycode_events.snapshot @@ -0,0 +1,7 @@ +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/1b-tap2/native_posix.keymap b/app/tests/tap-dance/1b-tap2/native_posix.keymap new file mode 100644 index 00000000..c5e1c8db --- /dev/null +++ b/app/tests/tap-dance/1b-tap2/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,200) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/1c-tap3/events.patterns b/app/tests/tap-dance/1c-tap3/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/1c-tap3/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/1c-tap3/keycode_events.snapshot b/app/tests/tap-dance/1c-tap3/keycode_events.snapshot new file mode 100644 index 00000000..1e68bae9 --- /dev/null +++ b/app/tests/tap-dance/1c-tap3/keycode_events.snapshot @@ -0,0 +1,9 @@ +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/1c-tap3/native_posix.keymap b/app/tests/tap-dance/1c-tap3/native_posix.keymap new file mode 100644 index 00000000..6813393e --- /dev/null +++ b/app/tests/tap-dance/1c-tap3/native_posix.keymap @@ -0,0 +1,15 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,200) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/2a-hold1/events.patterns b/app/tests/tap-dance/2a-hold1/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/2a-hold1/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/2a-hold1/keycode_events.snapshot b/app/tests/tap-dance/2a-hold1/keycode_events.snapshot new file mode 100644 index 00000000..2a0965dc --- /dev/null +++ b/app/tests/tap-dance/2a-hold1/keycode_events.snapshot @@ -0,0 +1,5 @@ +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 0 tap dance keybind released +kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/2a-hold1/native_posix.keymap b/app/tests/tap-dance/2a-hold1/native_posix.keymap new file mode 100644 index 00000000..f4c7a2d2 --- /dev/null +++ b/app/tests/tap-dance/2a-hold1/native_posix.keymap @@ -0,0 +1,11 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,400) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/2b-hold2/events.patterns b/app/tests/tap-dance/2b-hold2/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/2b-hold2/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/2b-hold2/keycode_events.snapshot b/app/tests/tap-dance/2b-hold2/keycode_events.snapshot new file mode 100644 index 00000000..dbccfc94 --- /dev/null +++ b/app/tests/tap-dance/2b-hold2/keycode_events.snapshot @@ -0,0 +1,7 @@ +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +td_binding_pressed: 0 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0xe2 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 0 tap dance keybind released +kp_released: usage_page 0x07 keycode 0xe2 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/2b-hold2/native_posix.keymap b/app/tests/tap-dance/2b-hold2/native_posix.keymap new file mode 100644 index 00000000..0fec2e40 --- /dev/null +++ b/app/tests/tap-dance/2b-hold2/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,400) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/2c-hold3/events.patterns b/app/tests/tap-dance/2c-hold3/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/2c-hold3/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/2c-hold3/keycode_events.snapshot b/app/tests/tap-dance/2c-hold3/keycode_events.snapshot new file mode 100644 index 00000000..3ac8e0e6 --- /dev/null +++ b/app/tests/tap-dance/2c-hold3/keycode_events.snapshot @@ -0,0 +1,9 @@ +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +td_binding_pressed: 0 tap dance pressed +td_binding_released: 0 tap dance keybind released +td_binding_pressed: 0 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0xe3 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 0 tap dance keybind released +kp_released: usage_page 0x07 keycode 0xe3 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/2c-hold3/native_posix.keymap b/app/tests/tap-dance/2c-hold3/native_posix.keymap new file mode 100644 index 00000000..8375c6f6 --- /dev/null +++ b/app/tests/tap-dance/2c-hold3/native_posix.keymap @@ -0,0 +1,15 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,400) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/3a-tap-int-mid/events.patterns b/app/tests/tap-dance/3a-tap-int-mid/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/3a-tap-int-mid/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/3a-tap-int-mid/keycode_events.snapshot b/app/tests/tap-dance/3a-tap-int-mid/keycode_events.snapshot new file mode 100644 index 00000000..715d0143 --- /dev/null +++ b/app/tests/tap-dance/3a-tap-int-mid/keycode_events.snapshot @@ -0,0 +1,10 @@ +td_binding_pressed: 2 created new tap dance +td_binding_pressed: 2 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 1 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 2 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/3a-tap-int-mid/native_posix.keymap b/app/tests/tap-dance/3a-tap-int-mid/native_posix.keymap new file mode 100644 index 00000000..8a62430c --- /dev/null +++ b/app/tests/tap-dance/3a-tap-int-mid/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(1,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/3b-tap-int-seq/events.patterns b/app/tests/tap-dance/3b-tap-int-seq/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/3b-tap-int-seq/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/3b-tap-int-seq/keycode_events.snapshot b/app/tests/tap-dance/3b-tap-int-seq/keycode_events.snapshot new file mode 100644 index 00000000..a973e426 --- /dev/null +++ b/app/tests/tap-dance/3b-tap-int-seq/keycode_events.snapshot @@ -0,0 +1,10 @@ +td_binding_pressed: 2 created new tap dance +td_binding_pressed: 2 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 2 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 1 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/3b-tap-int-seq/native_posix.keymap b/app/tests/tap-dance/3b-tap-int-seq/native_posix.keymap new file mode 100644 index 00000000..4a76bdb0 --- /dev/null +++ b/app/tests/tap-dance/3b-tap-int-seq/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/3c-tap-int-after/events.patterns b/app/tests/tap-dance/3c-tap-int-after/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/3c-tap-int-after/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/3c-tap-int-after/keycode_events.snapshot b/app/tests/tap-dance/3c-tap-int-after/keycode_events.snapshot new file mode 100644 index 00000000..2c715537 --- /dev/null +++ b/app/tests/tap-dance/3c-tap-int-after/keycode_events.snapshot @@ -0,0 +1,10 @@ +td_binding_pressed: 2 created new tap dance +td_binding_pressed: 2 tap dance pressed +td_binding_released: 2 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 1 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/3c-tap-int-after/native_posix.keymap b/app/tests/tap-dance/3c-tap-int-after/native_posix.keymap new file mode 100644 index 00000000..e1b6d979 --- /dev/null +++ b/app/tests/tap-dance/3c-tap-int-after/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/3d-hold-int-mid/events.patterns b/app/tests/tap-dance/3d-hold-int-mid/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/3d-hold-int-mid/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/3d-hold-int-mid/keycode_events.snapshot b/app/tests/tap-dance/3d-hold-int-mid/keycode_events.snapshot new file mode 100644 index 00000000..7631c4ac --- /dev/null +++ b/app/tests/tap-dance/3d-hold-int-mid/keycode_events.snapshot @@ -0,0 +1,10 @@ +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 1 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 0 tap dance keybind released +kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/3d-hold-int-mid/native_posix.keymap b/app/tests/tap-dance/3d-hold-int-mid/native_posix.keymap new file mode 100644 index 00000000..55a98d36 --- /dev/null +++ b/app/tests/tap-dance/3d-hold-int-mid/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,400) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/3e-hold-int-seq/events.patterns b/app/tests/tap-dance/3e-hold-int-seq/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/3e-hold-int-seq/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/3e-hold-int-seq/keycode_events.snapshot b/app/tests/tap-dance/3e-hold-int-seq/keycode_events.snapshot new file mode 100644 index 00000000..ca13f8bc --- /dev/null +++ b/app/tests/tap-dance/3e-hold-int-seq/keycode_events.snapshot @@ -0,0 +1,10 @@ +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 0 tap dance keybind released +kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 1 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/3e-hold-int-seq/native_posix.keymap b/app/tests/tap-dance/3e-hold-int-seq/native_posix.keymap new file mode 100644 index 00000000..b31e92dc --- /dev/null +++ b/app/tests/tap-dance/3e-hold-int-seq/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,400) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/3f-hold-int-after/events.patterns b/app/tests/tap-dance/3f-hold-int-after/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/3f-hold-int-after/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/3f-hold-int-after/keycode_events.snapshot b/app/tests/tap-dance/3f-hold-int-after/keycode_events.snapshot new file mode 100644 index 00000000..044018e0 --- /dev/null +++ b/app/tests/tap-dance/3f-hold-int-after/keycode_events.snapshot @@ -0,0 +1,10 @@ +td_binding_pressed: 0 created new tap dance +td_binding_pressed: 0 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 0 tap dance keybind released +kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 1 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/3f-hold-int-after/native_posix.keymap b/app/tests/tap-dance/3f-hold-int-after/native_posix.keymap new file mode 100644 index 00000000..6397fbb3 --- /dev/null +++ b/app/tests/tap-dance/3f-hold-int-after/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,400) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/4a-single/events.patterns b/app/tests/tap-dance/4a-single/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/4a-single/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/4a-single/keycode_events.snapshot b/app/tests/tap-dance/4a-single/keycode_events.snapshot new file mode 100644 index 00000000..6d60e842 --- /dev/null +++ b/app/tests/tap-dance/4a-single/keycode_events.snapshot @@ -0,0 +1,5 @@ +td_binding_pressed: 1 created new tap dance +td_binding_pressed: 1 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 1 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x16 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/4a-single/native_posix.keymap b/app/tests/tap-dance/4a-single/native_posix.keymap new file mode 100644 index 00000000..348a6827 --- /dev/null +++ b/app/tests/tap-dance/4a-single/native_posix.keymap @@ -0,0 +1,11 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/5a-tdint-mid/events.patterns b/app/tests/tap-dance/5a-tdint-mid/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/5a-tdint-mid/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/5a-tdint-mid/keycode_events.snapshot b/app/tests/tap-dance/5a-tdint-mid/keycode_events.snapshot new file mode 100644 index 00000000..301dc914 --- /dev/null +++ b/app/tests/tap-dance/5a-tdint-mid/keycode_events.snapshot @@ -0,0 +1,10 @@ +td_binding_pressed: 2 created new tap dance +td_binding_pressed: 2 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 3 created new tap dance +td_binding_pressed: 3 tap dance pressed +td_binding_released: 3 tap dance keybind released +td_binding_released: 2 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/5a-tdint-mid/native_posix.keymap b/app/tests/tap-dance/5a-tdint-mid/native_posix.keymap new file mode 100644 index 00000000..2188fd02 --- /dev/null +++ b/app/tests/tap-dance/5a-tdint-mid/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(1,1,10) + ZMK_MOCK_RELEASE(1,0,200) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/5b-tdint-seq/events.patterns b/app/tests/tap-dance/5b-tdint-seq/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/5b-tdint-seq/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/5b-tdint-seq/keycode_events.snapshot b/app/tests/tap-dance/5b-tdint-seq/keycode_events.snapshot new file mode 100644 index 00000000..567ec079 --- /dev/null +++ b/app/tests/tap-dance/5b-tdint-seq/keycode_events.snapshot @@ -0,0 +1,10 @@ +td_binding_pressed: 2 created new tap dance +td_binding_pressed: 2 tap dance pressed +kp_pressed: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 3 created new tap dance +td_binding_pressed: 3 tap dance pressed +td_binding_released: 2 tap dance keybind released +kp_released: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +td_binding_released: 3 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/5b-tdint-seq/native_posix.keymap b/app/tests/tap-dance/5b-tdint-seq/native_posix.keymap new file mode 100644 index 00000000..320b7199 --- /dev/null +++ b/app/tests/tap-dance/5b-tdint-seq/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(1,1,200) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/5c-tdint-after/events.patterns b/app/tests/tap-dance/5c-tdint-after/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/5c-tdint-after/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/5c-tdint-after/keycode_events.snapshot b/app/tests/tap-dance/5c-tdint-after/keycode_events.snapshot new file mode 100644 index 00000000..cc1da902 --- /dev/null +++ b/app/tests/tap-dance/5c-tdint-after/keycode_events.snapshot @@ -0,0 +1,10 @@ +td_binding_pressed: 2 created new tap dance +td_binding_pressed: 2 tap dance pressed +td_binding_released: 2 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 3 created new tap dance +td_binding_pressed: 3 tap dance pressed +td_binding_released: 3 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/5c-tdint-after/native_posix.keymap b/app/tests/tap-dance/5c-tdint-after/native_posix.keymap new file mode 100644 index 00000000..17e538bd --- /dev/null +++ b/app/tests/tap-dance/5c-tdint-after/native_posix.keymap @@ -0,0 +1,13 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(1,1,200) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/5d-tdint-multiple/events.patterns b/app/tests/tap-dance/5d-tdint-multiple/events.patterns new file mode 100644 index 00000000..1768fc21 --- /dev/null +++ b/app/tests/tap-dance/5d-tdint-multiple/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tap_dance_binding/td_binding/p \ No newline at end of file diff --git a/app/tests/tap-dance/5d-tdint-multiple/keycode_events.snapshot b/app/tests/tap-dance/5d-tdint-multiple/keycode_events.snapshot new file mode 100644 index 00000000..afb32824 --- /dev/null +++ b/app/tests/tap-dance/5d-tdint-multiple/keycode_events.snapshot @@ -0,0 +1,15 @@ +td_binding_pressed: 2 created new tap dance +td_binding_pressed: 2 tap dance pressed +td_binding_released: 2 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 3 created new tap dance +td_binding_pressed: 3 tap dance pressed +td_binding_released: 3 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +td_binding_pressed: 2 created new tap dance +td_binding_pressed: 2 tap dance pressed +td_binding_released: 2 tap dance keybind released +kp_pressed: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x1e implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tap-dance/5d-tdint-multiple/native_posix.keymap b/app/tests/tap-dance/5d-tdint-multiple/native_posix.keymap new file mode 100644 index 00000000..150f6d05 --- /dev/null +++ b/app/tests/tap-dance/5d-tdint-multiple/native_posix.keymap @@ -0,0 +1,15 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(1,1,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,200) + >; +}; \ No newline at end of file diff --git a/app/tests/tap-dance/behavior_keymap.dtsi b/app/tests/tap-dance/behavior_keymap.dtsi new file mode 100644 index 00000000..5e95cd50 --- /dev/null +++ b/app/tests/tap-dance/behavior_keymap.dtsi @@ -0,0 +1,60 @@ +#include +#include +#include + +/ { + behaviors { + ht: hold_tap { + compatible = "zmk,behavior-hold-tap"; + label = "HOLD_TAP"; + #binding-cells = <2>; + tapping-term-ms = <200>; + quick_tap_ms = <0>; + flavor = "tap-preferred"; + bindings = <&kp>, <&kp>; + }; + + tdm: tap_dance_mixed { + compatible = "zmk,behavior-tap-dance"; + label = "TAP_DANCE_MOD"; + #binding-cells = <0>; + tapping-term-ms = <200>; + bindings = <&ht LSHIFT A>, <&ht LALT B>, <&ht LGUI C>; + }; + + tdb: tap_dance_basic { + compatible = "zmk,behavior-tap-dance"; + label = "TAP_DANCE_BASIC"; + #binding-cells = <0>; + tapping-term-ms = <200>; + bindings = <&kp N1>, <&kp N2>, <&kp N3>; + }; + + td2: tap_dance_basic_2 { + compatible = "zmk,behavior-tap-dance"; + label = "TAP_DANCE_BASIC_2"; + #binding-cells = <0>; + tapping-term-ms = <200>; + bindings = <&kp A>, <&kp B>, <&kp C>; + }; + + tds: tap_dance_single { + compatible = "zmk,behavior-tap-dance"; + label = "TAP_DANCE_SINGlE"; + #binding-cells = <0>; + tapping-term-ms = <200>; + bindings = <&kp S>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &tdm &tds + &tdb &td2>; + }; + }; +}; diff --git a/docs/docs/assets/tap-dance/timing_diagram.svg b/docs/docs/assets/tap-dance/timing_diagram.svg new file mode 100644 index 00000000..ab02dcaf --- /dev/null +++ b/docs/docs/assets/tap-dance/timing_diagram.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/behaviors/tap-dance.md b/docs/docs/behaviors/tap-dance.md new file mode 100644 index 00000000..af49ca3c --- /dev/null +++ b/docs/docs/behaviors/tap-dance.md @@ -0,0 +1,76 @@ +--- +title: Tap-Dance Behavior +sidebar_label: Tap-Dance +--- + +## Summary + +A tap-dance key invokes a different behavior (e.g. `kp`) corresponding +to how many times it is pressed. For example, you could configure a +tap-dance key that acts as `LSHIFT` if tapped once, or Caps _Lock_ if tapped twice. +The expandability of the number of [`bindings`](#bindings) attached to a +particular tap-dance is a great way to add more functionality to a single key, +especially for keyboards with a limited number of keys. +Tap-dances are completely custom, so for every unique tap-dance key, +a new tap-dance must be defined in your keymap's `behaviors`. + +Tap-dances are designed to resolve immediately when interrupted by another keypress. +Meaning, when a keybind is pressed other than any active tap-dances, +the tap-dance will activate according to the current value of its +counter before the interrupting keybind is registered. + +### Configuration + +#### `tapping-term-ms` + +Defines the maximum elapsed time after the last tap-dance keybind press +before a binding is selected from [`bindings`](#bindings). +Default value is `200`ms. + +#### `bindings` + +An array of one or more keybinds. This list can include [any ZMK keycode](../codes/) and bindings for ZMK behaviors. + +#### Example Usage + +This example configures a tap-dance named `td0` that outputs the number of times it is pressed from 1-3. + +``` +#include +#include + +/ { + behaviors { + td0: tap_dance_0 { + compatible = "zmk,behavior-tap-dance"; + label = "TAP_DANCE_0"; + #binding-cells = <0>; + tapping-term-ms = <200>; + bindings = <&kp N1>, <&kp N2>, <&kp N3>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &td0 + >; + }; + }; +}; +``` + +The following image describes the behavior of this particular tap-dance. + +![Timing Diagram](../assets/tap-dance/timing_diagram.svg) + +:::note +Alphanumeric [`key press`](key-press.md) bindings, like those used for `td0`, +will release as soon as an interrupting key press occurs. +For instance, if a modifier key like `LSHIFT` were to replace the `N1` +binding in the last example above, it would remain pressed until `td0`'s +binding is released and the output would instead be `J`. Any following +alphanumeric key presses would be capitalized as long as `td0` is held down. +::: diff --git a/docs/sidebars.js b/docs/sidebars.js index 647399a7..7db3e055 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -28,6 +28,7 @@ module.exports = { "behaviors/mod-morph", "behaviors/sticky-key", "behaviors/sticky-layer", + "behaviors/tap-dance", "behaviors/caps-word", "behaviors/key-repeat", "behaviors/reset", From 58c7c0ee0c252be7b1d6b03f432c17cde34e9908 Mon Sep 17 00:00:00 2001 From: Kurtis Lew Date: Wed, 16 Mar 2022 08:06:55 -0700 Subject: [PATCH 08/10] feat(docs): Add tap-dance to feature matrix --- docs/docs/intro.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/intro.md b/docs/docs/intro.md index b8a6316c..39b53245 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -23,6 +23,7 @@ ZMK is currently missing some features found in other popular firmware. This tab | Split Keyboard Support | ✅ | ✅ | ✅ | | [Keymaps and Layers](behaviors/layers.md) | ✅ | ✅ | ✅ | | [Hold-Tap](behaviors/hold-tap.md) (which includes [Mod-Tap](behaviors/mod-tap.md) and [Layer-Tap](behaviors/layers.md/#layer-tap)) | ✅ | ✅ | ✅ | +| [Tap-Dance](behaviors/tap-dance.md) | ✅ | ✅[^3] | ✅ | | [Keyboard Codes](codes/index.mdx#keyboard) | ✅ | ✅ | ✅ | | [Media](codes/index.mdx#media-controls) & [Consumer](codes/index.mdx#consumer-controls) Codes | ✅ | ✅ | ✅ | | [Encoders](features/encoders.md)[^1] | ✅ | ✅ | ✅ | @@ -42,6 +43,7 @@ ZMK is currently missing some features found in other popular firmware. This tab | AVR/8 Bit | | | ✅ | | [Wide Range of ARM Chips Supported](https://docs.zephyrproject.org/latest/boards/index.html) | ✅ | | | +[^3]: Tap-Dances are limited to single and double-tap on BlueMicro [^2]: Encoders are not currently supported on peripheral side splits. [^1]: OLEDs are currently proof of concept in ZMK. From 8d8ba7d9b3e737c3ac254537840437550ffd5872 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Sat, 12 Mar 2022 15:15:39 +0000 Subject: [PATCH 09/10] feat(behaviors): Add macro support. * Fine grainted press/release/tap actions. * TIming between actions can be controlled. * Processed async, to avoid blocking. --- app/CMakeLists.txt | 2 + app/Kconfig | 8 + app/dts/behaviors.dtsi | 1 + app/dts/behaviors/macros.dtsi | 45 +++++ .../behaviors/zmk,behavior-macro.yaml | 21 ++ .../macros/zmk,macro-control-mode-press.yaml | 8 + .../zmk,macro-control-mode-release.yaml | 8 + .../macros/zmk,macro-control-mode-tap.yaml | 8 + .../macros/zmk,macro-control-tap-time.yaml | 8 + .../macros/zmk,macro-control-wait-time.yaml | 8 + .../macros/zmk,macro-pause-for-release.yaml | 8 + app/include/zmk/behavior_queue.h | 14 ++ app/src/behavior_queue.c | 70 +++++++ app/src/behaviors/behavior_macro.c | 187 ++++++++++++++++++ app/tests/macros/basic/events.patterns | 1 + .../macros/basic/keycode_events.snapshot | 6 + app/tests/macros/basic/native_posix.keymap | 14 ++ app/tests/macros/behavior_keymap.dtsi | 77 ++++++++ .../macros/press-mid-macro/events.patterns | 2 + .../press-mid-macro/keycode_events.snapshot | 10 + .../press-mid-macro/native_posix.keymap | 14 ++ .../macros/press-release/events.patterns | 1 + .../press-release/keycode_events.snapshot | 8 + .../macros/press-release/native_posix.keymap | 14 ++ .../macros/timing-override/events.patterns | 2 + .../timing-override/keycode_events.snapshot | 18 ++ .../timing-override/native_posix.keymap | 14 ++ .../macros/wait-macro-release/events.patterns | 1 + .../keycode_events.snapshot | 6 + .../wait-macro-release/native_posix.keymap | 14 ++ 30 files changed, 598 insertions(+) create mode 100644 app/dts/behaviors/macros.dtsi create mode 100644 app/dts/bindings/behaviors/zmk,behavior-macro.yaml create mode 100644 app/dts/bindings/macros/zmk,macro-control-mode-press.yaml create mode 100644 app/dts/bindings/macros/zmk,macro-control-mode-release.yaml create mode 100644 app/dts/bindings/macros/zmk,macro-control-mode-tap.yaml create mode 100644 app/dts/bindings/macros/zmk,macro-control-tap-time.yaml create mode 100644 app/dts/bindings/macros/zmk,macro-control-wait-time.yaml create mode 100644 app/dts/bindings/macros/zmk,macro-pause-for-release.yaml create mode 100644 app/include/zmk/behavior_queue.h create mode 100644 app/src/behavior_queue.c create mode 100644 app/src/behaviors/behavior_macro.c create mode 100644 app/tests/macros/basic/events.patterns create mode 100644 app/tests/macros/basic/keycode_events.snapshot create mode 100644 app/tests/macros/basic/native_posix.keymap create mode 100644 app/tests/macros/behavior_keymap.dtsi create mode 100644 app/tests/macros/press-mid-macro/events.patterns create mode 100644 app/tests/macros/press-mid-macro/keycode_events.snapshot create mode 100644 app/tests/macros/press-mid-macro/native_posix.keymap create mode 100644 app/tests/macros/press-release/events.patterns create mode 100644 app/tests/macros/press-release/keycode_events.snapshot create mode 100644 app/tests/macros/press-release/native_posix.keymap create mode 100644 app/tests/macros/timing-override/events.patterns create mode 100644 app/tests/macros/timing-override/keycode_events.snapshot create mode 100644 app/tests/macros/timing-override/native_posix.keymap create mode 100644 app/tests/macros/wait-macro-release/events.patterns create mode 100644 app/tests/macros/wait-macro-release/keycode_events.snapshot create mode 100644 app/tests/macros/wait-macro-release/native_posix.keymap diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index d853255c..0d547fbe 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -51,6 +51,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) target_sources(app PRIVATE src/behaviors/behavior_sticky_key.c) target_sources(app PRIVATE src/behaviors/behavior_caps_word.c) target_sources(app PRIVATE src/behaviors/behavior_key_repeat.c) + target_sources(app PRIVATE src/behaviors/behavior_macro.c) target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c) target_sources(app PRIVATE src/behaviors/behavior_mod_morph.c) target_sources(app PRIVATE src/behaviors/behavior_outputs.c) @@ -61,6 +62,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) 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/combo.c) + target_sources(app PRIVATE src/behavior_queue.c) target_sources(app PRIVATE src/conditional_layer.c) target_sources(app PRIVATE src/keymap.c) endif() diff --git a/app/Kconfig b/app/Kconfig index 1c9f929d..2cde34cc 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -410,6 +410,14 @@ config ZMK_COMBO_MAX_KEYS_PER_COMBO #Combo options endmenu +menu "Behavior Options" + +config ZMK_BEHAVIORS_QUEUE_SIZE + int "Maximum number of behaviors to allow queueing from a macro or other complex behavior" + default 20 + +endmenu + menu "Advanced" menu "Initialization Priorities" diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index 3e797cc9..c1dacbcd 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -17,3 +17,4 @@ #include #include #include +#include \ No newline at end of file diff --git a/app/dts/behaviors/macros.dtsi b/app/dts/behaviors/macros.dtsi new file mode 100644 index 00000000..37b364de --- /dev/null +++ b/app/dts/behaviors/macros.dtsi @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + + / { + behaviors { + macro_tap: macro_control_mode_tap { + compatible = "zmk,macro-control-mode-tap"; + label = "MAC_TAP"; + #binding-cells = <0>; + }; + + macro_press: macro_control_mode_press { + compatible = "zmk,macro-control-mode-press"; + label = "MAC_PRESS"; + #binding-cells = <0>; + }; + + macro_release: macro_control_mode_release { + compatible = "zmk,macro-control-mode-release"; + label = "MAC_REL"; + #binding-cells = <0>; + }; + + macro_tap_time: macro_control_tap_time { + compatible = "zmk,macro-control-tap-time"; + label = "MAC_TAP_TIME"; + #binding-cells = <1>; + }; + + macro_wait_time: macro_control_wait_time { + compatible = "zmk,macro-control-wait-time"; + label = "MAC_WAIT_TIME"; + #binding-cells = <1>; + }; + + macro_pause_for_release: macro_pause_for_release { + compatible = "zmk,macro-pause-for-release"; + label = "MAC_WAIT_REL"; + #binding-cells = <0>; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-macro.yaml b/app/dts/bindings/behaviors/zmk,behavior-macro.yaml new file mode 100644 index 00000000..ba5bcb07 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-macro.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Macro Behavior + +compatible: "zmk,behavior-macro" + +include: zero_param.yaml + +properties: + bindings: + type: phandle-array + required: true + wait-ms: + type: int + default: 100 + description: The default time to wait (in milliseconds) before triggering the next behavior in the macro bindings list. + tap-ms: + type: int + default: 100 + description: The default time to wait (in milliseconds) between the press and release events on a tapped macro behavior binding \ No newline at end of file diff --git a/app/dts/bindings/macros/zmk,macro-control-mode-press.yaml b/app/dts/bindings/macros/zmk,macro-control-mode-press.yaml new file mode 100644 index 00000000..64b3939b --- /dev/null +++ b/app/dts/bindings/macros/zmk,macro-control-mode-press.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Set Macro To Press Mode + +compatible: "zmk,macro-control-mode-press" + +include: zero_param.yaml \ No newline at end of file diff --git a/app/dts/bindings/macros/zmk,macro-control-mode-release.yaml b/app/dts/bindings/macros/zmk,macro-control-mode-release.yaml new file mode 100644 index 00000000..c1c27882 --- /dev/null +++ b/app/dts/bindings/macros/zmk,macro-control-mode-release.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Set Macro To Release Mode + +compatible: "zmk,macro-control-mode-release" + +include: zero_param.yaml \ No newline at end of file diff --git a/app/dts/bindings/macros/zmk,macro-control-mode-tap.yaml b/app/dts/bindings/macros/zmk,macro-control-mode-tap.yaml new file mode 100644 index 00000000..dde32c91 --- /dev/null +++ b/app/dts/bindings/macros/zmk,macro-control-mode-tap.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Set Macro To Tap Mode + +compatible: "zmk,macro-control-mode-tap" + +include: zero_param.yaml diff --git a/app/dts/bindings/macros/zmk,macro-control-tap-time.yaml b/app/dts/bindings/macros/zmk,macro-control-tap-time.yaml new file mode 100644 index 00000000..8dacdc2a --- /dev/null +++ b/app/dts/bindings/macros/zmk,macro-control-tap-time.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Set Macro Tap Duration + +compatible: "zmk,macro-control-tap-time" + +include: one_param.yaml \ No newline at end of file diff --git a/app/dts/bindings/macros/zmk,macro-control-wait-time.yaml b/app/dts/bindings/macros/zmk,macro-control-wait-time.yaml new file mode 100644 index 00000000..9e9beac2 --- /dev/null +++ b/app/dts/bindings/macros/zmk,macro-control-wait-time.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Set Macro Wait Duration + +compatible: "zmk,macro-control-wait-time" + +include: one_param.yaml \ No newline at end of file diff --git a/app/dts/bindings/macros/zmk,macro-pause-for-release.yaml b/app/dts/bindings/macros/zmk,macro-pause-for-release.yaml new file mode 100644 index 00000000..e89d8b24 --- /dev/null +++ b/app/dts/bindings/macros/zmk,macro-pause-for-release.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Macro Pause Until Release Marker + +compatible: "zmk,macro-pause-for-release" + +include: zero_param.yaml \ No newline at end of file diff --git a/app/include/zmk/behavior_queue.h b/app/include/zmk/behavior_queue.h new file mode 100644 index 00000000..8a184e4a --- /dev/null +++ b/app/include/zmk/behavior_queue.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include + +int zmk_behavior_queue_add(uint32_t position, const struct zmk_behavior_binding behavior, + bool press, uint32_t wait); diff --git a/app/src/behavior_queue.c b/app/src/behavior_queue.c new file mode 100644 index 00000000..bba98d45 --- /dev/null +++ b/app/src/behavior_queue.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +struct q_item { + uint32_t position; + struct zmk_behavior_binding binding; + bool press; + uint32_t wait; +}; + +K_MSGQ_DEFINE(zmk_behavior_queue_msgq, sizeof(struct q_item), CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE, 4); + +static void behavior_queue_process_next(struct k_work *work); +static K_DELAYED_WORK_DEFINE(queue_work, behavior_queue_process_next); + +static void behavior_queue_process_next(struct k_work *work) { + struct q_item item = {.wait = 0}; + int ret; + + while (k_msgq_get(&zmk_behavior_queue_msgq, &item, K_NO_WAIT) == 0) { + LOG_DBG("Invoking %s: 0x%02x 0x%02x", log_strdup(item.binding.behavior_dev), + item.binding.param1, item.binding.param2); + + struct zmk_behavior_binding_event event = {.position = item.position, + .timestamp = k_uptime_get()}; + + if (item.press) { + behavior_keymap_binding_pressed(&item.binding, event); + } else { + behavior_keymap_binding_released(&item.binding, event); + } + + LOG_DBG("Processing next queued behavior in %dms", item.wait); + + if (item.wait > 0) { + k_delayed_work_submit(&queue_work, K_MSEC(item.wait)); + break; + } + } +} + +int zmk_behavior_queue_add(uint32_t position, const struct zmk_behavior_binding binding, bool press, + uint32_t wait) { + struct q_item item = {.press = press, .binding = binding, .wait = wait}; + int ret; + + LOG_DBG(""); + + ret = k_msgq_put(&zmk_behavior_queue_msgq, &item, K_NO_WAIT); + if (ret < 0) { + return ret; + } + + if (!k_delayed_work_pending(&queue_work)) { + k_delayed_work_submit(&queue_work, K_NO_WAIT); + } + + return 0; +} diff --git a/app/src/behaviors/behavior_macro.c b/app/src/behaviors/behavior_macro.c new file mode 100644 index 00000000..12a2be15 --- /dev/null +++ b/app/src/behaviors/behavior_macro.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_macro + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +enum behavior_macro_mode { + MACRO_MODE_TAP, + MACRO_MODE_PRESS, + MACRO_MODE_RELEASE, +}; + +struct behavior_macro_trigger_state { + uint32_t wait_ms; + uint32_t tap_ms; + enum behavior_macro_mode mode; + uint8_t start_index; + uint8_t count; +}; + +struct behavior_macro_state { + struct behavior_macro_trigger_state release_state; + + uint8_t press_bindings_count; +}; + +struct behavior_macro_config { + uint32_t default_wait_ms; + uint32_t default_tap_ms; + uint8_t count; + struct zmk_behavior_binding bindings[]; +}; + +#define TAP_MODE DT_LABEL(DT_INST(0, zmk_macro_control_mode_tap)) +#define PRESS_MODE DT_LABEL(DT_INST(0, zmk_macro_control_mode_press)) +#define REL_MODE DT_LABEL(DT_INST(0, zmk_macro_control_mode_release)) + +#define TAP_TIME DT_LABEL(DT_INST(0, zmk_macro_control_tap_time)) +#define WAIT_TIME DT_LABEL(DT_INST(0, zmk_macro_control_wait_time)) +#define WAIT_REL DT_LABEL(DT_INST(0, zmk_macro_pause_for_release)) + +#define ZM_IS_NODE_MATCH(a, b) (strcmp(a, b) == 0) +#define IS_TAP_MODE(dev) ZM_IS_NODE_MATCH(dev, TAP_MODE) +#define IS_PRESS_MODE(dev) ZM_IS_NODE_MATCH(dev, PRESS_MODE) +#define IS_RELEASE_MODE(dev) ZM_IS_NODE_MATCH(dev, REL_MODE) + +#define IS_TAP_TIME(dev) ZM_IS_NODE_MATCH(dev, TAP_TIME) +#define IS_WAIT_TIME(dev) ZM_IS_NODE_MATCH(dev, WAIT_TIME) +#define IS_PAUSE(dev) ZM_IS_NODE_MATCH(dev, WAIT_REL) + +static bool handle_control_binding(struct behavior_macro_trigger_state *state, + const struct zmk_behavior_binding *binding) { + if (IS_TAP_MODE(binding->behavior_dev)) { + state->mode = MACRO_MODE_TAP; + LOG_DBG("macro mode set: tap"); + } else if (IS_PRESS_MODE(binding->behavior_dev)) { + state->mode = MACRO_MODE_PRESS; + LOG_DBG("macro mode set: press"); + } else if (IS_RELEASE_MODE(binding->behavior_dev)) { + state->mode = MACRO_MODE_RELEASE; + LOG_DBG("macro mode set: release"); + } else if (IS_TAP_TIME(binding->behavior_dev)) { + state->tap_ms = binding->param1; + LOG_DBG("macro tap time set: %d", state->tap_ms); + } else if (IS_WAIT_TIME(binding->behavior_dev)) { + state->wait_ms = binding->param1; + LOG_DBG("macro wait time set: %d", state->wait_ms); + } else { + return false; + } + + return true; +} + +static int behavior_macro_init(const struct device *dev) { + const struct behavior_macro_config *cfg = dev->config; + struct behavior_macro_state *state = dev->data; + state->press_bindings_count = cfg->count; + state->release_state.start_index = cfg->count; + state->release_state.count = 0; + + LOG_DBG("Precalculate initial release state:"); + for (int i = 0; i < cfg->count; i++) { + if (handle_control_binding(&state->release_state, &cfg->bindings[i])) { + // Updated state used for initial state on release. + } else if (IS_PAUSE(cfg->bindings[i].behavior_dev)) { + state->release_state.start_index = i + 1; + state->release_state.count = cfg->count - i - 1; + state->press_bindings_count = i; + LOG_DBG("Release will resume at %d", state->release_state.start_index); + break; + } else { + // Ignore regular invokable bindings + } + } + + return 0; +}; + +static void queue_macro(uint32_t position, const struct zmk_behavior_binding bindings[], + struct behavior_macro_trigger_state state) { + LOG_DBG("Iterating macro bindings from %d-%d", state.start_index, state.count); + for (int i = state.start_index; i < state.start_index + state.count; i++) { + if (!handle_control_binding(&state, &bindings[i])) { + switch (state.mode) { + case MACRO_MODE_TAP: + zmk_behavior_queue_add(position, bindings[i], true, state.tap_ms); + zmk_behavior_queue_add(position, bindings[i], false, state.wait_ms); + break; + case MACRO_MODE_PRESS: + zmk_behavior_queue_add(position, bindings[i], true, state.wait_ms); + break; + case MACRO_MODE_RELEASE: + zmk_behavior_queue_add(position, bindings[i], false, state.wait_ms); + break; + default: + LOG_ERR("Unknown macro mode: %d", state.mode); + break; + } + } + } +} + +static int on_macro_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct behavior_macro_config *cfg = dev->config; + struct behavior_macro_state *state = dev->data; + struct behavior_macro_trigger_state trigger_state = {.mode = MACRO_MODE_TAP, + .tap_ms = cfg->default_tap_ms, + .wait_ms = cfg->default_wait_ms, + .start_index = 0, + .count = state->press_bindings_count}; + + queue_macro(event.position, cfg->bindings, trigger_state); + + return ZMK_BEHAVIOR_OPAQUE; +} + +static int on_macro_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct behavior_macro_config *cfg = dev->config; + struct behavior_macro_state *state = dev->data; + + queue_macro(event.position, cfg->bindings, state->release_state); + + return ZMK_BEHAVIOR_OPAQUE; +} + +static const struct behavior_driver_api behavior_macro_driver_api = { + .binding_pressed = on_macro_binding_pressed, + .binding_released = on_macro_binding_released, +}; + +#define BINDING_WITH_COMMA(idx, drv_inst) ZMK_KEYMAP_EXTRACT_BINDING(idx, DT_DRV_INST(drv_inst)), + +#define TRANSFORMED_BEHAVIORS(n) \ + {UTIL_LISTIFY(DT_PROP_LEN(DT_DRV_INST(n), bindings), BINDING_WITH_COMMA, n)}, + +#define MACRO_INST(n) \ + static struct behavior_macro_state behavior_macro_state_##n = {}; \ + static struct behavior_macro_config behavior_macro_config_##n = { \ + .default_wait_ms = DT_INST_PROP_OR(drv_inst, wait_ms, 100), \ + .default_tap_ms = DT_INST_PROP_OR(drv_inst, tap_ms, 100), \ + .count = DT_INST_PROP_LEN(n, bindings), \ + .bindings = TRANSFORMED_BEHAVIORS(n)}; \ + DEVICE_DT_INST_DEFINE(n, behavior_macro_init, device_pm_control_nop, \ + &behavior_macro_state_##n, &behavior_macro_config_##n, APPLICATION, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_macro_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(MACRO_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/tests/macros/basic/events.patterns b/app/tests/macros/basic/events.patterns new file mode 100644 index 00000000..3c9d3f83 --- /dev/null +++ b/app/tests/macros/basic/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode/kp/p \ No newline at end of file diff --git a/app/tests/macros/basic/keycode_events.snapshot b/app/tests/macros/basic/keycode_events.snapshot new file mode 100644 index 00000000..9fe52e6d --- /dev/null +++ b/app/tests/macros/basic/keycode_events.snapshot @@ -0,0 +1,6 @@ +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/macros/basic/native_posix.keymap b/app/tests/macros/basic/native_posix.keymap new file mode 100644 index 00000000..6a2391db --- /dev/null +++ b/app/tests/macros/basic/native_posix.keymap @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = ; +}; \ No newline at end of file diff --git a/app/tests/macros/behavior_keymap.dtsi b/app/tests/macros/behavior_keymap.dtsi new file mode 100644 index 00000000..6860f2d5 --- /dev/null +++ b/app/tests/macros/behavior_keymap.dtsi @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include + +/ { + macros { + abc_macro: abc_macro { + label = "ABCs"; + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings = <&kp A &kp B &kp C>; + }; + + hold_shift_macro: hold_shift_macro { + label = "HOLD_SHFT"; + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings + = <¯o_press &kp LSHFT> + , <¯o_tap> + , <&kp D &kp O &kp G> + , <¯o_release &kp LSHFT> + ; + }; + + custom_timing: custom_timing_macro { + label = "ABC_TIMING"; + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings + = <¯o_wait_time 50> + , <&kp A> + , <¯o_tap_time 20> + , <&kp B &kp C> + ; + }; + + dual_sequence_macro: dual_sequence_macro { + label = "DUAL_SEQ"; + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + wait-ms = <10>; + bindings + = <¯o_press &kp LALT> + , <¯o_tap> + , <&kp TAB> + , <¯o_pause_for_release> + , <¯o_release &kp LALT> + ; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &abc_macro &mo 1 + &hold_shift_macro &custom_timing>; + }; + + extra_layer { + bindings = < + &dual_sequence_macro &trans + &kp TAB &none>; + + }; + + }; +}; diff --git a/app/tests/macros/press-mid-macro/events.patterns b/app/tests/macros/press-mid-macro/events.patterns new file mode 100644 index 00000000..cedbda8b --- /dev/null +++ b/app/tests/macros/press-mid-macro/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*keymap_apply_position_state/pos_state/p \ No newline at end of file diff --git a/app/tests/macros/press-mid-macro/keycode_events.snapshot b/app/tests/macros/press-mid-macro/keycode_events.snapshot new file mode 100644 index 00000000..4fad9154 --- /dev/null +++ b/app/tests/macros/press-mid-macro/keycode_events.snapshot @@ -0,0 +1,10 @@ +pos_state: layer: 0 position: 0, binding name: ABCs +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +pos_state: layer: 0 position: 0, binding name: ABCs +pos_state: layer: 0 position: 1, binding name: MO +pos_state: layer: 0 position: 1, binding name: MO +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/macros/press-mid-macro/native_posix.keymap b/app/tests/macros/press-mid-macro/native_posix.keymap new file mode 100644 index 00000000..a075a443 --- /dev/null +++ b/app/tests/macros/press-mid-macro/native_posix.keymap @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = ; +}; \ No newline at end of file diff --git a/app/tests/macros/press-release/events.patterns b/app/tests/macros/press-release/events.patterns new file mode 100644 index 00000000..3c9d3f83 --- /dev/null +++ b/app/tests/macros/press-release/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode/kp/p \ No newline at end of file diff --git a/app/tests/macros/press-release/keycode_events.snapshot b/app/tests/macros/press-release/keycode_events.snapshot new file mode 100644 index 00000000..ebe69d5c --- /dev/null +++ b/app/tests/macros/press-release/keycode_events.snapshot @@ -0,0 +1,8 @@ +kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x12 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x12 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x0a implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x0a implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/macros/press-release/native_posix.keymap b/app/tests/macros/press-release/native_posix.keymap new file mode 100644 index 00000000..6814d542 --- /dev/null +++ b/app/tests/macros/press-release/native_posix.keymap @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = ; +}; \ No newline at end of file diff --git a/app/tests/macros/timing-override/events.patterns b/app/tests/macros/timing-override/events.patterns new file mode 100644 index 00000000..0a5f25ca --- /dev/null +++ b/app/tests/macros/timing-override/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*behavior_queue_process_next/queue_process_next/p \ No newline at end of file diff --git a/app/tests/macros/timing-override/keycode_events.snapshot b/app/tests/macros/timing-override/keycode_events.snapshot new file mode 100644 index 00000000..650d6747 --- /dev/null +++ b/app/tests/macros/timing-override/keycode_events.snapshot @@ -0,0 +1,18 @@ +queue_process_next: Invoking KEY_PRESS: 0x70004 0x00 +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 100ms +queue_process_next: Invoking KEY_PRESS: 0x70004 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 50ms +queue_process_next: Invoking KEY_PRESS: 0x70005 0x00 +kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 20ms +queue_process_next: Invoking KEY_PRESS: 0x70005 0x00 +kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 50ms +queue_process_next: Invoking KEY_PRESS: 0x70006 0x00 +kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 20ms +queue_process_next: Invoking KEY_PRESS: 0x70006 0x00 +kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +queue_process_next: Processing next queued behavior in 50ms diff --git a/app/tests/macros/timing-override/native_posix.keymap b/app/tests/macros/timing-override/native_posix.keymap new file mode 100644 index 00000000..343926a7 --- /dev/null +++ b/app/tests/macros/timing-override/native_posix.keymap @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = ; +}; \ No newline at end of file diff --git a/app/tests/macros/wait-macro-release/events.patterns b/app/tests/macros/wait-macro-release/events.patterns new file mode 100644 index 00000000..3c9d3f83 --- /dev/null +++ b/app/tests/macros/wait-macro-release/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode/kp/p \ No newline at end of file diff --git a/app/tests/macros/wait-macro-release/keycode_events.snapshot b/app/tests/macros/wait-macro-release/keycode_events.snapshot new file mode 100644 index 00000000..f8e7bd2f --- /dev/null +++ b/app/tests/macros/wait-macro-release/keycode_events.snapshot @@ -0,0 +1,6 @@ +kp_pressed: usage_page 0x07 keycode 0xe2 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x2b implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x2b implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x2b implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x2b implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0xe2 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/macros/wait-macro-release/native_posix.keymap b/app/tests/macros/wait-macro-release/native_posix.keymap new file mode 100644 index 00000000..6dabaeca --- /dev/null +++ b/app/tests/macros/wait-macro-release/native_posix.keymap @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = ; +}; \ No newline at end of file From 42e92e10d87981527b0b24950947d62772dea7b1 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Wed, 16 Mar 2022 04:09:10 +0000 Subject: [PATCH 10/10] feat(docs): Document the macro behavior. --- docs/docs/behaviors/macros.md | 136 ++++++++++++++++++++++++++++++++++ docs/docs/intro.md | 2 +- docs/sidebars.js | 1 + 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 docs/docs/behaviors/macros.md diff --git a/docs/docs/behaviors/macros.md b/docs/docs/behaviors/macros.md new file mode 100644 index 00000000..04e393ba --- /dev/null +++ b/docs/docs/behaviors/macros.md @@ -0,0 +1,136 @@ +--- +title: Macro Behavior +sidebar_label: Macros +--- + +## Summary + +The macro behavior allows configuring a list of other behaviors to invoke +when the macro is pressed, including support for completing part of the binding list +once the macro is released. + +## Macro Definition + +Each macro you want to use in your keymap gets defined first, then bound in your keymap. + +A macro definition looks like: + +``` +/ { + macros { + zed_em_kay: zmk_macro { + label = "ZMK_MAC"; + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings + = <¯o_press &kp LSHFT> + , <¯o_tap &kp Z &kp M &kp K> + , <¯o_release &kp LSHFT> + ; + }; + }; +}; +``` + +:::note +The text before the colon (`:`) in the declaration of the macro node is the "node label", and is the text +used to reference the macro in your keymap +::: + +The macro can then be bound in your keymap by referencing it by the label `&zed_em_kay`, e.g.: + +``` + raise_layer { + bindings = <&zed_em_kay>; + }; +``` + +### Bindings + +Like [hold-taps](/docs/behaviors/hold-tap), macros are created by composing other behaviors, and any of those behaviors can +be added to the `bindings` list, e.g.: + +``` +bindings + = <&to 1> + , <&bl BL_ON> + , <&kp Z &kp M &kp K &kp EXCLAMATION> + ; +``` + +## Macro Controls + +There are a set of special macro controls that can be included in the `bindings` list to modify the +way the macro is processed. + +### Binding Activation Mode + +Bindings in a macro are activated differently, depending on the current "activation mode" of the macro. + +Available modes: + +- Tap - The default mode; when in this mode, the macro will press, then release, each behavior in the `bindings` list. This mode is useful for + basic keycode output to hosts, i.e. when activating a `&kp` behavior. +- Press - In this mode, the macro processing will only trigger a press on the behaviors in the macro. This is useful for holding down modifiers for some duration of a macro, e.g. `&kp LALT`. +- Release - In this mode, the macro processing will only trigger a release on the behaviors in the macro. This is useful for releasing modifiers previously pressed earlier in the macro processing, e.g. `&kp LALT`. + +To modify the activation mode, macro controls can be added at any point in the bindings list. + +- `¯o_tap` +- `¯o_press` +- `¯o_release` + +A concrete example, used to hold a modifier, tap multiple keys, then release the modifier, would look like: + +``` +bindings + = <¯o_press &kp LSHFT> + , <¯o_tap &kp Z &kp M &kp K> + , <¯o_release &kp LSHFT> + ; +``` + +### Processing Continuation on Release + +The macro can be paused so that only part of the `bindings` list is processed when the macro is pressed, and the remainder is processed once +the macro itself is released. + +To pause the macro until release, use `¯o_pause_for_release`, like in this example that will press a modifier and activate a layer when the macro is pressed, and once the macro is released, release the modifier and deactivate the layer by releasing the `&mo`: + +``` +bindings + = <¯o_press &mo 1 &kp LSHFT> + , <¯o_wait_for_release> + , <¯o_release &mo 1 &kp LSHFT> + ; +``` + +### Wait Time + +The wait time setting controls how long of a delay is introduced between behaviors in the `bindings` list. The initial wait time for a macro, 100ms by default, can +be set by assigning a value to the `wait-ms` property of the macro, e.g. `wait-ms = <20>;`. If you want to update the wait time at any +point in the macro bindings list, use `¯o_wait_time`, e.g. `¯o_wait_time 30`. A full example: + +``` +wait-ms = <10>; +bindings + = <&kp F &kp A &kp S &kp T> + , <¯o_wait_time 500> + , <&kp S &kp L &kp O &kp W> + ; +``` + +### Tap Time + +The tap time setting controls how long a tapped behavior is held in the `bindings` list. The initial tap time for a macro, 100ms by default, can +be set by assigning a value to the `tap-ms` property of the macro, e.g. `tap-ms = <20>;`. If you want to update the tap time at any +point in a macro bindings list, use `¯o_tap_time`, e.g. `¯o_tap_time 30`. A full example: + +``` +bindings + = <¯o_tap_time 10> + , <&kp S &kp H &kp O &kp R &kp T> + , <¯o_tap_time 500> + , <&kp L &kp O &kp N &kp G> + ; +``` diff --git a/docs/docs/intro.md b/docs/docs/intro.md index 39b53245..91b4e21b 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -32,7 +32,7 @@ ZMK is currently missing some features found in other popular firmware. This tab | [Backlight](features/backlight.md) | ✅ | ✅ | ✅ | | One Shot Keys | ✅ | ✅ | ✅ | | [Combo Keys](features/combos.md) | ✅ | | ✅ | -| Macros | 🚧 | ✅ | ✅ | +| [Macros](behaviors/macros) | ✅ | ✅ | ✅ | | Mouse Keys | 🚧 | ✅ | ✅ | | Low Active Power Usage | ✅ | | | | Low Power Sleep States | ✅ | ✅ | | diff --git a/docs/sidebars.js b/docs/sidebars.js index 7db3e055..5f0055f4 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -25,6 +25,7 @@ module.exports = { "behaviors/misc", "behaviors/hold-tap", "behaviors/mod-tap", + "behaviors/macros", "behaviors/mod-morph", "behaviors/sticky-key", "behaviors/sticky-layer",