Skip to content

Instantly share code, notes, and snippets.

@tschiemer
Last active August 2, 2023 19:59
Show Gist options
  • Save tschiemer/6a82cb62ee650e01434165858f87d9be to your computer and use it in GitHub Desktop.
Save tschiemer/6a82cb62ee650e01434165858f87d9be to your computer and use it in GitHub Desktop.
Detecting a generic Joystick on MacOS and receiving its reports
//with thanks to https://www.programmersought.com/article/3482617526/
// also see
//https://nachtimwald.com/2020/12/06/macos-usb-enumeration-in-c/
//https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/USBBook/USBDeviceInterfaces/USBDevInterfaces.html
// actually, it'd be much quicker just to use https://github.com/libusb/hidapi
#import <Foundation/Foundation.h>
#include <IOKit/hid/IOHIDManager.h>
static IOHIDManagerRef hidManagerRef;
static void MyInputCallback(void* context, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t *report, CFIndex reportLength) {
static uint8_t previous[64];
// only process if changed
if (memcmp(previous, report, reportLength) != 0){
// note: unless we know how to interprete the data, this is pretty meaningless.
// and for this we need access to the report descriptor, also see
// https://github.com/libusb/hidapi (the descriptor is passed to get_usage() in the enumerator)
// https://eleccelerator.com/usbdescreqparser/
// get the IORegistryExplorer and you'll see that the descriptor is present in the ioreg, thus it might be smarter to
// use ioreg services (see reference on top for Apple example)
memcpy(previous, report, reportLength);
printf("type = %d, %d =", type, reportLength);
for (int i = 0; i < reportLength; i++){
printf(" %02x", report[i]);
}
printf("\n");
}
}
static void MyInputValueCallback(void * context, IOReturn result, void * sender, IOHIDValueRef value)
{
IOHIDElementRef element = IOHIDValueGetElement(value);
uint32_t usage = IOHIDElementGetUsage(element);
printf("value changed: %02x %d\n", usage, IOHIDValueGetIntegerValue(value));
}
static void MyMultipltInputValueCallback(void * context,IOReturn result, void * sender, CFDictionaryRef multiple)
{
NSLog(@"%s", multiple);
}
static void Handle_DeviceMatchingCallback(void *inContext,IOReturn inResult,void *inSender,IOHIDDeviceRef inIOHIDDeviceRef) {
NSLog(@"insert: %p", (void *)inIOHIDDeviceRef);
IOOptionBits options = 0;
IOReturn ret = IOHIDDeviceOpen(inIOHIDDeviceRef,options);
if (ret == kIOReturnSuccess) {
NSLog(@"open success");
uint8_t buf[64];
// raw report
IOHIDDeviceRegisterInputReportCallback(inIOHIDDeviceRef, buf, sizeof(buf), MyInputCallback, NULL);
IOHIDDeviceRegisterInputValueCallback(inIOHIDDeviceRef, MyInputValueCallback, NULL);
// CFDictionaryRef multiple = CFDictionaryCreate(<#CFAllocatorRef allocator#>, <#const void * * keys#>, <#const void * * values#>, <#CFIndex numValues#>, <#const CFDictionaryKeyCallBacks * keyCallBacks#>, <#const CFDictionaryValueCallBacks * valueCallBacks#>)
// CFTimeInterval timeout = 1.0;
// IOHIDDeviceSetValueMultipleWithCallback(inIOHIDDeviceRef, multiple, timeout, MyMultipltInputValueCallback, NULL)
}
}
static void Handle_DeviceRemovalCallback(void *inContext,IOReturn inResult,void *inSender,IOHIDDeviceRef inIOHIDDeviceRef) {
NSLog(@" pull out: %p device", (void *) inIOHIDDeviceRef);
}
int main(int argc, char * argv[]){
int usbVendor = 0x046d;
int usagePage = 0x01; // general usage page ...
int usage = 0x04; // joystick
// Create a Matching Dictionary // IOServiceMatching(kIOHIDDeviceKey);//
CFMutableDictionaryRef matchingDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
// CFDictionarySetValue(matchingDict, CFSTR(kIOHIDVendorIDKey),
// CFNumberCreate(kCFAllocatorDefault,
// kCFNumberSInt32Type, &usbVendor));
// Add key for device usage page - 0x01 for "Generic Desktop"
CFDictionarySetValue( matchingDict, CFSTR( kIOHIDPrimaryUsagePageKey ), CFNumberCreate(kCFAllocatorDefault,
kCFNumberSInt32Type, &usagePage) );
// // Add key for device usage - 0x04 for "Joystick"
CFDictionarySetValue( matchingDict, CFSTR( kIOHIDPrimaryUsageKey ), CFNumberCreate(kCFAllocatorDefault,
kCFNumberSInt32Type, &usage) );
// not needed in this variation
// mach_port_t masterPort;
// CFRunLoopSourceRef runLoopSource;
// kern_return_t kr;
//
//
// //Create a master port for communication with the I/O Kit
// kr = IOMasterPort(MACH_PORT_NULL, &masterPort);
// if (kr || !masterPort)
// {
// printf("ERR: Couldn’t create a master I/O Kit port(%08x)\n", kr);
// return -1;
// }
//
// //To set up asynchronous notifications, create a notification port and
// //add its run loop event source to the program’s run loop
// gNotifyPort = IONotificationPortCreate(masterPort);
// runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort);
// CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource,
// kCFRunLoopDefaultMode);
hidManagerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
IOHIDManagerRegisterDeviceMatchingCallback(hidManagerRef, Handle_DeviceMatchingCallback, NULL);
IOHIDManagerRegisterDeviceRemovalCallback(hidManagerRef, Handle_DeviceRemovalCallback, NULL);
// Register the Matching Dictionary to the HID Manager
IOHIDManagerSetDeviceMatching(hidManagerRef, matchingDict);
IOHIDManagerScheduleWithRunLoop(hidManagerRef, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
// not needed
// IOReturn ret = IOHIDManagerOpen(hidManagerRef, kIOHIDOptionsTypeNone);
// if (ret == kIOReturnSuccess) {
// NSLog(@"IOHIDManager opened successfully");
// }
CFRunLoopRun();
IOHIDManagerClose(hidManagerRef, 0);
return 0;
}
@zuckschwerdt
Copy link

Thanks! Note that the buffer supplied to IOHIDDeviceRegisterInputReportCallback() needs to be per-device and particularly not on the stack as shown.
I.e. replace

        uint8_t buf[64];

        // raw report
        IOHIDDeviceRegisterInputReportCallback(inIOHIDDeviceRef, buf, sizeof(buf), MyInputCallback, NULL);

with

        uint8_t *buf = calloc(64, sizeof(uint8_t); // alloc buffer memory

        // raw report
        IOHIDDeviceRegisterInputReportCallback(inIOHIDDeviceRef, buf, sizeof(buf), MyInputCallback, (void *)buf); // pass buf as user context

and in Handle_DeviceRemovalCallback() drop the memory by adding

        free(inContext); // free user context, i.e. the report buffer

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