Old README was upstream QMK boilerplate (badges, generic feature list,
QMK maintainer credits) that no longer matched this repo's purpose.
Replaced with an overview of this fork's actual keymaps (Keychron Q5
Max, K17 Max) plus a note that git.rootiest.dev is the primary repo
and github.com is a one-way mirror (direct GitHub pushes get
overwritten) that also hosts the prebuilt firmware releases.
Adds .github/workflows/build-release.yml: on push to main, compiles
keychron/q5_max/ansi_encoder (via) via QMK's official installer
(uv-managed qmk CLI + prebuilt toolchains, matching the local dev
setup) and publishes the .bin to a rolling "latest" release on the
GitHub mirror.
Gated to the GitHub mirror only (github.server_url check), so the
toolchain install + build cost lands on GitHub's runners, not the
local Gitea host.
Also configured Gitea branch protection on main: direct pushes are
blocked, only PR merges land here.
- Add ~85 new chords: Celsius/Fahrenheit signs, emoji faces, gesture
emoji, symbols, nature, food/object emoji (incl. slang aliases)
- Widen chord index/count types uint8_t -> uint16_t (table now 241
entries, was nearing the 255 cap)
- Add gen_cheatsheet.py + cheatsheet.template.html: generates
cheatsheet.html from chord_unicode.c so the reference stays in sync
with the table
- Ignore /CLAUDE.md, /AGENTS.md, AGENTS/, __pycache__, and other
agents-init/editor/OS junk in .gitignore
Adds a new HID_CMD_BATTERY (0x44) command to the shared qmk-host protocol.
When the keyboard is in wireless transport mode (BT or 2.4G) the firmware
now pushes the current battery percentage to the host:
- Periodic push every 5 minutes via timer_read32()/timer_elapsed32(); fires
immediately on the first matrix_scan_user() call after boot so qmk-host
gets a reading as soon as the keyboard connects over USB in wireless mode.
- Redundant sends are suppressed: g_last_sent_bat tracks the last value
transmitted and the packet is skipped when the level has not changed.
- Query-response path: qmk-host can send CMD_BATTERY with HID_FLAG_QUERY at
any time; the keyboard replies with the current percentage or
HID_BATT_UNAVAILABLE (0xFF) when in USB transport mode.
- All battery and transport code is guarded by #ifdef LK_WIRELESS_ENABLE so
the keymap compiles cleanly on any wired-only build.
Firmware size delta: +160 bytes (well within flash budget).
Change the fallback combo's action from TO(BASE) to LCK_BASE.
Since LCK_BASE explicitly clears the locked_layers bitmask,
it ensures that the keyboard actually returns to the BASE
layer even if a layer was previously locked.
The previous TO(BASE) action only updated the temporary layer
state, which was immediately overwritten by the persistent
locked_layers bitmask in layer_state_set_user().
Port the Keychron RGB EEPROM persistence work from the Q5 Max (PR #18)
to the K17 Max ANSI Encoder RGB variant.
- Add KEYCHRON_RGB_ENABLE = yes to rules.mk
- Define default_per_key_led[] (red Esc/CapsLock, yellow modifiers and
numpad, blue alpha keys) and default_region[] for the 103-LED layout
in ansi_encoder/rgb/rgb.c; both are extern'd by keychron_rgb.c
- Add wireless_enter_connected_kb() hook and eeconfig_init_custom_rgb()
call in keyboard_post_init_kb() in k17_max.c, matching the Q5 Max
pattern for boot-time and transport-change RGB persistence
- Define VIA_EEPROM_MAGIC_ADDR 552 in ansi_encoder/rgb/config.h to pin
VIA keymap storage past the Keychron EEPROM data region end (540 for
K17 with 103 LEDs); prevents keymap corruption if the KB data block
grows in future
- Rename dev branch references from q5_dev to dev/q5; add dev/k17 for
K17 Max work
- Add K17 Max build/flash commands using the correct rgb subvariant path
(keychron/k17_max/ansi_encoder/rgb) to distinguish from the white LED
variant
- Add EEPROM Layout Notes section documenting the Q5 Max address map,
VIA_EEPROM_MAGIC_ADDR pinning rules, and EECONFIG_KB_DATA_SIZE #undef
requirement
- Add Keychron RGB section capturing lessons from the persistence work:
when to call eeconfig_init_custom_rgb(), version stamp placement,
kc_rgb_save() mode persistence, retail_demo_enable 0xFF bug, and
extern array requirements
- Add DIP Switch section explaining the frame overlay approach and why
direct rgb_matrix_mode() calls must be avoided
Enable KEYCHRON_RGB_ENABLE for the Q5 Max, wiring up PER_KEY_RGB and
MIXED_RGB effects, and fix a cascade of EEPROM bugs that caused the
Launcher-configured RGB mode to revert to the default heatmap on every
power cycle and wireless transport change.
Keychron RGB enablement:
- Add KEYCHRON_RGB_ENABLE = yes to rules.mk
- Define default_per_key_led[] and default_region[] for the ANSI Encoder
layout in ansi_encoder.c (extern'd by keychron_rgb.c)
- Fix missing #include "eeconfig_custom_rgb.h" in mixed_rgb.c and
rgb_matrix_kb.inc so EECONFIG_SIZE_CUSTOM_RGB is in scope for the
compile guards that gate the custom effects
- Add #undef EECONFIG_KB_DATA_SIZE before the Keychron redefinition in
eeconfig_kb.h to suppress redefinition of QMK's default-zero value
EEPROM persistence fixes (keychron_rgb.c):
- Fix retail_demo_enable never being written to EEPROM in
eeconfig_reset_custom_rgb(): original code used eeprom_read_block
instead of eeprom_update_block, leaving 0xFF on freshly-flashed
boards; retail_demo_task() treats any non-zero value as "demo active"
and forces the mode to CUSTOM_MIXED_RGB every scan
- Clamp retail_demo_enable > 1 to 0 on load to recover boards already
affected by the above bug
- Move EECONFIG_KEYBOARD version stamp from eeconfig_init_custom_rgb()
(load path) to eeconfig_reset_custom_rgb() (reset/write path) so the
version is only stamped when valid defaults are actually written
- Call eeconfig_update_rgb_matrix() in kc_rgb_save() so the current QMK
RGB mode is persisted alongside Keychron data; without this,
rgb_matrix_init() (called on every transport change) reloads the
compile-time default RGB_MATRIX_TYPING_HEATMAP from EEPROM
Transport-change persistence (q5_max.c):
- Call eeconfig_init_custom_rgb() in keyboard_post_init_kb() so Keychron
RGB arrays are loaded from EEPROM on every boot instead of being
zero-initialised
- Add wireless_enter_connected_kb() hook: re-applies the EEPROM-saved
QMK RGB mode after BT/2.4G reconnect in case the reconnect sequence
resets the in-RAM mode before the display settles
DIP switch Win-side override (keymap.c):
- Replace rgb_matrix_mode() / rgb_matrix_sethsv() calls (which write to
EEPROM and permanently overwrite the Launcher mode) with a
dip_win_active flag; rgb_matrix_indicators_advanced_user() paints all
LEDs white each frame when the flag is set, leaving the active effect
and EEPROM untouched
VIA keymap address pinning (config.h):
- Define VIA_EEPROM_MAGIC_ADDR 544 to anchor VIA keymap storage at a
fixed EEPROM offset; without this, growth in EECONFIG_KB_DATA_SIZE
silently shifts the keymap block, corrupting stored layouts (observed
as layer-0 keys reverting to KC_TRNS / KC_NONE on boot)
Replace the Win/Mac default-layer switch with an RGB effect toggle:
- Win side → solid white backlight
- Mac side → typing heatmap
Add a weak dip_switch_update_keymap() hook in q5_max.c to work around
factory_test.c already owning dip_switch_update_user().
Define TAPPING_TERM (200ms default) and TAPPING_TERM_PER_KEY in config.h,
then implement get_tapping_term() to set a tight 50ms window for TD_HOME_END
so a single Home tap never accidentally resolves as End.
CK_CTRL_K_C and CK_CTRL_K_D were leftover from an earlier failed
attempt using ACTION_TAP_DANCE_DOUBLE with custom keycodes. Now that
TD_CHORDS uses ACTION_TAP_DANCE_FN with SEND_STRING, they are unused.
Two build errors:
1. RAW_EPSIZE undeclared — usb_descriptor.h is not in scope when
keymap.c is compiled through Keychron's build path. Replace all
uses with HID_PACKET_SIZE (= 32), now defined in hid_protocol.h.
2. via_command_kb duplicate symbol — keychron_raw_hid.c already defines
via_command_kb (non-weak) so we cannot redefine it in keymap.c.
Fix by adding a weak kc_raw_hid_rx_kb() extension hook to
keychron_raw_hid.c (following the same pattern as kc_rgb_matrix_rx).
kc_raw_hid_rx() now calls this hook from its default case instead of
returning false directly. The keymap overrides kc_raw_hid_rx_kb() to
handle our custom HID command range (0x40-0x7E).
Add hid_protocol.h defining a shared 32-byte packet structure for the
qmk-host bridge application (command IDs 0x40-0x7E, clear of VIA's
range). Implement via_command_kb() in keymap.c to intercept incoming
packets: LAYER_SYNC applies a new active layer, VOLUME and BRIGHTNESS
store host-reported values for future RGB indicators, and ACTIVE_APP
is stubbed for a later commit. Layer state changes are broadcast to the
host via raw_hid_send() from layer_state_set_user(), guarded by
g_hid_recv_active to prevent echo loops when the change itself
originated from HID.