Skip to content

feat(hid): Dynamic nkro#3402

Open
t3dodson wants to merge 4 commits into
zmkfirmware:mainfrom
t3dodson:dynamic-nkro
Open

feat(hid): Dynamic nkro#3402
t3dodson wants to merge 4 commits into
zmkfirmware:mainfrom
t3dodson:dynamic-nkro

Conversation

@t3dodson

Copy link
Copy Markdown

Closes #3395 curious to get feedback, as I had mentioned in the issue I think this is a widely useful feature suitable for upstream zmk.

With this fix I can finally switch into my bios with the same keyboard and make my edits.

PR check-list

  • Branch has a clean commit history
  • Additional tests are included, if changing behaviors/core code that is testable.
  • Proper Copyright + License headers added to applicable files (Generally, we stick to "The ZMK Contributors" for copyrights to help avoid churn when files get edited)
  • Pre-commit used to check formatting of files, commit messages, etc.
  • Includes any necessary documentation changes.

I manually validated the following scenarios

Dynamic NKRO — Test Scenarios

Critical test matrix for the dynamic HKRO/NKRO feature. Columns are the relevant
Kconfig settings; rows are the build/runtime scenarios we care about. The first
two are regression baselines (feature absent → behavior must be unchanged); the
rest exercise the dynamic path.

HKRO / NKRO = CONFIG_ZMK_HID_REPORT_TYPE_HKRO / _NKRO (the report-type
choice). DYNAMIC = CONFIG_ZMK_HID_REPORT_TYPE_DYNAMIC. EXT =
CONFIG_ZMK_HID_KEYBOARD_NKRO_EXTENDED_REPORT. BOOT = CONFIG_ZMK_USB_BOOT.

# Scenario Type choice DYNAMIC EXT BOOT First-boot mode What to verify Result
1 NKRO baseline (regress) NKRO n n n NKRO (fixed) Full NKRO works as before; descriptor/report size unchanged vs. upstream; &dyn_nkro unavailable ✅ PASS (2026-06-22) — NKRO confirmed on test site; emptied &dyn_nkro keys inert as expected. Build dropped 1536 B.
2 HKRO baseline (regress) HKRO n n n HKRO (fixed) 6KRO works as before; 7th simultaneous key rolls over; behavior identical to upstream ✅ PASS (2026-06-22) — 6 keys register, 7th rolls off as expected. left FLASH 366228 B.
3 Dynamic, NKRO default NKRO y n n NKRO Boots NKRO; &dyn_nkro DYN_NKRO_TOG → reboot → HKRO; toggle back → NKRO; mode survives power cycle ✅ PASS (2026-06-22) — boots NKRO; TOG→HKRO, persists across power cycle; TOG→NKRO; force NKRO/HKRO cmds also verified. left FLASH 367040 B (both descriptors).
4 Dynamic, HKRO default HKRO y n n HKRO Boots HKRO; DYN_NKRO_NKRO → reboot → NKRO; DYN_NKRO_HKRO when already HKRO is a no-op (no reboot) ✅ PASS (2026-06-22) — boots HKRO; force-HKRO no-op while HKRO; NKRO works after toggle; force-NKRO no-op while NKRO. left FLASH 367040 B (= TC3).
5 Dynamic + boot protocol NKRO y n y NKRO Boot keyboard report correct in both modes (e.g. BIOS/UEFI screen) after switching each way ✅ PASS (2026-06-22) — OS works in both modes; in-BIOS HKRO works & toggling works in the BIOS menu. NKRO not accepted by this BIOS (host reads report-protocol descriptor, doesn't request USB boot protocol) — expected, and the motivation for the feature. No stuck keys / misbehavior. left FLASH 367472 B.

Bluetooth

I also validated behavior using bluetooth on an iphone
I connect, if I toggle to a different mode, I have to reconnect bluetooth then it does behave as expected. I understand some devices may not handle this gracefully, but I would hope using multiple profiles would be sufficient for that use case.

t3dodson added 4 commits June 21, 2026 10:26
Restructure the single zmk_hid_report_desc[] initializer into composable
macros (ZMK_HID_REPORT_DESC_COMMON_PREFIX/_COMMON_SUFFIX, the per-mode
_NKRO/_HKRO_KEYBOARD_ITEMS, and the ZMK_HID_REPORT_DESC() combiner) so the
descriptor body can be instantiated more than once.

No functional change: for the static NKRO and HKRO configurations the
generated report descriptor is byte-for-byte identical to before, verified
across indicators / pointing / smooth-scrolling and consumer BASIC/FULL
permutations.
Factor the per-usage press/release/is-pressed/boot-report loops out of the
static NKRO and HKRO report-type arms into buffer-parameterized static inline
primitives (nkro_*/hkro_*). Each arm becomes a thin binding of those primitives
to keyboard_report.body.keys; keys_held bookkeeping stays in the arms so its
ordering is unchanged. Pure refactor of existing code, no behavior change.
Compile in both the HKRO and NKRO HID report descriptors/report bodies and pick
between them at boot from a flash-persisted mode. The &dyn_nkro behavior flips
the active mode and warm-reboots (descriptors cannot change after enumeration).
The HID Report Type choice selects the first-boot default (HKRO at ZMK's own
default). Leverages the shared key primitives and the reusable report-descriptor
macros from the preceding refactor commits: the dynamic arm just binds those
primitives to body.nkro_keys/body.hkro_keys per the boot-resolved mode.
Add a Dynamic NKRO behavior page covering &dyn_nkro and its command
defines, and document CONFIG_ZMK_HID_REPORT_TYPE_DYNAMIC in the system
config reference.
@t3dodson t3dodson requested review from a team as code owners June 23, 2026 02:36
@t3dodson t3dodson changed the title Dynamic nkro feat(hid): Dynamic nkro Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Dynamic NKRO

1 participant