diff options
| author | Pascal Getreuer <50221757+getreuer@users.noreply.github.com> | 2025-11-11 03:27:12 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-11 22:27:12 +1100 |
| commit | efc5d63383b64291f25c8377bcfae8178dd63302 (patch) | |
| tree | 6da3bafb3a53f478acae6d007cf94f90a2f26057 /quantum/action_tapping.c | |
| parent | 2af9aac61c70b543f29f658984ea66993cc3db04 (diff) | |
[Core] Speculative Hold option for mod-taps: hold mods instantly while unsettled. (#25572)
Diffstat (limited to 'quantum/action_tapping.c')
| -rw-r--r-- | quantum/action_tapping.c | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/quantum/action_tapping.c b/quantum/action_tapping.c index c2f45ea178..5d43dd99ea 100644 --- a/quantum/action_tapping.c +++ b/quantum/action_tapping.c | |||
| @@ -6,8 +6,10 @@ | |||
| 6 | #include "action_tapping.h" | 6 | #include "action_tapping.h" |
| 7 | #include "action_util.h" | 7 | #include "action_util.h" |
| 8 | #include "keycode.h" | 8 | #include "keycode.h" |
| 9 | #include "keycode_config.h" | ||
| 9 | #include "quantum_keycodes.h" | 10 | #include "quantum_keycodes.h" |
| 10 | #include "timer.h" | 11 | #include "timer.h" |
| 12 | #include "wait.h" | ||
| 11 | 13 | ||
| 12 | #ifndef NO_ACTION_TAPPING | 14 | #ifndef NO_ACTION_TAPPING |
| 13 | 15 | ||
| @@ -51,6 +53,21 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re | |||
| 51 | } | 53 | } |
| 52 | # endif | 54 | # endif |
| 53 | 55 | ||
| 56 | # ifdef SPECULATIVE_HOLD | ||
| 57 | typedef struct { | ||
| 58 | keypos_t key; | ||
| 59 | uint8_t mods; | ||
| 60 | } speculative_key_t; | ||
| 61 | # define SPECULATIVE_KEYS_SIZE 8 | ||
| 62 | static speculative_key_t speculative_keys[SPECULATIVE_KEYS_SIZE] = {}; | ||
| 63 | static uint8_t num_speculative_keys = 0; | ||
| 64 | static uint8_t prev_speculative_mods = 0; | ||
| 65 | static uint8_t speculative_mods = 0; | ||
| 66 | |||
| 67 | /** Handler to be called on incoming press events. */ | ||
| 68 | static void speculative_key_press(keyrecord_t *record); | ||
| 69 | # endif // SPECULATIVE_HOLD | ||
| 70 | |||
| 54 | # if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM) | 71 | # if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM) |
| 55 | # define REGISTERED_TAPS_SIZE 8 | 72 | # define REGISTERED_TAPS_SIZE 8 |
| 56 | // Array of tap-hold keys that have been settled as tapped but not yet released. | 73 | // Array of tap-hold keys that have been settled as tapped but not yet released. |
| @@ -129,6 +146,13 @@ static void debug_waiting_buffer(void); | |||
| 129 | * FIXME: Needs doc | 146 | * FIXME: Needs doc |
| 130 | */ | 147 | */ |
| 131 | void action_tapping_process(keyrecord_t record) { | 148 | void action_tapping_process(keyrecord_t record) { |
| 149 | # ifdef SPECULATIVE_HOLD | ||
| 150 | prev_speculative_mods = speculative_mods; | ||
| 151 | if (record.event.pressed) { | ||
| 152 | speculative_key_press(&record); | ||
| 153 | } | ||
| 154 | # endif // SPECULATIVE_HOLD | ||
| 155 | |||
| 132 | if (process_tapping(&record)) { | 156 | if (process_tapping(&record)) { |
| 133 | if (IS_EVENT(record.event)) { | 157 | if (IS_EVENT(record.event)) { |
| 134 | ac_dprintf("processed: "); | 158 | ac_dprintf("processed: "); |
| @@ -145,6 +169,12 @@ void action_tapping_process(keyrecord_t record) { | |||
| 145 | } | 169 | } |
| 146 | } | 170 | } |
| 147 | 171 | ||
| 172 | # ifdef SPECULATIVE_HOLD | ||
| 173 | if (speculative_mods != prev_speculative_mods) { | ||
| 174 | send_keyboard_report(); | ||
| 175 | } | ||
| 176 | # endif // SPECULATIVE_HOLD | ||
| 177 | |||
| 148 | // process waiting_buffer | 178 | // process waiting_buffer |
| 149 | if (IS_EVENT(record.event) && waiting_buffer_head != waiting_buffer_tail) { | 179 | if (IS_EVENT(record.event) && waiting_buffer_head != waiting_buffer_tail) { |
| 150 | ac_dprintf("---- action_exec: process waiting_buffer -----\n"); | 180 | ac_dprintf("---- action_exec: process waiting_buffer -----\n"); |
| @@ -708,6 +738,147 @@ void waiting_buffer_scan_tap(void) { | |||
| 708 | } | 738 | } |
| 709 | } | 739 | } |
| 710 | 740 | ||
| 741 | # ifdef SPECULATIVE_HOLD | ||
| 742 | static void debug_speculative_keys(void) { | ||
| 743 | ac_dprintf("mods = { "); | ||
| 744 | for (int8_t i = 0; i < num_speculative_keys; ++i) { | ||
| 745 | ac_dprintf("%02X ", speculative_keys[i].mods); | ||
| 746 | } | ||
| 747 | ac_dprintf("}, keys = { "); | ||
| 748 | for (int8_t i = 0; i < num_speculative_keys; ++i) { | ||
| 749 | ac_dprintf("%02X%02X ", speculative_keys[i].key.row, speculative_keys[i].key.col); | ||
| 750 | } | ||
| 751 | ac_dprintf("}\n"); | ||
| 752 | } | ||
| 753 | |||
| 754 | // Find key in speculative_keys. Returns num_speculative_keys if not found. | ||
| 755 | static int8_t speculative_keys_find(keypos_t key) { | ||
| 756 | uint8_t i; | ||
| 757 | for (i = 0; i < num_speculative_keys; ++i) { | ||
| 758 | if (KEYEQ(speculative_keys[i].key, key)) { | ||
| 759 | break; | ||
| 760 | } | ||
| 761 | } | ||
| 762 | return i; | ||
| 763 | } | ||
| 764 | |||
| 765 | static void speculative_key_press(keyrecord_t *record) { | ||
| 766 | if (num_speculative_keys >= SPECULATIVE_KEYS_SIZE) { // Overflow! | ||
| 767 | ac_dprintf("SPECULATIVE KEYS OVERFLOW: IGNORING EVENT\n"); | ||
| 768 | return; // Don't trigger: speculative_keys is full. | ||
| 769 | } | ||
| 770 | if (speculative_keys_find(record->event.key) < num_speculative_keys) { | ||
| 771 | return; // Don't trigger: key is already in speculative_keys. | ||
| 772 | } | ||
| 773 | |||
| 774 | const uint16_t keycode = get_record_keycode(record, false); | ||
| 775 | if (!IS_QK_MOD_TAP(keycode)) { | ||
| 776 | return; // Don't trigger: not a mod-tap key. | ||
| 777 | } | ||
| 778 | |||
| 779 | uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode)); | ||
| 780 | if ((mods & 0x10) != 0) { // Unpack 5-bit mods to 8-bit representation. | ||
| 781 | mods <<= 4; | ||
| 782 | } | ||
| 783 | if ((~(get_mods() | speculative_mods) & mods) == 0) { | ||
| 784 | return; // Don't trigger: mods are already active. | ||
| 785 | } | ||
| 786 | |||
| 787 | // Don't do Speculative Hold when there are non-speculated buffered events, | ||
| 788 | // since that could result in sending keys out of order. | ||
| 789 | for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) { | ||
| 790 | if (!waiting_buffer[i].tap.speculated) { | ||
| 791 | return; | ||
| 792 | } | ||
| 793 | } | ||
| 794 | |||
| 795 | if (get_speculative_hold(keycode, record)) { | ||
| 796 | record->tap.speculated = true; | ||
| 797 | speculative_mods |= mods; | ||
| 798 | // Remember the keypos and mods associated with this key. | ||
| 799 | speculative_keys[num_speculative_keys] = (speculative_key_t){ | ||
| 800 | .key = record->event.key, | ||
| 801 | .mods = mods, | ||
| 802 | }; | ||
| 803 | ++num_speculative_keys; | ||
| 804 | |||
| 805 | ac_dprintf("Speculative Hold: "); | ||
| 806 | debug_speculative_keys(); | ||
| 807 | } | ||
| 808 | } | ||
| 809 | |||
| 810 | uint8_t get_speculative_mods(void) { | ||
| 811 | return speculative_mods; | ||
| 812 | } | ||
| 813 | |||
| 814 | __attribute__((weak)) bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) { | ||
| 815 | const uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode)); | ||
| 816 | return (mods & (MOD_LCTL | MOD_LSFT)) == mods; | ||
| 817 | } | ||
| 818 | |||
| 819 | void speculative_key_settled(keyrecord_t *record) { | ||
| 820 | if (num_speculative_keys == 0) { | ||
| 821 | return; // Early return when there are no active speculative keys. | ||
| 822 | } | ||
| 823 | |||
| 824 | uint8_t i = speculative_keys_find(record->event.key); | ||
| 825 | |||
| 826 | const uint16_t keycode = get_record_keycode(record, false); | ||
| 827 | if (IS_QK_MOD_TAP(keycode) && record->tap.count == 0) { // MT hold press. | ||
| 828 | if (i < num_speculative_keys) { | ||
| 829 | --num_speculative_keys; | ||
| 830 | const uint8_t cleared_mods = speculative_keys[i].mods; | ||
| 831 | |||
| 832 | if (num_speculative_keys) { | ||
| 833 | speculative_mods &= ~cleared_mods; | ||
| 834 | // Don't call send_keyboard_report() here; allow default | ||
| 835 | // handling to reapply the mod before the next report. | ||
| 836 | |||
| 837 | // Remove the ith entry from speculative_keys. | ||
| 838 | for (uint8_t j = i; j < num_speculative_keys; ++j) { | ||
| 839 | speculative_keys[j] = speculative_keys[j + 1]; | ||
| 840 | } | ||
| 841 | } else { | ||
| 842 | speculative_mods = 0; | ||
| 843 | } | ||
| 844 | |||
| 845 | ac_dprintf("Speculative Hold: settled %02x, ", cleared_mods); | ||
| 846 | debug_speculative_keys(); | ||
| 847 | } | ||
| 848 | } else { // Tap press event; cancel speculatively-held mod. | ||
| 849 | if (i >= num_speculative_keys) { | ||
| 850 | i = 0; | ||
| 851 | } | ||
| 852 | |||
| 853 | // Clear mods for the ith key and all keys that follow. | ||
| 854 | uint8_t cleared_mods = 0; | ||
| 855 | for (uint8_t j = i; j < num_speculative_keys; ++j) { | ||
| 856 | cleared_mods |= speculative_keys[j].mods; | ||
| 857 | } | ||
| 858 | |||
| 859 | num_speculative_keys = i; // Remove ith and following entries. | ||
| 860 | |||
| 861 | if ((prev_speculative_mods & cleared_mods) != 0) { | ||
| 862 | # ifdef DUMMY_MOD_NEUTRALIZER_KEYCODE | ||
| 863 | neutralize_flashing_modifiers(get_mods() | prev_speculative_mods); | ||
| 864 | # endif // DUMMY_MOD_NEUTRALIZER_KEYCODE | ||
| 865 | } | ||
| 866 | |||
| 867 | if (num_speculative_keys) { | ||
| 868 | speculative_mods &= ~cleared_mods; | ||
| 869 | } else { | ||
| 870 | speculative_mods = 0; | ||
| 871 | } | ||
| 872 | |||
| 873 | send_keyboard_report(); | ||
| 874 | wait_ms(TAP_CODE_DELAY); | ||
| 875 | |||
| 876 | ac_dprintf("Speculative Hold: canceled %02x, ", cleared_mods); | ||
| 877 | debug_speculative_keys(); | ||
| 878 | } | ||
| 879 | } | ||
| 880 | # endif // SPECULATIVE_HOLD | ||
| 881 | |||
| 711 | # if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM) | 882 | # if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM) |
| 712 | static void registered_taps_add(keypos_t key) { | 883 | static void registered_taps_add(keypos_t key) { |
| 713 | if (num_registered_taps >= REGISTERED_TAPS_SIZE) { | 884 | if (num_registered_taps >= REGISTERED_TAPS_SIZE) { |
