···779779780780[Auto Shift](features/auto_shift) has its own version of `retro tapping` called `retro shift`. It is extremely similar to `retro tapping`, but holding the key past `AUTO_SHIFT_TIMEOUT` results in the value it sends being shifted. Other configurations also affect it differently; see [here](features/auto_shift#retro-shift) for more information.
781781782782+### Speculative Hold
783783+784784+Speculative Hold makes mod-tap keys more responsive by applying the modifier instantly on keydown, before the tap-hold decision is made. This is especially useful for actions like Shift+Click with a mouse, which can feel laggy with standard mod-taps.
785785+786786+The firmware holds the modifier speculatively. Once the key's behavior is settled:
787787+788788+* If held, the modifier remains active as expected until the key is released.
789789+* If tapped, the speculative modifier is canceled just before the tapping keycode is sent.
790790+791791+Speculative Hold applies the modifier early but does not change the underlying tap-hold decision logic. Speculative Hold is compatible to use in combination with any other tap-hold options.
792792+793793+To enable Speculative Hold, add the following to your `config.h`:
794794+795795+```c
796796+#define SPECULATIVE_HOLD
797797+```
798798+799799+By default, Speculative Hold applies to mod-taps using Shift, Ctrl, or Shift + Ctrl. You can override this behavior by defining the `get_speculative_hold()` callback in your keymap, for instance:
800800+801801+```c
802802+bool get_speculative_hold(uint16_t keycode, keyrecord_t* record) {
803803+ switch (keycode) { // These keys may be speculatively held.
804804+ case LCTL_T(KC_ESC):
805805+ case LSFT_T(KC_Z):
806806+ case RSFT_T(KC_SLSH):
807807+ return true;
808808+ }
809809+ return false; // Disable otherwise.
810810+}
811811+```
812812+813813+Some operating systems or applications assign actions to tapping a modifier key by itself, e.g., tapping GUI to open a start menu. Because Speculative Hold sends a lone modifier key press in some cases, it can falsely trigger these actions. To prevent this, set `DUMMY_MOD_NEUTRALIZER_KEYCODE` (and optionally `MODS_TO_NEUTRALIZE`) in your `config.h` in the same way as described above for [Retro Tapping](#retro-tapping).
814814+782815## Why do we include the key record for the per key functions?
783816784817One thing that you may notice is that we include the key record for all of the "per key" functions, and may be wondering why we do that.
···66#include "action_tapping.h"
77#include "action_util.h"
88#include "keycode.h"
99+#include "keycode_config.h"
910#include "quantum_keycodes.h"
1011#include "timer.h"
1212+#include "wait.h"
11131214#ifndef NO_ACTION_TAPPING
1315···5153}
5254# endif
53555656+# ifdef SPECULATIVE_HOLD
5757+typedef struct {
5858+ keypos_t key;
5959+ uint8_t mods;
6060+} speculative_key_t;
6161+# define SPECULATIVE_KEYS_SIZE 8
6262+static speculative_key_t speculative_keys[SPECULATIVE_KEYS_SIZE] = {};
6363+static uint8_t num_speculative_keys = 0;
6464+static uint8_t prev_speculative_mods = 0;
6565+static uint8_t speculative_mods = 0;
6666+6767+/** Handler to be called on incoming press events. */
6868+static void speculative_key_press(keyrecord_t *record);
6969+# endif // SPECULATIVE_HOLD
7070+5471# if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
5572# define REGISTERED_TAPS_SIZE 8
5673// Array of tap-hold keys that have been settled as tapped but not yet released.
···129146 * FIXME: Needs doc
130147 */
131148void action_tapping_process(keyrecord_t record) {
149149+# ifdef SPECULATIVE_HOLD
150150+ prev_speculative_mods = speculative_mods;
151151+ if (record.event.pressed) {
152152+ speculative_key_press(&record);
153153+ }
154154+# endif // SPECULATIVE_HOLD
155155+132156 if (process_tapping(&record)) {
133157 if (IS_EVENT(record.event)) {
134158 ac_dprintf("processed: ");
···144168 tapping_key = (keyrecord_t){0};
145169 }
146170 }
171171+172172+# ifdef SPECULATIVE_HOLD
173173+ if (speculative_mods != prev_speculative_mods) {
174174+ send_keyboard_report();
175175+ }
176176+# endif // SPECULATIVE_HOLD
147177148178 // process waiting_buffer
149179 if (IS_EVENT(record.event) && waiting_buffer_head != waiting_buffer_tail) {
···707737 }
708738 }
709739}
740740+741741+# ifdef SPECULATIVE_HOLD
742742+static void debug_speculative_keys(void) {
743743+ ac_dprintf("mods = { ");
744744+ for (int8_t i = 0; i < num_speculative_keys; ++i) {
745745+ ac_dprintf("%02X ", speculative_keys[i].mods);
746746+ }
747747+ ac_dprintf("}, keys = { ");
748748+ for (int8_t i = 0; i < num_speculative_keys; ++i) {
749749+ ac_dprintf("%02X%02X ", speculative_keys[i].key.row, speculative_keys[i].key.col);
750750+ }
751751+ ac_dprintf("}\n");
752752+}
753753+754754+// Find key in speculative_keys. Returns num_speculative_keys if not found.
755755+static int8_t speculative_keys_find(keypos_t key) {
756756+ uint8_t i;
757757+ for (i = 0; i < num_speculative_keys; ++i) {
758758+ if (KEYEQ(speculative_keys[i].key, key)) {
759759+ break;
760760+ }
761761+ }
762762+ return i;
763763+}
764764+765765+static void speculative_key_press(keyrecord_t *record) {
766766+ if (num_speculative_keys >= SPECULATIVE_KEYS_SIZE) { // Overflow!
767767+ ac_dprintf("SPECULATIVE KEYS OVERFLOW: IGNORING EVENT\n");
768768+ return; // Don't trigger: speculative_keys is full.
769769+ }
770770+ if (speculative_keys_find(record->event.key) < num_speculative_keys) {
771771+ return; // Don't trigger: key is already in speculative_keys.
772772+ }
773773+774774+ const uint16_t keycode = get_record_keycode(record, false);
775775+ if (!IS_QK_MOD_TAP(keycode)) {
776776+ return; // Don't trigger: not a mod-tap key.
777777+ }
778778+779779+ uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode));
780780+ if ((mods & 0x10) != 0) { // Unpack 5-bit mods to 8-bit representation.
781781+ mods <<= 4;
782782+ }
783783+ if ((~(get_mods() | speculative_mods) & mods) == 0) {
784784+ return; // Don't trigger: mods are already active.
785785+ }
786786+787787+ // Don't do Speculative Hold when there are non-speculated buffered events,
788788+ // since that could result in sending keys out of order.
789789+ for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) {
790790+ if (!waiting_buffer[i].tap.speculated) {
791791+ return;
792792+ }
793793+ }
794794+795795+ if (get_speculative_hold(keycode, record)) {
796796+ record->tap.speculated = true;
797797+ speculative_mods |= mods;
798798+ // Remember the keypos and mods associated with this key.
799799+ speculative_keys[num_speculative_keys] = (speculative_key_t){
800800+ .key = record->event.key,
801801+ .mods = mods,
802802+ };
803803+ ++num_speculative_keys;
804804+805805+ ac_dprintf("Speculative Hold: ");
806806+ debug_speculative_keys();
807807+ }
808808+}
809809+810810+uint8_t get_speculative_mods(void) {
811811+ return speculative_mods;
812812+}
813813+814814+__attribute__((weak)) bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) {
815815+ const uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode));
816816+ return (mods & (MOD_LCTL | MOD_LSFT)) == mods;
817817+}
818818+819819+void speculative_key_settled(keyrecord_t *record) {
820820+ if (num_speculative_keys == 0) {
821821+ return; // Early return when there are no active speculative keys.
822822+ }
823823+824824+ uint8_t i = speculative_keys_find(record->event.key);
825825+826826+ const uint16_t keycode = get_record_keycode(record, false);
827827+ if (IS_QK_MOD_TAP(keycode) && record->tap.count == 0) { // MT hold press.
828828+ if (i < num_speculative_keys) {
829829+ --num_speculative_keys;
830830+ const uint8_t cleared_mods = speculative_keys[i].mods;
831831+832832+ if (num_speculative_keys) {
833833+ speculative_mods &= ~cleared_mods;
834834+ // Don't call send_keyboard_report() here; allow default
835835+ // handling to reapply the mod before the next report.
836836+837837+ // Remove the ith entry from speculative_keys.
838838+ for (uint8_t j = i; j < num_speculative_keys; ++j) {
839839+ speculative_keys[j] = speculative_keys[j + 1];
840840+ }
841841+ } else {
842842+ speculative_mods = 0;
843843+ }
844844+845845+ ac_dprintf("Speculative Hold: settled %02x, ", cleared_mods);
846846+ debug_speculative_keys();
847847+ }
848848+ } else { // Tap press event; cancel speculatively-held mod.
849849+ if (i >= num_speculative_keys) {
850850+ i = 0;
851851+ }
852852+853853+ // Clear mods for the ith key and all keys that follow.
854854+ uint8_t cleared_mods = 0;
855855+ for (uint8_t j = i; j < num_speculative_keys; ++j) {
856856+ cleared_mods |= speculative_keys[j].mods;
857857+ }
858858+859859+ num_speculative_keys = i; // Remove ith and following entries.
860860+861861+ if ((prev_speculative_mods & cleared_mods) != 0) {
862862+# ifdef DUMMY_MOD_NEUTRALIZER_KEYCODE
863863+ neutralize_flashing_modifiers(get_mods() | prev_speculative_mods);
864864+# endif // DUMMY_MOD_NEUTRALIZER_KEYCODE
865865+ }
866866+867867+ if (num_speculative_keys) {
868868+ speculative_mods &= ~cleared_mods;
869869+ } else {
870870+ speculative_mods = 0;
871871+ }
872872+873873+ send_keyboard_report();
874874+ wait_ms(TAP_CODE_DELAY);
875875+876876+ ac_dprintf("Speculative Hold: canceled %02x, ", cleared_mods);
877877+ debug_speculative_keys();
878878+ }
879879+}
880880+# endif // SPECULATIVE_HOLD
710881711882# if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
712883static void registered_taps_add(keypos_t key) {
+30
quantum/action_tapping.h
···4646bool get_retro_tapping(uint16_t keycode, keyrecord_t *record);
4747bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record);
48484949+#ifdef SPECULATIVE_HOLD
5050+/** Gets the currently active speculative mods. */
5151+uint8_t get_speculative_mods(void);
5252+5353+/**
5454+ * Callback to say if a mod-tap key may be speculatively held.
5555+ *
5656+ * By default, speculative hold is enabled for mod-tap keys where the mod is
5757+ * Ctrl, Shift, and Ctrl+Shift for either hand.
5858+ *
5959+ * @param keycode Keycode of the mod-tap key.
6060+ * @param record Record associated with the mod-tap press event.
6161+ * @return True if the mod-tap key may be speculatively held.
6262+ */
6363+bool get_speculative_hold(uint16_t keycode, keyrecord_t *record);
6464+6565+/**
6666+ * Handler to be called on press events after tap-holds are settled.
6767+ *
6868+ * This function is to be called in process_record() in action.c, that is, just
6969+ * after tap-hold events are settled as either tapped or held. When `record`
7070+ * corresponds to a speculatively-held key, the speculative mod is cleared.
7171+ *
7272+ * @param record Record associated with the mod-tap press event.
7373+ */
7474+void speculative_key_settled(keyrecord_t *record);
7575+#else
7676+# define get_speculative_mods() 0
7777+#endif // SPECULATIVE_HOLD
7878+4979#ifdef CHORDAL_HOLD
5080/**
5181 * Callback to say when a key chord before the tapping term may be held.
···11+/* Copyright 2022 Vladislav Kucheriavykh
22+ * Copyright 2025 Google LLC
33+ *
44+ * This program is free software: you can redistribute it and/or modify
55+ * it under the terms of the GNU General Public License as published by
66+ * the Free Software Foundation, either version 2 of the License, or
77+ * (at your option) any later version.
88+ *
99+ * This program is distributed in the hope that it will be useful,
1010+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1111+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1212+ * GNU General Public License for more details.
1313+ *
1414+ * You should have received a copy of the GNU General Public License
1515+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1616+ */
1717+1818+#pragma once
1919+2020+#include "test_common.h"
2121+2222+#define SPECULATIVE_HOLD
2323+#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24
···11+# Copyright 2022 Vladislav Kucheriavykh
22+# Copyright 2025 Google LLC
33+#
44+# This program is free software: you can redistribute it and/or modify
55+# it under the terms of the GNU General Public License as published by
66+# the Free Software Foundation, either version 2 of the License, or
77+# (at your option) any later version.
88+#
99+# This program is distributed in the hope that it will be useful,
1010+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1111+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1212+# GNU General Public License for more details.
1313+#
1414+# You should have received a copy of the GNU General Public License
1515+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616+1717+KEY_OVERRIDE_ENABLE = yes
1818+MAGIC_ENABLE = yes
1919+2020+INTROSPECTION_KEYMAP_C = test_keymap.c
2121+
···11+// Copyright 2025 Google LLC
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// https://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+#include "quantum.h"
1616+1717+// Shift + Esc = Home
1818+const key_override_t home_esc_override = ko_make_basic(MOD_MASK_SHIFT, KC_ESC, KC_HOME);
1919+2020+const key_override_t *key_overrides[] = {&home_esc_override};
···11+/* Copyright 2022 Vladislav Kucheriavykh
22+ * Copyright 2025 Google LLC
33+ *
44+ * This program is free software: you can redistribute it and/or modify
55+ * it under the terms of the GNU General Public License as published by
66+ * the Free Software Foundation, either version 2 of the License, or
77+ * (at your option) any later version.
88+ *
99+ * This program is distributed in the hope that it will be useful,
1010+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1111+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1212+ * GNU General Public License for more details.
1313+ *
1414+ * You should have received a copy of the GNU General Public License
1515+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1616+ */
1717+1818+#pragma once
1919+2020+#include "test_common.h"
2121+2222+#define SPECULATIVE_HOLD
2323+#define FLOW_TAP_TERM 150
2424+#define PERMISSIVE_HOLD
2525+#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24
···11+# Copyright 2022 Vladislav Kucheriavykh
22+#
33+# This program is free software: you can redistribute it and/or modify
44+# it under the terms of the GNU General Public License as published by
55+# the Free Software Foundation, either version 2 of the License, or
66+# (at your option) any later version.
77+#
88+# This program is distributed in the hope that it will be useful,
99+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1010+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1111+# GNU General Public License for more details.
1212+#
1313+# You should have received a copy of the GNU General Public License
1414+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1515+
···11+/* Copyright 2022 Isaac Elenbaas
22+ * Copyright 2025 Google LLC
33+ *
44+ * This program is free software: you can redistribute it and/or modify
55+ * it under the terms of the GNU General Public License as published by
66+ * the Free Software Foundation, either version 2 of the License, or
77+ * (at your option) any later version.
88+ *
99+ * This program is distributed in the hope that it will be useful,
1010+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1111+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1212+ * GNU General Public License for more details.
1313+ *
1414+ * You should have received a copy of the GNU General Public License
1515+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1616+ */
1717+1818+#pragma once
1919+2020+#include "test_common.h"
2121+2222+#define SPECULATIVE_HOLD
2323+#define PERMISSIVE_HOLD
2424+2525+#define RETRO_SHIFT 2 * TAPPING_TERM
2626+// releases between AUTO_SHIFT_TIMEOUT and TAPPING_TERM are not tested
2727+#define AUTO_SHIFT_TIMEOUT TAPPING_TERM
2828+#define AUTO_SHIFT_MODIFIERS
2929+3030+#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24
···11+# Copyright 2022 Isaac Elenbaas
22+# Copyright 2025 Google LLC
33+#
44+# This program is free software: you can redistribute it and/or modify
55+# it under the terms of the GNU General Public License as published by
66+# the Free Software Foundation, either version 2 of the License, or
77+# (at your option) any later version.
88+#
99+# This program is distributed in the hope that it will be useful,
1010+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1111+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1212+# GNU General Public License for more details.
1313+#
1414+# You should have received a copy of the GNU General Public License
1515+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616+1717+AUTO_SHIFT_ENABLE = yes
···11+/* Copyright 2022 Vladislav Kucheriavykh
22+ * Copyright 2025 Google LLC
33+ *
44+ * This program is free software: you can redistribute it and/or modify
55+ * it under the terms of the GNU General Public License as published by
66+ * the Free Software Foundation, either version 2 of the License, or
77+ * (at your option) any later version.
88+ *
99+ * This program is distributed in the hope that it will be useful,
1010+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1111+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1212+ * GNU General Public License for more details.
1313+ *
1414+ * You should have received a copy of the GNU General Public License
1515+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1616+ */
1717+1818+#pragma once
1919+2020+#include "test_common.h"
2121+2222+#define SPECULATIVE_HOLD
2323+#define RETRO_TAPPING
2424+#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24
···11+# Copyright 2022 Vladislav Kucheriavykh
22+# Copyright 2025 Google LLC
33+#
44+# This program is free software: you can redistribute it and/or modify
55+# it under the terms of the GNU General Public License as published by
66+# the Free Software Foundation, either version 2 of the License, or
77+# (at your option) any later version.
88+#
99+# This program is distributed in the hope that it will be useful,
1010+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1111+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1212+# GNU General Public License for more details.
1313+#
1414+# You should have received a copy of the GNU General Public License
1515+# along with this program. If not, see <http://www.gnu.org/licenses/>.