Last active
August 2, 2023 19:59
-
-
Save tschiemer/6a82cb62ee650e01434165858f87d9be to your computer and use it in GitHub Desktop.
Detecting a generic Joystick on MacOS and receiving its reports
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks! Note that the buffer supplied to
IOHIDDeviceRegisterInputReportCallback()
needs to be per-device and particularly not on the stack as shown.I.e. replace
with
and in
Handle_DeviceRemovalCallback()
drop the memory by adding