feat(q5_max): Add speculative hold for caps_mod key

This commit is contained in:
2026-04-22 22:36:16 -04:00
parent 363b43cafd
commit 73de490e53
3 changed files with 43 additions and 51 deletions
@@ -17,7 +17,16 @@
// COMM+DOT+SLSH fallback combo fires regardless of the active layer.
#define COMBO_ONLY_FROM_LAYER 0
// Pressing the Shift key continues Caps Word and inverts the shift state
#define CAPS_WORD_INVERT_ON_SHIFT
// Default tapping term for mod-tap, layer-tap, and tap-dance keys.
#define TAPPING_TERM 200
// Allow per-key overrides via get_tapping_term() in keymap.c.
#define TAPPING_TERM_PER_KEY
// Use right CTRL key to neutralize modifier taps when cancelled.
#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_RIGHT_CTRL
// Neutralize left ALT and left GUI (Default value)
#define MODS_TO_NEUTRALIZE {MOD_BIT(KC_LEFT_ALT), MOD_BIT(KC_LEFT_GUI)}
@@ -41,10 +41,11 @@ enum custom_keycodes {
LCK_FN4, // Lock/unlock FN4
LCK_CTL, // Lock/unlock KEEB_CTL
LCK_BASE, // Clear all locks and return to BASE
CAPS_MOD, // Tap=ESC, hold=Ctrl, Shift=CapsLock, Alt=CapsWord, GUI=Autocorrect
BSP_DEL, // Tap=Backspace, Shift+Tap=Delete
};
#define CAPS_MOD MT(MOD_LCTL, KC_ESC)
// Declare layers early so the HID functions below can reference KEEB_CTL.
enum layers {
BASE,
@@ -192,10 +193,7 @@ bool kc_raw_hid_rx_kb(uint8_t *data, uint8_t length) {
resp[HID_OFF_SRC] = HID_DEV_Q5MAX;
resp[HID_OFF_FLAGS] = HID_FLAG_RESPONSE;
#ifdef LK_WIRELESS_ENABLE
resp[HID_PAYLOAD(HID_BATT_OFF_LEVEL)] =
(get_transport() & TRANSPORT_WIRELESS)
? battery_get_percentage()
: HID_BATT_UNAVAILABLE;
resp[HID_PAYLOAD(HID_BATT_OFF_LEVEL)] = (get_transport() & TRANSPORT_WIRELESS) ? battery_get_percentage() : HID_BATT_UNAVAILABLE;
#else
resp[HID_PAYLOAD(HID_BATT_OFF_LEVEL)] = HID_BATT_UNAVAILABLE;
#endif
@@ -211,9 +209,7 @@ bool kc_raw_hid_rx_kb(uint8_t *data, uint8_t length) {
}
// CAPS_MOD state: tap=ESC, hold=Ctrl, Shift+tap=CapsLock, Alt+tap=CapsWord, GUI+tap=Autocorrect
static bool caps_mod_held = false;
static bool caps_mod_ctrl_registered = false;
static uint16_t caps_mod_timer = 0;
// (Refactored to use MT(MOD_LCTL, KC_ESC) with custom tap logic)
// clang-format off
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
@@ -350,38 +346,28 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case CAPS_MOD:
if (record->event.pressed) {
caps_mod_held = true;
caps_mod_timer = timer_read();
// If a real modifier is held, send a dummy key so the OS sees
// modifier+key rather than a bare modifier hold/tap. Without
// this, the OS never receives any keycode while the modifier is
// down and treats the eventual modifier release as a tap (e.g.
// GUI opening the app menu). KC_F24 is harmless and universally
// ignored by applications.
if (get_mods() & (MOD_MASK_GUI | MOD_MASK_ALT | MOD_MASK_SHIFT)) {
register_code(KC_F24);
unregister_code(KC_F24);
}
} else {
if (caps_mod_ctrl_registered) {
unregister_code(KC_LCTL);
caps_mod_ctrl_registered = false;
} else {
// Custom tap logic: only intercept if it's a TAP AND a modifier is held.
// If it's a pure hold (Ctrl) or a pure tap (Esc), return true to let
// the MT() core handle it.
if (record->tap.count > 0 && record->event.pressed) {
uint8_t mods = get_mods();
if (mods & (MOD_MASK_GUI | MOD_MASK_ALT)) {
// Neutralize the modifier hold so releasing GUI/Alt doesn't
// trigger an OS "tap" action (like opening the Start menu).
tap_code(DUMMY_MOD_NEUTRALIZER_KEYCODE);
if (mods & MOD_MASK_GUI) {
autocorrect_toggle();
} else if (mods & MOD_MASK_ALT) {
caps_word_toggle();
} else if (mods & MOD_MASK_SHIFT) {
tap_code(KC_CAPS); // Shift still held → host sees Shift+CapsLock (toggles on most OSes)
} else {
tap_code(KC_ESC);
caps_word_toggle();
}
return false; // suppress default Esc tap
} else if (mods & MOD_MASK_SHIFT) {
tap_code(KC_CAPS);
return false; // suppress default Esc tap
}
}
caps_mod_held = false; // cleared in both hold and tap paths
}
return false;
return true; // let core handle Esc tap or Ctrl hold
case LCK_FN1:
case LCK_FN2:
@@ -466,10 +452,6 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
}
void matrix_scan_user(void) {
if (caps_mod_held && !caps_mod_ctrl_registered && timer_elapsed(caps_mod_timer) > TAPPING_TERM) {
caps_mod_ctrl_registered = true;
register_code(KC_LCTL);
}
if (alt_tab_active && timer_elapsed(alt_tab_timer) > ALT_TAB_TIMEOUT) {
unregister_code(KC_LALT);
alt_tab_active = false;
@@ -493,7 +475,7 @@ void matrix_scan_user(void) {
// BASE stays dark; each FN/control layer gets a distinct colour.
#if defined(RGB_MATRIX_ENABLE)
bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) {
#ifdef DIP_SWITCH_ENABLE
# ifdef DIP_SWITCH_ENABLE
// Win-side override: paint all LEDs white so the user gets a clean white
// backlight regardless of which RGB effect is active. The effect keeps
// ticking internally and resumes the moment the switch returns to Mac side.
@@ -504,7 +486,7 @@ bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) {
rgb_matrix_set_color(i, 255, 255, 255);
}
}
#endif
# endif
switch (get_highest_layer(layer_state)) {
case FN1:
+2 -1
View File
@@ -18,7 +18,8 @@
"nkro" : true,
"rgb_matrix": true,
"raw" : true,
"send_string": true
"send_string": true,
"speculative_hold": true
},
"matrix_pins": {
"cols": ["C6", "C7", "C8", "A14", "A15", "C10", "C11", "C13", "C14", "C15", "C0", "C1", "C2", "C3", "A0", "A1", "A2", "A3", "B10"],