diff options
Diffstat (limited to 'contrib/SDL-3.2.8/src/hidapi/mac')
| -rw-r--r-- | contrib/SDL-3.2.8/src/hidapi/mac/CMakeLists.txt | 48 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/hidapi/mac/Makefile-manual | 27 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/hidapi/mac/Makefile.am | 9 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/hidapi/mac/hid.c | 1594 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/hidapi/mac/hidapi_darwin.h | 98 |
5 files changed, 1776 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/hidapi/mac/CMakeLists.txt b/contrib/SDL-3.2.8/src/hidapi/mac/CMakeLists.txt new file mode 100644 index 0000000..0a1c1d9 --- /dev/null +++ b/contrib/SDL-3.2.8/src/hidapi/mac/CMakeLists.txt | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | cmake_minimum_required(VERSION 3.4.3...3.25 FATAL_ERROR) | ||
| 2 | |||
| 3 | list(APPEND HIDAPI_PUBLIC_HEADERS "hidapi_darwin.h") | ||
| 4 | |||
| 5 | add_library(hidapi_darwin | ||
| 6 | ${HIDAPI_PUBLIC_HEADERS} | ||
| 7 | hid.c | ||
| 8 | ) | ||
| 9 | |||
| 10 | find_package(Threads REQUIRED) | ||
| 11 | |||
| 12 | target_link_libraries(hidapi_darwin | ||
| 13 | PUBLIC hidapi_include | ||
| 14 | PRIVATE Threads::Threads | ||
| 15 | PRIVATE "-framework IOKit" "-framework CoreFoundation" | ||
| 16 | ) | ||
| 17 | |||
| 18 | set_target_properties(hidapi_darwin | ||
| 19 | PROPERTIES | ||
| 20 | EXPORT_NAME "darwin" | ||
| 21 | OUTPUT_NAME "hidapi" | ||
| 22 | VERSION ${PROJECT_VERSION} | ||
| 23 | SOVERSION ${PROJECT_VERSION_MAJOR} | ||
| 24 | MACHO_COMPATIBILITY_VERSION ${PROJECT_VERSION_MAJOR} | ||
| 25 | FRAMEWORK_VERSION ${PROJECT_VERSION_MAJOR} | ||
| 26 | PUBLIC_HEADER "${HIDAPI_PUBLIC_HEADERS}" | ||
| 27 | ) | ||
| 28 | |||
| 29 | # compatibility with find_package() | ||
| 30 | add_library(hidapi::darwin ALIAS hidapi_darwin) | ||
| 31 | # compatibility with raw library link | ||
| 32 | add_library(hidapi ALIAS hidapi_darwin) | ||
| 33 | |||
| 34 | set(PUBLIC_HEADER_DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") | ||
| 35 | if(NOT CMAKE_FRAMEWORK) | ||
| 36 | set(PUBLIC_HEADER_DESTINATION "${PUBLIC_HEADER_DESTINATION}/hidapi") | ||
| 37 | endif() | ||
| 38 | |||
| 39 | if(HIDAPI_INSTALL_TARGETS) | ||
| 40 | install(TARGETS hidapi_darwin EXPORT hidapi | ||
| 41 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" | ||
| 42 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" | ||
| 43 | FRAMEWORK DESTINATION "${CMAKE_INSTALL_LIBDIR}" | ||
| 44 | PUBLIC_HEADER DESTINATION "${PUBLIC_HEADER_DESTINATION}" | ||
| 45 | ) | ||
| 46 | endif() | ||
| 47 | |||
| 48 | hidapi_configure_pc("${PROJECT_ROOT}/pc/hidapi.pc.in") | ||
diff --git a/contrib/SDL-3.2.8/src/hidapi/mac/Makefile-manual b/contrib/SDL-3.2.8/src/hidapi/mac/Makefile-manual new file mode 100644 index 0000000..667c863 --- /dev/null +++ b/contrib/SDL-3.2.8/src/hidapi/mac/Makefile-manual | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | ########################################### | ||
| 2 | # Simple Makefile for HIDAPI test program | ||
| 3 | # | ||
| 4 | # Alan Ott | ||
| 5 | # Signal 11 Software | ||
| 6 | # 2010-07-03 | ||
| 7 | ########################################### | ||
| 8 | |||
| 9 | all: hidtest | ||
| 10 | |||
| 11 | CC=gcc | ||
| 12 | COBJS=hid.o ../hidtest/test.o | ||
| 13 | OBJS=$(COBJS) | ||
| 14 | CFLAGS+=-I../hidapi -I. -Wall -g -c | ||
| 15 | LIBS=-framework IOKit -framework CoreFoundation | ||
| 16 | |||
| 17 | |||
| 18 | hidtest: $(OBJS) | ||
| 19 | $(CC) -Wall -g $^ $(LIBS) -o hidtest | ||
| 20 | |||
| 21 | $(COBJS): %.o: %.c | ||
| 22 | $(CC) $(CFLAGS) $< -o $@ | ||
| 23 | |||
| 24 | clean: | ||
| 25 | rm -f *.o hidtest | ||
| 26 | |||
| 27 | .PHONY: clean | ||
diff --git a/contrib/SDL-3.2.8/src/hidapi/mac/Makefile.am b/contrib/SDL-3.2.8/src/hidapi/mac/Makefile.am new file mode 100644 index 0000000..23d96e0 --- /dev/null +++ b/contrib/SDL-3.2.8/src/hidapi/mac/Makefile.am | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | lib_LTLIBRARIES = libhidapi.la | ||
| 2 | libhidapi_la_SOURCES = hid.c | ||
| 3 | libhidapi_la_LDFLAGS = $(LTLDFLAGS) | ||
| 4 | AM_CPPFLAGS = -I$(top_srcdir)/hidapi/ | ||
| 5 | |||
| 6 | hdrdir = $(includedir)/hidapi | ||
| 7 | hdr_HEADERS = $(top_srcdir)/hidapi/hidapi.h | ||
| 8 | |||
| 9 | EXTRA_DIST = Makefile-manual | ||
diff --git a/contrib/SDL-3.2.8/src/hidapi/mac/hid.c b/contrib/SDL-3.2.8/src/hidapi/mac/hid.c new file mode 100644 index 0000000..8acb6da --- /dev/null +++ b/contrib/SDL-3.2.8/src/hidapi/mac/hid.c | |||
| @@ -0,0 +1,1594 @@ | |||
| 1 | /******************************************************* | ||
| 2 | HIDAPI - Multi-Platform library for | ||
| 3 | communication with HID devices. | ||
| 4 | |||
| 5 | Alan Ott | ||
| 6 | Signal 11 Software | ||
| 7 | |||
| 8 | libusb/hidapi Team | ||
| 9 | |||
| 10 | Copyright 2022, All Rights Reserved. | ||
| 11 | |||
| 12 | At the discretion of the user of this library, | ||
| 13 | this software may be licensed under the terms of the | ||
| 14 | GNU General Public License v3, a BSD-Style license, or the | ||
| 15 | original HIDAPI license as outlined in the LICENSE.txt, | ||
| 16 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt | ||
| 17 | files located at the root of the source distribution. | ||
| 18 | These files may also be found in the public source | ||
| 19 | code repository located at: | ||
| 20 | https://github.com/libusb/hidapi . | ||
| 21 | ********************************************************/ | ||
| 22 | |||
| 23 | /* See Apple Technical Note TN2187 for details on IOHidManager. */ | ||
| 24 | |||
| 25 | #include <IOKit/hid/IOHIDManager.h> | ||
| 26 | #include <IOKit/hid/IOHIDKeys.h> | ||
| 27 | #include <IOKit/IOKitLib.h> | ||
| 28 | #include <IOKit/usb/USBSpec.h> | ||
| 29 | #include <CoreFoundation/CoreFoundation.h> | ||
| 30 | #include <mach/mach_error.h> | ||
| 31 | #include <stdbool.h> | ||
| 32 | #include <wchar.h> | ||
| 33 | #include <locale.h> | ||
| 34 | #include <pthread.h> | ||
| 35 | #include <sys/time.h> | ||
| 36 | #include <unistd.h> | ||
| 37 | #include <dlfcn.h> | ||
| 38 | |||
| 39 | #include "hidapi_darwin.h" | ||
| 40 | |||
| 41 | /* Barrier implementation because Mac OSX doesn't have pthread_barrier. | ||
| 42 | It also doesn't have clock_gettime(). So much for POSIX and SUSv2. | ||
| 43 | This implementation came from Brent Priddy and was posted on | ||
| 44 | StackOverflow. It is used with his permission. */ | ||
| 45 | typedef int pthread_barrierattr_t; | ||
| 46 | typedef struct pthread_barrier { | ||
| 47 | pthread_mutex_t mutex; | ||
| 48 | pthread_cond_t cond; | ||
| 49 | int count; | ||
| 50 | int trip_count; | ||
| 51 | } pthread_barrier_t; | ||
| 52 | |||
| 53 | static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) | ||
| 54 | { | ||
| 55 | (void) attr; | ||
| 56 | |||
| 57 | if (count == 0) { | ||
| 58 | errno = EINVAL; | ||
| 59 | return -1; | ||
| 60 | } | ||
| 61 | |||
| 62 | if (pthread_mutex_init(&barrier->mutex, 0) < 0) { | ||
| 63 | return -1; | ||
| 64 | } | ||
| 65 | if (pthread_cond_init(&barrier->cond, 0) < 0) { | ||
| 66 | pthread_mutex_destroy(&barrier->mutex); | ||
| 67 | return -1; | ||
| 68 | } | ||
| 69 | barrier->trip_count = count; | ||
| 70 | barrier->count = 0; | ||
| 71 | |||
| 72 | return 0; | ||
| 73 | } | ||
| 74 | |||
| 75 | static int pthread_barrier_destroy(pthread_barrier_t *barrier) | ||
| 76 | { | ||
| 77 | pthread_cond_destroy(&barrier->cond); | ||
| 78 | pthread_mutex_destroy(&barrier->mutex); | ||
| 79 | return 0; | ||
| 80 | } | ||
| 81 | |||
| 82 | static int pthread_barrier_wait(pthread_barrier_t *barrier) | ||
| 83 | { | ||
| 84 | pthread_mutex_lock(&barrier->mutex); | ||
| 85 | ++(barrier->count); | ||
| 86 | if (barrier->count >= barrier->trip_count) { | ||
| 87 | barrier->count = 0; | ||
| 88 | pthread_mutex_unlock(&barrier->mutex); | ||
| 89 | pthread_cond_broadcast(&barrier->cond); | ||
| 90 | return 1; | ||
| 91 | } | ||
| 92 | else { | ||
| 93 | do { | ||
| 94 | pthread_cond_wait(&barrier->cond, &(barrier->mutex)); | ||
| 95 | } | ||
| 96 | while (barrier->count != 0); | ||
| 97 | |||
| 98 | pthread_mutex_unlock(&barrier->mutex); | ||
| 99 | return 0; | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | static int return_data(hid_device *dev, unsigned char *data, size_t length); | ||
| 104 | |||
| 105 | /* Linked List of input reports received from the device. */ | ||
| 106 | struct input_report { | ||
| 107 | uint8_t *data; | ||
| 108 | size_t len; | ||
| 109 | struct input_report *next; | ||
| 110 | }; | ||
| 111 | |||
| 112 | static struct hid_api_version api_version = { | ||
| 113 | .major = HID_API_VERSION_MAJOR, | ||
| 114 | .minor = HID_API_VERSION_MINOR, | ||
| 115 | .patch = HID_API_VERSION_PATCH | ||
| 116 | }; | ||
| 117 | |||
| 118 | /* - Run context - */ | ||
| 119 | static IOHIDManagerRef hid_mgr = 0x0; | ||
| 120 | static int is_macos_10_10_or_greater = 0; | ||
| 121 | static IOOptionBits device_open_options = 0; | ||
| 122 | static wchar_t *last_global_error_str = NULL; | ||
| 123 | /* --- */ | ||
| 124 | |||
| 125 | struct hid_device_ { | ||
| 126 | IOHIDDeviceRef device_handle; | ||
| 127 | IOOptionBits open_options; | ||
| 128 | int blocking; | ||
| 129 | int disconnected; | ||
| 130 | CFStringRef run_loop_mode; | ||
| 131 | CFRunLoopRef run_loop; | ||
| 132 | CFRunLoopSourceRef source; | ||
| 133 | uint8_t *input_report_buf; | ||
| 134 | CFIndex max_input_report_len; | ||
| 135 | struct input_report *input_reports; | ||
| 136 | struct hid_device_info* device_info; | ||
| 137 | |||
| 138 | pthread_t thread; | ||
| 139 | pthread_mutex_t mutex; /* Protects input_reports */ | ||
| 140 | pthread_cond_t condition; | ||
| 141 | pthread_barrier_t barrier; /* Ensures correct startup sequence */ | ||
| 142 | pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */ | ||
| 143 | int shutdown_thread; | ||
| 144 | wchar_t *last_error_str; | ||
| 145 | }; | ||
| 146 | |||
| 147 | static hid_device *new_hid_device(void) | ||
| 148 | { | ||
| 149 | hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); | ||
| 150 | if (dev == NULL) { | ||
| 151 | return NULL; | ||
| 152 | } | ||
| 153 | |||
| 154 | dev->device_handle = NULL; | ||
| 155 | dev->open_options = device_open_options; | ||
| 156 | dev->blocking = 1; | ||
| 157 | dev->disconnected = 0; | ||
| 158 | dev->run_loop_mode = NULL; | ||
| 159 | dev->run_loop = NULL; | ||
| 160 | dev->source = NULL; | ||
| 161 | dev->input_report_buf = NULL; | ||
| 162 | dev->input_reports = NULL; | ||
| 163 | dev->device_info = NULL; | ||
| 164 | dev->shutdown_thread = 0; | ||
| 165 | dev->last_error_str = NULL; | ||
| 166 | |||
| 167 | /* Thread objects */ | ||
| 168 | pthread_mutex_init(&dev->mutex, NULL); | ||
| 169 | pthread_cond_init(&dev->condition, NULL); | ||
| 170 | pthread_barrier_init(&dev->barrier, NULL, 2); | ||
| 171 | pthread_barrier_init(&dev->shutdown_barrier, NULL, 2); | ||
| 172 | |||
| 173 | return dev; | ||
| 174 | } | ||
| 175 | |||
| 176 | static void free_hid_device(hid_device *dev) | ||
| 177 | { | ||
| 178 | if (!dev) | ||
| 179 | return; | ||
| 180 | |||
| 181 | /* Delete any input reports still left over. */ | ||
| 182 | struct input_report *rpt = dev->input_reports; | ||
| 183 | while (rpt) { | ||
| 184 | struct input_report *next = rpt->next; | ||
| 185 | free(rpt->data); | ||
| 186 | free(rpt); | ||
| 187 | rpt = next; | ||
| 188 | } | ||
| 189 | |||
| 190 | /* Free the string and the report buffer. The check for NULL | ||
| 191 | is necessary here as CFRelease() doesn't handle NULL like | ||
| 192 | free() and others do. */ | ||
| 193 | if (dev->run_loop_mode) | ||
| 194 | CFRelease(dev->run_loop_mode); | ||
| 195 | if (dev->source) | ||
| 196 | CFRelease(dev->source); | ||
| 197 | free(dev->input_report_buf); | ||
| 198 | hid_free_enumeration(dev->device_info); | ||
| 199 | |||
| 200 | /* Clean up the thread objects */ | ||
| 201 | pthread_barrier_destroy(&dev->shutdown_barrier); | ||
| 202 | pthread_barrier_destroy(&dev->barrier); | ||
| 203 | pthread_cond_destroy(&dev->condition); | ||
| 204 | pthread_mutex_destroy(&dev->mutex); | ||
| 205 | |||
| 206 | /* Free the structure itself. */ | ||
| 207 | free(dev); | ||
| 208 | } | ||
| 209 | |||
| 210 | |||
| 211 | #ifndef HIDAPI_USING_SDL_RUNTIME | ||
| 212 | /* The caller must free the returned string with free(). */ | ||
| 213 | static wchar_t *utf8_to_wchar_t(const char *utf8) | ||
| 214 | { | ||
| 215 | wchar_t *ret = NULL; | ||
| 216 | |||
| 217 | if (utf8) { | ||
| 218 | size_t wlen = mbstowcs(NULL, utf8, 0); | ||
| 219 | if ((size_t) -1 == wlen) { | ||
| 220 | return wcsdup(L""); | ||
| 221 | } | ||
| 222 | ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); | ||
| 223 | if (ret == NULL) { | ||
| 224 | /* as much as we can do at this point */ | ||
| 225 | return NULL; | ||
| 226 | } | ||
| 227 | mbstowcs(ret, utf8, wlen+1); | ||
| 228 | ret[wlen] = 0x0000; | ||
| 229 | } | ||
| 230 | |||
| 231 | return ret; | ||
| 232 | } | ||
| 233 | #endif | ||
| 234 | |||
| 235 | |||
| 236 | /* Makes a copy of the given error message (and decoded according to the | ||
| 237 | * currently locale) into the wide string pointer pointed by error_str. | ||
| 238 | * The last stored error string is freed. | ||
| 239 | * Use register_error_str(NULL) to free the error message completely. */ | ||
| 240 | static void register_error_str(wchar_t **error_str, const char *msg) | ||
| 241 | { | ||
| 242 | free(*error_str); | ||
| 243 | #ifdef HIDAPI_USING_SDL_RUNTIME | ||
| 244 | /* Thread-safe error handling */ | ||
| 245 | if (msg) { | ||
| 246 | SDL_SetError("%s", msg); | ||
| 247 | } else { | ||
| 248 | SDL_ClearError(); | ||
| 249 | } | ||
| 250 | #else | ||
| 251 | *error_str = utf8_to_wchar_t(msg); | ||
| 252 | #endif | ||
| 253 | } | ||
| 254 | |||
| 255 | /* Similar to register_error_str, but allows passing a format string with va_list args into this function. */ | ||
| 256 | static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) | ||
| 257 | { | ||
| 258 | char msg[1024]; | ||
| 259 | vsnprintf(msg, sizeof(msg), format, args); | ||
| 260 | |||
| 261 | register_error_str(error_str, msg); | ||
| 262 | } | ||
| 263 | |||
| 264 | /* Set the last global error to be reported by hid_error(NULL). | ||
| 265 | * The given error message will be copied (and decoded according to the | ||
| 266 | * currently locale, so do not pass in string constants). | ||
| 267 | * The last stored global error message is freed. | ||
| 268 | * Use register_global_error(NULL) to indicate "no error". */ | ||
| 269 | static void register_global_error(const char *msg) | ||
| 270 | { | ||
| 271 | register_error_str(&last_global_error_str, msg); | ||
| 272 | } | ||
| 273 | |||
| 274 | /* Similar to register_global_error, but allows passing a format string into this function. */ | ||
| 275 | static void register_global_error_format(const char *format, ...) | ||
| 276 | { | ||
| 277 | va_list args; | ||
| 278 | va_start(args, format); | ||
| 279 | register_error_str_vformat(&last_global_error_str, format, args); | ||
| 280 | va_end(args); | ||
| 281 | } | ||
| 282 | |||
| 283 | /* Set the last error for a device to be reported by hid_error(dev). | ||
| 284 | * The given error message will be copied (and decoded according to the | ||
| 285 | * currently locale, so do not pass in string constants). | ||
| 286 | * The last stored device error message is freed. | ||
| 287 | * Use register_device_error(dev, NULL) to indicate "no error". */ | ||
| 288 | static void register_device_error(hid_device *dev, const char *msg) | ||
| 289 | { | ||
| 290 | register_error_str(&dev->last_error_str, msg); | ||
| 291 | } | ||
| 292 | |||
| 293 | /* Similar to register_device_error, but you can pass a format string into this function. */ | ||
| 294 | static void register_device_error_format(hid_device *dev, const char *format, ...) | ||
| 295 | { | ||
| 296 | va_list args; | ||
| 297 | va_start(args, format); | ||
| 298 | register_error_str_vformat(&dev->last_error_str, format, args); | ||
| 299 | va_end(args); | ||
| 300 | } | ||
| 301 | |||
| 302 | |||
| 303 | static CFArrayRef get_array_property(IOHIDDeviceRef device, CFStringRef key) | ||
| 304 | { | ||
| 305 | CFTypeRef ref = IOHIDDeviceGetProperty(device, key); | ||
| 306 | if (ref != NULL && CFGetTypeID(ref) == CFArrayGetTypeID()) { | ||
| 307 | return (CFArrayRef)ref; | ||
| 308 | } else { | ||
| 309 | return NULL; | ||
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 | static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) | ||
| 314 | { | ||
| 315 | CFTypeRef ref; | ||
| 316 | int32_t value = 0; | ||
| 317 | |||
| 318 | ref = IOHIDDeviceGetProperty(device, key); | ||
| 319 | if (ref) { | ||
| 320 | if (CFGetTypeID(ref) == CFNumberGetTypeID()) { | ||
| 321 | CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &value); | ||
| 322 | return value; | ||
| 323 | } | ||
| 324 | } | ||
| 325 | return 0; | ||
| 326 | } | ||
| 327 | |||
| 328 | static bool try_get_int_property(IOHIDDeviceRef device, CFStringRef key, int32_t *out_val) | ||
| 329 | { | ||
| 330 | bool result = false; | ||
| 331 | CFTypeRef ref; | ||
| 332 | |||
| 333 | ref = IOHIDDeviceGetProperty(device, key); | ||
| 334 | if (ref) { | ||
| 335 | if (CFGetTypeID(ref) == CFNumberGetTypeID()) { | ||
| 336 | result = CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, out_val); | ||
| 337 | } | ||
| 338 | } | ||
| 339 | return result; | ||
| 340 | } | ||
| 341 | |||
| 342 | static bool try_get_ioregistry_int_property(io_service_t service, CFStringRef property, int32_t *out_val) | ||
| 343 | { | ||
| 344 | bool result = false; | ||
| 345 | CFTypeRef ref = IORegistryEntryCreateCFProperty(service, property, kCFAllocatorDefault, 0); | ||
| 346 | |||
| 347 | if (ref) { | ||
| 348 | if (CFGetTypeID(ref) == CFNumberGetTypeID()) { | ||
| 349 | result = CFNumberGetValue(ref, kCFNumberSInt32Type, out_val); | ||
| 350 | } | ||
| 351 | |||
| 352 | CFRelease(ref); | ||
| 353 | } | ||
| 354 | |||
| 355 | return result; | ||
| 356 | } | ||
| 357 | |||
| 358 | static CFArrayRef get_usage_pairs(IOHIDDeviceRef device) | ||
| 359 | { | ||
| 360 | return get_array_property(device, CFSTR(kIOHIDDeviceUsagePairsKey)); | ||
| 361 | } | ||
| 362 | |||
| 363 | static unsigned short get_vendor_id(IOHIDDeviceRef device) | ||
| 364 | { | ||
| 365 | return get_int_property(device, CFSTR(kIOHIDVendorIDKey)); | ||
| 366 | } | ||
| 367 | |||
| 368 | static unsigned short get_product_id(IOHIDDeviceRef device) | ||
| 369 | { | ||
| 370 | return get_int_property(device, CFSTR(kIOHIDProductIDKey)); | ||
| 371 | } | ||
| 372 | |||
| 373 | static int32_t get_max_report_length(IOHIDDeviceRef device) | ||
| 374 | { | ||
| 375 | return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey)); | ||
| 376 | } | ||
| 377 | |||
| 378 | static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len) | ||
| 379 | { | ||
| 380 | CFStringRef str; | ||
| 381 | |||
| 382 | if (!len) | ||
| 383 | return 0; | ||
| 384 | |||
| 385 | str = (CFStringRef) IOHIDDeviceGetProperty(device, prop); | ||
| 386 | |||
| 387 | buf[0] = 0; | ||
| 388 | |||
| 389 | if (str && CFGetTypeID(str) == CFStringGetTypeID()) { | ||
| 390 | CFIndex str_len = CFStringGetLength(str); | ||
| 391 | CFRange range; | ||
| 392 | CFIndex used_buf_len; | ||
| 393 | CFIndex chars_copied; | ||
| 394 | |||
| 395 | len --; | ||
| 396 | |||
| 397 | range.location = 0; | ||
| 398 | range.length = ((size_t) str_len > len)? len: (size_t) str_len; | ||
| 399 | chars_copied = CFStringGetBytes(str, | ||
| 400 | range, | ||
| 401 | kCFStringEncodingUTF32LE, | ||
| 402 | (char) '?', | ||
| 403 | FALSE, | ||
| 404 | (UInt8*)buf, | ||
| 405 | len * sizeof(wchar_t), | ||
| 406 | &used_buf_len); | ||
| 407 | |||
| 408 | if (chars_copied <= 0) | ||
| 409 | buf[0] = 0; | ||
| 410 | else | ||
| 411 | buf[chars_copied] = 0; | ||
| 412 | |||
| 413 | return 0; | ||
| 414 | } | ||
| 415 | else | ||
| 416 | return -1; | ||
| 417 | |||
| 418 | } | ||
| 419 | |||
| 420 | static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len) | ||
| 421 | { | ||
| 422 | return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len); | ||
| 423 | } | ||
| 424 | |||
| 425 | static int get_manufacturer_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) | ||
| 426 | { | ||
| 427 | return get_string_property(device, CFSTR(kIOHIDManufacturerKey), buf, len); | ||
| 428 | } | ||
| 429 | |||
| 430 | static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) | ||
| 431 | { | ||
| 432 | return get_string_property(device, CFSTR(kIOHIDProductKey), buf, len); | ||
| 433 | } | ||
| 434 | |||
| 435 | |||
| 436 | /* Implementation of wcsdup() for Mac. */ | ||
| 437 | static wchar_t *dup_wcs(const wchar_t *s) | ||
| 438 | { | ||
| 439 | size_t len = wcslen(s); | ||
| 440 | wchar_t *ret = (wchar_t*) malloc((len+1)*sizeof(wchar_t)); | ||
| 441 | wcscpy(ret, s); | ||
| 442 | |||
| 443 | return ret; | ||
| 444 | } | ||
| 445 | |||
| 446 | /* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */ | ||
| 447 | static int init_hid_manager(void) | ||
| 448 | { | ||
| 449 | /* Initialize all the HID Manager Objects */ | ||
| 450 | hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); | ||
| 451 | if (hid_mgr) { | ||
| 452 | IOHIDManagerSetDeviceMatching(hid_mgr, NULL); | ||
| 453 | IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | ||
| 454 | return 0; | ||
| 455 | } | ||
| 456 | |||
| 457 | register_global_error("Failed to create IOHIDManager"); | ||
| 458 | return -1; | ||
| 459 | } | ||
| 460 | |||
| 461 | HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) | ||
| 462 | { | ||
| 463 | return &api_version; | ||
| 464 | } | ||
| 465 | |||
| 466 | HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) | ||
| 467 | { | ||
| 468 | return HID_API_VERSION_STR; | ||
| 469 | } | ||
| 470 | |||
| 471 | /* Initialize the IOHIDManager if necessary. This is the public function, and | ||
| 472 | it is safe to call this function repeatedly. Return 0 for success and -1 | ||
| 473 | for failure. */ | ||
| 474 | int HID_API_EXPORT hid_init(void) | ||
| 475 | { | ||
| 476 | register_global_error(NULL); | ||
| 477 | |||
| 478 | if (!hid_mgr) { | ||
| 479 | is_macos_10_10_or_greater = (kCFCoreFoundationVersionNumber >= 1151.16); /* kCFCoreFoundationVersionNumber10_10 */ | ||
| 480 | hid_darwin_set_open_exclusive(1); /* Backward compatibility */ | ||
| 481 | return init_hid_manager(); | ||
| 482 | } | ||
| 483 | |||
| 484 | /* Already initialized. */ | ||
| 485 | return 0; | ||
| 486 | } | ||
| 487 | |||
| 488 | int HID_API_EXPORT hid_exit(void) | ||
| 489 | { | ||
| 490 | if (hid_mgr) { | ||
| 491 | /* Close the HID manager. */ | ||
| 492 | IOHIDManagerClose(hid_mgr, kIOHIDOptionsTypeNone); | ||
| 493 | CFRelease(hid_mgr); | ||
| 494 | hid_mgr = NULL; | ||
| 495 | } | ||
| 496 | |||
| 497 | /* Free global error message */ | ||
| 498 | register_global_error(NULL); | ||
| 499 | |||
| 500 | return 0; | ||
| 501 | } | ||
| 502 | |||
| 503 | static void process_pending_events(void) { | ||
| 504 | SInt32 res; | ||
| 505 | do { | ||
| 506 | res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, FALSE); | ||
| 507 | } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); | ||
| 508 | } | ||
| 509 | |||
| 510 | static int read_usb_interface_from_hid_service_parent(io_service_t hid_service) | ||
| 511 | { | ||
| 512 | int32_t result = -1; | ||
| 513 | bool success = false; | ||
| 514 | io_registry_entry_t current = IO_OBJECT_NULL; | ||
| 515 | kern_return_t res; | ||
| 516 | int parent_number = 0; | ||
| 517 | |||
| 518 | res = IORegistryEntryGetParentEntry(hid_service, kIOServicePlane, ¤t); | ||
| 519 | while (KERN_SUCCESS == res | ||
| 520 | /* Only search up to 3 parent entries. | ||
| 521 | * With the default driver - the parent-of-interest supposed to be the first one, | ||
| 522 | * but lets assume some custom drivers or so, with deeper tree. */ | ||
| 523 | && parent_number < 3) { | ||
| 524 | io_registry_entry_t parent = IO_OBJECT_NULL; | ||
| 525 | int32_t interface_number = -1; | ||
| 526 | parent_number++; | ||
| 527 | |||
| 528 | success = try_get_ioregistry_int_property(current, CFSTR(kUSBInterfaceNumber), &interface_number); | ||
| 529 | if (success) { | ||
| 530 | result = interface_number; | ||
| 531 | break; | ||
| 532 | } | ||
| 533 | |||
| 534 | res = IORegistryEntryGetParentEntry(current, kIOServicePlane, &parent); | ||
| 535 | if (parent) { | ||
| 536 | IOObjectRelease(current); | ||
| 537 | current = parent; | ||
| 538 | } | ||
| 539 | |||
| 540 | } | ||
| 541 | |||
| 542 | if (current) { | ||
| 543 | IOObjectRelease(current); | ||
| 544 | current = IO_OBJECT_NULL; | ||
| 545 | } | ||
| 546 | |||
| 547 | return result; | ||
| 548 | } | ||
| 549 | |||
| 550 | #ifdef HIDAPI_IGNORE_DEVICE | ||
| 551 | static hid_bus_type get_bus_type(IOHIDDeviceRef dev) | ||
| 552 | { | ||
| 553 | hid_bus_type bus_type = HID_API_BUS_UNKNOWN; | ||
| 554 | |||
| 555 | CFTypeRef transport_prop = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDTransportKey)); | ||
| 556 | |||
| 557 | if (transport_prop != NULL && CFGetTypeID(transport_prop) == CFStringGetTypeID()) { | ||
| 558 | if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportUSBValue), 0) == kCFCompareEqualTo) { | ||
| 559 | bus_type = HID_API_BUS_USB; | ||
| 560 | } else if (CFStringHasPrefix((CFStringRef)transport_prop, CFSTR(kIOHIDTransportBluetoothValue))) { | ||
| 561 | bus_type = HID_API_BUS_BLUETOOTH; | ||
| 562 | } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportI2CValue), 0) == kCFCompareEqualTo) { | ||
| 563 | bus_type = HID_API_BUS_I2C; | ||
| 564 | } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportSPIValue), 0) == kCFCompareEqualTo) { | ||
| 565 | bus_type = HID_API_BUS_SPI; | ||
| 566 | } | ||
| 567 | } | ||
| 568 | return bus_type; | ||
| 569 | } | ||
| 570 | #endif /* HIDAPI_IGNORE_DEVICE */ | ||
| 571 | |||
| 572 | static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, int32_t usage_page, int32_t usage) | ||
| 573 | { | ||
| 574 | unsigned short dev_vid; | ||
| 575 | unsigned short dev_pid; | ||
| 576 | int BUF_LEN = 256; | ||
| 577 | wchar_t buf[BUF_LEN]; | ||
| 578 | CFTypeRef transport_prop; | ||
| 579 | |||
| 580 | struct hid_device_info *cur_dev; | ||
| 581 | io_service_t hid_service; | ||
| 582 | kern_return_t res; | ||
| 583 | uint64_t entry_id = 0; | ||
| 584 | |||
| 585 | if (dev == NULL) { | ||
| 586 | return NULL; | ||
| 587 | } | ||
| 588 | |||
| 589 | cur_dev = (struct hid_device_info *)calloc(1, sizeof(struct hid_device_info)); | ||
| 590 | if (cur_dev == NULL) { | ||
| 591 | return NULL; | ||
| 592 | } | ||
| 593 | |||
| 594 | dev_vid = get_vendor_id(dev); | ||
| 595 | dev_pid = get_product_id(dev); | ||
| 596 | |||
| 597 | #ifdef HIDAPI_IGNORE_DEVICE | ||
| 598 | /* See if there are any devices we should skip in enumeration */ | ||
| 599 | if (HIDAPI_IGNORE_DEVICE(get_bus_type(dev), dev_vid, dev_pid, usage_page, usage)) { | ||
| 600 | free(cur_dev); | ||
| 601 | return NULL; | ||
| 602 | } | ||
| 603 | #endif | ||
| 604 | |||
| 605 | cur_dev->usage_page = usage_page; | ||
| 606 | cur_dev->usage = usage; | ||
| 607 | |||
| 608 | /* Fill out the record */ | ||
| 609 | cur_dev->next = NULL; | ||
| 610 | |||
| 611 | /* Fill in the path (as a unique ID of the service entry) */ | ||
| 612 | cur_dev->path = NULL; | ||
| 613 | hid_service = IOHIDDeviceGetService(dev); | ||
| 614 | if (hid_service != MACH_PORT_NULL) { | ||
| 615 | res = IORegistryEntryGetRegistryEntryID(hid_service, &entry_id); | ||
| 616 | } | ||
| 617 | else { | ||
| 618 | res = KERN_INVALID_ARGUMENT; | ||
| 619 | } | ||
| 620 | |||
| 621 | if (res == KERN_SUCCESS) { | ||
| 622 | /* max value of entry_id(uint64_t) is 18446744073709551615 which is 20 characters long, | ||
| 623 | so for (max) "path" string 'DevSrvsID:18446744073709551615' we would need | ||
| 624 | 9+1+20+1=31 bytes buffer, but allocate 32 for simple alignment */ | ||
| 625 | const size_t path_len = 32; | ||
| 626 | cur_dev->path = calloc(1, path_len); | ||
| 627 | if (cur_dev->path != NULL) { | ||
| 628 | snprintf(cur_dev->path, path_len, "DevSrvsID:%llu", entry_id); | ||
| 629 | } | ||
| 630 | } | ||
| 631 | |||
| 632 | if (cur_dev->path == NULL) { | ||
| 633 | /* for whatever reason, trying to keep it a non-NULL string */ | ||
| 634 | cur_dev->path = strdup(""); | ||
| 635 | } | ||
| 636 | |||
| 637 | /* Serial Number */ | ||
| 638 | get_serial_number(dev, buf, BUF_LEN); | ||
| 639 | cur_dev->serial_number = dup_wcs(buf); | ||
| 640 | |||
| 641 | /* Manufacturer and Product strings */ | ||
| 642 | get_manufacturer_string(dev, buf, BUF_LEN); | ||
| 643 | cur_dev->manufacturer_string = dup_wcs(buf); | ||
| 644 | get_product_string(dev, buf, BUF_LEN); | ||
| 645 | cur_dev->product_string = dup_wcs(buf); | ||
| 646 | |||
| 647 | /* VID/PID */ | ||
| 648 | cur_dev->vendor_id = dev_vid; | ||
| 649 | cur_dev->product_id = dev_pid; | ||
| 650 | |||
| 651 | /* Release Number */ | ||
| 652 | cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); | ||
| 653 | |||
| 654 | /* Interface Number. | ||
| 655 | * We can only retrieve the interface number for USB HID devices. | ||
| 656 | * See below */ | ||
| 657 | cur_dev->interface_number = -1; | ||
| 658 | |||
| 659 | /* Bus Type */ | ||
| 660 | transport_prop = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDTransportKey)); | ||
| 661 | |||
| 662 | if (transport_prop != NULL && CFGetTypeID(transport_prop) == CFStringGetTypeID()) { | ||
| 663 | if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportUSBValue), 0) == kCFCompareEqualTo) { | ||
| 664 | int32_t interface_number = -1; | ||
| 665 | cur_dev->bus_type = HID_API_BUS_USB; | ||
| 666 | |||
| 667 | /* A IOHIDDeviceRef used to have this simple property, | ||
| 668 | * until macOS 13.3 - we will try to use it. */ | ||
| 669 | if (try_get_int_property(dev, CFSTR(kUSBInterfaceNumber), &interface_number)) { | ||
| 670 | cur_dev->interface_number = interface_number; | ||
| 671 | } else { | ||
| 672 | /* Otherwise fallback to io_service_t property. | ||
| 673 | * (of one of the parent services). */ | ||
| 674 | cur_dev->interface_number = read_usb_interface_from_hid_service_parent(hid_service); | ||
| 675 | |||
| 676 | /* If the above doesn't work - | ||
| 677 | * no (known) fallback exists at this point. */ | ||
| 678 | } | ||
| 679 | |||
| 680 | /* Match "Bluetooth", "BluetoothLowEnergy" and "Bluetooth Low Energy" strings */ | ||
| 681 | } else if (CFStringHasPrefix((CFStringRef)transport_prop, CFSTR(kIOHIDTransportBluetoothValue))) { | ||
| 682 | cur_dev->bus_type = HID_API_BUS_BLUETOOTH; | ||
| 683 | } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportI2CValue), 0) == kCFCompareEqualTo) { | ||
| 684 | cur_dev->bus_type = HID_API_BUS_I2C; | ||
| 685 | } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportSPIValue), 0) == kCFCompareEqualTo) { | ||
| 686 | cur_dev->bus_type = HID_API_BUS_SPI; | ||
| 687 | } | ||
| 688 | } | ||
| 689 | |||
| 690 | return cur_dev; | ||
| 691 | } | ||
| 692 | |||
| 693 | static struct hid_device_info *create_device_info(IOHIDDeviceRef device) | ||
| 694 | { | ||
| 695 | const int32_t primary_usage_page = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); | ||
| 696 | const int32_t primary_usage = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); | ||
| 697 | |||
| 698 | /* Primary should always be first, to match previous behavior. */ | ||
| 699 | struct hid_device_info *root = create_device_info_with_usage(device, primary_usage_page, primary_usage); | ||
| 700 | struct hid_device_info *cur = root; | ||
| 701 | |||
| 702 | CFArrayRef usage_pairs = get_usage_pairs(device); | ||
| 703 | |||
| 704 | if (usage_pairs != NULL) { | ||
| 705 | struct hid_device_info *next = NULL; | ||
| 706 | for (CFIndex i = 0; i < CFArrayGetCount(usage_pairs); i++) { | ||
| 707 | CFTypeRef dict = CFArrayGetValueAtIndex(usage_pairs, i); | ||
| 708 | if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) { | ||
| 709 | continue; | ||
| 710 | } | ||
| 711 | |||
| 712 | CFTypeRef usage_page_ref, usage_ref; | ||
| 713 | int32_t usage_page, usage; | ||
| 714 | |||
| 715 | if (!CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR(kIOHIDDeviceUsagePageKey), &usage_page_ref) || | ||
| 716 | !CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR(kIOHIDDeviceUsageKey), &usage_ref) || | ||
| 717 | CFGetTypeID(usage_page_ref) != CFNumberGetTypeID() || | ||
| 718 | CFGetTypeID(usage_ref) != CFNumberGetTypeID() || | ||
| 719 | !CFNumberGetValue((CFNumberRef)usage_page_ref, kCFNumberSInt32Type, &usage_page) || | ||
| 720 | !CFNumberGetValue((CFNumberRef)usage_ref, kCFNumberSInt32Type, &usage)) { | ||
| 721 | continue; | ||
| 722 | } | ||
| 723 | if (usage_page == primary_usage_page && usage == primary_usage) | ||
| 724 | continue; /* Already added. */ | ||
| 725 | |||
| 726 | next = create_device_info_with_usage(device, usage_page, usage); | ||
| 727 | if (cur) { | ||
| 728 | if (next != NULL) { | ||
| 729 | cur->next = next; | ||
| 730 | cur = next; | ||
| 731 | } | ||
| 732 | } else { | ||
| 733 | root = cur = next; | ||
| 734 | } | ||
| 735 | } | ||
| 736 | } | ||
| 737 | |||
| 738 | return root; | ||
| 739 | } | ||
| 740 | |||
| 741 | struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) | ||
| 742 | { | ||
| 743 | struct hid_device_info *root = NULL; /* return object */ | ||
| 744 | struct hid_device_info *cur_dev = NULL; | ||
| 745 | CFIndex num_devices; | ||
| 746 | int i; | ||
| 747 | |||
| 748 | /* Set up the HID Manager if it hasn't been done */ | ||
| 749 | if (hid_init() < 0) { | ||
| 750 | return NULL; | ||
| 751 | } | ||
| 752 | /* register_global_error: global error is set/reset by hid_init */ | ||
| 753 | |||
| 754 | /* give the IOHIDManager a chance to update itself */ | ||
| 755 | process_pending_events(); | ||
| 756 | |||
| 757 | /* Get a list of the Devices */ | ||
| 758 | CFMutableDictionaryRef matching = NULL; | ||
| 759 | if (vendor_id != 0 || product_id != 0) { | ||
| 760 | matching = CFDictionaryCreateMutable(kCFAllocatorDefault, kIOHIDOptionsTypeNone, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | ||
| 761 | |||
| 762 | if (matching && vendor_id != 0) { | ||
| 763 | CFNumberRef v = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &vendor_id); | ||
| 764 | CFDictionarySetValue(matching, CFSTR(kIOHIDVendorIDKey), v); | ||
| 765 | CFRelease(v); | ||
| 766 | } | ||
| 767 | |||
| 768 | if (matching && product_id != 0) { | ||
| 769 | CFNumberRef p = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &product_id); | ||
| 770 | CFDictionarySetValue(matching, CFSTR(kIOHIDProductIDKey), p); | ||
| 771 | CFRelease(p); | ||
| 772 | } | ||
| 773 | } | ||
| 774 | IOHIDManagerSetDeviceMatching(hid_mgr, matching); | ||
| 775 | if (matching != NULL) { | ||
| 776 | CFRelease(matching); | ||
| 777 | } | ||
| 778 | |||
| 779 | CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); | ||
| 780 | |||
| 781 | IOHIDDeviceRef *device_array = NULL; | ||
| 782 | |||
| 783 | if (device_set != NULL) { | ||
| 784 | /* Convert the list into a C array so we can iterate easily. */ | ||
| 785 | num_devices = CFSetGetCount(device_set); | ||
| 786 | device_array = (IOHIDDeviceRef*) calloc(num_devices, sizeof(IOHIDDeviceRef)); | ||
| 787 | CFSetGetValues(device_set, (const void **) device_array); | ||
| 788 | } else { | ||
| 789 | num_devices = 0; | ||
| 790 | } | ||
| 791 | |||
| 792 | /* Iterate over each device, making an entry for it. */ | ||
| 793 | for (i = 0; i < num_devices; i++) { | ||
| 794 | |||
| 795 | IOHIDDeviceRef dev = device_array[i]; | ||
| 796 | if (!dev) { | ||
| 797 | continue; | ||
| 798 | } | ||
| 799 | |||
| 800 | struct hid_device_info *tmp = create_device_info(dev); | ||
| 801 | if (tmp == NULL) { | ||
| 802 | continue; | ||
| 803 | } | ||
| 804 | |||
| 805 | if (cur_dev) { | ||
| 806 | cur_dev->next = tmp; | ||
| 807 | } | ||
| 808 | else { | ||
| 809 | root = tmp; | ||
| 810 | } | ||
| 811 | cur_dev = tmp; | ||
| 812 | |||
| 813 | /* move the pointer to the tail of returned list */ | ||
| 814 | while (cur_dev->next != NULL) { | ||
| 815 | cur_dev = cur_dev->next; | ||
| 816 | } | ||
| 817 | } | ||
| 818 | |||
| 819 | free(device_array); | ||
| 820 | if (device_set != NULL) | ||
| 821 | CFRelease(device_set); | ||
| 822 | |||
| 823 | if (root == NULL) { | ||
| 824 | if (vendor_id == 0 && product_id == 0) { | ||
| 825 | register_global_error("No HID devices found in the system."); | ||
| 826 | } else { | ||
| 827 | register_global_error("No HID devices with requested VID/PID found in the system."); | ||
| 828 | } | ||
| 829 | } | ||
| 830 | |||
| 831 | return root; | ||
| 832 | } | ||
| 833 | |||
| 834 | void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) | ||
| 835 | { | ||
| 836 | /* This function is identical to the Linux version. Platform independent. */ | ||
| 837 | struct hid_device_info *d = devs; | ||
| 838 | while (d) { | ||
| 839 | struct hid_device_info *next = d->next; | ||
| 840 | free(d->path); | ||
| 841 | free(d->serial_number); | ||
| 842 | free(d->manufacturer_string); | ||
| 843 | free(d->product_string); | ||
| 844 | free(d); | ||
| 845 | d = next; | ||
| 846 | } | ||
| 847 | } | ||
| 848 | |||
| 849 | hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) | ||
| 850 | { | ||
| 851 | /* This function is identical to the Linux version. Platform independent. */ | ||
| 852 | |||
| 853 | struct hid_device_info *devs, *cur_dev; | ||
| 854 | const char *path_to_open = NULL; | ||
| 855 | hid_device * handle = NULL; | ||
| 856 | |||
| 857 | /* register_global_error: global error is reset by hid_enumerate/hid_init */ | ||
| 858 | devs = hid_enumerate(vendor_id, product_id); | ||
| 859 | if (devs == NULL) { | ||
| 860 | /* register_global_error: global error is already set by hid_enumerate */ | ||
| 861 | return NULL; | ||
| 862 | } | ||
| 863 | |||
| 864 | cur_dev = devs; | ||
| 865 | while (cur_dev) { | ||
| 866 | if (cur_dev->vendor_id == vendor_id && | ||
| 867 | cur_dev->product_id == product_id) { | ||
| 868 | if (serial_number) { | ||
| 869 | if (wcscmp(serial_number, cur_dev->serial_number) == 0) { | ||
| 870 | path_to_open = cur_dev->path; | ||
| 871 | break; | ||
| 872 | } | ||
| 873 | } | ||
| 874 | else { | ||
| 875 | path_to_open = cur_dev->path; | ||
| 876 | break; | ||
| 877 | } | ||
| 878 | } | ||
| 879 | cur_dev = cur_dev->next; | ||
| 880 | } | ||
| 881 | |||
| 882 | if (path_to_open) { | ||
| 883 | handle = hid_open_path(path_to_open); | ||
| 884 | } else { | ||
| 885 | register_global_error("Device with requested VID/PID/(SerialNumber) not found"); | ||
| 886 | } | ||
| 887 | |||
| 888 | hid_free_enumeration(devs); | ||
| 889 | |||
| 890 | return handle; | ||
| 891 | } | ||
| 892 | |||
| 893 | static void hid_device_removal_callback(void *context, IOReturn result, | ||
| 894 | void *sender) | ||
| 895 | { | ||
| 896 | (void) result; | ||
| 897 | (void) sender; | ||
| 898 | |||
| 899 | /* Stop the Run Loop for this device. */ | ||
| 900 | hid_device *d = (hid_device*) context; | ||
| 901 | |||
| 902 | d->disconnected = 1; | ||
| 903 | CFRunLoopStop(d->run_loop); | ||
| 904 | } | ||
| 905 | |||
| 906 | /* The Run Loop calls this function for each input report received. | ||
| 907 | This function puts the data into a linked list to be picked up by | ||
| 908 | hid_read(). */ | ||
| 909 | static void hid_report_callback(void *context, IOReturn result, void *sender, | ||
| 910 | IOHIDReportType report_type, uint32_t report_id, | ||
| 911 | uint8_t *report, CFIndex report_length) | ||
| 912 | { | ||
| 913 | (void) result; | ||
| 914 | (void) sender; | ||
| 915 | (void) report_type; | ||
| 916 | (void) report_id; | ||
| 917 | |||
| 918 | struct input_report *rpt; | ||
| 919 | hid_device *dev = (hid_device*) context; | ||
| 920 | |||
| 921 | /* Make a new Input Report object */ | ||
| 922 | rpt = (struct input_report*) calloc(1, sizeof(struct input_report)); | ||
| 923 | rpt->data = (uint8_t*) calloc(1, report_length); | ||
| 924 | memcpy(rpt->data, report, report_length); | ||
| 925 | rpt->len = report_length; | ||
| 926 | rpt->next = NULL; | ||
| 927 | |||
| 928 | /* Lock this section */ | ||
| 929 | pthread_mutex_lock(&dev->mutex); | ||
| 930 | |||
| 931 | /* Attach the new report object to the end of the list. */ | ||
| 932 | if (dev->input_reports == NULL) { | ||
| 933 | /* The list is empty. Put it at the root. */ | ||
| 934 | dev->input_reports = rpt; | ||
| 935 | } | ||
| 936 | else { | ||
| 937 | /* Find the end of the list and attach. */ | ||
| 938 | struct input_report *cur = dev->input_reports; | ||
| 939 | int num_queued = 0; | ||
| 940 | while (cur->next != NULL) { | ||
| 941 | cur = cur->next; | ||
| 942 | num_queued++; | ||
| 943 | } | ||
| 944 | cur->next = rpt; | ||
| 945 | |||
| 946 | /* Pop one off if we've reached 30 in the queue. This | ||
| 947 | way we don't grow forever if the user never reads | ||
| 948 | anything from the device. */ | ||
| 949 | if (num_queued > 30) { | ||
| 950 | return_data(dev, NULL, 0); | ||
| 951 | } | ||
| 952 | } | ||
| 953 | |||
| 954 | /* Signal a waiting thread that there is data. */ | ||
| 955 | pthread_cond_signal(&dev->condition); | ||
| 956 | |||
| 957 | /* Unlock */ | ||
| 958 | pthread_mutex_unlock(&dev->mutex); | ||
| 959 | |||
| 960 | } | ||
| 961 | |||
| 962 | /* This gets called when the read_thread's run loop gets signaled by | ||
| 963 | hid_close(), and serves to stop the read_thread's run loop. */ | ||
| 964 | static void perform_signal_callback(void *context) | ||
| 965 | { | ||
| 966 | hid_device *dev = (hid_device*) context; | ||
| 967 | CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/ | ||
| 968 | } | ||
| 969 | |||
| 970 | static void *read_thread(void *param) | ||
| 971 | { | ||
| 972 | hid_device *dev = (hid_device*) param; | ||
| 973 | SInt32 code; | ||
| 974 | |||
| 975 | /* Move the device's run loop to this thread. */ | ||
| 976 | IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode); | ||
| 977 | |||
| 978 | /* Create the RunLoopSource which is used to signal the | ||
| 979 | event loop to stop when hid_close() is called. */ | ||
| 980 | CFRunLoopSourceContext ctx; | ||
| 981 | memset(&ctx, 0, sizeof(ctx)); | ||
| 982 | ctx.version = 0; | ||
| 983 | ctx.info = dev; | ||
| 984 | ctx.perform = &perform_signal_callback; | ||
| 985 | dev->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx); | ||
| 986 | CFRunLoopAddSource(CFRunLoopGetCurrent(), dev->source, dev->run_loop_mode); | ||
| 987 | |||
| 988 | /* Store off the Run Loop so it can be stopped from hid_close() | ||
| 989 | and on device disconnection. */ | ||
| 990 | dev->run_loop = CFRunLoopGetCurrent(); | ||
| 991 | |||
| 992 | /* Notify the main thread that the read thread is up and running. */ | ||
| 993 | pthread_barrier_wait(&dev->barrier); | ||
| 994 | |||
| 995 | /* Run the Event Loop. CFRunLoopRunInMode() will dispatch HID input | ||
| 996 | reports into the hid_report_callback(). */ | ||
| 997 | while (!dev->shutdown_thread && !dev->disconnected) { | ||
| 998 | code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE); | ||
| 999 | /* Return if the device has been disconnected */ | ||
| 1000 | if (code == kCFRunLoopRunFinished || code == kCFRunLoopRunStopped) { | ||
| 1001 | dev->disconnected = 1; | ||
| 1002 | break; | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | |||
| 1006 | /* Break if The Run Loop returns Finished or Stopped. */ | ||
| 1007 | if (code != kCFRunLoopRunTimedOut && | ||
| 1008 | code != kCFRunLoopRunHandledSource) { | ||
| 1009 | /* There was some kind of error. Setting | ||
| 1010 | shutdown seems to make sense, but | ||
| 1011 | there may be something else more appropriate */ | ||
| 1012 | dev->shutdown_thread = 1; | ||
| 1013 | break; | ||
| 1014 | } | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | /* Now that the read thread is stopping, Wake any threads which are | ||
| 1018 | waiting on data (in hid_read_timeout()). Do this under a mutex to | ||
| 1019 | make sure that a thread which is about to go to sleep waiting on | ||
| 1020 | the condition actually will go to sleep before the condition is | ||
| 1021 | signaled. */ | ||
| 1022 | pthread_mutex_lock(&dev->mutex); | ||
| 1023 | pthread_cond_broadcast(&dev->condition); | ||
| 1024 | pthread_mutex_unlock(&dev->mutex); | ||
| 1025 | |||
| 1026 | /* Wait here until hid_close() is called and makes it past | ||
| 1027 | the call to CFRunLoopWakeUp(). This thread still needs to | ||
| 1028 | be valid when that function is called on the other thread. */ | ||
| 1029 | pthread_barrier_wait(&dev->shutdown_barrier); | ||
| 1030 | |||
| 1031 | return NULL; | ||
| 1032 | } | ||
| 1033 | |||
| 1034 | /* \p path must be one of: | ||
| 1035 | - in format 'DevSrvsID:<RegistryEntryID>' (as returned by hid_enumerate); | ||
| 1036 | - a valid path to an IOHIDDevice in the IOService plane (as returned by IORegistryEntryGetPath, | ||
| 1037 | e.g.: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"); | ||
| 1038 | Second format is for compatibility with paths accepted by older versions of HIDAPI. | ||
| 1039 | */ | ||
| 1040 | static io_registry_entry_t hid_open_service_registry_from_path(const char *path) | ||
| 1041 | { | ||
| 1042 | if (path == NULL) | ||
| 1043 | return MACH_PORT_NULL; | ||
| 1044 | |||
| 1045 | /* Get the IORegistry entry for the given path */ | ||
| 1046 | if (strncmp("DevSrvsID:", path, 10) == 0) { | ||
| 1047 | char *endptr; | ||
| 1048 | uint64_t entry_id = strtoull(path + 10, &endptr, 10); | ||
| 1049 | if (*endptr == '\0') { | ||
| 1050 | return IOServiceGetMatchingService((mach_port_t) 0, IORegistryEntryIDMatching(entry_id)); | ||
| 1051 | } | ||
| 1052 | } | ||
| 1053 | else { | ||
| 1054 | /* Fallback to older format of the path */ | ||
| 1055 | return IORegistryEntryFromPath((mach_port_t) 0, path); | ||
| 1056 | } | ||
| 1057 | |||
| 1058 | return MACH_PORT_NULL; | ||
| 1059 | } | ||
| 1060 | |||
| 1061 | hid_device * HID_API_EXPORT hid_open_path(const char *path) | ||
| 1062 | { | ||
| 1063 | hid_device *dev = NULL; | ||
| 1064 | io_registry_entry_t entry = MACH_PORT_NULL; | ||
| 1065 | IOReturn ret = kIOReturnInvalid; | ||
| 1066 | char str[32]; | ||
| 1067 | |||
| 1068 | /* Set up the HID Manager if it hasn't been done */ | ||
| 1069 | if (hid_init() < 0) { | ||
| 1070 | return NULL; | ||
| 1071 | } | ||
| 1072 | /* register_global_error: global error is set/reset by hid_init */ | ||
| 1073 | |||
| 1074 | dev = new_hid_device(); | ||
| 1075 | if (!dev) { | ||
| 1076 | register_global_error("Couldn't allocate memory"); | ||
| 1077 | return NULL; | ||
| 1078 | } | ||
| 1079 | |||
| 1080 | /* Get the IORegistry entry for the given path */ | ||
| 1081 | entry = hid_open_service_registry_from_path(path); | ||
| 1082 | if (entry == MACH_PORT_NULL) { | ||
| 1083 | /* Path wasn't valid (maybe device was removed?) */ | ||
| 1084 | register_global_error("hid_open_path: device mach entry not found with the given path"); | ||
| 1085 | goto return_error; | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | /* Create an IOHIDDevice for the entry */ | ||
| 1089 | dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry); | ||
| 1090 | if (dev->device_handle == NULL) { | ||
| 1091 | /* Error creating the HID device */ | ||
| 1092 | register_global_error("hid_open_path: failed to create IOHIDDevice from the mach entry"); | ||
| 1093 | goto return_error; | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | /* Open the IOHIDDevice */ | ||
| 1097 | ret = IOHIDDeviceOpen(dev->device_handle, dev->open_options); | ||
| 1098 | if (ret != kIOReturnSuccess) { | ||
| 1099 | register_global_error_format("hid_open_path: failed to open IOHIDDevice from mach entry: (0x%08X) %s", ret, mach_error_string(ret)); | ||
| 1100 | goto return_error; | ||
| 1101 | } | ||
| 1102 | |||
| 1103 | /* Create the buffers for receiving data */ | ||
| 1104 | dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle); | ||
| 1105 | dev->input_report_buf = (uint8_t*) calloc(dev->max_input_report_len, sizeof(uint8_t)); | ||
| 1106 | |||
| 1107 | /* Create the Run Loop Mode for this device. | ||
| 1108 | printing the reference seems to work. */ | ||
| 1109 | snprintf(str, sizeof(str), "HIDAPI_%p", (void*) dev->device_handle); | ||
| 1110 | dev->run_loop_mode = | ||
| 1111 | CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); | ||
| 1112 | |||
| 1113 | /* Attach the device to a Run Loop */ | ||
| 1114 | IOHIDDeviceRegisterInputReportCallback( | ||
| 1115 | dev->device_handle, dev->input_report_buf, dev->max_input_report_len, | ||
| 1116 | &hid_report_callback, dev); | ||
| 1117 | IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); | ||
| 1118 | |||
| 1119 | /* Start the read thread */ | ||
| 1120 | pthread_create(&dev->thread, NULL, read_thread, dev); | ||
| 1121 | |||
| 1122 | /* Wait here for the read thread to be initialized. */ | ||
| 1123 | pthread_barrier_wait(&dev->barrier); | ||
| 1124 | |||
| 1125 | IOObjectRelease(entry); | ||
| 1126 | return dev; | ||
| 1127 | |||
| 1128 | return_error: | ||
| 1129 | if (dev->device_handle != NULL) | ||
| 1130 | CFRelease(dev->device_handle); | ||
| 1131 | |||
| 1132 | if (entry != MACH_PORT_NULL) | ||
| 1133 | IOObjectRelease(entry); | ||
| 1134 | |||
| 1135 | free_hid_device(dev); | ||
| 1136 | return NULL; | ||
| 1137 | } | ||
| 1138 | |||
| 1139 | static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length) | ||
| 1140 | { | ||
| 1141 | const unsigned char *data_to_send = data; | ||
| 1142 | CFIndex length_to_send = length; | ||
| 1143 | IOReturn res; | ||
| 1144 | unsigned char report_id; | ||
| 1145 | |||
| 1146 | register_device_error(dev, NULL); | ||
| 1147 | |||
| 1148 | if (!data || (length == 0)) { | ||
| 1149 | register_device_error(dev, strerror(EINVAL)); | ||
| 1150 | return -1; | ||
| 1151 | } | ||
| 1152 | |||
| 1153 | report_id = data[0]; | ||
| 1154 | |||
| 1155 | if (report_id == 0x0) { | ||
| 1156 | /* Not using numbered Reports. | ||
| 1157 | Don't send the report number. */ | ||
| 1158 | data_to_send = data+1; | ||
| 1159 | length_to_send = length-1; | ||
| 1160 | } | ||
| 1161 | |||
| 1162 | /* Avoid crash if the device has been unplugged. */ | ||
| 1163 | if (dev->disconnected) { | ||
| 1164 | register_device_error(dev, "Device is disconnected"); | ||
| 1165 | return -1; | ||
| 1166 | } | ||
| 1167 | |||
| 1168 | res = IOHIDDeviceSetReport(dev->device_handle, | ||
| 1169 | type, | ||
| 1170 | report_id, | ||
| 1171 | data_to_send, length_to_send); | ||
| 1172 | |||
| 1173 | if (res != kIOReturnSuccess) { | ||
| 1174 | register_device_error_format(dev, "IOHIDDeviceSetReport failed: (0x%08X) %s", res, mach_error_string(res)); | ||
| 1175 | return -1; | ||
| 1176 | } | ||
| 1177 | |||
| 1178 | return (int) length; | ||
| 1179 | } | ||
| 1180 | |||
| 1181 | static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data, size_t length) | ||
| 1182 | { | ||
| 1183 | unsigned char *report = data; | ||
| 1184 | CFIndex report_length = length; | ||
| 1185 | IOReturn res = kIOReturnSuccess; | ||
| 1186 | const unsigned char report_id = data[0]; | ||
| 1187 | |||
| 1188 | register_device_error(dev, NULL); | ||
| 1189 | |||
| 1190 | if (report_id == 0x0) { | ||
| 1191 | /* Not using numbered Reports. | ||
| 1192 | Don't send the report number. */ | ||
| 1193 | report = data+1; | ||
| 1194 | report_length = length-1; | ||
| 1195 | } | ||
| 1196 | |||
| 1197 | /* Avoid crash if the device has been unplugged. */ | ||
| 1198 | if (dev->disconnected) { | ||
| 1199 | register_device_error(dev, "Device is disconnected"); | ||
| 1200 | return -1; | ||
| 1201 | } | ||
| 1202 | |||
| 1203 | res = IOHIDDeviceGetReport(dev->device_handle, | ||
| 1204 | type, | ||
| 1205 | report_id, | ||
| 1206 | report, &report_length); | ||
| 1207 | |||
| 1208 | if (res != kIOReturnSuccess) { | ||
| 1209 | register_device_error_format(dev, "IOHIDDeviceGetReport failed: (0x%08X) %s", res, mach_error_string(res)); | ||
| 1210 | return -1; | ||
| 1211 | } | ||
| 1212 | |||
| 1213 | if (report_id == 0x0) { /* 0 report number still present at the beginning */ | ||
| 1214 | report_length++; | ||
| 1215 | } | ||
| 1216 | |||
| 1217 | return (int) report_length; | ||
| 1218 | } | ||
| 1219 | |||
| 1220 | int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) | ||
| 1221 | { | ||
| 1222 | return set_report(dev, kIOHIDReportTypeOutput, data, length); | ||
| 1223 | } | ||
| 1224 | |||
| 1225 | /* Helper function, so that this isn't duplicated in hid_read(). */ | ||
| 1226 | static int return_data(hid_device *dev, unsigned char *data, size_t length) | ||
| 1227 | { | ||
| 1228 | /* Copy the data out of the linked list item (rpt) into the | ||
| 1229 | return buffer (data), and delete the liked list item. */ | ||
| 1230 | struct input_report *rpt = dev->input_reports; | ||
| 1231 | size_t len = (length < rpt->len)? length: rpt->len; | ||
| 1232 | if (data != NULL) { | ||
| 1233 | memcpy(data, rpt->data, len); | ||
| 1234 | } | ||
| 1235 | dev->input_reports = rpt->next; | ||
| 1236 | free(rpt->data); | ||
| 1237 | free(rpt); | ||
| 1238 | return (int) len; | ||
| 1239 | } | ||
| 1240 | |||
| 1241 | static int cond_wait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) | ||
| 1242 | { | ||
| 1243 | while (!dev->input_reports) { | ||
| 1244 | int res = pthread_cond_wait(cond, mutex); | ||
| 1245 | if (res != 0) | ||
| 1246 | return res; | ||
| 1247 | |||
| 1248 | /* A res of 0 means we may have been signaled or it may | ||
| 1249 | be a spurious wakeup. Check to see that there's actually | ||
| 1250 | data in the queue before returning, and if not, go back | ||
| 1251 | to sleep. See the pthread_cond_timedwait() man page for | ||
| 1252 | details. */ | ||
| 1253 | |||
| 1254 | if (dev->shutdown_thread || dev->disconnected) { | ||
| 1255 | return -1; | ||
| 1256 | } | ||
| 1257 | } | ||
| 1258 | |||
| 1259 | return 0; | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | static int cond_timedwait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) | ||
| 1263 | { | ||
| 1264 | while (!dev->input_reports) { | ||
| 1265 | int res = pthread_cond_timedwait(cond, mutex, abstime); | ||
| 1266 | if (res != 0) | ||
| 1267 | return res; | ||
| 1268 | |||
| 1269 | /* A res of 0 means we may have been signaled or it may | ||
| 1270 | be a spurious wakeup. Check to see that there's actually | ||
| 1271 | data in the queue before returning, and if not, go back | ||
| 1272 | to sleep. See the pthread_cond_timedwait() man page for | ||
| 1273 | details. */ | ||
| 1274 | |||
| 1275 | if (dev->shutdown_thread || dev->disconnected) { | ||
| 1276 | return -1; | ||
| 1277 | } | ||
| 1278 | } | ||
| 1279 | |||
| 1280 | return 0; | ||
| 1281 | |||
| 1282 | } | ||
| 1283 | |||
| 1284 | int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) | ||
| 1285 | { | ||
| 1286 | int bytes_read = -1; | ||
| 1287 | |||
| 1288 | /* Lock the access to the report list. */ | ||
| 1289 | pthread_mutex_lock(&dev->mutex); | ||
| 1290 | |||
| 1291 | /* There's an input report queued up. Return it. */ | ||
| 1292 | if (dev->input_reports) { | ||
| 1293 | /* Return the first one */ | ||
| 1294 | bytes_read = return_data(dev, data, length); | ||
| 1295 | goto ret; | ||
| 1296 | } | ||
| 1297 | |||
| 1298 | /* Return if the device has been disconnected. */ | ||
| 1299 | if (dev->disconnected) { | ||
| 1300 | bytes_read = -1; | ||
| 1301 | register_device_error(dev, "hid_read_timeout: device disconnected"); | ||
| 1302 | goto ret; | ||
| 1303 | } | ||
| 1304 | |||
| 1305 | if (dev->shutdown_thread) { | ||
| 1306 | /* This means the device has been closed (or there | ||
| 1307 | has been an error. An error code of -1 should | ||
| 1308 | be returned. */ | ||
| 1309 | bytes_read = -1; | ||
| 1310 | register_device_error(dev, "hid_read_timeout: thread shutdown"); | ||
| 1311 | goto ret; | ||
| 1312 | } | ||
| 1313 | |||
| 1314 | /* There is no data. Go to sleep and wait for data. */ | ||
| 1315 | |||
| 1316 | if (milliseconds == -1) { | ||
| 1317 | /* Blocking */ | ||
| 1318 | int res; | ||
| 1319 | res = cond_wait(dev, &dev->condition, &dev->mutex); | ||
| 1320 | if (res == 0) | ||
| 1321 | bytes_read = return_data(dev, data, length); | ||
| 1322 | else { | ||
| 1323 | /* There was an error, or a device disconnection. */ | ||
| 1324 | register_device_error(dev, "hid_read_timeout: error waiting for more data"); | ||
| 1325 | bytes_read = -1; | ||
| 1326 | } | ||
| 1327 | } | ||
| 1328 | else if (milliseconds > 0) { | ||
| 1329 | /* Non-blocking, but called with timeout. */ | ||
| 1330 | int res; | ||
| 1331 | struct timespec ts; | ||
| 1332 | struct timeval tv; | ||
| 1333 | gettimeofday(&tv, NULL); | ||
| 1334 | TIMEVAL_TO_TIMESPEC(&tv, &ts); | ||
| 1335 | ts.tv_sec += milliseconds / 1000; | ||
| 1336 | ts.tv_nsec += (milliseconds % 1000) * 1000000; | ||
| 1337 | if (ts.tv_nsec >= 1000000000L) { | ||
| 1338 | ts.tv_sec++; | ||
| 1339 | ts.tv_nsec -= 1000000000L; | ||
| 1340 | } | ||
| 1341 | |||
| 1342 | res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts); | ||
| 1343 | if (res == 0) { | ||
| 1344 | bytes_read = return_data(dev, data, length); | ||
| 1345 | } else if (res == ETIMEDOUT) { | ||
| 1346 | bytes_read = 0; | ||
| 1347 | } else { | ||
| 1348 | register_device_error(dev, "hid_read_timeout: error waiting for more data"); | ||
| 1349 | bytes_read = -1; | ||
| 1350 | } | ||
| 1351 | } | ||
| 1352 | else { | ||
| 1353 | /* Purely non-blocking */ | ||
| 1354 | bytes_read = 0; | ||
| 1355 | } | ||
| 1356 | |||
| 1357 | ret: | ||
| 1358 | /* Unlock */ | ||
| 1359 | pthread_mutex_unlock(&dev->mutex); | ||
| 1360 | return bytes_read; | ||
| 1361 | } | ||
| 1362 | |||
| 1363 | int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) | ||
| 1364 | { | ||
| 1365 | return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); | ||
| 1366 | } | ||
| 1367 | |||
| 1368 | int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) | ||
| 1369 | { | ||
| 1370 | /* All Nonblocking operation is handled by the library. */ | ||
| 1371 | dev->blocking = !nonblock; | ||
| 1372 | |||
| 1373 | return 0; | ||
| 1374 | } | ||
| 1375 | |||
| 1376 | int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) | ||
| 1377 | { | ||
| 1378 | return set_report(dev, kIOHIDReportTypeFeature, data, length); | ||
| 1379 | } | ||
| 1380 | |||
| 1381 | int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) | ||
| 1382 | { | ||
| 1383 | return get_report(dev, kIOHIDReportTypeFeature, data, length); | ||
| 1384 | } | ||
| 1385 | |||
| 1386 | int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) | ||
| 1387 | { | ||
| 1388 | return get_report(dev, kIOHIDReportTypeInput, data, length); | ||
| 1389 | } | ||
| 1390 | |||
| 1391 | void HID_API_EXPORT hid_close(hid_device *dev) | ||
| 1392 | { | ||
| 1393 | if (!dev) | ||
| 1394 | return; | ||
| 1395 | |||
| 1396 | /* Disconnect the report callback before close. | ||
| 1397 | See comment below. | ||
| 1398 | */ | ||
| 1399 | if (is_macos_10_10_or_greater || !dev->disconnected) { | ||
| 1400 | IOHIDDeviceRegisterInputReportCallback( | ||
| 1401 | dev->device_handle, dev->input_report_buf, dev->max_input_report_len, | ||
| 1402 | NULL, dev); | ||
| 1403 | IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev); | ||
| 1404 | IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode); | ||
| 1405 | IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode); | ||
| 1406 | } | ||
| 1407 | |||
| 1408 | /* Cause read_thread() to stop. */ | ||
| 1409 | dev->shutdown_thread = 1; | ||
| 1410 | |||
| 1411 | /* Wake up the run thread's event loop so that the thread can exit. */ | ||
| 1412 | CFRunLoopSourceSignal(dev->source); | ||
| 1413 | CFRunLoopWakeUp(dev->run_loop); | ||
| 1414 | |||
| 1415 | /* Notify the read thread that it can shut down now. */ | ||
| 1416 | pthread_barrier_wait(&dev->shutdown_barrier); | ||
| 1417 | |||
| 1418 | /* Wait for read_thread() to end. */ | ||
| 1419 | pthread_join(dev->thread, NULL); | ||
| 1420 | |||
| 1421 | /* Close the OS handle to the device, but only if it's not | ||
| 1422 | been unplugged. If it's been unplugged, then calling | ||
| 1423 | IOHIDDeviceClose() will crash. | ||
| 1424 | |||
| 1425 | UPD: The crash part was true in/until some version of macOS. | ||
| 1426 | Starting with macOS 10.15, there is an opposite effect in some environments: | ||
| 1427 | crash happenes if IOHIDDeviceClose() is not called. | ||
| 1428 | Not leaking a resource in all tested environments. | ||
| 1429 | */ | ||
| 1430 | if (is_macos_10_10_or_greater || !dev->disconnected) { | ||
| 1431 | IOHIDDeviceClose(dev->device_handle, dev->open_options); | ||
| 1432 | } | ||
| 1433 | |||
| 1434 | /* Clear out the queue of received reports. */ | ||
| 1435 | pthread_mutex_lock(&dev->mutex); | ||
| 1436 | while (dev->input_reports) { | ||
| 1437 | return_data(dev, NULL, 0); | ||
| 1438 | } | ||
| 1439 | pthread_mutex_unlock(&dev->mutex); | ||
| 1440 | CFRelease(dev->device_handle); | ||
| 1441 | |||
| 1442 | free_hid_device(dev); | ||
| 1443 | } | ||
| 1444 | |||
| 1445 | int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) | ||
| 1446 | { | ||
| 1447 | if (!string || !maxlen) | ||
| 1448 | { | ||
| 1449 | register_device_error(dev, "Zero buffer/length"); | ||
| 1450 | return -1; | ||
| 1451 | } | ||
| 1452 | |||
| 1453 | struct hid_device_info *info = hid_get_device_info(dev); | ||
| 1454 | if (!info) | ||
| 1455 | { | ||
| 1456 | // hid_get_device_info will have set an error already | ||
| 1457 | return -1; | ||
| 1458 | } | ||
| 1459 | |||
| 1460 | wcsncpy(string, info->manufacturer_string, maxlen); | ||
| 1461 | string[maxlen - 1] = L'\0'; | ||
| 1462 | |||
| 1463 | return 0; | ||
| 1464 | } | ||
| 1465 | |||
| 1466 | int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) | ||
| 1467 | { | ||
| 1468 | if (!string || !maxlen) { | ||
| 1469 | register_device_error(dev, "Zero buffer/length"); | ||
| 1470 | return -1; | ||
| 1471 | } | ||
| 1472 | |||
| 1473 | struct hid_device_info *info = hid_get_device_info(dev); | ||
| 1474 | if (!info) { | ||
| 1475 | // hid_get_device_info will have set an error already | ||
| 1476 | return -1; | ||
| 1477 | } | ||
| 1478 | |||
| 1479 | wcsncpy(string, info->product_string, maxlen); | ||
| 1480 | string[maxlen - 1] = L'\0'; | ||
| 1481 | |||
| 1482 | return 0; | ||
| 1483 | } | ||
| 1484 | |||
| 1485 | int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) | ||
| 1486 | { | ||
| 1487 | if (!string || !maxlen) { | ||
| 1488 | register_device_error(dev, "Zero buffer/length"); | ||
| 1489 | return -1; | ||
| 1490 | } | ||
| 1491 | |||
| 1492 | struct hid_device_info *info = hid_get_device_info(dev); | ||
| 1493 | if (!info) { | ||
| 1494 | // hid_get_device_info will have set an error already | ||
| 1495 | return -1; | ||
| 1496 | } | ||
| 1497 | |||
| 1498 | wcsncpy(string, info->serial_number, maxlen); | ||
| 1499 | string[maxlen - 1] = L'\0'; | ||
| 1500 | |||
| 1501 | return 0; | ||
| 1502 | } | ||
| 1503 | |||
| 1504 | HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { | ||
| 1505 | if (!dev->device_info) { | ||
| 1506 | dev->device_info = create_device_info(dev->device_handle); | ||
| 1507 | if (!dev->device_info) { | ||
| 1508 | register_device_error(dev, "Failed to create hid_device_info"); | ||
| 1509 | } | ||
| 1510 | } | ||
| 1511 | |||
| 1512 | return dev->device_info; | ||
| 1513 | } | ||
| 1514 | |||
| 1515 | int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) | ||
| 1516 | { | ||
| 1517 | (void) dev; | ||
| 1518 | (void) string_index; | ||
| 1519 | (void) string; | ||
| 1520 | (void) maxlen; | ||
| 1521 | |||
| 1522 | register_device_error(dev, "hid_get_indexed_string: not available on this platform"); | ||
| 1523 | return -1; | ||
| 1524 | } | ||
| 1525 | |||
| 1526 | int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id) | ||
| 1527 | { | ||
| 1528 | int res = get_int_property(dev->device_handle, CFSTR(kIOHIDLocationIDKey)); | ||
| 1529 | if (res != 0) { | ||
| 1530 | *location_id = (uint32_t) res; | ||
| 1531 | return 0; | ||
| 1532 | } else { | ||
| 1533 | register_device_error(dev, "Failed to get IOHIDLocationID property"); | ||
| 1534 | return -1; | ||
| 1535 | } | ||
| 1536 | } | ||
| 1537 | |||
| 1538 | void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive) | ||
| 1539 | { | ||
| 1540 | device_open_options = (open_exclusive == 0) ? kIOHIDOptionsTypeNone : kIOHIDOptionsTypeSeizeDevice; | ||
| 1541 | } | ||
| 1542 | |||
| 1543 | int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void) | ||
| 1544 | { | ||
| 1545 | return (device_open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0; | ||
| 1546 | } | ||
| 1547 | |||
| 1548 | int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev) | ||
| 1549 | { | ||
| 1550 | if (!dev) | ||
| 1551 | return -1; | ||
| 1552 | |||
| 1553 | return (dev->open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0; | ||
| 1554 | } | ||
| 1555 | |||
| 1556 | int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) | ||
| 1557 | { | ||
| 1558 | CFTypeRef ref = IOHIDDeviceGetProperty(dev->device_handle, CFSTR(kIOHIDReportDescriptorKey)); | ||
| 1559 | if (ref != NULL && CFGetTypeID(ref) == CFDataGetTypeID()) { | ||
| 1560 | CFDataRef report_descriptor = (CFDataRef) ref; | ||
| 1561 | const UInt8 *descriptor_buf = CFDataGetBytePtr(report_descriptor); | ||
| 1562 | CFIndex descriptor_buf_len = CFDataGetLength(report_descriptor); | ||
| 1563 | size_t copy_len = (size_t) descriptor_buf_len; | ||
| 1564 | |||
| 1565 | if (descriptor_buf == NULL || descriptor_buf_len < 0) { | ||
| 1566 | register_device_error(dev, "Zero buffer/length"); | ||
| 1567 | return -1; | ||
| 1568 | } | ||
| 1569 | |||
| 1570 | if (buf_size < copy_len) { | ||
| 1571 | copy_len = buf_size; | ||
| 1572 | } | ||
| 1573 | |||
| 1574 | memcpy(buf, descriptor_buf, copy_len); | ||
| 1575 | return (int)copy_len; | ||
| 1576 | } | ||
| 1577 | else { | ||
| 1578 | register_device_error(dev, "Failed to get kIOHIDReportDescriptorKey property"); | ||
| 1579 | return -1; | ||
| 1580 | } | ||
| 1581 | } | ||
| 1582 | |||
| 1583 | HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) | ||
| 1584 | { | ||
| 1585 | if (dev) { | ||
| 1586 | if (dev->last_error_str == NULL) | ||
| 1587 | return L"Success"; | ||
| 1588 | return dev->last_error_str; | ||
| 1589 | } | ||
| 1590 | |||
| 1591 | if (last_global_error_str == NULL) | ||
| 1592 | return L"Success"; | ||
| 1593 | return last_global_error_str; | ||
| 1594 | } | ||
diff --git a/contrib/SDL-3.2.8/src/hidapi/mac/hidapi_darwin.h b/contrib/SDL-3.2.8/src/hidapi/mac/hidapi_darwin.h new file mode 100644 index 0000000..1465583 --- /dev/null +++ b/contrib/SDL-3.2.8/src/hidapi/mac/hidapi_darwin.h | |||
| @@ -0,0 +1,98 @@ | |||
| 1 | /******************************************************* | ||
| 2 | HIDAPI - Multi-Platform library for | ||
| 3 | communication with HID devices. | ||
| 4 | |||
| 5 | libusb/hidapi Team | ||
| 6 | |||
| 7 | Copyright 2022, All Rights Reserved. | ||
| 8 | |||
| 9 | At the discretion of the user of this library, | ||
| 10 | this software may be licensed under the terms of the | ||
| 11 | GNU General Public License v3, a BSD-Style license, or the | ||
| 12 | original HIDAPI license as outlined in the LICENSE.txt, | ||
| 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt | ||
| 14 | files located at the root of the source distribution. | ||
| 15 | These files may also be found in the public source | ||
| 16 | code repository located at: | ||
| 17 | https://github.com/libusb/hidapi . | ||
| 18 | ********************************************************/ | ||
| 19 | |||
| 20 | /** @file | ||
| 21 | * @defgroup API hidapi API | ||
| 22 | |||
| 23 | * Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) | ||
| 24 | */ | ||
| 25 | |||
| 26 | #ifndef HIDAPI_DARWIN_H__ | ||
| 27 | #define HIDAPI_DARWIN_H__ | ||
| 28 | |||
| 29 | #include <stdint.h> | ||
| 30 | |||
| 31 | #include "../hidapi/hidapi.h" | ||
| 32 | |||
| 33 | #ifdef __cplusplus | ||
| 34 | extern "C" { | ||
| 35 | #endif | ||
| 36 | |||
| 37 | /** @brief Get the location ID for a HID device. | ||
| 38 | |||
| 39 | Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) | ||
| 40 | |||
| 41 | @ingroup API | ||
| 42 | @param dev A device handle returned from hid_open(). | ||
| 43 | @param location_id The device's location ID on return. | ||
| 44 | |||
| 45 | @returns | ||
| 46 | This function returns 0 on success and -1 on error. | ||
| 47 | */ | ||
| 48 | int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id); | ||
| 49 | |||
| 50 | |||
| 51 | /** @brief Changes the behavior of all further calls to @ref hid_open or @ref hid_open_path. | ||
| 52 | |||
| 53 | By default on Darwin platform all devices opened by HIDAPI with @ref hid_open or @ref hid_open_path | ||
| 54 | are opened in exclusive mode (see kIOHIDOptionsTypeSeizeDevice). | ||
| 55 | |||
| 56 | Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) | ||
| 57 | |||
| 58 | @ingroup API | ||
| 59 | @param open_exclusive When set to 0 - all further devices will be opened | ||
| 60 | in non-exclusive mode. Otherwise - all further devices will be opened | ||
| 61 | in exclusive mode. | ||
| 62 | |||
| 63 | @note During the initialisation by @ref hid_init - this property is set to 1 (TRUE). | ||
| 64 | This is done to preserve full backward compatibility with previous behavior. | ||
| 65 | |||
| 66 | @note Calling this function before @ref hid_init or after @ref hid_exit has no effect. | ||
| 67 | */ | ||
| 68 | void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive); | ||
| 69 | |||
| 70 | /** @brief Getter for option set by @ref hid_darwin_set_open_exclusive. | ||
| 71 | |||
| 72 | Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) | ||
| 73 | |||
| 74 | @ingroup API | ||
| 75 | @return 1 if all further devices will be opened in exclusive mode. | ||
| 76 | |||
| 77 | @note Value returned by this function before calling to @ref hid_init or after @ref hid_exit | ||
| 78 | is not reliable. | ||
| 79 | */ | ||
| 80 | int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void); | ||
| 81 | |||
| 82 | /** @brief Check how the device was opened. | ||
| 83 | |||
| 84 | Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) | ||
| 85 | |||
| 86 | @ingroup API | ||
| 87 | @param dev A device to get property from. | ||
| 88 | |||
| 89 | @return 1 if the device is opened in exclusive mode, 0 - opened in non-exclusive, | ||
| 90 | -1 - if dev is invalid. | ||
| 91 | */ | ||
| 92 | int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev); | ||
| 93 | |||
| 94 | #ifdef __cplusplus | ||
| 95 | } | ||
| 96 | #endif | ||
| 97 | |||
| 98 | #endif | ||
