|  | /******************************************************* | 
|  | 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 <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; | 
|  | } | 
|  |  | 
|  | static void hid_device_removal_callback(void *context, IOReturn result, | 
|  | void *sender, IOHIDDeviceRef hid_ref) | 
|  | { | 
|  | // 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->device_handle == hid_ref) { | 
|  | node->dev->disconnected = 1; | 
|  | CFRunLoopStop(node->dev->run_loop); | 
|  | break; | 
|  | } | 
|  |  | 
|  | node = node->next; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* 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); | 
|  | IOHIDManagerRegisterDeviceRemovalCallback(hid_mgr, hid_device_removal_callback, NULL); | 
|  | 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; | 
|  |  | 
|  | /* 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; | 
|  | } | 
|  |  | 
|  | /* 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); | 
|  |  | 
|  | 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); | 
|  | 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); | 
|  |  | 
|  | 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 */ |