Skip to content

Unable to use HID keyboard in boot OS (macOS boot screen) #1136

Closed

Description

@bmosley

I started putting together a project with my Trinket M0 to use for switching boot volumes on macOS. My goal was to have it select a volume by keystrokes. However, it doesn't seem to be working.Working correctly in macOS once booted

Using the same code for HID keyboard:

  1. plug trinket m0 into usb on mac
  2. reboot system
import time

import board
import digitalio
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode

# The keyboard object!
time.sleep(1)  # Sleep for a bit to avoid a race condition on some systems
keyboard = Keyboard()
keyboard_layout = KeyboardLayoutUS(keyboard)

while True:
    keyboard.press(Keycode.LEFT_ALT)
    time.sleep(40)
    led.value = False
    keyboard.release_all()

Activity

  1. dhalbert commented on Aug 22, 2018

    @dhalbert
    Collaborator

    The HID keyboard currently presented by CircuitPython is not a boot device keyboard, with its own endpoint(s), which is what the Mac Open Firmware ("BIOS") and regular PC BIOSes expect. It's part of a composite HID device that includes mouse, multimedia keys, and a gamepad. This was to save endpoints for other purposes.

    We've had one other person who wants a boot-compatible keyboard and we're thinking about that - we have to count up the available endpoints and what we want for the future (e.g. USB MIDI). Also, in the long run, we are thinking about how to provide user-configurable HID devices.

  2. bmosley commented on Aug 22, 2018

    @bmosley
    Author

    Thanks. I completely understand and figured that this wasn’t currently supported but thought filing a bug was the best way to ask/suggest implementation of the feature. Is there any roadmap for future HID support? And MIDI sounds quite awesome too :)

  3. dhalbert commented on Aug 22, 2018

    @dhalbert
    Collaborator

    No specific timeline with dates, but your request is noted and it's a vote!

  4. bmosley commented on Aug 22, 2018

    @bmosley
    Author

    wonderful, thank you!

  5. ATMakersBill commented on Feb 9, 2020

    @ATMakersBill
    Collaborator

    This is marked as closed - was it done? If the fix was the same that I used in the XBox stuff which requires a custom build, is there any example of making this work?

  6. added this to the Long term milestone on Feb 9, 2020
  7. dhalbert commented on Feb 9, 2020

    @dhalbert
    Collaborator

    The fix requires a new descriptor and its own endpoint, at least. I'm not sure what else. Perhaps it could be added as a compile option for the descriptor generating Python script, and then later as a boot.py. We can reopen this as long term.

  8. ATMakersBill commented on Feb 9, 2020

    @ATMakersBill
    Collaborator

    I understand this not being in the core CP, but I do think it should remain open until there's some way to do it. I don't mind having to do a custom build (like we do for the XBox Adaptive Controller), but we have found lots of folks who really want to use devices through KVMs, and the composite devices don't work through them.
    Here's our current pressing issue: https://www.facebook.com/groups/ATMakers/permalink/815702142174560/

  9. ATMakersBill commented on Feb 9, 2020

    @ATMakersBill
    Collaborator

    BTW, thanks Dan

  10. dhalbert commented on Feb 9, 2020

    @dhalbert
    Collaborator

    Do you need a bootmode mouse HID capability too?

  11. ATMakersBill commented on Feb 9, 2020

    @ATMakersBill
    Collaborator

    Um... I don't think so? I think the reason this is needed is that the KVM listens in on the keyboard commands and relays them. That lets it watch for trigger keystrokes ('scroll-scroll-enter') to jump between devices. We don't kneed the triggers, but the stupid KVM doesn't know what to do with composite devices. It might be that we just need a non-composite device w/no other profiles. Or only a single device (no serial/mass storage?)

    I just know it is a known issue that "complicated" keyboards (even those with media keys) don't work.

  12. bitboy85 commented on Mar 5, 2021

    @bitboy85

    Here is another vote for it :)
    By now i've tried arduino, vusb, teensy, and pico pi and only teensy seems to have a full featured keyboard that works in BIOS and KVM devices.
    @ATMakersBill
    The teensy itself is also a composite device, having keyboard, mouse, gamepad, serial and still works fine with a KVM. So my best guess is, that it only depends on a correct implementation of the boot keyboard.
    @dhalbert
    As far as i know, a boot keyboard needs its own descriptor and endpoint and on top of it, it has two additional functions which are described in the usb docs.

  13. dhalbert commented on Mar 5, 2021

    @dhalbert
    Collaborator

    We are currently out of endpoints on most boards with 8 endpoints, having added a second CDC. If someone would like to contribute boot keyboard and mouse descriptors and compile flags, we'd be happy to include them. We would not turn them on normally, since they would remove having MIDI or the second CDC available by default.

    This is another indicator for user-configurable USB and USB HID descriptors. However, we are pretty much out of space on the smallest builds, and don't have room to add the code for this. It would have to be on something other than the non-Express SAMD21's.

  14. 58 remaining items

  15. retrospecced commented on Dec 30, 2021

    @retrospecced

    Thank you for the analysis on the descriptor, and your corrections to mine also work with my KVM, as you probably expected!

    When I set boot_device=1, the KVM doesn't work unless I supply your descriptor as a parameter. I don't think it's ignoring it.

    #usb_hid.enable((usb_hid.Device.KEYBOARD,), boot_device=1 )
    # above line does not work with Belkin SOHO KVM
    
    #usb_hid.enable((reference_keyboard,), boot_device=1 )
    # above line works fine with Belkin SOHO KVM
    boot.py (updated)

    """CircuitPython Essentials Storage logging boot.py file"""
    import board
    import digitalio
    import storage
    import usb_cdc
    import usb_midi
    import usb_hid
    from time import sleep
    
    switch = digitalio.DigitalInOut(board.D5)
    
    switch.direction = digitalio.Direction.INPUT
    switch.pull = digitalio.Pull.UP
    sleep(0.1)
    
    REFERENCE_BOOT_KEYBOARD_DESCRIPTOR=bytes((
    0x05, 0x01,        # Usage Page (Generic Desktop Ctrls)
    0x09, 0x06,        # Usage (Keyboard)
    0xA1, 0x01,        # Collection (Application)
    0x05, 0x07,        #   Usage Page (Kbrd/Keypad)
    0x19, 0xE0,        #   Usage Minimum (0xE0)
    0x29, 0xE7,        #   Usage Maximum (0xE7)
    0x15, 0x00,        #   Logical Minimum (0)
    0x25, 0x01,        #   Logical Maximum (1)
    0x75, 0x01,        #   Report Size (1)
    0x95, 0x08,        #   Report Count (8)
    0x81, 0x02,        #   Input (Data,Var,Abs,No Wrap,Linear,Pr
    0x95, 0x01,        #   Report Count (1)
    0x75, 0x08,        #   Report Size (8)
    0x81, 0x01,        #   Input (Const,Array,Abs,No Wrap,Linear
    0x95, 0x03,        #   Report Count (3)
    0x75, 0x01,        #   Report Size (1)
    0x05, 0x08,        #   Usage Page (LEDs)
    0x19, 0x01,        #   Usage Minimum (Num Lock)
    0x29, 0x05,        #   Usage Maximum (Kana)
    0x91, 0x02,        #   Output (Data,Var,Abs,No Wrap,Linear,P
    0x95, 0x01,        #   Report Count (1)
    0x75, 0x05,        #   Report Size (5)
    0x91, 0x01,        #   Output (Const,Array,Abs,No Wrap,Linea
    0x95, 0x06,        #   Report Count (6)
    0x75, 0x08,        #   Report Size (8)
    0x15, 0x00,        #   Logical Minimum (0)
    0x26, 0xFF, 0x00,  #   Logical Maximum (255)
    0x05, 0x07,        #   Usage Page (Kbrd/Keypad)
    0x19, 0x00,        #   Usage Minimum (0x00)
    0x2A, 0xFF, 0x00,  #   Usage Maximum (0xFF)
    0x81, 0x00,        #   Input (Data,Array,Abs,No Wrap,Linear,
    0xC0,              # End Collection
    ))
    
    reference_keyboard = usb_hid.Device(
        report_descriptor=REFERENCE_BOOT_KEYBOARD_DESCRIPTOR,
        usage=0x06,
        usage_page=0x01,
        report_ids=(0,),
        in_report_lengths=(8,),
        out_report_lengths=(1,),
        )
        
    maintenance_mode = not switch.value
    if not maintenance_mode:
        storage.disable_usb_drive()
        usb_cdc.disable()
        usb_midi.disable()
        usb_hid.enable((reference_keyboard,), boot_device=1 )
    else:
        usb_hid.enable((usb_hid.Device.KEYBOARD,))
    
    #usb_hid.enable((usb_hid.Device.KEYBOARD,), boot_device=1 )
    # above line does not work with Belkin SOHO KVM
    
    #usb_hid.enable((reference_keyboard,), boot_device=1 )
    # above line works fine with Belkin SOHO KVM
    
    """output from this boot.py
    Adafruit CircuitPython 7.1.0-rc.1 on 2021-12-25; Adafruit Feather M4 Express with samd51j19
    Board ID:feather_m4_express
    boot.py output:
    """

    Again, thanks for your time, let me know if I can test anything else for you, as I am super-thankful of all of your time and effort spent on making CircuitPython work for us (especially for first-time Python programmers like myself!)

  16. dhalbert commented on Dec 30, 2021

    @dhalbert
    Collaborator

    When I set boot_device=1, the KVM doesn't work unless I supply your descriptor as a parameter. I don't think it's ignoring it.

    You do need a descriptor. How it works is a bit more complicated than one might expect. If you set boot_device=1, then CircuitPython says it is capable of being a boot keyboard. But it doesn't do so unless the host asks for it to be a boot keyboard. If the host doesn't ask, then the supplied descriptor is used. If the host does ask, then the supplied descriptor is sent but ignored, and the host assumes the "standard" one.

  17. bitboy85 commented on Jan 2, 2022

    @bitboy85

    Did some further testing and i think this is a weird one. I didn't change the descriptor. I just added usb_hid.get_boot_device() to check if the host requests boot mode -> Yes it does.

    So i said it locks up bios or grub boot loader. I found this is only half true.
    After sending the first keypress it locks up. Nether from the cp-device nor from the original keyboard an input is accepted.
    EXCEPT: pressing and releasing the left CTRL-key does unlock it. The normal keyboard starts working again and even better, the cp-device now acts as a keyboard perfectly fine.

    I do not use CTRL key anywhere in my script and i don't have any issues without cp-device so i don't think the error comes from the built-in keyboard.

    The behaviour is like a permanently pressed ctrl button. CTRL left button. But only one time after pluggin it in.
    Pressing ALT + DEL while in bios reboots the pc.

    But i can't find a part in my code where i send ctrl without releasing it.

    So its fixable with kbd.release(Keycode.CONTROL) at the beginning of my script. But it doesn't explain why it is pressed in the first place. Even more confusing: https://github.com/adafruit/Adafruit_CircuitPython_HID/blob/main/adafruit_hid/keyboard.py does a release_all in the init part. Maybe something is going wrong there. I'll take a closer look tomorrow.

  18. retrospecced commented on Jan 2, 2022

    @retrospecced

    I'm using a KVM, but what you describe is the same behavior I was seeing before I started using the custom descriptor. https://w3c.github.io/uievents/tools/key-event-viewer.html would report left-control was always pressed before my code ran. Also, the KVM wouldn't forward any shift key presses to the host. So an "A" was always showing as control-a (lowercase), and so on.

  19. justinesmithies commented on Feb 27, 2022

    @justinesmithies

    Thank you for the analysis on the descriptor, and your corrections to mine also work with my KVM, as you probably expected!

    When I set boot_device=1, the KVM doesn't work unless I supply your descriptor as a parameter. I don't think it's ignoring it.

    #usb_hid.enable((usb_hid.Device.KEYBOARD,), boot_device=1 )
    # above line does not work with Belkin SOHO KVM
    
    #usb_hid.enable((reference_keyboard,), boot_device=1 )
    # above line works fine with Belkin SOHO KVM

    boot.py (updated)

    Again, thanks for your time, let me know if I can test anything else for you, as I am super-thankful of all of your time and effort spent on making CircuitPython work for us (especially for first-time Python programmers like myself!)

    Just want to thank @retrospecced as using his boot.py resolved my issues whereby I couldn't access my ThinkPads BIOS or enter the grub menu on Arch Linux.
    Now I just need to see if it's possible to change my Pico manufacturer info as lsusb shows Adafruit Pico and I'd like to change it to something else.

  20. bitboy85 commented on Feb 28, 2022

    @bitboy85

    @retrospecced thx for your effort.
    i tried your descriptor (and boot.py) but for some reason the device gets an exclamation mark in the device manager. So i was unable to test with the mentioned startech kvm switch.

  21. dhalbert commented on Feb 28, 2022

    @dhalbert
    Collaborator

    @retrospecced's original descriptor has some errors. Did you try the left-hand one in the diff above.

  22. bitboy85 commented on Mar 9, 2022

    @bitboy85

    Ah thx. I've thought the provided source does already contain the fix.

    Meanwhile i found another descriptor and can confirm working with a startech kvm switch 👍
    Other USB functions like storage, midi, and cdc needs to be turned off.
    I'm using Circuitpython 7.2.0

    BOOT_KEYBOARD_DESCRIPTOR=bytes((
        0x05, 0x01,        # Usage Page (Generic Desktop Ctrls)
        0x09, 0x06,        # Usage (Keyboard)
        0xA1, 0x01,        # Collection (Application)
        0x75, 0x01,        # Report Size (1)
        0x95, 0x08,        # Report Count (8)    
        0x05, 0x07,        # Usage Page (Kbrd/Keypad)
        0x19, 0xE0,        # Usage Minimum (0xE0, 224)
        0x29, 0xE7,        # Usage Maximum (0xE7, 231)
        0x15, 0x00,        # Logical Minimum (0)
        0x25, 0x01,        # Logical Maximum (1)
        0x81, 0x02,        # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
        0x95, 0x01,        # Report Count (1)
        0x75, 0x08,        # Report Size (8)
        0x81, 0x03,        # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
        0x95, 0x05,        # Report Count (5)
        0x75, 0x01,        # Report Size (1)
        0x05, 0x08,        # Usage Page (LEDs)
        0x19, 0x01,        # Usage Minimum (Num Lock)
        0x29, 0x05,        # Usage Maximum (Kana)
        0x91, 0x02,        # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
        0x95, 0x01,        # Report Count (1)
        0x75, 0x03,        # Report Size (3)
        0x91, 0x03,        # Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
        0x95, 0x06,        # Report Count (6)
        0x75, 0x08,        # Report Size (8)
        0x15, 0x00,        # Logical Minimum (0)
        0x25, 0x68,        # Logical Maximum (104)
        0x05, 0x07,        # Usage Page (Kbrd/Keypad)
        0x19, 0x00,        # Usage Minimum (0)
        0x29, 0x68,        # Usage Maximum (104)
        0x81, 0x00,        # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
        0xC0,
    ))
    
  23. RefactorFactory commented on Jul 8, 2022

    @RefactorFactory

    I have a theory of the various keyboard descriptors discussed and the problem where the left Ctrl key seems to be pressed:

    If the device sends reports prefixed with a 1-byte report ID of 0x01, the host is not expecting the prefixed report ID byte, and it interprets it as the modifiers field, and a modifiers field of 0x01 corresponds to the left Ctrl key. By calling usb_hid.enable((reference_keyboard,)...) with reference_keyboard having a report ID of 0, that causes CircuitPython to pass a report ID of 0 to TinyUSB which prevents it from prefixing reports with a report ID, solving the issue. The implication is that perhaps the contents of the keyboard descriptor doesn't matter as much as using a report ID of 0. Conclusion: for boot keyboards, always use a report ID of 0 for simple BIOSes. Maybe something to consider for CircuitPython's defaults, so that users don't need to specify a custom keyboard descriptor.

    I may have found a way to keep CDC and possibly other functions enabled while also using a boot keyboard:

    Keep the other functions enabled, but make the boot keyboard use an endpoint address of EP 1 IN. By doing this with plain Arduino (no CircuitPython), I was able to use a boot keyboard and CDC with my old PC.

  24. zan73 commented on May 27, 2025

    @zan73

    Having tried a few variants of the boot descriptor from above comments and also just
    usb_hid.enable((Device.KEYBOARD,), boot_device=1)
    as documented here, I am still not able to get a working boot mode keyboard implementation to work with MacOS cold-boot (FileVault) login.

    Can anyone confirm that they have boot-mode keyboard working for MacOS? (specifically a cold-boot, FileVault login).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions