mirror of https://github.com/wb2osz/direwolf.git
1095 lines
30 KiB
C
1095 lines
30 KiB
C
/*******************************************************
|
|
HIDAPI - Multi-Platform library for
|
|
communication with HID devices.
|
|
|
|
Alan Ott
|
|
Signal 11 Software
|
|
|
|
8/22/2009
|
|
|
|
Copyright 2009, 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 .
|
|
********************************************************/
|
|
|
|
#include "../../src/direwolf.h" // for strlcpy
|
|
|
|
#include <windows.h>
|
|
|
|
#ifndef _NTDEF_
|
|
typedef LONG NTSTATUS;
|
|
#endif
|
|
|
|
#ifdef __MINGW32__
|
|
#include <ntdef.h>
|
|
#include <winbase.h>
|
|
#endif
|
|
|
|
#ifdef __CYGWIN__
|
|
#include <ntdef.h>
|
|
#define _wcsdup wcsdup
|
|
#endif
|
|
|
|
/* The maximum number of characters that can be passed into the
|
|
HidD_Get*String() functions without it failing.*/
|
|
#define MAX_STRING_WCHARS 0xFFF
|
|
|
|
/*#define HIDAPI_USE_DDK*/
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
#include <setupapi.h>
|
|
#include <winioctl.h>
|
|
#ifdef HIDAPI_USE_DDK
|
|
#include <hidsdi.h>
|
|
#endif
|
|
|
|
/* Copied from inc/ddk/hidclass.h, part of the Windows DDK. */
|
|
#define HID_OUT_CTL_CODE(id) \
|
|
CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
|
|
#define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100)
|
|
#define IOCTL_HID_GET_INPUT_REPORT HID_OUT_CTL_CODE(104)
|
|
|
|
#ifdef __cplusplus
|
|
} /* extern "C" */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
|
|
#include "hidapi.h"
|
|
|
|
#undef MIN
|
|
#define MIN(x,y) ((x) < (y)? (x): (y))
|
|
|
|
#ifdef _MSC_VER
|
|
/* Thanks Microsoft, but I know how to use strncpy(). */
|
|
#pragma warning(disable:4996)
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
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 SDK, we have to define all this
|
|
stuff here. In lookup_functions(), the function pointers
|
|
defined below are set. */
|
|
typedef struct _HIDD_ATTRIBUTES{
|
|
ULONG Size;
|
|
USHORT VendorID;
|
|
USHORT ProductID;
|
|
USHORT VersionNumber;
|
|
} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
|
|
|
|
typedef USHORT USAGE;
|
|
typedef struct _HIDP_CAPS {
|
|
USAGE Usage;
|
|
USAGE UsagePage;
|
|
USHORT InputReportByteLength;
|
|
USHORT OutputReportByteLength;
|
|
USHORT FeatureReportByteLength;
|
|
USHORT Reserved[17];
|
|
USHORT fields_not_used_by_hidapi[10];
|
|
} HIDP_CAPS, *PHIDP_CAPS;
|
|
typedef void* PHIDP_PREPARSED_DATA;
|
|
#define HIDP_STATUS_SUCCESS 0x110000
|
|
|
|
typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib);
|
|
typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len);
|
|
typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
|
|
typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
|
|
typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length);
|
|
typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length);
|
|
typedef BOOLEAN (__stdcall *HidD_GetInputReport_)(HANDLE handle, PVOID data, ULONG length);
|
|
typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len);
|
|
typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data);
|
|
typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data);
|
|
typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps);
|
|
typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers);
|
|
|
|
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_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 HMODULE lib_handle = NULL;
|
|
static BOOLEAN initialized = FALSE;
|
|
#endif /* HIDAPI_USE_DDK */
|
|
|
|
struct hid_device_ {
|
|
HANDLE device_handle;
|
|
BOOL blocking;
|
|
USHORT output_report_length;
|
|
size_t input_report_length;
|
|
USHORT feature_report_length;
|
|
unsigned char *feature_buf;
|
|
void *last_error_str;
|
|
DWORD last_error_num;
|
|
BOOL read_pending;
|
|
char *read_buf;
|
|
OVERLAPPED ol;
|
|
OVERLAPPED write_ol;
|
|
};
|
|
|
|
static hid_device *new_hid_device()
|
|
{
|
|
hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
|
|
dev->device_handle = INVALID_HANDLE_VALUE;
|
|
dev->blocking = TRUE;
|
|
dev->output_report_length = 0;
|
|
dev->input_report_length = 0;
|
|
dev->feature_report_length = 0;
|
|
dev->feature_buf = NULL;
|
|
dev->last_error_str = NULL;
|
|
dev->last_error_num = 0;
|
|
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 /*inital state f=nonsignaled*/, NULL);
|
|
|
|
return dev;
|
|
}
|
|
|
|
static void free_hid_device(hid_device *dev)
|
|
{
|
|
CloseHandle(dev->ol.hEvent);
|
|
CloseHandle(dev->write_ol.hEvent);
|
|
CloseHandle(dev->device_handle);
|
|
LocalFree(dev->last_error_str);
|
|
free(dev->feature_buf);
|
|
free(dev->read_buf);
|
|
free(dev);
|
|
}
|
|
|
|
static void register_error(hid_device *dev, const char *op)
|
|
{
|
|
WCHAR *ptr, *msg;
|
|
(void)op; // unreferenced param
|
|
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
GetLastError(),
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPWSTR)&msg, 0/*sz*/,
|
|
NULL);
|
|
|
|
/* Get rid of the CR and LF that FormatMessage() sticks at the
|
|
end of the message. Thanks Microsoft! */
|
|
ptr = msg;
|
|
while (*ptr) {
|
|
if (*ptr == '\r') {
|
|
*ptr = 0x0000;
|
|
break;
|
|
}
|
|
ptr++;
|
|
}
|
|
|
|
/* Store the message off in the Device entry so that
|
|
the hid_error() function can pick it up. */
|
|
LocalFree(dev->last_error_str);
|
|
dev->last_error_str = msg;
|
|
}
|
|
|
|
#ifndef HIDAPI_USE_DDK
|
|
static int lookup_functions()
|
|
{
|
|
lib_handle = LoadLibraryA("hid.dll");
|
|
if (lib_handle) {
|
|
#if defined(__GNUC__)
|
|
# pragma GCC diagnostic push
|
|
//# pragma GCC diagnostic ignored "-Wcast-function-type"
|
|
#endif
|
|
#define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1;
|
|
RESOLVE(HidD_GetAttributes);
|
|
RESOLVE(HidD_GetSerialNumberString);
|
|
RESOLVE(HidD_GetManufacturerString);
|
|
RESOLVE(HidD_GetProductString);
|
|
RESOLVE(HidD_SetFeature);
|
|
RESOLVE(HidD_GetFeature);
|
|
RESOLVE(HidD_GetInputReport);
|
|
RESOLVE(HidD_GetIndexedString);
|
|
RESOLVE(HidD_GetPreparsedData);
|
|
RESOLVE(HidD_FreePreparsedData);
|
|
RESOLVE(HidP_GetCaps);
|
|
RESOLVE(HidD_SetNumInputBuffers);
|
|
#undef RESOLVE
|
|
#if defined(__GNUC__)
|
|
# pragma GCC diagnostic pop
|
|
#endif
|
|
}
|
|
else
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static HANDLE open_device(const char *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 = CreateFileA(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()
|
|
{
|
|
return &api_version;
|
|
}
|
|
|
|
HID_API_EXPORT const char* HID_API_CALL hid_version_str()
|
|
{
|
|
return HID_API_VERSION_STR;
|
|
}
|
|
|
|
int HID_API_EXPORT hid_init(void)
|
|
{
|
|
#ifndef HIDAPI_USE_DDK
|
|
if (!initialized) {
|
|
if (lookup_functions() < 0) {
|
|
hid_exit();
|
|
return -1;
|
|
}
|
|
initialized = TRUE;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int HID_API_EXPORT hid_exit(void)
|
|
{
|
|
#ifndef HIDAPI_USE_DDK
|
|
if (lib_handle)
|
|
FreeLibrary(lib_handle);
|
|
lib_handle = NULL;
|
|
initialized = FALSE;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id)
|
|
{
|
|
BOOL res;
|
|
struct hid_device_info *root = NULL; /* return object */
|
|
struct hid_device_info *cur_dev = NULL;
|
|
|
|
/* Windows objects for interacting with the driver. */
|
|
GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} };
|
|
SP_DEVINFO_DATA devinfo_data;
|
|
SP_DEVICE_INTERFACE_DATA device_interface_data;
|
|
SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL;
|
|
HDEVINFO device_info_set = INVALID_HANDLE_VALUE;
|
|
int device_index = 0;
|
|
int i;
|
|
|
|
if (hid_init() < 0)
|
|
return NULL;
|
|
|
|
/* Initialize the Windows objects. */
|
|
memset(&devinfo_data, 0x0, sizeof(devinfo_data));
|
|
devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
|
|
device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
|
|
|
/* Get information for all the devices belonging to the HID class. */
|
|
device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
|
|
/* Iterate over each device in the HID class, looking for the right one. */
|
|
|
|
for (;;) {
|
|
HANDLE write_handle = INVALID_HANDLE_VALUE;
|
|
DWORD required_size = 0;
|
|
HIDD_ATTRIBUTES attrib;
|
|
|
|
res = SetupDiEnumDeviceInterfaces(device_info_set,
|
|
NULL,
|
|
&InterfaceClassGuid,
|
|
device_index,
|
|
&device_interface_data);
|
|
|
|
if (!res) {
|
|
/* A return of FALSE from this function means that
|
|
there are no more devices. */
|
|
break;
|
|
}
|
|
|
|
/* Call with 0-sized detail size, and let the function
|
|
tell us how long the detail struct needs to be. The
|
|
size is put in &required_size. */
|
|
res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
|
|
&device_interface_data,
|
|
NULL,
|
|
0,
|
|
&required_size,
|
|
NULL);
|
|
|
|
/* Allocate a long enough structure for device_interface_detail_data. */
|
|
device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size);
|
|
device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
|
|
|
|
/* Get the detailed data for this device. The detail data gives us
|
|
the device path for this device, which is then passed into
|
|
CreateFile() to get a handle to the device. */
|
|
res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
|
|
&device_interface_data,
|
|
device_interface_detail_data,
|
|
required_size,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (!res) {
|
|
/* register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail");
|
|
Continue to the next device. */
|
|
goto cont;
|
|
}
|
|
|
|
/* Make sure this device is of Setup Class "HIDClass" and has a
|
|
driver bound to it. */
|
|
for (i = 0; ; i++) {
|
|
char driver_name[256];
|
|
|
|
/* Populate devinfo_data. This function will return failure
|
|
when there are no more interfaces left. */
|
|
res = SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data);
|
|
if (!res)
|
|
goto cont;
|
|
|
|
res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
|
|
SPDRP_CLASS, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
|
|
if (!res)
|
|
goto cont;
|
|
|
|
if ((strcmp(driver_name, "HIDClass") == 0) ||
|
|
(strcmp(driver_name, "Mouse") == 0) ||
|
|
(strcmp(driver_name, "Keyboard") == 0)) {
|
|
/* See if there's a driver bound. */
|
|
res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
|
|
SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
|
|
if (res)
|
|
break;
|
|
}
|
|
}
|
|
|
|
//wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath);
|
|
|
|
/* Open a handle to the device */
|
|
write_handle = open_device(device_interface_detail_data->DevicePath, FALSE);
|
|
|
|
/* Check validity of write_handle. */
|
|
if (write_handle == INVALID_HANDLE_VALUE) {
|
|
/* Unable to open the device. */
|
|
//register_error(dev, "CreateFile");
|
|
goto cont_close;
|
|
}
|
|
|
|
|
|
/* Get the Vendor ID and Product ID for this device. */
|
|
attrib.Size = sizeof(HIDD_ATTRIBUTES);
|
|
HidD_GetAttributes(write_handle, &attrib);
|
|
//wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID);
|
|
|
|
/* 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)) {
|
|
|
|
#define WSTR_LEN 512
|
|
const char *str;
|
|
struct hid_device_info *tmp;
|
|
PHIDP_PREPARSED_DATA pp_data = NULL;
|
|
HIDP_CAPS caps;
|
|
NTSTATUS nt_res;
|
|
wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */
|
|
size_t len;
|
|
|
|
/* VID/PID match. Create the record. */
|
|
tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
|
|
if (cur_dev) {
|
|
cur_dev->next = tmp;
|
|
}
|
|
else {
|
|
root = tmp;
|
|
}
|
|
cur_dev = tmp;
|
|
|
|
/* Get the Usage Page and Usage for this device. */
|
|
res = HidD_GetPreparsedData(write_handle, &pp_data);
|
|
if (res) {
|
|
nt_res = HidP_GetCaps(pp_data, &caps);
|
|
if (nt_res == HIDP_STATUS_SUCCESS) {
|
|
cur_dev->usage_page = caps.UsagePage;
|
|
cur_dev->usage = caps.Usage;
|
|
}
|
|
|
|
HidD_FreePreparsedData(pp_data);
|
|
}
|
|
|
|
/* Fill out the record */
|
|
cur_dev->next = NULL;
|
|
str = device_interface_detail_data->DevicePath;
|
|
if (str) {
|
|
len = strlen(str);
|
|
cur_dev->path = (char*) calloc(len+1, sizeof(char));
|
|
//strncpy(cur_dev->path, str, len+1); // produces warning
|
|
strlcpy(cur_dev->path, str, len+1);
|
|
cur_dev->path[len] = '\0';
|
|
}
|
|
else
|
|
cur_dev->path = NULL;
|
|
|
|
/* Serial Number */
|
|
wstr[0]= 0x0000;
|
|
res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr));
|
|
wstr[WSTR_LEN-1] = 0x0000;
|
|
if (res) {
|
|
cur_dev->serial_number = _wcsdup(wstr);
|
|
}
|
|
|
|
/* Manufacturer String */
|
|
wstr[0]= 0x0000;
|
|
res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr));
|
|
wstr[WSTR_LEN-1] = 0x0000;
|
|
if (res) {
|
|
cur_dev->manufacturer_string = _wcsdup(wstr);
|
|
}
|
|
|
|
/* Product String */
|
|
wstr[0]= 0x0000;
|
|
res = HidD_GetProductString(write_handle, wstr, sizeof(wstr));
|
|
wstr[WSTR_LEN-1] = 0x0000;
|
|
if (res) {
|
|
cur_dev->product_string = _wcsdup(wstr);
|
|
}
|
|
|
|
/* VID/PID */
|
|
cur_dev->vendor_id = attrib.VendorID;
|
|
cur_dev->product_id = attrib.ProductID;
|
|
|
|
/* Release Number */
|
|
cur_dev->release_number = attrib.VersionNumber;
|
|
|
|
/* Interface Number. It can sometimes be parsed out of the path
|
|
on Windows if a device has multiple interfaces. See
|
|
http://msdn.microsoft.com/en-us/windows/hardware/gg487473 or
|
|
search for "Hardware IDs for HID Devices" at MSDN. If it's not
|
|
in the path, it's set to -1. */
|
|
cur_dev->interface_number = -1;
|
|
if (cur_dev->path) {
|
|
char *interface_component = strstr(cur_dev->path, "&mi_");
|
|
if (interface_component) {
|
|
char *hex_str = interface_component + 4;
|
|
char *endptr = NULL;
|
|
cur_dev->interface_number = strtol(hex_str, &endptr, 16);
|
|
if (endptr == hex_str) {
|
|
/* The parsing failed. Set interface_number to -1. */
|
|
cur_dev->interface_number = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cont_close:
|
|
CloseHandle(write_handle);
|
|
cont:
|
|
/* We no longer need the detail data. It can be freed */
|
|
free(device_interface_detail_data);
|
|
|
|
device_index++;
|
|
|
|
}
|
|
|
|
/* Close the device information handle. */
|
|
SetupDiDestroyDeviceInfoList(device_info_set);
|
|
|
|
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;
|
|
|
|
devs = hid_enumerate(vendor_id, product_id);
|
|
cur_dev = devs;
|
|
while (cur_dev) {
|
|
if (cur_dev->vendor_id == vendor_id &&
|
|
cur_dev->product_id == product_id) {
|
|
if (serial_number) {
|
|
if (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);
|
|
}
|
|
|
|
hid_free_enumeration(devs);
|
|
|
|
return handle;
|
|
}
|
|
|
|
HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
|
|
{
|
|
hid_device *dev;
|
|
HIDP_CAPS caps;
|
|
PHIDP_PREPARSED_DATA pp_data = NULL;
|
|
BOOLEAN res;
|
|
NTSTATUS nt_res;
|
|
|
|
if (hid_init() < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
dev = new_hid_device();
|
|
|
|
/* Open a handle to the device */
|
|
dev->device_handle = open_device(path, TRUE);
|
|
|
|
/* Check validity of write_handle. */
|
|
if (dev->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. */
|
|
dev->device_handle = open_device(path, FALSE);
|
|
|
|
/* Check the validity of the limited device_handle. */
|
|
if (dev->device_handle == INVALID_HANDLE_VALUE) {
|
|
/* Unable to open the device, even without read-write mode. */
|
|
register_error(dev, "CreateFile");
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
/* Set the Input Report buffer size to 64 reports. */
|
|
res = HidD_SetNumInputBuffers(dev->device_handle, 64);
|
|
if (!res) {
|
|
register_error(dev, "HidD_SetNumInputBuffers");
|
|
goto err;
|
|
}
|
|
|
|
/* Get the Input Report length for the device. */
|
|
res = HidD_GetPreparsedData(dev->device_handle, &pp_data);
|
|
if (!res) {
|
|
register_error(dev, "HidD_GetPreparsedData");
|
|
goto err;
|
|
}
|
|
nt_res = HidP_GetCaps(pp_data, &caps);
|
|
if (nt_res != HIDP_STATUS_SUCCESS) {
|
|
register_error(dev, "HidP_GetCaps");
|
|
goto err_pp_data;
|
|
}
|
|
dev->output_report_length = caps.OutputReportByteLength;
|
|
dev->input_report_length = caps.InputReportByteLength;
|
|
dev->feature_report_length = caps.FeatureReportByteLength;
|
|
HidD_FreePreparsedData(pp_data);
|
|
|
|
dev->read_buf = (char*) malloc(dev->input_report_length);
|
|
|
|
return dev;
|
|
|
|
err_pp_data:
|
|
HidD_FreePreparsedData(pp_data);
|
|
err:
|
|
free_hid_device(dev);
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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,
|
|
create a 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 {
|
|
/* Create a temporary buffer and copy the user's data
|
|
into it, padding the rest with zeros. */
|
|
buf = (unsigned char *) malloc(dev->output_report_length);
|
|
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_error(dev, "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_error(dev, "WriteFile/WaitForSingleObject Timeout");
|
|
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_error(dev, "WriteFile");
|
|
goto end_of_function;
|
|
}
|
|
}
|
|
|
|
end_of_function:
|
|
if (buf != data)
|
|
free(buf);
|
|
|
|
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;
|
|
|
|
/* 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. */
|
|
CancelIo(dev->device_handle);
|
|
dev->read_pending = FALSE;
|
|
goto end_of_function;
|
|
}
|
|
overlapped = TRUE;
|
|
}
|
|
}
|
|
else {
|
|
overlapped = TRUE;
|
|
}
|
|
|
|
if (overlapped) {
|
|
if (milliseconds >= 0) {
|
|
/* See if there is any data yet. */
|
|
res = WaitForSingleObject(ev, milliseconds);
|
|
if (res != WAIT_OBJECT_0) {
|
|
/* There was no data this time. Return zero bytes available,
|
|
but leave the Overlapped I/O running. */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Either WaitForSingleObject() told us that ReadFile has completed, or
|
|
we are in non-blocking mode. Get the number of bytes read. The actual
|
|
data has been copied to the data[] array which was passed to ReadFile(). */
|
|
res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/);
|
|
}
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
end_of_function:
|
|
if (!res) {
|
|
register_error(dev, "GetOverlappedResult");
|
|
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;
|
|
|
|
/* 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_error(dev, "HidD_SetFeature");
|
|
return -1;
|
|
}
|
|
|
|
return (int) length;
|
|
}
|
|
|
|
|
|
int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
|
|
{
|
|
BOOL res;
|
|
#if 0
|
|
res = HidD_GetFeature(dev->device_handle, data, length);
|
|
if (!res) {
|
|
register_error(dev, "HidD_GetFeature");
|
|
return -1;
|
|
}
|
|
return 0; /* HidD_GetFeature() doesn't give us an actual length, unfortunately */
|
|
#else
|
|
DWORD bytes_returned;
|
|
|
|
OVERLAPPED ol;
|
|
memset(&ol, 0, sizeof(ol));
|
|
|
|
res = DeviceIoControl(dev->device_handle,
|
|
IOCTL_HID_GET_FEATURE,
|
|
data, (DWORD) length,
|
|
data, (DWORD) length,
|
|
&bytes_returned, &ol);
|
|
|
|
if (!res) {
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
/* DeviceIoControl() failed. Return error. */
|
|
register_error(dev, "Send 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_error(dev, "Send Feature Report GetOverLappedResult");
|
|
return -1;
|
|
}
|
|
|
|
/* bytes_returned does not include the first byte which contains the
|
|
report ID. The data buffer actually contains one more byte than
|
|
bytes_returned. */
|
|
bytes_returned++;
|
|
|
|
return bytes_returned;
|
|
#endif
|
|
}
|
|
|
|
|
|
int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
|
|
{
|
|
BOOL res;
|
|
#if 0
|
|
res = HidD_GetInputReport(dev->device_handle, data, length);
|
|
if (!res) {
|
|
register_error(dev, "HidD_GetInputReport");
|
|
return -1;
|
|
}
|
|
return length;
|
|
#else
|
|
DWORD bytes_returned;
|
|
|
|
OVERLAPPED ol;
|
|
memset(&ol, 0, sizeof(ol));
|
|
|
|
res = DeviceIoControl(dev->device_handle,
|
|
IOCTL_HID_GET_INPUT_REPORT,
|
|
data, (DWORD) length,
|
|
data, (DWORD) length,
|
|
&bytes_returned, &ol);
|
|
|
|
if (!res) {
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
/* DeviceIoControl() failed. Return error. */
|
|
register_error(dev, "Send Input 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_error(dev, "Send Input Report GetOverLappedResult");
|
|
return -1;
|
|
}
|
|
|
|
/* bytes_returned does not include the first byte which contains the
|
|
report ID. The data buffer actually contains one more byte than
|
|
bytes_returned. */
|
|
bytes_returned++;
|
|
|
|
return bytes_returned;
|
|
#endif
|
|
}
|
|
|
|
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)
|
|
{
|
|
BOOL res;
|
|
|
|
res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
|
|
if (!res) {
|
|
register_error(dev, "HidD_GetManufacturerString");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
|
{
|
|
BOOL res;
|
|
|
|
res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
|
|
if (!res) {
|
|
register_error(dev, "HidD_GetProductString");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
|
{
|
|
BOOL res;
|
|
|
|
res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
|
|
if (!res) {
|
|
register_error(dev, "HidD_GetSerialNumberString");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
|
|
res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
|
|
if (!res) {
|
|
register_error(dev, "HidD_GetIndexedString");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
// Global error messages are not (yet) implemented on Windows.
|
|
return L"hid_error for global errors is not implemented yet";
|
|
}
|
|
|
|
|
|
/*#define PICPGM*/
|
|
/*#define S11*/
|
|
#define P32
|
|
#ifdef S11
|
|
unsigned short VendorID = 0xa0a0;
|
|
unsigned short ProductID = 0x0001;
|
|
#endif
|
|
|
|
#ifdef P32
|
|
unsigned short VendorID = 0x04d8;
|
|
unsigned short ProductID = 0x3f;
|
|
#endif
|
|
|
|
|
|
#ifdef PICPGM
|
|
unsigned short VendorID = 0x04d8;
|
|
unsigned short ProductID = 0x0033;
|
|
#endif
|
|
|
|
|
|
#if 0
|
|
int __cdecl main(int argc, char* argv[])
|
|
{
|
|
int res;
|
|
unsigned char buf[65];
|
|
|
|
UNREFERENCED_PARAMETER(argc);
|
|
UNREFERENCED_PARAMETER(argv);
|
|
|
|
/* Set up the command buffer. */
|
|
memset(buf,0x00,sizeof(buf));
|
|
buf[0] = 0;
|
|
buf[1] = 0x81;
|
|
|
|
|
|
/* Open the device. */
|
|
int handle = open(VendorID, ProductID, L"12345");
|
|
if (handle < 0)
|
|
printf("unable to open device\n");
|
|
|
|
|
|
/* Toggle LED (cmd 0x80) */
|
|
buf[1] = 0x80;
|
|
res = write(handle, buf, 65);
|
|
if (res < 0)
|
|
printf("Unable to write()\n");
|
|
|
|
/* Request state (cmd 0x81) */
|
|
buf[1] = 0x81;
|
|
write(handle, buf, 65);
|
|
if (res < 0)
|
|
printf("Unable to write() (2)\n");
|
|
|
|
/* Read requested state */
|
|
read(handle, buf, 65);
|
|
if (res < 0)
|
|
printf("Unable to read()\n");
|
|
|
|
/* Print out the returned buffer. */
|
|
for (int i = 0; i < 4; i++)
|
|
printf("buf[%d]: %d\n", i, buf[i]);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
} /* extern "C" */
|
|
#endif
|