diff --git a/hidtest/test.c b/hidtest/test.c index 1b6819280..94bbf37a3 100644 --- a/hidtest/test.c +++ b/hidtest/test.c @@ -52,6 +52,23 @@ #endif // +const char *hid_bus_name(hid_bus_type bus_type) { + static const char *const HidBusTypeName[] = { + "Unknown", + "USB", + "Bluetooth", + "I2C", + "SPI", + }; + + if ((int)bus_type < 0) + bus_type = HID_API_BUS_UNKNOWN; + if ((int)bus_type >= (int)(sizeof(HidBusTypeName) / sizeof(HidBusTypeName[0]))) + bus_type = HID_API_BUS_UNKNOWN; + + return HidBusTypeName[bus_type]; +} + void print_device(struct hid_device_info *cur_dev) { printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number); printf("\n"); @@ -60,7 +77,7 @@ void print_device(struct hid_device_info *cur_dev) { printf(" Release: %hx\n", cur_dev->release_number); printf(" Interface: %d\n", cur_dev->interface_number); printf(" Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page); - printf(" Bus type: %d\n", cur_dev->bus_type); + printf(" Bus type: %d (%s)\n", cur_dev->bus_type, hid_bus_name(cur_dev->bus_type)); printf("\n"); } diff --git a/mac/hid.c b/mac/hid.c index 39b3c56c0..42e9c7bb0 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -302,7 +303,7 @@ static CFArrayRef get_array_property(IOHIDDeviceRef device, CFStringRef key) static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) { CFTypeRef ref; - int32_t value; + int32_t value = 0; ref = IOHIDDeviceGetProperty(device, key); if (ref) { @@ -314,6 +315,36 @@ static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) return 0; } +static bool try_get_int_property(IOHIDDeviceRef device, CFStringRef key, int32_t *out_val) +{ + bool result = false; + CFTypeRef ref; + + ref = IOHIDDeviceGetProperty(device, key); + if (ref) { + if (CFGetTypeID(ref) == CFNumberGetTypeID()) { + result = CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, out_val); + } + } + return result; +} + +static bool try_get_ioregistry_int_property(io_service_t service, CFStringRef property, int32_t *out_val) +{ + bool result = false; + CFTypeRef ref = IORegistryEntryCreateCFProperty(service, property, kCFAllocatorDefault, 0); + + if (ref) { + if (CFGetTypeID(ref) == CFNumberGetTypeID()) { + result = CFNumberGetValue(ref, kCFNumberSInt32Type, out_val); + } + + CFRelease(ref); + } + + return result; +} + static CFArrayRef get_usage_pairs(IOHIDDeviceRef device) { return get_array_property(device, CFSTR(kIOHIDDeviceUsagePairsKey)); @@ -466,6 +497,46 @@ static void process_pending_events(void) { } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); } +static int read_usb_interface_from_hid_service_parent(io_service_t hid_service) +{ + int32_t result = -1; + bool success = false; + io_registry_entry_t current = IO_OBJECT_NULL; + kern_return_t res; + int parent_number = 0; + + res = IORegistryEntryGetParentEntry(hid_service, kIOServicePlane, ¤t); + while (KERN_SUCCESS == res + /* Only search up to 3 parent entries. + * With the default driver - the parent-of-interest supposed to be the first one, + * but lets assume some custom drivers or so, with deeper tree. */ + && parent_number < 3) { + io_registry_entry_t parent = IO_OBJECT_NULL; + int32_t interface_number = -1; + parent_number++; + + success = try_get_ioregistry_int_property(current, CFSTR(kUSBInterfaceNumber), &interface_number); + if (success) { + result = interface_number; + break; + } + + res = IORegistryEntryGetParentEntry(current, kIOServicePlane, &parent); + if (parent) { + IOObjectRelease(current); + current = parent; + } + + } + + if (current) { + IOObjectRelease(current); + current = IO_OBJECT_NULL; + } + + return result; +} + static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, int32_t usage_page, int32_t usage) { unsigned short dev_vid; @@ -475,7 +546,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, CFTypeRef transport_prop; struct hid_device_info *cur_dev; - io_object_t iokit_dev; + io_service_t hid_service; kern_return_t res; uint64_t entry_id = 0; @@ -499,9 +570,9 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, /* Fill in the path (as a unique ID of the service entry) */ cur_dev->path = NULL; - iokit_dev = IOHIDDeviceGetService(dev); - if (iokit_dev != MACH_PORT_NULL) { - res = IORegistryEntryGetRegistryEntryID(iokit_dev, &entry_id); + hid_service = IOHIDDeviceGetService(dev); + if (hid_service != MACH_PORT_NULL) { + res = IORegistryEntryGetRegistryEntryID(hid_service, &entry_id); } else { res = KERN_INVALID_ARGUMENT; @@ -540,24 +611,32 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, /* Release Number */ cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); - /* Interface Number */ - /* We can only retrieve the interface number for USB HID devices. - * IOKit always seems to return 0 when querying a standard USB device - * for its interface. */ - int is_usb_hid = get_int_property(dev, CFSTR(kUSBInterfaceClass)) == kUSBHIDClass; - if (is_usb_hid) { - /* Get the interface number */ - cur_dev->interface_number = get_int_property(dev, CFSTR(kUSBInterfaceNumber)); - } else { - cur_dev->interface_number = -1; - } + /* Interface Number. + * We can only retrieve the interface number for USB HID devices. + * See below */ + cur_dev->interface_number = -1; /* Bus Type */ transport_prop = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDTransportKey)); if (transport_prop != NULL && CFGetTypeID(transport_prop) == CFStringGetTypeID()) { if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportUSBValue), 0) == kCFCompareEqualTo) { + int32_t interface_number = -1; cur_dev->bus_type = HID_API_BUS_USB; + + /* A IOHIDDeviceRef used to have this simple property, + * until macOS 13.3 - we will try to use it. */ + if (try_get_int_property(dev, CFSTR(kUSBInterfaceNumber), &interface_number)) { + cur_dev->interface_number = interface_number; + } else { + /* Otherwise fallback to io_service_t property. + * (of one of the parent services). */ + cur_dev->interface_number = read_usb_interface_from_hid_service_parent(hid_service); + + /* If the above doesn't work - + * no (known) fallback exists at this point. */ + } + /* Match "Bluetooth", "BluetoothLowEnergy" and "Bluetooth Low Energy" strings */ } else if (CFStringHasPrefix((CFStringRef)transport_prop, CFSTR(kIOHIDTransportBluetoothValue))) { cur_dev->bus_type = HID_API_BUS_BLUETOOTH;