1550 lines
44 KiB
C
Executable File
1550 lines
44 KiB
C
Executable File
/*******************************************************
|
|
HIDAPI - Multi-Platform library for
|
|
communication with HID devices.
|
|
|
|
Alan Ott
|
|
Signal 11 Software
|
|
|
|
libusb/hidapi Team
|
|
|
|
Copyright 2022, All Rights Reserved.
|
|
|
|
At the discretion of the user of this library,
|
|
this software may be licensed under the terms of the
|
|
GNU General Public License v3, a BSD-Style license, or the
|
|
original HIDAPI license as outlined in the LICENSE.txt,
|
|
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
|
|
files located at the root of the source distribution.
|
|
These files may also be found in the public source
|
|
code repository located at:
|
|
https://github.com/libusb/hidapi .
|
|
********************************************************/
|
|
|
|
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
|
/* Do not warn about wcsncpy usage.
|
|
https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt */
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#include "hidapi_winapi.h"
|
|
|
|
#include <windows.h>
|
|
|
|
#ifndef _NTDEF_
|
|
typedef LONG NTSTATUS;
|
|
#endif
|
|
|
|
#ifdef __MINGW32__
|
|
#include <ntdef.h>
|
|
#include <winbase.h>
|
|
#define WC_ERR_INVALID_CHARS 0x00000080
|
|
#endif
|
|
|
|
#ifdef __CYGWIN__
|
|
#include <ntdef.h>
|
|
#include <wctype.h>
|
|
#define _wcsdup wcsdup
|
|
#endif
|
|
|
|
/*#define HIDAPI_USE_DDK*/
|
|
|
|
#include "hidapi_cfgmgr32.h"
|
|
#include "hidapi_hidclass.h"
|
|
#include "hidapi_hidsdi.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef MIN
|
|
#undef MIN
|
|
#endif
|
|
#define MIN(x,y) ((x) < (y)? (x): (y))
|
|
|
|
/* MAXIMUM_USB_STRING_LENGTH from usbspec.h is 255 */
|
|
/* BLUETOOTH_DEVICE_NAME_SIZE from bluetoothapis.h is 256 */
|
|
#define MAX_STRING_WCHARS 256
|
|
|
|
/* For certain USB devices, using a buffer larger or equal to 127 wchars results
|
|
in successful completion of HID API functions, but a broken string is stored
|
|
in the output buffer. This behaviour persists even if HID API is bypassed and
|
|
HID IOCTLs are passed to the HID driver directly. Therefore, for USB devices,
|
|
the buffer MUST NOT exceed 126 WCHARs.
|
|
*/
|
|
|
|
#define MAX_STRING_WCHARS_USB 126
|
|
|
|
static struct hid_api_version api_version = {
|
|
.major = HID_API_VERSION_MAJOR,
|
|
.minor = HID_API_VERSION_MINOR,
|
|
.patch = HID_API_VERSION_PATCH
|
|
};
|
|
|
|
#ifndef HIDAPI_USE_DDK
|
|
/* Since we're not building with the DDK, and the HID header
|
|
files aren't part of the Windows SDK, we define what we need ourselves.
|
|
In lookup_functions(), the function pointers
|
|
defined below are set. */
|
|
|
|
static HidD_GetHidGuid_ HidD_GetHidGuid;
|
|
static HidD_GetAttributes_ HidD_GetAttributes;
|
|
static HidD_GetSerialNumberString_ HidD_GetSerialNumberString;
|
|
static HidD_GetManufacturerString_ HidD_GetManufacturerString;
|
|
static HidD_GetProductString_ HidD_GetProductString;
|
|
static HidD_SetFeature_ HidD_SetFeature;
|
|
static HidD_GetFeature_ HidD_GetFeature;
|
|
static HidD_SetOutputReport_ HidD_SetOutputReport;
|
|
static HidD_GetInputReport_ HidD_GetInputReport;
|
|
static HidD_GetIndexedString_ HidD_GetIndexedString;
|
|
static HidD_GetPreparsedData_ HidD_GetPreparsedData;
|
|
static HidD_FreePreparsedData_ HidD_FreePreparsedData;
|
|
static HidP_GetCaps_ HidP_GetCaps;
|
|
static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers;
|
|
|
|
static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL;
|
|
static CM_Get_Parent_ CM_Get_Parent = NULL;
|
|
static CM_Get_DevNode_PropertyW_ CM_Get_DevNode_PropertyW = NULL;
|
|
static CM_Get_Device_Interface_PropertyW_ CM_Get_Device_Interface_PropertyW = NULL;
|
|
static CM_Get_Device_Interface_List_SizeW_ CM_Get_Device_Interface_List_SizeW = NULL;
|
|
static CM_Get_Device_Interface_ListW_ CM_Get_Device_Interface_ListW = NULL;
|
|
|
|
static HMODULE hid_lib_handle = NULL;
|
|
static HMODULE cfgmgr32_lib_handle = NULL;
|
|
static BOOLEAN hidapi_initialized = FALSE;
|
|
|
|
static void free_library_handles()
|
|
{
|
|
if (hid_lib_handle)
|
|
FreeLibrary(hid_lib_handle);
|
|
hid_lib_handle = NULL;
|
|
if (cfgmgr32_lib_handle)
|
|
FreeLibrary(cfgmgr32_lib_handle);
|
|
cfgmgr32_lib_handle = NULL;
|
|
}
|
|
|
|
static int lookup_functions()
|
|
{
|
|
hid_lib_handle = LoadLibraryW(L"hid.dll");
|
|
if (hid_lib_handle == NULL) {
|
|
goto err;
|
|
}
|
|
|
|
cfgmgr32_lib_handle = LoadLibraryW(L"cfgmgr32.dll");
|
|
if (cfgmgr32_lib_handle == NULL) {
|
|
goto err;
|
|
}
|
|
|
|
#if defined(__GNUC__)
|
|
# pragma GCC diagnostic push
|
|
# pragma GCC diagnostic ignored "-Wcast-function-type"
|
|
#endif
|
|
#define RESOLVE(lib_handle, x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) goto err;
|
|
|
|
RESOLVE(hid_lib_handle, HidD_GetHidGuid);
|
|
RESOLVE(hid_lib_handle, HidD_GetAttributes);
|
|
RESOLVE(hid_lib_handle, HidD_GetSerialNumberString);
|
|
RESOLVE(hid_lib_handle, HidD_GetManufacturerString);
|
|
RESOLVE(hid_lib_handle, HidD_GetProductString);
|
|
RESOLVE(hid_lib_handle, HidD_SetFeature);
|
|
RESOLVE(hid_lib_handle, HidD_GetFeature);
|
|
RESOLVE(hid_lib_handle, HidD_SetOutputReport);
|
|
RESOLVE(hid_lib_handle, HidD_GetInputReport);
|
|
RESOLVE(hid_lib_handle, HidD_GetIndexedString);
|
|
RESOLVE(hid_lib_handle, HidD_GetPreparsedData);
|
|
RESOLVE(hid_lib_handle, HidD_FreePreparsedData);
|
|
RESOLVE(hid_lib_handle, HidP_GetCaps);
|
|
RESOLVE(hid_lib_handle, HidD_SetNumInputBuffers);
|
|
|
|
RESOLVE(cfgmgr32_lib_handle, CM_Locate_DevNodeW);
|
|
RESOLVE(cfgmgr32_lib_handle, CM_Get_Parent);
|
|
RESOLVE(cfgmgr32_lib_handle, CM_Get_DevNode_PropertyW);
|
|
RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_PropertyW);
|
|
RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_List_SizeW);
|
|
RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_ListW);
|
|
|
|
#undef RESOLVE
|
|
#if defined(__GNUC__)
|
|
# pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
err:
|
|
free_library_handles();
|
|
return -1;
|
|
}
|
|
|
|
#endif /* HIDAPI_USE_DDK */
|
|
|
|
struct hid_device_ {
|
|
HANDLE device_handle;
|
|
BOOL blocking;
|
|
USHORT output_report_length;
|
|
unsigned char *write_buf;
|
|
size_t input_report_length;
|
|
USHORT feature_report_length;
|
|
unsigned char *feature_buf;
|
|
wchar_t *last_error_str;
|
|
BOOL read_pending;
|
|
char *read_buf;
|
|
OVERLAPPED ol;
|
|
OVERLAPPED write_ol;
|
|
struct hid_device_info* device_info;
|
|
};
|
|
|
|
static hid_device *new_hid_device()
|
|
{
|
|
hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
|
|
|
|
if (dev == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
dev->device_handle = INVALID_HANDLE_VALUE;
|
|
dev->blocking = TRUE;
|
|
dev->output_report_length = 0;
|
|
dev->write_buf = NULL;
|
|
dev->input_report_length = 0;
|
|
dev->feature_report_length = 0;
|
|
dev->feature_buf = NULL;
|
|
dev->last_error_str = NULL;
|
|
dev->read_pending = FALSE;
|
|
dev->read_buf = NULL;
|
|
memset(&dev->ol, 0, sizeof(dev->ol));
|
|
dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL);
|
|
memset(&dev->write_ol, 0, sizeof(dev->write_ol));
|
|
dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL);
|
|
dev->device_info = NULL;
|
|
|
|
return dev;
|
|
}
|
|
|
|
static void free_hid_device(hid_device *dev)
|
|
{
|
|
CloseHandle(dev->ol.hEvent);
|
|
CloseHandle(dev->write_ol.hEvent);
|
|
CloseHandle(dev->device_handle);
|
|
free(dev->last_error_str);
|
|
dev->last_error_str = NULL;
|
|
free(dev->write_buf);
|
|
free(dev->feature_buf);
|
|
free(dev->read_buf);
|
|
hid_free_enumeration(dev->device_info);
|
|
free(dev);
|
|
}
|
|
|
|
static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR *op)
|
|
{
|
|
free(*error_buffer);
|
|
*error_buffer = NULL;
|
|
|
|
/* Only clear out error messages if NULL is passed into op */
|
|
if (!op) {
|
|
return;
|
|
}
|
|
|
|
WCHAR system_err_buf[1024];
|
|
DWORD error_code = GetLastError();
|
|
|
|
DWORD system_err_len = FormatMessageW(
|
|
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
error_code,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
system_err_buf, ARRAYSIZE(system_err_buf),
|
|
NULL);
|
|
|
|
DWORD op_len = (DWORD)wcslen(op);
|
|
|
|
DWORD op_prefix_len =
|
|
op_len
|
|
+ 15 /*: (0x00000000) */
|
|
;
|
|
DWORD msg_len =
|
|
+ op_prefix_len
|
|
+ system_err_len
|
|
;
|
|
|
|
*error_buffer = (WCHAR *)calloc(msg_len + 1, sizeof (WCHAR));
|
|
WCHAR *msg = *error_buffer;
|
|
|
|
if (!msg)
|
|
return;
|
|
|
|
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);
|
|
|
|
if (printf_written < 0)
|
|
{
|
|
/* Highly unlikely */
|
|
msg[0] = L'\0';
|
|
return;
|
|
}
|
|
|
|
/* Get rid of the CR and LF that FormatMessage() sticks at the
|
|
end of the message. Thanks Microsoft! */
|
|
while(msg[msg_len-1] == L'\r' || msg[msg_len-1] == L'\n' || msg[msg_len-1] == L' ')
|
|
{
|
|
msg[msg_len-1] = L'\0';
|
|
msg_len--;
|
|
}
|
|
}
|
|
|
|
#if defined(__GNUC__)
|
|
# pragma GCC diagnostic push
|
|
# pragma GCC diagnostic ignored "-Warray-bounds"
|
|
#endif
|
|
/* A bug in GCC/mingw gives:
|
|
* error: array subscript 0 is outside array bounds of 'wchar_t *[0]' {aka 'short unsigned int *[]'} [-Werror=array-bounds]
|
|
* | free(*error_buffer);
|
|
* Which doesn't make sense in this context. */
|
|
|
|
static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR *string_error)
|
|
{
|
|
free(*error_buffer);
|
|
*error_buffer = NULL;
|
|
|
|
if (string_error) {
|
|
*error_buffer = _wcsdup(string_error);
|
|
}
|
|
}
|
|
|
|
#if defined(__GNUC__)
|
|
# pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
static void register_winapi_error(hid_device *dev, const WCHAR *op)
|
|
{
|
|
register_winapi_error_to_buffer(&dev->last_error_str, op);
|
|
}
|
|
|
|
static void register_string_error(hid_device *dev, const WCHAR *string_error)
|
|
{
|
|
register_string_error_to_buffer(&dev->last_error_str, string_error);
|
|
}
|
|
|
|
static wchar_t *last_global_error_str = NULL;
|
|
|
|
static void register_global_winapi_error(const WCHAR *op)
|
|
{
|
|
register_winapi_error_to_buffer(&last_global_error_str, op);
|
|
}
|
|
|
|
static void register_global_error(const WCHAR *string_error)
|
|
{
|
|
register_string_error_to_buffer(&last_global_error_str, string_error);
|
|
}
|
|
|
|
static HANDLE open_device(const wchar_t *path, BOOL open_rw)
|
|
{
|
|
HANDLE handle;
|
|
DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0;
|
|
DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
|
|
|
|
handle = CreateFileW(path,
|
|
desired_access,
|
|
share_mode,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/
|
|
0);
|
|
|
|
return handle;
|
|
}
|
|
|
|
HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void)
|
|
{
|
|
return &api_version;
|
|
}
|
|
|
|
HID_API_EXPORT const char* HID_API_CALL hid_version_str(void)
|
|
{
|
|
return HID_API_VERSION_STR;
|
|
}
|
|
|
|
int HID_API_EXPORT hid_init(void)
|
|
{
|
|
register_global_error(NULL);
|
|
#ifndef HIDAPI_USE_DDK
|
|
if (!hidapi_initialized) {
|
|
if (lookup_functions() < 0) {
|
|
register_global_winapi_error(L"resolve DLL functions");
|
|
return -1;
|
|
}
|
|
hidapi_initialized = TRUE;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int HID_API_EXPORT hid_exit(void)
|
|
{
|
|
#ifndef HIDAPI_USE_DDK
|
|
free_library_handles();
|
|
hidapi_initialized = FALSE;
|
|
#endif
|
|
register_global_error(NULL);
|
|
return 0;
|
|
}
|
|
|
|
static void* hid_internal_get_devnode_property(DEVINST dev_node, const DEVPROPKEY* property_key, DEVPROPTYPE expected_property_type)
|
|
{
|
|
ULONG len = 0;
|
|
CONFIGRET cr;
|
|
DEVPROPTYPE property_type;
|
|
PBYTE property_value = NULL;
|
|
|
|
cr = CM_Get_DevNode_PropertyW(dev_node, property_key, &property_type, NULL, &len, 0);
|
|
if (cr != CR_BUFFER_SMALL || property_type != expected_property_type)
|
|
return NULL;
|
|
|
|
property_value = (PBYTE)calloc(len, sizeof(BYTE));
|
|
cr = CM_Get_DevNode_PropertyW(dev_node, property_key, &property_type, property_value, &len, 0);
|
|
if (cr != CR_SUCCESS) {
|
|
free(property_value);
|
|
return NULL;
|
|
}
|
|
|
|
return property_value;
|
|
}
|
|
|
|
static void* hid_internal_get_device_interface_property(const wchar_t* interface_path, const DEVPROPKEY* property_key, DEVPROPTYPE expected_property_type)
|
|
{
|
|
ULONG len = 0;
|
|
CONFIGRET cr;
|
|
DEVPROPTYPE property_type;
|
|
PBYTE property_value = NULL;
|
|
|
|
cr = CM_Get_Device_Interface_PropertyW(interface_path, property_key, &property_type, NULL, &len, 0);
|
|
if (cr != CR_BUFFER_SMALL || property_type != expected_property_type)
|
|
return NULL;
|
|
|
|
property_value = (PBYTE)calloc(len, sizeof(BYTE));
|
|
cr = CM_Get_Device_Interface_PropertyW(interface_path, property_key, &property_type, property_value, &len, 0);
|
|
if (cr != CR_SUCCESS) {
|
|
free(property_value);
|
|
return NULL;
|
|
}
|
|
|
|
return property_value;
|
|
}
|
|
|
|
static void hid_internal_towupper(wchar_t* string)
|
|
{
|
|
for (wchar_t* p = string; *p; ++p) *p = towupper(*p);
|
|
}
|
|
|
|
static int hid_internal_extract_int_token_value(wchar_t* string, const wchar_t* token)
|
|
{
|
|
int token_value;
|
|
wchar_t* startptr, * endptr;
|
|
|
|
startptr = wcsstr(string, token);
|
|
if (!startptr)
|
|
return -1;
|
|
|
|
startptr += wcslen(token);
|
|
token_value = wcstol(startptr, &endptr, 16);
|
|
if (endptr == startptr)
|
|
return -1;
|
|
|
|
return token_value;
|
|
}
|
|
|
|
static void hid_internal_get_usb_info(struct hid_device_info* dev, DEVINST dev_node)
|
|
{
|
|
wchar_t *device_id = NULL, *hardware_ids = NULL;
|
|
|
|
device_id = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
|
|
if (!device_id)
|
|
goto end;
|
|
|
|
/* Normalize to upper case */
|
|
hid_internal_towupper(device_id);
|
|
|
|
/* Check for Xbox Common Controller class (XUSB) device.
|
|
https://docs.microsoft.com/windows/win32/xinput/directinput-and-xusb-devices
|
|
https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput
|
|
*/
|
|
if (hid_internal_extract_int_token_value(device_id, L"IG_") != -1) {
|
|
/* Get devnode parent to reach out USB device. */
|
|
if (CM_Get_Parent(&dev_node, dev_node, 0) != CR_SUCCESS)
|
|
goto end;
|
|
}
|
|
|
|
/* Get the hardware ids from devnode */
|
|
hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST);
|
|
if (!hardware_ids)
|
|
goto end;
|
|
|
|
/* Get additional information from USB device's Hardware ID
|
|
https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers
|
|
https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-interfaces-not-grouped-in-collections
|
|
*/
|
|
for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) {
|
|
/* Normalize to upper case */
|
|
hid_internal_towupper(hardware_id);
|
|
|
|
if (dev->release_number == 0) {
|
|
/* USB_DEVICE_DESCRIPTOR.bcdDevice value. */
|
|
int release_number = hid_internal_extract_int_token_value(hardware_id, L"REV_");
|
|
if (release_number != -1) {
|
|
dev->release_number = (unsigned short)release_number;
|
|
}
|
|
}
|
|
|
|
if (dev->interface_number == -1) {
|
|
/* USB_INTERFACE_DESCRIPTOR.bInterfaceNumber value. */
|
|
int interface_number = hid_internal_extract_int_token_value(hardware_id, L"MI_");
|
|
if (interface_number != -1) {
|
|
dev->interface_number = interface_number;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Try to get USB device manufacturer string if not provided by HidD_GetManufacturerString. */
|
|
if (wcslen(dev->manufacturer_string) == 0) {
|
|
wchar_t* manufacturer_string = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_Manufacturer, DEVPROP_TYPE_STRING);
|
|
if (manufacturer_string) {
|
|
free(dev->manufacturer_string);
|
|
dev->manufacturer_string = manufacturer_string;
|
|
}
|
|
}
|
|
|
|
/* Try to get USB device serial number if not provided by HidD_GetSerialNumberString. */
|
|
if (wcslen(dev->serial_number) == 0) {
|
|
DEVINST usb_dev_node = dev_node;
|
|
if (dev->interface_number != -1) {
|
|
/* Get devnode parent to reach out composite parent USB device.
|
|
https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-the-composite-parent-device
|
|
*/
|
|
if (CM_Get_Parent(&usb_dev_node, dev_node, 0) != CR_SUCCESS)
|
|
goto end;
|
|
}
|
|
|
|
/* Get the device id of the USB device. */
|
|
free(device_id);
|
|
device_id = hid_internal_get_devnode_property(usb_dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
|
|
if (!device_id)
|
|
goto end;
|
|
|
|
/* Extract substring after last '\\' of Instance ID.
|
|
For USB devices it may contain device's serial number.
|
|
https://docs.microsoft.com/windows-hardware/drivers/install/instance-ids
|
|
*/
|
|
for (wchar_t *ptr = device_id + wcslen(device_id); ptr > device_id; --ptr) {
|
|
/* Instance ID is unique only within the scope of the bus.
|
|
For USB devices it means that serial number is not available. Skip. */
|
|
if (*ptr == L'&')
|
|
break;
|
|
|
|
if (*ptr == L'\\') {
|
|
free(dev->serial_number);
|
|
dev->serial_number = _wcsdup(ptr + 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If we can't get the interface number, it means that there is only one interface. */
|
|
if (dev->interface_number == -1)
|
|
dev->interface_number = 0;
|
|
|
|
end:
|
|
free(device_id);
|
|
free(hardware_ids);
|
|
}
|
|
|
|
/* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices
|
|
Request this info via dev node properties instead.
|
|
https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html
|
|
*/
|
|
static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node)
|
|
{
|
|
if (wcslen(dev->manufacturer_string) == 0) {
|
|
/* Manufacturer Name String (UUID: 0x2A29) */
|
|
wchar_t* manufacturer_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_Manufacturer, DEVPROP_TYPE_STRING);
|
|
if (manufacturer_string) {
|
|
free(dev->manufacturer_string);
|
|
dev->manufacturer_string = manufacturer_string;
|
|
}
|
|
}
|
|
|
|
if (wcslen(dev->serial_number) == 0) {
|
|
/* Serial Number String (UUID: 0x2A25) */
|
|
wchar_t* serial_number = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_DeviceAddress, DEVPROP_TYPE_STRING);
|
|
if (serial_number) {
|
|
free(dev->serial_number);
|
|
dev->serial_number = serial_number;
|
|
}
|
|
}
|
|
|
|
if (wcslen(dev->product_string) == 0) {
|
|
/* Model Number String (UUID: 0x2A24) */
|
|
wchar_t* product_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_ModelNumber, DEVPROP_TYPE_STRING);
|
|
if (!product_string) {
|
|
DEVINST parent_dev_node = 0;
|
|
/* Fallback: Get devnode grandparent to reach out Bluetooth LE device node */
|
|
if (CM_Get_Parent(&parent_dev_node, dev_node, 0) == CR_SUCCESS) {
|
|
/* Device Name (UUID: 0x2A00) */
|
|
product_string = hid_internal_get_devnode_property(parent_dev_node, &DEVPKEY_NAME, DEVPROP_TYPE_STRING);
|
|
}
|
|
}
|
|
|
|
if (product_string) {
|
|
free(dev->product_string);
|
|
dev->product_string = product_string;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Unfortunately, HID_API_BUS_xxx constants alone aren't enough to distinguish between BLUETOOTH and BLE */
|
|
|
|
#define HID_API_BUS_FLAG_BLE 0x01
|
|
|
|
typedef struct hid_internal_detect_bus_type_result_ {
|
|
DEVINST dev_node;
|
|
hid_bus_type bus_type;
|
|
unsigned int bus_flags;
|
|
} hid_internal_detect_bus_type_result;
|
|
|
|
static hid_internal_detect_bus_type_result hid_internal_detect_bus_type(const wchar_t* interface_path)
|
|
{
|
|
wchar_t *device_id = NULL, *compatible_ids = NULL;
|
|
CONFIGRET cr;
|
|
DEVINST dev_node;
|
|
hid_internal_detect_bus_type_result result = { 0 };
|
|
|
|
/* Get the device id from interface path */
|
|
device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
|
|
if (!device_id)
|
|
goto end;
|
|
|
|
/* Open devnode from device id */
|
|
cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL);
|
|
if (cr != CR_SUCCESS)
|
|
goto end;
|
|
|
|
/* Get devnode parent */
|
|
cr = CM_Get_Parent(&dev_node, dev_node, 0);
|
|
if (cr != CR_SUCCESS)
|
|
goto end;
|
|
|
|
/* Get the compatible ids from parent devnode */
|
|
compatible_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_CompatibleIds, DEVPROP_TYPE_STRING_LIST);
|
|
if (!compatible_ids)
|
|
goto end;
|
|
|
|
/* Now we can parse parent's compatible IDs to find out the device bus type */
|
|
for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) {
|
|
/* Normalize to upper case */
|
|
hid_internal_towupper(compatible_id);
|
|
|
|
/* USB devices
|
|
https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support
|
|
https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers */
|
|
if (wcsstr(compatible_id, L"USB") != NULL) {
|
|
result.bus_type = HID_API_BUS_USB;
|
|
break;
|
|
}
|
|
|
|
/* Bluetooth devices
|
|
https://docs.microsoft.com/windows-hardware/drivers/bluetooth/installing-a-bluetooth-device */
|
|
if (wcsstr(compatible_id, L"BTHENUM") != NULL) {
|
|
result.bus_type = HID_API_BUS_BLUETOOTH;
|
|
break;
|
|
}
|
|
|
|
/* Bluetooth LE devices */
|
|
if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) {
|
|
result.bus_type = HID_API_BUS_BLUETOOTH;
|
|
result.bus_flags |= HID_API_BUS_FLAG_BLE;
|
|
break;
|
|
}
|
|
|
|
/* I2C devices
|
|
https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support-and-power-management */
|
|
if (wcsstr(compatible_id, L"PNP0C50") != NULL) {
|
|
result.bus_type = HID_API_BUS_I2C;
|
|
break;
|
|
}
|
|
|
|
/* SPI devices
|
|
https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-for-spi */
|
|
if (wcsstr(compatible_id, L"PNP0C51") != NULL) {
|
|
result.bus_type = HID_API_BUS_SPI;
|
|
break;
|
|
}
|
|
}
|
|
|
|
result.dev_node = dev_node;
|
|
|
|
end:
|
|
free(device_id);
|
|
free(compatible_ids);
|
|
return result;
|
|
}
|
|
|
|
static char *hid_internal_UTF16toUTF8(const wchar_t *src)
|
|
{
|
|
char *dst = NULL;
|
|
int len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL);
|
|
if (len) {
|
|
dst = (char*)calloc(len, sizeof(char));
|
|
if (dst == NULL) {
|
|
return NULL;
|
|
}
|
|
WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dst, len, NULL, NULL);
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
static wchar_t *hid_internal_UTF8toUTF16(const char *src)
|
|
{
|
|
wchar_t *dst = NULL;
|
|
int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0);
|
|
if (len) {
|
|
dst = (wchar_t*)calloc(len, sizeof(wchar_t));
|
|
if (dst == NULL) {
|
|
return NULL;
|
|
}
|
|
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dst, len);
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
static struct hid_device_info *hid_internal_get_device_info(const wchar_t *path, HANDLE handle)
|
|
{
|
|
struct hid_device_info *dev = NULL; /* return object */
|
|
HIDD_ATTRIBUTES attrib;
|
|
PHIDP_PREPARSED_DATA pp_data = NULL;
|
|
HIDP_CAPS caps;
|
|
wchar_t string[MAX_STRING_WCHARS + 1];
|
|
ULONG len;
|
|
ULONG size;
|
|
hid_internal_detect_bus_type_result detect_bus_type_result;
|
|
|
|
/* Create the record. */
|
|
dev = (struct hid_device_info*)calloc(1, sizeof(struct hid_device_info));
|
|
|
|
if (dev == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Fill out the record */
|
|
dev->next = NULL;
|
|
dev->path = hid_internal_UTF16toUTF8(path);
|
|
dev->interface_number = -1;
|
|
|
|
attrib.Size = sizeof(HIDD_ATTRIBUTES);
|
|
if (HidD_GetAttributes(handle, &attrib)) {
|
|
/* VID/PID */
|
|
dev->vendor_id = attrib.VendorID;
|
|
dev->product_id = attrib.ProductID;
|
|
|
|
/* Release Number */
|
|
dev->release_number = attrib.VersionNumber;
|
|
}
|
|
|
|
/* Get the Usage Page and Usage for this device. */
|
|
if (HidD_GetPreparsedData(handle, &pp_data)) {
|
|
if (HidP_GetCaps(pp_data, &caps) == HIDP_STATUS_SUCCESS) {
|
|
dev->usage_page = caps.UsagePage;
|
|
dev->usage = caps.Usage;
|
|
}
|
|
|
|
HidD_FreePreparsedData(pp_data);
|
|
}
|
|
|
|
/* detect bus type before reading string descriptors */
|
|
detect_bus_type_result = hid_internal_detect_bus_type(path);
|
|
dev->bus_type = detect_bus_type_result.bus_type;
|
|
|
|
len = dev->bus_type == HID_API_BUS_USB ? MAX_STRING_WCHARS_USB : MAX_STRING_WCHARS;
|
|
string[len] = L'\0';
|
|
size = len * sizeof(wchar_t);
|
|
|
|
/* Serial Number */
|
|
string[0] = L'\0';
|
|
HidD_GetSerialNumberString(handle, string, size);
|
|
dev->serial_number = _wcsdup(string);
|
|
|
|
/* Manufacturer String */
|
|
string[0] = L'\0';
|
|
HidD_GetManufacturerString(handle, string, size);
|
|
dev->manufacturer_string = _wcsdup(string);
|
|
|
|
/* Product String */
|
|
string[0] = L'\0';
|
|
HidD_GetProductString(handle, string, size);
|
|
dev->product_string = _wcsdup(string);
|
|
|
|
/* now, the portion that depends on string descriptors */
|
|
switch (dev->bus_type) {
|
|
case HID_API_BUS_USB:
|
|
hid_internal_get_usb_info(dev, detect_bus_type_result.dev_node);
|
|
break;
|
|
|
|
case HID_API_BUS_BLUETOOTH:
|
|
if (detect_bus_type_result.bus_flags & HID_API_BUS_FLAG_BLE)
|
|
hid_internal_get_ble_info(dev, detect_bus_type_result.dev_node);
|
|
break;
|
|
|
|
case HID_API_BUS_UNKNOWN:
|
|
case HID_API_BUS_SPI:
|
|
case HID_API_BUS_I2C:
|
|
/* shut down -Wswitch */
|
|
break;
|
|
}
|
|
|
|
return dev;
|
|
}
|
|
|
|
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id)
|
|
{
|
|
struct hid_device_info *root = NULL; /* return object */
|
|
struct hid_device_info *cur_dev = NULL;
|
|
GUID interface_class_guid;
|
|
CONFIGRET cr;
|
|
wchar_t* device_interface_list = NULL;
|
|
DWORD len;
|
|
|
|
if (hid_init() < 0) {
|
|
/* register_global_error: global error is reset by hid_init */
|
|
return NULL;
|
|
}
|
|
|
|
/* Retrieve HID Interface Class GUID
|
|
https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */
|
|
HidD_GetHidGuid(&interface_class_guid);
|
|
|
|
/* Get the list of all device interfaces belonging to the HID class. */
|
|
/* Retry in case of list was changed between calls to
|
|
CM_Get_Device_Interface_List_SizeW and CM_Get_Device_Interface_ListW */
|
|
do {
|
|
cr = CM_Get_Device_Interface_List_SizeW(&len, &interface_class_guid, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
|
|
if (cr != CR_SUCCESS) {
|
|
register_global_error(L"Failed to get size of HID device interface list");
|
|
break;
|
|
}
|
|
|
|
if (device_interface_list != NULL) {
|
|
free(device_interface_list);
|
|
}
|
|
|
|
device_interface_list = (wchar_t*)calloc(len, sizeof(wchar_t));
|
|
if (device_interface_list == NULL) {
|
|
register_global_error(L"Failed to allocate memory for HID device interface list");
|
|
return NULL;
|
|
}
|
|
cr = CM_Get_Device_Interface_ListW(&interface_class_guid, NULL, device_interface_list, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
|
|
if (cr != CR_SUCCESS && cr != CR_BUFFER_SMALL) {
|
|
register_global_error(L"Failed to get HID device interface list");
|
|
}
|
|
} while (cr == CR_BUFFER_SMALL);
|
|
|
|
if (cr != CR_SUCCESS) {
|
|
goto end_of_function;
|
|
}
|
|
|
|
/* Iterate over each device interface in the HID class, looking for the right one. */
|
|
for (wchar_t* device_interface = device_interface_list; *device_interface; device_interface += wcslen(device_interface) + 1) {
|
|
HANDLE device_handle = INVALID_HANDLE_VALUE;
|
|
HIDD_ATTRIBUTES attrib;
|
|
|
|
/* Open read-only handle to the device */
|
|
device_handle = open_device(device_interface, FALSE);
|
|
|
|
/* Check validity of device_handle. */
|
|
if (device_handle == INVALID_HANDLE_VALUE) {
|
|
/* Unable to open the device. */
|
|
continue;
|
|
}
|
|
|
|
/* Get the Vendor ID and Product ID for this device. */
|
|
attrib.Size = sizeof(HIDD_ATTRIBUTES);
|
|
if (!HidD_GetAttributes(device_handle, &attrib)) {
|
|
goto cont_close;
|
|
}
|
|
|
|
/* Check the VID/PID to see if we should add this
|
|
device to the enumeration list. */
|
|
if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) &&
|
|
(product_id == 0x0 || attrib.ProductID == product_id)) {
|
|
|
|
/* VID/PID match. Create the record. */
|
|
struct hid_device_info *tmp = hid_internal_get_device_info(device_interface, device_handle);
|
|
|
|
if (tmp == NULL) {
|
|
goto cont_close;
|
|
}
|
|
|
|
if (cur_dev) {
|
|
cur_dev->next = tmp;
|
|
}
|
|
else {
|
|
root = tmp;
|
|
}
|
|
cur_dev = tmp;
|
|
}
|
|
|
|
cont_close:
|
|
CloseHandle(device_handle);
|
|
}
|
|
|
|
if (root == NULL) {
|
|
if (vendor_id == 0 && product_id == 0) {
|
|
register_global_error(L"No HID devices found in the system.");
|
|
} else {
|
|
register_global_error(L"No HID devices with requested VID/PID found in the system.");
|
|
}
|
|
}
|
|
|
|
end_of_function:
|
|
free(device_interface_list);
|
|
|
|
return root;
|
|
}
|
|
|
|
void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs)
|
|
{
|
|
/* TODO: Merge this with the Linux version. This function is platform-independent. */
|
|
struct hid_device_info *d = devs;
|
|
while (d) {
|
|
struct hid_device_info *next = d->next;
|
|
free(d->path);
|
|
free(d->serial_number);
|
|
free(d->manufacturer_string);
|
|
free(d->product_string);
|
|
free(d);
|
|
d = next;
|
|
}
|
|
}
|
|
|
|
HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
|
|
{
|
|
/* TODO: Merge this functions with the Linux version. This function should be platform independent. */
|
|
struct hid_device_info *devs, *cur_dev;
|
|
const char *path_to_open = NULL;
|
|
hid_device *handle = NULL;
|
|
|
|
/* register_global_error: global error is reset by hid_enumerate/hid_init */
|
|
devs = hid_enumerate(vendor_id, product_id);
|
|
if (!devs) {
|
|
/* register_global_error: global error is already set by hid_enumerate */
|
|
return NULL;
|
|
}
|
|
|
|
cur_dev = devs;
|
|
while (cur_dev) {
|
|
if (cur_dev->vendor_id == vendor_id &&
|
|
cur_dev->product_id == product_id) {
|
|
if (serial_number) {
|
|
if (cur_dev->serial_number && wcscmp(serial_number, cur_dev->serial_number) == 0) {
|
|
path_to_open = cur_dev->path;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
path_to_open = cur_dev->path;
|
|
break;
|
|
}
|
|
}
|
|
cur_dev = cur_dev->next;
|
|
}
|
|
|
|
if (path_to_open) {
|
|
/* Open the device */
|
|
handle = hid_open_path(path_to_open);
|
|
} else {
|
|
register_global_error(L"Device with requested VID/PID/(SerialNumber) not found");
|
|
}
|
|
|
|
hid_free_enumeration(devs);
|
|
|
|
return handle;
|
|
}
|
|
|
|
HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
|
|
{
|
|
hid_device *dev = NULL;
|
|
wchar_t* interface_path = NULL;
|
|
HANDLE device_handle = INVALID_HANDLE_VALUE;
|
|
PHIDP_PREPARSED_DATA pp_data = NULL;
|
|
HIDP_CAPS caps;
|
|
|
|
if (hid_init() < 0) {
|
|
/* register_global_error: global error is reset by hid_init */
|
|
goto end_of_function;
|
|
}
|
|
|
|
interface_path = hid_internal_UTF8toUTF16(path);
|
|
if (!interface_path) {
|
|
register_global_error(L"Path conversion failure");
|
|
goto end_of_function;
|
|
}
|
|
|
|
/* Open a handle to the device */
|
|
device_handle = open_device(interface_path, TRUE);
|
|
|
|
/* Check validity of write_handle. */
|
|
if (device_handle == INVALID_HANDLE_VALUE) {
|
|
/* System devices, such as keyboards and mice, cannot be opened in
|
|
read-write mode, because the system takes exclusive control over
|
|
them. This is to prevent keyloggers. However, feature reports
|
|
can still be sent and received. Retry opening the device, but
|
|
without read/write access. */
|
|
device_handle = open_device(interface_path, FALSE);
|
|
|
|
/* Check the validity of the limited device_handle. */
|
|
if (device_handle == INVALID_HANDLE_VALUE) {
|
|
register_global_winapi_error(L"open_device");
|
|
goto end_of_function;
|
|
}
|
|
}
|
|
|
|
/* Set the Input Report buffer size to 64 reports. */
|
|
if (!HidD_SetNumInputBuffers(device_handle, 64)) {
|
|
register_global_winapi_error(L"set input buffers");
|
|
goto end_of_function;
|
|
}
|
|
|
|
/* Get the Input Report length for the device. */
|
|
if (!HidD_GetPreparsedData(device_handle, &pp_data)) {
|
|
register_global_winapi_error(L"get preparsed data");
|
|
goto end_of_function;
|
|
}
|
|
|
|
if (HidP_GetCaps(pp_data, &caps) != HIDP_STATUS_SUCCESS) {
|
|
register_global_error(L"HidP_GetCaps");
|
|
goto end_of_function;
|
|
}
|
|
|
|
dev = new_hid_device();
|
|
|
|
if (dev == NULL) {
|
|
register_global_error(L"hid_device allocation error");
|
|
goto end_of_function;
|
|
}
|
|
|
|
dev->device_handle = device_handle;
|
|
device_handle = INVALID_HANDLE_VALUE;
|
|
|
|
dev->output_report_length = caps.OutputReportByteLength;
|
|
dev->input_report_length = caps.InputReportByteLength;
|
|
dev->feature_report_length = caps.FeatureReportByteLength;
|
|
dev->read_buf = (char*) malloc(dev->input_report_length);
|
|
dev->device_info = hid_internal_get_device_info(interface_path, dev->device_handle);
|
|
|
|
end_of_function:
|
|
free(interface_path);
|
|
CloseHandle(device_handle);
|
|
|
|
if (pp_data) {
|
|
HidD_FreePreparsedData(pp_data);
|
|
}
|
|
|
|
return dev;
|
|
}
|
|
|
|
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
|
|
{
|
|
DWORD bytes_written = 0;
|
|
int function_result = -1;
|
|
BOOL res;
|
|
BOOL overlapped = FALSE;
|
|
|
|
unsigned char *buf;
|
|
|
|
if (!data || !length) {
|
|
register_string_error(dev, L"Zero buffer/length");
|
|
return function_result;
|
|
}
|
|
|
|
register_string_error(dev, NULL);
|
|
|
|
/* Make sure the right number of bytes are passed to WriteFile. Windows
|
|
expects the number of bytes which are in the _longest_ report (plus
|
|
one for the report number) bytes even if the data is a report
|
|
which is shorter than that. Windows gives us this value in
|
|
caps.OutputReportByteLength. If a user passes in fewer bytes than this,
|
|
use cached temporary buffer which is the proper size. */
|
|
if (length >= dev->output_report_length) {
|
|
/* The user passed the right number of bytes. Use the buffer as-is. */
|
|
buf = (unsigned char *) data;
|
|
} else {
|
|
if (dev->write_buf == NULL)
|
|
dev->write_buf = (unsigned char *) malloc(dev->output_report_length);
|
|
buf = dev->write_buf;
|
|
memcpy(buf, data, length);
|
|
memset(buf + length, 0, dev->output_report_length - length);
|
|
length = dev->output_report_length;
|
|
}
|
|
|
|
res = WriteFile(dev->device_handle, buf, (DWORD) length, NULL, &dev->write_ol);
|
|
|
|
if (!res) {
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
/* WriteFile() failed. Return error. */
|
|
register_winapi_error(dev, L"WriteFile");
|
|
goto end_of_function;
|
|
}
|
|
overlapped = TRUE;
|
|
}
|
|
|
|
if (overlapped) {
|
|
/* Wait for the transaction to complete. This makes
|
|
hid_write() synchronous. */
|
|
res = WaitForSingleObject(dev->write_ol.hEvent, 1000);
|
|
if (res != WAIT_OBJECT_0) {
|
|
/* There was a Timeout. */
|
|
register_winapi_error(dev, L"hid_write/WaitForSingleObject");
|
|
goto end_of_function;
|
|
}
|
|
|
|
/* Get the result. */
|
|
res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*wait*/);
|
|
if (res) {
|
|
function_result = bytes_written;
|
|
}
|
|
else {
|
|
/* The Write operation failed. */
|
|
register_winapi_error(dev, L"hid_write/GetOverlappedResult");
|
|
goto end_of_function;
|
|
}
|
|
}
|
|
|
|
end_of_function:
|
|
return function_result;
|
|
}
|
|
|
|
|
|
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
|
|
{
|
|
DWORD bytes_read = 0;
|
|
size_t copy_len = 0;
|
|
BOOL res = FALSE;
|
|
BOOL overlapped = FALSE;
|
|
|
|
if (!data || !length) {
|
|
register_string_error(dev, L"Zero buffer/length");
|
|
return -1;
|
|
}
|
|
|
|
register_string_error(dev, NULL);
|
|
|
|
/* Copy the handle for convenience. */
|
|
HANDLE ev = dev->ol.hEvent;
|
|
|
|
if (!dev->read_pending) {
|
|
/* Start an Overlapped I/O read. */
|
|
dev->read_pending = TRUE;
|
|
memset(dev->read_buf, 0, dev->input_report_length);
|
|
ResetEvent(ev);
|
|
res = ReadFile(dev->device_handle, dev->read_buf, (DWORD) dev->input_report_length, &bytes_read, &dev->ol);
|
|
|
|
if (!res) {
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
/* ReadFile() has failed.
|
|
Clean up and return error. */
|
|
register_winapi_error(dev, L"ReadFile");
|
|
CancelIo(dev->device_handle);
|
|
dev->read_pending = FALSE;
|
|
goto end_of_function;
|
|
}
|
|
overlapped = TRUE;
|
|
}
|
|
}
|
|
else {
|
|
overlapped = TRUE;
|
|
}
|
|
|
|
if (overlapped) {
|
|
/* See if there is any data yet. */
|
|
res = WaitForSingleObject(ev, milliseconds >= 0 ? (DWORD)milliseconds : INFINITE);
|
|
if (res != WAIT_OBJECT_0) {
|
|
/* There was no data this time. Return zero bytes available,
|
|
but leave the Overlapped I/O running. */
|
|
return 0;
|
|
}
|
|
|
|
/* Get the number of bytes read. The actual data has been copied to the data[]
|
|
array which was passed to ReadFile(). We must not wait here because we've
|
|
already waited on our event above, and since it's auto-reset, it will have
|
|
been reset back to unsignalled by now. */
|
|
res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, FALSE/*don't wait now - already did on the prev step*/);
|
|
}
|
|
/* Set pending back to false, even if GetOverlappedResult() returned error. */
|
|
dev->read_pending = FALSE;
|
|
|
|
if (res && bytes_read > 0) {
|
|
if (dev->read_buf[0] == 0x0) {
|
|
/* If report numbers aren't being used, but Windows sticks a report
|
|
number (0x0) on the beginning of the report anyway. To make this
|
|
work like the other platforms, and to make it work more like the
|
|
HID spec, we'll skip over this byte. */
|
|
bytes_read--;
|
|
copy_len = length > bytes_read ? bytes_read : length;
|
|
memcpy(data, dev->read_buf+1, copy_len);
|
|
}
|
|
else {
|
|
/* Copy the whole buffer, report number and all. */
|
|
copy_len = length > bytes_read ? bytes_read : length;
|
|
memcpy(data, dev->read_buf, copy_len);
|
|
}
|
|
}
|
|
if (!res) {
|
|
register_winapi_error(dev, L"hid_read_timeout/GetOverlappedResult");
|
|
}
|
|
|
|
end_of_function:
|
|
if (!res) {
|
|
return -1;
|
|
}
|
|
|
|
return (int) copy_len;
|
|
}
|
|
|
|
int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length)
|
|
{
|
|
return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0);
|
|
}
|
|
|
|
int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock)
|
|
{
|
|
dev->blocking = !nonblock;
|
|
return 0; /* Success */
|
|
}
|
|
|
|
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
|
|
{
|
|
BOOL res = FALSE;
|
|
unsigned char *buf;
|
|
size_t length_to_send;
|
|
|
|
if (!data || !length) {
|
|
register_string_error(dev, L"Zero buffer/length");
|
|
return -1;
|
|
}
|
|
|
|
register_string_error(dev, NULL);
|
|
|
|
/* Windows expects at least caps.FeatureReportByteLength bytes passed
|
|
to HidD_SetFeature(), even if the report is shorter. Any less sent and
|
|
the function fails with error ERROR_INVALID_PARAMETER set. Any more
|
|
and HidD_SetFeature() silently truncates the data sent in the report
|
|
to caps.FeatureReportByteLength. */
|
|
if (length >= dev->feature_report_length) {
|
|
buf = (unsigned char *) data;
|
|
length_to_send = length;
|
|
} else {
|
|
if (dev->feature_buf == NULL)
|
|
dev->feature_buf = (unsigned char *) malloc(dev->feature_report_length);
|
|
buf = dev->feature_buf;
|
|
memcpy(buf, data, length);
|
|
memset(buf + length, 0, dev->feature_report_length - length);
|
|
length_to_send = dev->feature_report_length;
|
|
}
|
|
|
|
res = HidD_SetFeature(dev->device_handle, (PVOID)buf, (DWORD) length_to_send);
|
|
|
|
if (!res) {
|
|
register_winapi_error(dev, L"HidD_SetFeature");
|
|
return -1;
|
|
}
|
|
|
|
return (int) length;
|
|
}
|
|
|
|
static int hid_get_report(hid_device *dev, DWORD report_type, unsigned char *data, size_t length)
|
|
{
|
|
BOOL res;
|
|
DWORD bytes_returned = 0;
|
|
|
|
OVERLAPPED ol;
|
|
memset(&ol, 0, sizeof(ol));
|
|
|
|
if (!data || !length) {
|
|
register_string_error(dev, L"Zero buffer/length");
|
|
return -1;
|
|
}
|
|
|
|
register_string_error(dev, NULL);
|
|
|
|
res = DeviceIoControl(dev->device_handle,
|
|
report_type,
|
|
data, (DWORD) length,
|
|
data, (DWORD) length,
|
|
&bytes_returned, &ol);
|
|
|
|
if (!res) {
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
/* DeviceIoControl() failed. Return error. */
|
|
register_winapi_error(dev, L"Get Input/Feature Report DeviceIoControl");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Wait here until the write is done. This makes
|
|
hid_get_feature_report() synchronous. */
|
|
res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/);
|
|
if (!res) {
|
|
/* The operation failed. */
|
|
register_winapi_error(dev, L"Get Input/Feature Report GetOverLappedResult");
|
|
return -1;
|
|
}
|
|
|
|
/* When numbered reports aren't used,
|
|
bytes_returned seem to include only what is actually received from the device
|
|
(not including the first byte with 0, as an indication "no numbered reports"). */
|
|
if (data[0] == 0x0) {
|
|
bytes_returned++;
|
|
}
|
|
|
|
return bytes_returned;
|
|
}
|
|
|
|
int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
|
|
{
|
|
/* We could use HidD_GetFeature() instead, but it doesn't give us an actual length, unfortunately */
|
|
return hid_get_report(dev, IOCTL_HID_GET_FEATURE, data, length);
|
|
}
|
|
|
|
int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device* dev, const unsigned char* data, size_t length)
|
|
{
|
|
BOOL res = FALSE;
|
|
unsigned char *buf;
|
|
size_t length_to_send;
|
|
|
|
if (!data || !length) {
|
|
register_string_error(dev, L"Zero buffer/length");
|
|
return -1;
|
|
}
|
|
|
|
register_string_error(dev, NULL);
|
|
|
|
/* Windows expects at least caps.OutputeportByteLength bytes passed
|
|
to HidD_SetOutputReport(), even if the report is shorter. Any less sent and
|
|
the function fails with error ERROR_INVALID_PARAMETER set. Any more
|
|
and HidD_SetOutputReport() silently truncates the data sent in the report
|
|
to caps.OutputReportByteLength. */
|
|
if (length >= dev->output_report_length) {
|
|
buf = (unsigned char *) data;
|
|
length_to_send = length;
|
|
} else {
|
|
if (dev->write_buf == NULL)
|
|
dev->write_buf = (unsigned char *) malloc(dev->output_report_length);
|
|
buf = dev->write_buf;
|
|
memcpy(buf, data, length);
|
|
memset(buf + length, 0, dev->output_report_length - length);
|
|
length_to_send = dev->output_report_length;
|
|
}
|
|
|
|
res = HidD_SetOutputReport(dev->device_handle, (PVOID)buf, (DWORD) length_to_send);
|
|
if (!res) {
|
|
register_string_error(dev, L"HidD_SetOutputReport");
|
|
return -1;
|
|
}
|
|
|
|
return (int) length;
|
|
}
|
|
|
|
int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
|
|
{
|
|
/* We could use HidD_GetInputReport() instead, but it doesn't give us an actual length, unfortunately */
|
|
return hid_get_report(dev, IOCTL_HID_GET_INPUT_REPORT, data, length);
|
|
}
|
|
|
|
void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)
|
|
{
|
|
if (!dev)
|
|
return;
|
|
|
|
CancelIo(dev->device_handle);
|
|
free_hid_device(dev);
|
|
}
|
|
|
|
int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
|
{
|
|
if (!string || !maxlen) {
|
|
register_string_error(dev, L"Zero buffer/length");
|
|
return -1;
|
|
}
|
|
|
|
if (!dev->device_info) {
|
|
register_string_error(dev, L"NULL device info");
|
|
return -1;
|
|
}
|
|
|
|
wcsncpy(string, dev->device_info->manufacturer_string, maxlen);
|
|
string[maxlen - 1] = L'\0';
|
|
|
|
register_string_error(dev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
|
{
|
|
if (!string || !maxlen) {
|
|
register_string_error(dev, L"Zero buffer/length");
|
|
return -1;
|
|
}
|
|
|
|
if (!dev->device_info) {
|
|
register_string_error(dev, L"NULL device info");
|
|
return -1;
|
|
}
|
|
|
|
wcsncpy(string, dev->device_info->product_string, maxlen);
|
|
string[maxlen - 1] = L'\0';
|
|
|
|
register_string_error(dev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
|
{
|
|
if (!string || !maxlen) {
|
|
register_string_error(dev, L"Zero buffer/length");
|
|
return -1;
|
|
}
|
|
|
|
if (!dev->device_info) {
|
|
register_string_error(dev, L"NULL device info");
|
|
return -1;
|
|
}
|
|
|
|
wcsncpy(string, dev->device_info->serial_number, maxlen);
|
|
string[maxlen - 1] = L'\0';
|
|
|
|
register_string_error(dev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
HID_API_EXPORT struct hid_device_info * HID_API_CALL hid_get_device_info(hid_device *dev) {
|
|
if (!dev->device_info)
|
|
{
|
|
register_string_error(dev, L"NULL device info");
|
|
return NULL;
|
|
}
|
|
|
|
return dev->device_info;
|
|
}
|
|
|
|
int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
|
|
{
|
|
BOOL res;
|
|
|
|
if (dev->device_info && dev->device_info->bus_type == HID_API_BUS_USB && maxlen > MAX_STRING_WCHARS_USB) {
|
|
string[MAX_STRING_WCHARS_USB] = L'\0';
|
|
maxlen = MAX_STRING_WCHARS_USB;
|
|
}
|
|
|
|
res = HidD_GetIndexedString(dev->device_handle, string_index, string, (ULONG)maxlen * sizeof(wchar_t));
|
|
if (!res) {
|
|
register_winapi_error(dev, L"HidD_GetIndexedString");
|
|
return -1;
|
|
}
|
|
|
|
register_string_error(dev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id)
|
|
{
|
|
wchar_t *interface_path = NULL, *device_id = NULL;
|
|
CONFIGRET cr = CR_FAILURE;
|
|
DEVINST dev_node;
|
|
DEVPROPTYPE property_type;
|
|
ULONG len;
|
|
|
|
if (!container_id) {
|
|
register_string_error(dev, L"Invalid Container ID");
|
|
return -1;
|
|
}
|
|
|
|
register_string_error(dev, NULL);
|
|
|
|
interface_path = hid_internal_UTF8toUTF16(dev->device_info->path);
|
|
if (!interface_path) {
|
|
register_string_error(dev, L"Path conversion failure");
|
|
goto end;
|
|
}
|
|
|
|
/* Get the device id from interface path */
|
|
device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
|
|
if (!device_id) {
|
|
register_string_error(dev, L"Failed to get device interface property InstanceId");
|
|
goto end;
|
|
}
|
|
|
|
/* Open devnode from device id */
|
|
cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL);
|
|
if (cr != CR_SUCCESS) {
|
|
register_string_error(dev, L"Failed to locate device node");
|
|
goto end;
|
|
}
|
|
|
|
/* Get the container id from devnode */
|
|
len = sizeof(*container_id);
|
|
cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_ContainerId, &property_type, (PBYTE)container_id, &len, 0);
|
|
if (cr == CR_SUCCESS && property_type != DEVPROP_TYPE_GUID)
|
|
cr = CR_FAILURE;
|
|
|
|
if (cr != CR_SUCCESS)
|
|
register_string_error(dev, L"Failed to read ContainerId property from device node");
|
|
|
|
end:
|
|
free(interface_path);
|
|
free(device_id);
|
|
|
|
return cr == CR_SUCCESS ? 0 : -1;
|
|
}
|
|
|
|
|
|
int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size)
|
|
{
|
|
PHIDP_PREPARSED_DATA pp_data = NULL;
|
|
|
|
if (!HidD_GetPreparsedData(dev->device_handle, &pp_data) || pp_data == NULL) {
|
|
register_string_error(dev, L"HidD_GetPreparsedData");
|
|
return -1;
|
|
}
|
|
|
|
int res = hid_winapi_descriptor_reconstruct_pp_data(pp_data, buf, buf_size);
|
|
|
|
HidD_FreePreparsedData(pp_data);
|
|
|
|
return res;
|
|
}
|
|
|
|
HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev)
|
|
{
|
|
if (dev) {
|
|
if (dev->last_error_str == NULL)
|
|
return L"Success";
|
|
return (wchar_t*)dev->last_error_str;
|
|
}
|
|
|
|
if (last_global_error_str == NULL)
|
|
return L"Success";
|
|
return last_global_error_str;
|
|
}
|
|
|
|
#ifndef hidapi_winapi_EXPORTS
|
|
#include "hidapi_descriptor_reconstruct.c"
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
} /* extern "C" */
|
|
#endif
|