mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-29 09:15:33 +01:00
8d5810a103
If available, use the system-generated unique ID for HID device paths instead of a transport/vid/pid/location tuple. The Mayflash Dolphinbar registers four HID devices (regardless of the number of connected Wiimotes) which had the same path with the previous path building method, causing a bit of confusion when detecting and connecting to Wiimotes. The unique IDs do not change if the computer is suspended and resumed, but do change if the HID device is unplugged/replugged.
1124 lines
28 KiB
C
1124 lines
28 KiB
C
/*******************************************************
|
|
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 General Public License v3, a BSD-Style license, or the
|
|
original HIDAPI license as outlined in the LICENSE.txt,
|
|
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
|
|
files located at the root of the source distribution.
|
|
These files may also be found in the public source
|
|
code repository located at:
|
|
http://github.com/signal11/hidapi .
|
|
********************************************************/
|
|
|
|
/* 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;
|
|
};
|
|
|
|
static hid_device *new_hid_device(void)
|
|
{
|
|
hid_device *dev = 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);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
static IOHIDManagerRef hid_mgr = 0x0;
|
|
|
|
|
|
#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_location_id(IOHIDDeviceRef device)
|
|
{
|
|
return get_int_property(device, CFSTR(kIOHIDLocationIDKey));
|
|
}
|
|
|
|
static int32_t get_unique_id(IOHIDDeviceRef device)
|
|
{
|
|
return get_int_property(device, CFSTR(kIOHIDUniqueIDKey));
|
|
}
|
|
|
|
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 = IOHIDDeviceGetProperty(device, prop);
|
|
|
|
buf[0] = 0;
|
|
|
|
if (str) {
|
|
CFIndex str_len = CFStringGetLength(str);
|
|
CFRange range;
|
|
CFIndex used_buf_len;
|
|
CFIndex chars_copied;
|
|
|
|
len --;
|
|
|
|
range.location = 0;
|
|
range.length = ((size_t)str_len > len)? len: (size_t)str_len;
|
|
chars_copied = CFStringGetBytes(str,
|
|
range,
|
|
kCFStringEncodingUTF32LE,
|
|
(char)'?',
|
|
FALSE,
|
|
(UInt8*)buf,
|
|
len * sizeof(wchar_t),
|
|
&used_buf_len);
|
|
|
|
if (chars_copied == len)
|
|
buf[len] = 0; /* len is decremented above */
|
|
else
|
|
buf[chars_copied] = 0;
|
|
|
|
return 0;
|
|
}
|
|
else
|
|
return -1;
|
|
|
|
}
|
|
|
|
static int get_string_property_utf8(IOHIDDeviceRef device, CFStringRef prop, char *buf, size_t len)
|
|
{
|
|
CFStringRef str;
|
|
if (!len)
|
|
return 0;
|
|
|
|
str = IOHIDDeviceGetProperty(device, prop);
|
|
|
|
buf[0] = 0;
|
|
|
|
if (str) {
|
|
len--;
|
|
|
|
CFIndex str_len = CFStringGetLength(str);
|
|
CFRange range;
|
|
range.location = 0;
|
|
range.length = str_len;
|
|
CFIndex used_buf_len;
|
|
CFIndex chars_copied;
|
|
chars_copied = CFStringGetBytes(str,
|
|
range,
|
|
kCFStringEncodingUTF8,
|
|
(char)'?',
|
|
FALSE,
|
|
(UInt8*)buf,
|
|
len,
|
|
&used_buf_len);
|
|
|
|
if (used_buf_len == len)
|
|
buf[len] = 0; /* len is decremented above */
|
|
else
|
|
buf[used_buf_len] = 0;
|
|
|
|
return 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 = 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];
|
|
int32_t location;
|
|
int32_t unique_id;
|
|
|
|
buf[0] = '\0';
|
|
|
|
res = get_string_property_utf8(
|
|
device, CFSTR(kIOHIDTransportKey),
|
|
transport, sizeof(transport));
|
|
|
|
if (!res)
|
|
return -1;
|
|
|
|
unique_id = get_unique_id(device);
|
|
if (unique_id != 0) {
|
|
res = snprintf(buf, len, "id_%x", unique_id);
|
|
} else {
|
|
location = get_location_id(device);
|
|
vid = get_vendor_id(device);
|
|
pid = get_product_id(device);
|
|
|
|
res = snprintf(buf, len, "%s_%04hx_%04hx_%x",
|
|
transport, vid, pid, location);
|
|
}
|
|
|
|
|
|
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(void) {
|
|
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 */
|
|
IOHIDManagerSetDeviceMatching(hid_mgr, NULL);
|
|
CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr);
|
|
|
|
/* Convert the list into a C array so we can iterate easily. */
|
|
num_devices = CFSetGetCount(device_set);
|
|
IOHIDDeviceRef *device_array = 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 || vendor_id == dev_vid) &&
|
|
(product_id == 0x0 || product_id == dev_pid)) {
|
|
struct hid_device_info *tmp;
|
|
size_t len;
|
|
|
|
/* VID/PID match. Create the record. */
|
|
tmp = malloc(sizeof(struct hid_device_info));
|
|
if (cur_dev) {
|
|
cur_dev->next = tmp;
|
|
}
|
|
else {
|
|
root = tmp;
|
|
}
|
|
cur_dev = tmp;
|
|
|
|
/* Get the Usage Page and Usage for this device. */
|
|
cur_dev->usage_page = get_int_property(dev, CFSTR(kIOHIDPrimaryUsagePageKey));
|
|
cur_dev->usage = get_int_property(dev, CFSTR(kIOHIDPrimaryUsageKey));
|
|
|
|
/* Fill out the record */
|
|
cur_dev->next = NULL;
|
|
len = make_path(dev, cbuf, sizeof(cbuf));
|
|
cur_dev->path = strdup(cbuf);
|
|
|
|
/* Serial Number */
|
|
get_serial_number(dev, buf, BUF_LEN);
|
|
cur_dev->serial_number = dup_wcs(buf);
|
|
|
|
/* Manufacturer and Product strings */
|
|
get_manufacturer_string(dev, buf, BUF_LEN);
|
|
cur_dev->manufacturer_string = dup_wcs(buf);
|
|
get_product_string(dev, buf, BUF_LEN);
|
|
cur_dev->product_string = dup_wcs(buf);
|
|
|
|
/* VID/PID */
|
|
cur_dev->vendor_id = dev_vid;
|
|
cur_dev->product_id = dev_pid;
|
|
|
|
/* Release Number */
|
|
cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey));
|
|
|
|
/* Interface Number (Unsupported on Mac)*/
|
|
cur_dev->interface_number = -1;
|
|
}
|
|
}
|
|
|
|
free(device_array);
|
|
CFRelease(device_set);
|
|
|
|
return root;
|
|
}
|
|
|
|
void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
|
|
{
|
|
/* This function is identical to the Linux version. Platform independent. */
|
|
struct hid_device_info *d = devs;
|
|
while (d) {
|
|
struct hid_device_info *next = d->next;
|
|
free(d->path);
|
|
free(d->serial_number);
|
|
free(d->manufacturer_string);
|
|
free(d->product_string);
|
|
free(d);
|
|
d = next;
|
|
}
|
|
}
|
|
|
|
hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
|
|
{
|
|
/* This function is identical to the Linux version. Platform independent. */
|
|
struct hid_device_info *devs, *cur_dev;
|
|
const char *path_to_open = NULL;
|
|
hid_device * handle = NULL;
|
|
|
|
devs = hid_enumerate(vendor_id, product_id);
|
|
cur_dev = devs;
|
|
while (cur_dev) {
|
|
if (cur_dev->vendor_id == vendor_id &&
|
|
cur_dev->product_id == product_id) {
|
|
if (serial_number) {
|
|
if (wcscmp(serial_number, cur_dev->serial_number) == 0) {
|
|
path_to_open = cur_dev->path;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
path_to_open = cur_dev->path;
|
|
break;
|
|
}
|
|
}
|
|
cur_dev = cur_dev->next;
|
|
}
|
|
|
|
if (path_to_open) {
|
|
/* Open the device */
|
|
handle = hid_open_path(path_to_open);
|
|
}
|
|
|
|
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 *d = context;
|
|
|
|
d->disconnected = 1;
|
|
CFRunLoopStop(d->run_loop);
|
|
}
|
|
|
|
/* 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 = context;
|
|
|
|
/* Make a new Input Report object */
|
|
rpt = calloc(1, sizeof(struct input_report));
|
|
rpt->data = 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 = context;
|
|
CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/
|
|
}
|
|
|
|
static void *read_thread(void *param)
|
|
{
|
|
hid_device *dev = param;
|
|
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. */
|
|
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(). */
|
|
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 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 = 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, kIOHIDOptionsTypeSeizeDevice);
|
|
if (ret == kIOReturnSuccess) {
|
|
char str[32];
|
|
|
|
free(device_array);
|
|
CFRetain(os_dev);
|
|
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 = 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);
|
|
|
|
/* 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 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 (data[0] == 0x0) {
|
|
/* Not using numbered Reports.
|
|
Don't send the report number. */
|
|
data_to_send = data+1;
|
|
length_to_send = length-1;
|
|
}
|
|
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,
|
|
data[0], /* Report ID*/
|
|
data_to_send, length_to_send);
|
|
|
|
if (res == kIOReturnSuccess) {
|
|
return length;
|
|
}
|
|
else
|
|
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 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;
|
|
|
|
res = IOHIDDeviceGetReport(dev->device_handle,
|
|
kIOHIDReportTypeFeature,
|
|
data[0], /* Report ID */
|
|
data, &len);
|
|
if (res == kIOReturnSuccess)
|
|
return len;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
|
|
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, kIOHIDOptionsTypeSeizeDevice);
|
|
}
|
|
|
|
/* 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);
|
|
CFRelease(dev->device_handle);
|
|
|
|
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_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
|