Compare commits

...

2 Commits

Author SHA1 Message Date
e3fa6244a7 Support hold-to-repeat for rewritten modifier combos
Replace tap_code() with register_code()/unregister_code() in the modifier
rewrite system so that holding a rewritten key combo (e.g. Ctrl+Left →
Alt+Left on Mac) produces auto-repeat instead of a single tap. Track active
rewrites to correctly restore modifiers on release, handling edge cases like
the modifier being released before the trigger key.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 07:19:51 -07:00
8a6819effc Replace right FN with dedicated CK_NOREMAP key to bypass remaps 2026-03-23 19:53:18 -07:00

View File

@@ -11,6 +11,7 @@ void system76_ec_rgb_eeprom(bool write);
enum custom_keycodes { enum custom_keycodes {
CK_OSMODE = SAFE_RANGE, // cycle OS mode CK_OSMODE = SAFE_RANGE, // cycle OS mode
CK_RGBMOD, // cycle RGB animation CK_RGBMOD, // cycle RGB animation
CK_NOREMAP, // hold to bypass all remaps
}; };
typedef struct { typedef struct {
@@ -36,34 +37,115 @@ static uint8_t normalize_mods(uint8_t mods) {
} }
// Check if current mods exactly match a rule's `from` mods. // Check if current mods exactly match a rule's `from` mods.
// Returns false if FN layer is active (skip rewrites when FN + any modifier is held).
static bool mods_match(uint8_t mods, mod_rewrite_t rule) { static bool mods_match(uint8_t mods, mod_rewrite_t rule) {
if (layer_state_is(1) && get_mods() != 0) return false;
return normalize_mods(mods) == normalize_mods(rule.from); return normalize_mods(mods) == normalize_mods(rule.from);
} }
// Rewrite modifier combos: swap exact `from` mod types to `to` mods, tap keycode, restore. // Active rewrite tracking for hold-to-repeat support.
//
// The old tap_code() approach sent an immediate press+release, so holding
// e.g. Ctrl+Left (remapped to Alt+Left on Mac) produced a single word-jump
// instead of repeating. Now rewrite_mods()/rewrite_mods_and_key() use
// register_code() to keep the key held, and this struct tracks the state
// needed to clean up correctly on release.
//
// Only one rewrite can be active at a time (only one key auto-repeats).
typedef struct {
uint16_t trigger_keycode; // physical key (e.g. KC_LEFT) — 0 when inactive
uint8_t output_keycode; // key registered with host (may differ from trigger)
uint8_t from_mods; // normalized mods that were removed
uint8_t to_mods; // normalized mods that were added
uint8_t saved_mods; // exact get_mods() at press time, for restoration
// updated as from-mods are released during hold
} active_rewrite_t;
static active_rewrite_t active_rw = {0};
// Undo an active rewrite: restore saved modifiers and unregister the held key.
// saved_mods starts as the exact get_mods() at press time, but is updated as
// individual from-mods are released during the hold, so it always reflects
// what should actually be active after teardown. No phantom mods.
// Mods are restored before unregister_code so only one USB report is sent.
static void teardown_active_rewrite(void) {
if (!active_rw.trigger_keycode) return;
set_mods(active_rw.saved_mods);
unregister_code(active_rw.output_keycode);
active_rw.trigger_keycode = 0;
}
// Intercept release events for active rewrites. Called at the top of
// process_record_user (after OS_MODE_NONE/no_remap bail-outs).
//
// Handles two cases:
// 1. The rewritten key itself is released — clean up via teardown_active_rewrite().
// 2. The original modifier is released while the key is still held —
// remove it from saved_mods so teardown won't restore a phantom modifier.
//
// Returns false to consume the event, true to continue normal processing.
static bool process_rewrite_release(uint16_t keycode, keyrecord_t *record) {
if (!active_rw.trigger_keycode) return true;
if (!record->event.pressed) {
if (keycode == active_rw.trigger_keycode) {
teardown_active_rewrite();
return false;
}
if (IS_MODIFIER_KEYCODE(keycode) &&
(normalize_mods(MOD_BIT(keycode)) & active_rw.from_mods)) {
active_rw.saved_mods &= ~MOD_BIT(keycode);
return false;
}
}
return true;
}
// Rewrite modifier combos: swap exact `from` mods to `to` mods and hold keycode.
// Left/right variants of the same modifier are treated as equivalent. // Left/right variants of the same modifier are treated as equivalent.
// Returns true if rewrite occurred. //
// Uses register_code() instead of tap_code() so the key stays held for
// auto-repeat. The key is unregistered later by process_rewrite_release()
// when the physical key is released. If another rewrite is already active,
// it is cleaned up first (only one key can auto-repeat at a time).
//
// Returns true if a rewrite was applied (caller should return false to
// suppress the original keycode).
static bool rewrite_mods(uint16_t keycode, mod_rewrite_t rule) { static bool rewrite_mods(uint16_t keycode, mod_rewrite_t rule) {
uint8_t mods = get_mods(); uint8_t mods = get_mods();
if (mods_match(mods, rule)) { if (mods_match(mods, rule)) {
set_mods(normalize_mods(rule.to)); if (active_rw.trigger_keycode) teardown_active_rewrite();
tap_code(keycode); active_rw = (active_rewrite_t){
set_mods(mods); .trigger_keycode = keycode,
.output_keycode = keycode,
.from_mods = normalize_mods(rule.from),
.to_mods = normalize_mods(rule.to),
.saved_mods = mods,
};
set_mods(active_rw.to_mods);
register_code(keycode);
return true; return true;
} }
return false; return false;
} }
// Rewrite modifier combo AND change the keycode sent. // Like rewrite_mods(), but also changes the keycode sent to the host.
// Returns true if rewrite occurred. // trigger_keycode is the physical key pressed (used to match the release
static bool rewrite_mods_and_key(mod_rewrite_t rule, uint16_t keycode_out) { // event later), while keycode_out is what the host actually receives.
// e.g. Home (trigger) -> Cmd+Left (output): trigger_keycode=KC_HOME,
// keycode_out=KC_LEFT, rule=NONE_TO_SUPER.
static bool rewrite_mods_and_key(uint16_t trigger_keycode, mod_rewrite_t rule, uint16_t keycode_out) {
uint8_t mods = get_mods(); uint8_t mods = get_mods();
if (mods_match(mods, rule)) { if (mods_match(mods, rule)) {
set_mods(normalize_mods(rule.to)); if (active_rw.trigger_keycode) teardown_active_rewrite();
tap_code(keycode_out); active_rw = (active_rewrite_t){
set_mods(mods); .trigger_keycode = trigger_keycode,
.output_keycode = keycode_out,
.from_mods = normalize_mods(rule.from),
.to_mods = normalize_mods(rule.to),
.saved_mods = mods,
};
set_mods(active_rw.to_mods);
register_code(keycode_out);
return true; return true;
} }
return false; return false;
@@ -89,7 +171,7 @@ ________________________________________________________________________________
| SHIFT | Z | X | C | V | B | N | M | < | > | ? | SHIFT | UP | | SHIFT | Z | X | C | V | B | N | M | < | > | ? | SHIFT | UP |
|________________|________|________|________|________|________|________|________|________|________|________|____________|________|_________ |________________|________|________|________|________|________|________|________|________|________|________|____________|________|_________
| | | | | | | | | | | | | | | | | | | | | | | | | | | |
| CTRL | FN | LGUI | LALT | SPACE | SPACE | RALT | RCTRL | FN | | LEFT | DOWN | RIGHT | | CTRL | FN | LGUI | LALT | SPACE | SPACE | RALT | RCTRL | NOREMAP | | LEFT | DOWN | RIGHT |
|____________|________|_______|________|_________________|_________________|________|________|_____________| |________|________|________| |____________|________|_______|________|_________________|_________________|________|________|_____________| |________|________|________|
*/ */
@@ -99,7 +181,7 @@ ________________________________________________________________________________
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_PGDN, KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_PGDN,
KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, KC_END, KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, KC_END,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP,
KC_LCTL, MO(1), KC_LGUI, KC_LALT, KC_SPC, KC_SPC, KC_RALT, KC_RCTL, MO(1), KC_LEFT, KC_DOWN, KC_RGHT KC_LCTL, MO(1), KC_LGUI, KC_LALT, KC_SPC, KC_SPC, KC_RALT, KC_RCTL, CK_NOREMAP, KC_LEFT, KC_DOWN, KC_RGHT
), ),
/* Layer 1, function layer /* Layer 1, function layer
@@ -120,7 +202,7 @@ ________________________________________________________________________________
| x | | | | | | | | | | | | PGUP | | x | | | | | | | | | | | | PGUP |
|________________|________|________|________|________|________|________|________|________|________|________|____________|________|_________ |________________|________|________|________|________|________|________|________|________|________|________|____________|________|_________
| | | | | | | | | | | | | | | | | | | | | | | | | | | |
| x | | x | x | | | x | x | | | HOME | PGDN | END | | x | | x | x | | | x | x | x | | HOME | PGDN | END |
|____________|________|_______|________|_________________|_________________|________|________|_____________| |________|________|________| |____________|________|_______|________|_________________|_________________|________|________|_____________| |________|________|________|
*/ */
@@ -130,7 +212,7 @@ ________________________________________________________________________________
KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO,
KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO,
KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_PGUP, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_PGUP,
KC_NO, KC_TRNS, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_TRNS, KC_HOME, KC_PGDN, KC_END KC_NO, KC_TRNS, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_HOME, KC_PGDN, KC_END
), ),
[2] = LAYOUT( [2] = LAYOUT(
@@ -152,20 +234,17 @@ ________________________________________________________________________________
), ),
}; };
// Override layer resolution: any modifier forces layer 0 (ignores FN)
uint16_t keymap_key_to_keycode(uint8_t layer, keypos_t key) {
if (os_mode != OS_MODE_NONE && get_mods()) {
layer = 0;
}
return keycode_at_keymap_location(layer, key.row, key.col);
}
static bool super_tapped = false; static bool super_tapped = false;
static bool no_remap = false;
bool process_record_user(uint16_t keycode, keyrecord_t *record) { bool process_record_user(uint16_t keycode, keyrecord_t *record) {
// Safety: no rewrites in none mode
if (os_mode == OS_MODE_NONE) return true;
// Cycle OS mode (FN+Del) // Cycle OS mode (FN+Del)
if (keycode == CK_OSMODE) { if (keycode == CK_OSMODE) {
if (record->event.pressed) { if (record->event.pressed) {
teardown_active_rewrite();
os_mode = (os_mode + 1) % 4; os_mode = (os_mode + 1) % 4;
os_mode_manual = true; os_mode_manual = true;
rgb_matrix_mode_noeeprom(RGB_MATRIX_SOLID_COLOR); rgb_matrix_mode_noeeprom(RGB_MATRIX_SOLID_COLOR);
@@ -193,8 +272,18 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
return false; return false;
} }
// No rewrites in none mode // Hold CK_NOREMAP to bypass all remaps
if (os_mode == OS_MODE_NONE) return true; if (keycode == CK_NOREMAP) {
no_remap = record->event.pressed;
if (no_remap) teardown_active_rewrite();
return false;
}
// No rewrites when noremap held
if (no_remap) return true;
// Handle release events for active modifier rewrites (key repeat support)
if (!process_rewrite_release(keycode, record)) return false;
// Any other key pressed while GUI held means it's being used as a modifier // Any other key pressed while GUI held means it's being used as a modifier
if (record->event.pressed && keycode != KC_LGUI) { if (record->event.pressed && keycode != KC_LGUI) {
@@ -281,25 +370,25 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
if (os_mode == OS_MODE_MAC && record->event.pressed) { if (os_mode == OS_MODE_MAC && record->event.pressed) {
// Terminal: Home -> Ctrl+A (line start) // Terminal: Home -> Ctrl+A (line start)
if (focused_app == APP_TERMINAL) { if (focused_app == APP_TERMINAL) {
if (rewrite_mods_and_key(NONE_TO_CTRL, KC_A)) return false; if (rewrite_mods_and_key(keycode, NONE_TO_CTRL, KC_A)) return false;
} }
if (rewrite_mods_and_key(NONE_TO_SUPER, KC_LEFT)) return false; if (rewrite_mods_and_key(keycode, NONE_TO_SUPER, KC_LEFT)) return false;
} }
break; break;
case KC_END: case KC_END:
if (os_mode == OS_MODE_MAC && record->event.pressed) { if (os_mode == OS_MODE_MAC && record->event.pressed) {
// Terminal: End -> Ctrl+E (line end) // Terminal: End -> Ctrl+E (line end)
if (focused_app == APP_TERMINAL) { if (focused_app == APP_TERMINAL) {
if (rewrite_mods_and_key(NONE_TO_CTRL, KC_E)) return false; if (rewrite_mods_and_key(keycode, NONE_TO_CTRL, KC_E)) return false;
} }
if (rewrite_mods_and_key(NONE_TO_SUPER, KC_RGHT)) return false; if (rewrite_mods_and_key(keycode, NONE_TO_SUPER, KC_RGHT)) return false;
} }
break; break;
// Mac mode rewrites: // Mac mode rewrites:
// Alt+F4 -> Super+Q (quit app) // Alt+F4 -> Super+Q (quit app)
case KC_F4: case KC_F4:
if (os_mode == OS_MODE_MAC && record->event.pressed) { if (os_mode == OS_MODE_MAC && record->event.pressed) {
if (rewrite_mods_and_key(ALT_TO_SUPER, KC_Q)) return false; if (rewrite_mods_and_key(keycode, ALT_TO_SUPER, KC_Q)) return false;
} }
break; break;
// Mac mode rewrites: // Mac mode rewrites:
@@ -318,9 +407,9 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
if (os_mode == OS_MODE_MAC) { if (os_mode == OS_MODE_MAC) {
if (rewrite_mods(keycode, SUPER_TO_CTRL)) return false; if (rewrite_mods(keycode, SUPER_TO_CTRL)) return false;
} else if (os_mode == OS_MODE_LINUX) { } else if (os_mode == OS_MODE_LINUX) {
if (rewrite_mods_and_key(SUPER_TO_SUPER, KC_W)) return false; if (rewrite_mods_and_key(keycode, SUPER_TO_SUPER, KC_W)) return false;
} else if (os_mode == OS_MODE_WINDOWS) { } else if (os_mode == OS_MODE_WINDOWS) {
if (rewrite_mods_and_key(SUPER_TO_SUPER, KC_TAB)) return false; if (rewrite_mods_and_key(keycode, SUPER_TO_SUPER, KC_TAB)) return false;
} }
} }
break; break;