Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SOFT_OFF app PRIVATE src/behaviors/beha
add_subdirectory_ifdef(CONFIG_ZMK_POINTING src/pointing/)
if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources(app PRIVATE src/hid.c)
target_sources_ifdef(CONFIG_ZMK_HID_REPORT_TYPE_DYNAMIC app PRIVATE src/hid_dynamic_nkro.c)
target_sources_ifdef(CONFIG_ZMK_HID_REPORT_TYPE_DYNAMIC app PRIVATE src/behaviors/behavior_dynamic_nkro.c)
target_sources(app PRIVATE src/behaviors/behavior_key_press.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_KEY_TOGGLE app PRIVATE src/behaviors/behavior_key_toggle.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_HOLD_TAP app PRIVATE src/behaviors/behavior_hold_tap.c)
Expand Down
32 changes: 29 additions & 3 deletions app/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,46 @@ config ZMK_HID_REPORT_TYPE_NKRO

endchoice

config ZMK_HID_REPORT_TYPE_DYNAMIC
bool "Allow switching HKRO/NKRO at runtime"
select SETTINGS
select ZMK_SETTINGS
help
Compile in both the HKRO and NKRO HID report descriptors/report bodies and pick
between them at boot. The HID Report Type choice above selects the first-boot
default (HKRO when left at ZMK's own default); the &dyn_nkro behavior flips the
active mode and reboots, so a single firmware build can switch between full NKRO
for daily use and "boot keyboard" compatible HKRO for BIOS/UEFI screens, without
reflashing. The chosen mode is persisted to flash and only takes effect after the
reboot the behavior triggers, since USB/BLE HID report descriptors cannot be
changed after enumeration.

config ZMK_HID_KEYBOARD_NKRO_EXTENDED_REPORT
bool "Enable extended NKRO reporting"
depends on ZMK_HID_REPORT_TYPE_NKRO
depends on ZMK_HID_REPORT_TYPE_NKRO || ZMK_HID_REPORT_TYPE_DYNAMIC
help
Enables higher usage range for NKRO (F13-F24 and INTL1-9).
Please note this is not compatible with Android currently and you will get no input

if ZMK_HID_REPORT_TYPE_DYNAMIC

config ZMK_HID_REPORT_TYPE_DYNAMIC_INIT_PRIORITY
int "Dynamic NKRO settings load init priority"
default 10
help
APPLICATION init priority used to eagerly load the persisted HID report mode
from settings. Must run before ZMK_BLE_INIT_PRIORITY and
ZMK_USB_HID_INIT_PRIORITY, since both register a fixed HID report
descriptor/size for the rest of the boot session.

endif # ZMK_HID_REPORT_TYPE_DYNAMIC

if ZMK_HID_REPORT_TYPE_HKRO
if ZMK_HID_REPORT_TYPE_HKRO || ZMK_HID_REPORT_TYPE_DYNAMIC

config ZMK_HID_KEYBOARD_REPORT_SIZE
int "# Keyboard Keys Reportable"

endif # ZMK_HID_REPORT_TYPE_HKRO
endif # ZMK_HID_REPORT_TYPE_HKRO || ZMK_HID_REPORT_TYPE_DYNAMIC

config ZMK_HID_CONSUMER_REPORT_SIZE
int "# Consumer Keys Reportable"
Expand Down
2 changes: 1 addition & 1 deletion app/Kconfig.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ config BT_DEVICE_APPEARANCE
default 961

# HID
if ZMK_HID_REPORT_TYPE_HKRO
if ZMK_HID_REPORT_TYPE_HKRO || ZMK_HID_REPORT_TYPE_DYNAMIC

config ZMK_HID_KEYBOARD_REPORT_SIZE
default 6
Expand Down
1 change: 1 addition & 0 deletions app/dts/behaviors.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@
#include <behaviors/soft_off.dtsi>
#include <behaviors/studio_unlock.dtsi>
#include <behaviors/mouse_keys.dtsi>
#include <behaviors/dynamic_nkro.dtsi>
15 changes: 15 additions & 0 deletions app/dts/behaviors/dynamic_nkro.dtsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2025 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

/ {
behaviors {
dyn_nkro: dyn_nkro {
compatible = "zmk,behavior-dynamic-nkro";
#binding-cells = <1>;
display-name = "Dynamic NKRO";
};
};
};
13 changes: 13 additions & 0 deletions app/dts/bindings/behaviors/zmk,behavior-dynamic-nkro.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2025 The ZMK Contributors
# SPDX-License-Identifier: MIT

description: |
Switches the active USB/BLE HID keyboard report mode between full NKRO and
"boot keyboard" compatible HKRO (see CONFIG_ZMK_HID_REPORT_TYPE_DYNAMIC). The
chosen mode is persisted to flash and only takes effect after the reboot
this behavior triggers, since the report descriptor can't change without a
full HID re-enumeration.
compatible: "zmk,behavior-dynamic-nkro"

include: one_param.yaml
9 changes: 9 additions & 0 deletions app/include/dt-bindings/zmk/dynamic_nkro.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2025 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#define DYN_NKRO_TOG 0
#define DYN_NKRO_NKRO 1
#define DYN_NKRO_HKRO 2
298 changes: 146 additions & 152 deletions app/include/zmk/hid.h

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions app/include/zmk/hid_dynamic_nkro.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#pragma once

#include <stdint.h>

enum zmk_hid_dynamic_nkro_mode {
ZMK_HID_DYNAMIC_NKRO_MODE_NKRO = 0,
ZMK_HID_DYNAMIC_NKRO_MODE_HKRO = 1,
};

/**
* The HID report mode active for the current boot session. Decided once, early in boot,
* before USB/BLE HID are initialized, since the report descriptor/size they register cannot
* change without a full re-enumeration (i.e. a reboot).
*/
enum zmk_hid_dynamic_nkro_mode zmk_hid_dynamic_nkro_get_mode(void);

/**
* Persist a new mode to flash. Does not take effect until the next reboot; callers that want
* the change to apply are expected to trigger one (see behavior_dynamic_nkro.c).
*/
int zmk_hid_dynamic_nkro_set_mode(enum zmk_hid_dynamic_nkro_mode mode);
75 changes: 75 additions & 0 deletions app/src/behaviors/behavior_dynamic_nkro.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2025 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#define DT_DRV_COMPAT zmk_behavior_dynamic_nkro

#include <zephyr/device.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/logging/log.h>

#include <drivers/behavior.h>
#include <dt-bindings/zmk/dynamic_nkro.h>

#include <zmk/hid_dynamic_nkro.h>

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)

