summaryrefslogtreecommitdiff
path: root/quantum/action_tapping.c
diff options
context:
space:
mode:
authorPascal Getreuer <50221757+getreuer@users.noreply.github.com>2025-11-11 03:27:12 -0800
committerGitHub <noreply@github.com>2025-11-11 22:27:12 +1100
commitefc5d63383b64291f25c8377bcfae8178dd63302 (patch)
tree6da3bafb3a53f478acae6d007cf94f90a2f26057 /quantum/action_tapping.c
parent2af9aac61c70b543f29f658984ea66993cc3db04 (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.c171
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
57typedef struct {
58 keypos_t key;
59 uint8_t mods;
60} speculative_key_t;
61# define SPECULATIVE_KEYS_SIZE 8
62static speculative_key_t speculative_keys[SPECULATIVE_KEYS_SIZE] = {};
63static uint8_t num_speculative_keys = 0;
64static uint8_t prev_speculative_mods = 0;
65static uint8_t speculative_mods = 0;
66
67/** Handler to be called on incoming press events. */
68static 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 */
131void action_tapping_process(keyrecord_t record) { 148void 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
742static 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.
755static 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
765static 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
810uint8_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
819void 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)
712static void registered_taps_add(keypos_t key) { 883static void registered_taps_add(keypos_t key) {
713 if (num_registered_taps >= REGISTERED_TAPS_SIZE) { 884 if (num_registered_taps >= REGISTERED_TAPS_SIZE) {