| /******************************************************* | |
| 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" | |
| #include "SDL_hints.h" | |
| /* 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/hidapi.h" | |
| #define VALVE_USB_VID 0x28DE | |
| /* 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) | |
| { | |
| struct input_report *rpt; | |
| if (!dev) | |
| return; | |
| /* Delete any input reports still left over. */ | |
| 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; | |
| if (CFGetTypeID(prop) != CFStringGetTypeID()) | |
| return 0; | |
| str = (CFStringRef)IOHIDDeviceGetProperty(device, prop); | |
| buf[0] = 0; | |
| if (str && CFGetTypeID(str) == CFStringGetTypeID()) { | |
| CFIndex used_buf_len, chars_copied; | |
| CFRange range; | |
| CFIndex str_len = CFStringGetLength(str); | |
| len --; | |
| range.location = 0; | |
| range.length = (str_len > len)? len: str_len; | |
| 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; | |
| if (CFGetTypeID(prop) != CFStringGetTypeID()) | |
| return 0; | |
| str = (CFStringRef)IOHIDDeviceGetProperty(device, prop); | |
| buf[0] = 0; | |
| if (str && CFGetTypeID(str) == CFStringGetTypeID()) { | |
| CFIndex used_buf_len, chars_copied; | |
| CFRange range; | |
| CFIndex str_len = CFStringGetLength(str); | |
| len--; | |
| range.location = 0; | |
| range.length = (str_len > len)? len: str_len; | |
| 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; | |
| } | |
| } | |
| static CFDictionaryRef | |
| create_usage_match(const UInt32 page, const UInt32 usage, int *okay) | |
| { | |
| CFDictionaryRef retval = NULL; | |
| CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); | |
| CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); | |
| const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) }; | |
| const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef }; | |
| if (pageNumRef && usageNumRef) { | |
| retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
| } | |
| if (pageNumRef) { | |
| CFRelease(pageNumRef); | |
| } | |
| if (usageNumRef) { | |
| CFRelease(usageNumRef); | |
| } | |
| if (!retval) { | |
| *okay = 0; | |
| } | |
| return retval; | |
| } | |
| static CFDictionaryRef | |
| create_vendor_match(const UInt32 vendor, int *okay) | |
| { | |
| CFDictionaryRef retval = NULL; | |
| CFNumberRef vidNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vendor); | |
| const void *keys[1] = { (void *) CFSTR(kIOHIDVendorIDKey) }; | |
| const void *vals[1] = { (void *) vidNumRef }; | |
| if (vidNumRef) { | |
| retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
| CFRelease(vidNumRef); | |
| } | |
| if (!retval) { | |
| *okay = 0; | |
| } | |
| return retval; | |
| } | |
| /* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */ | |
| static int init_hid_manager(void) | |
| { | |
| int okay = 1; | |
| const void *vals[] = { | |
| (void *) create_usage_match(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay), | |
| (void *) create_usage_match(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay), | |
| (void *) create_usage_match(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay), | |
| (void *) create_vendor_match(VALVE_USB_VID, &okay), | |
| }; | |
| const size_t numElements = SDL_arraysize(vals); | |
| CFArrayRef matchingArray = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL; | |
| size_t i; | |
| for (i = 0; i < numElements; i++) { | |
| if (vals[i]) { | |
| CFRelease((CFTypeRef) vals[i]); | |
| } | |
| } | |
| /* Initialize all the HID Manager Objects */ | |
| hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); | |
| if (hid_mgr) { | |
| IOHIDManagerSetDeviceMatchingMultiple(hid_mgr, matchingArray); | |
| IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | |
| IOHIDManagerRegisterDeviceRemovalCallback(hid_mgr, hid_device_removal_callback, NULL); | |
| } | |
| if (matchingArray != NULL) { | |
| CFRelease(matchingArray); | |
| } | |
| return hid_mgr ? 0 : -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; | |
| CFSetRef device_set; | |
| IOHIDDeviceRef *device_array; | |
| int i; | |
| const char *hint = SDL_GetHint(SDL_HINT_HIDAPI_IGNORE_DEVICES); | |
| /* 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 */ | |
| 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; | |
| } | |
| 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; | |
| } | |
| #if 0 // Prefer direct HID support as that has extended functionality | |
| #if defined(SDL_JOYSTICK_MFI) | |
| // We want to prefer Game Controller support where available, | |
| // as Apple will likely be requiring that for supported devices. | |
| extern SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device); | |
| if (IOS_SupportedHIDDevice(dev)) { | |
| continue; | |
| } | |
| #endif | |
| #endif | |
| dev_vid = get_vendor_id(dev); | |
| dev_pid = get_product_id(dev); | |
| /* See if there are any devices we should skip in enumeration */ | |
| if (hint) { | |
| char vendor_match[16], product_match[16]; | |
| SDL_snprintf(vendor_match, sizeof(vendor_match), "0x%.4x/0x0000", dev_vid); | |
| SDL_snprintf(product_match, sizeof(product_match), "0x%.4x/0x%.4x", dev_vid, dev_pid); | |
| if (SDL_strcasestr(hint, vendor_match) || SDL_strcasestr(hint, product_match)) { | |
| continue; | |
| } | |
| } | |
| /* Check the VID/PID against the arguments */ | |
| if ((vendor_id == 0x0 || dev_vid == vendor_id) && | |
| (product_id == 0x0 || dev_pid == product_id)) { | |
| struct hid_device_info *tmp; | |
| /* 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; | |
| 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; | |
| CFRunLoopSourceContext ctx; | |
| SInt32 code; | |
| /* 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. */ | |
| 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(). */ | |
| 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; | |
| CFSetRef device_set; | |
| IOHIDDeviceRef *device_array; | |
| 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(); | |
| device_set = IOHIDManagerCopyDevices(hid_mgr); | |
| if (!device_set) | |
| return NULL; | |
| num_devices = CFSetGetCount(device_set); | |
| 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]; | |
| IOHIDDeviceRef os_dev = device_array[i]; | |
| 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]; | |
| struct hid_device_list_node *node; | |
| 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); | |
| SDL_assert(dev->max_input_report_len > 0); | |
| 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); | |
| 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 = 0; | |
| if (rpt != NULL) { | |
| 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; | |
| int skipped_report_id = 0, report_number; | |
| /* Return if the device has been unplugged. */ | |
| if (dev->disconnected) | |
| return -1; | |
| 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 |