From 91ba034896c3b1668b88286e18d8f38314ec39c3 Mon Sep 17 00:00:00 2001
From: Pete Johanson <peter@peterjohanson.com>
Date: Mon, 15 Mar 2021 00:40:09 -0400
Subject: [PATCH] feat(hid): Configurable NKRO HID report support.

* Add Kconfig settings for NKRO or HKRO (boot compatible), HID
  report types for keyboard page.
* Updated report storage and set/unset for each config.
---
 app/Kconfig           | 40 +++++++++++++++++++++++++++++++++--
 app/include/zmk/hid.h | 49 ++++++++++++++++++++++++++++++++++---------
 app/src/hid.c         | 44 ++++++++++++++++++++++++++++++++++----
 3 files changed, 117 insertions(+), 16 deletions(-)

diff --git a/app/Kconfig b/app/Kconfig
index f23930b4..629dfa70 100644
--- a/app/Kconfig
+++ b/app/Kconfig
@@ -25,7 +25,40 @@ config USB_DEVICE_PID
 config USB_DEVICE_MANUFACTURER
 	default "ZMK Project"
 
-menu "HID Output Types"
+menu "HID"
+
+choice ZMK_HID_REPORT_TYPE
+	prompt "HID Report Type"
+
+config ZMK_HID_REPORT_TYPE_HKRO
+	bool "#-Key Roll Over (HKRO) HID Report"
+	help
+	  Enable # key roll over for HID report. This selection is "boot keyboard" compatible
+	  but limits the total number of possible keys to report as held to #.
+
+config ZMK_HID_REPORT_TYPE_NKRO
+	bool "Full N-Key Roll Over (NKRO) HID Report"
+	help
+	  Enable full N-Key Roll Over for HID output. This selection will prevent the keyboard
+	  from working with some BIOS/UEFI versions that only support "boot keyboard" support.
+	  This option also prevents using some infrequently used higher range HID usages.
+
+endchoice
+
+if ZMK_HID_REPORT_TYPE_HKRO
+
+config ZMK_HID_KEYBOARD_REPORT_SIZE
+	int "# Keyboard Keys Reportable"
+	default 6
+
+
+endif
+
+config ZMK_HID_CONSUMER_REPORT_SIZE
+	int "# Consumer Keys Reportable"
+	default 6
+
+menu "Output Types"
 
 config ZMK_USB
 	bool "USB"
@@ -92,7 +125,10 @@ config ZMK_BLE_PASSKEY_ENTRY
 #ZMK_BLE
 endif
 
-#HID Output Types
+#Output Types
+endmenu
+
+# HID
 endmenu
 
 menu "Split Support"
diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h
index 5aa004c2..be88c175 100644
--- a/app/include/zmk/hid.h
+++ b/app/include/zmk/hid.h
@@ -13,12 +13,10 @@
 #include <dt-bindings/zmk/hid_usage.h>
 #include <dt-bindings/zmk/hid_usage_pages.h>
 
+#define ZMK_HID_KEYBOARD_NKRO_MAX_USAGE HID_USAGE_KEY_KEYPAD_EQUAL
+
 #define COLLECTION_REPORT 0x03
 
