diff --git a/app/Kconfig b/app/Kconfig index 4a68cce01..301874fbb 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -444,12 +444,16 @@ config ZMK_COMBO_MAX_PRESSED_COMBOS default 4 config ZMK_COMBO_MAX_COMBOS_PER_KEY - int "Maximum number of combos per key" - default 5 + int + default 0 + help + Deprecated: Storage for combos is now determined automatically config ZMK_COMBO_MAX_KEYS_PER_COMBO - int "Maximum number of keys per combo" - default 4 + int + default 0 + help + Deprecated: This is now auto-calculated based on `key-positions` in devicetree # Combo options endmenu diff --git a/app/dts/bindings/zmk,combos.yaml b/app/dts/bindings/zmk,combos.yaml index f146ab7a2..5ab0085dc 100644 --- a/app/dts/bindings/zmk,combos.yaml +++ b/app/dts/bindings/zmk,combos.yaml @@ -25,4 +25,3 @@ child-binding: type: boolean layers: type: array - default: [-1] diff --git a/app/src/combo.c b/app/src/combo.c index 2f547e0ee..f8a6fd3fb 100644 --- a/app/src/combo.c +++ b/app/src/combo.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -26,53 +27,99 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +#if CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO > 0 + +#warning \ + "CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO is deprecated, and is auto-calculated from the devicetree now." + +#endif + +#if CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY > 0 + +#warning "CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY is deprecated, and is auto-calculated." + +#endif + +#define COMBOS_KEYS_BYTE_ARRAY(node_id) \ + uint8_t _CONCAT(combo_prop_, node_id)[DT_PROP_LEN(node_id, key_positions)]; + +#define MAX_COMBO_KEYS sizeof(union {DT_INST_FOREACH_CHILD(0, COMBOS_KEYS_BYTE_ARRAY)}) + struct combo_cfg { - int32_t key_positions[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO]; - int32_t key_position_len; - struct zmk_behavior_binding behavior; + int32_t key_positions[MAX_COMBO_KEYS]; + int16_t key_position_len; + int16_t require_prior_idle_ms; int32_t timeout_ms; - int32_t require_prior_idle_ms; + uint32_t layer_mask; + struct zmk_behavior_binding behavior; // if slow release is set, the combo releases when the last key is released. // otherwise, the combo releases when the first key is released. bool slow_release; - // 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; - int32_t layers_len; - int8_t layers[]; }; struct active_combo { - const struct combo_cfg *combo; + uint16_t combo_idx; // key_positions_pressed is filled with key_positions when the combo is pressed. // The keys are removed from this array when they are released. // Once this array is empty, the behavior is released. - uint32_t key_positions_pressed_count; - struct zmk_position_state_changed_event - key_positions_pressed[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO]; + uint16_t key_positions_pressed_count; + struct zmk_position_state_changed_event key_positions_pressed[MAX_COMBO_KEYS]; }; -struct combo_candidate { - const struct combo_cfg *combo; - // the time after which this behavior should be removed from candidates. - // by keeping track of when the candidate should be cleared there is no - // possibility of accidental releases. - int64_t timeout_at; -}; +#define PROP_BIT_AT_IDX(n, prop, idx) BIT(DT_PROP_BY_IDX(n, prop, idx)) -uint32_t pressed_keys_count = 0; +#define NODE_PROP_BITMASK(n, prop) \ + COND_CODE_1(DT_NODE_HAS_PROP(n, prop), \ + (DT_FOREACH_PROP_ELEM_SEP(n, prop, PROP_BIT_AT_IDX, (|))), (0)) + +#define GET_KEY_POSITION_MASK_PORTION(idx, n) ((NODE_PROP_BITMASK(n, key_positions) >> idx) & 0xFF) + +#define COMBO_INST(n, positions) \ + COND_CODE_1(IS_EQ(DT_PROP_LEN(n, key_positions), positions), \ + ( \ + { \ + .timeout_ms = DT_PROP(n, timeout_ms), \ + .require_prior_idle_ms = DT_PROP(n, require_prior_idle_ms), \ + .key_positions = DT_PROP(n, key_positions), \ + .key_position_len = DT_PROP_LEN(n, key_positions), \ + .behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, n), \ + .slow_release = DT_PROP(n, slow_release), \ + .layer_mask = NODE_PROP_BITMASK(n, layers), \ + }, ), \ + ()) + +#define COMBO_CONFIGS_WITH_MATCHING_POSITIONS_LEN(positions, _ignore) \ + DT_INST_FOREACH_CHILD_VARGS(0, COMBO_INST, positions) + +// We do some magic here to generate the `combos` array by "key position length", looping +// by key position length and on each iteration, only include entries where the `key-positions` +// length matches. +// Doing so allows our bitmasks to be "shorted key positions list first" when searching for matches. +// `20` is chosen as a reasonable limit, since the theoretical maximum number of keys you might +// reasonably press simultaneously with 10 fingers is 20 keys, two keys per finger. +static const struct combo_cfg combos[] = { + LISTIFY(20, COMBO_CONFIGS_WITH_MATCHING_POSITIONS_LEN, (), 0)}; + +#define COMBO_ONE(n) +1 + +#define COMBO_CHILDREN_COUNT (0 DT_INST_FOREACH_CHILD(0, COMBO_ONE)) + +// We need at least 4 bytes to avoid alignment issues +#define BYTES_FOR_COMBOS_MASK DIV_ROUND_UP(COMBO_CHILDREN_COUNT, 32) + +uint8_t pressed_keys_count = 0; // set of keys pressed -struct zmk_position_state_changed_event pressed_keys[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO] = {}; +struct zmk_position_state_changed_event pressed_keys[MAX_COMBO_KEYS] = {}; // the set of candidate combos based on the currently pressed_keys -struct combo_candidate candidates[CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY]; +uint32_t candidates[BYTES_FOR_COMBOS_MASK]; // the last candidate that was completely pressed -const struct combo_cfg *fully_pressed_combo = NULL; +int16_t fully_pressed_combo = INT16_MAX; // a lookup dict that maps a key position to all combos on that position -const struct combo_cfg *combo_lookup[ZMK_KEYMAP_LEN][CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY] = {NULL}; +uint32_t combo_lookup[ZMK_KEYMAP_LEN][BYTES_FOR_COMBOS_MASK] = {}; // combos that have been activated and still have (some) keys pressed // this array is always contiguous from 0. -struct active_combo active_combos[CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS] = {NULL}; -int active_combo_count = 0; +struct active_combo active_combos[CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS] = {}; +uint8_t active_combo_count = 0; struct k_work_delayable timeout_task; int64_t timeout_task_timeout_at; @@ -90,52 +137,22 @@ static void store_last_tapped(int64_t timestamp) { // Store the combo key pointer in the combos array, one pointer for each key position // The combos are sorted shortest-first, then by virtual-key-position. -static int initialize_combo(const struct combo_cfg *new_combo) { - for (int i = 0; i < new_combo->key_position_len; i++) { - int32_t position = new_combo->key_positions[i]; - if (position >= ZMK_KEYMAP_LEN) { - LOG_ERR("Unable to initialize combo, key position %d does not exist", position); - return -EINVAL; - } +static int initialize_combo(size_t index) { + const struct combo_cfg *new_combo = &combos[index]; - const struct combo_cfg *insert_combo = new_combo; - bool set = false; - for (int j = 0; j < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; j++) { - const struct combo_cfg *combo_at_j = combo_lookup[position][j]; - if (combo_at_j == NULL) { - combo_lookup[position][j] = insert_combo; - set = true; - break; - } - if (combo_at_j->key_position_len < insert_combo->key_position_len || - (combo_at_j->key_position_len == insert_combo->key_position_len && - combo_at_j->virtual_key_position < insert_combo->virtual_key_position)) { - continue; - } - // put insert_combo in this spot, move all other combos up. - combo_lookup[position][j] = insert_combo; - insert_combo = combo_at_j; - } - if (!set) { - LOG_ERR("Too many combos for key position %d, CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY %d.", - position, CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY); - return -ENOMEM; - } + for (size_t kp = 0; kp < new_combo->key_position_len; kp++) { + sys_bitfield_set_bit((mem_addr_t)&combo_lookup[new_combo->key_positions[kp]], index); } + return 0; } static bool combo_active_on_layer(const struct combo_cfg *combo, uint8_t layer) { - if (combo->layers[0] == -1) { - // -1 in the first layer position is global layer scope + if (!combo->layer_mask) { return true; } - for (int j = 0; j < combo->layers_len; j++) { - if (combo->layers[j] == layer) { - return true; - } - } - return false; + + return combo->layer_mask & BIT(layer); } static bool is_quick_tap(const struct combo_cfg *combo, int64_t timestamp) { @@ -145,66 +162,58 @@ static bool is_quick_tap(const struct combo_cfg *combo, int64_t timestamp) { 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++) { - const struct combo_cfg *combo = combo_lookup[position][i]; - if (combo == NULL) { - return number_of_combo_candidates; + + for (size_t i = 0; i < ARRAY_SIZE(combos); i++) { + if (sys_bitfield_test_bit((mem_addr_t)&combo_lookup[position], i)) { + const struct combo_cfg *combo = &combos[i]; + if (combo_active_on_layer(combo, highest_active_layer) && + !is_quick_tap(combo, timestamp)) { + sys_bitfield_set_bit((mem_addr_t)&candidates, i); + number_of_combo_candidates++; + } + // LOG_DBG("combo timeout %d %d %d", position, i, candidates[i].timeout_at); } - if (combo_active_on_layer(combo, highest_active_layer) && !is_quick_tap(combo, timestamp)) { - candidates[number_of_combo_candidates].combo = combo; - candidates[number_of_combo_candidates].timeout_at = timestamp + combo->timeout_ms; - number_of_combo_candidates++; - } - // LOG_DBG("combo timeout %d %d %d", position, i, candidates[i].timeout_at); } + return number_of_combo_candidates; } +static inline uint8_t zero_one_or_more_bits(uint32_t field) { + if (field == 0) { + return 0; + } + if ((field & (field - 1)) == 0) { + return 1; + } + return 2; +} + static int filter_candidates(int32_t position) { - // this code iterates over candidates and the lookup together to filter in O(n) - // assuming they are both sorted on key_position_len, virtual_key_position - int matches = 0, lookup_idx = 0, candidate_idx = 0; - while (lookup_idx < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY && - candidate_idx < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY) { - const struct combo_cfg *candidate = candidates[candidate_idx].combo; - const struct combo_cfg *lookup = combo_lookup[position][lookup_idx]; - if (candidate == NULL || lookup == NULL) { - break; - } - if (candidate->virtual_key_position == lookup->virtual_key_position) { - candidates[matches] = candidates[candidate_idx]; - matches++; - candidate_idx++; - lookup_idx++; - } else if (candidate->key_position_len > lookup->key_position_len) { - lookup_idx++; - } else if (candidate->key_position_len < lookup->key_position_len) { - candidate_idx++; - } else if (candidate->virtual_key_position > lookup->virtual_key_position) { - lookup_idx++; - } else if (candidate->virtual_key_position < lookup->virtual_key_position) { - candidate_idx++; + int matches = 0; + for (int i = 0; i < BYTES_FOR_COMBOS_MASK; i++) { + candidates[i] &= combo_lookup[position][i]; + if (matches < 2) { + matches += zero_one_or_more_bits(candidates[i]); } } - // clear unmatched candidates - for (int i = matches; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) { - candidates[i].combo = NULL; - } - // LOG_DBG("combo matches after filter %d", matches); + + LOG_DBG("combo matches after filter %d", matches); return matches; } static int64_t first_candidate_timeout() { + if (pressed_keys_count == 0) { + return LONG_MAX; + } + int64_t first_timeout = LONG_MAX; - for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) { - if (candidates[i].combo == NULL) { - break; - } - if (candidates[i].timeout_at < first_timeout) { - first_timeout = candidates[i].timeout_at; + for (int i = 0; i < ARRAY_SIZE(combos); i++) { + if (sys_bitfield_test_bit((mem_addr_t)&candidates, i)) { + first_timeout = MIN(first_timeout, combos[i].timeout_ms); } } - return first_timeout; + + return pressed_keys[0].data.timestamp + first_timeout; } static inline bool candidate_is_completely_pressed(const struct combo_cfg *candidate) { @@ -219,26 +228,17 @@ static inline bool candidate_is_completely_pressed(const struct combo_cfg *candi static int cleanup(); static int filter_timed_out_candidates(int64_t timestamp) { - int remaining_candidates = 0; - for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) { - struct combo_candidate *candidate = &candidates[i]; - if (candidate->combo == NULL) { - break; - } - if (candidate->timeout_at > timestamp) { - bool need_to_bubble_up = remaining_candidates != i; - if (need_to_bubble_up) { - // bubble up => reorder candidates so they're contiguous - candidates[remaining_candidates].combo = candidate->combo; - candidates[remaining_candidates].timeout_at = candidate->timeout_at; - // clear the previous location - candidates[i].combo = NULL; - candidates[i].timeout_at = 0; - } + __ASSERT(pressed_keys_count > 0, "Searching for a candidate timeout with no keys pressed"); - remaining_candidates++; - } else { - candidate->combo = NULL; + int remaining_candidates = 0; + for (int i = 0; i < ARRAY_SIZE(combos); i++) { + if (sys_bitfield_test_bit((mem_addr_t)&candidates, i)) { + + if (pressed_keys[0].data.timestamp + combos[i].timeout_ms > timestamp) { + remaining_candidates++; + } else { + sys_bitfield_clear_bit((mem_addr_t)&candidates, i); + } } } @@ -249,18 +249,8 @@ static int filter_timed_out_candidates(int64_t timestamp) { return remaining_candidates; } -static int clear_candidates() { - for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) { - if (candidates[i].combo == NULL) { - return i; - } - candidates[i].combo = NULL; - } - return CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; -} - static int capture_pressed_key(const struct zmk_position_state_changed *ev) { - if (pressed_keys_count == CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY) { + if (pressed_keys_count == MAX_COMBO_KEYS) { return ZMK_EV_EVENT_BUBBLE; } @@ -271,7 +261,7 @@ static int capture_pressed_key(const struct zmk_position_state_changed *ev) { const struct zmk_listener zmk_listener_combo; static int release_pressed_keys() { - uint32_t count = pressed_keys_count; + uint8_t count = pressed_keys_count; pressed_keys_count = 0; for (int i = 0; i < count; i++) { struct zmk_position_state_changed_event *ev = &pressed_keys[i]; @@ -288,9 +278,10 @@ static int release_pressed_keys() { return count; } -static inline int press_combo_behavior(const struct combo_cfg *combo, int32_t timestamp) { +static inline int press_combo_behavior(int combo_idx, const struct combo_cfg *combo, + int32_t timestamp) { struct zmk_behavior_binding_event event = { - .position = combo->virtual_key_position, + .position = ZMK_VIRTUAL_KEY_POSITION_COMBO(combo_idx), .timestamp = timestamp, #if IS_ENABLED(CONFIG_ZMK_SPLIT) .source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL, @@ -302,9 +293,10 @@ static inline int press_combo_behavior(const struct combo_cfg *combo, int32_t ti return zmk_behavior_invoke_binding(&combo->behavior, event, true); } -static inline int release_combo_behavior(const struct combo_cfg *combo, int32_t timestamp) { +static inline int release_combo_behavior(int combo_idx, const struct combo_cfg *combo, + int32_t timestamp) { struct zmk_behavior_binding_event event = { - .position = combo->virtual_key_position, + .position = ZMK_VIRTUAL_KEY_POSITION_COMBO(combo_idx), .timestamp = timestamp, #if IS_ENABLED(CONFIG_ZMK_SPLIT) .source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL, @@ -316,7 +308,7 @@ static inline int release_combo_behavior(const struct combo_cfg *combo, int32_t static void move_pressed_keys_to_active_combo(struct active_combo *active_combo) { - int combo_length = MIN(pressed_keys_count, active_combo->combo->key_position_len); + int combo_length = MIN(pressed_keys_count, combos[active_combo->combo_idx].key_position_len); for (int i = 0; i < combo_length; i++) { active_combo->key_positions_pressed[i] = pressed_keys[i]; } @@ -330,10 +322,10 @@ static void move_pressed_keys_to_active_combo(struct active_combo *active_combo) pressed_keys_count -= combo_length; } -static struct active_combo *store_active_combo(const struct combo_cfg *combo) { +static struct active_combo *store_active_combo(int32_t combo_idx) { for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS; i++) { - if (active_combos[i].combo == NULL) { - active_combos[i].combo = combo; + if (active_combos[i].combo_idx == UINT16_MAX) { + active_combos[i].combo_idx = combo_idx; active_combo_count++; return &active_combos[i]; } @@ -344,15 +336,16 @@ static struct active_combo *store_active_combo(const struct combo_cfg *combo) { return NULL; } -static void activate_combo(const struct combo_cfg *combo) { - struct active_combo *active_combo = store_active_combo(combo); +static void activate_combo(int combo_idx) { + struct active_combo *active_combo = store_active_combo(combo_idx); if (active_combo == NULL) { // unable to store combo release_pressed_keys(); return; } move_pressed_keys_to_active_combo(active_combo); - press_combo_behavior(combo, active_combo->key_positions_pressed[0].data.timestamp); + press_combo_behavior(combo_idx, &combos[combo_idx], + active_combo->key_positions_pressed[0].data.timestamp); } static void deactivate_combo(int active_combo_index) { @@ -361,8 +354,8 @@ static void deactivate_combo(int active_combo_index) { memcpy(&active_combos[active_combo_index], &active_combos[active_combo_count], sizeof(struct active_combo)); } - active_combos[active_combo_count].combo = NULL; active_combos[active_combo_count] = (struct active_combo){0}; + active_combos[active_combo_count].combo_idx = UINT16_MAX; } /* returns true if a key was released. */ @@ -371,8 +364,8 @@ static bool release_combo_key(int32_t position, int64_t timestamp) { struct active_combo *active_combo = &active_combos[combo_idx]; bool key_released = false; - bool all_keys_pressed = - active_combo->key_positions_pressed_count == active_combo->combo->key_position_len; + bool all_keys_pressed = active_combo->key_positions_pressed_count == + combos[active_combo->combo_idx].key_position_len; bool all_keys_released = true; for (int i = 0; i < active_combo->key_positions_pressed_count; i++) { if (key_released) { @@ -387,9 +380,9 @@ static bool release_combo_key(int32_t position, int64_t timestamp) { if (key_released) { active_combo->key_positions_pressed_count--; - if ((active_combo->combo->slow_release && all_keys_released) || - (!active_combo->combo->slow_release && all_keys_pressed)) { - release_combo_behavior(active_combo->combo, timestamp); + const struct combo_cfg *c = &combos[active_combo->combo_idx]; + if ((c->slow_release && all_keys_released) || (!c->slow_release && all_keys_pressed)) { + release_combo_behavior(active_combo->combo_idx, c, timestamp); } if (all_keys_released) { deactivate_combo(combo_idx); @@ -402,10 +395,10 @@ static bool release_combo_key(int32_t position, int64_t timestamp) { static int cleanup() { k_work_cancel_delayable(&timeout_task); - clear_candidates(); - if (fully_pressed_combo != NULL) { + memset(candidates, 0, BYTES_FOR_COMBOS_MASK); + if (fully_pressed_combo != INT16_MAX) { activate_combo(fully_pressed_combo); - fully_pressed_combo = NULL; + fully_pressed_combo = INT16_MAX; } return release_pressed_keys(); } @@ -427,7 +420,7 @@ static void update_timeout_task() { static int position_state_down(const zmk_event_t *ev, struct zmk_position_state_changed *data) { int num_candidates; - if (candidates[0].combo == NULL) { + if (!pressed_keys_count) { num_candidates = setup_candidates_for_first_keypress(data->position, data->timestamp); if (num_candidates == 0) { return ZMK_EV_EVENT_BUBBLE; @@ -436,27 +429,31 @@ static int position_state_down(const zmk_event_t *ev, struct zmk_position_state_ filter_timed_out_candidates(data->timestamp); num_candidates = filter_candidates(data->position); } - update_timeout_task(); - const struct combo_cfg *candidate_combo = candidates[0].combo; LOG_DBG("combo: capturing position event %d", data->position); int ret = capture_pressed_key(data); - switch (num_candidates) { - case 0: + update_timeout_task(); + + if (num_candidates) { + for (int i = 0; i < ARRAY_SIZE(combos); i++) { + if (sys_bitfield_test_bit((mem_addr_t)&candidates, i)) { + const struct combo_cfg *candidate_combo = &combos[i]; + if (candidate_is_completely_pressed(candidate_combo)) { + fully_pressed_combo = i; + if (num_candidates == 1) { + cleanup(); + } + } + + return ret; + } + } + } else { cleanup(); return ret; - case 1: - if (candidate_is_completely_pressed(candidate_combo)) { - fully_pressed_combo = candidate_combo; - cleanup(); - } - return ret; - default: - if (candidate_is_completely_pressed(candidate_combo)) { - fully_pressed_combo = candidate_combo; - } - return ret; } + + return -EINVAL; } static int position_state_up(const zmk_event_t *ev, struct zmk_position_state_changed *data) { @@ -481,8 +478,11 @@ static void combo_timeout_handler(struct k_work *item) { return; } if (filter_timed_out_candidates(timeout_task_timeout_at) == 0) { + LOG_DBG("CLEANUP!"); cleanup(); } + + LOG_DBG("ABOUT TO UPDATE IN TIMEOUT"); update_timeout_task(); } @@ -520,26 +520,16 @@ ZMK_LISTENER(combo, behavior_combo_listener); ZMK_SUBSCRIPTION(combo, zmk_position_state_changed); ZMK_SUBSCRIPTION(combo, zmk_keycode_state_changed); -#define COMBO_INST(n) \ - static const struct combo_cfg combo_config_##n = { \ - .timeout_ms = DT_PROP(n, timeout_ms), \ - .require_prior_idle_ms = DT_PROP(n, require_prior_idle_ms), \ - .key_positions = DT_PROP(n, key_positions), \ - .key_position_len = DT_PROP_LEN(n, key_positions), \ - .behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, n), \ - .virtual_key_position = ZMK_VIRTUAL_KEY_POSITION_COMBO(__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); - -DT_INST_FOREACH_CHILD(0, COMBO_INST) - static int combo_init(void) { + for (size_t i = 0; i < CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS; i++) { + active_combos[i].combo_idx = UINT16_MAX; + } + k_work_init_delayable(&timeout_task, combo_timeout_handler); - DT_INST_FOREACH_CHILD(0, INITIALIZE_COMBO); + LOG_WRN("Have %d combos!", ARRAY_SIZE(combos)); + for (int i = 0; i < ARRAY_SIZE(combos); i++) { + initialize_combo(i); + } return 0; } diff --git a/docs/docs/config/combos.md b/docs/docs/config/combos.md index d773628db..0400b1637 100644 --- a/docs/docs/config/combos.md +++ b/docs/docs/config/combos.md @@ -11,15 +11,9 @@ See [Configuration Overview](index.md) for instructions on how to change these s Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) -| Config | Type | Description | Default | -| ------------------------------------- | ---- | -------------------------------------------------------------- | ------- | -| `CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS` | int | Maximum number of combos that can be active at the same time | 4 | -| `CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY` | int | Maximum number of active combos that use the same key position | 5 | -| `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` | int | Maximum number of keys to press to activate a combo | 4 | - -If `CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY` is 5, you can have 5 separate combos that use position `0`, 5 combos that use position `1`, and so on. - -If you want a combo that triggers when pressing 5 keys, you must set `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` to 5. +| Config | Type | Description | Default | +| ------------------------------------- | ---- | ------------------------------------------------------------ | ------- | +| `CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS` | int | Maximum number of combos that can be active at the same time | 4 | ## Devicetree @@ -39,5 +33,3 @@ Each child node can have the following properties: | `require-prior-idle-ms` | int | If any non-modifier key is pressed within `require-prior-idle-ms` before a key in the combo, the key will not be considered for the combo | -1 (disabled) | | `slow-release` | bool | Releases the combo when all keys are released instead of when any key is released | false | | `layers` | array | A list of layers on which the combo may be triggered. `-1` allows all layers. | `<-1>` | - -The `key-positions` array must not be longer than the `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` setting, which defaults to 4. If you want a combo that triggers when pressing 5 keys, then you must change the setting to 5.