/******************************************************* HIDAPI - Multi-Platform library for communication with HID devices. Alan Ott Signal 11 Software 2010-07-03 Copyright 2010, All Rights Reserved. At the discretion of the user of this library, this software may be licensed under the terms of the GNU Public License v3, a BSD-Style license, or the original HIDAPI license as outlined in the LICENSE.txt, LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt files located at the root of the source distribution. These files may also be found in the public source code repository located at: http://github.com/signal11/hidapi . ********************************************************/ #include "../../SDL_internal.h" #ifdef SDL_JOYSTICK_HIDAPI /* See Apple Technical Note TN2187 for details on IOHidManager. */ #include #include #include #include #include #include #include #include #include "hidapi.h" /* Barrier implementation because Mac OSX doesn't have pthread_barrier. It also doesn't have clock_gettime(). So much for POSIX and SUSv2. This implementation came from Brent Priddy and was posted on StackOverflow. It is used with his permission. */ typedef int pthread_barrierattr_t; typedef struct pthread_barrier { pthread_mutex_t mutex; pthread_cond_t cond; int count; int trip_count; } pthread_barrier_t; static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) { if(count == 0) { errno = EINVAL; return -1; } if(pthread_mutex_init(&barrier->mutex, 0) < 0) { return -1; } if(pthread_cond_init(&barrier->cond, 0) < 0) { pthread_mutex_destroy(&barrier->mutex); return -1; } barrier->trip_count = count; barrier->count = 0; return 0; } static int pthread_barrier_destroy(pthread_barrier_t *barrier) { pthread_cond_destroy(&barrier->cond); pthread_mutex_destroy(&barrier->mutex); return 0; } static int pthread_barrier_wait(pthread_barrier_t *barrier) { pthread_mutex_lock(&barrier->mutex); ++(barrier->count); if(barrier->count >= barrier->trip_count) { barrier->count = 0; pthread_cond_broadcast(&barrier->cond); pthread_mutex_unlock(&barrier->mutex); return 1; } else { pthread_cond_wait(&barrier->cond, &(barrier->mutex)); pthread_mutex_unlock(&barrier->mutex); return 0; } } static int return_data(hid_device *dev, unsigned char *data, size_t length); /* Linked List of input reports received from the device. */ struct input_report { uint8_t *data; size_t len; struct input_report *next; }; struct hid_device_ { IOHIDDeviceRef device_handle; int blocking; int uses_numbered_reports; int disconnected; CFStringRef run_loop_mode; CFRunLoopRef run_loop; CFRunLoopSourceRef source; uint8_t *input_report_buf; CFIndex max_input_report_len; struct input_report *input_reports; pthread_t thread; pthread_mutex_t mutex; /* Protects input_reports */ pthread_cond_t condition; pthread_barrier_t barrier; /* Ensures correct startup sequence */ pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */ int shutdown_thread; }; struct hid_device_list_node { struct hid_device_ *dev; struct hid_device_list_node *next; }; static IOHIDManagerRef hid_mgr = 0x0; static hid_device_list_node *device_list = 0x0; static hid_device *new_hid_device(void) { hid_device *dev = (hid_device*)calloc(1, sizeof(hid_device)); dev->device_handle = NULL; dev->blocking = 1; dev->uses_numbered_reports = 0; dev->disconnected = 0; dev->run_loop_mode = NULL; dev->run_loop = NULL; dev->source = NULL; dev->input_report_buf = NULL; dev->input_reports = NULL; dev->shutdown_thread = 0; /* Thread objects */ pthread_mutex_init(&dev->mutex, NULL); pthread_cond_init(&dev->condition, NULL); pthread_barrier_init(&dev->barrier, NULL, 2); pthread_barrier_init(&dev->shutdown_barrier, NULL, 2); return dev; } static void free_hid_device(hid_device *dev) { if (!dev) return; /* Delete any input reports still left over. */ struct input_report *rpt = dev->input_reports; while (rpt) { struct input_report *next = rpt->next; free(rpt->data); free(rpt); rpt = next; } /* Free the string and the report buffer. The check for NULL is necessary here as CFRelease() doesn't handle NULL like free() and others do. */ if (dev->run_loop_mode) CFRelease(dev->run_loop_mode); if (dev->source) CFRelease(dev->source); free(dev->input_report_buf); if (device_list) { if (device_list->dev == dev) { device_list = device_list->next; } else { struct hid_device_list_node *node = device_list; while (node) { if (node->next && node->next->dev == dev) { hid_device_list_node *new_next = node->next->next; free(node->next); node->next = new_next; break; } node = node->next; } } } /* Clean up the thread objects */ pthread_barrier_destroy(&dev->shutdown_barrier); pthread_barrier_destroy(&dev->barrier); pthread_cond_destroy(&dev->condition); pthread_mutex_destroy(&dev->mutex); /* Free the structure itself. */ free(dev); } #if 0 static void register_error(hid_device *device, const char *op) { } #endif static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) { CFTypeRef ref; int32_t value; ref = IOHIDDeviceGetProperty(device, key); if (ref) { if (CFGetTypeID(ref) == CFNumberGetTypeID()) { CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &value); return value; } } return 0; } static unsigned short get_vendor_id(IOHIDDeviceRef device) { return get_int_property(device, CFSTR(kIOHIDVendorIDKey)); } static unsigned short get_product_id(IOHIDDeviceRef device) { return get_int_property(device, CFSTR(kIOHIDProductIDKey)); } static int32_t get_max_report_length(IOHIDDeviceRef device) { return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey)); } static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len) { CFStringRef str; if (!len) return 0; str = (CFStringRef)IOHIDDeviceGetProperty(device, prop); buf[0] = 0; if (str) { len --; CFIndex str_len = CFStringGetLength(str); CFRange range; range.location = 0; range.length = (str_len > len)? len: str_len; CFIndex used_buf_len; CFIndex chars_copied; chars_copied = CFStringGetBytes(str, range, kCFStringEncodingUTF32LE, (char)'?', FALSE, (UInt8*)buf, len, &used_buf_len); buf[chars_copied] = 0; return (int)chars_copied; } else return 0; } static int get_string_property_utf8(IOHIDDeviceRef device, CFStringRef prop, char *buf, size_t len) { CFStringRef str; if (!len) return 0; str = (CFStringRef)IOHIDDeviceGetProperty(device, prop); buf[0] = 0; if (str) { len--; CFIndex str_len = CFStringGetLength(str); CFRange range; range.location = 0; range.length = (str_len > len)? len: str_len; CFIndex used_buf_len; CFIndex chars_copied; chars_copied = CFStringGetBytes(str, range, kCFStringEncodingUTF8, (char)'?', FALSE, (UInt8*)buf, len, &used_buf_len); buf[chars_copied] = 0; return (int)used_buf_len; } else return 0; } static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len) { return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len); } static int get_manufacturer_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) { return get_string_property(device, CFSTR(kIOHIDManufacturerKey), buf, len); } static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) { return get_string_property(device, CFSTR(kIOHIDProductKey), buf, len); } /* Implementation of wcsdup() for Mac. */ static wchar_t *dup_wcs(const wchar_t *s) { size_t len = wcslen(s); wchar_t *ret = (wchar_t *)malloc((len+1)*sizeof(wchar_t)); wcscpy(ret, s); return ret; } static int make_path(IOHIDDeviceRef device, char *buf, size_t len) { int res; unsigned short vid, pid; char transport[32]; buf[0] = '\0'; res = get_string_property_utf8( device, CFSTR(kIOHIDTransportKey), transport, sizeof(transport)); if (!res) return -1; vid = get_vendor_id(device); pid = get_product_id(device); res = snprintf(buf, len, "%s_%04hx_%04hx_%p", transport, vid, pid, device); buf[len-1] = '\0'; return res+1; } /* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */ static int init_hid_manager(void) { /* Initialize all the HID Manager Objects */ hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); if (hid_mgr) { IOHIDManagerSetDeviceMatching(hid_mgr, NULL); IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); return 0; } return -1; } /* Initialize the IOHIDManager if necessary. This is the public function, and it is safe to call this function repeatedly. Return 0 for success and -1 for failure. */ int HID_API_EXPORT hid_init(void) { if (!hid_mgr) { return init_hid_manager(); } /* Already initialized. */ return 0; } int HID_API_EXPORT hid_exit(void) { if (hid_mgr) { /* Close the HID manager. */ IOHIDManagerClose(hid_mgr, kIOHIDOptionsTypeNone); CFRelease(hid_mgr); hid_mgr = NULL; } return 0; } static void process_pending_events() { SInt32 res; do { res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, FALSE); } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); } struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { struct hid_device_info *root = NULL; // return object struct hid_device_info *cur_dev = NULL; CFIndex num_devices; int i; setlocale(LC_ALL,""); /* Set up the HID Manager if it hasn't been done */ if (hid_init() < 0) return NULL; /* give the IOHIDManager a chance to update itself */ process_pending_events(); /* Get a list of the Devices */ CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); if (!device_set) return NULL; /* Convert the list into a C array so we can iterate easily. */ num_devices = CFSetGetCount(device_set); if (!num_devices) { CFRelease(device_set); return NULL; } IOHIDDeviceRef *device_array = (IOHIDDeviceRef*)calloc(num_devices, sizeof(IOHIDDeviceRef)); CFSetGetValues(device_set, (const void **) device_array); /* Iterate over each device, making an entry for it. */ for (i = 0; i < num_devices; i++) { unsigned short dev_vid; unsigned short dev_pid; #define BUF_LEN 256 wchar_t buf[BUF_LEN]; char cbuf[BUF_LEN]; IOHIDDeviceRef dev = device_array[i]; if (!dev) { continue; } dev_vid = get_vendor_id(dev); dev_pid = get_product_id(dev); /* Check the VID/PID against the arguments */ if ((vendor_id == 0x0 && product_id == 0x0) || (vendor_id == dev_vid && product_id == dev_pid)) { struct hid_device_info *tmp; size_t len; /* VID/PID match. Create the record. */ tmp = (struct hid_device_info *)malloc(sizeof(struct hid_device_info)); if (cur_dev) { cur_dev->next = tmp; } else { root = tmp; } cur_dev = tmp; // Get the Usage Page and Usage for this device. cur_dev->usage_page = get_int_property(dev, CFSTR(kIOHIDPrimaryUsagePageKey)); cur_dev->usage = get_int_property(dev, CFSTR(kIOHIDPrimaryUsageKey)); /* Fill out the record */ cur_dev->next = NULL; len = make_path(dev, cbuf, sizeof(cbuf)); cur_dev->path = strdup(cbuf); /* Serial Number */ get_serial_number(dev, buf, BUF_LEN); cur_dev->serial_number = dup_wcs(buf); /* Manufacturer and Product strings */ get_manufacturer_string(dev, buf, BUF_LEN); cur_dev->manufacturer_string = dup_wcs(buf); get_product_string(dev, buf, BUF_LEN); cur_dev->product_string = dup_wcs(buf); /* VID/PID */ cur_dev->vendor_id = dev_vid; cur_dev->product_id = dev_pid; /* Release Number */ cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); /* Interface Number (Unsupported on Mac)*/ cur_dev->interface_number = -1; } } free(device_array); CFRelease(device_set); return root; } void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) { /* This function is identical to the Linux version. Platform independent. */ struct hid_device_info *d = devs; while (d) { struct hid_device_info *next = d->next; free(d->path); free(d->serial_number); free(d->manufacturer_string); free(d->product_string); free(d); d = next; } } hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { /* This function is identical to the Linux version. Platform independent. */ struct hid_device_info *devs, *cur_dev; const char *path_to_open = NULL; hid_device * handle = NULL; devs = hid_enumerate(vendor_id, product_id); cur_dev = devs; while (cur_dev) { if (cur_dev->vendor_id == vendor_id && cur_dev->product_id == product_id) { if (serial_number) { if (wcscmp(serial_number, cur_dev->serial_number) == 0) { path_to_open = cur_dev->path; break; } } else { path_to_open = cur_dev->path; break; } } cur_dev = cur_dev->next; } if (path_to_open) { /* Open the device */ handle = hid_open_path(path_to_open, 0); } hid_free_enumeration(devs); return handle; } static void hid_device_removal_callback(void *context, IOReturn result, void *sender) { /* Stop the Run Loop for this device. */ hid_device *dev = (hid_device *)context; // The device removal callback is sometimes called even after being // unregistered, leading to a crash when trying to access fields in // the already freed hid_device. We keep a linked list of all created // hid_device's so that the one being removed can be checked against // the list to see if it really hasn't been closed yet and needs to // be dealt with here. hid_device_list_node *node = device_list; while (node) { if (node->dev == dev) { dev->disconnected = 1; CFRunLoopStop(dev->run_loop); break; } node = node->next; } } /* The Run Loop calls this function for each input report received. This function puts the data into a linked list to be picked up by hid_read(). */ static void hid_report_callback(void *context, IOReturn result, void *sender, IOHIDReportType report_type, uint32_t report_id, uint8_t *report, CFIndex report_length) { struct input_report *rpt; hid_device *dev = (hid_device *)context; /* Make a new Input Report object */ rpt = (struct input_report *)calloc(1, sizeof(struct input_report)); rpt->data = (uint8_t *)calloc(1, report_length); memcpy(rpt->data, report, report_length); rpt->len = report_length; rpt->next = NULL; /* Lock this section */ pthread_mutex_lock(&dev->mutex); /* Attach the new report object to the end of the list. */ if (dev->input_reports == NULL) { /* The list is empty. Put it at the root. */ dev->input_reports = rpt; } else { /* Find the end of the list and attach. */ struct input_report *cur = dev->input_reports; int num_queued = 0; while (cur->next != NULL) { cur = cur->next; num_queued++; } cur->next = rpt; /* Pop one off if we've reached 30 in the queue. This way we don't grow forever if the user never reads anything from the device. */ if (num_queued > 30) { return_data(dev, NULL, 0); } } /* Signal a waiting thread that there is data. */ pthread_cond_signal(&dev->condition); /* Unlock */ pthread_mutex_unlock(&dev->mutex); } /* This gets called when the read_thred's run loop gets signaled by hid_close(), and serves to stop the read_thread's run loop. */ static void perform_signal_callback(void *context) { hid_device *dev = (hid_device *)context; CFRunLoopStop(dev->run_loop); //TODO: CFRunLoopGetCurrent() } static void *read_thread(void *param) { hid_device *dev = (hid_device *)param; /* Move the device's run loop to this thread. */ IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode); /* Create the RunLoopSource which is used to signal the event loop to stop when hid_close() is called. */ CFRunLoopSourceContext ctx; memset(&ctx, 0, sizeof(ctx)); ctx.version = 0; ctx.info = dev; ctx.perform = &perform_signal_callback; dev->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx); CFRunLoopAddSource(CFRunLoopGetCurrent(), dev->source, dev->run_loop_mode); /* Store off the Run Loop so it can be stopped from hid_close() and on device disconnection. */ dev->run_loop = CFRunLoopGetCurrent(); /* Notify the main thread that the read thread is up and running. */ pthread_barrier_wait(&dev->barrier); /* Run the Event Loop. CFRunLoopRunInMode() will dispatch HID input reports into the hid_report_callback(). */ SInt32 code; while (!dev->shutdown_thread && !dev->disconnected) { code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE); /* Return if the device has been disconnected */ if (code == kCFRunLoopRunFinished) { dev->disconnected = 1; break; } /* Break if The Run Loop returns Finished or Stopped. */ if (code != kCFRunLoopRunTimedOut && code != kCFRunLoopRunHandledSource) { /* There was some kind of error. Setting shutdown seems to make sense, but there may be something else more appropriate */ dev->shutdown_thread = 1; break; } } /* Now that the read thread is stopping, Wake any threads which are waiting on data (in hid_read_timeout()). Do this under a mutex to make sure that a thread which is about to go to sleep waiting on the condition acutally will go to sleep before the condition is signaled. */ pthread_mutex_lock(&dev->mutex); pthread_cond_broadcast(&dev->condition); pthread_mutex_unlock(&dev->mutex); /* Wait here until hid_close() is called and makes it past the call to CFRunLoopWakeUp(). This thread still needs to be valid when that function is called on the other thread. */ pthread_barrier_wait(&dev->shutdown_barrier); return NULL; } hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive) { int i; hid_device *dev = NULL; CFIndex num_devices; dev = new_hid_device(); /* Set up the HID Manager if it hasn't been done */ if (hid_init() < 0) return NULL; /* give the IOHIDManager a chance to update itself */ process_pending_events(); CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); num_devices = CFSetGetCount(device_set); IOHIDDeviceRef *device_array = (IOHIDDeviceRef *)calloc(num_devices, sizeof(IOHIDDeviceRef)); CFSetGetValues(device_set, (const void **) device_array); for (i = 0; i < num_devices; i++) { char cbuf[BUF_LEN]; size_t len; IOHIDDeviceRef os_dev = device_array[i]; len = make_path(os_dev, cbuf, sizeof(cbuf)); if (!strcmp(cbuf, path)) { // Matched Paths. Open this Device. IOReturn ret = IOHIDDeviceOpen(os_dev, kIOHIDOptionsTypeNone); if (ret == kIOReturnSuccess) { char str[32]; free(device_array); CFRelease(device_set); dev->device_handle = os_dev; /* Create the buffers for receiving data */ dev->max_input_report_len = (CFIndex) get_max_report_length(os_dev); dev->input_report_buf = (uint8_t *)calloc(dev->max_input_report_len, sizeof(uint8_t)); /* Create the Run Loop Mode for this device. printing the reference seems to work. */ sprintf(str, "HIDAPI_%p", os_dev); dev->run_loop_mode = CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); /* Attach the device to a Run Loop */ IOHIDDeviceRegisterInputReportCallback( os_dev, dev->input_report_buf, dev->max_input_report_len, &hid_report_callback, dev); IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); hid_device_list_node *node = (hid_device_list_node *)calloc(1, sizeof(hid_device_list_node)); node->dev = dev; node->next = device_list; device_list = node; /* Start the read thread */ pthread_create(&dev->thread, NULL, read_thread, dev); /* Wait here for the read thread to be initialized. */ pthread_barrier_wait(&dev->barrier); return dev; } else { goto return_error; } } } return_error: free(device_array); CFRelease(device_set); free_hid_device(dev); return NULL; } static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length) { const char *pass_through_magic = "MAGIC0"; size_t pass_through_magic_length = strlen(pass_through_magic); unsigned char report_id = data[0]; const unsigned char *data_to_send; size_t length_to_send; IOReturn res; /* Return if the device has been disconnected. */ if (dev->disconnected) return -1; if (report_id == 0x0) { /* Not using numbered Reports. Don't send the report number. */ data_to_send = data+1; length_to_send = length-1; } else if (length > 6 && memcmp(data, pass_through_magic, pass_through_magic_length) == 0) { report_id = data[pass_through_magic_length]; data_to_send = data+pass_through_magic_length; length_to_send = length-pass_through_magic_length; } else { /* Using numbered Reports. Send the Report Number */ data_to_send = data; length_to_send = length; } if (!dev->disconnected) { res = IOHIDDeviceSetReport(dev->device_handle, type, report_id, /* Report ID*/ data_to_send, length_to_send); if (res == kIOReturnSuccess) { return (int)length; } else if (res == kIOReturnUnsupported) { /*printf("kIOReturnUnsupported\n");*/ return -1; } else { /*printf("0x%x\n", res);*/ return -1; } } return -1; } int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) { return set_report(dev, kIOHIDReportTypeOutput, data, length); } /* Helper function, so that this isn't duplicated in hid_read(). */ static int return_data(hid_device *dev, unsigned char *data, size_t length) { /* Copy the data out of the linked list item (rpt) into the return buffer (data), and delete the liked list item. */ struct input_report *rpt = dev->input_reports; size_t len = (length < rpt->len)? length: rpt->len; memcpy(data, rpt->data, len); dev->input_reports = rpt->next; free(rpt->data); free(rpt); return (int)len; } static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) { while (!dev->input_reports) { int res = pthread_cond_wait(cond, mutex); if (res != 0) return res; /* A res of 0 means we may have been signaled or it may be a spurious wakeup. Check to see that there's acutally data in the queue before returning, and if not, go back to sleep. See the pthread_cond_timedwait() man page for details. */ if (dev->shutdown_thread || dev->disconnected) return -1; } return 0; } static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) { while (!dev->input_reports) { int res = pthread_cond_timedwait(cond, mutex, abstime); if (res != 0) return res; /* A res of 0 means we may have been signaled or it may be a spurious wakeup. Check to see that there's acutally data in the queue before returning, and if not, go back to sleep. See the pthread_cond_timedwait() man page for details. */ if (dev->shutdown_thread || dev->disconnected) return -1; } return 0; } int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) { int bytes_read = -1; /* Lock the access to the report list. */ pthread_mutex_lock(&dev->mutex); /* There's an input report queued up. Return it. */ if (dev->input_reports) { /* Return the first one */ bytes_read = return_data(dev, data, length); goto ret; } /* Return if the device has been disconnected. */ if (dev->disconnected) { bytes_read = -1; goto ret; } if (dev->shutdown_thread) { /* This means the device has been closed (or there has been an error. An error code of -1 should be returned. */ bytes_read = -1; goto ret; } /* There is no data. Go to sleep and wait for data. */ if (milliseconds == -1) { /* Blocking */ int res; res = cond_wait(dev, &dev->condition, &dev->mutex); if (res == 0) bytes_read = return_data(dev, data, length); else { /* There was an error, or a device disconnection. */ bytes_read = -1; } } else if (milliseconds > 0) { /* Non-blocking, but called with timeout. */ int res; struct timespec ts; struct timeval tv; gettimeofday(&tv, NULL); TIMEVAL_TO_TIMESPEC(&tv, &ts); ts.tv_sec += milliseconds / 1000; ts.tv_nsec += (milliseconds % 1000) * 1000000; if (ts.tv_nsec >= 1000000000L) { ts.tv_sec++; ts.tv_nsec -= 1000000000L; } res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts); if (res == 0) bytes_read = return_data(dev, data, length); else if (res == ETIMEDOUT) bytes_read = 0; else bytes_read = -1; } else { /* Purely non-blocking */ bytes_read = 0; } ret: /* Unlock */ pthread_mutex_unlock(&dev->mutex); return bytes_read; } int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) { return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); } int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) { /* All Nonblocking operation is handled by the library. */ dev->blocking = !nonblock; return 0; } int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) { return set_report(dev, kIOHIDReportTypeFeature, data, length); } int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) { CFIndex len = length; IOReturn res; /* Return if the device has been unplugged. */ if (dev->disconnected) return -1; int skipped_report_id = 0; int report_number = data[0]; if (report_number == 0x0) { /* Offset the return buffer by 1, so that the report ID will remain in byte 0. */ data++; len--; skipped_report_id = 1; } res = IOHIDDeviceGetReport(dev->device_handle, kIOHIDReportTypeFeature, report_number, /* Report ID */ data, &len); if (res != kIOReturnSuccess) return -1; if (skipped_report_id) len++; return (int)len; } void HID_API_EXPORT hid_close(hid_device *dev) { if (!dev) return; /* Disconnect the report callback before close. */ if (!dev->disconnected) { IOHIDDeviceRegisterInputReportCallback( dev->device_handle, dev->input_report_buf, dev->max_input_report_len, NULL, dev); IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev); IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode); IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode); } /* Cause read_thread() to stop. */ dev->shutdown_thread = 1; /* Wake up the run thread's event loop so that the thread can exit. */ CFRunLoopSourceSignal(dev->source); CFRunLoopWakeUp(dev->run_loop); /* Notify the read thread that it can shut down now. */ pthread_barrier_wait(&dev->shutdown_barrier); /* Wait for read_thread() to end. */ pthread_join(dev->thread, NULL); /* Close the OS handle to the device, but only if it's not been unplugged. If it's been unplugged, then calling IOHIDDeviceClose() will crash. */ if (!dev->disconnected) { IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeNone); } /* Clear out the queue of received reports. */ pthread_mutex_lock(&dev->mutex); while (dev->input_reports) { return_data(dev, NULL, 0); } pthread_mutex_unlock(&dev->mutex); free_hid_device(dev); } int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { return get_manufacturer_string(dev->device_handle, string, maxlen); } int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { return get_product_string(dev->device_handle, string, maxlen); } int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { return get_serial_number(dev->device_handle, string, maxlen); } int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { // TODO: return 0; } HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { // TODO: return NULL; } #if 0 static int32_t get_location_id(IOHIDDeviceRef device) { return get_int_property(device, CFSTR(kIOHIDLocationIDKey)); } static int32_t get_usage(IOHIDDeviceRef device) { int32_t res; res = get_int_property(device, CFSTR(kIOHIDDeviceUsageKey)); if (!res) res = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); return res; } static int32_t get_usage_page(IOHIDDeviceRef device) { int32_t res; res = get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey)); if (!res) res = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); return res; } static int get_transport(IOHIDDeviceRef device, wchar_t *buf, size_t len) { return get_string_property(device, CFSTR(kIOHIDTransportKey), buf, len); } int main(void) { IOHIDManagerRef mgr; int i; mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); IOHIDManagerSetDeviceMatching(mgr, NULL); IOHIDManagerOpen(mgr, kIOHIDOptionsTypeNone); CFSetRef device_set = IOHIDManagerCopyDevices(mgr); CFIndex num_devices = CFSetGetCount(device_set); IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); CFSetGetValues(device_set, (const void **) device_array); setlocale(LC_ALL, ""); for (i = 0; i < num_devices; i++) { IOHIDDeviceRef dev = device_array[i]; printf("Device: %p\n", dev); printf(" %04hx %04hx\n", get_vendor_id(dev), get_product_id(dev)); wchar_t serial[256], buf[256]; char cbuf[256]; get_serial_number(dev, serial, 256); printf(" Serial: %ls\n", serial); printf(" Loc: %ld\n", get_location_id(dev)); get_transport(dev, buf, 256); printf(" Trans: %ls\n", buf); make_path(dev, cbuf, 256); printf(" Path: %s\n", cbuf); } return 0; } #endif #endif /* SDL_JOYSTICK_HIDAPI */