static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
enum zmk_hid_dynamic_nkro_mode current = zmk_hid_dynamic_nkro_get_mode();
enum zmk_hid_dynamic_nkro_mode target;

switch (binding->param1) {
case DYN_NKRO_TOG:
target = current == ZMK_HID_DYNAMIC_NKRO_MODE_NKRO ? ZMK_HID_DYNAMIC_NKRO_MODE_HKRO
: ZMK_HID_DYNAMIC_NKRO_MODE_NKRO;
break;
case DYN_NKRO_NKRO:
target = ZMK_HID_DYNAMIC_NKRO_MODE_NKRO;
break;
case DYN_NKRO_HKRO:
target = ZMK_HID_DYNAMIC_NKRO_MODE_HKRO;
break;
default:
LOG_ERR("Unknown dynamic NKRO command: %d", binding->param1);
return -ENOTSUP;
}

if (target == current) {
return ZMK_BEHAVIOR_OPAQUE;
}

int ret = zmk_hid_dynamic_nkro_set_mode(target);
if (ret < 0) {
return ret;
}

LOG_INF("Dynamic NKRO mode persisted, rebooting to apply it");

// The descriptor registered with USB/BLE HID can't change without a full
// re-enumeration, so the new mode only takes effect after this reboot.
sys_reboot(SYS_REBOOT_WARM);

return ZMK_BEHAVIOR_OPAQUE;
}

static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return ZMK_BEHAVIOR_OPAQUE;
}

static const struct behavior_driver_api behavior_dynamic_nkro_driver_api = {
.binding_pressed = on_keymap_binding_pressed,
.binding_released = on_keymap_binding_released,
.locality = BEHAVIOR_LOCALITY_GLOBAL,
};

BEHAVIOR_DT_INST_DEFINE(0, NULL, NULL, NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&behavior_dynamic_nkro_driver_api);

#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
Loading