If you’ve used a Mac in the last decade (or two?) you’ve noticed that the function keys are repurposed to do things like previous/next track, volume, brightness, and more.
Modern Macs deal with this by having a function key map for specific keyboards, so macOS knows that your Macbook internal keyboard has certain keys that map to certain functions. Prior to that they (sensibly) used USB HID usages to define the keys.
Is there a way to mimic this with our own USB device?
USB HID devices (keyboards, mice, game controllers, etc) describe their capabilities to the host using Usage Pages. These are defined in HID Usage Tables 1.12.
There are many, many possibilities available to use and it would have made sense to just use some of the extra keycodes already there, e.g. use F19 for launchpad. But why bother with that when you could make it more complicated and define your very own set of vendor-specific usages?
Thankfully there are a few resources to find out what usages the keys map to. Running
ioreg -l | grep FnFunctionUsageMap
(thanks to this page) shows the map used for an existing keyboard. The first value is the keyboard usage for F1, F2, etc. and the second is the alternate usage. FF01 is the vendor-specific page, 000C is the consumer devices page and is used for volume controls, play/pause, etc.
We can also reference the open source pieces of macOS, the most useful part being AppleHIDUsageTables.h. This shows all of the Apple-specific pages. The file is blank since 10.14. The relevant one for us is the keyboard page, 0xff01. Others are also worth looking into - what’s AppleVendorLisa?
The relevant usages for modern macOS are:
I already had a HID report for the consumer controls so I just added these onto that, here’s part of the HID descriptor as a C array. It defines a report of one byte with each bit corresponding to a specific usage:
0x05, 0x0c, // USAGE_PAGE (Consumer Devices) 0x09, 0x01, // USAGE (consumer control) 0xA1, 0x01, // COLLECTION (Application) 0x85, 0x01, // REPORT ID 1 0x15, 0x00, // Logical min 0x25, 0x01, // Logical max 0x75, 0x01, // Report size (1) 0x95, 0x08, // Report count (8) 0x09, 0xb5, // Usage: next track 0x09, 0xb6, // Usage: prev track 0x09, 0xcd, // Usage: play/pause 0x09, 0xe9, // Usage: vol up 0x09, 0xea, // Usage: vol down 0x06, 0x01, 0xff, // USAGE_PAGE (Vendor-defined, Apple Keyboard) 0x09, 0x21, // Usage: bright down 0x09, 0x20, // Usage: bright up 0x09, 0x04, // Usage: Launchpad 0x81, 0x02, // INPUT data, var, abs 0x0c // End collection
This works… on a Mac. If that’s all you care about then this is fine, but if you plug it into a Windows machine none of them work. Windows doesn’t know what to do with the vendor-specific piece, so it ignores the entire collection. I ended up using one report for the standard consumer controls and a separate report for the Mac-specific ones.