From b273f40bc0a857062bac57aab994806e0d0df847 Mon Sep 17 00:00:00 2001 From: Taylor Yu Date: Thu, 30 Nov 2023 11:36:01 -0600 Subject: [PATCH] hybrid boot keyboard Make the Boot Keyboard the only keyboard, and make it have a hybrid report format in Report Protoocol, while still ending Boot Reports in Boot Protocol. The hybrid reports have the Boot Report as a prefix, which might help hosts that don't request Boot Protocol but still expect to see it. Include a way to switch between the hybrid report, and sending only Boot Protocol regardless of whether the host has requested it. Defaults to sending hybrid protocol, but this can be changed at compile time. Signed-off-by: Taylor Yu --- src/BootKeyboard/BootKeyboard.cpp | 432 +++++++++++++++++++----------- src/BootKeyboard/BootKeyboard.h | 59 +++- src/KeyboardioHID.h | 1 - src/MultiReport/Keyboard.cpp | 276 ------------------- src/MultiReport/Keyboard.h | 77 ------ 5 files changed, 334 insertions(+), 511 deletions(-) delete mode 100644 src/MultiReport/Keyboard.cpp delete mode 100644 src/MultiReport/Keyboard.h diff --git a/src/BootKeyboard/BootKeyboard.cpp b/src/BootKeyboard/BootKeyboard.cpp index dd4b6156..c44791fa 100644 --- a/src/BootKeyboard/BootKeyboard.cpp +++ b/src/BootKeyboard/BootKeyboard.cpp @@ -35,14 +35,25 @@ static const uint8_t boot_keyboard_hid_descriptor_[] PROGMEM = { D_USAGE, D_USAGE_KEYBOARD, D_COLLECTION, D_APPLICATION, - // Modifiers - D_USAGE_PAGE, D_PAGE_KEYBOARD, - D_USAGE_MINIMUM, 0xe0, - D_USAGE_MAXIMUM, 0xe7, + + // LEDs + D_REPORT_COUNT, 0x8, + D_REPORT_SIZE, 0x1, + D_USAGE_PAGE, D_PAGE_LEDS, + D_USAGE_MINIMUM, 0x1, + D_USAGE_MAXIMUM, 0x8, D_LOGICAL_MINIMUM, 0x0, D_LOGICAL_MAXIMUM, 0x1, - D_REPORT_SIZE, 0x1, - D_REPORT_COUNT, 0x8, + D_OUTPUT, (D_DATA | D_VARIABLE | D_ABSOLUTE), + + // Modifiers + D_USAGE_PAGE, D_PAGE_KEYBOARD, + D_USAGE_MINIMUM, HID_KEYBOARD_FIRST_MODIFIER, + D_USAGE_MAXIMUM, HID_KEYBOARD_LAST_MODIFIER, + // D_LOGICAL_MINIMUM, 0x0, // redundant; already 0 + // D_LOGICAL_MAXIMUM, 0x1, // redundant; already 1 + // D_REPORT_SIZE, 0x1, // redundant; already 1 + // D_REPORT_COUNT, 0x8, // redundant; already 8 D_INPUT, (D_DATA | D_VARIABLE | D_ABSOLUTE), // Reserved byte @@ -50,40 +61,80 @@ static const uint8_t boot_keyboard_hid_descriptor_[] PROGMEM = { D_REPORT_SIZE, 0x8, D_INPUT, (D_CONSTANT), - // LEDs - D_REPORT_COUNT, 0x5, - D_REPORT_SIZE, 0x1, - D_USAGE_PAGE, D_PAGE_LEDS, - D_USAGE_MINIMUM, 0x1, - D_USAGE_MAXIMUM, 0x5, - D_OUTPUT, (D_DATA | D_VARIABLE | D_ABSOLUTE), - // Pad LEDs up to a byte - D_REPORT_COUNT, 0x1, - D_REPORT_SIZE, 0x3, - D_OUTPUT, (D_CONSTANT), - // Non-modifiers - D_REPORT_COUNT, 0x6, - D_REPORT_SIZE, 0x8, - D_LOGICAL_MINIMUM, 0x0, - D_LOGICAL_MAXIMUM, 0xff, - D_USAGE_PAGE, D_PAGE_KEYBOARD, - D_USAGE_MINIMUM, 0x0, - D_USAGE_MAXIMUM, 0xff, + D_REPORT_COUNT, BOOT_KEY_BYTES, + // D_REPORT_SIZE, 0x8, // redundant; already 8 + // D_LOGICAL_MINIMUM, HID_FIRST_KEY, // redundant; already 0 + D_MULTIBYTE(D_LOGICAL_MAXIMUM), HID_LAST_KEY, 0x0, // make sure it's not negative + // D_USAGE_PAGE, D_PAGE_KEYBOARD, // redundant; already KEYBOARD + D_USAGE_MINIMUM, HID_FIRST_KEY, + D_USAGE_MAXIMUM, HID_LAST_KEY, D_INPUT, (D_DATA | D_ARRAY | D_ABSOLUTE), D_END_COLLECTION }; -#ifdef ARCH_HAS_CONFIGURABLE_EP_SIZES -static const uint8_t BOOT_KEYBOARD_EP_SIZE = 8; -#else -static const uint8_t BOOT_KEYBOARD_EP_SIZE = USB_EP_SIZE; +static const uint8_t hybrid_keyboard_hid_descriptor_[] PROGMEM = { + // Hybrid Boot/NKRO Keyboard + D_USAGE_PAGE, D_PAGE_GENERIC_DESKTOP, + D_USAGE, D_USAGE_KEYBOARD, + + D_COLLECTION, D_APPLICATION, + + /* 5 LEDs for num lock etc, 3 left for advanced, custom usage */ + D_USAGE_PAGE, D_PAGE_LEDS, + D_USAGE_MINIMUM, 0x01, + D_USAGE_MAXIMUM, 0x08, + D_LOGICAL_MINIMUM, 0x00, + D_LOGICAL_MAXIMUM, 0x01, + D_REPORT_SIZE, 0x01, + D_REPORT_COUNT, 0x08, + D_OUTPUT, (D_DATA | D_VARIABLE | D_ABSOLUTE), + + D_USAGE_PAGE, D_PAGE_KEYBOARD, + + /* Key modifier byte for both boot and NKRO */ + D_USAGE_MINIMUM, HID_KEYBOARD_FIRST_MODIFIER, + D_USAGE_MAXIMUM, HID_KEYBOARD_LAST_MODIFIER, + // D_LOGICAL_MINIMUM, 0x00, // redundant; already 0 + // D_LOGICAL_MAXIMUM, 0x01, // redundant; already 1 + // D_REPORT_SIZE, 0x01, // redundant; already 1 + // D_REPORT_COUNT, 0x08, // redundant; already 8 + D_INPUT, (D_DATA | D_VARIABLE | D_ABSOLUTE), + + /* Send rest of boot report as padding, so HID-aware hosts will ignore */ + D_REPORT_SIZE, 0x8, + D_REPORT_COUNT, 0x7, + D_INPUT, (D_CONSTANT), + + /* NKRO key bitmap */ + + // Padding 4 bits, to skip NO_EVENT & 3 error states. + D_REPORT_SIZE, 0x1, + D_REPORT_COUNT, 0x04, + D_INPUT, (D_CONSTANT), + + // Actual non-modifier keys + D_USAGE_MINIMUM, HID_KEYBOARD_A_AND_A, + D_USAGE_MAXIMUM, HID_LAST_KEY, + // D_LOGICAL_MINIMUM, 0x00, // redundant; already 0 + // D_LOGICAL_MAXIMUM, 0x01, // redundant; already 1 + // D_REPORT_SIZE, 0x01, // redundant; already 1 + D_REPORT_COUNT, (NKRO_KEY_BITS - 4), + D_INPUT, (D_DATA | D_VARIABLE | D_ABSOLUTE), + +#if (NKRO_KEY_BITS % 8) + // Padding to round up the report to byte boundary. + // D_REPORT_SIZE, 0x01, // redundant; already 1 + D_REPORT_COUNT, (8 - (NKRO_KEY_BITS % 8)), + D_INPUT, (D_CONSTANT), #endif + D_END_COLLECTION, +}; -BootKeyboard_::BootKeyboard_(uint8_t protocol_) : PluggableUSBModule(1, 1, epType), default_protocol(protocol_), protocol(protocol_), idle(0), leds(0) { +BootKeyboard_::BootKeyboard_(uint8_t bootkb_only_) : PluggableUSBModule(1, 1, epType), protocol(HID_REPORT_PROTOCOL), idle(0), leds(0), bootkb_only(bootkb_only) { #ifdef ARCH_HAS_CONFIGURABLE_EP_SIZES - epType[0] = EP_TYPE_INTERRUPT_IN(BOOT_KEYBOARD_EP_SIZE); // This is an 8 byte report, so ask for an 8 byte buffer, so reports aren't split + epType[0] = EP_TYPE_INTERRUPT_IN(USB_EP_SIZE); #else epType[0] = EP_TYPE_INTERRUPT_IN; #endif @@ -92,10 +143,16 @@ BootKeyboard_::BootKeyboard_(uint8_t protocol_) : PluggableUSBModule(1, 1, epTyp int BootKeyboard_::getInterface(uint8_t* interfaceCount) { *interfaceCount += 1; // uses 1 + size_t desclen; + if (bootkb_only) { + desclen = sizeof(boot_keyboard_hid_descriptor_); + } else { + desclen = sizeof(hybrid_keyboard_hid_descriptor_); + } HIDDescriptor hidInterface = { D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_BOOT_INTERFACE, HID_PROTOCOL_KEYBOARD), - D_HIDREPORT(sizeof(boot_keyboard_hid_descriptor_)), - D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, BOOT_KEYBOARD_EP_SIZE, 0x01) + D_HIDREPORT(desclen), + D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01) }; return USB_SendControl(0, &hidInterface, sizeof(hidInterface)); } @@ -114,7 +171,11 @@ int BootKeyboard_::getDescriptor(USBSetup& setup) { return 0; } - return USB_SendControl(TRANSFER_PGM, boot_keyboard_hid_descriptor_, sizeof(boot_keyboard_hid_descriptor_)); + if (bootkb_only) { + return USB_SendControl(TRANSFER_PGM, boot_keyboard_hid_descriptor_, sizeof(boot_keyboard_hid_descriptor_)); + } else { + return USB_SendControl(TRANSFER_PGM, hybrid_keyboard_hid_descriptor_, sizeof(hybrid_keyboard_hid_descriptor_)); + } } @@ -185,14 +246,6 @@ bool BootKeyboard_::setup(USBSetup& setup) { return true; } } - - // Input (set HID report) - else if (setup.wValueH == HID_REPORT_TYPE_INPUT) { - if (length == sizeof(report_)) { - USB_RecvControl(&report_, length); - return true; - } - } } } @@ -207,131 +260,140 @@ uint8_t BootKeyboard_::getProtocol() { return protocol; } -void BootKeyboard_::setProtocol(uint8_t protocol) { - this->protocol = protocol; +uint8_t BootKeyboard_::getBootOnly() { + return bootkb_only; } -int BootKeyboard_::sendReport() { - if (memcmp(&last_report_, &report_, sizeof(report_))) { - // if the two reports are different, send a report - int returnCode = USB_Send(pluggedEndpoint | TRANSFER_RELEASE, &report_, sizeof(report_)); - HIDReportObserver::observeReport(HID_REPORTID_KEYBOARD, &report_, sizeof(report_), returnCode); - memcpy(&last_report_, &report_, sizeof(report_)); - return returnCode; - } - return -1; +/* + * Set whether to send only Boot Protocol regardless of whether Boot + * Protocol has been requested. Used by USBQuirks to toggle protocol + * modes. + * + * This is only really safe to call after detaching from USB, because + * otherwise, the report format might get out of sync with what the host + * expects, and the host probably won't see the new Report Descriptor + * until after a re-attach. + */ +void BootKeyboard_::setBootOnly(uint8_t bootonly) { + bootkb_only = bootonly; } -// press() adds the specified key (printing, non-printing, or modifier) -// to the persistent key report and sends the report. Because of the way -// USB HID works, the host acts like the key remains pressed until we -// call release(), releaseAll(), or otherwise clear the report and resend. - - -size_t BootKeyboard_::press(uint8_t k) { - uint8_t done = 0; - - if ((k >= HID_KEYBOARD_FIRST_MODIFIER) && (k <= HID_KEYBOARD_LAST_MODIFIER)) { - // it's a modifier key - report_.modifiers |= (0x01 << (k - HID_KEYBOARD_FIRST_MODIFIER)); - } else { - // it's some other key: - // Add k to the key report only if it's not already present - // and if there is an empty slot. - for (uint8_t i = 0; i < sizeof(report_.keycodes); i++) { - if (report_.keycodes[i] != k) { // is k already in list? - if (0 == report_.keycodes[i]) { // have we found an empty slot? - report_.keycodes[i] = k; - done = 1; - break; - } - } else { - done = 1; - break; - } - } - // use separate variable to check if slot was found - // for style reasons - we do not know how the compiler - // handles the for() index when it leaves the loop - if (0 == done) { - return 0; +void BootKeyboard_::convertReport(uint8_t *boot, const uint8_t *nkro) { + uint8_t n_boot_keys = 0; + uint8_t b; + // Convert NKRO report to boot report + memset(boot, HID_KEYBOARD_NO_EVENT, BOOT_KEY_BYTES); + for (uint8_t i = 0; i < NKRO_KEY_BYTES; i++) { + b = nkro[i]; + if (b == 0) { + continue; } - } - return 1; -} - - -// release() takes the specified key out of the persistent key report and -// sends the report. This tells the OS the key is no longer pressed and that -// it shouldn't be repeated any more. - -size_t BootKeyboard_::release(uint8_t k) { - if ((k >= HID_KEYBOARD_FIRST_MODIFIER) && (k <= HID_KEYBOARD_LAST_MODIFIER)) { - // it's a modifier key - report_.modifiers = report_.modifiers & (~(0x01 << (k - HID_KEYBOARD_FIRST_MODIFIER))); - } else { - // it's some other key: - // Test the key report to see if k is present. Clear it if it exists. - // Check all positions in case the key is present more than once (which it shouldn't be) - for (uint8_t i = 0; i < sizeof(report_.keycodes); i++) { - if (report_.keycodes[i] == k) { - report_.keycodes[i] = 0; + for (uint8_t j = 0; j < 8; j++) { + bool bit = b & 1; + b >>= 1; + if (bit == 0) { + continue; } - } - - // rearrange the keys list so that the free (= 0x00) are at the - // end of the keys list - some implementations stop for keys at the - // first occurence of an 0x00 in the keys list - // so (0x00)(0x01)(0x00)(0x03)(0x02)(0x00) becomes - // (0x03)(0x02)(0x01)(0x00)(0x00)(0x00) - uint8_t current = 0, nextpos = 0; - - while (current < sizeof(report_.keycodes)) { - if (report_.keycodes[current]) { - uint8_t tmp = report_.keycodes[nextpos]; - report_.keycodes[nextpos] = report_.keycodes[current]; - report_.keycodes[current] = tmp; - ++nextpos; + // Check is here so we can set all BOOT_KEY_BYTES + if (n_boot_keys >= BOOT_KEY_BYTES) { + // Send rollover error if too many keys are held + memset(boot, HID_KEYBOARD_ERROR_ROLLOVER, BOOT_KEY_BYTES); + return; } - ++current; + boot[n_boot_keys++] = 8 * i + j; } } - - return 1; } - -void BootKeyboard_::releaseAll() { - memset(&report_.bytes, 0x00, sizeof(report_.bytes)); +/* Send a report without the extra modifier change handling */ +int BootKeyboard_::sendReportUnchecked() { + HID_BootKeyboardReport_Data_t out_report; + out_report.modifiers = last_report_.modifiers; + out_report.reserved = 0; + memcpy(out_report.nkro_keys, last_report_.keys, sizeof(last_report_.keys)); + convertReport(out_report.boot_keycodes, out_report.nkro_keys); + size_t reportlen; + // Send only boot report if host requested boot protocol, or if configured as boot-only + if (protocol == HID_BOOT_PROTOCOL || bootkb_only) { + reportlen = BOOT_REPORT_LEN; + } else { + reportlen = sizeof(out_report); + } + int returnCode = USB_Send(pluggedEndpoint | TRANSFER_RELEASE, &out_report, reportlen); + HIDReportObserver::observeReport(HID_REPORTID_KEYBOARD, &out_report, reportlen, returnCode); + return returnCode; } +// Sending the current HID report to the host: +// +// Depending on the differences between the current and previous HID reports, we +// might need to send one or two extra reports to guarantee that the host will +// process the changes in the correct order. There are two important scenarios +// to consider: +// +// 1. If a non-modifier keycode toggles off in the same report as a modifier +// changes, the host might process the modifier change first. For example, if +// both `shift` and `4` toggle off in the same report (most likely from a +// `LSHIFT(Key_4)` key being released), and that key has been held long enough +// to trigger character repeat, we could end up with a plain `4` in the output +// at the end of the repeat: `$$$$4` instead of `$$$$$`. +// +// 2. If a non-modifier keycode toggles on in the same report as a modifier +// changes, the host might process the non-modifer first. For example, pressing +// and holding an `LSHIFT(Key_4)` key might result in `4$$$` rather than `$$$$`. +// +// Therefore, each call to `sendReport()` must send (up to) three reports to the +// host to guarantee the correct order of processing: +// +// 1. A report with toggled-off non-modifiers removed. +// 2. A report with changes to modifiers. +// 3. A report with toggled-on non-modifiers added. -/* Returns true if the non-modifer key passed in will be sent during this key report - * Returns false in all other cases - * */ -bool BootKeyboard_::isKeyPressed(uint8_t k) { - for (uint8_t i = 0; i < sizeof(report_.keycodes); i++) { - if (report_.keycodes[i] == k) { - return true; +int BootKeyboard_::sendReport() { + // If the new HID report differs from the previous one both in active modifier + // keycodes and non-modifier keycodes, we will need to send at least one extra + // report. First, we compare the modifiers bytes of the two reports. + const uint8_t old_modifiers = last_report_.modifiers; + const uint8_t new_modifiers = report_.modifiers; + + const uint8_t changed_modifiers = old_modifiers ^ new_modifiers; + + if (changed_modifiers != 0) { + // There was at least one modifier change (toggled on or off), remove any + // non-modifiers from the stored previous report that toggled off in the new + // report, and send it to the host. + bool non_modifiers_toggled_off = false; + for (uint8_t i = 0; i < NKRO_KEY_BYTES; ++i) { + byte released_keycodes = last_report_.keys[i] & ~(report_.keys[i]); + if (released_keycodes != 0) { + last_report_.keys[i] &= ~released_keycodes; + non_modifiers_toggled_off = true; + } + } + if (non_modifiers_toggled_off) { + sendReportUnchecked(); } + // Next, update the modifiers byte of the stored previous report, and send + // it. + last_report_.modifiers = new_modifiers; + sendReportUnchecked(); } - return false; -} -/* Returns true if the non-modifer key passed in was sent during the previous key report - * Returns false in all other cases - * */ -bool BootKeyboard_::wasKeyPressed(uint8_t k) { - for (uint8_t i = 0; i < sizeof(report_.keycodes); i++) { - if (last_report_.keycodes[i] == k) { - return true; - } + // Finally, copy the new report to the previous one, and send it. + if (memcmp(last_report_.keys, report_.keys, sizeof(report_.keys)) != 0) { + memcpy(last_report_.keys, report_.keys, sizeof(report_.keys)); + return sendReportUnchecked(); } - return false; + // A note on return values: Kaleidoscope doesn't actually check the return + // value of `sendReport()`, so this function could be changed to return + // void. It would be nice if we could do something to recover from an error + // here, but it's not at all clear what that should be. Also note that if the + // extra reports above return an error, there's not much we can do to try to + // recover. We could try to send the report again, but that would be likely to + // fail as well. + return -1; } - - /* Returns true if the modifer key passed in will be sent during this key report * Returns false in all other cases * */ @@ -354,20 +416,90 @@ bool BootKeyboard_::wasModifierActive(uint8_t k) { return false; } -/* Returns true if any modifier key will be sent during this key report +/* Returns true if *any* modifier will be sent during this key report * Returns false in all other cases * */ bool BootKeyboard_::isAnyModifierActive() { return report_.modifiers > 0; } -/* Returns true if any modifier key was being sent during the previous key report +/* Returns true if *any* modifier was being sent during the previous key report * Returns false in all other cases * */ bool BootKeyboard_::wasAnyModifierActive() { return last_report_.modifiers > 0; } + +/* Returns true if the non-modifier key passed in will be sent during this key report + * Returns false in all other cases + * */ +bool BootKeyboard_::isKeyPressed(uint8_t k) { + if (k <= HID_LAST_KEY) { + uint8_t bit = 1 << (uint8_t(k) % 8); + return !!(report_.keys[k / 8] & bit); + } + return false; +} + +/* Returns true if the non-modifer key passed in was sent during the previous key report + * Returns false in all other cases + * */ +bool BootKeyboard_::wasKeyPressed(uint8_t k) { + + if (k <= HID_LAST_KEY) { + uint8_t bit = 1 << (uint8_t(k) % 8); + return !!(last_report_.keys[k / 8] & bit); + } + return false; +} + + +size_t BootKeyboard_::press(uint8_t k) { + // If the key is in the range of 'printable' keys + if (k <= HID_LAST_KEY) { + uint8_t bit = 1 << (uint8_t(k) % 8); + report_.keys[k / 8] |= bit; + return 1; + } + + // It's a modifier key + else if (k >= HID_KEYBOARD_FIRST_MODIFIER && k <= HID_KEYBOARD_LAST_MODIFIER) { + // Convert key into bitfield (0 - 7) + k = k - HID_KEYBOARD_FIRST_MODIFIER; + report_.modifiers |= (1 << k); + return 1; + } + + // No empty/pressed key was found + return 0; +} + +size_t BootKeyboard_::release(uint8_t k) { + // If we're releasing a printable key + if (k <= HID_LAST_KEY) { + uint8_t bit = 1 << (k % 8); + report_.keys[k / 8] &= ~bit; + return 1; + } + + // It's a modifier key + else if (k >= HID_KEYBOARD_FIRST_MODIFIER && k <= HID_KEYBOARD_LAST_MODIFIER) { + // Convert key into bitfield (0 - 7) + k = k - HID_KEYBOARD_FIRST_MODIFIER; + report_.modifiers &= ~(1 << k); + return 1; + } + + // No empty/pressed key was found + return 0; +} + +void BootKeyboard_::releaseAll() { + // Release all keys + memset(&report_.allkeys, 0x00, sizeof(report_.allkeys)); +} + /* * Hook function to reset any needed state after a USB reset. * @@ -375,7 +507,7 @@ bool BootKeyboard_::wasAnyModifierActive() { * required by the HID specification. */ void BootKeyboard_::onUSBReset() { - protocol = default_protocol; + protocol = HID_REPORT_PROTOCOL; } __attribute__((weak)) diff --git a/src/BootKeyboard/BootKeyboard.h b/src/BootKeyboard/BootKeyboard.h index 38f263e7..65c07f4f 100644 --- a/src/BootKeyboard/BootKeyboard.h +++ b/src/BootKeyboard/BootKeyboard.h @@ -31,20 +31,59 @@ THE SOFTWARE. #include "HIDTables.h" #include "HIDAliases.h" +// Declare the hybrid boot keyboard feature so Kaleidoscope can depend on it +#define BOOTKB_HYBRID 1 + +#define BOOT_KEY_BYTES 6 +#define BOOT_REPORT_LEN 8 + +#define NKRO_KEY_BITS (4 + HID_LAST_KEY - HID_KEYBOARD_A_AND_A + 1) +#define NKRO_KEY_BYTES ((NKRO_KEY_BITS + 7) / 8) + +/* + * Keep the current key states in a NKRO bitmap. We'll convert it to Boot + * Protocol as needed. + */ +typedef union { + // Modifiers + keymap + struct { + uint8_t modifiers; + uint8_t keys[NKRO_KEY_BYTES]; + }; + uint8_t allkeys[1 + NKRO_KEY_BYTES]; +} HID_NKRO_KeyboardReport_Data_t; + +/* + * Hybrid boot report that contains the Boot Protocol report as a prefix to + * a bitmap NKRO report. The keycodes array of the Boot Report is marked as + * padding in the Report Descriptor, so HID-aware hosts will ignore it, but + * hosts that require Boot Protocol without requesting it have a chance of + * working, assuming they can deal with the extended report. + * + * We do send only the Boot Report if the host has requested Boot Protocol. + */ typedef union { - // Low level key report: up to 6 keys and shift, ctrl etc at once + // Hybrid report: boot keyboard report + NKRO report struct { + // Boot/NKRO keyboard modifiers uint8_t modifiers; uint8_t reserved; - uint8_t keycodes[6]; + // Boot keyboard non-modifier keys array + uint8_t boot_keycodes[BOOT_KEY_BYTES]; + // NKRO keyboard non-modifiers keys bitmap + uint8_t nkro_keys[NKRO_KEY_BYTES]; }; - uint8_t bytes[8]; + uint8_t bytes[BOOT_REPORT_LEN + NKRO_KEY_BYTES]; } HID_BootKeyboardReport_Data_t; class BootKeyboard_ : public PluggableUSBModule { public: - BootKeyboard_(uint8_t protocol_ = HID_REPORT_PROTOCOL); + /* + * Change to `bootkb_only_ = 1` if you need to default to only sending + * Boot Protocol, even if in Report Protocol. + */ + BootKeyboard_(uint8_t bootkb_only_ = 0); size_t press(uint8_t k); void begin(); void end(); @@ -62,13 +101,14 @@ class BootKeyboard_ : public PluggableUSBModule { uint8_t getLeds(); uint8_t getProtocol(); - void setProtocol(uint8_t protocol); - uint8_t default_protocol; + uint8_t getBootOnly(); + void setBootOnly(uint8_t bootonly); + void onUSBReset(); protected: - HID_BootKeyboardReport_Data_t report_, last_report_; + HID_NKRO_KeyboardReport_Data_t report_, last_report_; // Implementation of the PUSBListNode int getInterface(uint8_t* interfaceCount); @@ -80,5 +120,10 @@ class BootKeyboard_ : public PluggableUSBModule { uint8_t idle; uint8_t leds; + + uint8_t bootkb_only; + private: + void convertReport(uint8_t *boot, const uint8_t *nkro); + int sendReportUnchecked(); }; extern BootKeyboard_& BootKeyboard(); diff --git a/src/KeyboardioHID.h b/src/KeyboardioHID.h index 7f68e208..5e700cb5 100644 --- a/src/KeyboardioHID.h +++ b/src/KeyboardioHID.h @@ -47,7 +47,6 @@ THE SOFTWARE. #include "MultiReport/ConsumerControl.h" #include "MultiReport/Gamepad.h" #include "MultiReport/SystemControl.h" -#include "MultiReport/Keyboard.h" #include "SingleReport/SingleAbsoluteMouse.h" diff --git a/src/MultiReport/Keyboard.cpp b/src/MultiReport/Keyboard.cpp deleted file mode 100644 index 8cd8de4f..00000000 --- a/src/MultiReport/Keyboard.cpp +++ /dev/null @@ -1,276 +0,0 @@ -/* -Copyright (c) 2014-2015 NicoHood -Copyright (c) 2015-2018 Keyboard.io, Inc - -See the readme for credit to other people. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -#include "Keyboard.h" -#include "DescriptorPrimitives.h" - -static const uint8_t nkro_keyboard_hid_descriptor_[] PROGMEM = { - // NKRO Keyboard - D_USAGE_PAGE, D_PAGE_GENERIC_DESKTOP, - D_USAGE, D_USAGE_KEYBOARD, - D_COLLECTION, D_APPLICATION, - D_REPORT_ID, HID_REPORTID_NKRO_KEYBOARD, - D_USAGE_PAGE, D_PAGE_KEYBOARD, - - /* Key modifier byte */ - D_USAGE_MINIMUM, HID_KEYBOARD_FIRST_MODIFIER, - D_USAGE_MAXIMUM, HID_KEYBOARD_LAST_MODIFIER, - D_LOGICAL_MINIMUM, 0x00, - D_LOGICAL_MAXIMUM, 0x01, - D_REPORT_SIZE, 0x01, - D_REPORT_COUNT, 0x08, - D_INPUT, (D_DATA | D_VARIABLE | D_ABSOLUTE), - - /* 5 LEDs for num lock etc, 3 left for advanced, custom usage */ - D_USAGE_PAGE, D_PAGE_LEDS, - D_USAGE_MINIMUM, 0x01, - D_USAGE_MAXIMUM, 0x08, - D_REPORT_COUNT, 0x08, - D_REPORT_SIZE, 0x01, - D_OUTPUT, (D_DATA | D_VARIABLE | D_ABSOLUTE), - - /* NKRO Keyboard */ - D_USAGE_PAGE, D_PAGE_KEYBOARD, - - // Padding 4 bits, to skip NO_EVENT & 3 error states. - D_REPORT_SIZE, 0x04, - D_REPORT_COUNT, 0x01, - D_INPUT, (D_CONSTANT), - - D_USAGE_MINIMUM, HID_KEYBOARD_A_AND_A, - D_USAGE_MAXIMUM, HID_LAST_KEY, - D_LOGICAL_MINIMUM, 0x00, - D_LOGICAL_MAXIMUM, 0x01, - D_REPORT_SIZE, 0x01, - D_REPORT_COUNT, (KEY_BITS - 4), - D_INPUT, (D_DATA | D_VARIABLE | D_ABSOLUTE), - -#if (KEY_BITS % 8) - // Padding to round up the report to byte boundary. - D_REPORT_SIZE, (8 - (KEY_BITS % 8)), - D_REPORT_COUNT, 0x01, - D_INPUT, (D_CONSTANT), -#endif - - D_END_COLLECTION, -}; - -Keyboard_::Keyboard_() { - static HIDSubDescriptor node(nkro_keyboard_hid_descriptor_, - sizeof(nkro_keyboard_hid_descriptor_)); - HID().AppendDescriptor(&node); -} - -void Keyboard_::begin() { -} - - -void Keyboard_::end() { - releaseAll(); - sendReportUnchecked(); -} - -int Keyboard_::sendReportUnchecked() { - return HID().SendReport(HID_REPORTID_NKRO_KEYBOARD, - &last_report_, sizeof(last_report_)); -} - -// Sending the current HID report to the host: -// -// Depending on the differences between the current and previous HID reports, we -// might need to send one or two extra reports to guarantee that the host will -// process the changes in the correct order. There are two important scenarios -// to consider: -// -// 1. If a non-modifier keycode toggles off in the same report as a modifier -// changes, the host might process the modifier change first. For example, if -// both `shift` and `4` toggle off in the same report (most likely from a -// `LSHIFT(Key_4)` key being released), and that key has been held long enough -// to trigger character repeat, we could end up with a plain `4` in the output -// at the end of the repeat: `$$$$4` instead of `$$$$$`. -// -// 2. If a non-modifier keycode toggles on in the same report as a modifier -// changes, the host might process the non-modifer first. For example, pressing -// and holding an `LSHIFT(Key_4)` key might result in `4$$$` rather than `$$$$`. -// -// Therefore, each call to `sendReport()` must send (up to) three reports to the -// host to guarantee the correct order of processing: -// -// 1. A report with toggled-off non-modifiers removed. -// 2. A report with changes to modifiers. -// 3. A report with toggled-on non-modifiers added. - -int Keyboard_::sendReport() { - // If the new HID report differs from the previous one both in active modifier - // keycodes and non-modifier keycodes, we will need to send at least one extra - // report. First, we compare the modifiers bytes of the two reports. - const uint8_t old_modifiers = last_report_.modifiers; - const uint8_t new_modifiers = report_.modifiers; - - const uint8_t changed_modifiers = old_modifiers ^ new_modifiers; - - if (changed_modifiers != 0) { - // There was at least one modifier change (toggled on or off), remove any - // non-modifiers from the stored previous report that toggled off in the new - // report, and send it to the host. - bool non_modifiers_toggled_off = false; - for (uint8_t i = 0; i < KEY_BYTES; ++i) { - byte released_keycodes = last_report_.keys[i] & ~(report_.keys[i]); - if (released_keycodes != 0) { - last_report_.keys[i] &= ~released_keycodes; - non_modifiers_toggled_off = true; - } - } - if (non_modifiers_toggled_off) { - sendReportUnchecked(); - } - // Next, update the modifiers byte of the stored previous report, and send - // it. - last_report_.modifiers = new_modifiers; - sendReportUnchecked(); - } - - // Finally, copy the new report to the previous one, and send it. - if (memcmp(last_report_.keys, report_.keys, sizeof(report_.keys)) != 0) { - memcpy(last_report_.keys, report_.keys, sizeof(report_.keys)); - return sendReportUnchecked(); - } - // A note on return values: Kaleidoscope doesn't actually check the return - // value of `sendReport()`, so this function could be changed to return - // void. It would be nice if we could do something to recover from an error - // here, but it's not at all clear what that should be. Also note that if the - // extra reports above return an error, there's not much we can do to try to - // recover. We could try to send the report again, but that would be likely to - // fail as well. - return -1; -} - -/* Returns true if the modifer key passed in will be sent during this key report - * Returns false in all other cases - * */ -bool Keyboard_::isModifierActive(uint8_t k) { - if (k >= HID_KEYBOARD_FIRST_MODIFIER && k <= HID_KEYBOARD_LAST_MODIFIER) { - k = k - HID_KEYBOARD_FIRST_MODIFIER; - return !!(report_.modifiers & (1 << k)); - } - return false; -} - -/* Returns true if the modifer key passed in was being sent during the previous key report - * Returns false in all other cases - * */ -bool Keyboard_::wasModifierActive(uint8_t k) { - if (k >= HID_KEYBOARD_FIRST_MODIFIER && k <= HID_KEYBOARD_LAST_MODIFIER) { - k = k - HID_KEYBOARD_FIRST_MODIFIER; - return !!(last_report_.modifiers & (1 << k)); - } - return false; -} - -/* Returns true if *any* modifier will be sent during this key report - * Returns false in all other cases - * */ -bool Keyboard_::isAnyModifierActive() { - return report_.modifiers > 0; -} - -/* Returns true if *any* modifier was being sent during the previous key report - * Returns false in all other cases - * */ -bool Keyboard_::wasAnyModifierActive() { - return last_report_.modifiers > 0; -} - - -/* Returns true if the non-modifier key passed in will be sent during this key report - * Returns false in all other cases - * */ -bool Keyboard_::isKeyPressed(uint8_t k) { - if (k <= HID_LAST_KEY) { - uint8_t bit = 1 << (uint8_t(k) % 8); - return !!(report_.keys[k / 8] & bit); - } - return false; -} - -/* Returns true if the non-modifer key passed in was sent during the previous key report - * Returns false in all other cases - * */ -bool Keyboard_::wasKeyPressed(uint8_t k) { - - if (k <= HID_LAST_KEY) { - uint8_t bit = 1 << (uint8_t(k) % 8); - return !!(last_report_.keys[k / 8] & bit); - } - return false; -} - - -size_t Keyboard_::press(uint8_t k) { - // If the key is in the range of 'printable' keys - if (k <= HID_LAST_KEY) { - uint8_t bit = 1 << (uint8_t(k) % 8); - report_.keys[k / 8] |= bit; - return 1; - } - - // It's a modifier key - else if (k >= HID_KEYBOARD_FIRST_MODIFIER && k <= HID_KEYBOARD_LAST_MODIFIER) { - // Convert key into bitfield (0 - 7) - k = k - HID_KEYBOARD_FIRST_MODIFIER; - report_.modifiers |= (1 << k); - return 1; - } - - // No empty/pressed key was found - return 0; -} - -size_t Keyboard_::release(uint8_t k) { - // If we're releasing a printable key - if (k <= HID_LAST_KEY) { - uint8_t bit = 1 << (k % 8); - report_.keys[k / 8] &= ~bit; - return 1; - } - - // It's a modifier key - else if (k >= HID_KEYBOARD_FIRST_MODIFIER && k <= HID_KEYBOARD_LAST_MODIFIER) { - // Convert key into bitfield (0 - 7) - k = k - HID_KEYBOARD_FIRST_MODIFIER; - report_.modifiers &= ~(1 << k); - return 1; - } - - // No empty/pressed key was found - return 0; -} - -void Keyboard_::releaseAll() { - // Release all keys - memset(&report_.allkeys, 0x00, sizeof(report_.allkeys)); -} - -Keyboard_ Keyboard; diff --git a/src/MultiReport/Keyboard.h b/src/MultiReport/Keyboard.h deleted file mode 100644 index ef0881b8..00000000 --- a/src/MultiReport/Keyboard.h +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright (c) 2014-2015 NicoHood -Copyright (c) 2015-2018 Keyboard.io, Inc - -See the readme for credit to other people. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -// Include guard -#pragma once - -#include -#include "HID.h" -#include "HID-Settings.h" - -#include "HIDTables.h" -#include "HIDAliases.h" - -#define KEY_BITS (4 + HID_LAST_KEY - HID_KEYBOARD_A_AND_A + 1) -#define KEY_BYTES ((KEY_BITS + 7) / 8) - -typedef union { - // Modifiers + keymap - struct { - uint8_t modifiers; - uint8_t keys[KEY_BYTES ]; - }; - uint8_t allkeys[1 + KEY_BYTES]; -} HID_KeyboardReport_Data_t; - - -class Keyboard_ { - public: - Keyboard_(); - void begin(); - void end(); - - size_t press(uint8_t k); - size_t release(uint8_t k); - void releaseAll(); - int sendReport(); - - bool isKeyPressed(uint8_t k); - bool wasKeyPressed(uint8_t k); - bool isModifierActive(uint8_t k); - bool wasModifierActive(uint8_t k); - bool isAnyModifierActive(); - bool wasAnyModifierActive(); - - uint8_t getLEDs() { - return HID().getLEDs(); - }; - - private: - HID_KeyboardReport_Data_t report_; - HID_KeyboardReport_Data_t last_report_; - - int sendReportUnchecked(); -}; -extern Keyboard_ Keyboard;