Enable chordal hold, remove RPI and permissive_hold

- Define CHORDAL_HOLD in config.h (replaces PERMISSIVE_HOLD_PER_KEY)
- Add chordal_hold_layout[8][7] with L/R/*/0 hand assignments
- Remove all require-prior-idle (RPI) state and logic
- Remove get_permissive_hold callback
- Remove RPI threshold defines from timfee.h
- Chordal hold handles the opposite-hands rule at the firmware level:
  same-hand chord = tap, opposite-hand chord = hold

Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/f112c532-9be7-4612-8000-d680cd50c50c

Co-authored-by: timfee <3246342+timfee@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-14 18:53:45 +00:00 committed by GitHub
commit 0ae44543fa
Failed to generate hash of commit
3 changed files with 19 additions and 129 deletions

View file

@ -5,7 +5,7 @@
#define TAPPING_TERM_PER_KEY #define TAPPING_TERM_PER_KEY
#define QUICK_TAP_TERM 90 #define QUICK_TAP_TERM 90
#define QUICK_TAP_TERM_PER_KEY #define QUICK_TAP_TERM_PER_KEY
#define PERMISSIVE_HOLD_PER_KEY #define CHORDAL_HOLD
#define HOLD_ON_OTHER_KEY_PRESS_PER_KEY #define HOLD_ON_OTHER_KEY_PRESS_PER_KEY
#define RETRO_TAPPING #define RETRO_TAPPING
#define RETRO_TAPPING_PER_KEY #define RETRO_TAPPING_PER_KEY

View file

@ -5,24 +5,22 @@
#include "transactions.h" #include "transactions.h"
#endif #endif
// ── State for require-prior-idle (RPI) ── // ── Chordal Hold layout ──
// RPI must measure inter-key timing at PHYSICAL PRESS time, not at tap/hold // Defines hand assignments for the "opposite hands" tap-hold rule.
// resolution time. For mod-tap keys, process_record_user fires only after // 'L' = left hand, 'R' = right hand, '*' = either (thumb keys).
// action_tapping.c resolves the key (≈tapping_term ms later), so any timing // Matrix: 8 rows × 7 cols (4 rows per half, split keyboard).
// measured there is wrong: // Rows 0-2: left finger rows Rows 4-6: right finger rows
// - last_input_activity_elapsed() ≈ tapping_term → always < threshold → holds never work // Row 3: left thumb Row 7: right thumb
// - manual timer in process_record_user → skipped on return-false paths → stale timing const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = {
// causes the NEXT key to miss RPI and let QMK resolve as hold (CMD+key leak) {'L', 'L', 'L', 'L', 'L', 'L', 'L'}, // row 0: left top
// {'L', 'L', 'L', 'L', 'L', 'L', 'L'}, // row 1: left mid
// Fix: pre_process_record_user fires at physical press (before the tapping {'L', 'L', 'L', 'L', 'L', 'L', 0 }, // row 2: left bot
// state machine buffers the key). We capture the inter-key elapsed there and { 0, 0, 0, '*', '*', '*', 0 }, // row 3: left thumb
// tag the key position. Later, when process_record_user fires at resolution {'R', 'R', 'R', 'R', 'R', 'R', 'R'}, // row 4: right top
// time, we check the tag and force-tap if it was flagged. {'R', 'R', 'R', 'R', 'R', 'R', 'R'}, // row 5: right mid
static uint16_t rpi_prev_press_time = 0; {'R', 'R', 'R', 'R', 'R', 'R', 0 }, // row 6: right bot
static uint8_t rpi_prev_press_row = 255; { 0, 0, 0, '*', '*', '*', 0 }, // row 7: right thumb
static bool rpi_force_tap = false; };
static uint8_t rpi_force_tap_row = 255;
static uint8_t rpi_force_tap_col = 255;
// ── Combos (matching Vial config) ── // ── Combos (matching Vial config) ──
const uint16_t PROGMEM lparen_combo[] = {KC_R, KC_T, COMBO_END}; const uint16_t PROGMEM lparen_combo[] = {KC_R, KC_T, COMBO_END};
@ -206,54 +204,7 @@ void housekeeping_task_user(void) {
} }
#endif // OLED_ENABLE #endif // OLED_ENABLE
// ── Require-prior-idle: physical-press timing ── // ── OLED keypress tracking ──
// pre_process_record_user fires at the physical keypress moment, BEFORE the
// tapping state machine buffers the key. This is the correct place to measure
// inter-key timing for RPI.
//
// RPI only applies when the current key is on the SAME HAND as the previous
// key. Cross-hand sequences (e.g. right-thumb space → left-thumb CMD+V) skip
// RPI because accidental mod-holds from cross-hand rolling are rare and
// permissive_hold already guards against them.
bool pre_process_record_user(uint16_t keycode, keyrecord_t *record) {
if (record->event.pressed) {
uint16_t elapsed = timer_elapsed(rpi_prev_press_time);
// Determine if this key is on the same hand as the previous key
bool same_hand = false;
if (rpi_prev_press_row != 255) {
bool prev_left = rpi_prev_press_row < MATRIX_ROWS / 2;
bool curr_left = record->event.key.row < MATRIX_ROWS / 2;
same_hand = (prev_left == curr_left);
}
uint16_t threshold = 0;
if (same_hand) {
switch (keycode) {
case GU_SPC: threshold = RPI_SPACE; break;
case GU_BSP: threshold = RPI_BKSP; break;
case ESC_L2: threshold = RPI_ESC; break;
case MIN_L1: threshold = RPI_MINUS; break;
case CT_GRV: threshold = RPI_CTRL; break;
case CT_BSL: threshold = RPI_CTRL; break;
case AL_DEL: threshold = RPI_ALT; break;
case AL_ENT: threshold = RPI_ALT; break;
}
}
if (threshold > 0 && elapsed < threshold) {
rpi_force_tap = true;
rpi_force_tap_row = record->event.key.row;
rpi_force_tap_col = record->event.key.col;
}
rpi_prev_press_time = timer_read();
rpi_prev_press_row = record->event.key.row;
}
return true;
}
// ── Require-prior-idle + OLED keypress tracking ──
bool process_record_user(uint16_t keycode, keyrecord_t *record) { bool process_record_user(uint16_t keycode, keyrecord_t *record) {
#ifdef OLED_ENABLE #ifdef OLED_ENABLE
g_user_ontime = timer_read32(); g_user_ontime = timer_read32();
@ -261,7 +212,6 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
if (record->event.pressed) { if (record->event.pressed) {
#ifdef OLED_ENABLE #ifdef OLED_ENABLE
// Track every keypress for OLED (before RPI may intercept)
g_last_keycode = keycode; g_last_keycode = keycode;
if (record->event.key.row < MATRIX_ROWS / 2) { if (record->event.key.row < MATRIX_ROWS / 2) {
g_press_left++; g_press_left++;
@ -275,33 +225,6 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
(void)transaction_rpc_send(USER_SYNC_PRESSES, sizeof(ppkt), &ppkt); (void)transaction_rpc_send(USER_SYNC_PRESSES, sizeof(ppkt), &ppkt);
} }
#endif #endif
// Require-prior-idle: if pre_process_record_user flagged this key
// for force-tap (pressed too soon after the previous physical keypress),
// send the tap keycode now and suppress QMK's mod-tap/layer-tap handling.
if (rpi_force_tap &&
record->event.key.row == rpi_force_tap_row &&
record->event.key.col == rpi_force_tap_col) {
rpi_force_tap = false;
rpi_force_tap_row = 255;
rpi_force_tap_col = 255;
uint16_t tap_kc = KC_NO;
switch (keycode) {
case GU_SPC: tap_kc = KC_SPC; break;
case GU_BSP: tap_kc = KC_BSPC; break;
case ESC_L2: tap_kc = KC_ESC; break;
case MIN_L1: tap_kc = KC_MINS; break;
case CT_GRV: tap_kc = KC_GRV; break;
case CT_BSL: tap_kc = KC_BSLS; break;
case AL_DEL: tap_kc = KC_DEL; break;
case AL_ENT: tap_kc = KC_ENT; break;
}
if (tap_kc != KC_NO) {
tap_code(tap_kc);
return false;
}
}
} }
return true; return true;
} }
@ -317,33 +240,8 @@ uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) {
} }
} }
// ── Per-key permissive hold ──
// Layer-taps (pinky keys) and thumb mod-taps benefit from permissive
// hold: a nested tap (press+release another key within the hold)
// immediately selects the hold action.
bool get_permissive_hold(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case ESC_L2:
case MIN_L1:
case GU_BSP:
case GU_SPC:
case CT_GRV:
case CT_BSL:
case AL_DEL:
case AL_ENT:
return true;
default:
return false;
}
}
// ── Per-key hold on other key press ── // ── Per-key hold on other key press ──
// Disabled for all keys. We rely on permissive_hold instead, which only // Disabled; chordal hold handles the opposite-hands rule.
// resolves as hold when another key completes a full tap (press+release)
// within the hold — not just a press. This prevents the "release overlap"
// problem where rolling from a quick backspace tap into the next letter
// triggers CMD+letter instead of backspace+letter, while still making
// shortcuts like CMD+Z and CMD+SHIFT+V reliable (they are nested taps).
bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) { bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) {
return false; return false;
} }

View file

@ -10,11 +10,3 @@
#define GU_SPC RGUI_T(KC_SPC) #define GU_SPC RGUI_T(KC_SPC)
#define AL_ENT RALT_T(KC_ENT) #define AL_ENT RALT_T(KC_ENT)
#define CT_BSL RCTL_T(KC_BSLS) #define CT_BSL RCTL_T(KC_BSLS)
// ── Require-prior-idle thresholds (ms) ──
#define RPI_SPACE 150
#define RPI_BKSP 150
#define RPI_ESC 125
#define RPI_MINUS 150
#define RPI_CTRL 125
#define RPI_ALT 150