Skip to content

Commit

Permalink
Add combo key repress feature
Browse files Browse the repository at this point in the history
Allows for running code for combo key repressing after release
  • Loading branch information
Filios92 committed Jul 29, 2024
1 parent 9c1e1d1 commit 969503b
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 12 deletions.
44 changes: 44 additions & 0 deletions docs/features/combo.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,50 @@ bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key
return false;
}
```

### Customizable key repress
By defining `COMBO_PROCESS_KEY_REPRESS` and implementing `bool process_combo_key_repress(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode)` you can run your custom code when you repress just released key of a combo. By combining it with custom `process_combo_event` we can for example make special handling for Alt+Tab to switch windows, which, on combo F+G activation, registers Alt and presses Tab - then we can switch windows forward by releasing G and pressing it again, or backwards with F key. Here's the full example:

```c
enum combos {
CMB_ALTTAB
};

const uint16_t PROGMEM combo_alttab[] = {KC_F, KC_G, COMBO_END};

combo_t key_combos[COMBO_LENGTH] = {
[CMB_ALTTAB] = COMBO(combo_alttab, KC_NO), // KC_NO to leave processing for process_combo_event
};

void process_combo_event(uint16_t combo_index, bool pressed) {
switch (combo_index) {
case CMB_ALTTAB:
if (pressed) {
register_mods(MOD_LALT);
tap_code(KC_TAB);
} else {
unregister_mods(MOD_LALT);
}
break;
}
}

bool process_combo_key_repress(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) {
switch (combo_index) {
case CMB_ALTTAB:
switch (keycode) {
case KC_F:
tap_code16(S(KC_TAB));
return true;
case KC_G:
tap_code(KC_TAB);
return true;
}
}
return false;
}
```
### Layer independent combos
If you, for example, use multiple base layers for different key layouts, one for QWERTY, and another one for Colemak, you might want your combos to work from the same key positions on all layers. Defining the same combos again for another layout is redundant and takes more memory. The solution is to just check the keycodes from one layer.
Expand Down
46 changes: 34 additions & 12 deletions quantum/process_keycode/process_combo.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,20 @@ __attribute__((weak)) bool process_combo_key_release(uint16_t combo_index, combo
}
#endif

#ifdef COMBO_PROCESS_KEY_REPRESS
__attribute__((weak)) bool process_combo_key_repress(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) {
return false;
}
#endif

#ifdef COMBO_SHOULD_TRIGGER
__attribute__((weak)) bool combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode, keyrecord_t *record) {
return true;
}
#endif

typedef enum { COMBO_KEY_NOT_PRESSED = 0b00, COMBO_KEY_PRESSED = 0b01, COMBO_KEY_REPRESSED = 0b10 } combo_key_action_t;

#ifndef COMBO_NO_TIMER
static uint16_t timer = 0;
#endif
Expand Down Expand Up @@ -414,14 +422,14 @@ static bool keys_pressed_in_order(uint16_t combo_index, combo_t *combo, uint16_t
}
#endif

static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record, uint16_t combo_index) {
static combo_key_action_t process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record, uint16_t combo_index) {
uint8_t key_count = 0;
uint16_t key_index = -1;
_find_key_index_and_count(combo->keys, keycode, &key_index, &key_count);

/* Continue processing if key isn't part of current combo. */
if (-1 == (int16_t)key_index) {
return false;
return COMBO_KEY_NOT_PRESSED;
}

bool key_is_part_of_combo = (!COMBO_DISABLED(combo) && is_combo_enabled()
Expand Down Expand Up @@ -449,7 +457,7 @@ static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *
/* Don't buffer this combo if its combo term has passed. */
if (timer && timer_elapsed(timer) > time) {
DISABLE_COMBO(combo);
return true;
return COMBO_KEY_PRESSED;
} else
#endif
{
Expand Down Expand Up @@ -485,6 +493,15 @@ static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *
}
} // if timer elapsed end
}
#ifdef COMBO_PROCESS_KEY_REPRESS
} else if (record->event.pressed) {
if (COMBO_ACTIVE(combo)) {
KEY_STATE_DOWN(combo->state, key_index);
if (process_combo_key_repress(combo_index, combo, key_index, keycode)) {
return COMBO_KEY_REPRESSED;
}
}
#endif
} else {
// chord releases
if (!COMBO_ACTIVE(combo) && ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) {
Expand Down Expand Up @@ -531,12 +548,12 @@ static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *
KEY_STATE_UP(combo->state, key_index);
}

return key_is_part_of_combo;
return key_is_part_of_combo ? COMBO_KEY_PRESSED : COMBO_KEY_NOT_PRESSED;
}

bool process_combo(uint16_t keycode, keyrecord_t *record) {
bool is_combo_key = false;
bool no_combo_keys_pressed = true;
uint8_t is_combo_key = COMBO_KEY_NOT_PRESSED;
bool no_combo_keys_pressed = true;

if (keycode == QK_COMBO_ON && record->event.pressed) {
combo_enable();
Expand Down Expand Up @@ -582,12 +599,17 @@ bool process_combo(uint16_t keycode, keyrecord_t *record) {
# endif
#endif

if (key_buffer_size < COMBO_KEY_BUFFER_LENGTH) {
key_buffer[key_buffer_size++] = (queued_record_t){
.record = *record,
.keycode = keycode,
.combo_index = -1, // this will be set when applying combos
};
#ifdef COMBO_PROCESS_KEY_REPRESS
if (is_combo_key == COMBO_KEY_PRESSED)
#endif
{
if (key_buffer_size < COMBO_KEY_BUFFER_LENGTH) {
key_buffer[key_buffer_size++] = (queued_record_t){
.record = *record,
.keycode = keycode,
.combo_index = -1, // this will be set when applying combos
};
}
}
} else {
if (combo_buffer_read != combo_buffer_write) {
Expand Down

0 comments on commit 969503b

Please sign in to comment.