at master 22 kB view raw
1/* Copyright 2016 Jack Humbert 2 * 3 * This program is free software: you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation, either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16 17#include "process_combo.h" 18#include <stddef.h> 19#include "process_auto_shift.h" 20#include "caps_word.h" 21#include "timer.h" 22#include "wait.h" 23#include "keyboard.h" 24#include "keymap_common.h" 25#include "action_layer.h" 26#include "action_tapping.h" 27#include "action_util.h" 28#include "keymap_introspection.h" 29 30__attribute__((weak)) void process_combo_event(uint16_t combo_index, bool pressed) {} 31 32#ifndef COMBO_ONLY_FROM_LAYER 33__attribute__((weak)) uint8_t combo_ref_from_layer(uint8_t layer) { 34 return layer; 35} 36#endif 37 38#ifdef COMBO_MUST_HOLD_PER_COMBO 39__attribute__((weak)) bool get_combo_must_hold(uint16_t combo_index, combo_t *combo) { 40 return false; 41} 42#endif 43 44#ifdef COMBO_MUST_TAP_PER_COMBO 45__attribute__((weak)) bool get_combo_must_tap(uint16_t combo_index, combo_t *combo) { 46 return false; 47} 48#endif 49 50#ifdef COMBO_TERM_PER_COMBO 51__attribute__((weak)) uint16_t get_combo_term(uint16_t combo_index, combo_t *combo) { 52 return COMBO_TERM; 53} 54#endif 55 56#ifdef COMBO_MUST_PRESS_IN_ORDER_PER_COMBO 57__attribute__((weak)) bool get_combo_must_press_in_order(uint16_t combo_index, combo_t *combo) { 58 return true; 59} 60#endif 61 62#ifdef COMBO_PROCESS_KEY_RELEASE 63__attribute__((weak)) bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) { 64 return false; 65} 66#endif 67 68#ifdef COMBO_PROCESS_KEY_REPRESS 69__attribute__((weak)) bool process_combo_key_repress(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) { 70 return false; 71} 72#endif 73 74#ifdef COMBO_SHOULD_TRIGGER 75__attribute__((weak)) bool combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode, keyrecord_t *record) { 76 return true; 77} 78#endif 79 80typedef enum { COMBO_KEY_NOT_PRESSED, COMBO_KEY_PRESSED, COMBO_KEY_REPRESSED } combo_key_action_t; 81 82#ifndef COMBO_NO_TIMER 83static uint16_t timer = 0; 84#endif 85static bool b_combo_enable = true; // defaults to enabled 86static uint16_t longest_term = 0; 87 88typedef struct { 89 keyrecord_t record; 90 uint16_t combo_index; 91 uint16_t keycode; 92} queued_record_t; 93static uint8_t key_buffer_size = 0; 94static queued_record_t key_buffer[COMBO_KEY_BUFFER_LENGTH]; 95 96typedef struct { 97 uint16_t combo_index; 98} queued_combo_t; 99static uint8_t combo_buffer_write = 0; 100static uint8_t combo_buffer_read = 0; 101static queued_combo_t combo_buffer[COMBO_BUFFER_LENGTH]; 102 103#define INCREMENT_MOD(i) i = (i + 1) % COMBO_BUFFER_LENGTH 104 105#ifndef EXTRA_SHORT_COMBOS 106/* flags are their own elements in combo_t struct. */ 107# define COMBO_ACTIVE(combo) (combo->active) 108# define COMBO_DISABLED(combo) (combo->disabled) 109# define COMBO_STATE(combo) (combo->state) 110 111# define ACTIVATE_COMBO(combo) \ 112 do { \ 113 combo->active = true; \ 114 } while (0) 115# define DEACTIVATE_COMBO(combo) \ 116 do { \ 117 combo->active = false; \ 118 } while (0) 119# define DISABLE_COMBO(combo) \ 120 do { \ 121 combo->disabled = true; \ 122 } while (0) 123# define RESET_COMBO_STATE(combo) \ 124 do { \ 125 combo->disabled = false; \ 126 combo->state = 0; \ 127 } while (0) 128#else 129/* flags are at the two high bits of state. */ 130# define COMBO_ACTIVE(combo) (combo->state & 0x80) 131# define COMBO_DISABLED(combo) (combo->state & 0x40) 132# define COMBO_STATE(combo) (combo->state & 0x3F) 133 134# define ACTIVATE_COMBO(combo) \ 135 do { \ 136 combo->state |= 0x80; \ 137 } while (0) 138# define DEACTIVATE_COMBO(combo) \ 139 do { \ 140 combo->state &= ~0x80; \ 141 } while (0) 142# define DISABLE_COMBO(combo) \ 143 do { \ 144 combo->state |= 0x40; \ 145 } while (0) 146# define RESET_COMBO_STATE(combo) \ 147 do { \ 148 combo->state &= ~0x7F; \ 149 } while (0) 150#endif 151 152static inline void release_combo(uint16_t combo_index, combo_t *combo) { 153 if (combo->keycode) { 154 keyrecord_t record = { 155 .event = MAKE_COMBOEVENT(false), 156 .keycode = combo->keycode, 157 }; 158#ifndef NO_ACTION_TAPPING 159 action_tapping_process(record); 160#else 161 process_record(&record); 162#endif 163 } else { 164 process_combo_event(combo_index, false); 165 } 166 DEACTIVATE_COMBO(combo); 167} 168 169static inline bool _get_combo_must_hold(uint16_t combo_index, combo_t *combo) { 170#ifdef COMBO_NO_TIMER 171 return false; 172#elif defined(COMBO_MUST_HOLD_PER_COMBO) 173 return get_combo_must_hold(combo_index, combo); 174#elif defined(COMBO_MUST_HOLD_MODS) 175 return (KEYCODE_IS_MOD(combo->keycode) || (combo->keycode >= QK_MOMENTARY && combo->keycode <= QK_MOMENTARY_MAX)); 176#endif 177 return false; 178} 179 180static inline uint16_t _get_wait_time(uint16_t combo_index, combo_t *combo) { 181 if (_get_combo_must_hold(combo_index, combo) 182#ifdef COMBO_MUST_TAP_PER_COMBO 183 || get_combo_must_tap(combo_index, combo) 184#endif 185 ) { 186 if (longest_term < COMBO_HOLD_TERM) { 187 return COMBO_HOLD_TERM; 188 } 189 } 190 191 return longest_term; 192} 193 194static inline uint16_t _get_combo_term(uint16_t combo_index, combo_t *combo) { 195#if defined(COMBO_TERM_PER_COMBO) 196 return get_combo_term(combo_index, combo); 197#endif 198 199 return COMBO_TERM; 200} 201 202void clear_combos(void) { 203 uint16_t index = 0; 204 longest_term = 0; 205 for (index = 0; index < combo_count(); ++index) { 206 combo_t *combo = combo_get(index); 207 if (!COMBO_ACTIVE(combo)) { 208 RESET_COMBO_STATE(combo); 209 } 210 } 211} 212 213static inline void dump_key_buffer(void) { 214 /* First call start from 0 index; recursive calls need to start from i+1 index */ 215 static uint8_t key_buffer_next = 0; 216#if TAP_CODE_DELAY > 0 217 bool delay_done = false; 218#endif 219 220 if (key_buffer_size == 0) { 221 return; 222 } 223 224 for (uint8_t key_buffer_i = key_buffer_next; key_buffer_i < key_buffer_size; key_buffer_i++) { 225 key_buffer_next = key_buffer_i + 1; 226 227 queued_record_t *qrecord = &key_buffer[key_buffer_i]; 228 keyrecord_t *record = &qrecord->record; 229 230 if (IS_NOEVENT(record->event)) { 231 continue; 232 } 233 234 if (!record->keycode && qrecord->combo_index != (uint16_t)-1) { 235 process_combo_event(qrecord->combo_index, true); 236 } else { 237#ifndef NO_ACTION_TAPPING 238 action_tapping_process(*record); 239#else 240 process_record(record); 241#endif 242 } 243 record->event.type = TICK_EVENT; 244 245#if defined(CAPS_WORD_ENABLE) && defined(AUTO_SHIFT_ENABLE) 246 // Edge case: preserve the weak Left Shift mod if both Caps Word and 247 // Auto Shift are on. Caps Word capitalizes by setting the weak Left 248 // Shift mod during the press event, but Auto Shift doesn't send the 249 // key until it receives the release event. 250 del_weak_mods((is_caps_word_on() && get_autoshift_state()) ? ~MOD_BIT(KC_LSFT) : 0xff); 251#else 252 clear_weak_mods(); 253#endif // defined(CAPS_WORD_ENABLE) && defined(AUTO_SHIFT_ENABLE) 254 255#if TAP_CODE_DELAY > 0 256 // only delay once and for a non-tapping key 257 if (!delay_done && !is_tap_record(record)) { 258 delay_done = true; 259 wait_ms(TAP_CODE_DELAY); 260 } 261#endif 262 } 263 264 key_buffer_next = key_buffer_size = 0; 265} 266 267#define NO_COMBO_KEYS_ARE_DOWN (0 == COMBO_STATE(combo)) 268#define ALL_COMBO_KEYS_ARE_DOWN(state, key_count) (((1 << key_count) - 1) == state) 269#define ONLY_ONE_KEY_IS_DOWN(state) !(state & (state - 1)) 270#define KEY_NOT_YET_RELEASED(state, key_index) ((1 << key_index) & state) 271#define KEY_STATE_DOWN(state, key_index) \ 272 do { \ 273 state |= (1 << key_index); \ 274 } while (0) 275#define KEY_STATE_UP(state, key_index) \ 276 do { \ 277 state &= ~(1 << key_index); \ 278 } while (0) 279 280static inline void _find_key_index_and_count(const uint16_t *keys, uint16_t keycode, uint16_t *key_index, uint8_t *key_count) { 281 while (true) { 282 uint16_t key = pgm_read_word(&keys[*key_count]); 283 if (keycode == key) *key_index = *key_count; 284 if (COMBO_END == key) break; 285 (*key_count)++; 286 } 287} 288 289void drop_combo_from_buffer(uint16_t combo_index) { 290 /* Mark a combo as processed from the buffer. If the buffer is in the 291 * beginning of the buffer, drop it. */ 292 uint8_t i = combo_buffer_read; 293 while (i != combo_buffer_write) { 294 queued_combo_t *qcombo = &combo_buffer[i]; 295 296 if (qcombo->combo_index == combo_index) { 297 combo_t *combo = combo_get(combo_index); 298 DISABLE_COMBO(combo); 299 300 if (i == combo_buffer_read) { 301 INCREMENT_MOD(combo_buffer_read); 302 } 303 break; 304 } 305 INCREMENT_MOD(i); 306 } 307} 308 309void apply_combo(uint16_t combo_index, combo_t *combo) { 310 /* Apply combo's result keycode to the last chord key of the combo and 311 * disable the other keys. */ 312 313 if (COMBO_DISABLED(combo)) { 314 return; 315 } 316 317 // state to check against so we find the last key of the combo from the buffer 318#if defined(EXTRA_EXTRA_LONG_COMBOS) 319 uint32_t state = 0; 320#elif defined(EXTRA_LONG_COMBOS) 321 uint16_t state = 0; 322#else 323 uint8_t state = 0; 324#endif 325 326 for (uint8_t key_buffer_i = 0; key_buffer_i < key_buffer_size; key_buffer_i++) { 327 queued_record_t *qrecord = &key_buffer[key_buffer_i]; 328 keyrecord_t *record = &qrecord->record; 329 uint16_t keycode = qrecord->keycode; 330 331 uint8_t key_count = 0; 332 uint16_t key_index = -1; 333 _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count); 334 335 if (-1 == (int16_t)key_index) { 336 // key not part of this combo 337 continue; 338 } 339 340 KEY_STATE_DOWN(state, key_index); 341 if (ALL_COMBO_KEYS_ARE_DOWN(state, key_count)) { 342 // this in the end executes the combo when the key_buffer is dumped. 343 record->keycode = combo->keycode; 344 record->event.type = COMBO_EVENT; 345 record->event.key = MAKE_KEYPOS(0, 0); 346 347 qrecord->combo_index = combo_index; 348 ACTIVATE_COMBO(combo); 349 350 if (key_count == 1) { 351 release_combo(combo_index, combo); 352 } 353 354 break; 355 } else { 356 // key was part of the combo but not the last one, "disable" it 357 // by making it a TICK event. 358 record->event.type = TICK_EVENT; 359 } 360 } 361 drop_combo_from_buffer(combo_index); 362} 363 364static inline void apply_combos(void) { 365 // Apply all buffered normal combos. 366 for (uint8_t i = combo_buffer_read; i != combo_buffer_write; INCREMENT_MOD(i)) { 367 queued_combo_t *buffered_combo = &combo_buffer[i]; 368 combo_t *combo = combo_get(buffered_combo->combo_index); 369 370#ifdef COMBO_MUST_TAP_PER_COMBO 371 if (get_combo_must_tap(buffered_combo->combo_index, combo)) { 372 // Tap-only combos are applied on key release only, so let's drop 'em here. 373 drop_combo_from_buffer(buffered_combo->combo_index); 374 continue; 375 } 376#endif 377 apply_combo(buffered_combo->combo_index, combo); 378 } 379 dump_key_buffer(); 380 clear_combos(); 381} 382 383combo_t *overlaps(combo_t *combo1, combo_t *combo2) { 384 /* Checks if the combos overlap and returns the combo that should be 385 * dropped from the combo buffer. 386 * The combo that has less keys will be dropped. If they have the same 387 * amount of keys, drop combo1. */ 388 389 uint8_t idx1 = 0, idx2 = 0; 390 uint16_t key1, key2; 391 bool overlaps = false; 392 393 while ((key1 = pgm_read_word(&combo1->keys[idx1])) != COMBO_END) { 394 idx2 = 0; 395 while ((key2 = pgm_read_word(&combo2->keys[idx2])) != COMBO_END) { 396 if (key1 == key2) overlaps = true; 397 idx2 += 1; 398 } 399 idx1 += 1; 400 } 401 402 if (!overlaps) return NULL; 403 if (idx2 < idx1) return combo2; 404 return combo1; 405} 406 407#if defined(COMBO_MUST_PRESS_IN_ORDER) || defined(COMBO_MUST_PRESS_IN_ORDER_PER_COMBO) 408static bool keys_pressed_in_order(uint16_t combo_index, combo_t *combo, uint16_t key_index, uint16_t keycode, keyrecord_t *record) { 409# ifdef COMBO_MUST_PRESS_IN_ORDER_PER_COMBO 410 if (!get_combo_must_press_in_order(combo_index, combo)) { 411 return true; 412 } 413# endif 414 if ( 415 // The `state` bit for the key being pressed. 416 (1 << key_index) == 417 // The *next* combo key's bit. 418 (COMBO_STATE(combo) + 1) 419 // E.g. two keys already pressed: `state == 11`. 420 // Next possible `state` is `111`. 421 // So the needed bit is `100` which we get with `11 + 1`. 422 ) { 423 return true; 424 } 425 return false; 426} 427#endif 428 429static combo_key_action_t process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record, uint16_t combo_index) { 430 uint8_t key_count = 0; 431 uint16_t key_index = -1; 432 _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count); 433 434 /* Continue processing if key isn't part of current combo. */ 435 if (-1 == (int16_t)key_index) { 436 return COMBO_KEY_NOT_PRESSED; 437 } 438 439 bool key_is_part_of_combo = (!COMBO_DISABLED(combo) && is_combo_enabled() 440#if defined(COMBO_MUST_PRESS_IN_ORDER) || defined(COMBO_MUST_PRESS_IN_ORDER_PER_COMBO) 441 && keys_pressed_in_order(combo_index, combo, key_index, keycode, record) 442#endif 443#ifdef COMBO_SHOULD_TRIGGER 444 && combo_should_trigger(combo_index, combo, keycode, record) 445#endif 446 ); 447 448 if (record->event.pressed && key_is_part_of_combo) { 449 uint16_t time = _get_combo_term(combo_index, combo); 450 if (!COMBO_ACTIVE(combo)) { 451 KEY_STATE_DOWN(combo->state, key_index); 452 if (longest_term < time) { 453 longest_term = time; 454 } 455 } 456 if (ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) { 457 /* Combo was fully pressed */ 458 /* Buffer the combo so we can fire it after COMBO_TERM */ 459 460#ifndef COMBO_NO_TIMER 461 /* Don't buffer this combo if its combo term has passed. */ 462 if (timer && timer_elapsed(timer) > time) { 463 DISABLE_COMBO(combo); 464 return COMBO_KEY_PRESSED; 465 } else 466#endif 467 { 468 469 // disable readied combos that overlap with this combo 470 combo_t *drop = NULL; 471 for (uint8_t combo_buffer_i = combo_buffer_read; combo_buffer_i != combo_buffer_write; INCREMENT_MOD(combo_buffer_i)) { 472 queued_combo_t *qcombo = &combo_buffer[combo_buffer_i]; 473 combo_t *buffered_combo = combo_get(qcombo->combo_index); 474 475 if ((drop = overlaps(buffered_combo, combo))) { 476 DISABLE_COMBO(drop); 477 if (drop == combo) { 478 // stop checking for overlaps if dropped combo was current combo. 479 break; 480 } else if (combo_buffer_i == combo_buffer_read && drop == buffered_combo) { 481 /* Drop the disabled buffered combo from the buffer if 482 * it is in the beginning of the buffer. */ 483 INCREMENT_MOD(combo_buffer_read); 484 } 485 } 486 } 487 488 if (drop != combo) { 489 // save this combo to buffer 490 combo_buffer[combo_buffer_write] = (queued_combo_t){ 491 .combo_index = combo_index, 492 }; 493 INCREMENT_MOD(combo_buffer_write); 494 495 // get possible longer waiting time for tap-/hold-only combos. 496 longest_term = _get_wait_time(combo_index, combo); 497 } 498 } // if timer elapsed end 499 } 500#ifdef COMBO_PROCESS_KEY_REPRESS 501 } else if (record->event.pressed) { 502 if (COMBO_ACTIVE(combo)) { 503 if (process_combo_key_repress(combo_index, combo, key_index, keycode)) { 504 KEY_STATE_DOWN(combo->state, key_index); 505 return COMBO_KEY_REPRESSED; 506 } 507 } 508#endif 509 } else { 510 // chord releases 511 if (!COMBO_ACTIVE(combo) && ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) { 512 /* First key quickly released */ 513 if (COMBO_DISABLED(combo) || _get_combo_must_hold(combo_index, combo)) { 514 // combo wasn't tappable, disable it and drop it from buffer. 515 drop_combo_from_buffer(combo_index); 516 key_is_part_of_combo = false; 517 } 518#ifdef COMBO_MUST_TAP_PER_COMBO 519 else if (get_combo_must_tap(combo_index, combo)) { 520 // immediately apply tap-only combo 521 apply_combo(combo_index, combo); 522 apply_combos(); // also apply other prepared combos and dump key buffer 523# ifdef COMBO_PROCESS_KEY_RELEASE 524 if (process_combo_key_release(combo_index, combo, key_index, keycode)) { 525 release_combo(combo_index, combo); 526 } 527# endif 528 } 529#endif 530 } else if (COMBO_ACTIVE(combo) && ONLY_ONE_KEY_IS_DOWN(COMBO_STATE(combo)) && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index)) { 531 /* last key released */ 532 release_combo(combo_index, combo); 533 key_is_part_of_combo = true; 534 535#ifdef COMBO_PROCESS_KEY_RELEASE 536 process_combo_key_release(combo_index, combo, key_index, keycode); 537#endif 538 } else if (COMBO_ACTIVE(combo) && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index)) { 539 /* first or middle key released */ 540 key_is_part_of_combo = true; 541 542#ifdef COMBO_PROCESS_KEY_RELEASE 543 if (process_combo_key_release(combo_index, combo, key_index, keycode)) { 544 release_combo(combo_index, combo); 545 } 546#endif 547 } else { 548 /* The released key was part of an incomplete combo */ 549 key_is_part_of_combo = false; 550 } 551 552 KEY_STATE_UP(combo->state, key_index); 553 } 554 555 return key_is_part_of_combo ? COMBO_KEY_PRESSED : COMBO_KEY_NOT_PRESSED; 556} 557 558bool process_combo(uint16_t keycode, keyrecord_t *record) { 559 uint8_t is_combo_key = COMBO_KEY_NOT_PRESSED; 560 bool no_combo_keys_pressed = true; 561 562 if (keycode == QK_COMBO_ON && record->event.pressed) { 563 combo_enable(); 564 return true; 565 } 566 567 if (keycode == QK_COMBO_OFF && record->event.pressed) { 568 combo_disable(); 569 return true; 570 } 571 572 if (keycode == QK_COMBO_TOGGLE && record->event.pressed) { 573 combo_toggle(); 574 return true; 575 } 576 577#ifdef COMBO_ONLY_FROM_LAYER 578 /* Only check keycodes from one layer. */ 579 keycode = keymap_key_to_keycode(COMBO_ONLY_FROM_LAYER, record->event.key); 580#else 581 uint8_t highest_layer = get_highest_layer(layer_state | default_layer_state); 582 uint8_t ref_layer = combo_ref_from_layer(highest_layer); 583 if (ref_layer != highest_layer) { 584 keycode = keymap_key_to_keycode(ref_layer, record->event.key); 585 } 586#endif 587 588 for (uint16_t idx = 0; idx < combo_count(); ++idx) { 589 combo_t *combo = combo_get(idx); 590 is_combo_key |= process_single_combo(combo, keycode, record, idx); 591 no_combo_keys_pressed = no_combo_keys_pressed && (NO_COMBO_KEYS_ARE_DOWN || COMBO_ACTIVE(combo) || COMBO_DISABLED(combo)); 592 } 593 594 if (record->event.pressed && is_combo_key) { 595#ifndef COMBO_NO_TIMER 596# ifdef COMBO_STRICT_TIMER 597 if (!timer) { 598 // timer is set only on the first key 599 timer = timer_read(); 600 } 601# else 602 timer = timer_read(); 603# endif 604#endif 605 606#ifdef COMBO_PROCESS_KEY_REPRESS 607 if (is_combo_key == COMBO_KEY_PRESSED) 608#endif 609 { 610 if (key_buffer_size < COMBO_KEY_BUFFER_LENGTH) { 611 key_buffer[key_buffer_size++] = (queued_record_t){ 612 .record = *record, 613 .keycode = keycode, 614 .combo_index = -1, // this will be set when applying combos 615 }; 616 } 617 } 618 } else { 619 if (combo_buffer_read != combo_buffer_write) { 620 // some combo is prepared 621 apply_combos(); 622 } else { 623 // reset state if there are no combo keys pressed at all 624 dump_key_buffer(); 625#ifndef COMBO_NO_TIMER 626 timer = 0; 627#endif 628 clear_combos(); 629 } 630 } 631 return !is_combo_key; 632} 633 634void combo_task(void) { 635 if (!b_combo_enable) { 636 return; 637 } 638 639#ifndef COMBO_NO_TIMER 640 if (timer && timer_elapsed(timer) > longest_term) { 641 if (combo_buffer_read != combo_buffer_write) { 642 apply_combos(); 643 longest_term = 0; 644 timer = 0; 645 } else { 646 dump_key_buffer(); 647 timer = 0; 648 clear_combos(); 649 } 650 } 651#endif 652} 653 654void combo_enable(void) { 655 b_combo_enable = true; 656} 657 658void combo_disable(void) { 659#ifndef COMBO_NO_TIMER 660 timer = 0; 661#endif 662 b_combo_enable = false; 663 combo_buffer_read = combo_buffer_write; 664 clear_combos(); 665 dump_key_buffer(); 666} 667 668void combo_toggle(void) { 669 if (b_combo_enable) { 670 combo_disable(); 671 } else { 672 combo_enable(); 673 } 674} 675 676bool is_combo_enabled(void) { 677 return b_combo_enable; 678}