-#define ZMK_HID_KEYBOARD_NKRO_SIZE 6
-
-#define ZMK_HID_CONSUMER_NKRO_SIZE 6
-
 static const uint8_t zmk_hid_report_desc[] = {
     /* USAGE_PAGE (Generic Desktop) */
     HID_GI_USAGE_PAGE,
@@ -74,6 +72,30 @@ static const uint8_t zmk_hid_report_desc[] = {
     /* USAGE_PAGE (Keyboard/Keypad) */
     HID_GI_USAGE_PAGE,
     HID_USAGE_KEY,
+
+#if IS_ENABLED(CONFIG_ZMK_HID_REPORT_TYPE_NKRO)
+    /* LOGICAL_MINIMUM (0) */
+    HID_GI_LOGICAL_MIN(1),
+    0x00,
+    /* LOGICAL_MAXIMUM (1) */
+    HID_GI_LOGICAL_MAX(1),
+    0x01,
+    /* USAGE_MINIMUM (Reserved) */
+    HID_LI_USAGE_MIN(1),
+    0x00,
+    /* USAGE_MAXIMUM (Keyboard Application) */
+    HID_LI_USAGE_MAX(1),
+    ZMK_HID_KEYBOARD_NKRO_MAX_USAGE,
+    /* REPORT_SIZE (8) */
+    HID_GI_REPORT_SIZE,
+    0x01,
+    /* REPORT_COUNT (6) */
+    HID_GI_REPORT_COUNT,
+    ZMK_HID_KEYBOARD_NKRO_MAX_USAGE + 1,
+    /* INPUT (Data,Ary,Abs) */
+    HID_MI_INPUT,
+    0x02,
+#elif IS_ENABLED(CONFIG_ZMK_HID_REPORT_TYPE_HKRO)
     /* LOGICAL_MINIMUM (0) */
     HID_GI_LOGICAL_MIN(1),
     0x00,
@@ -89,12 +111,15 @@ static const uint8_t zmk_hid_report_desc[] = {
     /* REPORT_SIZE (1) */
     HID_GI_REPORT_SIZE,
     0x08,
-    /* REPORT_COUNT (ZMK_HID_KEYBOARD_NKRO_SIZE) */
+    /* REPORT_COUNT (CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE) */
     HID_GI_REPORT_COUNT,
-    ZMK_HID_KEYBOARD_NKRO_SIZE,
+    CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE,
     /* INPUT (Data,Ary,Abs) */
     HID_MI_INPUT,
     0x00,
+#else
+#error "A proper HID report type must be selected"
+#endif
 
     /* END_COLLECTION */
     HID_MI_COLLECTION_END,
@@ -130,9 +155,9 @@ static const uint8_t zmk_hid_report_desc[] = {
     /* REPORT_SIZE (16) */
     HID_GI_REPORT_SIZE,
     0x10,
-    /* REPORT_COUNT (ZMK_HID_CONSUMER_NKRO_SIZE) */
+    /* REPORT_COUNT (CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE) */
     HID_GI_REPORT_COUNT,
-    ZMK_HID_CONSUMER_NKRO_SIZE,
+    CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE,
     HID_MI_INPUT,
     0x00,
     /* END COLLECTION */
@@ -149,7 +174,11 @@ static const uint8_t zmk_hid_report_desc[] = {
 struct zmk_hid_keyboard_report_body {
     zmk_mod_flags_t modifiers;
     uint8_t _reserved;
-    uint8_t keys[ZMK_HID_KEYBOARD_NKRO_SIZE];
+#if IS_ENABLED(CONFIG_ZMK_HID_REPORT_TYPE_NKRO)
+    uint8_t keys[(ZMK_HID_KEYBOARD_NKRO_MAX_USAGE + 1) / 8];
+#elif IS_ENABLED(CONFIG_ZMK_HID_REPORT_TYPE_HKRO)
+    uint8_t keys[CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE];
+#endif
 } __packed;
 
 struct zmk_hid_keyboard_report {
@@ -158,7 +187,7 @@ struct zmk_hid_keyboard_report {
 } __packed;
 
 struct zmk_hid_consumer_report_body {
-    uint16_t keys[ZMK_HID_CONSUMER_NKRO_SIZE];
+    uint16_t keys[CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE];
 } __packed;
 
 struct zmk_hid_consumer_report {
diff --git a/app/src/hid.c b/app/src/hid.c
index 7ab080e5..756ed900 100644
--- a/app/src/hid.c
+++ b/app/src/hid.c
@@ -69,8 +69,30 @@ int zmk_hid_unregister_mods(zmk_mod_flags_t modifiers) {
     return 0;
 }
 
+#if IS_ENABLED(CONFIG_ZMK_HID_REPORT_TYPE_NKRO)
+
+#define TOGGLE_KEYBOARD(code, val) WRITE_BIT(keyboard_report.body.keys[code / 8], code % 8, val)
+
+static inline int select_keyboard_usage(zmk_key_t usage) {
+    if (usage > ZMK_HID_KEYBOARD_NKRO_MAX_USAGE) {
+        return -EINVAL;
+    }
+    TOGGLE_KEYBOARD(usage, 1);
+    return 0;
+}
+
+static inline int deselect_keyboard_usage(zmk_key_t usage) {
+    if (usage > ZMK_HID_KEYBOARD_NKRO_MAX_USAGE) {
+        return -EINVAL;
+    }
+    TOGGLE_KEYBOARD(usage, 0);
+    return 0;
+}
+
+#elif IS_ENABLED(CONFIG_ZMK_HID_REPORT_TYPE_HKRO)
+
 #define TOGGLE_KEYBOARD(match, val)                                                                \
-    for (int idx = 0; idx < ZMK_HID_KEYBOARD_NKRO_SIZE; idx++) {                                   \
+    for (int idx = 0; idx < CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE; idx++) {                          \
         if (keyboard_report.body.keys[idx] != match) {                                             \
             continue;                                                                              \
         }                                                                                          \
@@ -80,8 +102,22 @@ int zmk_hid_unregister_mods(zmk_mod_flags_t modifiers) {
         }                                                                                          \
     }
 
+static inline int select_keyboard_usage(zmk_key_t usage) {
+    TOGGLE_KEYBOARD(0U, usage);
+    return 0;
+}
+
+static inline int deselect_keyboard_usage(zmk_key_t usage) {
+    TOGGLE_KEYBOARD(usage, 0U);
+    return 0;
+}
+
+#else
+#error "A proper HID report type must be selected"
+#endif
+
 #define TOGGLE_CONSUMER(match, val)                                                                \
-    for (int idx = 0; idx < ZMK_HID_CONSUMER_NKRO_SIZE; idx++) {                                   \
+    for (int idx = 0; idx < CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE; idx++) {                          \
         if (consumer_report.body.keys[idx] != match) {                                             \
             continue;                                                                              \
         }                                                                                          \
@@ -105,7 +141,7 @@ int zmk_hid_keyboard_press(zmk_key_t code) {
     if (code >= HID_USAGE_KEY_KEYBOARD_LEFTCONTROL && code <= HID_USAGE_KEY_KEYBOARD_RIGHT_GUI) {
         return zmk_hid_register_mod(code - HID_USAGE_KEY_KEYBOARD_LEFTCONTROL);
     }
-    TOGGLE_KEYBOARD(0U, code);
+    select_keyboard_usage(code);
     return 0;
 };
 
@@ -113,7 +149,7 @@ int zmk_hid_keyboard_release(zmk_key_t code) {
     if (code >= HID_USAGE_KEY_KEYBOARD_LEFTCONTROL && code <= HID_USAGE_KEY_KEYBOARD_RIGHT_GUI) {
         return zmk_hid_unregister_mod(code - HID_USAGE_KEY_KEYBOARD_LEFTCONTROL);
     }
-    TOGGLE_KEYBOARD(code, 0U);
+    deselect_keyboard_usage(code);
     return 0;
 };