diff options
| author | 3gg <3gg@shellblade.net> | 2025-12-27 12:03:39 -0800 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2025-12-27 12:03:39 -0800 |
| commit | 5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch) | |
| tree | 8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/hidapi/windows/hid.c | |
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/hidapi/windows/hid.c')
| -rw-r--r-- | contrib/SDL-3.2.8/src/hidapi/windows/hid.c | 1728 |
1 files changed, 1728 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/hidapi/windows/hid.c b/contrib/SDL-3.2.8/src/hidapi/windows/hid.c new file mode 100644 index 0000000..3376ba9 --- /dev/null +++ b/contrib/SDL-3.2.8/src/hidapi/windows/hid.c | |||
| @@ -0,0 +1,1728 @@ | |||
| 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 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) | ||
| 24 | /* Do not warn about wcsncpy usage. | ||
| 25 | https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt */ | ||
| 26 | #define _CRT_SECURE_NO_WARNINGS | ||
| 27 | #endif | ||
| 28 | |||
| 29 | #ifdef __cplusplus | ||
| 30 | extern "C" { | ||
| 31 | #endif | ||
| 32 | |||
| 33 | #include "hidapi_winapi.h" | ||
| 34 | |||
| 35 | #include <windows.h> | ||
| 36 | |||
| 37 | #ifndef _NTDEF_ | ||
| 38 | typedef LONG NTSTATUS; | ||
| 39 | #endif | ||
| 40 | |||
| 41 | #ifndef WC_ERR_INVALID_CHARS | ||
| 42 | #define WC_ERR_INVALID_CHARS 0x00000080 | ||
| 43 | #endif | ||
| 44 | #ifndef _WIN32_WINNT_WIN8 | ||
| 45 | #define _WIN32_WINNT_WIN8 0x0602 | ||
| 46 | #endif | ||
| 47 | |||
| 48 | #ifdef __CYGWIN__ | ||
| 49 | #include <ntdef.h> | ||
| 50 | #include <wctype.h> | ||
| 51 | #define _wcsdup wcsdup | ||
| 52 | #endif | ||
| 53 | |||
| 54 | /*#define HIDAPI_USE_DDK*/ | ||
| 55 | |||
| 56 | #include "hidapi_cfgmgr32.h" | ||
| 57 | #include "hidapi_hidclass.h" | ||
| 58 | #include "hidapi_hidsdi.h" | ||
| 59 | |||
| 60 | #ifndef HIDAPI_USING_SDL_RUNTIME | ||
| 61 | #include <stdio.h> | ||
| 62 | #include <stdlib.h> | ||
| 63 | #include <string.h> | ||
| 64 | #endif | ||
| 65 | |||
| 66 | #ifdef MIN | ||
| 67 | #undef MIN | ||
| 68 | #endif | ||
| 69 | #define MIN(x,y) ((x) < (y)? (x): (y)) | ||
| 70 | |||
| 71 | /* MAXIMUM_USB_STRING_LENGTH from usbspec.h is 255 */ | ||
| 72 | /* BLUETOOTH_DEVICE_NAME_SIZE from bluetoothapis.h is 256 */ | ||
| 73 | #define MAX_STRING_WCHARS 256 | ||
| 74 | |||
| 75 | /* For certain USB devices, using a buffer larger or equal to 127 wchars results | ||
| 76 | in successful completion of HID API functions, but a broken string is stored | ||
| 77 | in the output buffer. This behaviour persists even if HID API is bypassed and | ||
| 78 | HID IOCTLs are passed to the HID driver directly. Therefore, for USB devices, | ||
| 79 | the buffer MUST NOT exceed 126 WCHARs. | ||
| 80 | */ | ||
| 81 | |||
| 82 | #define MAX_STRING_WCHARS_USB 126 | ||
| 83 | |||
| 84 | static struct hid_api_version api_version = { | ||
| 85 | .major = HID_API_VERSION_MAJOR, | ||
| 86 | .minor = HID_API_VERSION_MINOR, | ||
| 87 | .patch = HID_API_VERSION_PATCH | ||
| 88 | }; | ||
| 89 | |||
| 90 | #ifndef HIDAPI_USE_DDK | ||
| 91 | /* Since we're not building with the DDK, and the HID header | ||
| 92 | files aren't part of the Windows SDK, we define what we need ourselves. | ||
| 93 | In lookup_functions(), the function pointers | ||
| 94 | defined below are set. */ | ||
| 95 | |||
| 96 | static HidD_GetHidGuid_ HidD_GetHidGuid; | ||
| 97 | static HidD_GetAttributes_ HidD_GetAttributes; | ||
| 98 | static HidD_GetSerialNumberString_ HidD_GetSerialNumberString; | ||
| 99 | static HidD_GetManufacturerString_ HidD_GetManufacturerString; | ||
| 100 | static HidD_GetProductString_ HidD_GetProductString; | ||
| 101 | static HidD_SetFeature_ HidD_SetFeature; | ||
| 102 | static HidD_GetFeature_ HidD_GetFeature; | ||
| 103 | static HidD_GetInputReport_ HidD_GetInputReport; | ||
| 104 | static HidD_GetIndexedString_ HidD_GetIndexedString; | ||
| 105 | static HidD_GetPreparsedData_ HidD_GetPreparsedData; | ||
| 106 | static HidD_FreePreparsedData_ HidD_FreePreparsedData; | ||
| 107 | static HidP_GetCaps_ HidP_GetCaps; | ||
| 108 | static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers; | ||
| 109 | static HidD_SetOutputReport_ HidD_SetOutputReport; | ||
| 110 | |||
| 111 | static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL; | ||
| 112 | static CM_Get_Parent_ CM_Get_Parent = NULL; | ||
| 113 | static CM_Get_DevNode_PropertyW_ CM_Get_DevNode_PropertyW = NULL; | ||
| 114 | static CM_Get_Device_Interface_PropertyW_ CM_Get_Device_Interface_PropertyW = NULL; | ||
| 115 | static CM_Get_Device_Interface_List_SizeW_ CM_Get_Device_Interface_List_SizeW = NULL; | ||
| 116 | static CM_Get_Device_Interface_ListW_ CM_Get_Device_Interface_ListW = NULL; | ||
| 117 | |||
| 118 | static HMODULE hid_lib_handle = NULL; | ||
| 119 | static HMODULE cfgmgr32_lib_handle = NULL; | ||
| 120 | static BOOLEAN hidapi_initialized = FALSE; | ||
| 121 | |||
| 122 | static void free_library_handles(void) | ||
| 123 | { | ||
| 124 | if (hid_lib_handle) | ||
| 125 | FreeLibrary(hid_lib_handle); | ||
| 126 | hid_lib_handle = NULL; | ||
| 127 | if (cfgmgr32_lib_handle) | ||
| 128 | FreeLibrary(cfgmgr32_lib_handle); | ||
| 129 | cfgmgr32_lib_handle = NULL; | ||
| 130 | } | ||
| 131 | |||
| 132 | #if defined(__GNUC__) && __GNUC__ >= 8 | ||
| 133 | # pragma GCC diagnostic push | ||
| 134 | # pragma GCC diagnostic ignored "-Wcast-function-type" | ||
| 135 | #endif | ||
| 136 | |||
| 137 | static int lookup_functions(void) | ||
| 138 | { | ||
| 139 | hid_lib_handle = LoadLibraryW(L"hid.dll"); | ||
| 140 | if (hid_lib_handle == NULL) { | ||
| 141 | goto err; | ||
| 142 | } | ||
| 143 | |||
| 144 | cfgmgr32_lib_handle = LoadLibraryW(L"cfgmgr32.dll"); | ||
| 145 | if (cfgmgr32_lib_handle == NULL) { | ||
| 146 | goto err; | ||
| 147 | } | ||
| 148 | |||
| 149 | #define RESOLVE(lib_handle, x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) goto err; | ||
| 150 | |||
| 151 | RESOLVE(hid_lib_handle, HidD_GetHidGuid); | ||
| 152 | RESOLVE(hid_lib_handle, HidD_GetAttributes); | ||
| 153 | RESOLVE(hid_lib_handle, HidD_GetSerialNumberString); | ||
| 154 | RESOLVE(hid_lib_handle, HidD_GetManufacturerString); | ||
| 155 | RESOLVE(hid_lib_handle, HidD_GetProductString); | ||
| 156 | RESOLVE(hid_lib_handle, HidD_SetFeature); | ||
| 157 | RESOLVE(hid_lib_handle, HidD_GetFeature); | ||
| 158 | RESOLVE(hid_lib_handle, HidD_GetInputReport); | ||
| 159 | RESOLVE(hid_lib_handle, HidD_GetIndexedString); | ||
| 160 | RESOLVE(hid_lib_handle, HidD_GetPreparsedData); | ||
| 161 | RESOLVE(hid_lib_handle, HidD_FreePreparsedData); | ||
| 162 | RESOLVE(hid_lib_handle, HidP_GetCaps); | ||
| 163 | RESOLVE(hid_lib_handle, HidD_SetNumInputBuffers); | ||
| 164 | RESOLVE(hid_lib_handle, HidD_SetOutputReport); | ||
| 165 | |||
| 166 | RESOLVE(cfgmgr32_lib_handle, CM_Locate_DevNodeW); | ||
| 167 | RESOLVE(cfgmgr32_lib_handle, CM_Get_Parent); | ||
| 168 | RESOLVE(cfgmgr32_lib_handle, CM_Get_DevNode_PropertyW); | ||
| 169 | RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_PropertyW); | ||
| 170 | RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_List_SizeW); | ||
| 171 | RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_ListW); | ||
| 172 | |||
| 173 | #undef RESOLVE | ||
| 174 | |||
| 175 | return 0; | ||
| 176 | |||
| 177 | err: | ||
| 178 | free_library_handles(); | ||
| 179 | return -1; | ||
| 180 | } | ||
| 181 | |||
| 182 | #if defined(__GNUC__) && __GNUC__ >= 8 | ||
| 183 | # pragma GCC diagnostic pop | ||
| 184 | #endif | ||
| 185 | |||
| 186 | #endif /* HIDAPI_USE_DDK */ | ||
| 187 | |||
| 188 | struct hid_device_ { | ||
| 189 | HANDLE device_handle; | ||
| 190 | BOOL blocking; | ||
| 191 | USHORT output_report_length; | ||
| 192 | unsigned char *write_buf; | ||
| 193 | size_t input_report_length; | ||
| 194 | USHORT feature_report_length; | ||
| 195 | unsigned char *feature_buf; | ||
| 196 | wchar_t *last_error_str; | ||
| 197 | BOOL read_pending; | ||
| 198 | char *read_buf; | ||
| 199 | OVERLAPPED ol; | ||
| 200 | OVERLAPPED write_ol; | ||
| 201 | struct hid_device_info* device_info; | ||
| 202 | BOOL use_hid_write_output_report; | ||
| 203 | }; | ||
| 204 | |||
| 205 | static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) | ||
| 206 | { | ||
| 207 | OSVERSIONINFOEXW osvi; | ||
| 208 | DWORDLONG const dwlConditionMask = VerSetConditionMask( | ||
| 209 | VerSetConditionMask( | ||
| 210 | VerSetConditionMask( | ||
| 211 | 0, VER_MAJORVERSION, VER_GREATER_EQUAL ), | ||
| 212 | VER_MINORVERSION, VER_GREATER_EQUAL ), | ||
| 213 | VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL ); | ||
| 214 | |||
| 215 | memset(&osvi, 0, sizeof(osvi)); | ||
| 216 | osvi.dwOSVersionInfoSize = sizeof( osvi ); | ||
| 217 | osvi.dwMajorVersion = wMajorVersion; | ||
| 218 | osvi.dwMinorVersion = wMinorVersion; | ||
| 219 | osvi.wServicePackMajor = wServicePackMajor; | ||
| 220 | |||
| 221 | return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; | ||
| 222 | } | ||
| 223 | |||
| 224 | static hid_device *new_hid_device(void) | ||
| 225 | { | ||
| 226 | hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); | ||
| 227 | |||
| 228 | if (dev == NULL) { | ||
| 229 | return NULL; | ||
| 230 | } | ||
| 231 | |||
| 232 | dev->device_handle = INVALID_HANDLE_VALUE; | ||
| 233 | dev->blocking = TRUE; | ||
| 234 | dev->output_report_length = 0; | ||
| 235 | dev->write_buf = NULL; | ||
| 236 | dev->input_report_length = 0; | ||
| 237 | dev->feature_report_length = 0; | ||
| 238 | dev->feature_buf = NULL; | ||
| 239 | dev->last_error_str = NULL; | ||
| 240 | dev->read_pending = FALSE; | ||
| 241 | dev->read_buf = NULL; | ||
| 242 | memset(&dev->ol, 0, sizeof(dev->ol)); | ||
| 243 | dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL); | ||
| 244 | memset(&dev->write_ol, 0, sizeof(dev->write_ol)); | ||
| 245 | dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL); | ||
| 246 | dev->device_info = NULL; | ||
| 247 | |||
| 248 | return dev; | ||
| 249 | } | ||
| 250 | |||
| 251 | static void free_hid_device(hid_device *dev) | ||
| 252 | { | ||
| 253 | CloseHandle(dev->ol.hEvent); | ||
| 254 | CloseHandle(dev->write_ol.hEvent); | ||
| 255 | CloseHandle(dev->device_handle); | ||
| 256 | free(dev->last_error_str); | ||
| 257 | dev->last_error_str = NULL; | ||
| 258 | free(dev->write_buf); | ||
| 259 | free(dev->feature_buf); | ||
| 260 | free(dev->read_buf); | ||
| 261 | hid_free_enumeration(dev->device_info); | ||
| 262 | free(dev); | ||
| 263 | } | ||
| 264 | |||
| 265 | static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR *op) | ||
| 266 | { | ||
| 267 | WCHAR system_err_buf[1024]; | ||
| 268 | DWORD error_code = GetLastError(); | ||
| 269 | |||
| 270 | free(*error_buffer); | ||
| 271 | *error_buffer = NULL; | ||
| 272 | |||
| 273 | #ifdef HIDAPI_USING_SDL_RUNTIME | ||
| 274 | /* Thread-safe error handling */ | ||
| 275 | SDL_ClearError(); | ||
| 276 | #endif | ||
| 277 | |||
| 278 | /* Only clear out error messages if NULL is passed into op */ | ||
| 279 | if (!op) { | ||
| 280 | return; | ||
| 281 | } | ||
| 282 | |||
| 283 | DWORD system_err_len = FormatMessageW( | ||
| 284 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | ||
| 285 | NULL, | ||
| 286 | error_code, | ||
| 287 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | ||
| 288 | system_err_buf, ARRAYSIZE(system_err_buf), | ||
| 289 | NULL); | ||
| 290 | |||
| 291 | DWORD op_len = (DWORD)wcslen(op); | ||
| 292 | |||
| 293 | DWORD op_prefix_len = | ||
| 294 | op_len | ||
| 295 | + 15 /*: (0x00000000) */ | ||
| 296 | ; | ||
| 297 | DWORD msg_len = | ||
| 298 | + op_prefix_len | ||
| 299 | + system_err_len | ||
| 300 | ; | ||
| 301 | |||
| 302 | WCHAR *msg = (WCHAR *)calloc(msg_len + 1, sizeof (WCHAR)); | ||
| 303 | |||
| 304 | if (!msg) | ||
| 305 | return; | ||
| 306 | |||
| 307 | int printf_written = swprintf(msg, msg_len + 1, L"%.*ls: (0x%08X) %.*ls", (int)op_len, op, error_code, (int)system_err_len, system_err_buf); | ||
| 308 | |||
| 309 | if (printf_written < 0) | ||
| 310 | { | ||
| 311 | /* Highly unlikely */ | ||
| 312 | msg[0] = L'\0'; | ||
| 313 | return; | ||
| 314 | } | ||
| 315 | |||
| 316 | /* Get rid of the CR and LF that FormatMessage() sticks at the | ||
| 317 | end of the message. Thanks Microsoft! */ | ||
| 318 | while(msg[msg_len-1] == L'\r' || msg[msg_len-1] == L'\n' || msg[msg_len-1] == L' ') | ||
| 319 | { | ||
| 320 | msg[msg_len-1] = L'\0'; | ||
| 321 | msg_len--; | ||
| 322 | } | ||
| 323 | |||
| 324 | #ifdef HIDAPI_USING_SDL_RUNTIME | ||
| 325 | /* Thread-safe error handling */ | ||
| 326 | char *error_utf8 = SDL_iconv_wchar_utf8(msg); | ||
| 327 | if (error_utf8) { | ||
| 328 | SDL_SetError("%s", error_utf8); | ||
| 329 | SDL_free(error_utf8); | ||
| 330 | } | ||
| 331 | free(msg); | ||
| 332 | #else | ||
| 333 | *error_buffer = msg; | ||
| 334 | #endif | ||
| 335 | } | ||
| 336 | |||
| 337 | #if defined(__GNUC__) && (__GNUC__ + (__GNUC_MINOR__ >= 6) > 4) | ||
| 338 | # pragma GCC diagnostic push | ||
| 339 | # pragma GCC diagnostic ignored "-Warray-bounds" | ||
| 340 | #endif | ||
| 341 | /* A bug in GCC/mingw gives: | ||
| 342 | * error: array subscript 0 is outside array bounds of 'wchar_t *[0]' {aka 'short unsigned int *[]'} [-Werror=array-bounds] | ||
| 343 | * | free(*error_buffer); | ||
| 344 | * Which doesn't make sense in this context. */ | ||
| 345 | |||
| 346 | static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR *string_error) | ||
| 347 | { | ||
| 348 | free(*error_buffer); | ||
| 349 | *error_buffer = NULL; | ||
| 350 | |||
| 351 | #ifdef HIDAPI_USING_SDL_RUNTIME | ||
| 352 | /* Thread-safe error handling */ | ||
| 353 | char *error_utf8 = string_error ? SDL_iconv_wchar_utf8(string_error) : NULL; | ||
| 354 | if (error_utf8) { | ||
| 355 | SDL_SetError("%s", error_utf8); | ||
| 356 | SDL_free(error_utf8); | ||
| 357 | } else { | ||
| 358 | SDL_ClearError(); | ||
| 359 | } | ||
| 360 | #else | ||
| 361 | if (string_error) { | ||
| 362 | *error_buffer = _wcsdup(string_error); | ||
| 363 | } | ||
| 364 | #endif /* HIDAPI_USING_SDL_RUNTIME */ | ||
| 365 | } | ||
| 366 | |||
| 367 | #if defined(__GNUC__) && (__GNUC__ + (__GNUC_MINOR__ >= 6) > 4) | ||
| 368 | # pragma GCC diagnostic pop | ||
| 369 | #endif | ||
| 370 | |||
| 371 | static void register_winapi_error(hid_device *dev, const WCHAR *op) | ||
| 372 | { | ||
| 373 | register_winapi_error_to_buffer(&dev->last_error_str, op); | ||
| 374 | } | ||
| 375 | |||
| 376 | static void register_string_error(hid_device *dev, const WCHAR *string_error) | ||
| 377 | { | ||
| 378 | register_string_error_to_buffer(&dev->last_error_str, string_error); | ||
| 379 | } | ||
| 380 | |||
| 381 | static wchar_t *last_global_error_str = NULL; | ||
| 382 | |||
| 383 | static void register_global_winapi_error(const WCHAR *op) | ||
| 384 | { | ||
| 385 | register_winapi_error_to_buffer(&last_global_error_str, op); | ||
| 386 | } | ||
| 387 | |||
| 388 | static void register_global_error(const WCHAR *string_error) | ||
| 389 | { | ||
| 390 | register_string_error_to_buffer(&last_global_error_str, string_error); | ||
| 391 | } | ||
| 392 | |||
| 393 | static HANDLE open_device(const wchar_t *path, BOOL open_rw) | ||
| 394 | { | ||
| 395 | HANDLE handle; | ||
| 396 | DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0; | ||
| 397 | DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE; | ||
| 398 | |||
| 399 | handle = CreateFileW(path, | ||
| 400 | desired_access, | ||
| 401 | share_mode, | ||
| 402 | NULL, | ||
| 403 | OPEN_EXISTING, | ||
| 404 | FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/ | ||
| 405 | 0); | ||
| 406 | |||
| 407 | return handle; | ||
| 408 | } | ||
| 409 | |||
| 410 | HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) | ||
| 411 | { | ||
| 412 | return &api_version; | ||
| 413 | } | ||
| 414 | |||
| 415 | HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) | ||
| 416 | { | ||
| 417 | return HID_API_VERSION_STR; | ||
| 418 | } | ||
| 419 | |||
| 420 | int HID_API_EXPORT hid_init(void) | ||
| 421 | { | ||
| 422 | register_global_error(NULL); | ||
| 423 | #ifndef HIDAPI_USE_DDK | ||
| 424 | if (!hidapi_initialized) { | ||
| 425 | if (lookup_functions() < 0) { | ||
| 426 | register_global_winapi_error(L"resolve DLL functions"); | ||
| 427 | return -1; | ||
| 428 | } | ||
| 429 | hidapi_initialized = TRUE; | ||
| 430 | } | ||
| 431 | #endif | ||
| 432 | return 0; | ||
| 433 | } | ||
| 434 | |||
| 435 | int HID_API_EXPORT hid_exit(void) | ||
| 436 | { | ||
| 437 | #ifndef HIDAPI_USE_DDK | ||
| 438 | free_library_handles(); | ||
| 439 | hidapi_initialized = FALSE; | ||
| 440 | #endif | ||
| 441 | register_global_error(NULL); | ||
| 442 | return 0; | ||
| 443 | } | ||
| 444 | |||
| 445 | static void* hid_internal_get_devnode_property(DEVINST dev_node, const DEVPROPKEY* property_key, DEVPROPTYPE expected_property_type) | ||
| 446 | { | ||
| 447 | ULONG len = 0; | ||
| 448 | CONFIGRET cr; | ||
| 449 | DEVPROPTYPE property_type; | ||
| 450 | PBYTE property_value = NULL; | ||
| 451 | |||
| 452 | cr = CM_Get_DevNode_PropertyW(dev_node, property_key, &property_type, NULL, &len, 0); | ||
| 453 | if (cr != CR_BUFFER_SMALL || property_type != expected_property_type) | ||
| 454 | return NULL; | ||
| 455 | |||
| 456 | property_value = (PBYTE)calloc(len, sizeof(BYTE)); | ||
| 457 | cr = CM_Get_DevNode_PropertyW(dev_node, property_key, &property_type, property_value, &len, 0); | ||
| 458 | if (cr != CR_SUCCESS) { | ||
| 459 | free(property_value); | ||
| 460 | return NULL; | ||
| 461 | } | ||
| 462 | |||
| 463 | return property_value; | ||
| 464 | } | ||
| 465 | |||
| 466 | static void* hid_internal_get_device_interface_property(const wchar_t* interface_path, const DEVPROPKEY* property_key, DEVPROPTYPE expected_property_type) | ||
| 467 | { | ||
| 468 | ULONG len = 0; | ||
| 469 | CONFIGRET cr; | ||
| 470 | DEVPROPTYPE property_type; | ||
| 471 | PBYTE property_value = NULL; | ||
| 472 | |||
| 473 | cr = CM_Get_Device_Interface_PropertyW(interface_path, property_key, &property_type, NULL, &len, 0); | ||
| 474 | if (cr != CR_BUFFER_SMALL || property_type != expected_property_type) | ||
| 475 | return NULL; | ||
| 476 | |||
| 477 | property_value = (PBYTE)calloc(len, sizeof(BYTE)); | ||
| 478 | cr = CM_Get_Device_Interface_PropertyW(interface_path, property_key, &property_type, property_value, &len, 0); | ||
| 479 | if (cr != CR_SUCCESS) { | ||
| 480 | free(property_value); | ||
| 481 | return NULL; | ||
| 482 | } | ||
| 483 | |||
| 484 | return property_value; | ||
| 485 | } | ||
| 486 | |||
| 487 | static void hid_internal_towupper(wchar_t* string) | ||
| 488 | { | ||
| 489 | for (wchar_t* p = string; *p; ++p) *p = towupper(*p); | ||
| 490 | } | ||
| 491 | |||
| 492 | static int hid_internal_extract_int_token_value(wchar_t* string, const wchar_t* token) | ||
| 493 | { | ||
| 494 | int token_value; | ||
| 495 | wchar_t* startptr, * endptr; | ||
| 496 | |||
| 497 | startptr = wcsstr(string, token); | ||
| 498 | if (!startptr) | ||
| 499 | return -1; | ||
| 500 | |||
| 501 | startptr += wcslen(token); | ||
| 502 | token_value = wcstol(startptr, &endptr, 16); | ||
| 503 | if (endptr == startptr) | ||
| 504 | return -1; | ||
| 505 | |||
| 506 | return token_value; | ||
| 507 | } | ||
| 508 | |||
| 509 | static void hid_internal_get_usb_info(struct hid_device_info* dev, DEVINST dev_node) | ||
| 510 | { | ||
| 511 | wchar_t *device_id = NULL, *hardware_ids = NULL; | ||
| 512 | |||
| 513 | device_id = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); | ||
| 514 | if (!device_id) | ||
| 515 | goto end; | ||
| 516 | |||
| 517 | /* Normalize to upper case */ | ||
| 518 | hid_internal_towupper(device_id); | ||
| 519 | |||
| 520 | /* Check for Xbox Common Controller class (XUSB) device. | ||
| 521 | https://docs.microsoft.com/windows/win32/xinput/directinput-and-xusb-devices | ||
| 522 | https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput | ||
| 523 | */ | ||
| 524 | if (hid_internal_extract_int_token_value(device_id, L"IG_") != -1) { | ||
| 525 | /* Get devnode parent to reach out USB device. */ | ||
| 526 | if (CM_Get_Parent(&dev_node, dev_node, 0) != CR_SUCCESS) | ||
| 527 | goto end; | ||
| 528 | } | ||
| 529 | |||
| 530 | /* Get the hardware ids from devnode */ | ||
| 531 | hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST); | ||
| 532 | if (!hardware_ids) | ||
| 533 | goto end; | ||
| 534 | |||
| 535 | /* Get additional information from USB device's Hardware ID | ||
| 536 | https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers | ||
| 537 | https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-interfaces-not-grouped-in-collections | ||
| 538 | */ | ||
| 539 | for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) { | ||
| 540 | /* Normalize to upper case */ | ||
| 541 | hid_internal_towupper(hardware_id); | ||
| 542 | |||
| 543 | if (dev->release_number == 0) { | ||
| 544 | /* USB_DEVICE_DESCRIPTOR.bcdDevice value. */ | ||
| 545 | int release_number = hid_internal_extract_int_token_value(hardware_id, L"REV_"); | ||
| 546 | if (release_number != -1) { | ||
| 547 | dev->release_number = (unsigned short)release_number; | ||
| 548 | } | ||
| 549 | } | ||
| 550 | |||
| 551 | if (dev->interface_number == -1) { | ||
| 552 | /* USB_INTERFACE_DESCRIPTOR.bInterfaceNumber value. */ | ||
| 553 | int interface_number = hid_internal_extract_int_token_value(hardware_id, L"MI_"); | ||
| 554 | if (interface_number != -1) { | ||
| 555 | dev->interface_number = interface_number; | ||
| 556 | } | ||
| 557 | } | ||
| 558 | } | ||
| 559 | |||
| 560 | /* Try to get USB device manufacturer string if not provided by HidD_GetManufacturerString. */ | ||
| 561 | if (wcslen(dev->manufacturer_string) == 0) { | ||
| 562 | wchar_t* manufacturer_string = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_Manufacturer, DEVPROP_TYPE_STRING); | ||
| 563 | if (manufacturer_string) { | ||
| 564 | free(dev->manufacturer_string); | ||
| 565 | dev->manufacturer_string = manufacturer_string; | ||
| 566 | } | ||
| 567 | } | ||
| 568 | |||
| 569 | /* Try to get USB device serial number if not provided by HidD_GetSerialNumberString. */ | ||
| 570 | if (wcslen(dev->serial_number) == 0) { | ||
| 571 | DEVINST usb_dev_node = dev_node; | ||
| 572 | if (dev->interface_number != -1) { | ||
| 573 | /* Get devnode parent to reach out composite parent USB device. | ||
| 574 | https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-the-composite-parent-device | ||
| 575 | */ | ||
| 576 | if (CM_Get_Parent(&usb_dev_node, dev_node, 0) != CR_SUCCESS) | ||
| 577 | goto end; | ||
| 578 | } | ||
| 579 | |||
| 580 | /* Get the device id of the USB device. */ | ||
| 581 | free(device_id); | ||
| 582 | device_id = hid_internal_get_devnode_property(usb_dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); | ||
| 583 | if (!device_id) | ||
| 584 | goto end; | ||
| 585 | |||
| 586 | /* Extract substring after last '\\' of Instance ID. | ||
| 587 | For USB devices it may contain device's serial number. | ||
| 588 | https://docs.microsoft.com/windows-hardware/drivers/install/instance-ids | ||
| 589 | */ | ||
| 590 | for (wchar_t *ptr = device_id + wcslen(device_id); ptr > device_id; --ptr) { | ||
| 591 | /* Instance ID is unique only within the scope of the bus. | ||
| 592 | For USB devices it means that serial number is not available. Skip. */ | ||
| 593 | if (*ptr == L'&') | ||
| 594 | break; | ||
| 595 | |||
| 596 | if (*ptr == L'\\') { | ||
| 597 | free(dev->serial_number); | ||
| 598 | dev->serial_number = _wcsdup(ptr + 1); | ||
| 599 | break; | ||
| 600 | } | ||
| 601 | } | ||
| 602 | } | ||
| 603 | |||
| 604 | /* If we can't get the interface number, it means that there is only one interface. */ | ||
| 605 | if (dev->interface_number == -1) | ||
| 606 | dev->interface_number = 0; | ||
| 607 | |||
| 608 | end: | ||
| 609 | free(device_id); | ||
| 610 | free(hardware_ids); | ||
| 611 | } | ||
| 612 | |||
| 613 | /* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices | ||
| 614 | Request this info via dev node properties instead. | ||
| 615 | https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html | ||
| 616 | */ | ||
| 617 | static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node) | ||
| 618 | { | ||
| 619 | if (wcslen(dev->manufacturer_string) == 0) { | ||
| 620 | /* Manufacturer Name String (UUID: 0x2A29) */ | ||
| 621 | wchar_t* manufacturer_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_Manufacturer, DEVPROP_TYPE_STRING); | ||
| 622 | if (manufacturer_string) { | ||
| 623 | free(dev->manufacturer_string); | ||
| 624 | dev->manufacturer_string = manufacturer_string; | ||
| 625 | } | ||
| 626 | } | ||
| 627 | |||
| 628 | if (wcslen(dev->serial_number) == 0) { | ||
| 629 | /* Serial Number String (UUID: 0x2A25) */ | ||
| 630 | wchar_t* serial_number = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_DeviceAddress, DEVPROP_TYPE_STRING); | ||
| 631 | if (serial_number) { | ||
| 632 | free(dev->serial_number); | ||
| 633 | dev->serial_number = serial_number; | ||
| 634 | } | ||
| 635 | } | ||
| 636 | |||
| 637 | if (wcslen(dev->product_string) == 0) { | ||
| 638 | /* Model Number String (UUID: 0x2A24) */ | ||
| 639 | wchar_t* product_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_ModelNumber, DEVPROP_TYPE_STRING); | ||
| 640 | if (!product_string) { | ||
| 641 | DEVINST parent_dev_node = 0; | ||
| 642 | /* Fallback: Get devnode grandparent to reach out Bluetooth LE device node */ | ||
| 643 | if (CM_Get_Parent(&parent_dev_node, dev_node, 0) == CR_SUCCESS) { | ||
| 644 | /* Device Name (UUID: 0x2A00) */ | ||
| 645 | product_string = hid_internal_get_devnode_property(parent_dev_node, &DEVPKEY_NAME, DEVPROP_TYPE_STRING); | ||
| 646 | } | ||
| 647 | } | ||
| 648 | |||
| 649 | if (product_string) { | ||
| 650 | free(dev->product_string); | ||
| 651 | dev->product_string = product_string; | ||
| 652 | } | ||
| 653 | } | ||
| 654 | } | ||
| 655 | |||
| 656 | #ifdef HIDAPI_IGNORE_DEVICE | ||
| 657 | static hid_bus_type get_bus_type(const wchar_t* interface_path) | ||
| 658 | { | ||
| 659 | wchar_t *device_id = NULL, *compatible_ids = NULL; | ||
| 660 | CONFIGRET cr; | ||
| 661 | DEVINST dev_node; | ||
| 662 | hid_bus_type bus_type = HID_API_BUS_UNKNOWN; | ||
| 663 | |||
| 664 | /* Get the device id from interface path */ | ||
| 665 | device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); | ||
| 666 | if (!device_id) | ||
| 667 | goto end; | ||
| 668 | |||
| 669 | /* Open devnode from device id */ | ||
| 670 | cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL); | ||
| 671 | if (cr != CR_SUCCESS) | ||
| 672 | goto end; | ||
| 673 | |||
| 674 | /* Get devnode parent */ | ||
| 675 | cr = CM_Get_Parent(&dev_node, dev_node, 0); | ||
| 676 | if (cr != CR_SUCCESS) | ||
| 677 | goto end; | ||
| 678 | |||
| 679 | /* Get the compatible ids from parent devnode */ | ||
| 680 | compatible_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_CompatibleIds, DEVPROP_TYPE_STRING_LIST); | ||
| 681 | if (!compatible_ids) | ||
| 682 | goto end; | ||
| 683 | |||
| 684 | /* Now we can parse parent's compatible IDs to find out the device bus type */ | ||
| 685 | for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) { | ||
| 686 | /* Normalize to upper case */ | ||
| 687 | hid_internal_towupper(compatible_id); | ||
| 688 | |||
| 689 | /* USB devices | ||
| 690 | https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support | ||
| 691 | https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers */ | ||
| 692 | if (wcsstr(compatible_id, L"USB") != NULL) { | ||
| 693 | bus_type = HID_API_BUS_USB; | ||
| 694 | break; | ||
| 695 | } | ||
| 696 | |||
| 697 | /* Bluetooth devices | ||
| 698 | https://docs.microsoft.com/windows-hardware/drivers/bluetooth/installing-a-bluetooth-device */ | ||
| 699 | if (wcsstr(compatible_id, L"BTHENUM") != NULL) { | ||
| 700 | bus_type = HID_API_BUS_BLUETOOTH; | ||
| 701 | break; | ||
| 702 | } | ||
| 703 | |||
| 704 | /* Bluetooth LE devices */ | ||
| 705 | if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) { | ||
| 706 | bus_type = HID_API_BUS_BLUETOOTH; | ||
| 707 | break; | ||
| 708 | } | ||
| 709 | |||
| 710 | /* I2C devices | ||
| 711 | https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support-and-power-management */ | ||
| 712 | if (wcsstr(compatible_id, L"PNP0C50") != NULL) { | ||
| 713 | bus_type = HID_API_BUS_I2C; | ||
| 714 | break; | ||
| 715 | } | ||
| 716 | |||
| 717 | /* SPI devices | ||
| 718 | https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-for-spi */ | ||
| 719 | if (wcsstr(compatible_id, L"PNP0C51") != NULL) { | ||
| 720 | bus_type = HID_API_BUS_SPI; | ||
| 721 | break; | ||
| 722 | } | ||
| 723 | } | ||
| 724 | end: | ||
| 725 | free(device_id); | ||
| 726 | free(compatible_ids); | ||
| 727 | return bus_type; | ||
| 728 | } | ||
| 729 | #endif /* HIDAPI_IGNORE_DEVICE */ | ||
| 730 | |||
| 731 | /* Unfortunately, HID_API_BUS_xxx constants alone aren't enough to distinguish between BLUETOOTH and BLE */ | ||
| 732 | |||
| 733 | #define HID_API_BUS_FLAG_BLE 0x01 | ||
| 734 | |||
| 735 | typedef struct hid_internal_detect_bus_type_result_ { | ||
| 736 | DEVINST dev_node; | ||
| 737 | hid_bus_type bus_type; | ||
| 738 | unsigned int bus_flags; | ||
| 739 | } hid_internal_detect_bus_type_result; | ||
| 740 | |||
| 741 | static hid_internal_detect_bus_type_result hid_internal_detect_bus_type(const wchar_t* interface_path) | ||
| 742 | { | ||
| 743 | wchar_t *device_id = NULL, *compatible_ids = NULL; | ||
| 744 | CONFIGRET cr; | ||
| 745 | DEVINST dev_node; | ||
| 746 | hid_internal_detect_bus_type_result result = { 0 }; | ||
| 747 | |||
| 748 | /* Get the device id from interface path */ | ||
| 749 | device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); | ||
| 750 | if (!device_id) | ||
| 751 | goto end; | ||
| 752 | |||
| 753 | /* Open devnode from device id */ | ||
| 754 | cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL); | ||
| 755 | if (cr != CR_SUCCESS) | ||
| 756 | goto end; | ||
| 757 | |||
| 758 | /* Get devnode parent */ | ||
| 759 | cr = CM_Get_Parent(&dev_node, dev_node, 0); | ||
| 760 | if (cr != CR_SUCCESS) | ||
| 761 | goto end; | ||
| 762 | |||
| 763 | /* Get the compatible ids from parent devnode */ | ||
| 764 | compatible_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_CompatibleIds, DEVPROP_TYPE_STRING_LIST); | ||
| 765 | if (!compatible_ids) | ||
| 766 | goto end; | ||
| 767 | |||
| 768 | /* Now we can parse parent's compatible IDs to find out the device bus type */ | ||
| 769 | for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) { | ||
| 770 | /* Normalize to upper case */ | ||
| 771 | hid_internal_towupper(compatible_id); | ||
| 772 | |||
| 773 | /* USB devices | ||
| 774 | https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support | ||
| 775 | https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers */ | ||
| 776 | if (wcsstr(compatible_id, L"USB") != NULL) { | ||
| 777 | result.bus_type = HID_API_BUS_USB; | ||
| 778 | break; | ||
| 779 | } | ||
| 780 | |||
| 781 | /* Bluetooth devices | ||
| 782 | https://docs.microsoft.com/windows-hardware/drivers/bluetooth/installing-a-bluetooth-device */ | ||
| 783 | if (wcsstr(compatible_id, L"BTHENUM") != NULL) { | ||
| 784 | result.bus_type = HID_API_BUS_BLUETOOTH; | ||
| 785 | break; | ||
| 786 | } | ||
| 787 | |||
| 788 | /* Bluetooth LE devices */ | ||
| 789 | if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) { | ||
| 790 | result.bus_type = HID_API_BUS_BLUETOOTH; | ||
| 791 | result.bus_flags |= HID_API_BUS_FLAG_BLE; | ||
| 792 | break; | ||
| 793 | } | ||
| 794 | |||
| 795 | /* I2C devices | ||
| 796 | https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support-and-power-management */ | ||
| 797 | if (wcsstr(compatible_id, L"PNP0C50") != NULL) { | ||
| 798 | result.bus_type = HID_API_BUS_I2C; | ||
| 799 | break; | ||
| 800 | } | ||
| 801 | |||
| 802 | /* SPI devices | ||
| 803 | https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-for-spi */ | ||
| 804 | if (wcsstr(compatible_id, L"PNP0C51") != NULL) { | ||
| 805 | result.bus_type = HID_API_BUS_SPI; | ||
| 806 | break; | ||
| 807 | } | ||
| 808 | } | ||
| 809 | |||
| 810 | result.dev_node = dev_node; | ||
| 811 | |||
| 812 | end: | ||
| 813 | free(device_id); | ||
| 814 | free(compatible_ids); | ||
| 815 | return result; | ||
| 816 | } | ||
| 817 | |||
| 818 | static char *hid_internal_UTF16toUTF8(const wchar_t *src) | ||
| 819 | { | ||
| 820 | char *dst = NULL; | ||
| 821 | #ifdef HIDAPI_USING_SDL_RUNTIME | ||
| 822 | int len = WIN_WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); | ||
| 823 | #else | ||
| 824 | int len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); | ||
| 825 | #endif | ||
| 826 | if (len) { | ||
| 827 | dst = (char*)calloc(len, sizeof(char)); | ||
| 828 | if (dst == NULL) { | ||
| 829 | return NULL; | ||
| 830 | } | ||
| 831 | #ifdef HIDAPI_USING_SDL_RUNTIME | ||
| 832 | WIN_WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dst, len, NULL, NULL); | ||
| 833 | #else | ||
| 834 | WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dst, len, NULL, NULL); | ||
| 835 | #endif | ||
| 836 | } | ||
| 837 | |||
| 838 | return dst; | ||
| 839 | } | ||
| 840 | |||
| 841 | static wchar_t *hid_internal_UTF8toUTF16(const char *src) | ||
| 842 | { | ||
| 843 | wchar_t *dst = NULL; | ||
| 844 | int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); | ||
| 845 | if (len) { | ||
| 846 | dst = (wchar_t*)calloc(len, sizeof(wchar_t)); | ||
| 847 | if (dst == NULL) { | ||
| 848 | return NULL; | ||
| 849 | } | ||
| 850 | MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dst, len); | ||
| 851 | } | ||
| 852 | |||
| 853 | return dst; | ||
| 854 | } | ||
| 855 | |||
| 856 | static struct hid_device_info *hid_internal_get_device_info(const wchar_t *path, HANDLE handle) | ||
| 857 | { | ||
| 858 | struct hid_device_info *dev = NULL; /* return object */ | ||
| 859 | HIDD_ATTRIBUTES attrib; | ||
| 860 | PHIDP_PREPARSED_DATA pp_data = NULL; | ||
| 861 | HIDP_CAPS caps; | ||
| 862 | wchar_t string[MAX_STRING_WCHARS + 1]; | ||
| 863 | ULONG len; | ||
| 864 | ULONG size; | ||
| 865 | hid_internal_detect_bus_type_result detect_bus_type_result; | ||
| 866 | |||
| 867 | /* Create the record. */ | ||
| 868 | dev = (struct hid_device_info*)calloc(1, sizeof(struct hid_device_info)); | ||
| 869 | |||
| 870 | if (dev == NULL) { | ||
| 871 | return NULL; | ||
| 872 | } | ||
| 873 | |||
| 874 | /* Fill out the record */ | ||
| 875 | dev->next = NULL; | ||
| 876 | dev->path = hid_internal_UTF16toUTF8(path); | ||
| 877 | dev->interface_number = -1; | ||
| 878 | |||
| 879 | attrib.Size = sizeof(HIDD_ATTRIBUTES); | ||
| 880 | if (HidD_GetAttributes(handle, &attrib)) { | ||
| 881 | /* VID/PID */ | ||
| 882 | dev->vendor_id = attrib.VendorID; | ||
| 883 | dev->product_id = attrib.ProductID; | ||
| 884 | |||
| 885 | /* Release Number */ | ||
| 886 | dev->release_number = attrib.VersionNumber; | ||
| 887 | } | ||
| 888 | |||
| 889 | /* Get the Usage Page and Usage for this device. */ | ||
| 890 | if (HidD_GetPreparsedData(handle, &pp_data)) { | ||
| 891 | if (HidP_GetCaps(pp_data, &caps) == HIDP_STATUS_SUCCESS) { | ||
| 892 | dev->usage_page = caps.UsagePage; | ||
| 893 | dev->usage = caps.Usage; | ||
| 894 | } | ||
| 895 | |||
| 896 | HidD_FreePreparsedData(pp_data); | ||
| 897 | } | ||
| 898 | |||
| 899 | /* detect bus type before reading string descriptors */ | ||
| 900 | detect_bus_type_result = hid_internal_detect_bus_type(path); | ||
| 901 | dev->bus_type = detect_bus_type_result.bus_type; | ||
| 902 | |||
| 903 | len = dev->bus_type == HID_API_BUS_USB ? MAX_STRING_WCHARS_USB : MAX_STRING_WCHARS; | ||
| 904 | string[len] = L'\0'; | ||
| 905 | size = len * sizeof(wchar_t); | ||
| 906 | |||
| 907 | /* Serial Number */ | ||
| 908 | string[0] = L'\0'; | ||
| 909 | HidD_GetSerialNumberString(handle, string, size); | ||
| 910 | dev->serial_number = _wcsdup(string); | ||
| 911 | |||
| 912 | /* Manufacturer String */ | ||
| 913 | string[0] = L'\0'; | ||
| 914 | HidD_GetManufacturerString(handle, string, size); | ||
| 915 | dev->manufacturer_string = _wcsdup(string); | ||
| 916 | |||
| 917 | /* Product String */ | ||
| 918 | string[0] = L'\0'; | ||
| 919 | HidD_GetProductString(handle, string, size); | ||
| 920 | dev->product_string = _wcsdup(string); | ||
| 921 | |||
| 922 | /* now, the portion that depends on string descriptors */ | ||
| 923 | switch (dev->bus_type) { | ||
| 924 | case HID_API_BUS_USB: | ||
| 925 | hid_internal_get_usb_info(dev, detect_bus_type_result.dev_node); | ||
| 926 | break; | ||
| 927 | |||
| 928 | case HID_API_BUS_BLUETOOTH: | ||
| 929 | if (detect_bus_type_result.bus_flags & HID_API_BUS_FLAG_BLE) | ||
| 930 | hid_internal_get_ble_info(dev, detect_bus_type_result.dev_node); | ||
| 931 | break; | ||
| 932 | |||
| 933 | case HID_API_BUS_UNKNOWN: | ||
| 934 | case HID_API_BUS_SPI: | ||
| 935 | case HID_API_BUS_I2C: | ||
| 936 | /* shut down -Wswitch */ | ||
| 937 | break; | ||
| 938 | } | ||
| 939 | |||
| 940 | return dev; | ||
| 941 | } | ||
| 942 | |||
| 943 | static int hid_blacklist(unsigned short vendor_id, unsigned short product_id) | ||
| 944 | { | ||
| 945 | size_t i; | ||
| 946 | static const struct { unsigned short vid; unsigned short pid; } known_bad[] = { | ||
| 947 | { 0x045E, 0x0822 }, /* Microsoft Precision Mouse - causes deadlock asking for device details */ | ||
| 948 | { 0x0738, 0x2217 }, /* SPEEDLINK COMPETITION PRO - turns into an Android controller when enumerated */ | ||
| 949 | { 0x0D8C, 0x0014 }, /* Sharkoon Skiller SGH2 headset - causes deadlock asking for device details */ | ||
| 950 | { 0x1532, 0x0109 }, /* Razer Lycosa Gaming keyboard - causes deadlock asking for device details */ | ||
| 951 | { 0x1532, 0x010B }, /* Razer Arctosa Gaming keyboard - causes deadlock asking for device details */ | ||
| 952 | { 0x1B1C, 0x1B3D }, /* Corsair Gaming keyboard - causes deadlock asking for device details */ | ||
| 953 | { 0x1CCF, 0x0000 } /* All Konami Amusement Devices - causes deadlock asking for device details */ | ||
| 954 | }; | ||
| 955 | |||
| 956 | for (i = 0; i < (sizeof(known_bad)/sizeof(known_bad[0])); i++) { | ||
| 957 | if ((vendor_id == known_bad[i].vid) && (product_id == known_bad[i].pid || known_bad[i].pid == 0x0000)) { | ||
| 958 | return 1; | ||
| 959 | } | ||
| 960 | } | ||
| 961 | |||
| 962 | return 0; | ||
| 963 | } | ||
| 964 | |||
| 965 | struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) | ||
| 966 | { | ||
| 967 | struct hid_device_info *root = NULL; /* return object */ | ||
| 968 | struct hid_device_info *cur_dev = NULL; | ||
| 969 | GUID interface_class_guid; | ||
| 970 | CONFIGRET cr; | ||
| 971 | wchar_t* device_interface_list = NULL; | ||
| 972 | DWORD len; | ||
| 973 | |||
| 974 | if (hid_init() < 0) { | ||
| 975 | /* register_global_error: global error is reset by hid_init */ | ||
| 976 | return NULL; | ||
| 977 | } | ||
| 978 | |||
| 979 | /* Retrieve HID Interface Class GUID | ||
| 980 | https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */ | ||
| 981 | HidD_GetHidGuid(&interface_class_guid); | ||
| 982 | |||
| 983 | /* Get the list of all device interfaces belonging to the HID class. */ | ||
| 984 | /* Retry in case of list was changed between calls to | ||
| 985 | CM_Get_Device_Interface_List_SizeW and CM_Get_Device_Interface_ListW */ | ||
| 986 | do { | ||
| 987 | cr = CM_Get_Device_Interface_List_SizeW(&len, &interface_class_guid, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); | ||
| 988 | if (cr != CR_SUCCESS) { | ||
| 989 | register_global_error(L"Failed to get size of HID device interface list"); | ||
| 990 | break; | ||
| 991 | } | ||
| 992 | |||
| 993 | if (device_interface_list != NULL) { | ||
| 994 | free(device_interface_list); | ||
| 995 | } | ||
| 996 | |||
| 997 | device_interface_list = (wchar_t*)calloc(len, sizeof(wchar_t)); | ||
| 998 | if (device_interface_list == NULL) { | ||
| 999 | register_global_error(L"Failed to allocate memory for HID device interface list"); | ||
| 1000 | return NULL; | ||
| 1001 | } | ||
| 1002 | cr = CM_Get_Device_Interface_ListW(&interface_class_guid, NULL, device_interface_list, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); | ||
| 1003 | if (cr != CR_SUCCESS && cr != CR_BUFFER_SMALL) { | ||
| 1004 | register_global_error(L"Failed to get HID device interface list"); | ||
| 1005 | } | ||
| 1006 | } while (cr == CR_BUFFER_SMALL); | ||
| 1007 | |||
| 1008 | if (cr != CR_SUCCESS) { | ||
| 1009 | goto end_of_function; | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | /* Iterate over each device interface in the HID class, looking for the right one. */ | ||
| 1013 | for (wchar_t* device_interface = device_interface_list; *device_interface; device_interface += wcslen(device_interface) + 1) { | ||
| 1014 | HANDLE device_handle = INVALID_HANDLE_VALUE; | ||
| 1015 | HIDD_ATTRIBUTES attrib; | ||
| 1016 | |||
| 1017 | /* XInput devices don't get real HID reports and are better handled by the raw input driver */ | ||
| 1018 | if (wcsstr(device_interface, L"&IG_") != NULL) { | ||
| 1019 | continue; | ||
| 1020 | } | ||
| 1021 | |||
| 1022 | /* Open read-only handle to the device */ | ||
| 1023 | device_handle = open_device(device_interface, FALSE); | ||
| 1024 | |||
| 1025 | /* Check validity of device_handle. */ | ||
| 1026 | if (device_handle == INVALID_HANDLE_VALUE) { | ||
| 1027 | /* Unable to open the device. */ | ||
| 1028 | continue; | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | /* Get the Vendor ID and Product ID for this device. */ | ||
| 1032 | attrib.Size = sizeof(HIDD_ATTRIBUTES); | ||
| 1033 | if (!HidD_GetAttributes(device_handle, &attrib)) { | ||
| 1034 | goto cont_close; | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | #ifdef HIDAPI_IGNORE_DEVICE | ||
| 1038 | /* See if there are any devices we should skip in enumeration */ | ||
| 1039 | hid_bus_type bus_type = get_bus_type(device_interface); | ||
| 1040 | PHIDP_PREPARSED_DATA pp_data = NULL; | ||
| 1041 | HIDP_CAPS caps = { 0 }; | ||
| 1042 | if (HidD_GetPreparsedData(device_handle, &pp_data)) { | ||
| 1043 | HidP_GetCaps(pp_data, &caps); | ||
| 1044 | HidD_FreePreparsedData(pp_data); | ||
| 1045 | } | ||
| 1046 | if (HIDAPI_IGNORE_DEVICE(bus_type, attrib.VendorID, attrib.ProductID, caps.UsagePage, caps.Usage)) { | ||
| 1047 | goto cont_close; | ||
| 1048 | } | ||
| 1049 | #endif | ||
| 1050 | |||
| 1051 | /* Check the VID/PID to see if we should add this | ||
| 1052 | device to the enumeration list. */ | ||
| 1053 | if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) && | ||
| 1054 | (product_id == 0x0 || attrib.ProductID == product_id) && | ||
| 1055 | !hid_blacklist(attrib.VendorID, attrib.ProductID)) { | ||
| 1056 | |||
| 1057 | /* VID/PID match. Create the record. */ | ||
| 1058 | struct hid_device_info *tmp = hid_internal_get_device_info(device_interface, device_handle); | ||
| 1059 | |||
| 1060 | if (tmp == NULL) { | ||
| 1061 | goto cont_close; | ||
| 1062 | } | ||
| 1063 | |||
| 1064 | if (cur_dev) { | ||
| 1065 | cur_dev->next = tmp; | ||
| 1066 | } | ||
| 1067 | else { | ||
| 1068 | root = tmp; | ||
| 1069 | } | ||
| 1070 | cur_dev = tmp; | ||
| 1071 | } | ||
| 1072 | |||
| 1073 | cont_close: | ||
| 1074 | CloseHandle(device_handle); | ||
| 1075 | } | ||
| 1076 | |||
| 1077 | if (root == NULL) { | ||
| 1078 | if (vendor_id == 0 && product_id == 0) { | ||
| 1079 | register_global_error(L"No HID devices found in the system."); | ||
| 1080 | } else { | ||
| 1081 | register_global_error(L"No HID devices with requested VID/PID found in the system."); | ||
| 1082 | } | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | end_of_function: | ||
| 1086 | free(device_interface_list); | ||
| 1087 | |||
| 1088 | return root; | ||
| 1089 | } | ||
| 1090 | |||
| 1091 | void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) | ||
| 1092 | { | ||
| 1093 | /* TODO: Merge this with the Linux version. This function is platform-independent. */ | ||
| 1094 | struct hid_device_info *d = devs; | ||
| 1095 | while (d) { | ||
| 1096 | struct hid_device_info *next = d->next; | ||
| 1097 | free(d->path); | ||
| 1098 | free(d->serial_number); | ||
| 1099 | free(d->manufacturer_string); | ||
| 1100 | free(d->product_string); | ||
| 1101 | free(d); | ||
| 1102 | d = next; | ||
| 1103 | } | ||
| 1104 | } | ||
| 1105 | |||
| 1106 | HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) | ||
| 1107 | { | ||
| 1108 | /* TODO: Merge this functions with the Linux version. This function should be platform independent. */ | ||
| 1109 | struct hid_device_info *devs, *cur_dev; | ||
| 1110 | const char *path_to_open = NULL; | ||
| 1111 | hid_device *handle = NULL; | ||
| 1112 | |||
| 1113 | /* register_global_error: global error is reset by hid_enumerate/hid_init */ | ||
| 1114 | devs = hid_enumerate(vendor_id, product_id); | ||
| 1115 | if (!devs) { | ||
| 1116 | /* register_global_error: global error is already set by hid_enumerate */ | ||
| 1117 | return NULL; | ||
| 1118 | } | ||
| 1119 | |||
| 1120 | cur_dev = devs; | ||
| 1121 | while (cur_dev) { | ||
| 1122 | if (cur_dev->vendor_id == vendor_id && | ||
| 1123 | cur_dev->product_id == product_id) { | ||
| 1124 | if (serial_number) { | ||
| 1125 | if (cur_dev->serial_number && wcscmp(serial_number, cur_dev->serial_number) == 0) { | ||
| 1126 | path_to_open = cur_dev->path; | ||
| 1127 | break; | ||
| 1128 | } | ||
| 1129 | } | ||
| 1130 | else { | ||
| 1131 | path_to_open = cur_dev->path; | ||
| 1132 | break; | ||
| 1133 | } | ||
| 1134 | } | ||
| 1135 | cur_dev = cur_dev->next; | ||
| 1136 | } | ||
| 1137 | |||
| 1138 | if (path_to_open) { | ||
| 1139 | /* Open the device */ | ||
| 1140 | handle = hid_open_path(path_to_open); | ||
| 1141 | } else { | ||
| 1142 | register_global_error(L"Device with requested VID/PID/(SerialNumber) not found"); | ||
| 1143 | } | ||
| 1144 | |||
| 1145 | hid_free_enumeration(devs); | ||
| 1146 | |||
| 1147 | return handle; | ||
| 1148 | } | ||
| 1149 | |||
| 1150 | HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) | ||
| 1151 | { | ||
| 1152 | hid_device *dev = NULL; | ||
| 1153 | wchar_t* interface_path = NULL; | ||
| 1154 | HANDLE device_handle = INVALID_HANDLE_VALUE; | ||
| 1155 | PHIDP_PREPARSED_DATA pp_data = NULL; | ||
| 1156 | HIDP_CAPS caps; | ||
| 1157 | |||
| 1158 | if (hid_init() < 0) { | ||
| 1159 | /* register_global_error: global error is reset by hid_init */ | ||
| 1160 | goto end_of_function; | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | interface_path = hid_internal_UTF8toUTF16(path); | ||
| 1164 | if (!interface_path) { | ||
| 1165 | register_global_error(L"Path conversion failure"); | ||
| 1166 | goto end_of_function; | ||
| 1167 | } | ||
| 1168 | |||
| 1169 | /* Open a handle to the device */ | ||
| 1170 | device_handle = open_device(interface_path, TRUE); | ||
| 1171 | |||
| 1172 | /* Check validity of write_handle. */ | ||
| 1173 | if (device_handle == INVALID_HANDLE_VALUE) { | ||
| 1174 | /* System devices, such as keyboards and mice, cannot be opened in | ||
| 1175 | read-write mode, because the system takes exclusive control over | ||
| 1176 | them. This is to prevent keyloggers. However, feature reports | ||
| 1177 | can still be sent and received. Retry opening the device, but | ||
| 1178 | without read/write access. */ | ||
| 1179 | device_handle = open_device(interface_path, FALSE); | ||
| 1180 | |||
| 1181 | /* Check the validity of the limited device_handle. */ | ||
| 1182 | if (device_handle == INVALID_HANDLE_VALUE) { | ||
| 1183 | register_global_winapi_error(L"open_device"); | ||
| 1184 | goto end_of_function; | ||
| 1185 | } | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | /* Set the Input Report buffer size to 64 reports. */ | ||
| 1189 | if (!HidD_SetNumInputBuffers(device_handle, 64)) { | ||
| 1190 | register_global_winapi_error(L"set input buffers"); | ||
| 1191 | goto end_of_function; | ||
| 1192 | } | ||
| 1193 | |||
| 1194 | /* Get the Input Report length for the device. */ | ||
| 1195 | if (!HidD_GetPreparsedData(device_handle, &pp_data)) { | ||
| 1196 | register_global_winapi_error(L"get preparsed data"); | ||
| 1197 | goto end_of_function; | ||
| 1198 | } | ||
| 1199 | |||
| 1200 | if (HidP_GetCaps(pp_data, &caps) != HIDP_STATUS_SUCCESS) { | ||
| 1201 | register_global_error(L"HidP_GetCaps"); | ||
| 1202 | goto end_of_function; | ||
| 1203 | } | ||
| 1204 | |||
| 1205 | dev = new_hid_device(); | ||
| 1206 | |||
| 1207 | if (dev == NULL) { | ||
| 1208 | register_global_error(L"hid_device allocation error"); | ||
| 1209 | goto end_of_function; | ||
| 1210 | } | ||
| 1211 | |||
| 1212 | dev->device_handle = device_handle; | ||
| 1213 | device_handle = INVALID_HANDLE_VALUE; | ||
| 1214 | |||
| 1215 | dev->output_report_length = caps.OutputReportByteLength; | ||
| 1216 | dev->input_report_length = caps.InputReportByteLength; | ||
| 1217 | dev->feature_report_length = caps.FeatureReportByteLength; | ||
| 1218 | dev->read_buf = (char*) malloc(dev->input_report_length); | ||
| 1219 | dev->device_info = hid_internal_get_device_info(interface_path, dev->device_handle); | ||
| 1220 | |||
| 1221 | /* On Windows 7, we need to use hid_write_output_report() over Bluetooth */ | ||
| 1222 | if (dev->output_report_length > 512) { | ||
| 1223 | dev->use_hid_write_output_report = !IsWindowsVersionOrGreater( HIBYTE( _WIN32_WINNT_WIN8 ), LOBYTE( _WIN32_WINNT_WIN8 ), 0 ); | ||
| 1224 | } | ||
| 1225 | |||
| 1226 | end_of_function: | ||
| 1227 | free(interface_path); | ||
| 1228 | CloseHandle(device_handle); | ||
| 1229 | |||
| 1230 | if (pp_data) { | ||
| 1231 | HidD_FreePreparsedData(pp_data); | ||
| 1232 | } | ||
| 1233 | |||
| 1234 | return dev; | ||
| 1235 | } | ||
| 1236 | |||
| 1237 | static int hid_write_output_report(hid_device *dev, const unsigned char *data, size_t length) | ||
| 1238 | { | ||
| 1239 | BOOL res; | ||
| 1240 | res = HidD_SetOutputReport(dev->device_handle, (void *)data, (ULONG)length); | ||
| 1241 | if (res) | ||
| 1242 | return (int)length; | ||
| 1243 | else | ||
| 1244 | return -1; | ||
| 1245 | } | ||
| 1246 | |||
| 1247 | int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length) | ||
| 1248 | { | ||
| 1249 | DWORD bytes_written = 0; | ||
| 1250 | int function_result = -1; | ||
| 1251 | BOOL res; | ||
| 1252 | BOOL overlapped = FALSE; | ||
| 1253 | |||
| 1254 | unsigned char *buf; | ||
| 1255 | |||
| 1256 | if (!data || !length) { | ||
| 1257 | register_string_error(dev, L"Zero buffer/length"); | ||
| 1258 | return function_result; | ||
| 1259 | } | ||
| 1260 | |||
| 1261 | register_string_error(dev, NULL); | ||
| 1262 | |||
| 1263 | if (dev->use_hid_write_output_report) { | ||
| 1264 | return hid_write_output_report(dev, data, length); | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | /* Make sure the right number of bytes are passed to WriteFile. Windows | ||
| 1268 | expects the number of bytes which are in the _longest_ report (plus | ||
| 1269 | one for the report number) bytes even if the data is a report | ||
| 1270 | which is shorter than that. Windows gives us this value in | ||
| 1271 | caps.OutputReportByteLength. If a user passes in fewer bytes than this, | ||
| 1272 | use cached temporary buffer which is the proper size. */ | ||
| 1273 | if (length >= dev->output_report_length) { | ||
| 1274 | /* The user passed the right number of bytes. Use the buffer as-is. */ | ||
| 1275 | buf = (unsigned char *) data; | ||
| 1276 | } else { | ||
| 1277 | if (dev->write_buf == NULL) | ||
| 1278 | dev->write_buf = (unsigned char *) malloc(dev->output_report_length); | ||
| 1279 | buf = dev->write_buf; | ||
| 1280 | memcpy(buf, data, length); | ||
| 1281 | memset(buf + length, 0, dev->output_report_length - length); | ||
| 1282 | length = dev->output_report_length; | ||
| 1283 | } | ||
| 1284 | |||
| 1285 | res = WriteFile(dev->device_handle, buf, (DWORD) length, &bytes_written, &dev->write_ol); | ||
| 1286 | |||
| 1287 | if (!res) { | ||
| 1288 | if (GetLastError() != ERROR_IO_PENDING) { | ||
| 1289 | /* WriteFile() failed. Return error. */ | ||
| 1290 | register_winapi_error(dev, L"WriteFile"); | ||
| 1291 | goto end_of_function; | ||
| 1292 | } | ||
| 1293 | overlapped = TRUE; | ||
| 1294 | } else { | ||
| 1295 | /* WriteFile() succeeded synchronously. */ | ||
| 1296 | function_result = bytes_written; | ||
| 1297 | } | ||
| 1298 | |||
| 1299 | if (overlapped) { | ||
| 1300 | /* Wait for the transaction to complete. This makes | ||
| 1301 | hid_write() synchronous. */ | ||
| 1302 | res = WaitForSingleObject(dev->write_ol.hEvent, 1000); | ||
| 1303 | if (res != WAIT_OBJECT_0) { | ||
| 1304 | /* There was a Timeout. */ | ||
| 1305 | register_winapi_error(dev, L"hid_write/WaitForSingleObject"); | ||
| 1306 | goto end_of_function; | ||
| 1307 | } | ||
| 1308 | |||
| 1309 | /* Get the result. */ | ||
| 1310 | res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*wait*/); | ||
| 1311 | if (res) { | ||
| 1312 | function_result = bytes_written; | ||
| 1313 | } | ||
| 1314 | else { | ||
| 1315 | /* The Write operation failed. */ | ||
| 1316 | register_winapi_error(dev, L"hid_write/GetOverlappedResult"); | ||
| 1317 | goto end_of_function; | ||
| 1318 | } | ||
| 1319 | } | ||
| 1320 | |||
| 1321 | end_of_function: | ||
| 1322 | return function_result; | ||
| 1323 | } | ||
| 1324 | |||
| 1325 | |||
| 1326 | int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) | ||
| 1327 | { | ||
| 1328 | DWORD bytes_read = 0; | ||
| 1329 | size_t copy_len = 0; | ||
| 1330 | BOOL res = FALSE; | ||
| 1331 | BOOL overlapped = FALSE; | ||
| 1332 | |||
| 1333 | if (!data || !length) { | ||
| 1334 | register_string_error(dev, L"Zero buffer/length"); | ||
| 1335 | return -1; | ||
| 1336 | } | ||
| 1337 | |||
| 1338 | register_string_error(dev, NULL); | ||
| 1339 | |||
| 1340 | /* Copy the handle for convenience. */ | ||
| 1341 | HANDLE ev = dev->ol.hEvent; | ||
| 1342 | |||
| 1343 | if (!dev->read_pending) { | ||
| 1344 | /* Start an Overlapped I/O read. */ | ||
| 1345 | dev->read_pending = TRUE; | ||
| 1346 | memset(dev->read_buf, 0, dev->input_report_length); | ||
| 1347 | ResetEvent(ev); | ||
| 1348 | res = ReadFile(dev->device_handle, dev->read_buf, (DWORD) dev->input_report_length, &bytes_read, &dev->ol); | ||
| 1349 | |||
| 1350 | if (!res) { | ||
| 1351 | if (GetLastError() != ERROR_IO_PENDING) { | ||
| 1352 | /* ReadFile() has failed. | ||
| 1353 | Clean up and return error. */ | ||
| 1354 | register_winapi_error(dev, L"ReadFile"); | ||
| 1355 | CancelIo(dev->device_handle); | ||
| 1356 | dev->read_pending = FALSE; | ||
| 1357 | goto end_of_function; | ||
| 1358 | } | ||
| 1359 | overlapped = TRUE; | ||
| 1360 | } | ||
| 1361 | } | ||
| 1362 | else { | ||
| 1363 | overlapped = TRUE; | ||
| 1364 | } | ||
| 1365 | |||
| 1366 | if (overlapped) { | ||
| 1367 | /* See if there is any data yet. */ | ||
| 1368 | res = WaitForSingleObject(ev, milliseconds >= 0 ? (DWORD)milliseconds : INFINITE); | ||
| 1369 | if (res != WAIT_OBJECT_0) { | ||
| 1370 | /* There was no data this time. Return zero bytes available, | ||
| 1371 | but leave the Overlapped I/O running. */ | ||
| 1372 | return 0; | ||
| 1373 | } | ||
| 1374 | |||
| 1375 | /* Get the number of bytes read. The actual data has been copied to the data[] | ||
| 1376 | array which was passed to ReadFile(). We must not wait here because we've | ||
| 1377 | already waited on our event above, and since it's auto-reset, it will have | ||
| 1378 | been reset back to unsignalled by now. */ | ||
| 1379 | res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, FALSE/*don't wait now - already did on the prev step*/); | ||
| 1380 | } | ||
| 1381 | /* Set pending back to false, even if GetOverlappedResult() returned error. */ | ||
| 1382 | dev->read_pending = FALSE; | ||
| 1383 | |||
| 1384 | if (res && bytes_read > 0) { | ||
| 1385 | if (dev->read_buf[0] == 0x0) { | ||
| 1386 | /* If report numbers aren't being used, but Windows sticks a report | ||
| 1387 | number (0x0) on the beginning of the report anyway. To make this | ||
| 1388 | work like the other platforms, and to make it work more like the | ||
| 1389 | HID spec, we'll skip over this byte. */ | ||
| 1390 | bytes_read--; | ||
| 1391 | copy_len = length > bytes_read ? bytes_read : length; | ||
| 1392 | memcpy(data, dev->read_buf+1, copy_len); | ||
| 1393 | } | ||
| 1394 | else { | ||
| 1395 | /* Copy the whole buffer, report number and all. */ | ||
| 1396 | copy_len = length > bytes_read ? bytes_read : length; | ||
| 1397 | memcpy(data, dev->read_buf, copy_len); | ||
| 1398 | } | ||
| 1399 | } | ||
| 1400 | if (!res) { | ||
| 1401 | register_winapi_error(dev, L"hid_read_timeout/GetOverlappedResult"); | ||
| 1402 | } | ||
| 1403 | |||
| 1404 | end_of_function: | ||
| 1405 | if (!res) { | ||
| 1406 | return -1; | ||
| 1407 | } | ||
| 1408 | |||
| 1409 | return (int) copy_len; | ||
| 1410 | } | ||
| 1411 | |||
| 1412 | int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length) | ||
| 1413 | { | ||
| 1414 | return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); | ||
| 1415 | } | ||
| 1416 | |||
| 1417 | int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) | ||
| 1418 | { | ||
| 1419 | dev->blocking = !nonblock; | ||
| 1420 | return 0; /* Success */ | ||
| 1421 | } | ||
| 1422 | |||
| 1423 | int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) | ||
| 1424 | { | ||
| 1425 | BOOL res = FALSE; | ||
| 1426 | unsigned char *buf; | ||
| 1427 | size_t length_to_send; | ||
| 1428 | |||
| 1429 | if (!data || !length) { | ||
| 1430 | register_string_error(dev, L"Zero buffer/length"); | ||
| 1431 | return -1; | ||
| 1432 | } | ||
| 1433 | |||
| 1434 | register_string_error(dev, NULL); | ||
| 1435 | |||
| 1436 | /* Windows expects at least caps.FeatureReportByteLength bytes passed | ||
| 1437 | to HidD_SetFeature(), even if the report is shorter. Any less sent and | ||
| 1438 | the function fails with error ERROR_INVALID_PARAMETER set. Any more | ||
| 1439 | and HidD_SetFeature() silently truncates the data sent in the report | ||
| 1440 | to caps.FeatureReportByteLength. */ | ||
| 1441 | if (length >= dev->feature_report_length) { | ||
| 1442 | buf = (unsigned char *) data; | ||
| 1443 | length_to_send = length; | ||
| 1444 | } else { | ||
| 1445 | if (dev->feature_buf == NULL) | ||
| 1446 | dev->feature_buf = (unsigned char *) malloc(dev->feature_report_length); | ||
| 1447 | buf = dev->feature_buf; | ||
| 1448 | memcpy(buf, data, length); | ||
| 1449 | memset(buf + length, 0, dev->feature_report_length - length); | ||
| 1450 | length_to_send = dev->feature_report_length; | ||
| 1451 | } | ||
| 1452 | |||
| 1453 | res = HidD_SetFeature(dev->device_handle, (PVOID)buf, (DWORD) length_to_send); | ||
| 1454 | |||
| 1455 | if (!res) { | ||
| 1456 | register_winapi_error(dev, L"HidD_SetFeature"); | ||
| 1457 | return -1; | ||
| 1458 | } | ||
| 1459 | |||
| 1460 | return (int) length; | ||
| 1461 | } | ||
| 1462 | |||
| 1463 | static int hid_get_report(hid_device *dev, DWORD report_type, unsigned char *data, size_t length) | ||
| 1464 | { | ||
| 1465 | BOOL res; | ||
| 1466 | DWORD bytes_returned = 0; | ||
| 1467 | |||
| 1468 | OVERLAPPED ol; | ||
| 1469 | memset(&ol, 0, sizeof(ol)); | ||
| 1470 | |||
| 1471 | if (!data || !length) { | ||
| 1472 | register_string_error(dev, L"Zero buffer/length"); | ||
| 1473 | return -1; | ||
| 1474 | } | ||
| 1475 | |||
| 1476 | register_string_error(dev, NULL); | ||
| 1477 | |||
| 1478 | res = DeviceIoControl(dev->device_handle, | ||
| 1479 | report_type, | ||
| 1480 | data, (DWORD) length, | ||
| 1481 | data, (DWORD) length, | ||
| 1482 | &bytes_returned, &ol); | ||
| 1483 | |||
| 1484 | if (!res) { | ||
| 1485 | if (GetLastError() != ERROR_IO_PENDING) { | ||
| 1486 | /* DeviceIoControl() failed. Return error. */ | ||
| 1487 | register_winapi_error(dev, L"Get Input/Feature Report DeviceIoControl"); | ||
| 1488 | return -1; | ||
| 1489 | } | ||
| 1490 | } | ||
| 1491 | |||
| 1492 | /* Wait here until the write is done. This makes | ||
| 1493 | hid_get_feature_report() synchronous. */ | ||
| 1494 | res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/); | ||
| 1495 | if (!res) { | ||
| 1496 | /* The operation failed. */ | ||
| 1497 | register_winapi_error(dev, L"Get Input/Feature Report GetOverLappedResult"); | ||
| 1498 | return -1; | ||
| 1499 | } | ||
| 1500 | |||
| 1501 | /* When numbered reports aren't used, | ||
| 1502 | bytes_returned seem to include only what is actually received from the device | ||
| 1503 | (not including the first byte with 0, as an indication "no numbered reports"). */ | ||
| 1504 | if (data[0] == 0x0) { | ||
| 1505 | bytes_returned++; | ||
| 1506 | } | ||
| 1507 | |||
| 1508 | return bytes_returned; | ||
| 1509 | } | ||
| 1510 | |||
| 1511 | int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) | ||
| 1512 | { | ||
| 1513 | /* We could use HidD_GetFeature() instead, but it doesn't give us an actual length, unfortunately */ | ||
| 1514 | return hid_get_report(dev, IOCTL_HID_GET_FEATURE, data, length); | ||
| 1515 | } | ||
| 1516 | |||
| 1517 | int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) | ||
| 1518 | { | ||
| 1519 | /* We could use HidD_GetInputReport() instead, but it doesn't give us an actual length, unfortunately */ | ||
| 1520 | return hid_get_report(dev, IOCTL_HID_GET_INPUT_REPORT, data, length); | ||
| 1521 | } | ||
| 1522 | |||
| 1523 | #if defined(__GNUC__) && __GNUC__ >= 8 | ||
| 1524 | # pragma GCC diagnostic push | ||
| 1525 | # pragma GCC diagnostic ignored "-Wcast-function-type" | ||
| 1526 | #endif | ||
| 1527 | void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) | ||
| 1528 | { | ||
| 1529 | typedef BOOL (WINAPI *CancelIoEx_t)(HANDLE hFile, LPOVERLAPPED lpOverlapped); | ||
| 1530 | CancelIoEx_t CancelIoExFunc = (CancelIoEx_t)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CancelIoEx"); | ||
| 1531 | |||
| 1532 | if (!dev) | ||
| 1533 | return; | ||
| 1534 | |||
| 1535 | if (CancelIoExFunc) { | ||
| 1536 | CancelIoExFunc(dev->device_handle, NULL); | ||
| 1537 | } else { | ||
| 1538 | /* Windows XP, this will only cancel I/O on the current thread */ | ||
| 1539 | CancelIo(dev->device_handle); | ||
| 1540 | } | ||
| 1541 | if (dev->read_pending) { | ||
| 1542 | DWORD bytes_read = 0; | ||
| 1543 | GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/); | ||
| 1544 | } | ||
| 1545 | free_hid_device(dev); | ||
| 1546 | } | ||
| 1547 | #if defined(__GNUC__) && __GNUC__ >= 8 | ||
| 1548 | # pragma GCC diagnostic pop | ||
| 1549 | #endif | ||
| 1550 | |||
| 1551 | int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) | ||
| 1552 | { | ||
| 1553 | if (!string || !maxlen) { | ||
| 1554 | register_string_error(dev, L"Zero buffer/length"); | ||
| 1555 | return -1; | ||
| 1556 | } | ||
| 1557 | |||
| 1558 | if (!dev->device_info) { | ||
| 1559 | register_string_error(dev, L"NULL device info"); | ||
| 1560 | return -1; | ||
| 1561 | } | ||
| 1562 | |||
| 1563 | wcsncpy(string, dev->device_info->manufacturer_string, maxlen); | ||
| 1564 | string[maxlen - 1] = L'\0'; | ||
| 1565 | |||
| 1566 | register_string_error(dev, NULL); | ||
| 1567 | |||
| 1568 | return 0; | ||
| 1569 | } | ||
| 1570 | |||
| 1571 | int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) | ||
| 1572 | { | ||
| 1573 | if (!string || !maxlen) { | ||
| 1574 | register_string_error(dev, L"Zero buffer/length"); | ||
| 1575 | return -1; | ||
| 1576 | } | ||
| 1577 | |||
| 1578 | if (!dev->device_info) { | ||
| 1579 | register_string_error(dev, L"NULL device info"); | ||
| 1580 | return -1; | ||
| 1581 | } | ||
| 1582 | |||
| 1583 | wcsncpy(string, dev->device_info->product_string, maxlen); | ||
| 1584 | string[maxlen - 1] = L'\0'; | ||
| 1585 | |||
| 1586 | register_string_error(dev, NULL); | ||
| 1587 | |||
| 1588 | return 0; | ||
| 1589 | } | ||
| 1590 | |||
| 1591 | int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) | ||
| 1592 | { | ||
| 1593 | if (!string || !maxlen) { | ||
| 1594 | register_string_error(dev, L"Zero buffer/length"); | ||
| 1595 | return -1; | ||
| 1596 | } | ||
| 1597 | |||
| 1598 | if (!dev->device_info) { | ||
| 1599 | register_string_error(dev, L"NULL device info"); | ||
| 1600 | return -1; | ||
| 1601 | } | ||
| 1602 | |||
| 1603 | wcsncpy(string, dev->device_info->serial_number, maxlen); | ||
| 1604 | string[maxlen - 1] = L'\0'; | ||
| 1605 | |||
| 1606 | register_string_error(dev, NULL); | ||
| 1607 | |||
| 1608 | return 0; | ||
| 1609 | } | ||
| 1610 | |||
| 1611 | HID_API_EXPORT struct hid_device_info * HID_API_CALL hid_get_device_info(hid_device *dev) { | ||
| 1612 | if (!dev->device_info) | ||
| 1613 | { | ||
| 1614 | register_string_error(dev, L"NULL device info"); | ||
| 1615 | return NULL; | ||
| 1616 | } | ||
| 1617 | |||
| 1618 | return dev->device_info; | ||
| 1619 | } | ||
| 1620 | |||
| 1621 | int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) | ||
| 1622 | { | ||
| 1623 | BOOL res; | ||
| 1624 | |||
| 1625 | if (dev->device_info && dev->device_info->bus_type == HID_API_BUS_USB && maxlen > MAX_STRING_WCHARS_USB) { | ||
| 1626 | string[MAX_STRING_WCHARS_USB] = L'\0'; | ||
| 1627 | maxlen = MAX_STRING_WCHARS_USB; | ||
| 1628 | } | ||
| 1629 | |||
| 1630 | res = HidD_GetIndexedString(dev->device_handle, string_index, string, (ULONG)maxlen * sizeof(wchar_t)); | ||
| 1631 | if (!res) { | ||
| 1632 | register_winapi_error(dev, L"HidD_GetIndexedString"); | ||
| 1633 | return -1; | ||
| 1634 | } | ||
| 1635 | |||
| 1636 | register_string_error(dev, NULL); | ||
| 1637 | |||
| 1638 | return 0; | ||
| 1639 | } | ||
| 1640 | |||
| 1641 | int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id) | ||
| 1642 | { | ||
| 1643 | wchar_t *interface_path = NULL, *device_id = NULL; | ||
| 1644 | CONFIGRET cr = CR_FAILURE; | ||
| 1645 | DEVINST dev_node; | ||
| 1646 | DEVPROPTYPE property_type; | ||
| 1647 | ULONG len; | ||
| 1648 | |||
| 1649 | if (!container_id) { | ||
| 1650 | register_string_error(dev, L"Invalid Container ID"); | ||
| 1651 | return -1; | ||
| 1652 | } | ||
| 1653 | |||
| 1654 | register_string_error(dev, NULL); | ||
| 1655 | |||
| 1656 | interface_path = hid_internal_UTF8toUTF16(dev->device_info->path); | ||
| 1657 | if (!interface_path) { | ||
| 1658 | register_string_error(dev, L"Path conversion failure"); | ||
| 1659 | goto end; | ||
| 1660 | } | ||
| 1661 | |||
| 1662 | /* Get the device id from interface path */ | ||
| 1663 | device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); | ||
| 1664 | if (!device_id) { | ||
| 1665 | register_string_error(dev, L"Failed to get device interface property InstanceId"); | ||
| 1666 | goto end; | ||
| 1667 | } | ||
| 1668 | |||
| 1669 | /* Open devnode from device id */ | ||
| 1670 | cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL); | ||
| 1671 | if (cr != CR_SUCCESS) { | ||
| 1672 | register_string_error(dev, L"Failed to locate device node"); | ||
| 1673 | goto end; | ||
| 1674 | } | ||
| 1675 | |||
| 1676 | /* Get the container id from devnode */ | ||
| 1677 | len = sizeof(*container_id); | ||
| 1678 | cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_ContainerId, &property_type, (PBYTE)container_id, &len, 0); | ||
| 1679 | if (cr == CR_SUCCESS && property_type != DEVPROP_TYPE_GUID) | ||
| 1680 | cr = CR_FAILURE; | ||
| 1681 | |||
| 1682 | if (cr != CR_SUCCESS) | ||
| 1683 | register_string_error(dev, L"Failed to read ContainerId property from device node"); | ||
| 1684 | |||
| 1685 | end: | ||
| 1686 | free(interface_path); | ||
| 1687 | free(device_id); | ||
| 1688 | |||
| 1689 | return cr == CR_SUCCESS ? 0 : -1; | ||
| 1690 | } | ||
| 1691 | |||
| 1692 | |||
| 1693 | int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) | ||
| 1694 | { | ||
| 1695 | PHIDP_PREPARSED_DATA pp_data = NULL; | ||
| 1696 | |||
| 1697 | if (!HidD_GetPreparsedData(dev->device_handle, &pp_data) || pp_data == NULL) { | ||
| 1698 | register_string_error(dev, L"HidD_GetPreparsedData"); | ||
| 1699 | return -1; | ||
| 1700 | } | ||
| 1701 | |||
| 1702 | int res = hid_winapi_descriptor_reconstruct_pp_data(pp_data, buf, buf_size); | ||
| 1703 | |||
| 1704 | HidD_FreePreparsedData(pp_data); | ||
| 1705 | |||
| 1706 | return res; | ||
| 1707 | } | ||
| 1708 | |||
| 1709 | HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) | ||
| 1710 | { | ||
| 1711 | if (dev) { | ||
| 1712 | if (dev->last_error_str == NULL) | ||
| 1713 | return L"Success"; | ||
| 1714 | return (wchar_t*)dev->last_error_str; | ||
| 1715 | } | ||
| 1716 | |||
| 1717 | if (last_global_error_str == NULL) | ||
| 1718 | return L"Success"; | ||
| 1719 | return last_global_error_str; | ||
| 1720 | } | ||
| 1721 | |||
| 1722 | #ifndef hidapi_winapi_EXPORTS | ||
| 1723 | #include "hidapi_descriptor_reconstruct.c" | ||
| 1724 | #endif | ||
| 1725 | |||
| 1726 | #ifdef __cplusplus | ||
| 1727 | } /* extern "C" */ | ||
| 1728 | #endif | ||
