Search code examples
swiftmacoshidiokit

How to write to IOHIDDevice endpoint with IOKit


I'm writing a desktop app for Mac OS Catalina. GPL licensed for now. I might make it MIT licensed in the future, but that's secondary. It is written in Swift and supports some gamepads using user space IOKit. I got it working for Dualshock 4 and Xbox 360 controller using HID APIs. Now I'm implementing support for the Xbox One controller, but it needs to receive a byte sequence before it starts sending reports (0x05, 0x20). I saw this in several C/C++ kext projects but I'm struggling to figure out how to do this within my app in user space. I'm able to get an IOHIDDevice but I couldn't figure out how to get an endpoint from there. Can this even be done from a HID level or do I need to use a lower level API like USB or Bluetooth? I wanted to avoid using libusb just for this since I already got it working with other controllers.

Other suggestion I found was on this question: Gamepad and joystick support on Mac OS X in user space but no sample code for Mac was provided. And replacing a device descriptor sounds a bit overkill for me just to accomplish this. Also, it suggests libusb.

Some parts of my code:

let hidManager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))
let deviceCriteria:NSArray = [
    [
        kIOHIDDeviceUsagePageKey: kHIDPage_GenericDesktop,
        kIOHIDDeviceUsageKey: kHIDUsage_GD_GamePad
    ]
]
IOHIDManagerSetDeviceMatchingMultiple(hidManager, deviceCriteria)
IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue)
IOHIDManagerOpen(hidManager, IOOptionBits(kIOHIDOptionsTypeNone))
IOHIDManagerRegisterDeviceMatchingCallback(...)

func hidDeviceAddedCallback(_ result:IOReturn, sender:UnsafeMutableRawPointer, device:IOHIDDevice) {
    // from here I usually send hid reports using IOHIDDeviceSetReport(device, kIOHIDReportTypeOutput, etc...)
    // but now I need to write directly to and endpoint of an interface
}

Solution

  • You can grab a reference to the IOService object corresponding to an IOHIDDevice using IOHIDDeviceGetService(). You can then walk up the I/O registry provider chain to find the underlying IOUSBInterface/IOUSBHostInterface object. However, the generic HID driver will presumably have acquired exclusive access to the USB interface, so submitting a transfer on the pipe presumably won't be permitted without kicking off the HID driver first.