From 66a4ecac05088eb51efcdb223d535c4ce75b942f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 00:11:21 +0000 Subject: [PATCH 01/17] Replace QMK logo with large layer digit on both OLED halves; add per-layer RGB matrix lighting Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/cc846d9d-7039-4650-9167-e05fa242c89f Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/bitmaps.h | 79 +++++++++++++++++++++++++++++++ users/timfee/rules.mk | 1 + users/timfee/timfee.c | 102 +++++++++++++++++++++++++++++++++++------ 3 files changed, 168 insertions(+), 14 deletions(-) diff --git a/users/timfee/bitmaps.h b/users/timfee/bitmaps.h index 27c3bd1b..58c2c603 100644 --- a/users/timfee/bitmaps.h +++ b/users/timfee/bitmaps.h @@ -19,3 +19,82 @@ static const char SCROLL_LOCK_BITMAP [] PROGMEM = { 0x00, 0xc0, 0x20, 0x38, 0x24, 0x32, 0xaa, 0xaa, 0xaa, 0xaa, 0xb2, 0x24, 0x38, 0x20, 0xc0, 0x00, 0x00, 0x3f, 0x40, 0x40, 0x40, 0x53, 0x52, 0x52, 0x54, 0x54, 0x4c, 0x40, 0x40, 0x40, 0x3f, 0x00 }; + +// ── Large digit bitmaps (32×32, 4 OLED pages) for layer display ── +#define LARGE_DIGIT_WIDTH 32 +#define LARGE_DIGIT_PAGES 4 + +static const char PROGMEM DIGIT_0[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const char PROGMEM DIGIT_1[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const char PROGMEM DIGIT_2[] = { + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, +}; + +static const char PROGMEM DIGIT_3[] = { + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const char PROGMEM DIGIT_4[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const char PROGMEM DIGIT_5[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const char PROGMEM DIGIT_6[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const char PROGMEM DIGIT_7[] = { + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const char PROGMEM DIGIT_8[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const char PROGMEM DIGIT_9[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const char * const PROGMEM LARGE_DIGITS[] = { + DIGIT_0, DIGIT_1, DIGIT_2, DIGIT_3, DIGIT_4, + DIGIT_5, DIGIT_6, DIGIT_7, DIGIT_8, DIGIT_9, +}; diff --git a/users/timfee/rules.mk b/users/timfee/rules.mk index 9bccca09..41e04cf2 100644 --- a/users/timfee/rules.mk +++ b/users/timfee/rules.mk @@ -4,3 +4,4 @@ OLED_DRIVER = ssd1306 OLED_TRANSPORT = i2c WPM_ENABLE = yes KEYCODE_STRING_ENABLE = yes +RGB_MATRIX_ENABLE = yes diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index cbfe7a2e..1c04adc7 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -36,10 +36,6 @@ combo_t key_combos[COMBO_COUNT] = { static const uint8_t OLED_WIDTH = OLED_DISPLAY_HEIGHT; -static const char PROGMEM QMK_LOGO_1[] = { 0x81, 0x82, 0x83, 0x84, 0x00 }; -static const char PROGMEM QMK_LOGO_2[] = { 0xA1, 0xA2, 0xA3, 0xA4, 0x00 }; -static const char PROGMEM QMK_LOGO_3[] = { 0xC1, 0xC2, 0xC3, 0xC4, 0x00 }; - typedef struct { bool oled_on; } oled_state_m2s_t; typedef struct { uint16_t keycode; } lastkey_m2s_t; typedef struct { uint32_t left; uint32_t right; } presses_m2s_t; @@ -89,6 +85,21 @@ static void oled_print_right_aligned(const char *text, uint8_t width) { oled_write(text, false); } +// ── Large digit renderer (32×32 bitmap, centered) ── +static void render_large_layer_digit(uint8_t start_page) { + uint8_t layer = get_highest_layer(layer_state); + if (layer > 9) layer = 9; + const char *bitmap = (const char *)pgm_read_ptr(&LARGE_DIGITS[layer]); + uint8_t x_offset = (OLED_WIDTH - LARGE_DIGIT_WIDTH) / 2; + for (uint8_t page = 0; page < LARGE_DIGIT_PAGES; page++) { + for (uint8_t col = 0; col < LARGE_DIGIT_WIDTH; col++) { + oled_write_raw_byte( + pgm_read_byte(&bitmap[page * LARGE_DIGIT_WIDTH + col]), + (start_page + page) * OLED_WIDTH + x_offset + col); + } + } +} + // ── Info renderers ── static void print_current_layer(uint8_t row) { char buf[8]; @@ -402,11 +413,8 @@ bool oled_task_user(void) { oled_print_right_aligned(get_keycode_string(unwrap_keycode(g_last_keycode)), g_oled_max_char); - // QMK logo - oled_set_cursor(0, 13); oled_write_P(QMK_LOGO_1, false); - oled_set_cursor(0, 14); oled_write_P(QMK_LOGO_2, false); - oled_set_cursor(0, 15); oled_write_P(QMK_LOGO_3, false); - oled_set_cursor(7, 15); oled_write_P(PSTR("QMK"), false); + // Large layer digit (centered, pages 12–15) + render_large_layer_digit(12); } // ── Right half ── else { @@ -424,14 +432,80 @@ bool oled_task_user(void) { oled_write_P(PSTR("Right:"), false); print_balance(8, pct_right); - // QMK logo - oled_set_cursor(0, 13); oled_write_P(QMK_LOGO_1, false); - oled_set_cursor(0, 14); oled_write_P(QMK_LOGO_2, false); - oled_set_cursor(0, 15); oled_write_P(QMK_LOGO_3, false); - oled_set_cursor(7, 15); oled_write_P(PSTR("QMK"), false); + // Large layer digit (centered, pages 12–15) + render_large_layer_digit(12); } return false; } #endif // OLED_ENABLE + +// ═══════════════════════════════════════════════════════════════════ +// RGB Matrix – per-layer LED colours +// ═══════════════════════════════════════════════════════════════════ +#ifdef RGB_MATRIX_ENABLE + +// LED indices derived from keyboard.json rgb_matrix.layout +// Left half (0-22) – matrix position → LED index: +// [3,5]=0 [2,5]=1 [1,5]=2 [0,5]=3 [0,4]=4 [1,4]=5 [2,4]=6 +// [3,4]=7 [3,3]=8 [2,3]=9 [1,3]=10 [0,3]=11 [0,2]=12 [1,2]=13 +// [2,2]=14 [2,1]=15 [1,1]=16 [0,1]=17 [0,0]=18 [1,0]=19 [2,0]=20 +// [0,6]=21 [1,6]=22 +// Right half (23-45): +// [7,5]=23 [6,5]=24 [5,5]=25 [4,5]=26 [4,4]=27 [5,4]=28 [6,4]=29 +// [7,4]=30 [7,3]=31 [6,3]=32 [5,3]=33 [4,3]=34 [4,2]=35 [5,2]=36 +// [6,2]=37 [6,1]=38 [5,1]=39 [4,1]=40 [4,0]=41 [5,0]=42 [6,0]=43 +// [4,6]=44 [5,6]=45 + +// Layer 1: left-side symbol keys (!, @, #, $, %, ^, &, *, (, )) +static const uint8_t L1_SYMBOL_LEDS[] = { 17, 12, 11, 4, 3, 16, 13, 10, 5, 2 }; +// Layer 1: right-side number keys (7-9, 4-6, 1-3, comma/0/dot on thumbs) +static const uint8_t L1_NUMBER_LEDS[] = { 26, 27, 34, 25, 28, 33, 24, 29, 32, 23, 30, 31 }; +// Layer 2: left-side F-keys (F1-F10) +static const uint8_t L2_FKEY_LEDS[] = { 17, 12, 11, 4, 3, 16, 13, 10, 5, 2 }; +// Layer 2: right-side arrow keys (all 3 rows: alt-arrows, arrows, gui-arrows) +static const uint8_t L2_ARROW_LEDS[] = { 26, 27, 34, 35, 25, 28, 33, 36, 24, 29, 32, 37 }; + +static inline bool led_in_set(uint8_t led, const uint8_t *set, uint8_t count) { + for (uint8_t j = 0; j < count; j++) { + if (set[j] == led) return true; + } + return false; +} + +bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { + uint8_t layer = get_highest_layer(layer_state); + + for (uint8_t i = led_min; i < led_max; i++) { + switch (layer) { + case 1: + if (led_in_set(i, L1_SYMBOL_LEDS, sizeof(L1_SYMBOL_LEDS))) { + rgb_matrix_set_color(i, 0, 255, 0); // bright green + } else if (led_in_set(i, L1_NUMBER_LEDS, sizeof(L1_NUMBER_LEDS))) { + rgb_matrix_set_color(i, 255, 0, 0); // bright red + } else { + rgb_matrix_set_color(i, 200, 0, 0); // base red + } + break; + + case 2: + if (led_in_set(i, L2_FKEY_LEDS, sizeof(L2_FKEY_LEDS))) { + rgb_matrix_set_color(i, 148, 0, 211); // purple + } else if (led_in_set(i, L2_ARROW_LEDS, sizeof(L2_ARROW_LEDS))) { + rgb_matrix_set_color(i, 0, 0, 255); // bright blue + } else { + rgb_matrix_set_color(i, 200, 0, 0); // base red + } + break; + + default: + rgb_matrix_set_color(i, 200, 0, 0); // solid red + break; + } + } + + return false; +} + +#endif // RGB_MATRIX_ENABLE From ea7d94b9a063aa0f1ddfba7de9fc823c92cb5fbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 00:12:14 +0000 Subject: [PATCH 02/17] Clarify RGB color comments for consistency Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/cc846d9d-7039-4650-9167-e05fa242c89f Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/timfee.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index 1c04adc7..a15513aa 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -481,26 +481,26 @@ bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { switch (layer) { case 1: if (led_in_set(i, L1_SYMBOL_LEDS, sizeof(L1_SYMBOL_LEDS))) { - rgb_matrix_set_color(i, 0, 255, 0); // bright green + rgb_matrix_set_color(i, 0, 255, 0); // bright green (symbols) } else if (led_in_set(i, L1_NUMBER_LEDS, sizeof(L1_NUMBER_LEDS))) { - rgb_matrix_set_color(i, 255, 0, 0); // bright red + rgb_matrix_set_color(i, 255, 0, 0); // bright red (numbers) } else { - rgb_matrix_set_color(i, 200, 0, 0); // base red + rgb_matrix_set_color(i, 200, 0, 0); // dim red (inactive) } break; case 2: if (led_in_set(i, L2_FKEY_LEDS, sizeof(L2_FKEY_LEDS))) { - rgb_matrix_set_color(i, 148, 0, 211); // purple + rgb_matrix_set_color(i, 148, 0, 211); // purple (F-keys) } else if (led_in_set(i, L2_ARROW_LEDS, sizeof(L2_ARROW_LEDS))) { - rgb_matrix_set_color(i, 0, 0, 255); // bright blue + rgb_matrix_set_color(i, 0, 0, 255); // bright blue (arrows) } else { - rgb_matrix_set_color(i, 200, 0, 0); // base red + rgb_matrix_set_color(i, 200, 0, 0); // dim red (inactive) } break; default: - rgb_matrix_set_color(i, 200, 0, 0); // solid red + rgb_matrix_set_color(i, 200, 0, 0); // solid red (base layer) break; } } From 48c207c13540664ffb718c11c357367a8ae35bf5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:38:35 +0000 Subject: [PATCH 03/17] Disable retro tapping on GU_BSP to fix spurious backspace after Cmd+click in Figma Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/f1a0d611-da80-4c58-a482-1ddc3acb083f Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/config.h | 2 -- users/timfee/timfee.c | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/users/timfee/config.h b/users/timfee/config.h index 77c211ee..75a91967 100644 --- a/users/timfee/config.h +++ b/users/timfee/config.h @@ -7,8 +7,6 @@ #define QUICK_TAP_TERM_PER_KEY #define PERMISSIVE_HOLD_PER_KEY #define HOLD_ON_OTHER_KEY_PRESS_PER_KEY -#define RETRO_TAPPING -#define RETRO_TAPPING_PER_KEY // ── Combos ── #define COMBO_COUNT 8 diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index a15513aa..c57f9eea 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -294,12 +294,10 @@ bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) { } } -// ── Per-key retro tapping (yeet only) ── +// ── Per-key retro tapping — disabled to prevent unintended tap action +// firing after modifier-only holds (e.g. Cmd+click in apps like Figma) ── bool get_retro_tapping(uint16_t keycode, keyrecord_t *record) { - switch (keycode) { - case GU_BSP: return true; - default: return false; - } + return false; } // ── Per-key quick tap term ── From 7cb53d85b9f59f57ee29600e31e915e1feed497d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:45:55 +0000 Subject: [PATCH 04/17] Remove layer-tap from Z/Slash; extend RPI to all mod-tap keys; disable hold-on-other-key-press Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/2a2d454b-14c3-485c-9968-a5297bba60e4 Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- .../corne_choc_pro/keymaps/timfee/keymap.c | 2 +- users/timfee/timfee.c | 53 ++++++++++--------- users/timfee/timfee.h | 8 ++- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/keyboards/keebart/corne_choc_pro/keymaps/timfee/keymap.c b/keyboards/keebart/corne_choc_pro/keymaps/timfee/keymap.c index 564f31bd..2e4afbba 100644 --- a/keyboards/keebart/corne_choc_pro/keymaps/timfee/keymap.c +++ b/keyboards/keebart/corne_choc_pro/keymaps/timfee/keymap.c @@ -6,7 +6,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { [0] = LAYOUT_split_3x6_3( ESC_L2, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, MIN_L1, KC_TAB, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_EQL, - KC_LSFT, Z_L1, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, SL_L2, KC_QUOT, + KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_QUOT, CT_GRV, AL_DEL, GU_BSP, GU_SPC, AL_ENT, CT_BSL ), diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index c57f9eea..c06079c2 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -15,8 +15,8 @@ const uint16_t PROGMEM lbkt_combo[] = {KC_F, KC_G, COMBO_END}; const uint16_t PROGMEM rbkt_combo[] = {KC_H, KC_J, COMBO_END}; const uint16_t PROGMEM lbrace_combo[] = {KC_V, KC_B, COMBO_END}; const uint16_t PROGMEM rbrace_combo[] = {KC_N, KC_M, COMBO_END}; -const uint16_t PROGMEM pipe_combo[] = {Z_L1, KC_X, COMBO_END}; -const uint16_t PROGMEM bslh_combo[] = {SL_L2, KC_QUOT, COMBO_END}; +const uint16_t PROGMEM pipe_combo[] = {KC_Z, KC_X, COMBO_END}; +const uint16_t PROGMEM bslh_combo[] = {KC_SLSH, KC_QUOT, COMBO_END}; combo_t key_combos[COMBO_COUNT] = { COMBO(lparen_combo, KC_LPRN), @@ -229,18 +229,6 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { return false; } break; - case Z_L1: - if (elapsed < RPI_Z) { - tap_code(KC_Z); - return false; - } - break; - case SL_L2: - if (elapsed < RPI_SLASH) { - tap_code(KC_SLSH); - return false; - } - break; case ESC_L2: if (elapsed < RPI_ESC) { tap_code(KC_ESC); @@ -253,6 +241,30 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { return false; } break; + case CT_GRV: + if (elapsed < RPI_CTRL) { + tap_code(KC_GRV); + return false; + } + break; + case CT_BSL: + if (elapsed < RPI_CTRL) { + tap_code(KC_BSLS); + return false; + } + break; + case AL_DEL: + if (elapsed < RPI_ALT) { + tap_code(KC_DEL); + return false; + } + break; + case AL_ENT: + if (elapsed < RPI_ALT) { + tap_code(KC_ENT); + return false; + } + break; } last_key_time = timer_read(); @@ -265,8 +277,6 @@ uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { switch (keycode) { case GU_BSP: return 100; case GU_SPC: return 150; - case Z_L1: - case SL_L2: return 120; case AL_DEL: case AL_ENT: return 130; default: return TAPPING_TERM; @@ -277,21 +287,16 @@ uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { bool get_permissive_hold(uint16_t keycode, keyrecord_t *record) { switch (keycode) { case ESC_L2: - case Z_L1: case MIN_L1: - case SL_L2: return true; default: return false; } } -// ── Per-key hold on other key press (yeet only) ── +// ── Per-key hold on other key press — disabled for all keys ── bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) { - switch (keycode) { - case GU_BSP: return true; - default: return false; - } + return false; } // ── Per-key retro tapping — disabled to prevent unintended tap action @@ -304,8 +309,6 @@ bool get_retro_tapping(uint16_t keycode, keyrecord_t *record) { uint16_t get_quick_tap_term(uint16_t keycode, keyrecord_t *record) { switch (keycode) { case GU_SPC: return 90; - case Z_L1: - case SL_L2: return 80; default: return QUICK_TAP_TERM; } } diff --git a/users/timfee/timfee.h b/users/timfee/timfee.h index 90eba2cf..d4041f69 100644 --- a/users/timfee/timfee.h +++ b/users/timfee/timfee.h @@ -3,9 +3,7 @@ // ── Key aliases (layer numbers match Vial: L1=symbols, L2=nav) ── #define ESC_L2 LT(2, KC_ESC) -#define Z_L1 LT(1, KC_Z) #define MIN_L1 LT(1, KC_MINS) -#define SL_L2 LT(2, KC_SLSH) #define CT_GRV LCTL_T(KC_GRV) #define AL_DEL LALT_T(KC_DEL) #define GU_BSP LGUI_T(KC_BSPC) @@ -15,8 +13,8 @@ // ── Require-prior-idle thresholds (ms) ── #define RPI_SPACE 150 -#define RPI_Z 125 -#define RPI_SLASH 150 +#define RPI_BKSP 150 #define RPI_ESC 125 #define RPI_MINUS 150 -#define RPI_BKSP 150 +#define RPI_CTRL 125 +#define RPI_ALT 150 From bb425b3cfe1effe3440deaf2157d75a35f1aa1eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:58:11 +0000 Subject: [PATCH 05/17] Fix cross-half RPI timing with SPLIT_ACTIVITY_ENABLE + last_input_activity_elapsed Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/c129e415-070b-4b3e-b26d-2262310412aa Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/config.h | 1 + users/timfee/timfee.c | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/users/timfee/config.h b/users/timfee/config.h index 75a91967..f28ca664 100644 --- a/users/timfee/config.h +++ b/users/timfee/config.h @@ -29,5 +29,6 @@ // ── Split transport ── #define SPLIT_TRANSPORT_MIRROR #define SPLIT_WPM_ENABLE +#define SPLIT_ACTIVITY_ENABLE #define SPLIT_WATCHDOG_TIMEOUT 4000 #define SPLIT_TRANSACTION_IDS_USER USER_SYNC_OLED_STATE, USER_SYNC_LASTKEY, USER_SYNC_PRESSES diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index c06079c2..10969ba6 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -6,7 +6,8 @@ #endif // ── State for require-prior-idle ── -static uint16_t last_key_time = 0; +// (last_key_time removed: using last_input_activity_elapsed() instead, which +// is synced from both halves via SPLIT_ACTIVITY_ENABLE, fixing cross-half RPI) // ── Combos (matching Vial config) ── const uint16_t PROGMEM lparen_combo[] = {KC_R, KC_T, COMBO_END}; @@ -213,8 +214,12 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { } #endif - // Require-prior-idle handling - uint16_t elapsed = timer_elapsed(last_key_time); + // Require-prior-idle: force tap if pressed too soon after any recent + // activity on either half. SPLIT_ACTIVITY_ENABLE synchronizes the + // underlying activity timestamp from the slave to the master at the + // transport level, so last_input_activity_elapsed() reflects the most + // recent keypress on either half — not just the master side. + uint32_t elapsed = last_input_activity_elapsed(); switch (keycode) { case GU_SPC: @@ -266,8 +271,6 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { } break; } - - last_key_time = timer_read(); } return true; } From 847d7313b1dc5c72799c7a89693e36f95266ee42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 00:37:31 +0000 Subject: [PATCH 06/17] Fix RPI breaking all mod-tap holds: revert to manual last_key_time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit last_input_activity_elapsed() includes the mod-tap key's OWN matrix event. Since process_record_user fires ~tapping_term ms after the physical press (after action_tapping resolves tap/hold), the elapsed time is always ≈ tapping_term < RPI thresholds, so holds never work. Revert to manual last_key_time updated at the END of process_record_user. Remove SPLIT_ACTIVITY_ENABLE (not needed — master processes all keys from both halves). Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/14d8fea9-7e5d-40c4-9b47-abab68e5d4e2 Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/config.h | 1 - users/timfee/timfee.c | 22 ++++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/users/timfee/config.h b/users/timfee/config.h index f28ca664..75a91967 100644 --- a/users/timfee/config.h +++ b/users/timfee/config.h @@ -29,6 +29,5 @@ // ── Split transport ── #define SPLIT_TRANSPORT_MIRROR #define SPLIT_WPM_ENABLE -#define SPLIT_ACTIVITY_ENABLE #define SPLIT_WATCHDOG_TIMEOUT 4000 #define SPLIT_TRANSACTION_IDS_USER USER_SYNC_OLED_STATE, USER_SYNC_LASTKEY, USER_SYNC_PRESSES diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index 10969ba6..8ca0d203 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -6,8 +6,16 @@ #endif // ── State for require-prior-idle ── -// (last_key_time removed: using last_input_activity_elapsed() instead, which -// is synced from both halves via SPLIT_ACTIVITY_ENABLE, fixing cross-half RPI) +// Manual timestamp of the last key processed through process_record_user. +// This is intentionally NOT last_input_activity_elapsed() — that API reflects +// the most recent matrix scan change, which includes the current mod-tap key's +// OWN press. Since process_record_user fires ~tapping_term ms after the +// physical press (after tap/hold resolution), the elapsed time would always be +// ≈ tapping_term, which is less than the RPI thresholds — killing every hold. +// A manual variable updated at the END of process_record_user correctly +// captures the previous key's timing. This works for both halves because QMK +// split processes all key events on the master side. +static uint16_t last_key_time = 0; // ── Combos (matching Vial config) ── const uint16_t PROGMEM lparen_combo[] = {KC_R, KC_T, COMBO_END}; @@ -214,12 +222,8 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { } #endif - // Require-prior-idle: force tap if pressed too soon after any recent - // activity on either half. SPLIT_ACTIVITY_ENABLE synchronizes the - // underlying activity timestamp from the slave to the master at the - // transport level, so last_input_activity_elapsed() reflects the most - // recent keypress on either half — not just the master side. - uint32_t elapsed = last_input_activity_elapsed(); + // Require-prior-idle: force tap if pressed too soon after last key + uint16_t elapsed = timer_elapsed(last_key_time); switch (keycode) { case GU_SPC: @@ -271,6 +275,8 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { } break; } + + last_key_time = timer_read(); } return true; } From 0d1521ad4bf3b2371ab7738e89b12d14b32115bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 13:43:02 +0000 Subject: [PATCH 07/17] Fix RPI: move timing to pre_process_record_user for correct physical-press measurement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both previous approaches measured inter-key timing in process_record_user, which fires at tap/hold RESOLUTION time (not physical press time): - last_input_activity_elapsed(): always ≈ tapping_term → holds never worked - manual last_key_time: stale on return-false paths → CMD+key leak pre_process_record_user fires at the physical keypress moment, BEFORE the tapping state machine buffers the key. Flag the key position there, then check the flag in process_record_user at resolution time to force-tap. Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/40e6f36c-6ca7-4425-830c-0dac14106e87 Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/timfee.c | 136 ++++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 64 deletions(-) diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index 8ca0d203..56808e29 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -5,17 +5,23 @@ #include "transactions.h" #endif -// ── State for require-prior-idle ── -// Manual timestamp of the last key processed through process_record_user. -// This is intentionally NOT last_input_activity_elapsed() — that API reflects -// the most recent matrix scan change, which includes the current mod-tap key's -// OWN press. Since process_record_user fires ~tapping_term ms after the -// physical press (after tap/hold resolution), the elapsed time would always be -// ≈ tapping_term, which is less than the RPI thresholds — killing every hold. -// A manual variable updated at the END of process_record_user correctly -// captures the previous key's timing. This works for both halves because QMK -// split processes all key events on the master side. -static uint16_t last_key_time = 0; +// ── State for require-prior-idle (RPI) ── +// RPI must measure inter-key timing at PHYSICAL PRESS time, not at tap/hold +// resolution time. For mod-tap keys, process_record_user fires only after +// action_tapping.c resolves the key (≈tapping_term ms later), so any timing +// measured there is wrong: +// - last_input_activity_elapsed() ≈ tapping_term → always < threshold → holds never work +// - manual timer in process_record_user → skipped on return-false paths → stale timing +// causes the NEXT key to miss RPI and let QMK resolve as hold (CMD+key leak) +// +// Fix: pre_process_record_user fires at physical press (before the tapping +// state machine buffers the key). We capture the inter-key elapsed there and +// tag the key position. Later, when process_record_user fires at resolution +// time, we check the tag and force-tap if it was flagged. +static uint16_t rpi_prev_press_time = 0; +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) ── const uint16_t PROGMEM lparen_combo[] = {KC_R, KC_T, COMBO_END}; @@ -199,6 +205,37 @@ void housekeeping_task_user(void) { } #endif // OLED_ENABLE +// ── Require-prior-idle: physical-press timing ── +// 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. +bool pre_process_record_user(uint16_t keycode, keyrecord_t *record) { + if (record->event.pressed) { + uint16_t elapsed = timer_elapsed(rpi_prev_press_time); + + uint16_t threshold = 0; + 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(); + } + return true; +} + // ── Require-prior-idle + OLED keypress tracking ── bool process_record_user(uint16_t keycode, keyrecord_t *record) { #ifdef OLED_ENABLE @@ -222,61 +259,32 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { } #endif - // Require-prior-idle: force tap if pressed too soon after last key - uint16_t elapsed = timer_elapsed(last_key_time); + // 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; - switch (keycode) { - case GU_SPC: - if (elapsed < RPI_SPACE) { - tap_code(KC_SPC); - return false; - } - break; - case GU_BSP: - if (elapsed < RPI_BKSP) { - tap_code(KC_BSPC); - return false; - } - break; - case ESC_L2: - if (elapsed < RPI_ESC) { - tap_code(KC_ESC); - return false; - } - break; - case MIN_L1: - if (elapsed < RPI_MINUS) { - tap_code(KC_MINS); - return false; - } - break; - case CT_GRV: - if (elapsed < RPI_CTRL) { - tap_code(KC_GRV); - return false; - } - break; - case CT_BSL: - if (elapsed < RPI_CTRL) { - tap_code(KC_BSLS); - return false; - } - break; - case AL_DEL: - if (elapsed < RPI_ALT) { - tap_code(KC_DEL); - return false; - } - break; - case AL_ENT: - if (elapsed < RPI_ALT) { - tap_code(KC_ENT); - return false; - } - break; + 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; + } } - - last_key_time = timer_read(); } return true; } From d1b49e5a2e299567c3eb683dae33d61d4432789d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:13:33 +0000 Subject: [PATCH 08/17] Fix retro tapping: re-add RETRO_TAPPING + RETRO_TAPPING_PER_KEY so get_retro_tapping (returns false) is called for all keys Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/83618c37-92f1-4f03-9f2a-02163323dbbe Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/config.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/users/timfee/config.h b/users/timfee/config.h index 75a91967..77c211ee 100644 --- a/users/timfee/config.h +++ b/users/timfee/config.h @@ -7,6 +7,8 @@ #define QUICK_TAP_TERM_PER_KEY #define PERMISSIVE_HOLD_PER_KEY #define HOLD_ON_OTHER_KEY_PRESS_PER_KEY +#define RETRO_TAPPING +#define RETRO_TAPPING_PER_KEY // ── Combos ── #define COMBO_COUNT 8 From 41de8dfa7b531aeda5c2de4afd06128783618193 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:30:07 +0000 Subject: [PATCH 09/17] Enable hold_on_other_key_press for thumb keys + increase COMBO_TERM to 50 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hold_on_other_key_press for thumb mod-tap keys (GU_BSP, GU_SPC, CT_GRV, AL_DEL, AL_ENT, CT_BSL): shortcuts like CMD+Z, CMD+SHIFT+V resolve immediately when the next key is pressed. RPI guard still prevents accidental modifier activation during fast typing. - permissive_hold also enabled for thumb keys as backup for nested taps. - COMBO_TERM 40 → 50: slightly more forgiving combo activation window without triggering on fast typing. Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/a215aefa-1750-491a-8a85-8745466bb539 Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/config.h | 2 +- users/timfee/timfee.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/users/timfee/config.h b/users/timfee/config.h index 77c211ee..fdc6a6ce 100644 --- a/users/timfee/config.h +++ b/users/timfee/config.h @@ -12,7 +12,7 @@ // ── Combos ── #define COMBO_COUNT 8 -#define COMBO_TERM 40 +#define COMBO_TERM 50 #define COMBO_ONLY_FROM_LAYER 0 // ── OLED (SSD1312 on Keebart Corne Choc Pro) ── diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index 56808e29..5094cc02 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -300,20 +300,43 @@ uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { } } -// ── Per-key permissive hold (pinky layer-taps only) ── +// ── 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 — disabled for all keys ── +// ── Per-key hold on other key press ── +// Enabled for thumb mod-tap keys so shortcuts like CMD+Z, CMD+SHIFT+V +// resolve immediately when the next key is pressed (no need to wait for +// tapping_term to expire). The RPI guard in pre_process_record_user +// still prevents accidental modifier activation during fast typing. bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) { - return false; + switch (keycode) { + 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 retro tapping — disabled to prevent unintended tap action From ddac648083f9451ce5f1a8cbb82a924159eac851 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:05:41 +0000 Subject: [PATCH 10/17] Fix release overlap: disable hold_on_other_key_press, rely on permissive_hold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hold_on_other_key_press resolved mod-tap as hold the instant ANY key was pressed — including rolling overlap when quickly tapping backspace then typing the next letter. This caused CMD+letter instead of backspace+letter. permissive_hold only resolves as hold when another key completes a full tap (press AND release) within the hold. This correctly distinguishes: - Nested tap (CMD+Z: hold GU_BSP, tap Z) → hold (CMD) ✓ - Rolling press (type: tap GU_BSP, overlap with next letter) → tap (BS) ✓ Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/69339f4f-1bae-4f6c-92d2-067980175947 Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/timfee.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index 5094cc02..66ebcd06 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -321,22 +321,14 @@ bool get_permissive_hold(uint16_t keycode, keyrecord_t *record) { } // ── Per-key hold on other key press ── -// Enabled for thumb mod-tap keys so shortcuts like CMD+Z, CMD+SHIFT+V -// resolve immediately when the next key is pressed (no need to wait for -// tapping_term to expire). The RPI guard in pre_process_record_user -// still prevents accidental modifier activation during fast typing. +// Disabled for all keys. We rely on permissive_hold instead, which only +// 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) { - switch (keycode) { - case GU_BSP: - case GU_SPC: - case CT_GRV: - case CT_BSL: - case AL_DEL: - case AL_ENT: - return true; - default: - return false; - } + return false; } // ── Per-key retro tapping — disabled to prevent unintended tap action From 2c6c58f674cadb2386e4784798189605526efb13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:19:35 +0000 Subject: [PATCH 11/17] Disable NKRO to fix phantom macOS volume controls Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/3f9911e9-1e44-4705-83e0-dd54234a3521 Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/rules.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/users/timfee/rules.mk b/users/timfee/rules.mk index 41e04cf2..d13023f6 100644 --- a/users/timfee/rules.mk +++ b/users/timfee/rules.mk @@ -5,3 +5,4 @@ OLED_TRANSPORT = i2c WPM_ENABLE = yes KEYCODE_STRING_ENABLE = yes RGB_MATRIX_ENABLE = yes +NKRO_ENABLE = no From 9b6f5f203a644a34806972b920d37ea5c5329ce1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:47:31 +0000 Subject: [PATCH 12/17] Fix cross-hand CMD+V: make RPI same-hand only Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/18731635-1080-449b-8957-211985b72dd5 Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/timfee.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index 66ebcd06..3c6e0b95 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -19,6 +19,7 @@ // tag the key position. Later, when process_record_user fires at resolution // time, we check the tag and force-tap if it was flagged. static uint16_t rpi_prev_press_time = 0; +static uint8_t rpi_prev_press_row = 255; static bool rpi_force_tap = false; static uint8_t rpi_force_tap_row = 255; static uint8_t rpi_force_tap_col = 255; @@ -209,20 +210,35 @@ void housekeeping_task_user(void) { // 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; - 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 (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) { @@ -232,6 +248,7 @@ bool pre_process_record_user(uint16_t keycode, keyrecord_t *record) { } rpi_prev_press_time = timer_read(); + rpi_prev_press_row = record->event.key.row; } return true; } From 46d4f4b78ec535a79c53954deb584d6dd43043e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:58:24 +0000 Subject: [PATCH 13/17] Disable EXTRAKEY to eliminate phantom volume keys during layer navigation Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/7a224981-1f4e-428d-95f1-711bb01d3eec Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/rules.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/users/timfee/rules.mk b/users/timfee/rules.mk index d13023f6..a0e4ba52 100644 --- a/users/timfee/rules.mk +++ b/users/timfee/rules.mk @@ -6,3 +6,4 @@ WPM_ENABLE = yes KEYCODE_STRING_ENABLE = yes RGB_MATRIX_ENABLE = yes NKRO_ENABLE = no +EXTRAKEY_ENABLE = no From 989f5f1049dca64598e5e0f6175e337af2dda0c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:30:09 +0000 Subject: [PATCH 14/17] Make repo self-contained: embed QMK firmware as submodule, remove external dependency on Keebart - Add Keebart/vial-qmk-corne-choc-pro (vial branch) as git submodule at qmk_firmware/ - Remove qmk_repo/qmk_ref from CI workflow (reusable workflow auto-detects submodule) - Rewrite README with self-contained setup, upstream pull, and cherry-pick instructions Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/1be9b3e0-bc94-471c-b013-1192141c550a Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- .github/workflows/build_binaries.yaml | 5 +- .gitmodules | 4 ++ README.md | 73 ++++++++++++++++++++++----- qmk_firmware | 1 + 4 files changed, 67 insertions(+), 16 deletions(-) create mode 100644 .gitmodules create mode 160000 qmk_firmware diff --git a/.github/workflows/build_binaries.yaml b/.github/workflows/build_binaries.yaml index f53c3c81..67103f27 100755 --- a/.github/workflows/build_binaries.yaml +++ b/.github/workflows/build_binaries.yaml @@ -9,9 +9,8 @@ jobs: build: name: 'QMK Userspace Build' uses: qmk/.github/.github/workflows/qmk_userspace_build.yml@main - with: - qmk_repo: Keebart/vial-qmk-corne-choc-pro - qmk_ref: vial + # Firmware lives in qmk_firmware/ submodule — no external repo needed. + # The reusable workflow detects the submodule and skips its own clone. publish: name: 'QMK Userspace Publish' diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..00746970 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "qmk_firmware"] + path = qmk_firmware + url = https://github.com/Keebart/vial-qmk-corne-choc-pro.git + branch = vial diff --git a/README.md b/README.md index 55a38834..3f54f715 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,71 @@ # QMK Userspace — Keebart Corne Choc Pro -## Setup +Self-contained repo: firmware (Keebart's QMK fork) is embedded as a submodule at `qmk_firmware/`. +No waiting on upstream — cherry-pick, merge, or patch anything you want right here. -1. Use [qmk/qmk_userspace](https://github.com/qmk/qmk_userspace) as a template to create your repo -2. Clone your new repo -3. Unzip this into it -4. Add the Keebart fork as a submodule: +## Quick start ```bash -git submodule add https://github.com/Keebart/vial-qmk-corne-choc-pro.git qmk_firmware -git submodule update --init --recursive +git clone --recursive https://github.com/timfee/qmk_userspace.git +cd qmk_userspace ``` -5. Commit and push: +GitHub Actions builds the `.uf2` automatically on push — download from the **Releases** tab. + +Flash: hold **Q** while plugging in the left half, drag `.uf2` onto `RPI-RP2`. Repeat with **P** for the right half. + +## Local builds ```bash -git add . -git commit -m "Initial keymap" -git push +pip install qmk +qmk setup -H qmk_firmware # point QMK at the submodule +qmk compile -kb keebart/corne_choc_pro/standard -km timfee ``` -6. GitHub Actions builds the .uf2 — download from the Releases tab -7. Flash: hold Q while plugging in left half, drag .uf2 onto RPI-RP2. Repeat with P for right. +## Pulling upstream changes + +The submodule tracks the `vial` branch of `Keebart/vial-qmk-corne-choc-pro`. + +```bash +# Pull latest from Keebart +cd qmk_firmware +git fetch origin +git merge origin/vial # or: git rebase origin/vial +cd .. +git add qmk_firmware +git commit -m "Update firmware submodule" +``` + +### Cherry-picking from other repos (e.g. Keebart/vial-qmk-keebart, qmk/qmk_firmware) + +```bash +cd qmk_firmware + +# Add any upstream remote once +git remote add upstream-qmk https://github.com/qmk/qmk_firmware.git +git remote add keebart-full https://github.com/Keebart/vial-qmk-keebart.git + +# Fetch and cherry-pick +git fetch keebart-full +git cherry-pick # e.g. OLED SSD1312 support + +cd .. +git add qmk_firmware +git commit -m "Cherry-pick: " +``` + +### Switching to your own firmware fork + +If you fork `Keebart/vial-qmk-corne-choc-pro` to your own GitHub account: + +```bash +cd qmk_firmware +git remote set-url origin https://github.com/YOUR_USER/vial-qmk-corne-choc-pro.git +git push origin vial # push your changes to your fork +cd .. +# Update .gitmodules to point at your fork +git config -f .gitmodules submodule.qmk_firmware.url \ + https://github.com/YOUR_USER/vial-qmk-corne-choc-pro.git +git add .gitmodules qmk_firmware +git commit -m "Point submodule at my firmware fork" +``` diff --git a/qmk_firmware b/qmk_firmware new file mode 160000 index 00000000..4649f864 --- /dev/null +++ b/qmk_firmware @@ -0,0 +1 @@ +Subproject commit 4649f864f15ba9f540ee83d66b50cb9fcd4f7e59 From 0ae44543fac0a032a085d36081faac10789ea5d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:53:45 +0000 Subject: [PATCH 15/17] 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> --- users/timfee/config.h | 2 +- users/timfee/timfee.c | 138 ++++++------------------------------------ users/timfee/timfee.h | 8 --- 3 files changed, 19 insertions(+), 129 deletions(-) diff --git a/users/timfee/config.h b/users/timfee/config.h index fdc6a6ce..fdb85827 100644 --- a/users/timfee/config.h +++ b/users/timfee/config.h @@ -5,7 +5,7 @@ #define TAPPING_TERM_PER_KEY #define QUICK_TAP_TERM 90 #define QUICK_TAP_TERM_PER_KEY -#define PERMISSIVE_HOLD_PER_KEY +#define CHORDAL_HOLD #define HOLD_ON_OTHER_KEY_PRESS_PER_KEY #define RETRO_TAPPING #define RETRO_TAPPING_PER_KEY diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index 3c6e0b95..05925453 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -5,24 +5,22 @@ #include "transactions.h" #endif -// ── State for require-prior-idle (RPI) ── -// RPI must measure inter-key timing at PHYSICAL PRESS time, not at tap/hold -// resolution time. For mod-tap keys, process_record_user fires only after -// action_tapping.c resolves the key (≈tapping_term ms later), so any timing -// measured there is wrong: -// - last_input_activity_elapsed() ≈ tapping_term → always < threshold → holds never work -// - manual timer in process_record_user → skipped on return-false paths → stale timing -// causes the NEXT key to miss RPI and let QMK resolve as hold (CMD+key leak) -// -// Fix: pre_process_record_user fires at physical press (before the tapping -// state machine buffers the key). We capture the inter-key elapsed there and -// tag the key position. Later, when process_record_user fires at resolution -// time, we check the tag and force-tap if it was flagged. -static uint16_t rpi_prev_press_time = 0; -static uint8_t rpi_prev_press_row = 255; -static bool rpi_force_tap = false; -static uint8_t rpi_force_tap_row = 255; -static uint8_t rpi_force_tap_col = 255; +// ── Chordal Hold layout ── +// Defines hand assignments for the "opposite hands" tap-hold rule. +// 'L' = left hand, 'R' = right hand, '*' = either (thumb keys). +// Matrix: 8 rows × 7 cols (4 rows per half, split keyboard). +// Rows 0-2: left finger rows Rows 4-6: right finger rows +// Row 3: left thumb Row 7: right thumb +const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = { + {'L', 'L', 'L', 'L', 'L', 'L', 'L'}, // row 0: left top + {'L', 'L', 'L', 'L', 'L', 'L', 'L'}, // row 1: left mid + {'L', 'L', 'L', 'L', 'L', 'L', 0 }, // row 2: left bot + { 0, 0, 0, '*', '*', '*', 0 }, // row 3: left thumb + {'R', 'R', 'R', 'R', 'R', 'R', 'R'}, // row 4: right top + {'R', 'R', 'R', 'R', 'R', 'R', 'R'}, // row 5: right mid + {'R', 'R', 'R', 'R', 'R', 'R', 0 }, // row 6: right bot + { 0, 0, 0, '*', '*', '*', 0 }, // row 7: right thumb +}; // ── Combos (matching Vial config) ── const uint16_t PROGMEM lparen_combo[] = {KC_R, KC_T, COMBO_END}; @@ -206,54 +204,7 @@ void housekeeping_task_user(void) { } #endif // OLED_ENABLE -// ── Require-prior-idle: physical-press timing ── -// 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 ── +// ── OLED keypress tracking ── bool process_record_user(uint16_t keycode, keyrecord_t *record) { #ifdef OLED_ENABLE g_user_ontime = timer_read32(); @@ -261,7 +212,6 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { if (record->event.pressed) { #ifdef OLED_ENABLE - // Track every keypress for OLED (before RPI may intercept) g_last_keycode = keycode; if (record->event.key.row < MATRIX_ROWS / 2) { 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); } #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; } @@ -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 ── -// Disabled for all keys. We rely on permissive_hold instead, which only -// 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). +// Disabled; chordal hold handles the opposite-hands rule. bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) { return false; } diff --git a/users/timfee/timfee.h b/users/timfee/timfee.h index d4041f69..4c9d62ab 100644 --- a/users/timfee/timfee.h +++ b/users/timfee/timfee.h @@ -10,11 +10,3 @@ #define GU_SPC RGUI_T(KC_SPC) #define AL_ENT RALT_T(KC_ENT) #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 From d1339355fd6ef1b34e37bd6daa9134fdc50999e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:16:13 +0000 Subject: [PATCH 16/17] Revert to pure userspace; fix chordal hold thumb keys and add layer-tap override - Remove qmk_firmware submodule, restore CI to build against external Keebart repo - Fix chordal_hold_layout: thumbs marked 'L'/'R' (not '*') so same-hand fast typing resolves as tap, cross-hand resolves as hold - Add get_chordal_hold() override: layer-taps always hold (for same-hand layer access), mod-taps use default cross-hand logic - Simplify README back to standard userspace overlay Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/2bf6f380-de28-4523-a92b-40d4f1aa2c25 Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- .github/workflows/build_binaries.yaml | 5 ++- .gitmodules | 4 -- README.md | 61 ++------------------------- qmk_firmware | 1 - users/timfee/timfee.c | 25 ++++++++--- 5 files changed, 26 insertions(+), 70 deletions(-) delete mode 100644 .gitmodules delete mode 160000 qmk_firmware diff --git a/.github/workflows/build_binaries.yaml b/.github/workflows/build_binaries.yaml index 67103f27..f53c3c81 100755 --- a/.github/workflows/build_binaries.yaml +++ b/.github/workflows/build_binaries.yaml @@ -9,8 +9,9 @@ jobs: build: name: 'QMK Userspace Build' uses: qmk/.github/.github/workflows/qmk_userspace_build.yml@main - # Firmware lives in qmk_firmware/ submodule — no external repo needed. - # The reusable workflow detects the submodule and skips its own clone. + with: + qmk_repo: Keebart/vial-qmk-corne-choc-pro + qmk_ref: vial publish: name: 'QMK Userspace Publish' diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 00746970..00000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "qmk_firmware"] - path = qmk_firmware - url = https://github.com/Keebart/vial-qmk-corne-choc-pro.git - branch = vial diff --git a/README.md b/README.md index 3f54f715..449da86d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,7 @@ # QMK Userspace — Keebart Corne Choc Pro -Self-contained repo: firmware (Keebart's QMK fork) is embedded as a submodule at `qmk_firmware/`. -No waiting on upstream — cherry-pick, merge, or patch anything you want right here. - -## Quick start - -```bash -git clone --recursive https://github.com/timfee/qmk_userspace.git -cd qmk_userspace -``` +QMK userspace overlay for the Keebart Corne Choc Pro. Builds against +[Keebart/vial-qmk-corne-choc-pro](https://github.com/Keebart/vial-qmk-corne-choc-pro) (vial branch). GitHub Actions builds the `.uf2` automatically on push — download from the **Releases** tab. @@ -18,54 +11,6 @@ Flash: hold **Q** while plugging in the left half, drag `.uf2` onto `RPI-RP2`. R ```bash pip install qmk -qmk setup -H qmk_firmware # point QMK at the submodule +qmk setup -H . --home $(pwd)/qmk_firmware qmk compile -kb keebart/corne_choc_pro/standard -km timfee ``` - -## Pulling upstream changes - -The submodule tracks the `vial` branch of `Keebart/vial-qmk-corne-choc-pro`. - -```bash -# Pull latest from Keebart -cd qmk_firmware -git fetch origin -git merge origin/vial # or: git rebase origin/vial -cd .. -git add qmk_firmware -git commit -m "Update firmware submodule" -``` - -### Cherry-picking from other repos (e.g. Keebart/vial-qmk-keebart, qmk/qmk_firmware) - -```bash -cd qmk_firmware - -# Add any upstream remote once -git remote add upstream-qmk https://github.com/qmk/qmk_firmware.git -git remote add keebart-full https://github.com/Keebart/vial-qmk-keebart.git - -# Fetch and cherry-pick -git fetch keebart-full -git cherry-pick # e.g. OLED SSD1312 support - -cd .. -git add qmk_firmware -git commit -m "Cherry-pick: " -``` - -### Switching to your own firmware fork - -If you fork `Keebart/vial-qmk-corne-choc-pro` to your own GitHub account: - -```bash -cd qmk_firmware -git remote set-url origin https://github.com/YOUR_USER/vial-qmk-corne-choc-pro.git -git push origin vial # push your changes to your fork -cd .. -# Update .gitmodules to point at your fork -git config -f .gitmodules submodule.qmk_firmware.url \ - https://github.com/YOUR_USER/vial-qmk-corne-choc-pro.git -git add .gitmodules qmk_firmware -git commit -m "Point submodule at my firmware fork" -``` diff --git a/qmk_firmware b/qmk_firmware deleted file mode 160000 index 4649f864..00000000 --- a/qmk_firmware +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4649f864f15ba9f540ee83d66b50cb9fcd4f7e59 diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index 05925453..4160e9d0 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -7,21 +7,36 @@ // ── Chordal Hold layout ── // Defines hand assignments for the "opposite hands" tap-hold rule. -// 'L' = left hand, 'R' = right hand, '*' = either (thumb keys). +// 'L' = left hand, 'R' = right hand. +// Thumbs are assigned to their physical hand so that same-hand fast +// typing (e.g. backspace → A) resolves as tap, while cross-hand +// shortcuts (e.g. left-thumb CMD + right-hand key) resolve as hold. // Matrix: 8 rows × 7 cols (4 rows per half, split keyboard). // Rows 0-2: left finger rows Rows 4-6: right finger rows // Row 3: left thumb Row 7: right thumb const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = { {'L', 'L', 'L', 'L', 'L', 'L', 'L'}, // row 0: left top {'L', 'L', 'L', 'L', 'L', 'L', 'L'}, // row 1: left mid - {'L', 'L', 'L', 'L', 'L', 'L', 0 }, // row 2: left bot - { 0, 0, 0, '*', '*', '*', 0 }, // row 3: left thumb + {'L', 'L', 'L', 'L', 'L', 'L', 0 }, // row 2: left bot + { 0, 0, 0, 'L', 'L', 'L', 0 }, // row 3: left thumb {'R', 'R', 'R', 'R', 'R', 'R', 'R'}, // row 4: right top {'R', 'R', 'R', 'R', 'R', 'R', 'R'}, // row 5: right mid - {'R', 'R', 'R', 'R', 'R', 'R', 0 }, // row 6: right bot - { 0, 0, 0, '*', '*', '*', 0 }, // row 7: right thumb + {'R', 'R', 'R', 'R', 'R', 'R', 0 }, // row 6: right bot + { 0, 0, 0, 'R', 'R', 'R', 0 }, // row 7: right thumb }; +// ── Chordal Hold per-key override ── +// Layer-tap keys (ESC_L2, MIN_L1) must always resolve as hold when +// another key is pressed so the layer activates — even for same-hand keys. +// Mod-tap keys use the default layout-based logic (cross-hand = hold). +bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t *tap_hold_record, + uint16_t other_keycode, keyrecord_t *other_record) { + if (IS_QK_LAYER_TAP(tap_hold_keycode)) { + return true; + } + return get_chordal_hold_default(tap_hold_record, other_record); +} + // ── Combos (matching Vial config) ── const uint16_t PROGMEM lparen_combo[] = {KC_R, KC_T, COMBO_END}; const uint16_t PROGMEM rparen_combo[] = {KC_Y, KC_U, COMBO_END}; From 5104ff89656983d3e029c750182e25b20e24e268 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:06:20 +0000 Subject: [PATCH 17/17] Fix Cmd+Shift+V: add timing-based same-hand hold for mod-taps in get_chordal_hold Agent-Logs-Url: https://github.com/timfee/qmk_userspace/sessions/682413db-c19e-483e-bdef-6b171183b6de Co-authored-by: timfee <3246342+timfee@users.noreply.github.com> --- users/timfee/timfee.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/users/timfee/timfee.c b/users/timfee/timfee.c index 4160e9d0..3a476b63 100644 --- a/users/timfee/timfee.c +++ b/users/timfee/timfee.c @@ -26,15 +26,35 @@ const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = { }; // ── Chordal Hold per-key override ── -// Layer-tap keys (ESC_L2, MIN_L1) must always resolve as hold when -// another key is pressed so the layer activates — even for same-hand keys. -// Mod-tap keys use the default layout-based logic (cross-hand = hold). +// Layer-tap keys always resolve as hold so the layer activates for +// same-hand keys. Cross-hand mod-tap chords always resolve as hold. +// Same-hand mod-tap chords use a timing heuristic: if the other key +// arrives within 150 ms of the tap-hold key press it is a fast typing +// roll (tap); otherwise the user is deliberately holding a modifier +// for a shortcut like Cmd+V or Cmd+Shift+V (hold). +#define CHORDAL_SAME_HAND_MS 150 + bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t *tap_hold_record, uint16_t other_keycode, keyrecord_t *other_record) { + // Layer-tap: always hold (layers need same-hand access). if (IS_QK_LAYER_TAP(tap_hold_keycode)) { return true; } - return get_chordal_hold_default(tap_hold_record, other_record); + + // Cross-hand: always hold (standard chordal behaviour). + if (get_chordal_hold_default(tap_hold_record, other_record)) { + return true; + } + + // Same-hand mod-tap: timing decides tap vs hold. + if (IS_QK_MOD_TAP(tap_hold_keycode)) { + uint16_t elapsed = + other_record->event.time - tap_hold_record->event.time; + return elapsed >= CHORDAL_SAME_HAND_MS; + } + + // Any other same-hand combo: tap. + return false; } // ── Combos (matching Vial config) ──