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;