Skip to content
This repository was archived by the owner on Feb 26, 2020. It is now read-only.

Commit 59a0767

Browse files
mrpippysignal11
authored andcommitted
Mac: Use IOKit IOService:/ paths for devices
Change hid_enumerate() and hid_open_path() to use IOKit IOService:/ paths to uniquely identify devices, instead of creating a path (done by the make_path() function). The path passed to hid_open_path() now must be a valid path to an IOHIDDevice in the IOService plane, like: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/ AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"
1 parent 98629a1 commit 59a0767

File tree

1 file changed

+121
-83
lines changed

1 file changed

+121
-83
lines changed

mac/hid.c

Lines changed: 121 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424

2525
#include <IOKit/hid/IOHIDManager.h>
2626
#include <IOKit/hid/IOHIDKeys.h>
27+
#include <IOKit/IOKitLib.h>
2728
#include <CoreFoundation/CoreFoundation.h>
2829
#include <wchar.h>
2930
#include <locale.h>
3031
#include <pthread.h>
3132
#include <sys/time.h>
3233
#include <unistd.h>
34+
#include <dlfcn.h>
3335

3436
#include "hidapi.h"
3537

@@ -330,33 +332,61 @@ static wchar_t *dup_wcs(const wchar_t *s)
330332
return ret;
331333
}
332334

333-
334-
static int make_path(IOHIDDeviceRef device, char *buf, size_t len)
335+
/* hidapi_IOHIDDeviceGetService()
336+
*
337+
* Return the io_service_t corresponding to a given IOHIDDeviceRef, either by:
338+
* - on OS X 10.6 and above, calling IOHIDDeviceGetService()
339+
* - on OS X 10.5, extract it from the IOHIDDevice struct
340+
*/
341+
static io_service_t hidapi_IOHIDDeviceGetService(IOHIDDeviceRef device)
335342
{
336-
int res;
337-
unsigned short vid, pid;
338-
char transport[32];
339-
int32_t location;
340-
341-
buf[0] = '\0';
342-
343-
res = get_string_property_utf8(
344-
device, CFSTR(kIOHIDTransportKey),
345-
transport, sizeof(transport));
346-
347-
if (!res)
348-
return -1;
349-
350-
location = get_location_id(device);
351-
vid = get_vendor_id(device);
352-
pid = get_product_id(device);
353-
354-
res = snprintf(buf, len, "%s_%04hx_%04hx_%x",
355-
transport, vid, pid, location);
343+
static void *iokit_framework = NULL;
344+
static io_service_t (*dynamic_IOHIDDeviceGetService)(IOHIDDeviceRef device) = NULL;
345+
346+
/* Use dlopen()/dlsym() to get a pointer to IOHIDDeviceGetService() if it exists.
347+
* If any of these steps fail, dynamic_IOHIDDeviceGetService will be left NULL
348+
* and the fallback method will be used.
349+
*/
350+
if (iokit_framework == NULL) {
351+
iokit_framework = dlopen("/System/Library/IOKit.framework/IOKit", RTLD_LAZY);
352+
353+
if (iokit_framework != NULL)
354+
dynamic_IOHIDDeviceGetService = dlsym(iokit_framework, "IOHIDDeviceGetService");
355+
}
356356

357+
if (dynamic_IOHIDDeviceGetService != NULL) {
358+
/* Running on OS X 10.6 and above: IOHIDDeviceGetService() exists */
359+
return dynamic_IOHIDDeviceGetService(device);
360+
}
361+
else
362+
{
363+
/* Running on OS X 10.5: IOHIDDeviceGetService() doesn't exist.
364+
*
365+
* Be naughty and pull the service out of the IOHIDDevice.
366+
* IOHIDDevice is an opaque struct not exposed to applications, but its
367+
* layout is stable through all available versions of OS X.
368+
* Tested and working on OS X 10.5.8 i386, x86_64, and ppc.
369+
*/
370+
struct IOHIDDevice_internal {
371+
/* The first field of the IOHIDDevice struct is a
372+
* CFRuntimeBase (which is a private CF struct).
373+
*
374+
* a, b, and c are the 3 fields that make up a CFRuntimeBase.
375+
* See http://opensource.apple.com/source/CF/CF-476.18/CFRuntime.h
376+
*
377+
* The second field of the IOHIDDevice is the io_service_t we're looking for.
378+
*/
379+
uintptr_t a;
380+
uint8_t b[4];
381+
#if __LP64__
382+
uint32_t c;
383+
#endif
384+
io_service_t service;
385+
};
386+
struct IOHIDDevice_internal *tmp = (struct IOHIDDevice_internal *)device;
357387

358-
buf[len-1] = '\0';
359-
return res+1;
388+
return tmp->service;
389+
}
360390
}
361391

362392
/* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */
@@ -434,7 +464,6 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
434464
unsigned short dev_pid;
435465
#define BUF_LEN 256
436466
wchar_t buf[BUF_LEN];
437-
char cbuf[BUF_LEN];
438467

439468
IOHIDDeviceRef dev = device_array[i];
440469

