Compare commits

...

15 Commits

Author SHA1 Message Date
rootiest d2a67b2bb3 fix(q5_max): invert autocorrect LED indicator — purple when autocorrect is OFF 2026-04-08 00:47:29 -04:00
rootiest 0210caf90e feat(q5_max): add RGB indicators for CapsWord/Autocorrect/CapsLock on LED 55 2026-04-08 00:47:29 -04:00
rootiest 6045cfa866 fix(q5_max): add clarifying comments to CAPS_MOD release handler 2026-04-08 00:47:29 -04:00
rootiest b77eea4192 feat(q5_max): implement CAPS_MOD tap/hold logic (ESC/Ctrl/CapsLock/CapsWord/Autocorrect) 2026-04-08 00:47:29 -04:00
rootiest bab766887a fix(q5_max): restore void casts for CAPS_MOD vars (QMK uses -Werror=unused-variable) 2026-04-08 00:47:28 -04:00
rootiest a2ef1712ab feat(q5_max): add CAPS_MOD keycode and replace KC_CAPS in all layers 2026-04-08 00:47:28 -04:00
rootiest 0865d7ed26 docs: update capslock-mod plan to reflect autocorrect_data.h already defines AUTOCORRECT_MIN_LENGTH 2026-04-08 00:47:28 -04:00
rootiest 3d283cdf48 fix(q5_max): remove duplicate AUTOCORRECT_MIN_LENGTH define from config.h 2026-04-08 00:47:28 -04:00
rootiest aeae6d485d feat(q5_max): enable CAPS_WORD and AUTOCORRECT features 2026-04-08 00:47:28 -04:00
rootiest 926628bc4f docs: add Caps Lock mod design spec
Spec for CAPS_MOD custom keycode: tap=ESC, hold=Ctrl, Shift+tap=CapsLock,
Alt+tap=CapsWord, GUI+tap=Autocorrect toggle. Includes RGB indicators and
feature enablement plan.
2026-04-08 00:47:28 -04:00
rootiest 45276ac042 feat(q5_max): add layer-lock keycodes for persistent layer activation
Introduces LCK_FN1–LCK_FN4, LCK_CTL, and LCK_BASE custom keycodes that
toggle a layer into a locked state so it stays active after momentary keys
are released. A second press on the same lock key or pressing LCK_BASE
clears all locks and returns to BASE.
2026-04-08 00:47:22 -04:00
rootiest 423e544097 feat(q5_max): add ESC key RGB indicator for active layer
Implements rgb_matrix_indicators_advanced_user to colour the ESC key
(LED 0) based on the current layer: dark on BASE, blue on FN1, green
on FN2, orange on FN3, purple on FN4, and red on KEEB_CTL.
2026-04-08 00:47:22 -04:00
rootiest 86cad58f34 feat(q5_max): add COMM+DOT+SLSH fallback combo to return to BASE
Adds a three-key combo (,  .  /) that fires TO(BASE), providing an
emergency escape hatch when stuck on an unknown layer. COMBO_ONLY_FROM_LAYER
is set to 0 in the keymap config.h so keycodes are always resolved from
BASE, making the combo reliable regardless of the active layer.
2026-04-08 00:47:22 -04:00
rootiest e897c73ff4 chore: ignore Claude session and session memory ID files
Pull Request Labeler / triage (pull_request) Successful in 4s
2026-04-08 00:47:06 -04:00
rootiest 949d34941a chore: remove upstream GitHub Actions workflows
Pull Request Labeler / triage (pull_request) Successful in 4s
Keychron's CI workflows don't apply to this personal fork and trigger
unwanted runs. Remove the entire .github/workflows directory.
2026-04-08 00:46:53 -04:00
31 changed files with 8000 additions and 944 deletions
-41
View File
@@ -1,41 +0,0 @@
name: Bug report
description: Create a report to help us improve QMK Firmware.
title: "[Bug] "
labels: ["bug", "help wanted"]
body:
- type: markdown
attributes:
value: |
Provide a general summary of the bug in the title above.
- type: textarea
attributes:
label: Describe the Bug
description: A clear and concise description of what the bug is.
- type: input
attributes:
label: Keyboard Used
description: The name of the keyboard from the `make` or `qmk compile`/`qmk flash` commands, eg. `planck/rev6`.
- type: input
attributes:
label: Link to product page (if applicable)
- type: input
attributes:
label: Operating System
- type: textarea
attributes:
label: qmk doctor Output
description: Output from running the `qmk doctor` command.
render: text
- type: checkboxes
attributes:
label: Is AutoHotKey / Karabiner installed
options:
- label: AutoHotKey (Windows)
- label: Karabiner (macOS)
- type: input
attributes:
label: Other keyboard-related software installed
- type: textarea
attributes:
label: Additional Context
description: Add any other relevant information about the problem here.
-8
View File
@@ -1,8 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: QMK Discord
url: https://discord.gg/Uq7gcHh
about: Ask questions, discuss issues and features. Chill.
- name: OLKB Subreddit
url: https://www.reddit.com/r/olkb
about: All things OLKB and QMK.
@@ -1,24 +0,0 @@
name: Feature request
description: Suggest a new feature or changes to existing features.
title: "[Feature Request] "
labels: ["enhancement", "help wanted"]
body:
- type: markdown
attributes:
value: |
Provide a general summary of the changes you want in the title above.
Please refrain from asking maintainers to add support for specific keyboards -- it is unlikely they will have hardware available, and will not be able to help.
Your best bet is to take the initiative, add support, then submit a PR yourself.
- type: checkboxes
attributes:
label: Feature Request Type
options:
- label: Core functionality
- label: Add-on hardware support (eg. audio, RGB, OLED screen, etc.)
- label: Alteration (enhancement/optimization) of existing feature(s)
- label: New behavior
- type: textarea
attributes:
label: Description
description: A few sentences describing what it is that you'd like to see in QMK. Additional information (such as links to spec sheets, licensing info, other related issues or PRs, etc) would be helpful.
-19
View File
@@ -1,19 +0,0 @@
name: Other issues
description: Anything else that doesn't fall into the above categories.
labels: ["help wanted", "question"]
body:
- type: markdown
attributes:
value: |
Provide a general summary of the changes you want in the title above.
- type: markdown
attributes:
value: |
Please check [https://docs.qmk.fm/#/support](https://docs.qmk.fm/#/support) for additional resources first. If that doesn't answer your question, choose the bug report template instead, as that may be more appropriate.
Please refrain from asking maintainers to add support for specific keyboards -- it is unlikely they will have hardware available, and will not be able to help.
Your best bet is to take the initiative, add support, then submit a PR yourself.
- type: textarea
attributes:
label: Issue Description
description: Describe your issue in as much detail as possible.
-11
View File
@@ -1,11 +0,0 @@
---
name: Blank issue
about: If you're 100% sure that you don't need one of the other issue templates, use
this one instead.
title: ''
labels: help wanted, question
assignees: ''
---
-35
View File
@@ -1,35 +0,0 @@
<!--- Provide a general summary of your changes in the title above. -->
<!--- This template is entirely optional and can be removed, but is here to help both you and us. -->
<!--- Anything on lines wrapped in comments like these will not show up in the final text. -->
## Description
<!--- Describe your changes in detail here. -->
## Types of Changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply. -->
- [ ] Core
- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement/optimization
- [ ] Keyboard (addition or update)
- [ ] Keymap/layout/userspace (addition or update)
- [ ] Documentation
## Issues Fixed or Closed by This PR
*
## Checklist
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My code follows the code style of this project: [**C**](https://docs.qmk.fm/#/coding_conventions_c), [**Python**](https://docs.qmk.fm/#/coding_conventions_python)
- [ ] I have read the [**PR Checklist** document](https://docs.qmk.fm/#/pr_checklist) and have made the appropriate changes.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the [**CONTRIBUTING** document](https://docs.qmk.fm/#/contributing).
- [ ] I have added tests to cover my changes.
- [ ] I have tested the changes and verified that they work and don't break anything (as well as I can manage).
-9
View File
@@ -1,9 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
labels: CI
reviewers:
- "qmk/collaborators"
schedule:
interval: "daily"
-46
View File
@@ -1,46 +0,0 @@
core:
- quantum/**/*
- tmk_core/**/*
- drivers/**/*
- tests/**/*
- util/**/*
- platforms/**/*
- builddefs/**/*
- Makefile
- '*.mk'
dependencies:
- any:
- 'lib/**/*'
- '!lib/python/**/*'
keyboard:
- any:
- 'keyboards/**/*'
- '!keyboards/**/keymaps/**/*'
keymap:
- users/**/*
- layouts/**/*
- keyboards/**/keymaps/**/*
via:
- keyboards/**/keymaps/via/*
cli:
- requirements.txt
- lib/python/**/*
python:
- '**/*.py'
documentation:
- docs/**/*
translation:
- docs/fr-fr/**/*
- docs/es/**/*
- docs/ja/**/*
- docs/he-il/**/*
- docs/pt-br/**/*
- docs/zh-cn/**/*
- docs/de/**/*
- docs/ru-ru/**/*
CI:
- .github/**/*
dd:
- data/constants/**/*
- data/mappings/**/*
- data/schemas/**/*
-50
View File
@@ -1,50 +0,0 @@
name: Update API Data
permissions:
contents: read
on:
push:
branches:
- master
- develop
paths:
- 'keyboards/**'
- 'layouts/community/**'
- 'lib/python/**'
- 'data/**'
- '.github/workflows/api.yml'
workflow_dispatch:
jobs:
api_data:
runs-on: ubuntu-latest
container: ghcr.io/qmk/qmk_cli
# protect against those who work in their fork on 'important' branches
if: github.repository == 'qmk/qmk_firmware'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
persist-credentials: false
- name: Install dependencies
run: |
pip3 install -r requirements-dev.txt
- name: Generate API Data
run: |
qmk generate-api
- name: Upload API Data
uses: jakejarvis/s3-sync-action@master
with:
args: --acl public-read --follow-symlinks --delete
env:
AWS_S3_BUCKET: ${{ github.ref == 'refs/heads/develop' && secrets['API_SPACE_DEVELOP'] || secrets['API_SPACE_MASTER'] }}
AWS_ACCESS_KEY_ID: ${{ secrets.SPACES_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET_KEY }}
AWS_S3_ENDPOINT: https://nyc3.digitaloceanspaces.com
SOURCE_DIR: '.build/api_data'
-20
View File
@@ -1,20 +0,0 @@
name: Automatic Approve
permissions: {}
on:
schedule:
- cron: "*/5 * * * *"
jobs:
automatic_approve:
runs-on: ubuntu-latest
if: github.repository == 'qmk/qmk_firmware'
steps:
- uses: mheap/automatic-approve-action@v1
with:
token: ${{ secrets.QMK_BOT_TOKEN }}
workflows: "format.yml,lint.yml,unit_test.yml"
dangerous_files: "lib/python/,Makefile,paths.mk,builddefs/"
-38
View File
@@ -1,38 +0,0 @@
name: Essential files modified
permissions:
contents: write
on:
workflow_dispatch:
push:
branches:
- master
paths:
- builddefs/**/*
- drivers/**/*
- platforms/**/*
- quantum/**/*
- tests/**/*
- tmk_core/**/*
- util/**/*
- Makefile
- '*.mk'
jobs:
tag:
runs-on: ubuntu-latest
# protect against those who develop with their fork on master
if: github.repository == 'qmk/qmk_firmware'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Bump version and push tag
uses: anothrNick/github-tag-action@1.66.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEFAULT_BUMP: 'patch'
-74
View File
@@ -1,74 +0,0 @@
name: CI Builds
permissions:
contents: read
on:
push:
branches: [master, develop]
workflow_dispatch:
inputs:
branch:
type: choice
description: 'Branch to build'
options: [master, develop]
concurrency: ci_build-${{ github.event.inputs.branch || github.ref_name }}
jobs:
ci_builds:
if: github.repository == 'qmk/qmk_firmware'
name: "CI Build"
runs-on: self-hosted
timeout-minutes: 1380
strategy:
fail-fast: false
matrix:
keymap: [default, via]
container: ghcr.io/qmk/qmk_cli
steps:
- name: Disable safe.directory check
run : git config --global --add safe.directory '*'
- uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{ github.event.inputs.branch || github.ref }}
- name: Install dependencies
run: pip3 install -r requirements.txt
- name: Run `qmk mass-compile` (keymap ${{ matrix.keymap }})
run: |
export NCPUS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null)
qmk mass-compile -t -j $NCPUS -km ${{ matrix.keymap }} -e DUMP_CI_METADATA=yes || touch .failed
# Generate the step summary markdown
./util/ci/generate_failure_markdown.sh > $GITHUB_STEP_SUMMARY || true
# Truncate to a maximum of 1MB to deal with GitHub workflow limit
truncate --size='<960K' $GITHUB_STEP_SUMMARY || true
# Exit with failure if the compilation stage failed
[ ! -f .failed ] || exit 1
- name: 'Upload artifacts'
uses: actions/upload-artifact@v4
if: always()
with:
name: artifacts-${{ github.event.inputs.branch || github.ref_name }}-${{ matrix.keymap }}
if-no-files-found: ignore
path: |
*.bin
*.hex
*.uf2
.build/failed.*
- name: 'CI Discord Notification'
if: always()
working-directory: util/ci/
env:
DISCORD_WEBHOOK: ${{ secrets.CI_DISCORD_WEBHOOK }}
run: |
python3 -m pip install -r requirements.txt
python3 ./discord-results.py --branch ${{ github.event.inputs.branch || github.ref_name }} --keymap ${{ matrix.keymap }} --url ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
-34
View File
@@ -1,34 +0,0 @@
name: CLI CI
permissions:
contents: read
on:
push:
branches:
- master
- develop
pull_request:
paths:
- 'lib/python/**'
- 'requirements.txt'
- '.github/workflows/cli.yml'
jobs:
test:
runs-on: ubuntu-latest
container: ghcr.io/qmk/qmk_cli
steps:
- name: Disable safe.directory check
run : git config --global --add safe.directory '*'
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: pip3 install -r requirements-dev.txt
- name: Run tests
run: qmk pytest
-37
View File
@@ -1,37 +0,0 @@
name: Update develop after master merge
permissions:
contents: write
on:
push:
branches:
- master
jobs:
develop_update:
runs-on: ubuntu-latest
if: github.repository == 'qmk/qmk_firmware'
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.QMK_BOT_TOKEN }}
fetch-depth: 0
- name: Disable automatic eol conversion
run: |
echo "* -text" > .git/info/attributes
- name: Checkout develop
run: |
git fetch origin master develop
git checkout develop
- name: Update develop from master
run: |
git config --global user.name "QMK Bot"
git config --global user.email "hello@qmk.fm"
git merge origin/master
git push origin develop
-46
View File
@@ -1,46 +0,0 @@
name: Generate Docs
permissions:
contents: write
on:
push:
branches:
- master
paths:
- 'tmk_core/**'
- 'quantum/**'
- 'platforms/**'
- 'docs/**'
- '.github/workflows/docs.yml'
jobs:
generate:
runs-on: ubuntu-latest
container: ghcr.io/qmk/qmk_cli
# protect against those who develop with their fork on master
if: github.repository == 'qmk/qmk_firmware'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install dependencies
run: |
apt-get update && apt-get install -y rsync nodejs npm doxygen
npm install -g moxygen
- name: Build docs
run: |
qmk --verbose generate-docs
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4.5.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BASE_BRANCH: master
BRANCH: gh-pages
FOLDER: .build/docs
GIT_CONFIG_EMAIL: hello@qmk.fm
@@ -1,43 +0,0 @@
name: Update feature branches after develop merge
permissions:
contents: write
on:
push:
branches:
- develop
jobs:
feature_branch_update:
runs-on: ubuntu-latest
if: github.repository == 'qmk/qmk_firmware'
strategy:
matrix:
branch:
- xap
- riot
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.QMK_BOT_TOKEN }}
fetch-depth: 0
- name: Disable automatic eol conversion
run: |
echo "* -text" > .git/info/attributes
- name: Checkout branch
run: |
git fetch origin develop ${{ matrix.branch }}
git checkout ${{ matrix.branch }}
- name: Update branch from develop
run: |
git config --global user.name "QMK Bot"
git config --global user.email "hello@qmk.fm"
git merge origin/develop
git push origin ${{ matrix.branch }}
-55
View File
@@ -1,55 +0,0 @@
name: PR Lint Format
permissions:
contents: read
on:
pull_request:
paths:
- 'drivers/**'
- 'lib/arm_atsam/**'
- 'lib/lib8tion/**'
- 'lib/python/**'
- 'platforms/**'
- 'quantum/**'
- 'tests/**'
- 'tmk_core/**'
jobs:
lint:
runs-on: ubuntu-latest
container: ghcr.io/qmk/qmk_cli
steps:
- name: Disable safe.directory check
run : git config --global --add safe.directory '*'
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
run: |
pip3 install -r requirements-dev.txt
- name: Get changed files
id: file_changes
uses: tj-actions/changed-files@v40
- name: Run qmk formatters
shell: 'bash {0}'
run: |
echo '${{ steps.file_changes.outputs.added_files}}' '${{ steps.file_changes.outputs.modified_files}}' > ~/files_changed.txt
qmk format-c --core-only $(< ~/files_changed.txt) || true
qmk format-python $(< ~/files_changed.txt) || true
qmk format-text $(< ~/files_changed.txt) || true
- name: Fail when formatting required
run: |
git diff
for file in $(git diff --name-only); do
echo "File '${file}' Requires Formatting"
echo "::error file=${file}::Requires Formatting"
done
test -z "$(git diff --name-only)"
-59
View File
@@ -1,59 +0,0 @@
name: Lint Format
permissions:
contents: read
on:
push:
branches:
- master
- develop
jobs:
lint:
runs-on: ubuntu-latest
container: ghcr.io/qmk/qmk_cli
steps:
- name: Disable safe.directory check
run : git config --global --add safe.directory '*'
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Disable automatic eol conversion
run: |
echo "* -text" > .git/info/attributes
- name: Install dependencies
run: |
pip3 install -r requirements-dev.txt
- name: Run qmk formatters
shell: 'bash {0}'
run: |
qmk format-c -a
qmk format-python -a
qmk format-text -a
git diff
- uses: rlespinasse/github-slug-action@v3.x
- name: Become QMK Bot
run: |
git config user.name 'QMK Bot'
git config user.email 'hello@qmk.fm'
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
if: ${{ github.repository == 'qmk/qmk_firmware'}}
with:
token: ${{ secrets.QMK_BOT_TOKEN }}
delete-branch: true
branch: bugfix/format_${{ env.GITHUB_REF_SLUG }}
author: QMK Bot <hello@qmk.fm>
committer: QMK Bot <hello@qmk.fm>
commit-message: Format code according to conventions
title: '[CI] Format code according to conventions'
-18
View File
@@ -1,18 +0,0 @@
name: "Pull Request Labeler"
permissions:
contents: read
pull-requests: write
on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review, locked]
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
configuration-path: '.github/labeler.yml'
-83
View File
@@ -1,83 +0,0 @@
name: PR Lint keyboards
permissions:
contents: read
on:
pull_request:
paths:
- 'keyboards/**'
jobs:
lint:
runs-on: ubuntu-latest
container: ghcr.io/qmk/qmk_cli
steps:
- name: Disable safe.directory check
run : git config --global --add safe.directory '*'
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
run: pip3 install -r requirements-dev.txt
- name: Get changed files
id: file_changes
uses: tj-actions/changed-files@v40
- name: Print info
run: |
git rev-parse --short HEAD
echo ${{ github.event.pull_request.base.sha }}
echo '${{ steps.file_changes.outputs.all_changed_files}}'
- name: Run qmk lint
if: always()
shell: 'bash {0}'
run: |
QMK_CHANGES=$(echo -e '${{ steps.file_changes.outputs.all_changed_files}}' | sed 's/ /\n/g')
QMK_KEYBOARDS=$(qmk list-keyboards)
exit_code=0
for KB in $QMK_KEYBOARDS; do
KEYBOARD_CHANGES=$(echo "$QMK_CHANGES" | grep -E '^(keyboards/'${KB}'/)')
if [[ -z "$KEYBOARD_CHANGES" ]]; then
# skip as no changes for this keyboard
continue
fi
KEYMAP_ONLY=$(echo "$KEYBOARD_CHANGES" | grep -cv /keymaps/)
if [[ $KEYMAP_ONLY -gt 0 ]]; then
echo "linting ${KB}"
qmk lint --keyboard ${KB} && qmk info -l --keyboard ${KB}
exit_code=$(($exit_code + $?))
fi
done
qmk format-text ${{ steps.file_changes.outputs.all_changed_files}} || true
for file in ${{ steps.file_changes.outputs.all_changed_files}}; do
if ! git diff --quiet $file; then
echo "File '${file}' Requires Formatting"
echo "::error file=${file}::Requires Formatting"
exit_code=$(($exit_code + 1))
fi
done
if [[ $exit_code -gt 255 ]]; then
exit 255
fi
exit $exit_code
- name: Verify keyboard aliases
if: always()
shell: 'bash {0}'
run: |
git reset --hard
git clean -xfd
qmk ci-validate-aliases
-36
View File
@@ -1,36 +0,0 @@
name: PR Regenerate Files
permissions:
contents: read
on:
pull_request:
paths:
- 'data/constants/**'
- 'lib/python/**'
jobs:
regen:
runs-on: ubuntu-latest
container: ghcr.io/qmk/qmk_cli
steps:
- name: Disable safe.directory check
run : git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Run qmk generators
run: |
util/regen.sh
git diff
- name: Fail when regeneration required
run: |
git diff
for file in $(git diff --name-only); do
echo "File '${file}' Requires Regeneration"
echo "::error file=${file}::Requires Regeneration"
done
test -z "$(git diff --name-only)"
-46
View File
@@ -1,46 +0,0 @@
name: Regenerate Files
permissions:
contents: write
on:
push:
branches:
- master
- develop
jobs:
regen:
runs-on: ubuntu-latest
container: ghcr.io/qmk/qmk_cli
steps:
- name: Disable safe.directory check
run : git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Run qmk generators
run: |
util/regen.sh
git diff
- uses: rlespinasse/github-slug-action@v3.x
- name: Become QMK Bot
run: |
git config user.name 'QMK Bot'
git config user.email 'hello@qmk.fm'
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
if: ${{ github.repository == 'qmk/qmk_firmware'}}
with:
token: ${{ secrets.QMK_BOT_TOKEN }}
delete-branch: true
branch: bugfix/regen_${{ env.GITHUB_REF_SLUG }}
author: QMK Bot <hello@qmk.fm>
committer: QMK Bot <hello@qmk.fm>
commit-message: Regenerate Files
title: '[CI] Regenerate Files'
-66
View File
@@ -1,66 +0,0 @@
name: 'Close stale issues and PRs'
permissions:
issues: write
pull-requests: write
actions: write
on:
schedule:
- cron: '30 1 * * *'
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@main
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
remove-stale-when-updated: true
exempt-draft-pr: true
ascending: true
operations-per-run: 150
stale-issue-label: stale
days-before-issue-stale: 90
days-before-issue-close: 30
exempt-issue-labels: bug,in progress,on hold,discussion,to do
stale-issue-message: >
This issue has been automatically marked as stale because it has not had activity in the
last 90 days. It will be closed in the next 30 days unless it is tagged properly or other activity
occurs.
For maintainers: Please label with `bug`, `in progress`, `on hold`, `discussion` or `to do` to prevent
the issue from being re-flagged.
close-issue-message: >
This issue has been automatically closed because it has not had activity in the last 30 days.
If this issue is still valid, re-open the issue and let us know.
// [stale-action-closed]
stale-pr-label: stale
days-before-pr-stale: 45
days-before-pr-close: 30
exempt-pr-labels: bug,awaiting review,breaking_change,in progress,on hold
stale-pr-message: >
Thank you for your contribution!
This pull request has been automatically marked as stale because it has not had
activity in the last 45 days. It will be closed in 30 days if no further activity occurs.
Please feel free to give a status update now, or re-open when it's ready.
For maintainers: Please label with `bug`, `awaiting review`, `breaking_change`, `in progress`, or `on hold`
to prevent the issue from being re-flagged.
close-pr-message: >
Thank you for your contribution!
This pull request has been automatically closed because it has not had activity in the last 30 days.
Please feel free to give a status update now, ping for review, or re-open when it's ready.
// [stale-action-closed]
-35
View File
@@ -1,35 +0,0 @@
name: Unit Tests
permissions:
contents: read
on:
push:
branches:
- master
- develop
pull_request:
paths:
- 'builddefs/**'
- 'quantum/**'
- 'platforms/**'
- 'tmk_core/**'
- 'tests/**'
- '*.mk'
- 'Makefile'
- '.github/workflows/unit_test.yml'
jobs:
test:
runs-on: ubuntu-latest
container: ghcr.io/qmk/qmk_cli
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: pip3 install -r requirements-dev.txt
- name: Run tests
run: make test:all
+2
View File
@@ -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 ~2737):
```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 ~4446):
```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