From 1dcb2f905cd953c732476eab092fae8d104b4af9 Mon Sep 17 00:00:00 2001 From: KemoNine Date: Wed, 3 Feb 2021 00:55:41 +0000 Subject: [PATCH] feat(combos): add layer filtering --- app/Kconfig | 4 + app/dts/bindings/zmk,combos.yaml | 3 + app/src/combo.c | 31 +++++++- .../combo/layer-filter-0/events.patterns | 2 + .../layer-filter-0/keycode_events.snapshot | 8 ++ .../combo/layer-filter-0/native_posix.keymap | 78 +++++++++++++++++++ .../combo/layer-filter-1/events.patterns | 2 + .../layer-filter-1/keycode_events.snapshot | 4 + .../combo/layer-filter-1/native_posix.keymap | 40 ++++++++++ docs/docs/features/combos.md | 3 + 10 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 app/tests/combo/layer-filter-0/events.patterns create mode 100644 app/tests/combo/layer-filter-0/keycode_events.snapshot create mode 100644 app/tests/combo/layer-filter-0/native_posix.keymap create mode 100644 app/tests/combo/layer-filter-1/events.patterns create mode 100644 app/tests/combo/layer-filter-1/keycode_events.snapshot create mode 100644 app/tests/combo/layer-filter-1/native_posix.keymap diff --git a/app/Kconfig b/app/Kconfig index aa6143ce..6454a848 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -301,6 +301,10 @@ config ZMK_COMBO_MAX_KEYS_PER_COMBO int "Maximum number of keys per combo" default 4 +config ZMK_COMBO_MAX_LAYER_FILTERS_PER_COMBO + int "Maximum number of layers that can be specified per combo" + default 4 + #Display/LED Options endmenu diff --git a/app/dts/bindings/zmk,combos.yaml b/app/dts/bindings/zmk,combos.yaml index 75eaa3e1..1a914a7f 100644 --- a/app/dts/bindings/zmk,combos.yaml +++ b/app/dts/bindings/zmk,combos.yaml @@ -20,3 +20,6 @@ child-binding: default: 50 slow-release: type: boolean + layers: + type: array + default: [-1] \ No newline at end of file diff --git a/app/src/combo.c b/app/src/combo.c index 82f6538f..176cad14 100644 --- a/app/src/combo.c +++ b/app/src/combo.c @@ -17,6 +17,7 @@ #include #include #include +#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -33,6 +34,8 @@ struct combo_cfg { // the virtual key position is a key position outside the range used by the keyboard. // it is necessary so hold-taps can uniquely identify a behavior. int32_t virtual_key_position; + int8_t layers[CONFIG_ZMK_COMBO_MAX_LAYER_FILTERS_PER_COMBO]; + int32_t layers_len; }; struct active_combo { @@ -104,17 +107,35 @@ static int initialize_combo(struct combo_cfg *new_combo) { return 0; } +static bool combo_active_on_layer(struct combo_cfg *combo, uint8_t layer) { + if (combo->layers[0] == -1) { + // -1 in the first layer position is global layer scope + return true; + } + for (int j = 0; j < combo->layers_len; j++) { + if (combo->layers[j] == layer) { + return true; + } + } + return false; +} + static int setup_candidates_for_first_keypress(int32_t position, int64_t timestamp) { + int number_of_combo_candidates = 0; + uint8_t highest_active_layer = zmk_keymap_highest_layer_active(); for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) { struct combo_cfg *combo = combo_lookup[position][i]; if (combo == NULL) { - return i; + return number_of_combo_candidates; + } + if (combo_active_on_layer(combo, highest_active_layer)) { + candidates[number_of_combo_candidates].combo = combo; + candidates[number_of_combo_candidates].timeout_at = timestamp + combo->timeout_ms; + number_of_combo_candidates++; } - candidates[i].combo = combo; - candidates[i].timeout_at = timestamp + combo->timeout_ms; // LOG_DBG("combo timeout %d %d %d", position, i, candidates[i].timeout_at); } - return CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; + return number_of_combo_candidates; } static int filter_candidates(int32_t position) { @@ -451,6 +472,8 @@ ZMK_SUBSCRIPTION(combo, zmk_position_state_changed); .behavior = KEY_BINDING_TO_STRUCT(0, n), \ .virtual_key_position = ZMK_KEYMAP_LEN + __COUNTER__, \ .slow_release = DT_PROP(n, slow_release), \ + .layers = DT_PROP(n, layers), \ + .layers_len = DT_PROP_LEN(n, layers), \ }; #define INITIALIZE_COMBO(n) initialize_combo(&combo_config_##n); diff --git a/app/tests/combo/layer-filter-0/events.patterns b/app/tests/combo/layer-filter-0/events.patterns new file mode 100644 index 00000000..b90d7863 --- /dev/null +++ b/app/tests/combo/layer-filter-0/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode_//p +s/.*combo//p \ No newline at end of file diff --git a/app/tests/combo/layer-filter-0/keycode_events.snapshot b/app/tests/combo/layer-filter-0/keycode_events.snapshot new file mode 100644 index 00000000..f845fd16 --- /dev/null +++ b/app/tests/combo/layer-filter-0/keycode_events.snapshot @@ -0,0 +1,8 @@ +pressed: usage_page 0x07 keycode 0x1b implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x1b 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 0x1c implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x1c 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/combo/layer-filter-0/native_posix.keymap b/app/tests/combo/layer-filter-0/native_posix.keymap new file mode 100644 index 00000000..aac330f9 --- /dev/null +++ b/app/tests/combo/layer-filter-0/native_posix.keymap @@ -0,0 +1,78 @@ +#include +#include +#include + +/* it is useful to set timeout to a large value when attaching a debugger. */ +#define TIMEOUT (60*60*1000) + +/ { + combos { + compatible = "zmk,combos"; + combo_one { + timeout-ms = ; + key-positions = <0 1>; + bindings = <&kp X>; + layers = <0>; + }; + + combo_two { + timeout-ms = ; + key-positions = <0 1>; + bindings = <&kp Y>; + layers = <1>; + }; + + combo_three { + timeout-ms = ; + key-positions = <0 2>; + bindings = <&kp Z>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &kp A &kp B + &kp C &tog 1 + >; + }; + + filtered_layer { + bindings = < + &kp A &kp B + &kp C &tog 0 + >; + }; + }; +}; + +&kscan { + events = < + /* Combo One */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + /* Combo Three */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(1,1,10) + /* Toggle Layer */ + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(1,1,10) + /* Combo Two */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + /* Combo Three */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(1,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/combo/layer-filter-1/events.patterns b/app/tests/combo/layer-filter-1/events.patterns new file mode 100644 index 00000000..b90d7863 --- /dev/null +++ b/app/tests/combo/layer-filter-1/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode_//p +s/.*combo//p \ No newline at end of file diff --git a/app/tests/combo/layer-filter-1/keycode_events.snapshot b/app/tests/combo/layer-filter-1/keycode_events.snapshot new file mode 100644 index 00000000..bb47d852 --- /dev/null +++ b/app/tests/combo/layer-filter-1/keycode_events.snapshot @@ -0,0 +1,4 @@ +pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/combo/layer-filter-1/native_posix.keymap b/app/tests/combo/layer-filter-1/native_posix.keymap new file mode 100644 index 00000000..995f27ee --- /dev/null +++ b/app/tests/combo/layer-filter-1/native_posix.keymap @@ -0,0 +1,40 @@ +#include +#include +#include + +/* it is useful to set timeout to a large value when attaching a debugger. */ +#define TIMEOUT (60*60*1000) + +/ { + combos { + compatible = "zmk,combos"; + combo_one { + timeout-ms = ; + key-positions = <0 1>; + bindings = <&kp X>; + layers = <1>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &kp A &kp B + &kp C &tog 1 + >; + }; + }; +}; + +&kscan { + events = < + /* Combo One */ + ZMK_MOCK_PRESS(0,0,10) + 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/docs/docs/features/combos.md b/docs/docs/features/combos.md index 845bb018..5fd33a46 100644 --- a/docs/docs/features/combos.md +++ b/docs/docs/features/combos.md @@ -18,6 +18,7 @@ Combos configured in your `.keymap` file, but are separate from the `keymap` nod timeout-ms = <50>; key-positions = <0 1>; bindings = <&kp ESC>; + layers = <-1>; }; }; }; @@ -27,6 +28,7 @@ Combos configured in your `.keymap` file, but are separate from the `keymap` nod - The `compatible` property should always be `"zmk,combos"` for combos. - `timeout-ms` is the number of milliseconds that all keys of the combo must be pressed. - `key-positions` is an array of key positions. See the info section below about how to figure out the positions on your board. +- `layers = <0 1...>` will allow limiting a combo to specific layers. this is an _optional_ parameter and defaults to `-1` which is global scope. - `bindings` is the behavior that is activated when the behavior is pressed. - (advanced) you can specify `slow-release` if you want the combo binding to be released when all key-positions are released. The default is to release the combo as soon as any of the keys in the combo is released. @@ -49,3 +51,4 @@ There are three global combo parameters which are set through KConfig. You can s - `CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS` is the number of combos that can be active at the same time. Default 4. - `CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY` is the maximum number of combos that can be active on a key position. Defaults to 5. (So you can have 5 separate combos that use position `3` for example) - `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` is the maximum number of keys that need to be pressed to activate a combo. Default 4. If you want a combo that triggers when pressing 5 keys, you'd set this to 5 for example. +- `ZMK_COMBO_MAX_LAYER_FILTERS_PER_COMBO` is the maximum number of layer filters that can be specified. Default 4. If you want use layer filters with a combo that spans more than 4 layers, you'll need to change this value.