| /******************************************************* |
| 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: |
| https://github.com/libusb/hidapi . |
| ********************************************************/ |
| #include "../../SDL_internal.h" |
| |
| #ifdef SDL_JOYSTICK_HIDAPI |
| |
| /* See Apple Technical Note TN2187 for details on IOHidManager. */ |
| |
| #include <IOKit/hid/IOHIDManager.h> |
| #include <IOKit/hid/IOHIDKeys.h> |
| #include <CoreFoundation/CoreFoundation.h> |
| #include <wchar.h> |
| #include <locale.h> |
| #include <pthread.h> |
| #include <sys/time.h> |
| #include <unistd.h> |
| |
| #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 struct 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) { |
| struct 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 *)calloc(1, 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. |
| struct 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); |
| |
| struct hid_device_list_node *node = (struct hid_device_list_node *)calloc(1, sizeof(struct 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 */ |