chore/feat(q5_max): gitignore update, layer control, and CAPS_MOD #6
@@ -114,3 +114,5 @@ via*.json
|
||||
|
||||
# Keep firmware file
|
||||
!keyboards/keychron/*/firmware/*.bin
|
||||
/.claude_session
|
||||
/.remember/tmp/save-session.pid
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
# Caps Lock Mod Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Replace the plain Caps Lock key with a smart `CAPS_MOD` key: tap=ESC, hold=Ctrl, Shift+tap=CapsLock, Alt+tap=CapsWord, GUI+tap=Autocorrect toggle, with RGB indicators on the Caps Lock key.
|
||||
|
||||
**Architecture:** Custom keycode `CAPS_MOD` with a timer-based tap/hold split. `process_record_user` starts the timer on press and dispatches actions on release. `matrix_scan_user` promotes a held key to Ctrl once `TAPPING_TERM` elapses. RGB indicators for CapsWord (green), Autocorrect (purple), and host CapsLock (white) are added to the existing `rgb_matrix_indicators_advanced_user` function.
|
||||
|
||||
**Tech Stack:** QMK firmware (C), RGB Matrix, Caps Word, Autocorrect — all built in to QMK.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-04-07-capslock-mod-design.md`
|
||||
|
||||
**Build command** (always activate venv first):
|
||||
```bash
|
||||
source .venv/bin/activate && qmk compile -kb keychron/q5_max/ansi_encoder -km via
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/rules.mk` | Add `CAPS_WORD_ENABLE` and `AUTOCORRECT_ENABLE` |
|
||||
| `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/config.h` | No change needed (`AUTOCORRECT_MIN_LENGTH` already defined in `autocorrect_data.h`) |
|
||||
| `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/autocorrect_data.h` | Commit to git (was untracked; pre-generated dictionary with `AUTOCORRECT_MIN_LENGTH 4` already inside) |
|
||||
| `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/keymap.c` | Add `CAPS_MOD` to enum; add state vars; replace all `KC_CAPS`; add press/release + scan logic; add LED 55 indicators |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Enable Caps Word and Autocorrect features
|
||||
|
||||
**Files:**
|
||||
- Modify: `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/rules.mk`
|
||||
- Commit (untracked): `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/autocorrect_data.h`
|
||||
- Note: `config.h` needs no change — `AUTOCORRECT_MIN_LENGTH 4` is already defined inside `autocorrect_data.h`
|
||||
|
||||
- [ ] **Step 1: Add feature flags to rules.mk**
|
||||
|
||||
Replace the entire file content with:
|
||||
|
||||
```makefile
|
||||
VIA_ENABLE = yes
|
||||
TAP_DANCE_ENABLE = yes
|
||||
UNICODE_ENABLE = yes
|
||||
COMBO_ENABLE = yes
|
||||
CAPS_WORD_ENABLE = yes
|
||||
AUTOCORRECT_ENABLE = yes
|
||||
SRC += chord_unicode.c
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify autocorrect_data.h is present and has AUTOCORRECT_MIN_LENGTH**
|
||||
|
||||
Check that `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/autocorrect_data.h` exists and contains `#define AUTOCORRECT_MIN_LENGTH`. Do NOT add it to `config.h` — it is already defined in the pre-generated file.
|
||||
|
||||
- [ ] **Step 3: Compile to verify no breakage**
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate && qmk compile -kb keychron/q5_max/ansi_encoder -km via
|
||||
```
|
||||
|
||||
Expected: build succeeds, `.bin` file produced. No errors.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add keyboards/keychron/q5_max/ansi_encoder/keymaps/via/rules.mk \
|
||||
keyboards/keychron/q5_max/ansi_encoder/keymaps/via/config.h
|
||||
git commit -m "feat(q5_max): enable CAPS_WORD and AUTOCORRECT features"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Add CAPS_MOD keycode, state variables, and replace KC_CAPS
|
||||
|
||||
**Files:**
|
||||
- Modify: `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/keymap.c`
|
||||
|
||||
- [ ] **Step 1: Add CAPS_MOD to the custom keycodes enum**
|
||||
|
||||
Find this block (lines ~27–37):
|
||||
|
||||
```c
|
||||
enum custom_keycodes {
|
||||
ALT_TAB_FWD = SAFE_RANGE, // Alt+Tab (forward)
|
||||
ALT_TAB_BWD, // Alt+Shift+Tab (backward)
|
||||
CHORD_KEY, // Fn1+LeftAlt → chord/unicode entry mode
|
||||
LCK_FN1, // Lock/unlock FN1
|
||||
LCK_FN2, // Lock/unlock FN2
|
||||
LCK_FN3, // Lock/unlock FN3
|
||||
LCK_FN4, // Lock/unlock FN4
|
||||
LCK_CTL, // Lock/unlock KEEB_CTL
|
||||
LCK_BASE, // Clear all locks and return to BASE
|
||||
};
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```c
|
||||
enum custom_keycodes {
|
||||
ALT_TAB_FWD = SAFE_RANGE, // Alt+Tab (forward)
|
||||
ALT_TAB_BWD, // Alt+Shift+Tab (backward)
|
||||
CHORD_KEY, // Fn1+LeftAlt → chord/unicode entry mode
|
||||
LCK_FN1, // Lock/unlock FN1
|
||||
LCK_FN2, // Lock/unlock FN2
|
||||
LCK_FN3, // Lock/unlock FN3
|
||||
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
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add CAPS_MOD state variables**
|
||||
|
||||
Find this block (lines ~44–46):
|
||||
|
||||
```c
|
||||
// Layer-lock state: bitmask of layers that should stay active even after
|
||||
// momentary (TT/MO) keys are released.
|
||||
static layer_state_t locked_layers = 0;
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```c
|
||||
// Layer-lock state: bitmask of layers that should stay active even after
|
||||
// momentary (TT/MO) keys are released.
|
||||
static layer_state_t locked_layers = 0;
|
||||
|
||||
// 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;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Replace KC_CAPS with CAPS_MOD in all five layers**
|
||||
|
||||
In the keymap arrays, find every occurrence of `KC_CAPS` and replace with `CAPS_MOD`. There are five instances — one at the start of row 3 in each of: BASE, FN1, FN2, FN3, FN4. KEEB_CTL already uses `_______` for that position and stays unchanged.
|
||||
|
||||
Each row looks like:
|
||||
```c
|
||||
KC_CAPS, KC_A, KC_S, ...
|
||||
```
|
||||
Change to:
|
||||
```c
|
||||
CAPS_MOD, KC_A, KC_S, ...
|
||||
```
|
||||
|
||||
Do this for all five layers.
|
||||
|
||||
- [ ] **Step 4: Compile to verify**
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate && qmk compile -kb keychron/q5_max/ansi_encoder -km via
|
||||
```
|
||||
|
||||
Expected: build succeeds. `CAPS_MOD` is defined but not yet handled — QMK will pass it through to `process_record_user` which returns `true` by default, so no errors.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add keyboards/keychron/q5_max/ansi_encoder/keymaps/via/keymap.c
|
||||
git commit -m "feat(q5_max): add CAPS_MOD keycode and replace KC_CAPS in all layers"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Implement tap/hold logic in process_record_user and matrix_scan_user
|
||||
|
||||
**Files:**
|
||||
- Modify: `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/keymap.c`
|
||||
|
||||
- [ ] **Step 1: Add CAPS_MOD case to the switch in process_record_user**
|
||||
|
||||
Find the `switch (keycode)` block in `process_record_user`. It currently starts with `case LCK_FN1:`. Add the `CAPS_MOD` case **before** the `LCK_FN1` case:
|
||||
|
||||
```c
|
||||
case CAPS_MOD:
|
||||
if (record->event.pressed) {
|
||||
caps_mod_held = true;
|
||||
caps_mod_timer = timer_read();
|
||||
} else {
|
||||
if (caps_mod_ctrl_registered) {
|
||||
unregister_code(KC_LCTL);
|
||||
caps_mod_ctrl_registered = false;
|
||||
} else {
|
||||
uint8_t mods = get_mods();
|
||||
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);
|
||||
} else {
|
||||
tap_code(KC_ESC);
|
||||
}
|
||||
}
|
||||
caps_mod_held = false;
|
||||
}
|
||||
return false;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add Ctrl promotion to matrix_scan_user**
|
||||
|
||||
Find `matrix_scan_user`:
|
||||
|
||||
```c
|
||||
void matrix_scan_user(void) {
|
||||
if (alt_tab_active && timer_elapsed(alt_tab_timer) > ALT_TAB_TIMEOUT) {
|
||||
unregister_code(KC_LALT);
|
||||
alt_tab_active = false;
|
||||
}
|
||||
chord_scan();
|
||||
}
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```c
|
||||
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;
|
||||
}
|
||||
chord_scan();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Compile to verify**
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate && qmk compile -kb keychron/q5_max/ansi_encoder -km via
|
||||
```
|
||||
|
||||
Expected: build succeeds, no errors or warnings about undefined functions. (`autocorrect_toggle`, `caps_word_toggle`, `get_mods`, `MOD_MASK_*` are all QMK builtins.)
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add keyboards/keychron/q5_max/ansi_encoder/keymaps/via/keymap.c
|
||||
git commit -m "feat(q5_max): implement CAPS_MOD tap/hold logic (ESC/Ctrl/CapsLock/CapsWord/Autocorrect)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Add RGB indicators for the Caps Lock key (LED 55)
|
||||
|
||||
**Files:**
|
||||
- Modify: `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/keymap.c`
|
||||
|
||||
- [ ] **Step 1: Add LED 55 indicators to rgb_matrix_indicators_advanced_user**
|
||||
|
||||
Find `rgb_matrix_indicators_advanced_user`. It currently ends with:
|
||||
|
||||
```c
|
||||
default: // BASE — keep ESC dark
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
Replace that closing section with:
|
||||
|
||||
```c
|
||||
default: // BASE — keep ESC dark
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
// Caps Lock key (LED 55): shows CapsWord/Autocorrect/CapsLock state.
|
||||
if (is_caps_word_on()) {
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(55, 0, 200, 0); // green: Caps Word active
|
||||
} else if (autocorrect_is_enabled()) {
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(55, 150, 0, 255); // purple: Autocorrect active
|
||||
} else if (host_keyboard_led_state().caps_lock) {
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(55, 255, 255, 255); // white: normal Caps Lock on
|
||||
} else {
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(55, 0, 0, 0); // off
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Compile to verify**
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate && qmk compile -kb keychron/q5_max/ansi_encoder -km via
|
||||
```
|
||||
|
||||
Expected: build succeeds. (`is_caps_word_on`, `autocorrect_is_enabled`, `host_keyboard_led_state` are all QMK builtins available when their features are enabled.)
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add keyboards/keychron/q5_max/ansi_encoder/keymaps/via/keymap.c
|
||||
git commit -m "feat(q5_max): add RGB indicators for CapsWord/Autocorrect/CapsLock on LED 55"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review Checklist
|
||||
|
||||
- [x] Tap=ESC → handled in `process_record_user` else branch, `tap_code(KC_ESC)`
|
||||
- [x] Hold=Ctrl → `matrix_scan_user` promotes after `TAPPING_TERM`, released on key-up
|
||||
- [x] Shift+tap=CapsLock → `MOD_MASK_SHIFT` branch, `tap_code(KC_CAPS)`
|
||||
- [x] Alt+tap=CapsWord → `MOD_MASK_ALT` branch, `caps_word_toggle()`
|
||||
- [x] GUI+tap=Autocorrect → `MOD_MASK_GUI` branch, `autocorrect_toggle()`
|
||||
- [x] Green LED for CapsWord → `is_caps_word_on()` branch
|
||||
- [x] Purple LED for Autocorrect → `autocorrect_is_enabled()` branch
|
||||
- [x] White LED for CapsLock → `host_keyboard_led_state().caps_lock` branch
|
||||
- [x] `CAPS_WORD_ENABLE = yes` in rules.mk
|
||||
- [x] `AUTOCORRECT_ENABLE = yes` in rules.mk
|
||||
- [x] `AUTOCORRECT_MIN_LENGTH 4` in config.h
|
||||
- [x] All 5 `KC_CAPS` instances replaced (BASE, FN1, FN2, FN3, FN4)
|
||||
- [x] KEEB_CTL left unchanged (`_______`)
|
||||
- [x] LED index 55 confirmed from `ansi_encoder.c` matrix map (row 3, col 0)
|
||||
@@ -0,0 +1,130 @@
|
||||
# Caps Lock Mod — Design Spec
|
||||
|
||||
**Date:** 2026-04-07
|
||||
**Keyboard:** Keychron Q5 Max (ANSI Encoder)
|
||||
**Keymap:** `keyboards/keychron/q5_max/ansi_encoder/keymaps/via/`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Replace the plain `KC_CAPS` key with a smart `CAPS_MOD` custom keycode that provides
|
||||
tap-vs-hold behavior and modifier-aware tap actions. Enable Caps Word and Autocorrect
|
||||
features with RGB indicators on the Caps Lock key (LED 55).
|
||||
|
||||
---
|
||||
|
||||
## Behavior
|
||||
|
||||
| Action | Result |
|
||||
|---|---|
|
||||
| Tap | Escape |
|
||||
| Hold (past tapping term) | Left Ctrl (held until release) |
|
||||
| Shift + Tap | Toggle normal Caps Lock |
|
||||
| Alt + Tap | Toggle Caps Word |
|
||||
| GUI + Tap | Toggle Autocorrect |
|
||||
|
||||
**Priority for modifier-aware taps:** GUI → Alt → Shift → default (ESC).
|
||||
Modifiers are checked via `get_mods()` at key release time (they remain held by the user).
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
### New custom keycode
|
||||
|
||||
```c
|
||||
CAPS_MOD // added to enum custom_keycodes, after existing entries
|
||||
```
|
||||
|
||||
Replaces every `KC_CAPS` in all layers of `keymap.c`.
|
||||
|
||||
### State variables
|
||||
|
||||
```c
|
||||
static bool caps_mod_held = false;
|
||||
static bool caps_mod_ctrl_registered = false;
|
||||
static uint16_t caps_mod_timer = 0;
|
||||
```
|
||||
|
||||
### `process_record_user` logic
|
||||
|
||||
**On press:**
|
||||
```c
|
||||
caps_mod_held = true;
|
||||
caps_mod_timer = timer_read();
|
||||
```
|
||||
|
||||
**On release:**
|
||||
```c
|
||||
if (caps_mod_ctrl_registered) {
|
||||
unregister_code(KC_LCTL);
|
||||
caps_mod_ctrl_registered = false;
|
||||
} else {
|
||||
uint8_t mods = get_mods();
|
||||
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);
|
||||
} else {
|
||||
tap_code(KC_ESC);
|
||||
}
|
||||
}
|
||||
caps_mod_held = false;
|
||||
```
|
||||
|
||||
### `matrix_scan_user` addition
|
||||
|
||||
```c
|
||||
if (caps_mod_held && !caps_mod_ctrl_registered
|
||||
&& timer_elapsed(caps_mod_timer) > TAPPING_TERM) {
|
||||
caps_mod_ctrl_registered = true;
|
||||
register_code(KC_LCTL);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## RGB Indicators (LED 55 — Caps Lock physical position)
|
||||
|
||||
Added inside `rgb_matrix_indicators_advanced_user`, checked in priority order:
|
||||
|
||||
| State | Color |
|
||||
|---|---|
|
||||
| Caps Word on | Green `(0, 200, 0)` |
|
||||
| Autocorrect on | Purple `(150, 0, 255)` |
|
||||
| Normal Caps Lock on | White `(255, 255, 255)` |
|
||||
| All off | Dark `(0, 0, 0)` |
|
||||
|
||||
Caps Word takes visual priority over host Caps Lock because both can technically be
|
||||
active simultaneously.
|
||||
|
||||
---
|
||||
|
||||
## Feature Enablement
|
||||
|
||||
### `rules.mk` additions
|
||||
```makefile
|
||||
CAPS_WORD_ENABLE = yes
|
||||
AUTOCORRECT_ENABLE = yes
|
||||
```
|
||||
|
||||
### `config.h` addition
|
||||
```c
|
||||
#define AUTOCORRECT_MIN_LENGTH 4
|
||||
```
|
||||
|
||||
`autocorrect_data.h` is already present in the keymap folder. QMK picks it up
|
||||
automatically when `AUTOCORRECT_ENABLE = yes` — no manual `#include` needed.
|
||||
|
||||
---
|
||||
|
||||
## Files Changed
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `keymap.c` | Add `CAPS_MOD` to enum; add state vars; add press/release logic in `process_record_user`; add scan logic in `matrix_scan_user`; add LED 55 indicators in `rgb_matrix_indicators_advanced_user`; replace all `KC_CAPS` with `CAPS_MOD` |
|
||||
| `rules.mk` | Add `CAPS_WORD_ENABLE = yes` and `AUTOCORRECT_ENABLE = yes` |
|
||||
| `config.h` | Add `AUTOCORRECT_MIN_LENGTH 4` |
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
// Copyright 2024 rootiest
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
// Always resolve combo keycodes from BASE (layer 0) so the
|
||||
// COMM+DOT+SLSH fallback combo fires regardless of the active layer.
|
||||
#define COMBO_ONLY_FROM_LAYER 0
|
||||
@@ -28,6 +28,13 @@ enum custom_keycodes {
|
||||
ALT_TAB_FWD = SAFE_RANGE, // Alt+Tab (forward)
|
||||
ALT_TAB_BWD, // Alt+Shift+Tab (backward)
|
||||
CHORD_KEY, // Fn1+LeftAlt → chord/unicode entry mode
|
||||
LCK_FN1, // Lock/unlock FN1
|
||||
LCK_FN2, // Lock/unlock FN2
|
||||
LCK_FN3, // Lock/unlock FN3
|
||||
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
|
||||
};
|
||||
|
||||
// Alt-Tab cycling state
|
||||
@@ -35,6 +42,15 @@ static bool alt_tab_active = false;
|
||||
static uint16_t alt_tab_timer = 0;
|
||||
#define ALT_TAB_TIMEOUT 750 // ms to hold Alt after last encoder tick
|
||||
|
||||
// Layer-lock state: bitmask of layers that should stay active even after
|
||||
// momentary (TT/MO) keys are released.
|
||||
static layer_state_t locked_layers = 0;
|
||||
|
||||
// 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;
|
||||
|
||||
enum layers {
|
||||
BASE,
|
||||
FN1,
|
||||
@@ -50,45 +66,45 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
|
||||
KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_DEL, KC_PSCR, KC_CALC, KC_FIND, KC_MPLY,
|
||||
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP, KC_NUM, KC_PSLS, KC_PAST, KC_PMNS,
|
||||
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_P7, KC_P8, KC_P9,
|
||||
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, TD(TD_HOME_END), KC_P4, KC_P5, KC_P6, KC_PPLS,
|
||||
CAPS_MOD, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, TD(TD_HOME_END), KC_P4, KC_P5, KC_P6, KC_PPLS,
|
||||
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_P1, KC_P2, KC_P3,
|
||||
KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, TT(FN2), TT(FN1), KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT, KC_P0, KC_PDOT, KC_PENT),
|
||||
|
||||
[FN1] = LAYOUT_ansi_101(
|
||||
KC_SLEP, KC_BRID, KC_BRIU, KC_MCTRL, KC_LNPAD, RGB_VAD, RGB_VAI, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, KC_DEL, KC_PSCR, KC_CALC, KC_FIND, KC_MUTE,
|
||||
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP, KC_NUM, KC_PSLS, KC_PAST, KC_PMNS,
|
||||
KC_GRV, LCK_FN1, LCK_FN2, LCK_FN3, LCK_FN4, LCK_CTL, KC_6, KC_7, KC_8, KC_9, LCK_BASE, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP, KC_NUM, KC_PSLS, KC_PAST, KC_PMNS,
|
||||
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_P7, KC_P8, KC_P9,
|
||||
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_P4, KC_P5, KC_P6, KC_PPLS,
|
||||
CAPS_MOD, 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_P4, KC_P5, KC_P6, KC_PPLS,
|
||||
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_P1, KC_P2, KC_P3,
|
||||
KC_LCTL, KC_LGUI, CHORD_KEY, KC_SPC, TT(FN3), TG(FN1), OSL(KEEB_CTL), KC_HOME, KC_DOWN, KC_END, KC_P0, KC_PDOT, KC_PENT),
|
||||
|
||||
[FN2] = LAYOUT_ansi_101(
|
||||
KC_PWR, KC_F13, KC_F14, KC_F15, KC_F16, KC_F17, KC_F18, KC_F19, KC_F20, KC_F21, KC_F22, KC_F23, KC_F24, KC_DEL, KC_PSCR, KC_CALC, KC_FIND, KC_MPLY,
|
||||
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_MS_WH_UP, KC_NUM, KC_PSLS, KC_PAST, KC_PMNS,
|
||||
KC_GRV, LCK_FN1, LCK_FN2, LCK_FN3, LCK_FN4, LCK_CTL, KC_6, KC_7, KC_8, KC_9, LCK_BASE, KC_MINS, KC_EQL, KC_BSPC, KC_MS_WH_UP, KC_NUM, KC_PSLS, KC_PAST, KC_PMNS,
|
||||
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_MS_WH_DOWN, KC_P7, KC_P8, KC_P9,
|
||||
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_MS_BTN3, KC_P4, KC_P5, KC_P6, KC_PPLS,
|
||||
CAPS_MOD, 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_MS_BTN3, KC_P4, KC_P5, KC_P6, KC_PPLS,
|
||||
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_MS_UP, KC_P1, KC_P2, KC_P3,
|
||||
KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, TG(FN2), TT(FN4), KC_RCTL, KC_MS_LEFT, KC_MS_DOWN, KC_MS_RIGHT, KC_MS_BTN1, KC_MS_BTN2, KC_PENT),
|
||||
|
||||
[FN3] = LAYOUT_ansi_101(
|
||||
KC_ESC, KC_BRID, KC_BRIU, KC_MCTRL, KC_LNPAD, RGB_VAD, RGB_VAI, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, KC_DEL, KC_PSCR, KC_CALC, KC_FIND, KC_MPLY,
|
||||
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP, KC_NUM, KC_PSLS, KC_PAST, KC_PMNS,
|
||||
KC_GRV, LCK_FN1, LCK_FN2, LCK_FN3, LCK_FN4, LCK_CTL, KC_6, KC_7, KC_8, KC_9, LCK_BASE, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP, KC_NUM, KC_PSLS, KC_PAST, KC_PMNS,
|
||||
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_P7, KC_P8, KC_P9,
|
||||
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, TD(TD_HOME_END), KC_P4, KC_P5, KC_P6, KC_PPLS,
|
||||
CAPS_MOD, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, TD(TD_HOME_END), KC_P4, KC_P5, KC_P6, KC_PPLS,
|
||||
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_P1, KC_P2, KC_P3,
|
||||
KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, TG(FN3), TT(FN4), KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT, KC_P0, KC_PDOT, KC_PENT),
|
||||
|
||||
[FN4] = LAYOUT_ansi_101(
|
||||
KC_ESC, KC_BRID, KC_BRIU, KC_MCTRL, KC_LNPAD, RGB_VAD, RGB_VAI, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, KC_DEL, KC_PSCR, KC_CALC, KC_FIND, KC_MPLY,
|
||||
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP, KC_NUM, KC_PSLS, KC_PAST, KC_PMNS,
|
||||
KC_GRV, LCK_FN1, LCK_FN2, LCK_FN3, LCK_FN4, LCK_CTL, KC_6, KC_7, KC_8, KC_9, LCK_BASE, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP, KC_NUM, KC_PSLS, KC_PAST, KC_PMNS,
|
||||
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_P7, KC_P8, KC_P9,
|
||||
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, TD(TD_HOME_END), KC_P4, KC_P5, KC_P6, KC_PPLS,
|
||||
CAPS_MOD, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, TD(TD_HOME_END), KC_P4, KC_P5, KC_P6, KC_PPLS,
|
||||
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_P1, KC_P2, KC_P3,
|
||||
KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, TO(BASE), TG(FN4), KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT, KC_P0, KC_PDOT, KC_PENT),
|
||||
|
||||
[KEEB_CTL] = LAYOUT_ansi_101(
|
||||
_______, KC_BRID, KC_BRIU, KC_TASK, KC_FILE, RGB_VAD, RGB_VAI, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, _______, _______, _______, _______, RGB_TOG,
|
||||
_______, BT_HST1, BT_HST2, BT_HST3, P2P4G, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
_______, BT_HST1, BT_HST2, BT_HST3, P2P4G, _______, _______, _______, _______, _______, LCK_BASE, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
RGB_TOG, RGB_MOD, RGB_VAI, RGB_HUI, RGB_SAI, RGB_SPI, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
_______, RGB_RMOD, RGB_VAD, RGB_HUD, RGB_SAD, RGB_SPD, _______, _______, _______, _______, _______, _______, _______, KC_END, _______, _______, _______, _______,
|
||||
_______, _______, _______, _______, _______, BAT_LVL, NK_TOGG, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
@@ -108,6 +124,20 @@ const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][2] = {
|
||||
|
||||
// clang-format on
|
||||
|
||||
// Combos -----------------------------------------------------------------
|
||||
// COMM + DOT + SLSH → TO(BASE): emergency fallback to base layer.
|
||||
// COMBO_ONLY_FROM_LAYER 0 (config.h) ensures these keycodes are always
|
||||
// resolved from BASE so the combo fires regardless of the active layer.
|
||||
const uint16_t PROGMEM fallback_combo[] = {KC_COMM, KC_DOT, KC_SLSH, COMBO_END};
|
||||
combo_t key_combos[] = {
|
||||
COMBO(fallback_combo, TO(BASE)),
|
||||
};
|
||||
|
||||
// Re-assert locked layers whenever QMK modifies layer state (e.g. TT release).
|
||||
layer_state_t layer_state_set_user(layer_state_t state) {
|
||||
return state | locked_layers;
|
||||
}
|
||||
|
||||
void keyboard_post_init_user(void) {
|
||||
chord_init();
|
||||
// Use the Linux unicode input method (Ctrl+Shift+U → hex → Enter).
|
||||
@@ -135,6 +165,61 @@ 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();
|
||||
} else {
|
||||
if (caps_mod_ctrl_registered) {
|
||||
unregister_code(KC_LCTL);
|
||||
caps_mod_ctrl_registered = false;
|
||||
} else {
|
||||
uint8_t mods = get_mods();
|
||||
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_mod_held = false; // cleared in both hold and tap paths
|
||||
}
|
||||
return false;
|
||||
|
||||
case LCK_FN1:
|
||||
case LCK_FN2:
|
||||
case LCK_FN3:
|
||||
case LCK_FN4:
|
||||
case LCK_CTL:
|
||||
case LCK_BASE:
|
||||
if (record->event.pressed) {
|
||||
uint8_t target;
|
||||
switch (keycode) {
|
||||
case LCK_FN1: target = FN1; break;
|
||||
case LCK_FN2: target = FN2; break;
|
||||
case LCK_FN3: target = FN3; break;
|
||||
case LCK_FN4: target = FN4; break;
|
||||
case LCK_CTL: target = KEEB_CTL; break;
|
||||
default: target = BASE; break;
|
||||
}
|
||||
if (target != BASE && (locked_layers & (1UL << target))) {
|
||||
// Already locked on this layer — unlock and return to BASE.
|
||||
locked_layers = 0;
|
||||
layer_move(BASE);
|
||||
} else {
|
||||
// Lock the target layer (clears any other lock first).
|
||||
locked_layers = 0;
|
||||
layer_move(target);
|
||||
if (target != BASE) {
|
||||
locked_layers = (1UL << target);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case ALT_TAB_FWD:
|
||||
if (record->event.pressed) {
|
||||
if (!alt_tab_active) {
|
||||
@@ -162,6 +247,11 @@ 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;
|
||||
@@ -169,8 +259,49 @@ void matrix_scan_user(void) {
|
||||
chord_scan();
|
||||
}
|
||||
|
||||
// RGB Matrix Indicators --------------------------------------------------
|
||||
// ESC key (LED index 0) shows which layer is active at a glance.
|
||||
// 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) {
|
||||
switch (get_highest_layer(layer_state)) {
|
||||
case FN1:
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(0, 0, 128, 255); // blue
|
||||
break;
|
||||
case FN2:
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(0, 0, 220, 80); // green
|
||||
break;
|
||||
case FN3:
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(0, 255, 120, 0); // orange
|
||||
break;
|
||||
case FN4:
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(0, 180, 0, 255); // purple
|
||||
break;
|
||||
case KEEB_CTL:
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(0, 255, 0, 0); // red
|
||||
break;
|
||||
default: // BASE — keep ESC dark
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
// Caps Lock key (LED 55): shows CapsWord/Autocorrect/CapsLock state.
|
||||
if (is_caps_word_on()) {
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(55, 0, 200, 0); // green: Caps Word active
|
||||
} else if (!autocorrect_is_enabled()) {
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(55, 150, 0, 255); // purple: Autocorrect disabled
|
||||
} else if (host_keyboard_led_state().caps_lock) {
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(55, 255, 255, 255); // white: normal Caps Lock on
|
||||
} else {
|
||||
RGB_MATRIX_INDICATOR_SET_COLOR(55, 0, 0, 0); // off
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif // RGB_MATRIX_ENABLE
|
||||
|
||||
// Tap Dance definitions
|
||||
tap_dance_action_t tap_dance_actions[] = {
|
||||
// Tap once for Home, twice for End
|
||||
[TD_HOME_END] = ACTION_TAP_DANCE_DOUBLE(KC_HOME, KC_END),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
VIA_ENABLE = yes
|
||||
TAP_DANCE_ENABLE = yes
|
||||
UNICODE_ENABLE = yes
|
||||
COMBO_ENABLE = yes
|
||||
CAPS_WORD_ENABLE = yes
|
||||
AUTOCORRECT_ENABLE = yes
|
||||
SRC += chord_unicode.c
|
||||
|
||||
Reference in New Issue
Block a user