Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akemin-dayo/0e7291b85efc107427b5b12e6d6608bd to your computer and use it in GitHub Desktop.
Save akemin-dayo/0e7291b85efc107427b5b12e6d6608bd to your computer and use it in GitHub Desktop.
When `kIOHIDOptionsTypeSeizeDevice` is used in a DriverKit system extension (dext), the seized device fails to be hidden from `IOHIDManager` functions such as `IOHIDManagerSetDeviceMatchingMultiple`.

IMPORTANT

This is a modified, Markdown rich text version of my Radar bug report writeup to Apple (FB9785807).

I will not be posting or conveying any of Apple's responses, as I want to err on the side of caution and do not want to run the risk of accidentally posting what may be considered confidential information.

Thank you for your understanding.


Issue description

When kIOHIDOptionsTypeSeizeDevice is used in a DriverKit system extension (dext), the seized device fails to be hidden from IOHIDManager functions such as IOHIDManagerSetDeviceMatchingMultiple.

When kIOHIDOptionsTypeSeizeDevice is used in a kernel extension (kext) instead, this functionality behaves as expected.

This issue results in broken functionality on systems with the popular keyboard rebinding tool Karabiner-Elements installed, which moved its virtual HID keyboard component to DriverKit ever since version 13.0.0.

Any application that uses IOHIDManager for global hotkey functionality is affected to varying degrees, most notably Discord (Stable, "PTB" (Beta), and "Canary" (Alpha)), where global hotkey functionality is more or less completely broken when using a remapped keyboard.

Some macOS built-in functionality is also affected — specifically the Mouse Keys accessibility toggle hotkey ("Press the Option key five times to toggle Mouse Keys") albeit core functionality is not completely lost there, unlike Discord. The only effect that occurs with the Mouse Keys toggle hotkey is that it ends up taking just 3 presses to trigger (which due to the erroneous keystroke duplication, effectively counts as 6 presses).


I have written some test code available at https://github.com/akemin-dayo/IOKitHIDKeyboardTester which should be helpful in testing the issue and any possible fixes made to address it. (It is also how I diagnosed and determined the cause of this issue to begin with.)

When running the above code, the following behaviour is observed from a machine with the Karabiner-Elements (14.3.0) virtual HID keyboard DriverKit system extension (dext) installed and enabled, running macOS 12.0.1 21A559 (arm64e):

2021-11-28 08:15:01.525 IOKitHIDKeyboardTester[86479:8855288] Received an event from IOHIDDevice device: <IOHIDDevice 0x7fb098804c60 [0x7ff845d39d80]  'ClassName=AppleHIDTransportHIDDevice' Transport=SPI VendorID=1452 ProductID=641 Manufacturer=Apple Inc. Product=Apple Internal Keyboard / Trackpad PrimaryUsagePage=1 PrimaryUsage=6 ReportInterval=8000>
Received keyboard/keypad scancode: 226 with state: pressed (down)

2021-11-28 08:15:01.529 IOKitHIDKeyboardTester[86479:8855288] Received an event from IOHIDDevice device: <IOHIDDevice 0x7fb098804500 [0x7ff845d39d80]  'ClassName=org_pqrs_Karabiner_DriverKit_VirtualHIDKeyboard' VendorID=5824 ProductID=10203 Manufacturer=pqrs.org Product=Karabiner DriverKit VirtualHIDKeyboard 1.6.0 PrimaryUsagePage=1 PrimaryUsage=6 ReportInterval=8000>
Received keyboard/keypad scancode: 224 with state: pressed (down)

Notice how IOHIDManager is receiving events from the seized "Apple Internal Keyboard" keyboard device, despite the fact that it should not be visible at all when using kIOHIDOptionsTypeSeizeDevice.

Now compare this to the behaviour from a machine with the Karabiner-Elements (12.10.0) virtual HID keyboard kernel extension (kext) installed and enabled, running macOS 10.14.6 18G9323 (x86_64):

2021-11-28 10:19:24.852 IOKitHIDKeyboardTester[21468:12260257] Received an event from IOHIDDevice device: <IOHIDDevice 0x7fdfdc800780 [0x7fff8f983920]  'ClassName=org_pqrs_driver_Karabiner_VirtualHIDDevice_VirtualHIDKeyboard_v061000' (ref:2 xref:1)  VendorID=5824 ProductID=10203 Manufacturer=pqrs.org Product=Karabiner VirtualHIDKeyboard PrimaryUsagePage=1 PrimaryUsage=6 ReportInterval=8000>
Received keyboard/keypad scancode: 224 with state: pressed (down)

Notice how IOHIDManager does not receive any events from the seized Apple keyboard device when using a kernel extension. This is the expected behaviour from a device that has been opened with kIOHIDOptionsTypeSeizeDevice.


One last thing of note — closing and opening the seized keyboard HID device will temporarily fix any existing IOHIDManager instances, but new IOHIDManager instances will still be susceptible to the issue.

You can reproduce this behaviour by going to Karabiner-Elements Preferences → Devices and toggling the relevant keyboard HID device. IOKitHIDKeyboardTester will then behave correctly until the next time you relaunch it, and Discord will behave correctly until you open the global hotkey configuration menu in its settings.

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