@@ -448,7 +477,9 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
448477
if ((vendor_id == 0x0 || vendor_id == dev_vid) &&
449478
(product_id == 0x0 || product_id == dev_pid)) {
450479
struct hid_device_info *tmp;
451-
size_t len;
480+
io_object_t iokit_dev;
481+
kern_return_t res;
482+
io_string_t path;
452483

453484
/* VID/PID match. Create the record. */
454485
tmp = malloc(sizeof(struct hid_device_info));
@@ -466,8 +497,14 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
466497

467498
/* Fill out the record */
468499
cur_dev->next = NULL;
469-
len = make_path(dev, cbuf, sizeof(cbuf));
470-
cur_dev->path = strdup(cbuf);
500+
501+
/* Fill in the path (IOService plane) */
502+
iokit_dev = hidapi_IOHIDDeviceGetService(dev);
503+
res = IORegistryEntryGetPath(iokit_dev, kIOServicePlane, path);
504+
if (res == KERN_SUCCESS)
505+
cur_dev->path = strdup(path);
506+
else
507+
cur_dev->path = strdup("");
471508

472509
/* Serial Number */
473510
get_serial_number(dev, buf, BUF_LEN);
@@ -681,76 +718,77 @@ static void *read_thread(void *param)
681718
return NULL;
682719
}
683720

721+
/* hid_open_path()
722+
*
723+
* path must be a valid path to an IOHIDDevice in the IOService plane
724+
* Example: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"
725+
*/
684726
hid_device * HID_API_EXPORT hid_open_path(const char *path)
685727
{
686-
int i;
687728
hid_device *dev = NULL;
688-
CFIndex num_devices;
729+
io_registry_entry_t entry = MACH_PORT_NULL;
689730

690731
dev = new_hid_device();
691732

692733
/* Set up the HID Manager if it hasn't been done */
693734
if (hid_init() < 0)
694735
return NULL;
695736

696-
/* give the IOHIDManager a chance to update itself */
697-
process_pending_events();
737+
/* Get the IORegistry entry for the given path */
738+
entry = IORegistryEntryFromPath(kIOMasterPortDefault, path);
739+
if (entry == MACH_PORT_NULL) {
740+
/* Path wasn't valid (maybe device was removed?) */
741+
goto return_error;
742+
}
698743

699-
CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr);
744+
/* Create an IOHIDDevice for the entry */
745+
dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry);
746+
if (dev->device_handle == NULL) {
747+
/* Error creating the HID device */
748+
goto return_error;
749+
}
700750

701-
num_devices = CFSetGetCount(device_set);
702-
IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef));
703-
CFSetGetValues(device_set, (const void **) device_array);
704-
for (i = 0; i < num_devices; i++) {
705-
char cbuf[BUF_LEN];
706-
size_t len;
707-
IOHIDDeviceRef os_dev = device_array[i];
708-
709-
len = make_path(os_dev, cbuf, sizeof(cbuf));
710-
if (!strcmp(cbuf, path)) {
711-
/* Matched Paths. Open this Device. */
712-
IOReturn ret = IOHIDDeviceOpen(os_dev, kIOHIDOptionsTypeSeizeDevice);
713-
if (ret == kIOReturnSuccess) {
714-
char str[32];
715-
716-
free(device_array);
717-
CFRetain(os_dev);
718-
CFRelease(device_set);
719-
dev->device_handle = os_dev;
720-
721-
/* Create the buffers for receiving data */
722-
dev->max_input_report_len = (CFIndex) get_max_report_length(os_dev);
723-
dev->input_report_buf = calloc(dev->max_input_report_len, sizeof(uint8_t));
724-
725-
/* Create the Run Loop Mode for this device.
726-
printing the reference seems to work. */
727-
sprintf(str, "HIDAPI_%p", os_dev);
728-
dev->run_loop_mode =
729-
CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);
730-
731-
/* Attach the device to a Run Loop */
732-
IOHIDDeviceRegisterInputReportCallback(
733-
os_dev, dev->input_report_buf, dev->max_input_report_len,
734-
&hid_report_callback, dev);
735-
IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev);
736-
737-
/* Start the read thread */
738-
pthread_create(&dev->thread, NULL, read_thread, dev);
739-
740-
/* Wait here for the read thread to be initialized. */
741-
pthread_barrier_wait(&dev->barrier);
742-
743-
return dev;
744-
}
745-
else {
746-
goto return_error;
747-
}
748-
}
751+
/* Open the IOHIDDevice */
752+
IOReturn ret = IOHIDDeviceOpen(dev->device_handle, kIOHIDOptionsTypeSeizeDevice);
753+
if (ret == kIOReturnSuccess) {
754+
char str[32];
755+
756+
/* Create the buffers for receiving data */
757+
dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle);
758+
dev->input_report_buf = calloc(dev->max_input_report_len, sizeof(uint8_t));
759+
760+
/* Create the Run Loop Mode for this device.
761+
printing the reference seems to work. */
762+
sprintf(str, "HIDAPI_%p", dev->device_handle);
763+
dev->run_loop_mode =
764+
CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);
765+
766+
/* Attach the device to a Run Loop */
767+
IOHIDDeviceRegisterInputReportCallback(
768+
dev->device_handle, dev->input_report_buf, dev->max_input_report_len,
769+
&hid_report_callback, dev);
770+
IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev);
771+
772+
/* Start the read thread */
773+
pthread_create(&dev->thread, NULL, read_thread, dev);
774+
775+
/* Wait here for the read thread to be initialized. */
776+
pthread_barrier_wait(&dev->barrier);
777+
778+
IOObjectRelease(entry);
779+
return dev;
780+
}
781+
else {
782+
goto return_error;
749783
}
750784

751785
return_error:
752-
free(device_array);
753-
CFRelease(device_set);
786+
if (dev->device_handle != NULL)
787+
CFRelease(dev->device_handle);
788+
789+
if (entry != MACH_PORT_NULL)
790+
IOObjectRelease(entry);
791+
754792
free_hid_device(dev);
755793
return NULL;
756794
}

0 commit comments

Comments
 (0)