From 8fea1ea42ec9c0f37e002f301f59041a7b5bd255 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 3 Sep 2024 19:34:44 +0200 Subject: [PATCH] Update hidapi --- libs/hidapi/README | 3 +- libs/hidapi/hidapi/hidapi.h | 381 +++- libs/hidapi/linux/hid.c | 1305 +++++++++---- libs/hidapi/mac/hid.c | 1111 +++++++---- libs/hidapi/mac/hidapi_darwin.h | 98 + libs/hidapi/windows/hid.c | 1633 +++++++++++------ libs/hidapi/windows/hidapi_cfgmgr32.h | 75 + .../windows/hidapi_descriptor_reconstruct.c | 987 ++++++++++ .../windows/hidapi_descriptor_reconstruct.h | 247 +++ libs/hidapi/windows/hidapi_hidclass.h | 38 + libs/hidapi/windows/hidapi_hidpi.h | 72 + libs/hidapi/windows/hidapi_hidsdi.h | 59 + libs/hidapi/windows/hidapi_winapi.h | 74 + libs/hidapi/wscript | 1 + 14 files changed, 4812 insertions(+), 1272 deletions(-) create mode 100644 libs/hidapi/mac/hidapi_darwin.h create mode 100644 libs/hidapi/windows/hidapi_cfgmgr32.h create mode 100644 libs/hidapi/windows/hidapi_descriptor_reconstruct.c create mode 100644 libs/hidapi/windows/hidapi_descriptor_reconstruct.h create mode 100644 libs/hidapi/windows/hidapi_hidclass.h create mode 100644 libs/hidapi/windows/hidapi_hidpi.h create mode 100644 libs/hidapi/windows/hidapi_hidsdi.h create mode 100644 libs/hidapi/windows/hidapi_winapi.h diff --git a/libs/hidapi/README b/libs/hidapi/README index eda4740b10..bcbe1d66fd 100644 --- a/libs/hidapi/README +++ b/libs/hidapi/README @@ -1,2 +1 @@ -http://www.signal11.us/oss/hidapi/ -hidapi-0.8.0-rc1-21-ga6a622f (2016-01-08) from https://github.com/signal11/hidapi +hidapi-0.14.0-35-gc3c79a7 (2024-08-21) from https://github.com/libusb/hidapi diff --git a/libs/hidapi/hidapi/hidapi.h b/libs/hidapi/hidapi/hidapi.h index a75dc5ac3e..62ca8a80e6 100644 --- a/libs/hidapi/hidapi/hidapi.h +++ b/libs/hidapi/hidapi/hidapi.h @@ -5,9 +5,9 @@ Alan Ott Signal 11 Software - 8/22/2009 + libusb/hidapi Team - Copyright 2009, All Rights Reserved. + Copyright 2023, All Rights Reserved. At the discretion of the user of this library, this software may be licensed under the terms of the @@ -17,7 +17,7 @@ files located at the root of the source distribution. These files may also be found in the public source code repository located at: - http://github.com/signal11/hidapi . + https://github.com/libusb/hidapi . ********************************************************/ /** @file @@ -29,22 +29,123 @@ #include -#if 0 // XXX we compile hidapi as static library +/* #480: this is to be refactored properly for v1.0 */ +#ifdef _WIN32 + #ifndef HID_API_NO_EXPORT_DEFINE #define HID_API_EXPORT __declspec(dllexport) - #define HID_API_CALL -#else - #define HID_API_EXPORT /**< API export macro */ - #define HID_API_CALL /**< API call macro */ + #endif #endif +#ifndef HID_API_EXPORT + #define HID_API_EXPORT /**< API export macro */ +#endif +/* To be removed in v1.0 */ +#define HID_API_CALL /**< API call macro */ #define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ +/** @brief Static/compile-time major version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_MAJOR 0 +/** @brief Static/compile-time minor version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_MINOR 15 +/** @brief Static/compile-time patch version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_PATCH 0 + +/* Helper macros */ +#define HID_API_AS_STR_IMPL(x) #x +#define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x) +#define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3) + +/** @brief Coverts a version as Major/Minor/Patch into a number: + <8 bit major><16 bit minor><8 bit patch>. + + This macro was added in version 0.12.0. + + Convenient function to be used for compile-time checks, like: + @code{.c} + #if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + @endcode + + @ingroup API +*/ +#define HID_API_MAKE_VERSION(mj, mn, p) (((mj) << 24) | ((mn) << 8) | (p)) + +/** @brief Static/compile-time version of the library. + + This macro was added in version 0.12.0. + + @see @ref HID_API_MAKE_VERSION. + + @ingroup API +*/ +#define HID_API_VERSION HID_API_MAKE_VERSION(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) + +/** @brief Static/compile-time string version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) + +/** @brief Maximum expected HID Report descriptor size in bytes. + + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + + @ingroup API +*/ +#define HID_API_MAX_REPORT_DESCRIPTOR_SIZE 4096 + #ifdef __cplusplus extern "C" { #endif + /** A structure to hold the version numbers. */ + struct hid_api_version { + int major; /**< major version number */ + int minor; /**< minor version number */ + int patch; /**< patch version number */ + }; + struct hid_device_; typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ + /** @brief HID underlying bus types. + + @ingroup API + */ + typedef enum { + /** Unknown bus type */ + HID_API_BUS_UNKNOWN = 0x00, + + /** USB bus + Specifications: + https://usb.org/hid */ + HID_API_BUS_USB = 0x01, + + /** Bluetooth or Bluetooth LE bus + Specifications: + https://www.bluetooth.com/specifications/specs/human-interface-device-profile-1-1-1/ + https://www.bluetooth.com/specifications/specs/hid-service-1-0/ + https://www.bluetooth.com/specifications/specs/hid-over-gatt-profile-1-0/ */ + HID_API_BUS_BLUETOOTH = 0x02, + + /** I2C bus + Specifications: + https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642101(v=vs.85) */ + HID_API_BUS_I2C = 0x03, + + /** SPI bus + Specifications: + https://www.microsoft.com/download/details.aspx?id=103325 */ + HID_API_BUS_SPI = 0x04, + } hid_bus_type; + /** hidapi info structure */ struct hid_device_info { /** Platform-specific device path */ @@ -63,19 +164,26 @@ extern "C" { /** Product string */ wchar_t *product_string; /** Usage Page for this Device/Interface - (Windows/Mac only). */ + (Windows/Mac/hidraw only) */ unsigned short usage_page; /** Usage for this Device/Interface - (Windows/Mac only).*/ + (Windows/Mac/hidraw only) */ unsigned short usage; /** The USB interface which this logical device - represents. Valid on both Linux implementations - in all cases, and valid on the Windows implementation - only if the device contains more than one interface. */ + represents. + + Valid only if the device is a USB HID device. + Set to -1 in all other cases. + */ int interface_number; /** Pointer to the next device */ struct hid_device_info *next; + + /** Underlying bus type + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + */ + hid_bus_type bus_type; }; @@ -87,11 +195,12 @@ extern "C" { needed. This function should be called at the beginning of execution however, if there is a chance of HIDAPI handles being opened by different threads simultaneously. - + @ingroup API @returns This function returns 0 on success and -1 on error. + Call hid_error(NULL) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_init(void); @@ -103,7 +212,7 @@ extern "C" { @ingroup API - @returns + @returns This function returns 0 on success and -1 on error. */ int HID_API_EXPORT HID_API_CALL hid_exit(void); @@ -123,21 +232,25 @@ extern "C" { @param product_id The Product ID (PID) of the types of device to open. - @returns - This function returns a pointer to a linked list of type - struct #hid_device, containing information about the HID devices - attached to the system, or NULL in the case of failure. Free - this linked list by calling hid_free_enumeration(). + @returns + This function returns a pointer to a linked list of type + struct #hid_device_info, containing information about the HID devices + attached to the system, + or NULL in the case of failure or if no HID devices present in the system. + Call hid_error(NULL) to get the failure reason. + + @note The returned value by this function must to be freed by calling hid_free_enumeration(), + when not needed anymore. */ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); /** @brief Free an enumeration Linked List - This function frees a linked list created by hid_enumerate(). + This function frees a linked list created by hid_enumerate(). @ingroup API - @param devs Pointer to a list of struct_device returned from - hid_enumerate(). + @param devs Pointer to a list of struct_device returned from + hid_enumerate(). */ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); @@ -151,11 +264,15 @@ extern "C" { @param vendor_id The Vendor ID (VID) of the device to open. @param product_id The Product ID (PID) of the device to open. @param serial_number The Serial Number of the device to open - (Optionally NULL). + (Optionally NULL). @returns This function returns a pointer to a #hid_device object on success or NULL on failure. + Call hid_error(NULL) to get the failure reason. + + @note The returned object must be freed by calling hid_close(), + when not needed anymore. */ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); @@ -166,11 +283,15 @@ extern "C" { Linux). @ingroup API - @param path The path name of the device to open + @param path The path name of the device to open @returns This function returns a pointer to a #hid_device object on success or NULL on failure. + Call hid_error(NULL) to get the failure reason. + + @note The returned object must be freed by calling hid_close(), + when not needed anymore. */ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); @@ -186,12 +307,12 @@ extern "C" { single report), followed by the report data (16 bytes). In this example, the length passed in would be 17. - hid_write() will send the data on the first OUT endpoint, if - one exists. If it does not, it will send the data through - the Control Endpoint (Endpoint 0). + hid_write() will send the data on the first interrupt OUT + endpoint, if one exists. If it does not the behaviour is as + @ref hid_send_output_report @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param data The data to send, including the report number as the first byte. @param length The length in bytes of the data to send. @@ -199,8 +320,9 @@ extern "C" { @returns This function returns the actual number of bytes written and -1 on error. + Call hid_error(dev) to get the failure reason. */ - int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length); + int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length); /** @brief Read an Input report from a HID device with timeout. @@ -209,7 +331,7 @@ extern "C" { contain the Report number if the device uses numbered reports. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into. @param length The number of bytes to read. For devices with multiple reports, make sure to read an extra byte for @@ -218,7 +340,9 @@ extern "C" { @returns This function returns the actual number of bytes read and - -1 on error. If no packet was available to be read within + -1 on error. + Call hid_error(dev) to get the failure reason. + If no packet was available to be read within the timeout period, this function returns 0. */ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); @@ -226,11 +350,11 @@ extern "C" { /** @brief Read an Input report from a HID device. Input reports are returned - to the host through the INTERRUPT IN endpoint. The first byte will + to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number if the device uses numbered reports. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into. @param length The number of bytes to read. For devices with multiple reports, make sure to read an extra byte for @@ -238,10 +362,12 @@ extern "C" { @returns This function returns the actual number of bytes read and - -1 on error. If no packet was available to be read and + -1 on error. + Call hid_error(dev) to get the failure reason. + If no packet was available to be read and the handle is in non-blocking mode, this function returns 0. */ - int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length); + int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length); /** @brief Set the device handle to be non-blocking. @@ -253,15 +379,16 @@ extern "C" { Nonblocking can be turned on and off at any time. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param nonblock enable or not the nonblocking reads - 1 to enable nonblocking - 0 to disable nonblocking. @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ - int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock); + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); /** @brief Send a Feature report to the device. @@ -279,7 +406,7 @@ extern "C" { in would be 17. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param data The data to send, including the report number as the first byte. @param length The length in bytes of the data to send, including @@ -288,8 +415,9 @@ extern "C" { @returns This function returns the actual number of bytes written and -1 on error. + Call hid_error(dev) to get the failure reason. */ - int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length); + int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length); /** @brief Get a feature report from a HID device. @@ -300,7 +428,7 @@ extern "C" { start in data[1]. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into, including the Report ID. Set the first byte of @p data[] to the Report ID of the report to be read, or set it to zero @@ -313,79 +441,218 @@ extern "C" { This function returns the number of bytes read plus one for the report ID (which is still in the first byte), or -1 on error. + Call hid_error(dev) to get the failure reason. */ - int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length); + int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length); + + /** @brief Send a Output report to the device. + + Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) + + Output reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_output_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_output_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + + This function sets the return value of hid_error(). + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + + @returns + This function returns the actual number of bytes written and + -1 on error. + + @see @ref hid_write + */ + int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device* dev, const unsigned char* data, size_t length); + + /** @brief Get a input report from a HID device. + + Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0) + + Set the first byte of @p data[] to the Report ID of the + report to be read. Make sure to allow space for this + extra byte in @p data[]. Upon return, the first byte will + still contain the Report ID, and the report data will + start in data[1]. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data A buffer to put the read data into, including + the Report ID. Set the first byte of @p data[] to the + Report ID of the report to be read, or set it to zero + if your device does not use numbered reports. + @param length The number of bytes to read, including an + extra byte for the report ID. The buffer can be longer + than the actual report. + + @returns + This function returns the number of bytes read plus + one for the report ID (which is still in the first + byte), or -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length); /** @brief Close a HID device. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). */ - void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device); + void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev); /** @brief Get The Manufacturer String from a HID device. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param string A wide string buffer to put the data into. @param maxlen The length of the buffer in multiples of wchar_t. @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ - int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen); + int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen); /** @brief Get The Product String from a HID device. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param string A wide string buffer to put the data into. @param maxlen The length of the buffer in multiples of wchar_t. @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ - int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen); + int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen); /** @brief Get The Serial Number String from a HID device. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param string A wide string buffer to put the data into. @param maxlen The length of the buffer in multiples of wchar_t. @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ - int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen); + int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); + + /** @brief Get The struct #hid_device_info from a HID device. + + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + + @ingroup API + @param dev A device handle returned from hid_open(). + + @returns + This function returns a pointer to the struct #hid_device_info + for this hid_device, or NULL in the case of failure. + Call hid_error(dev) to get the failure reason. + This struct is valid until the device is closed with hid_close(). + + @note The returned object is owned by the @p dev, and SHOULD NOT be freed by the user. + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev); /** @brief Get a string from a HID device, based on its string index. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param string_index The index of the string to get. @param string A wide string buffer to put the data into. @param maxlen The length of the buffer in multiples of wchar_t. @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ - int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen); + int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen); + + /** @brief Get a report descriptor from a HID device. + + Since version 0.14.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 14, 0) + + User has to provide a preallocated buffer where descriptor will be copied to. + The recommended size for preallocated buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param buf The buffer to copy descriptor into. + @param buf_size The size of the buffer in bytes. + + @returns + This function returns non-negative number of bytes actually copied, or -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size); /** @brief Get a string describing the last error which occurred. + This function is intended for logging/debugging purposes. + + This function guarantees to never return NULL. + If there was no error in the last function call - + the returned string clearly indicates that. + + Any HIDAPI function that can explicitly indicate an execution failure + (e.g. by an error code, or by returning NULL) - may set the error string, + to be returned by this function. + + Strings returned from hid_error() must not be freed by the user, + i.e. owned by HIDAPI library. + Device-specific error string may remain allocated at most until hid_close() is called. + Global error string may remain allocated at most until hid_exit() is called. + @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(), + or NULL to get the last non-device-specific error + (e.g. for errors in hid_open() or hid_enumerate()). @returns - This function returns a string containing the last error - which occurred or NULL if none has occurred. + A string describing the last error (if any). */ - HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device); + HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev); + + /** @brief Get a runtime version of the library. + + This function is thread-safe. + + @ingroup API + + @returns + Pointer to statically allocated struct, that contains version. + */ + HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void); + + + /** @brief Get a runtime version string of the library. + + This function is thread-safe. + + @ingroup API + + @returns + Pointer to statically allocated string, that contains version string. + */ + HID_API_EXPORT const char* HID_API_CALL hid_version_str(void); #ifdef __cplusplus } #endif #endif - diff --git a/libs/hidapi/linux/hid.c b/libs/hidapi/linux/hid.c index bc6429e4e1..cb8a78fd11 100644 --- a/libs/hidapi/linux/hid.c +++ b/libs/hidapi/linux/hid.c @@ -5,10 +5,9 @@ Alan Ott Signal 11 Software - 8/22/2009 - Linux Version - 6/2/2009 + libusb/hidapi Team - Copyright 2009, All Rights Reserved. + Copyright 2022, All Rights Reserved. At the discretion of the user of this library, this software may be licensed under the terms of the @@ -18,11 +17,9 @@ files located at the root of the source distribution. These files may also be found in the public source code repository located at: - http://github.com/signal11/hidapi . + https://github.com/libusb/hidapi . ********************************************************/ -#define _POSIX_C_SOURCE 200809L - /* C */ #include #include @@ -47,8 +44,13 @@ #include "hidapi.h" -/* Definitions from linux/hidraw.h. Since these are new, some distros - may not have header files which contain them. */ +#ifdef HIDAPI_ALLOW_BUILD_WORKAROUND_KERNEL_2_6_39 +/* This definitions first appeared in Linux Kernel 2.6.39 in linux/hidraw.h. + hidapi doesn't support kernels older than that, + so we don't define macros below explicitly, to fail builds on old kernels. + For those who really need this as a workaround (e.g. to be able to build on old build machines), + can workaround by defining the macro above. +*/ #ifndef HIDIOCSFEATURE #define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len) #endif @@ -56,59 +58,46 @@ #define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len) #endif +#endif -/* USB HID device property names */ -const char *device_string_names[] = { - "manufacturer", - "product", - "serial", -}; -/* Symbolic names for the properties above */ -enum device_string_id { - DEVICE_STRING_MANUFACTURER, - DEVICE_STRING_PRODUCT, - DEVICE_STRING_SERIAL, - - DEVICE_STRING_COUNT, -}; +// HIDIOCGINPUT and HIDIOCSOUTPUT are not defined in Linux kernel headers < 5.11. +// These definitions are from hidraw.h in Linux >= 5.11. +// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f43d3870cafa2a0f3854c1819c8385733db8f9ae +#ifndef HIDIOCGINPUT +#define HIDIOCGINPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len) +#endif +#ifndef HIDIOCSOUTPUT +#define HIDIOCSOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0B, len) +#endif struct hid_device_ { int device_handle; int blocking; - int uses_numbered_reports; + wchar_t *last_error_str; + struct hid_device_info* device_info; }; +static struct hid_api_version api_version = { + .major = HID_API_VERSION_MAJOR, + .minor = HID_API_VERSION_MINOR, + .patch = HID_API_VERSION_PATCH +}; -static __u32 kernel_version = 0; +static wchar_t *last_global_error_str = NULL; -static __u32 detect_kernel_version(void) -{ - struct utsname name; - int major, minor, release; - int ret; - - uname(&name); - ret = sscanf(name.release, "%d.%d.%d", &major, &minor, &release); - if (ret == 3) { - return KERNEL_VERSION(major, minor, release); - } - - ret = sscanf(name.release, "%d.%d", &major, &minor); - if (ret == 2) { - return KERNEL_VERSION(major, minor, 0); - } - - printf("Couldn't determine kernel version from version string \"%s\"\n", name.release); - return 0; -} static hid_device *new_hid_device(void) { - hid_device *dev = calloc(1, sizeof(hid_device)); + hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + if (dev == NULL) { + return NULL; + } + dev->device_handle = -1; dev->blocking = 1; - dev->uses_numbered_reports = 0; + dev->last_error_str = NULL; + dev->device_info = NULL; return dev; } @@ -124,7 +113,11 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) if ((size_t) -1 == wlen) { return wcsdup(L""); } - ret = calloc(wlen+1, sizeof(wchar_t)); + ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); + if (ret == NULL) { + /* as much as we can do at this point */ + return NULL; + } mbstowcs(ret, utf8, wlen+1); ret[wlen] = 0x0000; } @@ -132,6 +125,64 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) return ret; } + +/* Makes a copy of the given error message (and decoded according to the + * currently locale) into the wide string pointer pointed by error_str. + * The last stored error string is freed. + * Use register_error_str(NULL) to free the error message completely. */ +static void register_error_str(wchar_t **error_str, const char *msg) +{ + free(*error_str); + *error_str = utf8_to_wchar_t(msg); +} + +/* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */ +static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) +{ + char msg[256]; + vsnprintf(msg, sizeof(msg), format, args); + + register_error_str(error_str, msg); +} + +/* Set the last global error to be reported by hid_error(NULL). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored global error message is freed. + * Use register_global_error(NULL) to indicate "no error". */ +static void register_global_error(const char *msg) +{ + register_error_str(&last_global_error_str, msg); +} + +/* Similar to register_global_error, but allows passing a format string into this function. */ +static void register_global_error_format(const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&last_global_error_str, format, args); + va_end(args); +} + +/* Set the last error for a device to be reported by hid_error(dev). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored device error message is freed. + * Use register_device_error(dev, NULL) to indicate "no error". */ +static void register_device_error(hid_device *dev, const char *msg) +{ + register_error_str(&dev->last_error_str, msg); +} + +/* Similar to register_device_error, but you can pass a format string into this function. */ +static void register_device_error_format(hid_device *dev, const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&dev->last_error_str, format, args); + va_end(args); +} + /* Get an attribute value from a udev_device and return it as a whar_t string. The returned string must be freed with free() when done.*/ static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name) @@ -139,78 +190,397 @@ static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name) return utf8_to_wchar_t(udev_device_get_sysattr_value(dev, udev_name)); } -/* uses_numbered_reports() returns 1 if report_descriptor describes a device - which contains numbered reports. */ -static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { - unsigned int i = 0; +/* + * Gets the size of the HID item at the given position + * Returns 1 if successful, 0 if an invalid key + * Sets data_len and key_size when successful + */ +static int get_hid_item_size(const __u8 *report_descriptor, __u32 size, unsigned int pos, int *data_len, int *key_size) +{ + int key = report_descriptor[pos]; int size_code; - int data_len, key_size; - while (i < size) { - int key = report_descriptor[i]; + /* + * This is a Long Item. The next byte contains the + * length of the data section (value) for this key. + * See the HID specification, version 1.11, section + * 6.2.2.3, titled "Long Items." + */ + if ((key & 0xf0) == 0xf0) { + if (pos + 1 < size) + { + *data_len = report_descriptor[pos + 1]; + *key_size = 3; + return 1; + } + *data_len = 0; /* malformed report */ + *key_size = 0; + } - /* Check for the Report ID key */ - if (key == 0x85/*Report ID*/) { - /* This device has a Report ID, which means it uses - numbered reports. */ + /* + * This is a Short Item. The bottom two bits of the + * key contain the size code for the data section + * (value) for this key. Refer to the HID + * specification, version 1.11, section 6.2.2.2, + * titled "Short Items." + */ + size_code = key & 0x3; + switch (size_code) { + case 0: + case 1: + case 2: + *data_len = size_code; + *key_size = 1; + return 1; + case 3: + *data_len = 4; + *key_size = 1; + return 1; + default: + /* Can't ever happen since size_code is & 0x3 */ + *data_len = 0; + *key_size = 0; + break; + }; + + /* malformed report */ + return 0; +} + +/* + * Get bytes from a HID Report Descriptor. + * Only call with a num_bytes of 0, 1, 2, or 4. + */ +static __u32 get_hid_report_bytes(const __u8 *rpt, size_t len, size_t num_bytes, size_t cur) +{ + /* Return if there aren't enough bytes. */ + if (cur + num_bytes >= len) + return 0; + + if (num_bytes == 0) + return 0; + else if (num_bytes == 1) + return rpt[cur + 1]; + else if (num_bytes == 2) + return (rpt[cur + 2] * 256 + rpt[cur + 1]); + else if (num_bytes == 4) + return ( + rpt[cur + 4] * 0x01000000 + + rpt[cur + 3] * 0x00010000 + + rpt[cur + 2] * 0x00000100 + + rpt[cur + 1] * 0x00000001 + ); + else + return 0; +} + +/* + * Iterates until the end of a Collection. + * Assumes that *pos is exactly at the beginning of a Collection. + * Skips all nested Collection, i.e. iterates until the end of current level Collection. + * + * The return value is non-0 when an end of current Collection is found, + * 0 when error is occured (broken Descriptor, end of a Collection is found before its begin, + * or no Collection is found at all). + */ +static int hid_iterate_over_collection(const __u8 *report_descriptor, __u32 size, unsigned int *pos, int *data_len, int *key_size) +{ + int collection_level = 0; + + while (*pos < size) { + int key = report_descriptor[*pos]; + int key_cmd = key & 0xfc; + + /* Determine data_len and key_size */ + if (!get_hid_item_size(report_descriptor, size, *pos, data_len, key_size)) + return 0; /* malformed report */ + + switch (key_cmd) { + case 0xa0: /* Collection 6.2.2.4 (Main) */ + collection_level++; + break; + case 0xc0: /* End Collection 6.2.2.4 (Main) */ + collection_level--; + break; + } + + if (collection_level < 0) { + /* Broken descriptor or someone is using this function wrong, + * i.e. should be called exactly at the collection start */ + return 0; + } + + if (collection_level == 0) { + /* Found it! + * Also possible when called not at the collection start, but should not happen if used correctly */ return 1; } - //printf("key: %02hhx\n", key); - - if ((key & 0xf0) == 0xf0) { - /* This is a Long Item. The next byte contains the - length of the data section (value) for this key. - See the HID specification, version 1.11, section - 6.2.2.3, titled "Long Items." */ - if (i+1 < size) - data_len = report_descriptor[i+1]; - else - data_len = 0; /* malformed report */ - key_size = 3; - } - else { - /* This is a Short Item. The bottom two bits of the - key contain the size code for the data section - (value) for this key. Refer to the HID - specification, version 1.11, section 6.2.2.2, - titled "Short Items." */ - size_code = key & 0x3; - switch (size_code) { - case 0: - case 1: - case 2: - data_len = size_code; - break; - case 3: - data_len = 4; - break; - default: - /* Can't ever happen since size_code is & 0x3 */ - data_len = 0; - break; - }; - key_size = 1; - } - - /* Skip over this key and it's associated data */ - i += data_len + key_size; + *pos += *data_len + *key_size; } - /* Didn't find a Report ID key. Device doesn't use numbered reports. */ + return 0; /* Did not find the end of a Collection */ +} + +struct hid_usage_iterator { + unsigned int pos; + int usage_page_found; + unsigned short usage_page; +}; + +/* + * Retrieves the device's Usage Page and Usage from the report descriptor. + * The algorithm returns the current Usage Page/Usage pair whenever a new + * Collection is found and a Usage Local Item is currently in scope. + * Usage Local Items are consumed by each Main Item (See. 6.2.2.8). + * The algorithm should give similar results as Apple's: + * https://developer.apple.com/documentation/iokit/kiohiddeviceusagepairskey?language=objc + * Physical Collections are also matched (macOS does the same). + * + * This function can be called repeatedly until it returns non-0 + * Usage is found. pos is the starting point (initially 0) and will be updated + * to the next search position. + * + * The return value is 0 when a pair is found. + * 1 when finished processing descriptor. + * -1 on a malformed report. + */ +static int get_next_hid_usage(const __u8 *report_descriptor, __u32 size, struct hid_usage_iterator *ctx, unsigned short *usage_page, unsigned short *usage) +{ + int data_len, key_size; + int initial = ctx->pos == 0; /* Used to handle case where no top-level application collection is defined */ + + int usage_found = 0; + + while (ctx->pos < size) { + int key = report_descriptor[ctx->pos]; + int key_cmd = key & 0xfc; + + /* Determine data_len and key_size */ + if (!get_hid_item_size(report_descriptor, size, ctx->pos, &data_len, &key_size)) + return -1; /* malformed report */ + + switch (key_cmd) { + case 0x4: /* Usage Page 6.2.2.7 (Global) */ + ctx->usage_page = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos); + ctx->usage_page_found = 1; + break; + + case 0x8: /* Usage 6.2.2.8 (Local) */ + if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */ + ctx->usage_page = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos + 2); + ctx->usage_page_found = 1; + *usage = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos); + usage_found = 1; + } + else { + *usage = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos); + usage_found = 1; + } + break; + + case 0xa0: /* Collection 6.2.2.4 (Main) */ + if (!hid_iterate_over_collection(report_descriptor, size, &ctx->pos, &data_len, &key_size)) { + return -1; + } + + /* A pair is valid - to be reported when Collection is found */ + if (usage_found && ctx->usage_page_found) { + *usage_page = ctx->usage_page; + return 0; + } + + break; + } + + /* Skip over this key and its associated data */ + ctx->pos += data_len + key_size; + } + + /* If no top-level application collection is found and usage page/usage pair is found, pair is valid + https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */ + if (initial && usage_found && ctx->usage_page_found) { + *usage_page = ctx->usage_page; + return 0; /* success */ + } + + return 1; /* finished processing */ +} + +/* + * Retrieves the hidraw report descriptor from a file. + * When using this form, /device/report_descriptor, elevated privileges are not required. + */ +static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_descriptor *rpt_desc) +{ + int rpt_handle; + ssize_t res; + + rpt_handle = open(rpt_path, O_RDONLY | FD_CLOEXEC); + if (rpt_handle < 0) { + register_global_error_format("open failed (%s): %s", rpt_path, strerror(errno)); + return -1; + } + + /* + * Read in the Report Descriptor + * The sysfs file has a maximum size of 4096 (which is the same as HID_MAX_DESCRIPTOR_SIZE) so we should always + * be ok when reading the descriptor. + * In practice if the HID descriptor is any larger I suspect many other things will break. + */ + memset(rpt_desc, 0x0, sizeof(*rpt_desc)); + res = read(rpt_handle, rpt_desc->value, HID_MAX_DESCRIPTOR_SIZE); + if (res < 0) { + register_global_error_format("read failed (%s): %s", rpt_path, strerror(errno)); + } + rpt_desc->size = (__u32) res; + + close(rpt_handle); + return (int) res; +} + +/* return size of the descriptor, or -1 on failure */ +static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc) +{ + int res = -1; + /* Construct /device/report_descriptor */ + size_t rpt_path_len = strlen(sysfs_path) + 25 + 1; + char* rpt_path = (char*) calloc(1, rpt_path_len); + snprintf(rpt_path, rpt_path_len, "%s/device/report_descriptor", sysfs_path); + + res = get_hid_report_descriptor(rpt_path, rpt_desc); + free(rpt_path); + + return res; +} + +/* return non-zero if successfully parsed */ +static int parse_hid_vid_pid_from_uevent(const char *uevent, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + char tmp[1024]; + size_t uevent_len = strlen(uevent); + if (uevent_len > sizeof(tmp) - 1) + uevent_len = sizeof(tmp) - 1; + memcpy(tmp, uevent, uevent_len); + tmp[uevent_len] = '\0'; + + char *saveptr = NULL; + char *line; + char *key; + char *value; + + line = strtok_r(tmp, "\n", &saveptr); + while (line != NULL) { + /* line: "KEY=value" */ + key = line; + value = strchr(line, '='); + if (!value) { + goto next_line; + } + *value = '\0'; + value++; + + if (strcmp(key, "HID_ID") == 0) { + /** + * type vendor product + * HID_ID=0003:000005AC:00008242 + **/ + int ret = sscanf(value, "%x:%hx:%hx", bus_type, vendor_id, product_id); + if (ret == 3) { + return 1; + } + } + +next_line: + line = strtok_r(NULL, "\n", &saveptr); + } + + register_global_error("Couldn't find/parse HID_ID"); return 0; } +/* return non-zero if successfully parsed */ +static int parse_hid_vid_pid_from_uevent_path(const char *uevent_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + int handle; + ssize_t res; + + handle = open(uevent_path, O_RDONLY | FD_CLOEXEC); + if (handle < 0) { + register_global_error_format("open failed (%s): %s", uevent_path, strerror(errno)); + return 0; + } + + char buf[1024]; + res = read(handle, buf, sizeof(buf) - 1); /* -1 for '\0' at the end */ + close(handle); + + if (res < 0) { + register_global_error_format("read failed (%s): %s", uevent_path, strerror(errno)); + return 0; + } + + buf[res] = '\0'; + return parse_hid_vid_pid_from_uevent(buf, bus_type, vendor_id, product_id); +} + +/* return non-zero if successfully read/parsed */ +static int parse_hid_vid_pid_from_sysfs(const char *sysfs_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + int res = 0; + /* Construct /device/uevent */ + size_t uevent_path_len = strlen(sysfs_path) + 14 + 1; + char* uevent_path = (char*) calloc(1, uevent_path_len); + snprintf(uevent_path, uevent_path_len, "%s/device/uevent", sysfs_path); + + res = parse_hid_vid_pid_from_uevent_path(uevent_path, bus_type, vendor_id, product_id); + free(uevent_path); + + return res; +} + +static int get_hid_report_descriptor_from_hidraw(hid_device *dev, struct hidraw_report_descriptor *rpt_desc) +{ + int desc_size = 0; + + /* Get Report Descriptor Size */ + int res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); + if (res < 0) { + register_device_error_format(dev, "ioctl(GRDESCSIZE): %s", strerror(errno)); + return res; + } + + /* Get Report Descriptor */ + memset(rpt_desc, 0x0, sizeof(*rpt_desc)); + rpt_desc->size = desc_size; + res = ioctl(dev->device_handle, HIDIOCGRDESC, rpt_desc); + if (res < 0) { + register_device_error_format(dev, "ioctl(GRDESC): %s", strerror(errno)); + } + + return res; +} + /* * The caller is responsible for free()ing the (newly-allocated) character * strings pointed to by serial_number_utf8 and product_name_utf8 after use. */ -static int -parse_uevent_info(const char *uevent, int *bus_type, +static int parse_uevent_info(const char *uevent, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id, char **serial_number_utf8, char **product_name_utf8) { - char *tmp = strdup(uevent); + char tmp[1024]; + + if (!uevent) { + return 0; + } + + size_t uevent_len = strlen(uevent); + if (uevent_len > sizeof(tmp) - 1) + uevent_len = sizeof(tmp) - 1; + memcpy(tmp, uevent, uevent_len); + tmp[uevent_len] = '\0'; + char *saveptr = NULL; char *line; char *key; @@ -254,134 +624,288 @@ next_line: line = strtok_r(NULL, "\n", &saveptr); } - free(tmp); return (found_id && found_name && found_serial); } -static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t *string, size_t maxlen) +static struct hid_device_info * create_device_info_for_device(struct udev_device *raw_dev) { - struct udev *udev; - struct udev_device *udev_dev, *parent, *hid_dev; - struct stat s; - int ret = -1; - char *serial_number_utf8 = NULL; - char *product_name_utf8 = NULL; + struct hid_device_info *root = NULL; + struct hid_device_info *cur_dev = NULL; - /* Create the udev object */ - udev = udev_new(); - if (!udev) { - printf("Can't create udev\n"); - return -1; + const char *sysfs_path; + const char *dev_path; + const char *str; + struct udev_device *hid_dev; /* The device's HID udev node. */ + struct udev_device *usb_dev; /* The device's USB udev node. */ + struct udev_device *intf_dev; /* The device's interface (in the USB sense). */ + unsigned short dev_vid; + unsigned short dev_pid; + char *serial_number_utf8 = NULL; + char *product_name_utf8 = NULL; + unsigned bus_type; + int result; + struct hidraw_report_descriptor report_desc; + + sysfs_path = udev_device_get_syspath(raw_dev); + dev_path = udev_device_get_devnode(raw_dev); + + hid_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "hid", + NULL); + + if (!hid_dev) { + /* Unable to find parent hid device. */ + goto end; } - /* Get the dev_t (major/minor numbers) from the file handle. */ - ret = fstat(dev->device_handle, &s); - if (-1 == ret) - return ret; - /* Open a udev device from the dev_t. 'c' means character device. */ - udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); - if (udev_dev) { - hid_dev = udev_device_get_parent_with_subsystem_devtype( - udev_dev, - "hid", - NULL); - if (hid_dev) { - unsigned short dev_vid; - unsigned short dev_pid; - int bus_type; - size_t retm; + result = parse_uevent_info( + udev_device_get_sysattr_value(hid_dev, "uevent"), + &bus_type, + &dev_vid, + &dev_pid, + &serial_number_utf8, + &product_name_utf8); - ret = parse_uevent_info( - udev_device_get_sysattr_value(hid_dev, "uevent"), - &bus_type, - &dev_vid, - &dev_pid, - &serial_number_utf8, - &product_name_utf8); + if (!result) { + /* parse_uevent_info() failed for at least one field. */ + goto end; + } - if (bus_type == BUS_BLUETOOTH) { - switch (key) { - case DEVICE_STRING_MANUFACTURER: - wcsncpy(string, L"", maxlen); - ret = 0; - break; - case DEVICE_STRING_PRODUCT: - retm = mbstowcs(string, product_name_utf8, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - break; - case DEVICE_STRING_SERIAL: - retm = mbstowcs(string, serial_number_utf8, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - break; - case DEVICE_STRING_COUNT: - default: - ret = -1; - break; - } + /* Filter out unhandled devices right away */ + switch (bus_type) { + case BUS_BLUETOOTH: + case BUS_I2C: + case BUS_USB: + case BUS_SPI: + break; + + default: + goto end; + } + + /* Create the record. */ + root = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + if (!root) + goto end; + + cur_dev = root; + + /* Fill out the record */ + cur_dev->next = NULL; + cur_dev->path = dev_path? strdup(dev_path): NULL; + + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + + /* Serial Number */ + cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); + + /* Release Number */ + cur_dev->release_number = 0x0; + + /* Interface Number */ + cur_dev->interface_number = -1; + + switch (bus_type) { + case BUS_USB: + /* The device pointed to by raw_dev contains information about + the hidraw device. In order to get information about the + USB device, get the parent device with the + subsystem/devtype pair of "usb"/"usb_device". This will + be several levels up the tree, but the function will find + it. */ + usb_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "usb", + "usb_device"); + + /* uhid USB devices + * Since this is a virtual hid interface, no USB information will + * be available. */ + if (!usb_dev) { + /* Manufacturer and Product strings */ + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + break; } - else { - /* This is a USB device. Find its parent USB Device node. */ - parent = udev_device_get_parent_with_subsystem_devtype( - udev_dev, - "usb", - "usb_device"); - if (parent) { - const char *str; - const char *key_str = NULL; - if (key >= 0 && key < DEVICE_STRING_COUNT) { - key_str = device_string_names[key]; - } else { - ret = -1; - goto end; - } + cur_dev->manufacturer_string = copy_udev_string(usb_dev, "manufacturer"); + cur_dev->product_string = copy_udev_string(usb_dev, "product"); - str = udev_device_get_sysattr_value(parent, key_str); - if (str) { - /* Convert the string from UTF-8 to wchar_t */ - retm = mbstowcs(string, str, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - goto end; - } - } + cur_dev->bus_type = HID_API_BUS_USB; + + str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); + cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; + + /* Get a handle to the interface's udev node. */ + intf_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "usb", + "usb_interface"); + if (intf_dev) { + str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber"); + cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1; } + + break; + + case BUS_BLUETOOTH: + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + + cur_dev->bus_type = HID_API_BUS_BLUETOOTH; + + break; + case BUS_I2C: + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + + cur_dev->bus_type = HID_API_BUS_I2C; + + break; + + case BUS_SPI: + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + + cur_dev->bus_type = HID_API_BUS_SPI; + + break; + + default: + /* Unknown device type - this should never happen, as we + * check for USB and Bluetooth devices above */ + break; + } + + /* Usage Page and Usage */ + result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); + if (result >= 0) { + unsigned short page = 0, usage = 0; + struct hid_usage_iterator usage_iterator; + memset(&usage_iterator, 0, sizeof(usage_iterator)); + + /* + * Parse the first usage and usage page + * out of the report descriptor. + */ + if (!get_next_hid_usage(report_desc.value, report_desc.size, &usage_iterator, &page, &usage)) { + cur_dev->usage_page = page; + cur_dev->usage = usage; + } + + /* + * Parse any additional usage and usage pages + * out of the report descriptor. + */ + while (!get_next_hid_usage(report_desc.value, report_desc.size, &usage_iterator, &page, &usage)) { + /* Create new record for additional usage pairs */ + struct hid_device_info *tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + struct hid_device_info *prev_dev = cur_dev; + + if (!tmp) + continue; + cur_dev->next = tmp; + cur_dev = tmp; + + /* Update fields */ + cur_dev->path = dev_path? strdup(dev_path): NULL; + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL; + cur_dev->release_number = prev_dev->release_number; + cur_dev->interface_number = prev_dev->interface_number; + cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; + cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; + cur_dev->usage_page = page; + cur_dev->usage = usage; + cur_dev->bus_type = prev_dev->bus_type; } } end: - free(serial_number_utf8); - free(product_name_utf8); + free(serial_number_utf8); + free(product_name_utf8); + + return root; +} + +static struct hid_device_info * create_device_info_for_hid_device(hid_device *dev) { + struct udev *udev; + struct udev_device *udev_dev; + struct stat s; + int ret = -1; + struct hid_device_info *root = NULL; + + register_device_error(dev, NULL); + + /* Get the dev_t (major/minor numbers) from the file handle. */ + ret = fstat(dev->device_handle, &s); + if (-1 == ret) { + register_device_error(dev, "Failed to stat device handle"); + return NULL; + } + + /* Create the udev object */ + udev = udev_new(); + if (!udev) { + register_device_error(dev, "Couldn't create udev context"); + return NULL; + } + + /* Open a udev device from the dev_t. 'c' means character device. */ + udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); + if (udev_dev) { + root = create_device_info_for_device(udev_dev); + } + + if (!root) { + /* TODO: have a better error reporting via create_device_info_for_device */ + register_device_error(dev, "Couldn't create hid_device_info"); + } udev_device_unref(udev_dev); - /* parent and hid_dev don't need to be (and can't be) unref'd. - I'm not sure why, but they'll throw double-free() errors. */ udev_unref(udev); - return ret; + return root; +} + +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) { const char *locale; + /* indicate no error */ + register_global_error(NULL); + /* Set the locale if it's not set. */ locale = setlocale(LC_CTYPE, NULL); if (!locale) setlocale(LC_CTYPE, ""); - kernel_version = detect_kernel_version(); - return 0; } int HID_API_EXPORT hid_exit(void) { - /* Nothing to do for this in the Linux/hidraw implementation. */ + /* Free global error message */ + register_global_error(NULL); + return 0; } - struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { struct udev *udev; @@ -390,14 +914,14 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, struct hid_device_info *root = NULL; /* return object */ struct hid_device_info *cur_dev = NULL; - struct hid_device_info *prev_dev = NULL; /* previous device */ hid_init(); + /* register_global_error: global error is reset by hid_init */ /* Create the udev object */ udev = udev_new(); if (!udev) { - printf("Can't create udev\n"); + register_global_error("Couldn't create udev context"); return NULL; } @@ -410,163 +934,62 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, create a udev_device record for it */ udev_list_entry_foreach(dev_list_entry, devices) { const char *sysfs_path; - const char *dev_path; - const char *str; + unsigned short dev_vid = 0; + unsigned short dev_pid = 0; + unsigned bus_type = 0; struct udev_device *raw_dev; /* The device's hidraw udev node. */ - struct udev_device *hid_dev; /* The device's HID udev node. */ - struct udev_device *usb_dev; /* The device's USB udev node. */ - struct udev_device *intf_dev; /* The device's interface (in the USB sense). */ - unsigned short dev_vid; - unsigned short dev_pid; - char *serial_number_utf8 = NULL; - char *product_name_utf8 = NULL; - int bus_type; - int result; + struct hid_device_info * tmp; /* Get the filename of the /sys entry for the device and create a udev_device object (dev) representing it */ sysfs_path = udev_list_entry_get_name(dev_list_entry); + if (!sysfs_path) + continue; + + if (vendor_id != 0 || product_id != 0) { + if (!parse_hid_vid_pid_from_sysfs(sysfs_path, &bus_type, &dev_vid, &dev_pid)) + continue; + + if (vendor_id != 0 && vendor_id != dev_vid) + continue; + if (product_id != 0 && product_id != dev_pid) + continue; + } + raw_dev = udev_device_new_from_syspath(udev, sysfs_path); - dev_path = udev_device_get_devnode(raw_dev); + if (!raw_dev) + continue; - hid_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "hid", - NULL); - - if (!hid_dev) { - /* Unable to find parent hid device. */ - goto next; - } - - result = parse_uevent_info( - udev_device_get_sysattr_value(hid_dev, "uevent"), - &bus_type, - &dev_vid, - &dev_pid, - &serial_number_utf8, - &product_name_utf8); - - if (!result) { - /* parse_uevent_info() failed for at least one field. */ - goto next; - } - - if (bus_type != BUS_USB && bus_type != BUS_BLUETOOTH) { - /* We only know how to handle USB and BT devices. */ - goto next; - } - - /* Check the VID/PID against the arguments */ - if ((vendor_id == 0x0 || vendor_id == dev_vid) && - (product_id == 0x0 || product_id == dev_pid)) { - struct hid_device_info *tmp; - - /* VID/PID match. Create the record. */ - tmp = malloc(sizeof(struct hid_device_info)); + tmp = create_device_info_for_device(raw_dev); + if (tmp) { if (cur_dev) { cur_dev->next = tmp; } else { root = tmp; } - prev_dev = cur_dev; cur_dev = tmp; - /* Fill out the record */ - cur_dev->next = NULL; - cur_dev->path = dev_path? strdup(dev_path): NULL; - - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - - /* Serial Number */ - cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); - - /* Release Number */ - cur_dev->release_number = 0x0; - - /* Interface Number */ - cur_dev->interface_number = -1; - - switch (bus_type) { - case BUS_USB: - /* The device pointed to by raw_dev contains information about - the hidraw device. In order to get information about the - USB device, get the parent device with the - subsystem/devtype pair of "usb"/"usb_device". This will - be several levels up the tree, but the function will find - it. */ - usb_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "usb", - "usb_device"); - - if (!usb_dev) { - /* Free this device */ - free(cur_dev->serial_number); - free(cur_dev->path); - free(cur_dev); - - /* Take it off the device list. */ - if (prev_dev) { - prev_dev->next = NULL; - cur_dev = prev_dev; - } - else { - cur_dev = root = NULL; - } - - goto next; - } - - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_MANUFACTURER]); - cur_dev->product_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_PRODUCT]); - - /* Release Number */ - str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); - cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; - - /* Get a handle to the interface's udev node. */ - intf_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "usb", - "usb_interface"); - if (intf_dev) { - str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber"); - cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1; - } - - break; - - case BUS_BLUETOOTH: - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = wcsdup(L""); - cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); - - break; - - default: - /* Unknown device type - this should never happen, as we - * check for USB and Bluetooth devices above */ - break; + /* move the pointer to the tail of returned list */ + while (cur_dev->next != NULL) { + cur_dev = cur_dev->next; } } - next: - free(serial_number_utf8); - free(product_name_utf8); udev_device_unref(raw_dev); - /* hid_dev, usb_dev and intf_dev don't need to be (and can't be) - unref()d. It will cause a double-free() error. I'm not - sure why. */ } /* Free the enumerator and udev objects. */ udev_enumerate_unref(enumerate); udev_unref(udev); + if (root == NULL) { + if (vendor_id == 0 && product_id == 0) { + register_global_error("No HID devices found in the system."); + } else { + register_global_error("No HID devices with requested VID/PID found in the system."); + } + } + return root; } @@ -590,7 +1013,13 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const 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 == NULL) { + /* 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 && @@ -612,6 +1041,8 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const if (path_to_open) { /* Open the device */ handle = hid_open_path(path_to_open); + } else { + register_global_error("Device with requested VID/PID/(SerialNumber) not found"); } hid_free_enumeration(devs); @@ -624,44 +1055,33 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) hid_device *dev = NULL; hid_init(); + /* register_global_error: global error is reset by hid_init */ dev = new_hid_device(); + if (!dev) { + register_global_error("Couldn't allocate memory"); + return NULL; + } - /* OPEN HERE */ - dev->device_handle = open(path, O_RDWR); + dev->device_handle = open(path, O_RDWR | FD_CLOEXEC); - /* If we have a good handle, return it. */ - if (dev->device_handle > 0) { - - /* Get the report descriptor */ + if (dev->device_handle >= 0) { int res, desc_size = 0; - struct hidraw_report_descriptor rpt_desc; - memset(&rpt_desc, 0x0, sizeof(rpt_desc)); - - /* Get Report Descriptor Size */ + /* Make sure this is a HIDRAW device - responds to HIDIOCGRDESCSIZE */ res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); - if (res < 0) - perror("HIDIOCGRDESCSIZE"); - - - /* Get Report Descriptor */ - rpt_desc.size = desc_size; - res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc); if (res < 0) { - perror("HIDIOCGRDESC"); - } else { - /* Determine if this device uses numbered reports. */ - dev->uses_numbered_reports = - uses_numbered_reports(rpt_desc.value, - rpt_desc.size); + hid_close(dev); + register_global_error_format("ioctl(GRDESCSIZE) error for '%s', not a HIDRAW device?: %s", path, strerror(errno)); + return NULL; } return dev; } else { - /* Unable to open any devices. */ + /* Unable to open a device. */ free(dev); + register_global_error_format("Failed to open a device with path '%s': %s", path, strerror(errno)); return NULL; } } @@ -671,14 +1091,25 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t { int bytes_written; + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, strerror(errno)); + return -1; + } + bytes_written = write(dev->device_handle, data, length); + register_device_error(dev, (bytes_written == -1)? strerror(errno): NULL); + return bytes_written; } int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) { + /* Set device error to none */ + register_device_error(dev, NULL); + int bytes_read; if (milliseconds >= 0) { @@ -695,29 +1126,32 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t fds.events = POLLIN; fds.revents = 0; ret = poll(&fds, 1, milliseconds); - if (ret == -1 || ret == 0) { - /* Error or timeout */ + if (ret == 0) { + /* Timeout */ + return ret; + } + if (ret == -1) { + /* Error */ + register_device_error(dev, strerror(errno)); return ret; } else { /* Check for errors on the file descriptor. This will indicate a device disconnection. */ - if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) + if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) { + // We cannot use strerror() here as no -1 was returned from poll(). + register_device_error(dev, "hid_read_timeout: unexpected poll error (device disconnected)"); return -1; + } } } bytes_read = read(dev->device_handle, data, length); - if (bytes_read < 0 && (errno == EAGAIN || errno == EINPROGRESS)) - bytes_read = 0; - - if (bytes_read >= 0 && - kernel_version != 0 && - kernel_version < KERNEL_VERSION(2,6,34) && - dev->uses_numbered_reports) { - /* Work around a kernel bug. Chop off the first byte. */ - memmove(data, data+1, bytes_read); - bytes_read--; + if (bytes_read < 0) { + if (errno == EAGAIN || errno == EINPROGRESS) + bytes_read = 0; + else + register_device_error(dev, strerror(errno)); } return bytes_read; @@ -743,9 +1177,11 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char { int res; + register_device_error(dev, NULL); + res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data); if (res < 0) - perror("ioctl (SFEATURE)"); + register_device_error_format(dev, "ioctl (SFEATURE): %s", strerror(errno)); return res; } @@ -754,46 +1190,181 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, { int res; + register_device_error(dev, NULL); + res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data); if (res < 0) - perror("ioctl (GFEATURE)"); - + register_device_error_format(dev, "ioctl (GFEATURE): %s", strerror(errno)); return res; } +int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device *dev, const unsigned char *data, size_t length) +{ + int res; + + register_device_error(dev, NULL); + + res = ioctl(dev->device_handle, HIDIOCSOUTPUT(length), data); + if (res < 0) + register_device_error_format(dev, "ioctl (SOUTPUT): %s", strerror(errno)); + + return res; +} + +int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) +{ + int res; + + register_device_error(dev, NULL); + + res = ioctl(dev->device_handle, HIDIOCGINPUT(length), data); + if (res < 0) + register_device_error_format(dev, "ioctl (GINPUT): %s", strerror(errno)); + + return res; +} void HID_API_EXPORT hid_close(hid_device *dev) { if (!dev) return; + close(dev->device_handle); + + /* Free the device error message */ + register_device_error(dev, NULL); + + hid_free_enumeration(dev->device_info); + free(dev); } int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_MANUFACTURER, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->manufacturer_string) { + wcsncpy(string, info->manufacturer_string, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; } int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_PRODUCT, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->product_string) { + wcsncpy(string, info->product_string, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; } int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_SERIAL, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->serial_number) { + wcsncpy(string, info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; +} + + +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) { + // Lazy initialize device_info + dev->device_info = create_device_info_for_hid_device(dev); + } + + // create_device_info_for_hid_device will set an error if needed + return dev->device_info; } int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { + (void)string_index; + (void)string; + (void)maxlen; + + register_device_error(dev, "hid_get_indexed_string: not supported by hidraw"); + return -1; } +int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) +{ + struct hidraw_report_descriptor rpt_desc; + int res = get_hid_report_descriptor_from_hidraw(dev, &rpt_desc); + if (res < 0) { + /* error already registered */ + return res; + } + + if (rpt_desc.size < buf_size) { + buf_size = (size_t) rpt_desc.size; + } + + memcpy(buf, rpt_desc.value, buf_size); + + return (int) buf_size; +} + + +/* Passing in NULL means asking for the last global error message. */ HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { - return NULL; + if (dev) { + if (dev->last_error_str == NULL) + return L"Success"; + return dev->last_error_str; + } + + if (last_global_error_str == NULL) + return L"Success"; + return last_global_error_str; } diff --git a/libs/hidapi/mac/hid.c b/libs/hidapi/mac/hid.c index e0756a1588..e2b365c4f3 100644 --- a/libs/hidapi/mac/hid.c +++ b/libs/hidapi/mac/hid.c @@ -5,9 +5,9 @@ Alan Ott Signal 11 Software - 2010-07-03 + libusb/hidapi Team - Copyright 2010, All Rights Reserved. + Copyright 2022, All Rights Reserved. At the discretion of the user of this library, this software may be licensed under the terms of the @@ -17,7 +17,7 @@ files located at the root of the source distribution. These files may also be found in the public source code repository located at: - http://github.com/signal11/hidapi . + https://github.com/libusb/hidapi . ********************************************************/ /* See Apple Technical Note TN2187 for details on IOHidManager. */ @@ -25,7 +25,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -33,7 +36,7 @@ #include #include -#include "hidapi.h" +#include "hidapi_darwin.h" /* Barrier implementation because Mac OSX doesn't have pthread_barrier. It also doesn't have clock_gettime(). So much for POSIX and SUSv2. @@ -49,15 +52,17 @@ typedef struct pthread_barrier { static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) { - if(count == 0) { + (void) attr; + + if (count == 0) { errno = EINVAL; return -1; } - if(pthread_mutex_init(&barrier->mutex, 0) < 0) { + if (pthread_mutex_init(&barrier->mutex, 0) < 0) { return -1; } - if(pthread_cond_init(&barrier->cond, 0) < 0) { + if (pthread_cond_init(&barrier->cond, 0) < 0) { pthread_mutex_destroy(&barrier->mutex); return -1; } @@ -78,16 +83,18 @@ static int pthread_barrier_wait(pthread_barrier_t *barrier) { pthread_mutex_lock(&barrier->mutex); ++(barrier->count); - if(barrier->count >= barrier->trip_count) - { + if (barrier->count >= barrier->trip_count) { barrier->count = 0; - pthread_cond_broadcast(&barrier->cond); pthread_mutex_unlock(&barrier->mutex); + pthread_cond_broadcast(&barrier->cond); return 1; } - else - { - pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + else { + do { + pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + } + while (barrier->count != 0); + pthread_mutex_unlock(&barrier->mutex); return 0; } @@ -102,10 +109,23 @@ struct input_report { struct input_report *next; }; +static struct hid_api_version api_version = { + .major = HID_API_VERSION_MAJOR, + .minor = HID_API_VERSION_MINOR, + .patch = HID_API_VERSION_PATCH +}; + +/* - Run context - */ +static IOHIDManagerRef hid_mgr = 0x0; +static int is_macos_10_10_or_greater = 0; +static IOOptionBits device_open_options = 0; +static wchar_t *last_global_error_str = NULL; +/* --- */ + struct hid_device_ { IOHIDDeviceRef device_handle; + IOOptionBits open_options; int blocking; - int uses_numbered_reports; int disconnected; CFStringRef run_loop_mode; CFRunLoopRef run_loop; @@ -113,6 +133,7 @@ struct hid_device_ { uint8_t *input_report_buf; CFIndex max_input_report_len; struct input_report *input_reports; + struct hid_device_info* device_info; pthread_t thread; pthread_mutex_t mutex; /* Protects input_reports */ @@ -120,21 +141,28 @@ struct hid_device_ { pthread_barrier_t barrier; /* Ensures correct startup sequence */ pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */ int shutdown_thread; + wchar_t *last_error_str; }; static hid_device *new_hid_device(void) { - hid_device *dev = calloc(1, sizeof(hid_device)); + hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + if (dev == NULL) { + return NULL; + } + dev->device_handle = NULL; + dev->open_options = device_open_options; dev->blocking = 1; - dev->uses_numbered_reports = 0; dev->disconnected = 0; dev->run_loop_mode = NULL; dev->run_loop = NULL; dev->source = NULL; dev->input_report_buf = NULL; dev->input_reports = NULL; + dev->device_info = NULL; dev->shutdown_thread = 0; + dev->last_error_str = NULL; /* Thread objects */ pthread_mutex_init(&dev->mutex, NULL); @@ -167,6 +195,7 @@ static void free_hid_device(hid_device *dev) if (dev->source) CFRelease(dev->source); free(dev->input_report_buf); + hid_free_enumeration(dev->device_info); /* Clean up the thread objects */ pthread_barrier_destroy(&dev->shutdown_barrier); @@ -178,21 +207,102 @@ static void free_hid_device(hid_device *dev) free(dev); } -static IOHIDManagerRef hid_mgr = 0x0; - -#if 0 -static void register_error(hid_device *device, const char *op) +/* The caller must free the returned string with free(). */ +static wchar_t *utf8_to_wchar_t(const char *utf8) { + wchar_t *ret = NULL; + if (utf8) { + size_t wlen = mbstowcs(NULL, utf8, 0); + if ((size_t) -1 == wlen) { + return wcsdup(L""); + } + ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); + if (ret == NULL) { + /* as much as we can do at this point */ + return NULL; + } + mbstowcs(ret, utf8, wlen+1); + ret[wlen] = 0x0000; + } + + return ret; } -#endif +/* Makes a copy of the given error message (and decoded according to the + * currently locale) into the wide string pointer pointed by error_str. + * The last stored error string is freed. + * Use register_error_str(NULL) to free the error message completely. */ +static void register_error_str(wchar_t **error_str, const char *msg) +{ + free(*error_str); + *error_str = utf8_to_wchar_t(msg); +} + +/* Similar to register_error_str, but allows passing a format string with va_list args into this function. */ +static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) +{ + char msg[1024]; + vsnprintf(msg, sizeof(msg), format, args); + + register_error_str(error_str, msg); +} + +/* Set the last global error to be reported by hid_error(NULL). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored global error message is freed. + * Use register_global_error(NULL) to indicate "no error". */ +static void register_global_error(const char *msg) +{ + register_error_str(&last_global_error_str, msg); +} + +/* Similar to register_global_error, but allows passing a format string into this function. */ +static void register_global_error_format(const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&last_global_error_str, format, args); + va_end(args); +} + +/* Set the last error for a device to be reported by hid_error(dev). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored device error message is freed. + * Use register_device_error(dev, NULL) to indicate "no error". */ +static void register_device_error(hid_device *dev, const char *msg) +{ + register_error_str(&dev->last_error_str, msg); +} + +/* Similar to register_device_error, but you can pass a format string into this function. */ +static void register_device_error_format(hid_device *dev, const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&dev->last_error_str, format, args); + va_end(args); +} + + +static CFArrayRef get_array_property(IOHIDDeviceRef device, CFStringRef key) +{ + CFTypeRef ref = IOHIDDeviceGetProperty(device, key); + if (ref != NULL && CFGetTypeID(ref) == CFArrayGetTypeID()) { + return (CFArrayRef)ref; + } else { + return NULL; + } +} + static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) { CFTypeRef ref; - int32_t value; + int32_t value = 0; ref = IOHIDDeviceGetProperty(device, key); if (ref) { @@ -204,6 +314,41 @@ static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) return 0; } +static bool try_get_int_property(IOHIDDeviceRef device, CFStringRef key, int32_t *out_val) +{ + bool result = false; + CFTypeRef ref; + + ref = IOHIDDeviceGetProperty(device, key); + if (ref) { + if (CFGetTypeID(ref) == CFNumberGetTypeID()) { + result = CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, out_val); + } + } + return result; +} + +static bool try_get_ioregistry_int_property(io_service_t service, CFStringRef property, int32_t *out_val) +{ + bool result = false; + CFTypeRef ref = IORegistryEntryCreateCFProperty(service, property, kCFAllocatorDefault, 0); + + if (ref) { + if (CFGetTypeID(ref) == CFNumberGetTypeID()) { + result = CFNumberGetValue(ref, kCFNumberSInt32Type, out_val); + } + + CFRelease(ref); + } + + return result; +} + +static CFArrayRef get_usage_pairs(IOHIDDeviceRef device) +{ + return get_array_property(device, CFSTR(kIOHIDDeviceUsagePairsKey)); +} + static unsigned short get_vendor_id(IOHIDDeviceRef device) { return get_int_property(device, CFSTR(kIOHIDVendorIDKey)); @@ -226,11 +371,11 @@ static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t if (!len) return 0; - str = IOHIDDeviceGetProperty(device, prop); + str = (CFStringRef) IOHIDDeviceGetProperty(device, prop); buf[0] = 0; - if (str) { + if (str && CFGetTypeID(str) == CFStringGetTypeID()) { CFIndex str_len = CFStringGetLength(str); CFRange range; CFIndex used_buf_len; @@ -239,18 +384,18 @@ static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t len --; range.location = 0; - range.length = ((size_t)str_len > len)? len: (size_t)str_len; + range.length = ((size_t) str_len > len)? len: (size_t) str_len; chars_copied = CFStringGetBytes(str, range, kCFStringEncodingUTF32LE, - (char)'?', + (char) '?', FALSE, (UInt8*)buf, len * sizeof(wchar_t), &used_buf_len); - if (chars_copied == len) - buf[len] = 0; /* len is decremented above */ + if (chars_copied <= 0) + buf[0] = 0; else buf[chars_copied] = 0; @@ -281,69 +426,12 @@ static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) static wchar_t *dup_wcs(const wchar_t *s) { size_t len = wcslen(s); - wchar_t *ret = malloc((len+1)*sizeof(wchar_t)); + wchar_t *ret = (wchar_t*) malloc((len+1)*sizeof(wchar_t)); wcscpy(ret, s); return ret; } -/* hidapi_IOHIDDeviceGetService() - * - * Return the io_service_t corresponding to a given IOHIDDeviceRef, either by: - * - on OS X 10.6 and above, calling IOHIDDeviceGetService() - * - on OS X 10.5, extract it from the IOHIDDevice struct - */ -static io_service_t hidapi_IOHIDDeviceGetService(IOHIDDeviceRef device) -{ - static void *iokit_framework = NULL; - static io_service_t (*dynamic_IOHIDDeviceGetService)(IOHIDDeviceRef device) = NULL; - - /* Use dlopen()/dlsym() to get a pointer to IOHIDDeviceGetService() if it exists. - * If any of these steps fail, dynamic_IOHIDDeviceGetService will be left NULL - * and the fallback method will be used. - */ - if (iokit_framework == NULL) { - iokit_framework = dlopen("/System/Library/IOKit.framework/IOKit", RTLD_LAZY); - - if (iokit_framework != NULL) - dynamic_IOHIDDeviceGetService = dlsym(iokit_framework, "IOHIDDeviceGetService"); - } - - if (dynamic_IOHIDDeviceGetService != NULL) { - /* Running on OS X 10.6 and above: IOHIDDeviceGetService() exists */ - return dynamic_IOHIDDeviceGetService(device); - } - else - { - /* Running on OS X 10.5: IOHIDDeviceGetService() doesn't exist. - * - * Be naughty and pull the service out of the IOHIDDevice. - * IOHIDDevice is an opaque struct not exposed to applications, but its - * layout is stable through all available versions of OS X. - * Tested and working on OS X 10.5.8 i386, x86_64, and ppc. - */ - struct IOHIDDevice_internal { - /* The first field of the IOHIDDevice struct is a - * CFRuntimeBase (which is a private CF struct). - * - * a, b, and c are the 3 fields that make up a CFRuntimeBase. - * See http://opensource.apple.com/source/CF/CF-476.18/CFRuntime.h - * - * The second field of the IOHIDDevice is the io_service_t we're looking for. - */ - uintptr_t a; - uint8_t b[4]; -#if __LP64__ - uint32_t c; -#endif - io_service_t service; - }; - struct IOHIDDevice_internal *tmp = (struct IOHIDDevice_internal *)device; - - return tmp->service; - } -} - /* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */ static int init_hid_manager(void) { @@ -355,15 +443,30 @@ static int init_hid_manager(void) return 0; } + register_global_error("Failed to create IOHIDManager"); return -1; } +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; +} + /* Initialize the IOHIDManager if necessary. This is the public function, and it is safe to call this function repeatedly. Return 0 for success and -1 for failure. */ int HID_API_EXPORT hid_init(void) { + register_global_error(NULL); + if (!hid_mgr) { + is_macos_10_10_or_greater = (kCFCoreFoundationVersionNumber >= 1151.16); /* kCFCoreFoundationVersionNumber10_10 */ + hid_darwin_set_open_exclusive(1); /* Backward compatibility */ return init_hid_manager(); } @@ -380,6 +483,9 @@ int HID_API_EXPORT hid_exit(void) hid_mgr = NULL; } + /* Free global error message */ + register_global_error(NULL); + return 0; } @@ -390,6 +496,206 @@ static void process_pending_events(void) { } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); } +static int read_usb_interface_from_hid_service_parent(io_service_t hid_service) +{ + int32_t result = -1; + bool success = false; + io_registry_entry_t current = IO_OBJECT_NULL; + kern_return_t res; + int parent_number = 0; + + res = IORegistryEntryGetParentEntry(hid_service, kIOServicePlane, ¤t); + while (KERN_SUCCESS == res + /* Only search up to 3 parent entries. + * With the default driver - the parent-of-interest supposed to be the first one, + * but lets assume some custom drivers or so, with deeper tree. */ + && parent_number < 3) { + io_registry_entry_t parent = IO_OBJECT_NULL; + int32_t interface_number = -1; + parent_number++; + + success = try_get_ioregistry_int_property(current, CFSTR(kUSBInterfaceNumber), &interface_number); + if (success) { + result = interface_number; + break; + } + + res = IORegistryEntryGetParentEntry(current, kIOServicePlane, &parent); + if (parent) { + IOObjectRelease(current); + current = parent; + } + + } + + if (current) { + IOObjectRelease(current); + current = IO_OBJECT_NULL; + } + + return result; +} + +static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, int32_t usage_page, int32_t usage) +{ + unsigned short dev_vid; + unsigned short dev_pid; + int BUF_LEN = 256; + wchar_t buf[BUF_LEN]; + CFTypeRef transport_prop; + + struct hid_device_info *cur_dev; + io_service_t hid_service; + kern_return_t res; + uint64_t entry_id = 0; + + if (dev == NULL) { + return NULL; + } + + cur_dev = (struct hid_device_info *)calloc(1, sizeof(struct hid_device_info)); + if (cur_dev == NULL) { + return NULL; + } + + dev_vid = get_vendor_id(dev); + dev_pid = get_product_id(dev); + + cur_dev->usage_page = usage_page; + cur_dev->usage = usage; + + /* Fill out the record */ + cur_dev->next = NULL; + + /* Fill in the path (as a unique ID of the service entry) */ + cur_dev->path = NULL; + hid_service = IOHIDDeviceGetService(dev); + if (hid_service != MACH_PORT_NULL) { + res = IORegistryEntryGetRegistryEntryID(hid_service, &entry_id); + } + else { + res = KERN_INVALID_ARGUMENT; + } + + if (res == KERN_SUCCESS) { + /* max value of entry_id(uint64_t) is 18446744073709551615 which is 20 characters long, + so for (max) "path" string 'DevSrvsID:18446744073709551615' we would need + 9+1+20+1=31 bytes buffer, but allocate 32 for simple alignment */ + const size_t path_len = 32; + cur_dev->path = calloc(1, path_len); + if (cur_dev->path != NULL) { + snprintf(cur_dev->path, path_len, "DevSrvsID:%llu", entry_id); + } + } + + if (cur_dev->path == NULL) { + /* for whatever reason, trying to keep it a non-NULL string */ + cur_dev->path = strdup(""); + } + + /* Serial Number */ + get_serial_number(dev, buf, BUF_LEN); + cur_dev->serial_number = dup_wcs(buf); + + /* Manufacturer and Product strings */ + get_manufacturer_string(dev, buf, BUF_LEN); + cur_dev->manufacturer_string = dup_wcs(buf); + get_product_string(dev, buf, BUF_LEN); + cur_dev->product_string = dup_wcs(buf); + + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + + /* Release Number */ + cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); + + /* Interface Number. + * We can only retrieve the interface number for USB HID devices. + * See below */ + cur_dev->interface_number = -1; + + /* Bus Type */ + transport_prop = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDTransportKey)); + + if (transport_prop != NULL && CFGetTypeID(transport_prop) == CFStringGetTypeID()) { + if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportUSBValue), 0) == kCFCompareEqualTo) { + int32_t interface_number = -1; + cur_dev->bus_type = HID_API_BUS_USB; + + /* A IOHIDDeviceRef used to have this simple property, + * until macOS 13.3 - we will try to use it. */ + if (try_get_int_property(dev, CFSTR(kUSBInterfaceNumber), &interface_number)) { + cur_dev->interface_number = interface_number; + } else { + /* Otherwise fallback to io_service_t property. + * (of one of the parent services). */ + cur_dev->interface_number = read_usb_interface_from_hid_service_parent(hid_service); + + /* If the above doesn't work - + * no (known) fallback exists at this point. */ + } + + /* Match "Bluetooth", "BluetoothLowEnergy" and "Bluetooth Low Energy" strings */ + } else if (CFStringHasPrefix((CFStringRef)transport_prop, CFSTR(kIOHIDTransportBluetoothValue))) { + cur_dev->bus_type = HID_API_BUS_BLUETOOTH; + } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportI2CValue), 0) == kCFCompareEqualTo) { + cur_dev->bus_type = HID_API_BUS_I2C; + } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportSPIValue), 0) == kCFCompareEqualTo) { + cur_dev->bus_type = HID_API_BUS_SPI; + } + } + + return cur_dev; +} + +static struct hid_device_info *create_device_info(IOHIDDeviceRef device) +{ + const int32_t primary_usage_page = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); + const int32_t primary_usage = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); + + /* Primary should always be first, to match previous behavior. */ + struct hid_device_info *root = create_device_info_with_usage(device, primary_usage_page, primary_usage); + struct hid_device_info *cur = root; + + if (!root) + return NULL; + + CFArrayRef usage_pairs = get_usage_pairs(device); + + if (usage_pairs != NULL) { + struct hid_device_info *next = NULL; + for (CFIndex i = 0; i < CFArrayGetCount(usage_pairs); i++) { + CFTypeRef dict = CFArrayGetValueAtIndex(usage_pairs, i); + if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) { + continue; + } + + CFTypeRef usage_page_ref, usage_ref; + int32_t usage_page, usage; + + if (!CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR(kIOHIDDeviceUsagePageKey), &usage_page_ref) || + !CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR(kIOHIDDeviceUsageKey), &usage_ref) || + CFGetTypeID(usage_page_ref) != CFNumberGetTypeID() || + CFGetTypeID(usage_ref) != CFNumberGetTypeID() || + !CFNumberGetValue((CFNumberRef)usage_page_ref, kCFNumberSInt32Type, &usage_page) || + !CFNumberGetValue((CFNumberRef)usage_ref, kCFNumberSInt32Type, &usage)) { + continue; + } + if (usage_page == primary_usage_page && usage == primary_usage) + continue; /* Already added. */ + + next = create_device_info_with_usage(device, usage_page, usage); + cur->next = next; + if (next != NULL) { + cur = next; + } + } + } + + return root; +} + struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { struct hid_device_info *root = NULL; /* return object */ @@ -398,93 +704,87 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, int i; /* Set up the HID Manager if it hasn't been done */ - if (hid_init() < 0) + if (hid_init() < 0) { return NULL; + } + /* register_global_error: global error is set/reset by hid_init */ /* give the IOHIDManager a chance to update itself */ process_pending_events(); /* Get a list of the Devices */ - IOHIDManagerSetDeviceMatching(hid_mgr, NULL); + CFMutableDictionaryRef matching = NULL; + if (vendor_id != 0 || product_id != 0) { + matching = CFDictionaryCreateMutable(kCFAllocatorDefault, kIOHIDOptionsTypeNone, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + if (matching && vendor_id != 0) { + CFNumberRef v = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &vendor_id); + CFDictionarySetValue(matching, CFSTR(kIOHIDVendorIDKey), v); + CFRelease(v); + } + + if (matching && product_id != 0) { + CFNumberRef p = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &product_id); + CFDictionarySetValue(matching, CFSTR(kIOHIDProductIDKey), p); + CFRelease(p); + } + } + IOHIDManagerSetDeviceMatching(hid_mgr, matching); + if (matching != NULL) { + CFRelease(matching); + } + CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); - /* Convert the list into a C array so we can iterate easily. */ - num_devices = CFSetGetCount(device_set); - IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); - CFSetGetValues(device_set, (const void **) device_array); + IOHIDDeviceRef *device_array = NULL; + + if (device_set != NULL) { + /* Convert the list into a C array so we can iterate easily. */ + num_devices = CFSetGetCount(device_set); + device_array = (IOHIDDeviceRef*) calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(device_set, (const void **) device_array); + } else { + num_devices = 0; + } /* Iterate over each device, making an entry for it. */ for (i = 0; i < num_devices; i++) { - unsigned short dev_vid; - unsigned short dev_pid; - #define BUF_LEN 256 - wchar_t buf[BUF_LEN]; IOHIDDeviceRef dev = device_array[i]; + if (!dev) { + continue; + } - if (!dev) { - continue; - } - dev_vid = get_vendor_id(dev); - dev_pid = get_product_id(dev); + struct hid_device_info *tmp = create_device_info(dev); + if (tmp == NULL) { + continue; + } - /* Check the VID/PID against the arguments */ - if ((vendor_id == 0x0 || vendor_id == dev_vid) && - (product_id == 0x0 || product_id == dev_pid)) { - struct hid_device_info *tmp; - io_object_t iokit_dev; - kern_return_t res; - io_string_t path; + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; - /* VID/PID match. Create the record. */ - tmp = malloc(sizeof(struct hid_device_info)); - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; - } - cur_dev = tmp; - - /* Get the Usage Page and Usage for this device. */ - cur_dev->usage_page = get_int_property(dev, CFSTR(kIOHIDPrimaryUsagePageKey)); - cur_dev->usage = get_int_property(dev, CFSTR(kIOHIDPrimaryUsageKey)); - - /* Fill out the record */ - cur_dev->next = NULL; - - /* Fill in the path (IOService plane) */ - iokit_dev = hidapi_IOHIDDeviceGetService(dev); - res = IORegistryEntryGetPath(iokit_dev, kIOServicePlane, path); - if (res == KERN_SUCCESS) - cur_dev->path = strdup(path); - else - cur_dev->path = strdup(""); - - /* Serial Number */ - get_serial_number(dev, buf, BUF_LEN); - cur_dev->serial_number = dup_wcs(buf); - - /* Manufacturer and Product strings */ - get_manufacturer_string(dev, buf, BUF_LEN); - cur_dev->manufacturer_string = dup_wcs(buf); - get_product_string(dev, buf, BUF_LEN); - cur_dev->product_string = dup_wcs(buf); - - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - - /* Release Number */ - cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); - - /* Interface Number (Unsupported on Mac)*/ - cur_dev->interface_number = -1; + /* move the pointer to the tail of returned list */ + while (cur_dev->next != NULL) { + cur_dev = cur_dev->next; } } free(device_array); - CFRelease(device_set); + if (device_set != NULL) + CFRelease(device_set); + + if (root == NULL) { + if (vendor_id == 0 && product_id == 0) { + register_global_error("No HID devices found in the system."); + } else { + register_global_error("No HID devices with requested VID/PID found in the system."); + } + } return root; } @@ -507,11 +807,18 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { /* This function is identical to the Linux version. Platform independent. */ + struct hid_device_info *devs, *cur_dev; const char *path_to_open = NULL; hid_device * handle = NULL; + /* register_global_error: global error is reset by hid_enumerate/hid_init */ devs = hid_enumerate(vendor_id, product_id); + if (devs == NULL) { + /* 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 && @@ -531,8 +838,9 @@ hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short pr } if (path_to_open) { - /* Open the device */ handle = hid_open_path(path_to_open); + } else { + register_global_error("Device with requested VID/PID/(SerialNumber) not found"); } hid_free_enumeration(devs); @@ -543,8 +851,11 @@ hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short pr static void hid_device_removal_callback(void *context, IOReturn result, void *sender) { + (void) result; + (void) sender; + /* Stop the Run Loop for this device. */ - hid_device *d = context; + hid_device *d = (hid_device*) context; d->disconnected = 1; CFRunLoopStop(d->run_loop); @@ -557,12 +868,17 @@ static void hid_report_callback(void *context, IOReturn result, void *sender, IOHIDReportType report_type, uint32_t report_id, uint8_t *report, CFIndex report_length) { + (void) result; + (void) sender; + (void) report_type; + (void) report_id; + struct input_report *rpt; - hid_device *dev = context; + hid_device *dev = (hid_device*) context; /* Make a new Input Report object */ - rpt = calloc(1, sizeof(struct input_report)); - rpt->data = calloc(1, report_length); + rpt = (struct input_report*) calloc(1, sizeof(struct input_report)); + rpt->data = (uint8_t*) calloc(1, report_length); memcpy(rpt->data, report, report_length); rpt->len = report_length; rpt->next = NULL; @@ -605,13 +921,13 @@ static void hid_report_callback(void *context, IOReturn result, void *sender, hid_close(), and serves to stop the read_thread's run loop. */ static void perform_signal_callback(void *context) { - hid_device *dev = context; + hid_device *dev = (hid_device*) context; CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/ } static void *read_thread(void *param) { - hid_device *dev = param; + hid_device *dev = (hid_device*) param; SInt32 code; /* Move the device's run loop to this thread. */ @@ -639,7 +955,7 @@ static void *read_thread(void *param) while (!dev->shutdown_thread && !dev->disconnected) { code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE); /* Return if the device has been disconnected */ - if (code == kCFRunLoopRunFinished) { + if (code == kCFRunLoopRunFinished || code == kCFRunLoopRunStopped) { dev->disconnected = 1; break; } @@ -673,26 +989,57 @@ static void *read_thread(void *param) return NULL; } -/* hid_open_path() - * - * path must be a valid path to an IOHIDDevice in the IOService plane - * Example: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver" - */ +/* \p path must be one of: + - in format 'DevSrvsID:' (as returned by hid_enumerate); + - a valid path to an IOHIDDevice in the IOService plane (as returned by IORegistryEntryGetPath, + e.g.: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"); + Second format is for compatibility with paths accepted by older versions of HIDAPI. +*/ +static io_registry_entry_t hid_open_service_registry_from_path(const char *path) +{ + if (path == NULL) + return MACH_PORT_NULL; + + /* Get the IORegistry entry for the given path */ + if (strncmp("DevSrvsID:", path, 10) == 0) { + char *endptr; + uint64_t entry_id = strtoull(path + 10, &endptr, 10); + if (*endptr == '\0') { + return IOServiceGetMatchingService((mach_port_t) 0, IORegistryEntryIDMatching(entry_id)); + } + } + else { + /* Fallback to older format of the path */ + return IORegistryEntryFromPath((mach_port_t) 0, path); + } + + return MACH_PORT_NULL; +} + hid_device * HID_API_EXPORT hid_open_path(const char *path) { hid_device *dev = NULL; io_registry_entry_t entry = MACH_PORT_NULL; - - dev = new_hid_device(); + IOReturn ret = kIOReturnInvalid; + char str[32]; /* Set up the HID Manager if it hasn't been done */ - if (hid_init() < 0) + if (hid_init() < 0) { return NULL; + } + /* register_global_error: global error is set/reset by hid_init */ + + dev = new_hid_device(); + if (!dev) { + register_global_error("Couldn't allocate memory"); + return NULL; + } /* Get the IORegistry entry for the given path */ - entry = IORegistryEntryFromPath(kIOMasterPortDefault, path); + entry = hid_open_service_registry_from_path(path); if (entry == MACH_PORT_NULL) { /* Path wasn't valid (maybe device was removed?) */ + register_global_error("hid_open_path: device mach entry not found with the given path"); goto return_error; } @@ -700,43 +1047,42 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry); if (dev->device_handle == NULL) { /* Error creating the HID device */ + register_global_error("hid_open_path: failed to create IOHIDDevice from the mach entry"); goto return_error; } /* Open the IOHIDDevice */ - IOReturn ret = IOHIDDeviceOpen(dev->device_handle, kIOHIDOptionsTypeSeizeDevice); - if (ret == kIOReturnSuccess) { - char str[32]; - - /* Create the buffers for receiving data */ - dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle); - dev->input_report_buf = calloc(dev->max_input_report_len, sizeof(uint8_t)); - - /* Create the Run Loop Mode for this device. - printing the reference seems to work. */ - sprintf(str, "HIDAPI_%p", dev->device_handle); - dev->run_loop_mode = - CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); - - /* Attach the device to a Run Loop */ - IOHIDDeviceRegisterInputReportCallback( - dev->device_handle, dev->input_report_buf, dev->max_input_report_len, - &hid_report_callback, dev); - IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); - - /* Start the read thread */ - pthread_create(&dev->thread, NULL, read_thread, dev); - - /* Wait here for the read thread to be initialized. */ - pthread_barrier_wait(&dev->barrier); - - IOObjectRelease(entry); - return dev; - } - else { + ret = IOHIDDeviceOpen(dev->device_handle, dev->open_options); + if (ret != kIOReturnSuccess) { + register_global_error_format("hid_open_path: failed to open IOHIDDevice from mach entry: (0x%08X) %s", ret, mach_error_string(ret)); goto return_error; } + /* Create the buffers for receiving data */ + dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle); + dev->input_report_buf = (uint8_t*) calloc(dev->max_input_report_len, sizeof(uint8_t)); + + /* Create the Run Loop Mode for this device. + printing the reference seems to work. */ + snprintf(str, sizeof(str), "HIDAPI_%p", (void*) dev->device_handle); + dev->run_loop_mode = + CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); + + /* Attach the device to a Run Loop */ + IOHIDDeviceRegisterInputReportCallback( + dev->device_handle, dev->input_report_buf, dev->max_input_report_len, + &hid_report_callback, dev); + IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); + + /* Start the read thread */ + pthread_create(&dev->thread, NULL, read_thread, dev); + + /* Wait here for the read thread to be initialized. */ + pthread_barrier_wait(&dev->barrier); + + IOObjectRelease(entry); + return dev; + return_error: if (dev->device_handle != NULL) CFRelease(dev->device_handle); @@ -750,41 +1096,83 @@ return_error: static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length) { - const unsigned char *data_to_send; - size_t length_to_send; + const unsigned char *data_to_send = data; + CFIndex length_to_send = length; IOReturn res; + unsigned char report_id; - /* Return if the device has been disconnected. */ - if (dev->disconnected) + register_device_error(dev, NULL); + + if (!data || (length == 0)) { + register_device_error(dev, strerror(EINVAL)); return -1; + } - if (data[0] == 0x0) { + report_id = data[0]; + + if (report_id == 0x0) { /* Not using numbered Reports. Don't send the report number. */ data_to_send = data+1; length_to_send = length-1; } - else { - /* Using numbered Reports. - Send the Report Number */ - data_to_send = data; - length_to_send = length; + + /* Avoid crash if the device has been unplugged. */ + if (dev->disconnected) { + register_device_error(dev, "Device is disconnected"); + return -1; } - if (!dev->disconnected) { - res = IOHIDDeviceSetReport(dev->device_handle, - type, - data[0], /* Report ID*/ - data_to_send, length_to_send); + res = IOHIDDeviceSetReport(dev->device_handle, + type, + report_id, + data_to_send, length_to_send); - if (res == kIOReturnSuccess) { - return length; - } - else - return -1; + if (res != kIOReturnSuccess) { + register_device_error_format(dev, "IOHIDDeviceSetReport failed: (0x%08X) %s", res, mach_error_string(res)); + return -1; } - return -1; + return (int) length; +} + +static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data, size_t length) +{ + unsigned char *report = data; + CFIndex report_length = length; + IOReturn res = kIOReturnSuccess; + const unsigned char report_id = data[0]; + + register_device_error(dev, NULL); + + if (report_id == 0x0) { + /* Not using numbered Reports. + Don't send the report number. */ + report = data+1; + report_length = length-1; + } + + /* Avoid crash if the device has been unplugged. */ + if (dev->disconnected) { + register_device_error(dev, "Device is disconnected"); + return -1; + } + + res = IOHIDDeviceGetReport(dev->device_handle, + type, + report_id, + report, &report_length); + + if (res != kIOReturnSuccess) { + register_device_error_format(dev, "IOHIDDeviceGetReport failed: (0x%08X) %s", res, mach_error_string(res)); + return -1; + } + + if (report_id == 0x0) { /* 0 report number still present at the beginning */ + report_length++; + } + + return (int) report_length; } int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) @@ -799,14 +1187,16 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length) return buffer (data), and delete the liked list item. */ struct input_report *rpt = dev->input_reports; size_t len = (length < rpt->len)? length: rpt->len; - memcpy(data, rpt->data, len); + if (data != NULL) { + memcpy(data, rpt->data, len); + } dev->input_reports = rpt->next; free(rpt->data); free(rpt); - return len; + return (int) len; } -static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) +static int cond_wait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) { while (!dev->input_reports) { int res = pthread_cond_wait(cond, mutex); @@ -814,19 +1204,20 @@ static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_ return res; /* A res of 0 means we may have been signaled or it may - be a spurious wakeup. Check to see that there's acutally + be a spurious wakeup. Check to see that there's actually data in the queue before returning, and if not, go back to sleep. See the pthread_cond_timedwait() man page for details. */ - if (dev->shutdown_thread || dev->disconnected) + if (dev->shutdown_thread || dev->disconnected) { return -1; + } } return 0; } -static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) +static int cond_timedwait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) { while (!dev->input_reports) { int res = pthread_cond_timedwait(cond, mutex, abstime); @@ -834,13 +1225,14 @@ static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_m return res; /* A res of 0 means we may have been signaled or it may - be a spurious wakeup. Check to see that there's acutally + be a spurious wakeup. Check to see that there's actually data in the queue before returning, and if not, go back to sleep. See the pthread_cond_timedwait() man page for details. */ - if (dev->shutdown_thread || dev->disconnected) + if (dev->shutdown_thread || dev->disconnected) { return -1; + } } return 0; @@ -864,6 +1256,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t /* Return if the device has been disconnected. */ if (dev->disconnected) { bytes_read = -1; + register_device_error(dev, "hid_read_timeout: device disconnected"); goto ret; } @@ -872,6 +1265,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t has been an error. An error code of -1 should be returned. */ bytes_read = -1; + register_device_error(dev, "hid_read_timeout: thread shutdown"); goto ret; } @@ -885,6 +1279,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t bytes_read = return_data(dev, data, length); else { /* There was an error, or a device disconnection. */ + register_device_error(dev, "hid_read_timeout: error waiting for more data"); bytes_read = -1; } } @@ -903,12 +1298,14 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t } res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts); - if (res == 0) + if (res == 0) { bytes_read = return_data(dev, data, length); - else if (res == ETIMEDOUT) + } else if (res == ETIMEDOUT) { bytes_read = 0; - else + } else { + register_device_error(dev, "hid_read_timeout: error waiting for more data"); bytes_read = -1; + } } else { /* Purely non-blocking */ @@ -941,31 +1338,28 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) { - CFIndex len = length; - IOReturn res; - - /* Return if the device has been unplugged. */ - if (dev->disconnected) - return -1; - - res = IOHIDDeviceGetReport(dev->device_handle, - kIOHIDReportTypeFeature, - data[0], /* Report ID */ - data, &len); - if (res == kIOReturnSuccess) - return len; - else - return -1; + return get_report(dev, kIOHIDReportTypeFeature, data, length); } +int HID_API_EXPORT hid_send_output_report(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, kIOHIDReportTypeOutput, data, length); +} + +int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) +{ + return get_report(dev, kIOHIDReportTypeInput, data, length); +} void HID_API_EXPORT hid_close(hid_device *dev) { if (!dev) return; - /* Disconnect the report callback before close. */ - if (!dev->disconnected) { + /* Disconnect the report callback before close. + See comment below. + */ + if (is_macos_10_10_or_greater || !dev->disconnected) { IOHIDDeviceRegisterInputReportCallback( dev->device_handle, dev->input_report_buf, dev->max_input_report_len, NULL, dev); @@ -989,9 +1383,15 @@ void HID_API_EXPORT hid_close(hid_device *dev) /* Close the OS handle to the device, but only if it's not been unplugged. If it's been unplugged, then calling - IOHIDDeviceClose() will crash. */ - if (!dev->disconnected) { - IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeSeizeDevice); + IOHIDDeviceClose() will crash. + + UPD: The crash part was true in/until some version of macOS. + Starting with macOS 10.15, there is an opposite effect in some environments: + crash happenes if IOHIDDeviceClose() is not called. + Not leaking a resource in all tested environments. + */ + if (is_macos_10_10_or_greater || !dev->disconnected) { + IOHIDDeviceClose(dev->device_handle, dev->open_options); } /* Clear out the queue of received reports. */ @@ -1007,104 +1407,151 @@ void HID_API_EXPORT hid_close(hid_device *dev) int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_manufacturer_string(dev->device_handle, string, maxlen); + if (!string || !maxlen) + { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) + { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->manufacturer_string, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; } int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_product_string(dev->device_handle, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->product_string, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; } int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_serial_number(dev->device_handle, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; +} + +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) { + dev->device_info = create_device_info(dev->device_handle); + if (!dev->device_info) { + register_device_error(dev, "Failed to create hid_device_info"); + } + } + + return dev->device_info; } int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { - /* TODO: */ + (void) dev; + (void) string_index; + (void) string; + (void) maxlen; - return 0; + register_device_error(dev, "hid_get_indexed_string: not available on this platform"); + return -1; } +int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id) +{ + int res = get_int_property(dev->device_handle, CFSTR(kIOHIDLocationIDKey)); + if (res != 0) { + *location_id = (uint32_t) res; + return 0; + } else { + register_device_error(dev, "Failed to get IOHIDLocationID property"); + return -1; + } +} + +void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive) +{ + device_open_options = (open_exclusive == 0) ? kIOHIDOptionsTypeNone : kIOHIDOptionsTypeSeizeDevice; +} + +int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void) +{ + return (device_open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0; +} + +int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev) +{ + if (!dev) + return -1; + + return (dev->open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0; +} + +int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) +{ + CFTypeRef ref = IOHIDDeviceGetProperty(dev->device_handle, CFSTR(kIOHIDReportDescriptorKey)); + if (ref != NULL && CFGetTypeID(ref) == CFDataGetTypeID()) { + CFDataRef report_descriptor = (CFDataRef) ref; + const UInt8 *descriptor_buf = CFDataGetBytePtr(report_descriptor); + CFIndex descriptor_buf_len = CFDataGetLength(report_descriptor); + size_t copy_len = (size_t) descriptor_buf_len; + + if (descriptor_buf == NULL || descriptor_buf_len < 0) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + if (buf_size < copy_len) { + copy_len = buf_size; + } + + memcpy(buf, descriptor_buf, copy_len); + return copy_len; + } + else { + register_device_error(dev, "Failed to get kIOHIDReportDescriptorKey property"); + return -1; + } +} HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { - /* TODO: */ - - return NULL; -} - - - - - - - -#if 0 -static int32_t get_location_id(IOHIDDeviceRef device) -{ - return get_int_property(device, CFSTR(kIOHIDLocationIDKey)); -} - -static int32_t get_usage(IOHIDDeviceRef device) -{ - int32_t res; - res = get_int_property(device, CFSTR(kIOHIDDeviceUsageKey)); - if (!res) - res = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); - return res; -} - -static int32_t get_usage_page(IOHIDDeviceRef device) -{ - int32_t res; - res = get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey)); - if (!res) - res = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); - return res; -} - -static int get_transport(IOHIDDeviceRef device, wchar_t *buf, size_t len) -{ - return get_string_property(device, CFSTR(kIOHIDTransportKey), buf, len); -} - - -int main(void) -{ - IOHIDManagerRef mgr; - int i; - - mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - IOHIDManagerSetDeviceMatching(mgr, NULL); - IOHIDManagerOpen(mgr, kIOHIDOptionsTypeNone); - - CFSetRef device_set = IOHIDManagerCopyDevices(mgr); - - CFIndex num_devices = CFSetGetCount(device_set); - IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); - CFSetGetValues(device_set, (const void **) device_array); - - for (i = 0; i < num_devices; i++) { - IOHIDDeviceRef dev = device_array[i]; - printf("Device: %p\n", dev); - printf(" %04hx %04hx\n", get_vendor_id(dev), get_product_id(dev)); - - wchar_t serial[256], buf[256]; - char cbuf[256]; - get_serial_number(dev, serial, 256); - - - printf(" Serial: %ls\n", serial); - printf(" Loc: %ld\n", get_location_id(dev)); - get_transport(dev, buf, 256); - printf(" Trans: %ls\n", buf); - make_path(dev, cbuf, 256); - printf(" Path: %s\n", cbuf); - + if (dev) { + if (dev->last_error_str == NULL) + return L"Success"; + return dev->last_error_str; } - return 0; + if (last_global_error_str == NULL) + return L"Success"; + return last_global_error_str; } -#endif diff --git a/libs/hidapi/mac/hidapi_darwin.h b/libs/hidapi/mac/hidapi_darwin.h new file mode 100644 index 0000000000..34c30a07de --- /dev/null +++ b/libs/hidapi/mac/hidapi_darwin.h @@ -0,0 +1,98 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + 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 . +********************************************************/ + +/** @file + * @defgroup API hidapi API + + * Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + */ + +#ifndef HIDAPI_DARWIN_H__ +#define HIDAPI_DARWIN_H__ + +#include + +#include "hidapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /** @brief Get the location ID for a HID device. + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + @ingroup API + @param dev A device handle returned from hid_open(). + @param location_id The device's location ID on return. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id); + + + /** @brief Changes the behavior of all further calls to @ref hid_open or @ref hid_open_path. + + By default on Darwin platform all devices opened by HIDAPI with @ref hid_open or @ref hid_open_path + are opened in exclusive mode (see kIOHIDOptionsTypeSeizeDevice). + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + @ingroup API + @param open_exclusive When set to 0 - all further devices will be opened + in non-exclusive mode. Otherwise - all further devices will be opened + in exclusive mode. + + @note During the initialisation by @ref hid_init - this property is set to 1 (TRUE). + This is done to preserve full backward compatibility with previous behavior. + + @note Calling this function before @ref hid_init or after @ref hid_exit has no effect. + */ + void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive); + + /** @brief Getter for option set by @ref hid_darwin_set_open_exclusive. + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + @ingroup API + @return 1 if all further devices will be opened in exclusive mode. + + @note Value returned by this function before calling to @ref hid_init or after @ref hid_exit + is not reliable. + */ + int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void); + + /** @brief Check how the device was opened. + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + @ingroup API + @param dev A device to get property from. + + @return 1 if the device is opened in exclusive mode, 0 - opened in non-exclusive, + -1 - if dev is invalid. + */ + int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/hidapi/windows/hid.c b/libs/hidapi/windows/hid.c index 86810d7e56..35c2de04e1 100755 --- a/libs/hidapi/windows/hid.c +++ b/libs/hidapi/windows/hid.c @@ -5,10 +5,10 @@ Alan Ott Signal 11 Software - 8/22/2009 + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. - 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 @@ -17,9 +17,21 @@ files located at the root of the source distribution. These files may also be found in the public source code repository located at: - http://github.com/signal11/hidapi . + 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 #ifndef _NTDEF_ @@ -29,133 +41,184 @@ typedef LONG NTSTATUS; #ifdef __MINGW32__ #include #include +#define WC_ERR_INVALID_CHARS 0x00000080 #endif #ifdef __CYGWIN__ #include +#include #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 - #include - #ifdef HIDAPI_USE_DDK - #include - #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) - -#ifdef __cplusplus -} /* extern "C" */ -#endif +#include "hidapi_cfgmgr32.h" +#include "hidapi_hidclass.h" +#include "hidapi_hidsdi.h" #include #include +#include - -#include "hidapi.h" - +#ifdef MIN #undef MIN +#endif #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 +/* MAXIMUM_USB_STRING_LENGTH from usbspec.h is 255 */ +/* BLUETOOTH_DEVICE_NAME_SIZE from bluetoothapis.h is 256 */ +#define MAX_STRING_WCHARS 256 -#ifdef __cplusplus -extern "C" { -#endif +/* 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 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; +/* 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. */ - 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 +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; - 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_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 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 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_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 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; +} - 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; + unsigned char *write_buf; size_t input_report_length; - void *last_error_str; - DWORD last_error_num; + 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->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 /*initial state f=nonsignaled*/, NULL); + dev->device_info = NULL; return dev; } @@ -163,75 +226,125 @@ static hid_device *new_hid_device() 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->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_error(hid_device *device, const char *op) +static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR *op) { - WCHAR *ptr, *msg; + free(*error_buffer); + *error_buffer = NULL; - FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, + /* 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, - GetLastError(), + error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPVOID)&msg, 0/*sz*/, + 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! */ - ptr = msg; - while (*ptr) { - if (*ptr == '\r') { - *ptr = 0x0000; - break; - } - ptr++; + 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--; } - - /* Store the message off in the Device entry so that - the hid_error() function can pick it up. */ - LocalFree(device->last_error_str); - device->last_error_str = msg; } -#ifndef HIDAPI_USE_DDK -static int lookup_functions() +#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) { - lib_handle = LoadLibraryA("hid.dll"); - if (lib_handle) { -#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_GetIndexedString); - RESOLVE(HidD_GetPreparsedData); - RESOLVE(HidD_FreePreparsedData); - RESOLVE(HidP_GetCaps); - RESOLVE(HidD_SetNumInputBuffers); -#undef RESOLVE - } - else - return -1; + free(*error_buffer); + *error_buffer = NULL; - return 0; + if (string_error) { + *error_buffer = _wcsdup(string_error); + } } + +#if defined(__GNUC__) +# pragma GCC diagnostic pop #endif -static HANDLE open_device(const char *path, BOOL enumerate) +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 = (enumerate)? 0: (GENERIC_WRITE | GENERIC_READ); + DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0; DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE; - handle = CreateFileA(path, + handle = CreateFileW(path, desired_access, share_mode, NULL, @@ -242,15 +355,26 @@ static HANDLE open_device(const char *path, BOOL enumerate) 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 (!initialized) { + if (!hidapi_initialized) { if (lookup_functions() < 0) { - hid_exit(); + register_global_winapi_error(L"resolve DLL functions"); return -1; } - initialized = TRUE; + hidapi_initialized = TRUE; } #endif return 0; @@ -259,149 +383,507 @@ int HID_API_EXPORT hid_init(void) int HID_API_EXPORT hid_exit(void) { #ifndef HIDAPI_USE_DDK - if (lib_handle) - FreeLibrary(lib_handle); - lib_handle = NULL; - initialized = FALSE; + 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) { - BOOL res; 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; - /* 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) + if (hid_init() < 0) { + /* register_global_error: global error is reset by hid_init */ 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); + /* Retrieve HID Interface Class GUID + https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */ + HidD_GetHidGuid(&interface_class_guid); - /* 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. */ + /* 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; } - /* 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; + if (device_interface_list != NULL) { + free(device_interface_list); } - /* 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) { - /* 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; - } + 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); - //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath); + if (cr != CR_SUCCESS) { + goto end_of_function; + } - /* Open a handle to the device */ - write_handle = open_device(device_interface_detail_data->DevicePath, TRUE); + /* 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; - /* Check validity of write_handle. */ - if (write_handle == INVALID_HANDLE_VALUE) { + /* 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. */ - //register_error(dev, "CreateFile"); - goto cont_close; - } - + continue; + } /* 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); + 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)) { - #define WSTR_LEN 512 - const char *str; - struct hid_device_info *tmp; - PHIDP_PREPARSED_DATA pp_data = NULL; - HIDP_CAPS caps; - BOOLEAN res; - 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)); + 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; } @@ -409,94 +891,24 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor 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); - cur_dev->path[len] = '\0'; - } - else - cur_dev->path = NULL; - - /* Serial Number */ - res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN-1] = 0x0000; - if (res) { - cur_dev->serial_number = _wcsdup(wstr); - } - - /* Manufacturer String */ - res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN-1] = 0x0000; - if (res) { - cur_dev->manufacturer_string = _wcsdup(wstr); - } - - /* Product String */ - 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++; - + CloseHandle(device_handle); } - /* Close the device information handle. */ - SetupDiDestroyDeviceInfoList(device_info_set); + 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) @@ -514,21 +926,26 @@ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *d } } - 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 (wcscmp(serial_number, cur_dev->serial_number) == 0) { + if (cur_dev->serial_number && wcscmp(serial_number, cur_dev->serial_number) == 0) { path_to_open = cur_dev->path; break; } @@ -544,123 +961,166 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi 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; - HIDP_CAPS caps; + hid_device *dev = NULL; + wchar_t* interface_path = NULL; + HANDLE device_handle = INVALID_HANDLE_VALUE; PHIDP_PREPARSED_DATA pp_data = NULL; - BOOLEAN res; - NTSTATUS nt_res; + HIDP_CAPS caps; if (hid_init() < 0) { - return NULL; + /* 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(); - /* Open a handle to the device */ - dev->device_handle = open_device(path, FALSE); - - /* Check validity of write_handle. */ - if (dev->device_handle == INVALID_HANDLE_VALUE) { - /* Unable to open the device. */ - register_error(dev, "CreateFile"); - goto err; + if (dev == NULL) { + register_global_error(L"hid_device allocation error"); + goto end_of_function; } - /* 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; - } + dev->device_handle = device_handle; + device_handle = INVALID_HANDLE_VALUE; - /* 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; - HidD_FreePreparsedData(pp_data); - + 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; - -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; + DWORD bytes_written = 0; + int function_result = -1; BOOL res; + BOOL overlapped = FALSE; - OVERLAPPED ol; unsigned char *buf; - memset(&ol, 0, sizeof(ol)); + + 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, - create a temporary buffer which is the proper size. */ + 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 { - /* 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); + 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, length, NULL, &ol); - + 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"); - bytes_written = -1; + 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; } } - /* Wait here until the write is done. This makes - hid_write() synchronous. */ - res = GetOverlappedResult(dev->device_handle, &ol, &bytes_written, TRUE/*wait*/); - if (!res) { - /* The Write operation failed. */ - register_error(dev, "WriteFile"); - bytes_written = -1; - goto end_of_function; - } - end_of_function: - if (buf != data) - free(buf); - - return bytes_written; + return function_result; } @@ -668,7 +1128,15 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char { DWORD bytes_read = 0; size_t copy_len = 0; - BOOL res; + 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; @@ -678,34 +1146,39 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char dev->read_pending = TRUE; memset(dev->read_buf, 0, dev->input_report_length); ResetEvent(ev); - res = ReadFile(dev->device_handle, dev->read_buf, dev->input_report_length, &bytes_read, &dev->ol); - + 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 (milliseconds >= 0) { + if (overlapped) { /* See if there is any data yet. */ - res = WaitForSingleObject(ev, milliseconds); + 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; } - } - /* 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*/); - + /* 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; @@ -725,14 +1198,16 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char memcpy(data, dev->read_buf, copy_len); } } - + if (!res) { + register_winapi_error(dev, L"hid_read_timeout/GetOverlappedResult"); + } + end_of_function: if (!res) { - register_error(dev, "GetOverlappedResult"); return -1; } - - return copy_len; + + return (int) copy_len; } int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length) @@ -748,42 +1223,69 @@ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonbloc int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) { - BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, length); - if (!res) { - register_error(dev, "HidD_SetFeature"); + BOOL res = FALSE; + unsigned char *buf; + size_t length_to_send; + + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); return -1; } - return length; + 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; } - -int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +static int hid_get_report(hid_device *dev, DWORD report_type, 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; + 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, - IOCTL_HID_GET_FEATURE, - data, length, - data, length, + report_type, + 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"); + register_winapi_error(dev, L"Get Input/Feature Report DeviceIoControl"); return -1; } } @@ -793,150 +1295,253 @@ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/); if (!res) { /* The operation failed. */ - register_error(dev, "Send Feature Report GetOverLappedResult"); + register_winapi_error(dev, L"Get Input/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++; + /* 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; -#endif +} + +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) { - BOOL res; - - res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)); - if (!res) { - register_error(dev, "HidD_GetManufacturerString"); + 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) { - BOOL res; - - res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)); - if (!res) { - register_error(dev, "HidD_GetProductString"); + 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) { - BOOL res; - - res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)); - if (!res) { - register_error(dev, "HidD_GetSerialNumberString"); + 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; - res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)); + 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_error(dev, "HidD_GetIndexedString"); + 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) { - return (wchar_t*)dev->last_error_str; + 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; } - -/*#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; -} +#ifndef hidapi_winapi_EXPORTS +#include "hidapi_descriptor_reconstruct.c" #endif #ifdef __cplusplus diff --git a/libs/hidapi/windows/hidapi_cfgmgr32.h b/libs/hidapi/windows/hidapi_cfgmgr32.h new file mode 100644 index 0000000000..638512a8b4 --- /dev/null +++ b/libs/hidapi/windows/hidapi_cfgmgr32.h @@ -0,0 +1,75 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + 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 . +********************************************************/ + +#ifndef HIDAPI_CFGMGR32_H +#define HIDAPI_CFGMGR32_H + +#ifdef HIDAPI_USE_DDK + +#include +#include +#include +#include + +#else + +/* This part of the header mimics cfgmgr32.h, + but only what is used by HIDAPI */ + +#include +#include +#include + +typedef DWORD RETURN_TYPE; +typedef RETURN_TYPE CONFIGRET; +typedef DWORD DEVNODE, DEVINST; +typedef DEVNODE* PDEVNODE, * PDEVINST; +typedef WCHAR* DEVNODEID_W, * DEVINSTID_W; + +#define CR_SUCCESS (0x00000000) +#define CR_BUFFER_SMALL (0x0000001A) +#define CR_FAILURE (0x00000013) + +#define CM_LOCATE_DEVNODE_NORMAL 0x00000000 + +#define CM_GET_DEVICE_INTERFACE_LIST_PRESENT (0x00000000) + +typedef CONFIGRET(__stdcall* CM_Locate_DevNodeW_)(PDEVINST pdnDevInst, DEVINSTID_W pDeviceID, ULONG ulFlags); +typedef CONFIGRET(__stdcall* CM_Get_Parent_)(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags); +typedef CONFIGRET(__stdcall* CM_Get_DevNode_PropertyW_)(DEVINST dnDevInst, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); +typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDeviceInterface, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); +typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_List_SizeW_)(PULONG pulLen, LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, ULONG ulFlags); +typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_ListW_)(LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, PZZWSTR Buffer, ULONG BufferLen, ULONG ulFlags); + +// from devpkey.h +DEFINE_DEVPROPKEY(DEVPKEY_NAME, 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac, 10); // DEVPROP_TYPE_STRING +DEFINE_DEVPROPKEY(DEVPKEY_Device_Manufacturer, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); // DEVPROP_TYPE_STRING +DEFINE_DEVPROPKEY(DEVPKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 256); // DEVPROP_TYPE_STRING +DEFINE_DEVPROPKEY(DEVPKEY_Device_HardwareIds, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 3); // DEVPROP_TYPE_STRING_LIST +DEFINE_DEVPROPKEY(DEVPKEY_Device_CompatibleIds, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 4); // DEVPROP_TYPE_STRING_LIST +DEFINE_DEVPROPKEY(DEVPKEY_Device_ContainerId, 0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c, 2); // DEVPROP_TYPE_GUID + +// from propkey.h +DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_DeviceAddress, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 1); // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_Manufacturer, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 4); // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_ModelNumber, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 5); // DEVPROP_TYPE_STRING + +#endif + +#endif /* HIDAPI_CFGMGR32_H */ diff --git a/libs/hidapi/windows/hidapi_descriptor_reconstruct.c b/libs/hidapi/windows/hidapi_descriptor_reconstruct.c new file mode 100644 index 0000000000..c76d4ea68c --- /dev/null +++ b/libs/hidapi/windows/hidapi_descriptor_reconstruct.c @@ -0,0 +1,987 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + 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 . +********************************************************/ +#include "hidapi_descriptor_reconstruct.h" + +/** + * @brief References to report descriptor buffer. + * + */ +struct rd_buffer { + unsigned char* buf; /* Pointer to the array which stores the reconstructed descriptor */ + size_t buf_size; /* Size of the buffer in bytes */ + size_t byte_idx; /* Index of the next report byte to write to buf array */ +}; + +/** + * @brief Function that appends a byte to encoded report descriptor buffer. + * + * @param[in] byte Single byte to append. + * @param rpt_desc Pointer to report descriptor buffer struct. + */ +static void rd_append_byte(unsigned char byte, struct rd_buffer* rpt_desc) { + if (rpt_desc->byte_idx < rpt_desc->buf_size) { + rpt_desc->buf[rpt_desc->byte_idx] = byte; + rpt_desc->byte_idx++; + } +} + +/** + * @brief Writes a short report descriptor item according USB HID spec 1.11 chapter 6.2.2.2. + * + * @param[in] rd_item Enumeration identifying type (Main, Global, Local) and function (e.g Usage or Report Count) of the item. + * @param[in] data Data (Size depends on rd_item 0,1,2 or 4bytes). + * @param rpt_desc Pointer to report descriptor buffer struct. + * + * @return Returns 0 if successful, -1 for error. + */ +static int rd_write_short_item(rd_items rd_item, LONG64 data, struct rd_buffer* rpt_desc) { + if (rd_item & 0x03) { + // Invalid input data, last to bits are reserved for data size + return -1; + } + + if (rd_item == rd_main_collection_end) { + // Item without data (1Byte prefix only) + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x00; + rd_append_byte(oneBytePrefix, rpt_desc); + } + else if ((rd_item == rd_global_logical_minimum) || + (rd_item == rd_global_logical_maximum) || + (rd_item == rd_global_physical_minimum) || + (rd_item == rd_global_physical_maximum)) { + // Item with signed integer data + if ((data >= -128) && (data <= 127)) { + // 1Byte prefix + 1Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x01; + char localData = (char)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + } + else if ((data >= -32768) && (data <= 32767)) { + // 1Byte prefix + 2Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x02; + INT16 localData = (INT16)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + rd_append_byte(localData >> 8 & 0xFF, rpt_desc); + } + else if ((data >= -2147483648LL) && (data <= 2147483647)) { + // 1Byte prefix + 4Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x03; + INT32 localData = (INT32)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + rd_append_byte(localData >> 8 & 0xFF, rpt_desc); + rd_append_byte(localData >> 16 & 0xFF, rpt_desc); + rd_append_byte(localData >> 24 & 0xFF, rpt_desc); + } + else { + // Data out of 32 bit signed integer range + return -1; + } + } + else { + // Item with unsigned integer data + if ((data >= 0) && (data <= 0xFF)) { + // 1Byte prefix + 1Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x01; + unsigned char localData = (unsigned char)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + } + else if ((data >= 0) && (data <= 0xFFFF)) { + // 1Byte prefix + 2Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x02; + UINT16 localData = (UINT16)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + rd_append_byte(localData >> 8 & 0xFF, rpt_desc); + } + else if ((data >= 0) && (data <= 0xFFFFFFFF)) { + // 1Byte prefix + 4Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x03; + UINT32 localData = (UINT32)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + rd_append_byte(localData >> 8 & 0xFF, rpt_desc); + rd_append_byte(localData >> 16 & 0xFF, rpt_desc); + rd_append_byte(localData >> 24 & 0xFF, rpt_desc); + } + else { + // Data out of 32 bit unsigned integer range + return -1; + } + } + return 0; +} + +static struct rd_main_item_node * rd_append_main_item_node(int first_bit, int last_bit, rd_node_type type_of_node, int caps_index, int collection_index, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) { + struct rd_main_item_node *new_list_node; + + // Determine last node in the list + while (*list != NULL) + { + list = &(*list)->next; + } + + new_list_node = malloc(sizeof(*new_list_node)); // Create new list entry + new_list_node->FirstBit = first_bit; + new_list_node->LastBit = last_bit; + new_list_node->TypeOfNode = type_of_node; + new_list_node->CapsIndex = caps_index; + new_list_node->CollectionIndex = collection_index; + new_list_node->MainItemType = main_item_type; + new_list_node->ReportID = report_id; + new_list_node->next = NULL; // NULL marks last node in the list + + *list = new_list_node; + return new_list_node; +} + +static struct rd_main_item_node * rd_insert_main_item_node(int first_bit, int last_bit, rd_node_type type_of_node, int caps_index, int collection_index, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) { + // Insert item after the main item node referenced by list + struct rd_main_item_node *next_item = (*list)->next; + (*list)->next = NULL; + rd_append_main_item_node(first_bit, last_bit, type_of_node, caps_index, collection_index, main_item_type, report_id, list); + (*list)->next->next = next_item; + return (*list)->next; +} + +static struct rd_main_item_node * rd_search_main_item_list_for_bit_position(int search_bit, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) { + // Determine first INPUT/OUTPUT/FEATURE main item, where the last bit position is equal or greater than the search bit position + + while (((*list)->next->MainItemType != rd_collection) && + ((*list)->next->MainItemType != rd_collection_end) && + !(((*list)->next->LastBit >= search_bit) && + ((*list)->next->ReportID == report_id) && + ((*list)->next->MainItemType == main_item_type)) + ) + { + list = &(*list)->next; + } + return *list; +} + +int hid_winapi_descriptor_reconstruct_pp_data(void *preparsed_data, unsigned char *buf, size_t buf_size) +{ + hidp_preparsed_data *pp_data = (hidp_preparsed_data *) preparsed_data; + + // Check if MagicKey is correct, to ensure that pp_data points to an valid preparse data structure + if (memcmp(pp_data->MagicKey, "HidP KDR", 8) != 0) { + return -1; + } + + struct rd_buffer rpt_desc = { + .buf = buf, + .buf_size = buf_size, + .byte_idx = 0 + }; + + // Set pointer to the first node of link_collection_nodes + phid_pp_link_collection_node link_collection_nodes = (phid_pp_link_collection_node)(((unsigned char*)&pp_data->caps[0]) + pp_data->FirstByteOfLinkCollectionArray); + + // **************************************************************************************************************************** + // Create lookup tables for the bit range of each report per collection (position of first bit and last bit in each collection) + // coll_bit_range[COLLECTION_INDEX][REPORT_ID][INPUT/OUTPUT/FEATURE] + // **************************************************************************************************************************** + + // Allocate memory and initialize lookup table + rd_bit_range ****coll_bit_range; + coll_bit_range = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_bit_range)); + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + coll_bit_range[collection_node_idx] = malloc(256 * sizeof(*coll_bit_range[0])); // 256 possible report IDs (incl. 0x00) + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + coll_bit_range[collection_node_idx][reportid_idx] = malloc(NUM_OF_HIDP_REPORT_TYPES * sizeof(*coll_bit_range[0][0])); + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + coll_bit_range[collection_node_idx][reportid_idx][rt_idx] = malloc(sizeof(rd_bit_range)); + coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit = -1; + coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit = -1; + } + } + } + + // Fill the lookup table where caps exist + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + for (USHORT caps_idx = pp_data->caps_info[rt_idx].FirstCap; caps_idx < pp_data->caps_info[rt_idx].LastCap; caps_idx++) { + int first_bit, last_bit; + first_bit = (pp_data->caps[caps_idx].BytePosition - 1) * 8 + + pp_data->caps[caps_idx].BitPosition; + last_bit = first_bit + pp_data->caps[caps_idx].ReportSize + * pp_data->caps[caps_idx].ReportCount - 1; + if (coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit == -1 || + coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit > first_bit) { + coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit = first_bit; + } + if (coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->LastBit < last_bit) { + coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->LastBit = last_bit; + } + } + } + + // ************************************************************************* + // -Determine hierarchy levels of each collections and store it in: + // coll_levels[COLLECTION_INDEX] + // -Determine number of direct childs of each collections and store it in: + // coll_number_of_direct_childs[COLLECTION_INDEX] + // ************************************************************************* + int max_coll_level = 0; + int *coll_levels = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_levels[0])); + int *coll_number_of_direct_childs = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_number_of_direct_childs[0])); + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + coll_levels[collection_node_idx] = -1; + coll_number_of_direct_childs[collection_node_idx] = 0; + } + + { + int actual_coll_level = 0; + USHORT collection_node_idx = 0; + while (actual_coll_level >= 0) { + coll_levels[collection_node_idx] = actual_coll_level; + if ((link_collection_nodes[collection_node_idx].NumberOfChildren > 0) && + (coll_levels[link_collection_nodes[collection_node_idx].FirstChild] == -1)) { + actual_coll_level++; + coll_levels[collection_node_idx] = actual_coll_level; + if (max_coll_level < actual_coll_level) { + max_coll_level = actual_coll_level; + } + coll_number_of_direct_childs[collection_node_idx]++; + collection_node_idx = link_collection_nodes[collection_node_idx].FirstChild; + } + else if (link_collection_nodes[collection_node_idx].NextSibling != 0) { + coll_number_of_direct_childs[link_collection_nodes[collection_node_idx].Parent]++; + collection_node_idx = link_collection_nodes[collection_node_idx].NextSibling; + } + else { + actual_coll_level--; + if (actual_coll_level >= 0) { + collection_node_idx = link_collection_nodes[collection_node_idx].Parent; + } + } + } + } + + // ********************************************************************************* + // Propagate the bit range of each report from the child collections to their parent + // and store the merged result for the parent + // ********************************************************************************* + for (int actual_coll_level = max_coll_level - 1; actual_coll_level >= 0; actual_coll_level--) { + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + if (coll_levels[collection_node_idx] == actual_coll_level) { + USHORT child_idx = link_collection_nodes[collection_node_idx].FirstChild; + while (child_idx) { + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + // Merge bit range from childs + if ((coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit != -1) && + (coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit > coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit)) { + coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit = coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit; + } + if (coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit < coll_bit_range[child_idx][reportid_idx][rt_idx]->LastBit) { + coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit = coll_bit_range[child_idx][reportid_idx][rt_idx]->LastBit; + } + child_idx = link_collection_nodes[child_idx].NextSibling; + } + } + } + } + } + } + + // ************************************************************************************************** + // Determine child collection order of the whole hierarchy, based on previously determined bit ranges + // and store it this index coll_child_order[COLLECTION_INDEX][DIRECT_CHILD_INDEX] + // ************************************************************************************************** + USHORT **coll_child_order; + coll_child_order = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_child_order)); + { + BOOLEAN *coll_parsed_flag; + coll_parsed_flag = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_parsed_flag[0])); + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + coll_parsed_flag[collection_node_idx] = FALSE; + } + int actual_coll_level = 0; + USHORT collection_node_idx = 0; + while (actual_coll_level >= 0) { + if ((coll_number_of_direct_childs[collection_node_idx] != 0) && + (coll_parsed_flag[link_collection_nodes[collection_node_idx].FirstChild] == FALSE)) { + coll_parsed_flag[link_collection_nodes[collection_node_idx].FirstChild] = TRUE; + coll_child_order[collection_node_idx] = malloc((coll_number_of_direct_childs[collection_node_idx]) * sizeof(*coll_child_order[0])); + + { + // Create list of child collection indices + // sorted reverse to the order returned to HidP_GetLinkCollectionNodeschild + // which seems to match the original order, as long as no bit position needs to be considered + USHORT child_idx = link_collection_nodes[collection_node_idx].FirstChild; + int child_count = coll_number_of_direct_childs[collection_node_idx] - 1; + coll_child_order[collection_node_idx][child_count] = child_idx; + while (link_collection_nodes[child_idx].NextSibling) { + child_count--; + child_idx = link_collection_nodes[child_idx].NextSibling; + coll_child_order[collection_node_idx][child_count] = child_idx; + } + } + + if (coll_number_of_direct_childs[collection_node_idx] > 1) { + // Sort child collections indices by bit positions + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + for (int child_idx = 1; child_idx < coll_number_of_direct_childs[collection_node_idx]; child_idx++) { + // since the coll_bit_range array is not sorted, we need to reference the collection index in + // our sorted coll_child_order array, and look up the corresponding bit ranges for comparing values to sort + int prev_coll_idx = coll_child_order[collection_node_idx][child_idx - 1]; + int cur_coll_idx = coll_child_order[collection_node_idx][child_idx]; + if ((coll_bit_range[prev_coll_idx][reportid_idx][rt_idx]->FirstBit != -1) && + (coll_bit_range[cur_coll_idx][reportid_idx][rt_idx]->FirstBit != -1) && + (coll_bit_range[prev_coll_idx][reportid_idx][rt_idx]->FirstBit > coll_bit_range[cur_coll_idx][reportid_idx][rt_idx]->FirstBit)) { + // Swap position indices of the two compared child collections + USHORT idx_latch = coll_child_order[collection_node_idx][child_idx - 1]; + coll_child_order[collection_node_idx][child_idx - 1] = coll_child_order[collection_node_idx][child_idx]; + coll_child_order[collection_node_idx][child_idx] = idx_latch; + } + } + } + } + } + actual_coll_level++; + collection_node_idx = link_collection_nodes[collection_node_idx].FirstChild; + } + else if (link_collection_nodes[collection_node_idx].NextSibling != 0) { + collection_node_idx = link_collection_nodes[collection_node_idx].NextSibling; + } + else { + actual_coll_level--; + if (actual_coll_level >= 0) { + collection_node_idx = link_collection_nodes[collection_node_idx].Parent; + } + } + } + free(coll_parsed_flag); + } + + + // *************************************************************************************** + // Create sorted main_item_list containing all the Collection and CollectionEnd main items + // *************************************************************************************** + struct rd_main_item_node *main_item_list = NULL; // List root + // Lookup table to find the Collection items in the list by index + struct rd_main_item_node **coll_begin_lookup = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_begin_lookup)); + struct rd_main_item_node **coll_end_lookup = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_end_lookup)); + { + int *coll_last_written_child = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_last_written_child[0])); + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + coll_last_written_child[collection_node_idx] = -1; + } + + int actual_coll_level = 0; + USHORT collection_node_idx = 0; + struct rd_main_item_node *firstDelimiterNode = NULL; + struct rd_main_item_node *delimiterCloseNode = NULL; + coll_begin_lookup[0] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list); + while (actual_coll_level >= 0) { + if ((coll_number_of_direct_childs[collection_node_idx] != 0) && + (coll_last_written_child[collection_node_idx] == -1)) { + // Collection has child collections, but none is written to the list yet + + coll_last_written_child[collection_node_idx] = coll_child_order[collection_node_idx][0]; + collection_node_idx = coll_child_order[collection_node_idx][0]; + + // In a HID Report Descriptor, the first usage declared is the most preferred usage for the control. + // While the order in the WIN32 capabiliy strutures is the opposite: + // Here the preferred usage is the last aliased usage in the sequence. + + if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode == NULL)) { + // Alliased Collection (First node in link_collection_nodes -> Last entry in report descriptor output) + firstDelimiterNode = main_item_list; + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &main_item_list); + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_close, 0, &main_item_list); + delimiterCloseNode = main_item_list; + } + else { + // Normal not aliased collection + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list); + actual_coll_level++; + } + + + } + else if ((coll_number_of_direct_childs[collection_node_idx] > 1) && + (coll_last_written_child[collection_node_idx] != coll_child_order[collection_node_idx][coll_number_of_direct_childs[collection_node_idx] - 1])) { + // Collection has child collections, and this is not the first child + + int nextChild = 1; + while (coll_last_written_child[collection_node_idx] != coll_child_order[collection_node_idx][nextChild - 1]) { + nextChild++; + } + coll_last_written_child[collection_node_idx] = coll_child_order[collection_node_idx][nextChild]; + collection_node_idx = coll_child_order[collection_node_idx][nextChild]; + + if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode == NULL)) { + // Alliased Collection (First node in link_collection_nodes -> Last entry in report descriptor output) + firstDelimiterNode = main_item_list; + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &main_item_list); + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_close, 0, &main_item_list); + delimiterCloseNode = main_item_list; + } + else if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode != NULL)) { + coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &firstDelimiterNode); + } + else if (!link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode != NULL)) { + coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &firstDelimiterNode); + coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_open, 0, &firstDelimiterNode); + firstDelimiterNode = NULL; + main_item_list = delimiterCloseNode; + delimiterCloseNode = NULL; // Last entry of alias has .IsAlias == FALSE + } + if (!link_collection_nodes[collection_node_idx].IsAlias) { + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list); + actual_coll_level++; + } + } + else { + actual_coll_level--; + coll_end_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection_end, 0, &main_item_list); + collection_node_idx = link_collection_nodes[collection_node_idx].Parent; + } + } + free(coll_last_written_child); + } + + + // **************************************************************** + // Inserted Input/Output/Feature main items into the main_item_list + // in order of reconstructed bit positions + // **************************************************************** + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + // Add all value caps to node list + struct rd_main_item_node *firstDelimiterNode = NULL; + struct rd_main_item_node *delimiterCloseNode = NULL; + for (USHORT caps_idx = pp_data->caps_info[rt_idx].FirstCap; caps_idx < pp_data->caps_info[rt_idx].LastCap; caps_idx++) { + struct rd_main_item_node *coll_begin = coll_begin_lookup[pp_data->caps[caps_idx].LinkCollection]; + int first_bit, last_bit; + first_bit = (pp_data->caps[caps_idx].BytePosition - 1) * 8 + + pp_data->caps[caps_idx].BitPosition; + last_bit = first_bit + pp_data->caps[caps_idx].ReportSize * + pp_data->caps[caps_idx].ReportCount - 1; + + for (int child_idx = 0; child_idx < coll_number_of_direct_childs[pp_data->caps[caps_idx].LinkCollection]; child_idx++) { + // Determine in which section before/between/after child collection the item should be inserted + if (first_bit < coll_bit_range[coll_child_order[pp_data->caps[caps_idx].LinkCollection][child_idx]][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit) + { + // Note, that the default value for undefined coll_bit_range is -1, which can't be greater than the bit position + break; + } + coll_begin = coll_end_lookup[coll_child_order[pp_data->caps[caps_idx].LinkCollection][child_idx]]; + } + struct rd_main_item_node *list_node; + list_node = rd_search_main_item_list_for_bit_position(first_bit, (rd_main_items) rt_idx, pp_data->caps[caps_idx].ReportID, &coll_begin); + + // In a HID Report Descriptor, the first usage declared is the most preferred usage for the control. + // While the order in the WIN32 capabiliy strutures is the opposite: + // Here the preferred usage is the last aliased usage in the sequence. + + if (pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode == NULL)) { + // Alliased Usage (First node in pp_data->caps -> Last entry in report descriptor output) + firstDelimiterNode = list_node; + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node); + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_close, pp_data->caps[caps_idx].ReportID, &list_node); + delimiterCloseNode = list_node; + } else if (pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode != NULL)) { + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node); + } + else if (!pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode != NULL)) { + // Alliased Collection (Last node in pp_data->caps -> First entry in report descriptor output) + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node); + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_open, pp_data->caps[caps_idx].ReportID, &list_node); + firstDelimiterNode = NULL; + list_node = delimiterCloseNode; + delimiterCloseNode = NULL; // Last entry of alias has .IsAlias == FALSE + } + if (!pp_data->caps[caps_idx].IsAlias) { + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, (rd_main_items) rt_idx, pp_data->caps[caps_idx].ReportID, &list_node); + } + } + } + + + // *********************************************************** + // Add const main items for padding to main_item_list + // -To fill all bit gaps + // -At each report end for 8bit padding + // Note that information about the padding at the report end, + // is not stored in the preparsed data, but in practice all + // report descriptors seem to have it, as assumed here. + // *********************************************************** + { + int last_bit_position[NUM_OF_HIDP_REPORT_TYPES][256]; + struct rd_main_item_node *last_report_item_lookup[NUM_OF_HIDP_REPORT_TYPES][256]; + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + last_bit_position[rt_idx][reportid_idx] = -1; + last_report_item_lookup[rt_idx][reportid_idx] = NULL; + } + } + + struct rd_main_item_node *list = main_item_list; // List root; + + while (list->next != NULL) + { + if ((list->MainItemType >= rd_input) && + (list->MainItemType <= rd_feature)) { + // INPUT, OUTPUT or FEATURE + if (list->FirstBit != -1) { + if ((last_bit_position[list->MainItemType][list->ReportID] + 1 != list->FirstBit) && + (last_report_item_lookup[list->MainItemType][list->ReportID] != NULL) && + (last_report_item_lookup[list->MainItemType][list->ReportID]->FirstBit != list->FirstBit) // Happens in case of IsMultipleItemsForArray for multiple dedicated usages for a multi-button array + ) { + struct rd_main_item_node *list_node = rd_search_main_item_list_for_bit_position(last_bit_position[list->MainItemType][list->ReportID], list->MainItemType, list->ReportID, &last_report_item_lookup[list->MainItemType][list->ReportID]); + rd_insert_main_item_node(last_bit_position[list->MainItemType][list->ReportID] + 1, list->FirstBit - 1, rd_item_node_padding, -1, 0, list->MainItemType, list->ReportID, &list_node); + } + last_bit_position[list->MainItemType][list->ReportID] = list->LastBit; + last_report_item_lookup[list->MainItemType][list->ReportID] = list; + } + } + list = list->next; + } + // Add 8 bit padding at each report end + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + if (last_bit_position[rt_idx][reportid_idx] != -1) { + int padding = 8 - ((last_bit_position[rt_idx][reportid_idx] + 1) % 8); + if (padding < 8) { + // Insert padding item after item referenced in last_report_item_lookup + rd_insert_main_item_node(last_bit_position[rt_idx][reportid_idx] + 1, last_bit_position[rt_idx][reportid_idx] + padding, rd_item_node_padding, -1, 0, (rd_main_items) rt_idx, (unsigned char) reportid_idx, &last_report_item_lookup[rt_idx][reportid_idx]); + } + } + } + } + } + + + // *********************************** + // Encode the report descriptor output + // *********************************** + UCHAR last_report_id = 0; + USAGE last_usage_page = 0; + LONG last_physical_min = 0;// If both, Physical Minimum and Physical Maximum are 0, the logical limits should be taken as physical limits according USB HID spec 1.11 chapter 6.2.2.7 + LONG last_physical_max = 0; + ULONG last_unit_exponent = 0; // If Unit Exponent is Undefined it should be considered as 0 according USB HID spec 1.11 chapter 6.2.2.7 + ULONG last_unit = 0; // If the first nibble is 7, or second nibble of Unit is 0, the unit is None according USB HID spec 1.11 chapter 6.2.2.7 + BOOLEAN inhibit_write_of_usage = FALSE; // Needed in case of delimited usage print, before the normal collection or cap + int report_count = 0; + while (main_item_list != NULL) + { + int rt_idx = main_item_list->MainItemType; + int caps_idx = main_item_list->CapsIndex; + if (main_item_list->MainItemType == rd_collection) { + if (last_usage_page != link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage) { + // Write "Usage Page" at the begin of a collection - except it refers the same table as wrote last + rd_write_short_item(rd_global_usage_page, link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage, &rpt_desc); + last_usage_page = link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage; + } + if (inhibit_write_of_usage) { + // Inhibit only once after DELIMITER statement + inhibit_write_of_usage = FALSE; + } + else { + // Write "Usage" of collection + rd_write_short_item(rd_local_usage, link_collection_nodes[main_item_list->CollectionIndex].LinkUsage, &rpt_desc); + } + // Write begin of "Collection" + rd_write_short_item(rd_main_collection, link_collection_nodes[main_item_list->CollectionIndex].CollectionType, &rpt_desc); + } + else if (main_item_list->MainItemType == rd_collection_end) { + // Write "End Collection" + rd_write_short_item(rd_main_collection_end, 0, &rpt_desc); + } + else if (main_item_list->MainItemType == rd_delimiter_open) { + if (main_item_list->CollectionIndex != -1) { + // Write "Usage Page" inside of a collection delmiter section + if (last_usage_page != link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage) { + rd_write_short_item(rd_global_usage_page, link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage, &rpt_desc); + last_usage_page = link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage; + } + } + else if (main_item_list->CapsIndex != 0) { + // Write "Usage Page" inside of a main item delmiter section + if (pp_data->caps[caps_idx].UsagePage != last_usage_page) { + rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc); + last_usage_page = pp_data->caps[caps_idx].UsagePage; + } + } + // Write "Delimiter Open" + rd_write_short_item(rd_local_delimiter, 1, &rpt_desc); // 1 = open set of aliased usages + } + else if (main_item_list->MainItemType == rd_delimiter_usage) { + if (main_item_list->CollectionIndex != -1) { + // Write aliased collection "Usage" + rd_write_short_item(rd_local_usage, link_collection_nodes[main_item_list->CollectionIndex].LinkUsage, &rpt_desc); + } if (main_item_list->CapsIndex != 0) { + // Write aliased main item range from "Usage Minimum" to "Usage Maximum" + if (pp_data->caps[caps_idx].IsRange) { + rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc); + rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc); + } + else { + // Write single aliased main item "Usage" + rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc); + } + } + } + else if (main_item_list->MainItemType == rd_delimiter_close) { + // Write "Delimiter Close" + rd_write_short_item(rd_local_delimiter, 0, &rpt_desc); // 0 = close set of aliased usages + // Inhibit next usage write + inhibit_write_of_usage = TRUE; + } + else if (main_item_list->TypeOfNode == rd_item_node_padding) { + // Padding + // The preparsed data doesn't contain any information about padding. Therefore all undefined gaps + // in the reports are filled with the same style of constant padding. + + // Write "Report Size" with number of padding bits + rd_write_short_item(rd_global_report_size, (main_item_list->LastBit - main_item_list->FirstBit + 1), &rpt_desc); + + // Write "Report Count" for padding always as 1 + rd_write_short_item(rd_global_report_count, 1, &rpt_desc); + + if (rt_idx == HidP_Input) { + // Write "Input" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const + rd_write_short_item(rd_main_input, 0x03, &rpt_desc); // Const / Abs + } + else if (rt_idx == HidP_Output) { + // Write "Output" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const + rd_write_short_item(rd_main_output, 0x03, &rpt_desc); // Const / Abs + } + else if (rt_idx == HidP_Feature) { + // Write "Feature" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const + rd_write_short_item(rd_main_feature, 0x03, &rpt_desc); // Const / Abs + } + report_count = 0; + } + else if (pp_data->caps[caps_idx].IsButtonCap) { + // Button + // (The preparsed data contain different data for 1 bit Button caps, than for parametric Value caps) + + if (last_report_id != pp_data->caps[caps_idx].ReportID) { + // Write "Report ID" if changed + rd_write_short_item(rd_global_report_id, pp_data->caps[caps_idx].ReportID, &rpt_desc); + last_report_id = pp_data->caps[caps_idx].ReportID; + } + + // Write "Usage Page" when changed + if (pp_data->caps[caps_idx].UsagePage != last_usage_page) { + rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc); + last_usage_page = pp_data->caps[caps_idx].UsagePage; + } + + // Write only local report items for each cap, if ReportCount > 1 + if (pp_data->caps[caps_idx].IsRange) { + report_count += (pp_data->caps[caps_idx].Range.DataIndexMax - pp_data->caps[caps_idx].Range.DataIndexMin); + } + + if (inhibit_write_of_usage) { + // Inhibit only once after Delimiter - Reset flag + inhibit_write_of_usage = FALSE; + } + else { + if (pp_data->caps[caps_idx].IsRange) { + // Write range from "Usage Minimum" to "Usage Maximum" + rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc); + rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc); + } + else { + // Write single "Usage" + rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc); + } + } + + if (pp_data->caps[caps_idx].IsDesignatorRange) { + // Write physical descriptor indices range from "Designator Minimum" to "Designator Maximum" + rd_write_short_item(rd_local_designator_minimum, pp_data->caps[caps_idx].Range.DesignatorMin, &rpt_desc); + rd_write_short_item(rd_local_designator_maximum, pp_data->caps[caps_idx].Range.DesignatorMax, &rpt_desc); + } + else if (pp_data->caps[caps_idx].NotRange.DesignatorIndex != 0) { + // Designator set 0 is a special descriptor set (of the HID Physical Descriptor), + // that specifies the number of additional descriptor sets. + // Therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. + // Write single "Designator Index" + rd_write_short_item(rd_local_designator_index, pp_data->caps[caps_idx].NotRange.DesignatorIndex, &rpt_desc); + } + + if (pp_data->caps[caps_idx].IsStringRange) { + // Write range of indices of the USB string descriptor, from "String Minimum" to "String Maximum" + rd_write_short_item(rd_local_string_minimum, pp_data->caps[caps_idx].Range.StringMin, &rpt_desc); + rd_write_short_item(rd_local_string_maximum, pp_data->caps[caps_idx].Range.StringMax, &rpt_desc); + } + else if (pp_data->caps[caps_idx].NotRange.StringIndex != 0) { + // String Index 0 is a special entry of the USB string descriptor, that contains a list of supported languages, + // therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. + // Write single "String Index" + rd_write_short_item(rd_local_string, pp_data->caps[caps_idx].NotRange.StringIndex, &rpt_desc); + } + + if ((main_item_list->next != NULL) && + ((int)main_item_list->next->MainItemType == rt_idx) && + (main_item_list->next->TypeOfNode == rd_item_node_cap) && + (pp_data->caps[main_item_list->next->CapsIndex].IsButtonCap) && + (!pp_data->caps[caps_idx].IsRange) && // This node in list is no array + (!pp_data->caps[main_item_list->next->CapsIndex].IsRange) && // Next node in list is no array + (pp_data->caps[main_item_list->next->CapsIndex].UsagePage == pp_data->caps[caps_idx].UsagePage) && + (pp_data->caps[main_item_list->next->CapsIndex].ReportID == pp_data->caps[caps_idx].ReportID) && + (pp_data->caps[main_item_list->next->CapsIndex].BitField == pp_data->caps[caps_idx].BitField) + ) { + if (main_item_list->next->FirstBit != main_item_list->FirstBit) { + // In case of IsMultipleItemsForArray for multiple dedicated usages for a multi-button array, the report count should be incremented + + // Skip global items until any of them changes, than use ReportCount item to write the count of identical report fields + report_count++; + } + } + else { + + if ((pp_data->caps[caps_idx].Button.LogicalMin == 0) && + (pp_data->caps[caps_idx].Button.LogicalMax == 0)) { + // While a HID report descriptor must always contain LogicalMinimum and LogicalMaximum, + // the preparsed data contain both fields set to zero, for the case of simple buttons + // Write "Logical Minimum" set to 0 and "Logical Maximum" set to 1 + rd_write_short_item(rd_global_logical_minimum, 0, &rpt_desc); + rd_write_short_item(rd_global_logical_maximum, 1, &rpt_desc); + } + else { + // Write logical range from "Logical Minimum" to "Logical Maximum" + rd_write_short_item(rd_global_logical_minimum, pp_data->caps[caps_idx].Button.LogicalMin, &rpt_desc); + rd_write_short_item(rd_global_logical_maximum, pp_data->caps[caps_idx].Button.LogicalMax, &rpt_desc); + } + + // Write "Report Size" + rd_write_short_item(rd_global_report_size, pp_data->caps[caps_idx].ReportSize, &rpt_desc); + + // Write "Report Count" + if (!pp_data->caps[caps_idx].IsRange) { + // Variable bit field with one bit per button + // In case of multiple usages with the same items, only "Usage" is written per cap, and "Report Count" is incremented + rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount + report_count, &rpt_desc); + } + else { + // Button array of "Report Size" x "Report Count + rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount, &rpt_desc); + } + + + // Buttons have only 1 bit and therefore no physical limits/units -> Set to undefined state + if (last_physical_min != 0) { + // Write "Physical Minimum", but only if changed + last_physical_min = 0; + rd_write_short_item(rd_global_physical_minimum, last_physical_min, &rpt_desc); + } + if (last_physical_max != 0) { + // Write "Physical Maximum", but only if changed + last_physical_max = 0; + rd_write_short_item(rd_global_physical_maximum, last_physical_max, &rpt_desc); + } + if (last_unit_exponent != 0) { + // Write "Unit Exponent", but only if changed + last_unit_exponent = 0; + rd_write_short_item(rd_global_unit_exponent, last_unit_exponent, &rpt_desc); + } + if (last_unit != 0) { + // Write "Unit",but only if changed + last_unit = 0; + rd_write_short_item(rd_global_unit, last_unit, &rpt_desc); + } + + // Write "Input" main item + if (rt_idx == HidP_Input) { + rd_write_short_item(rd_main_input, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + // Write "Output" main item + else if (rt_idx == HidP_Output) { + rd_write_short_item(rd_main_output, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + // Write "Feature" main item + else if (rt_idx == HidP_Feature) { + rd_write_short_item(rd_main_feature, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + report_count = 0; + } + } + else { + + if (last_report_id != pp_data->caps[caps_idx].ReportID) { + // Write "Report ID" if changed + rd_write_short_item(rd_global_report_id, pp_data->caps[caps_idx].ReportID, &rpt_desc); + last_report_id = pp_data->caps[caps_idx].ReportID; + } + + // Write "Usage Page" if changed + if (pp_data->caps[caps_idx].UsagePage != last_usage_page) { + rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc); + last_usage_page = pp_data->caps[caps_idx].UsagePage; + } + + if (inhibit_write_of_usage) { + // Inhibit only once after Delimiter - Reset flag + inhibit_write_of_usage = FALSE; + } + else { + if (pp_data->caps[caps_idx].IsRange) { + // Write usage range from "Usage Minimum" to "Usage Maximum" + rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc); + rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc); + } + else { + // Write single "Usage" + rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc); + } + } + + if (pp_data->caps[caps_idx].IsDesignatorRange) { + // Write physical descriptor indices range from "Designator Minimum" to "Designator Maximum" + rd_write_short_item(rd_local_designator_minimum, pp_data->caps[caps_idx].Range.DesignatorMin, &rpt_desc); + rd_write_short_item(rd_local_designator_maximum, pp_data->caps[caps_idx].Range.DesignatorMax, &rpt_desc); + } + else if (pp_data->caps[caps_idx].NotRange.DesignatorIndex != 0) { + // Designator set 0 is a special descriptor set (of the HID Physical Descriptor), + // that specifies the number of additional descriptor sets. + // Therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. + // Write single "Designator Index" + rd_write_short_item(rd_local_designator_index, pp_data->caps[caps_idx].NotRange.DesignatorIndex, &rpt_desc); + } + + if (pp_data->caps[caps_idx].IsStringRange) { + // Write range of indices of the USB string descriptor, from "String Minimum" to "String Maximum" + rd_write_short_item(rd_local_string_minimum, pp_data->caps[caps_idx].Range.StringMin, &rpt_desc); + rd_write_short_item(rd_local_string_maximum, pp_data->caps[caps_idx].Range.StringMax, &rpt_desc); + } + else if (pp_data->caps[caps_idx].NotRange.StringIndex != 0) { + // String Index 0 is a special entry of the USB string descriptor, that contains a list of supported languages, + // therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. + // Write single "String Index" + rd_write_short_item(rd_local_string, pp_data->caps[caps_idx].NotRange.StringIndex, &rpt_desc); + } + + if ((pp_data->caps[caps_idx].BitField & 0x02) != 0x02) { + // In case of an value array overwrite "Report Count" + pp_data->caps[caps_idx].ReportCount = pp_data->caps[caps_idx].Range.DataIndexMax - pp_data->caps[caps_idx].Range.DataIndexMin + 1; + } + + + // Print only local report items for each cap, if ReportCount > 1 + if ((main_item_list->next != NULL) && + ((int) main_item_list->next->MainItemType == rt_idx) && + (main_item_list->next->TypeOfNode == rd_item_node_cap) && + (!pp_data->caps[main_item_list->next->CapsIndex].IsButtonCap) && + (!pp_data->caps[caps_idx].IsRange) && // This node in list is no array + (!pp_data->caps[main_item_list->next->CapsIndex].IsRange) && // Next node in list is no array + (pp_data->caps[main_item_list->next->CapsIndex].UsagePage == pp_data->caps[caps_idx].UsagePage) && + (pp_data->caps[main_item_list->next->CapsIndex].NotButton.LogicalMin == pp_data->caps[caps_idx].NotButton.LogicalMin) && + (pp_data->caps[main_item_list->next->CapsIndex].NotButton.LogicalMax == pp_data->caps[caps_idx].NotButton.LogicalMax) && + (pp_data->caps[main_item_list->next->CapsIndex].NotButton.PhysicalMin == pp_data->caps[caps_idx].NotButton.PhysicalMin) && + (pp_data->caps[main_item_list->next->CapsIndex].NotButton.PhysicalMax == pp_data->caps[caps_idx].NotButton.PhysicalMax) && + (pp_data->caps[main_item_list->next->CapsIndex].UnitsExp == pp_data->caps[caps_idx].UnitsExp) && + (pp_data->caps[main_item_list->next->CapsIndex].Units == pp_data->caps[caps_idx].Units) && + (pp_data->caps[main_item_list->next->CapsIndex].ReportSize == pp_data->caps[caps_idx].ReportSize) && + (pp_data->caps[main_item_list->next->CapsIndex].ReportID == pp_data->caps[caps_idx].ReportID) && + (pp_data->caps[main_item_list->next->CapsIndex].BitField == pp_data->caps[caps_idx].BitField) && + (pp_data->caps[main_item_list->next->CapsIndex].ReportCount == 1) && + (pp_data->caps[caps_idx].ReportCount == 1) + ) { + // Skip global items until any of them changes, than use ReportCount item to write the count of identical report fields + report_count++; + } + else { + // Value + + // Write logical range from "Logical Minimum" to "Logical Maximum" + rd_write_short_item(rd_global_logical_minimum, pp_data->caps[caps_idx].NotButton.LogicalMin, &rpt_desc); + rd_write_short_item(rd_global_logical_maximum, pp_data->caps[caps_idx].NotButton.LogicalMax, &rpt_desc); + + if ((last_physical_min != pp_data->caps[caps_idx].NotButton.PhysicalMin) || + (last_physical_max != pp_data->caps[caps_idx].NotButton.PhysicalMax)) { + // Write range from "Physical Minimum" to " Physical Maximum", but only if one of them changed + rd_write_short_item(rd_global_physical_minimum, pp_data->caps[caps_idx].NotButton.PhysicalMin, &rpt_desc); + last_physical_min = pp_data->caps[caps_idx].NotButton.PhysicalMin; + rd_write_short_item(rd_global_physical_maximum, pp_data->caps[caps_idx].NotButton.PhysicalMax, &rpt_desc); + last_physical_max = pp_data->caps[caps_idx].NotButton.PhysicalMax; + } + + + if (last_unit_exponent != pp_data->caps[caps_idx].UnitsExp) { + // Write "Unit Exponent", but only if changed + rd_write_short_item(rd_global_unit_exponent, pp_data->caps[caps_idx].UnitsExp, &rpt_desc); + last_unit_exponent = pp_data->caps[caps_idx].UnitsExp; + } + + if (last_unit != pp_data->caps[caps_idx].Units) { + // Write physical "Unit", but only if changed + rd_write_short_item(rd_global_unit, pp_data->caps[caps_idx].Units, &rpt_desc); + last_unit = pp_data->caps[caps_idx].Units; + } + + // Write "Report Size" + rd_write_short_item(rd_global_report_size, pp_data->caps[caps_idx].ReportSize, &rpt_desc); + + // Write "Report Count" + rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount + report_count, &rpt_desc); + + if (rt_idx == HidP_Input) { + // Write "Input" main item + rd_write_short_item(rd_main_input, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + else if (rt_idx == HidP_Output) { + // Write "Output" main item + rd_write_short_item(rd_main_output, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + else if (rt_idx == HidP_Feature) { + // Write "Feature" main item + rd_write_short_item(rd_main_feature, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + report_count = 0; + } + } + + // Go to next item in main_item_list and free the memory of the actual item + struct rd_main_item_node *main_item_list_prev = main_item_list; + main_item_list = main_item_list->next; + free(main_item_list_prev); + } + + // Free multidimensionable array: coll_bit_range[COLLECTION_INDEX][REPORT_ID][INPUT/OUTPUT/FEATURE] + // Free multidimensionable array: coll_child_order[COLLECTION_INDEX][DIRECT_CHILD_INDEX] + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + free(coll_bit_range[collection_node_idx][reportid_idx][rt_idx]); + } + free(coll_bit_range[collection_node_idx][reportid_idx]); + } + free(coll_bit_range[collection_node_idx]); + if (coll_number_of_direct_childs[collection_node_idx] != 0) free(coll_child_order[collection_node_idx]); + } + free(coll_bit_range); + free(coll_child_order); + + // Free one dimensional arrays + free(coll_begin_lookup); + free(coll_end_lookup); + free(coll_levels); + free(coll_number_of_direct_childs); + + return (int) rpt_desc.byte_idx; +} diff --git a/libs/hidapi/windows/hidapi_descriptor_reconstruct.h b/libs/hidapi/windows/hidapi_descriptor_reconstruct.h new file mode 100644 index 0000000000..4b8ca83fbc --- /dev/null +++ b/libs/hidapi/windows/hidapi_descriptor_reconstruct.h @@ -0,0 +1,247 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + 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 . +********************************************************/ +#ifndef HIDAPI_DESCRIPTOR_RECONSTRUCT_H__ +#define HIDAPI_DESCRIPTOR_RECONSTRUCT_H__ + +#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 + +#include "hidapi_winapi.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4200) +#pragma warning(disable: 4201) +#pragma warning(disable: 4214) +#endif + +#include + +#include "hidapi_hidsdi.h" +#include + +#define NUM_OF_HIDP_REPORT_TYPES 3 + +typedef enum rd_items_ { + rd_main_input = 0x80, /* 1000 00 nn */ + rd_main_output = 0x90, /* 1001 00 nn */ + rd_main_feature = 0xB0, /* 1011 00 nn */ + rd_main_collection = 0xA0, /* 1010 00 nn */ + rd_main_collection_end = 0xC0, /* 1100 00 nn */ + rd_global_usage_page = 0x04, /* 0000 01 nn */ + rd_global_logical_minimum = 0x14, /* 0001 01 nn */ + rd_global_logical_maximum = 0x24, /* 0010 01 nn */ + rd_global_physical_minimum = 0x34, /* 0011 01 nn */ + rd_global_physical_maximum = 0x44, /* 0100 01 nn */ + rd_global_unit_exponent = 0x54, /* 0101 01 nn */ + rd_global_unit = 0x64, /* 0110 01 nn */ + rd_global_report_size = 0x74, /* 0111 01 nn */ + rd_global_report_id = 0x84, /* 1000 01 nn */ + rd_global_report_count = 0x94, /* 1001 01 nn */ + rd_global_push = 0xA4, /* 1010 01 nn */ + rd_global_pop = 0xB4, /* 1011 01 nn */ + rd_local_usage = 0x08, /* 0000 10 nn */ + rd_local_usage_minimum = 0x18, /* 0001 10 nn */ + rd_local_usage_maximum = 0x28, /* 0010 10 nn */ + rd_local_designator_index = 0x38, /* 0011 10 nn */ + rd_local_designator_minimum = 0x48, /* 0100 10 nn */ + rd_local_designator_maximum = 0x58, /* 0101 10 nn */ + rd_local_string = 0x78, /* 0111 10 nn */ + rd_local_string_minimum = 0x88, /* 1000 10 nn */ + rd_local_string_maximum = 0x98, /* 1001 10 nn */ + rd_local_delimiter = 0xA8 /* 1010 10 nn */ +} rd_items; + +typedef enum rd_main_items_ { + rd_input = HidP_Input, + rd_output = HidP_Output, + rd_feature = HidP_Feature, + rd_collection, + rd_collection_end, + rd_delimiter_open, + rd_delimiter_usage, + rd_delimiter_close, +} rd_main_items; + +typedef struct rd_bit_range_ { + int FirstBit; + int LastBit; +} rd_bit_range; + +typedef enum rd_item_node_type_ { + rd_item_node_cap, + rd_item_node_padding, + rd_item_node_collection, +} rd_node_type; + +struct rd_main_item_node { + int FirstBit; /* Position of first bit in report (counting from 0) */ + int LastBit; /* Position of last bit in report (counting from 0) */ + rd_node_type TypeOfNode; /* Information if caps index refers to the array of button caps, value caps, + or if the node is just a padding element to fill unused bit positions. + The node can also be a collection node without any bits in the report. */ + int CapsIndex; /* Index in the array of caps */ + int CollectionIndex; /* Index in the array of link collections */ + rd_main_items MainItemType; /* Input, Output, Feature, Collection or Collection End */ + unsigned char ReportID; + struct rd_main_item_node* next; +}; + +typedef struct hid_pp_caps_info_ { + USHORT FirstCap; + USHORT NumberOfCaps; // Includes empty caps after LastCap + USHORT LastCap; + USHORT ReportByteLength; +} hid_pp_caps_info, *phid_pp_caps_info; + +typedef struct hid_pp_link_collection_node_ { + USAGE LinkUsage; + USAGE LinkUsagePage; + USHORT Parent; + USHORT NumberOfChildren; + USHORT NextSibling; + USHORT FirstChild; + ULONG CollectionType : 8; + ULONG IsAlias : 1; + ULONG Reserved : 23; + // Same as the public API structure HIDP_LINK_COLLECTION_NODE, but without PVOID UserContext at the end +} hid_pp_link_collection_node, *phid_pp_link_collection_node; + +// Note: This is risk-reduction-measure for this specific struct, as it has ULONG bit-field. +// Although very unlikely, it might still be possible that the compiler creates a memory layout that is +// not binary compatile. +// Other structs are not checked at the time of writing. +static_assert(sizeof(struct hid_pp_link_collection_node_) == 16, + "Size of struct hid_pp_link_collection_node_ not as expected. This might break binary compatibility"); + +typedef struct hidp_unknown_token_ { + UCHAR Token; /* Specifies the one-byte prefix of a global item. */ + UCHAR Reserved[3]; + ULONG BitField; /* Specifies the data part of the global item. */ +} hidp_unknown_token, * phidp_unknown_token; + +typedef struct hid_pp_cap_ { + USAGE UsagePage; + UCHAR ReportID; + UCHAR BitPosition; + USHORT ReportSize; // WIN32 term for this is BitSize + USHORT ReportCount; + USHORT BytePosition; + USHORT BitCount; + ULONG BitField; + USHORT NextBytePosition; + USHORT LinkCollection; + USAGE LinkUsagePage; + USAGE LinkUsage; + + // Start of 8 Flags in one byte + BOOLEAN IsMultipleItemsForArray:1; + + BOOLEAN IsPadding:1; + BOOLEAN IsButtonCap:1; + BOOLEAN IsAbsolute:1; + BOOLEAN IsRange:1; + BOOLEAN IsAlias:1; // IsAlias is set to TRUE in the first n-1 capability structures added to the capability array. IsAlias set to FALSE in the nth capability structure. + BOOLEAN IsStringRange:1; + BOOLEAN IsDesignatorRange:1; + // End of 8 Flags in one byte + BOOLEAN Reserved1[3]; + + hidp_unknown_token UnknownTokens[4]; // 4 x 8 Byte + + union { + struct { + USAGE UsageMin; + USAGE UsageMax; + USHORT StringMin; + USHORT StringMax; + USHORT DesignatorMin; + USHORT DesignatorMax; + USHORT DataIndexMin; + USHORT DataIndexMax; + } Range; + struct { + USAGE Usage; + USAGE Reserved1; + USHORT StringIndex; + USHORT Reserved2; + USHORT DesignatorIndex; + USHORT Reserved3; + USHORT DataIndex; + USHORT Reserved4; + } NotRange; + }; + union { + struct { + LONG LogicalMin; + LONG LogicalMax; + } Button; + struct { + BOOLEAN HasNull; + UCHAR Reserved4[3]; + LONG LogicalMin; + LONG LogicalMax; + LONG PhysicalMin; + LONG PhysicalMax; + } NotButton; + }; + ULONG Units; + ULONG UnitsExp; + +} hid_pp_cap, *phid_pp_cap; + +typedef struct hidp_preparsed_data_ { + UCHAR MagicKey[8]; + USAGE Usage; + USAGE UsagePage; + USHORT Reserved[2]; + + // CAPS structure for Input, Output and Feature + hid_pp_caps_info caps_info[3]; + + USHORT FirstByteOfLinkCollectionArray; + USHORT NumberLinkCollectionNodes; + +#ifndef _MSC_VER + // MINGW fails with: Flexible array member in union not supported + // Solution: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html + union { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + hid_pp_cap caps[0]; + hid_pp_link_collection_node LinkCollectionArray[0]; +#pragma GCC diagnostic pop + }; +#else + union { + hid_pp_cap caps[]; + hid_pp_link_collection_node LinkCollectionArray[]; + }; +#endif + +} hidp_preparsed_data; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/libs/hidapi/windows/hidapi_hidclass.h b/libs/hidapi/windows/hidapi_hidclass.h new file mode 100644 index 0000000000..13bd6f22bd --- /dev/null +++ b/libs/hidapi/windows/hidapi_hidclass.h @@ -0,0 +1,38 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + 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 . +********************************************************/ + +#ifndef HIDAPI_HIDCLASS_H +#define HIDAPI_HIDCLASS_H + +#ifdef HIDAPI_USE_DDK + +#include + +#else + +/* This part of the header mimics hidclass.h, + but only what is used by HIDAPI */ + +#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) + +#endif + +#endif /* HIDAPI_HIDCLASS_H */ diff --git a/libs/hidapi/windows/hidapi_hidpi.h b/libs/hidapi/windows/hidapi_hidpi.h new file mode 100644 index 0000000000..75a5812c94 --- /dev/null +++ b/libs/hidapi/windows/hidapi_hidpi.h @@ -0,0 +1,72 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + 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 . +********************************************************/ + +#ifndef HIDAPI_HIDPI_H +#define HIDAPI_HIDPI_H + +#ifdef HIDAPI_USE_DDK + +#include + +#else + +/* This part of the header mimics hidpi.h, + but only what is used by HIDAPI */ + +typedef enum _HIDP_REPORT_TYPE +{ + HidP_Input, + HidP_Output, + HidP_Feature +} HIDP_REPORT_TYPE; + +typedef struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA; + +typedef struct _HIDP_CAPS +{ + USAGE Usage; + USAGE UsagePage; + USHORT InputReportByteLength; + USHORT OutputReportByteLength; + USHORT FeatureReportByteLength; + USHORT Reserved[17]; + + USHORT NumberLinkCollectionNodes; + + USHORT NumberInputButtonCaps; + USHORT NumberInputValueCaps; + USHORT NumberInputDataIndices; + + USHORT NumberOutputButtonCaps; + USHORT NumberOutputValueCaps; + USHORT NumberOutputDataIndices; + + USHORT NumberFeatureButtonCaps; + USHORT NumberFeatureValueCaps; + USHORT NumberFeatureDataIndices; +} HIDP_CAPS, *PHIDP_CAPS; + +#define HIDP_STATUS_SUCCESS 0x00110000 +#define HIDP_STATUS_INVALID_PREPARSED_DATA 0xc0110001 + +typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, PHIDP_CAPS caps); + +#endif + +#endif /* HIDAPI_HIDPI_H */ diff --git a/libs/hidapi/windows/hidapi_hidsdi.h b/libs/hidapi/windows/hidapi_hidsdi.h new file mode 100644 index 0000000000..ffed5b2f88 --- /dev/null +++ b/libs/hidapi/windows/hidapi_hidsdi.h @@ -0,0 +1,59 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + 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 . +********************************************************/ + +#ifndef HIDAPI_HIDSDI_H +#define HIDAPI_HIDSDI_H + +#ifdef HIDAPI_USE_DDK + +#include + +#else + +/* This part of the header mimics hidsdi.h, + but only what is used by HIDAPI */ + +typedef USHORT USAGE; + +#include "hidapi_hidpi.h" + +typedef struct _HIDD_ATTRIBUTES{ + ULONG Size; + USHORT VendorID; + USHORT ProductID; + USHORT VersionNumber; +} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; + +typedef void (__stdcall *HidD_GetHidGuid_)(LPGUID hid_guid); +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_SetOutputReport_)(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 BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers); + +#endif + +#endif /* HIDAPI_HIDSDI_H */ diff --git a/libs/hidapi/windows/hidapi_winapi.h b/libs/hidapi/windows/hidapi_winapi.h new file mode 100644 index 0000000000..a9919923c7 --- /dev/null +++ b/libs/hidapi/windows/hidapi_winapi.h @@ -0,0 +1,74 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + 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 . +********************************************************/ + +/** @file + * @defgroup API hidapi API + * + * Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + */ + +#ifndef HIDAPI_WINAPI_H__ +#define HIDAPI_WINAPI_H__ + +#include + +#include + +#include "hidapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /** @brief Get the container ID for a HID device. + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + This function returns the `DEVPKEY_Device_ContainerId` property of + the given device. This can be used to correlate different + interfaces/ports on the same hardware device. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param container_id The device's container ID on return. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id); + + /** + * @brief Reconstructs a HID Report Descriptor from a Win32 HIDP_PREPARSED_DATA structure. + * This reconstructed report descriptor is logical identical to the real report descriptor, + * but not byte wise identical. + * + * @param[in] hidp_preparsed_data Pointer to the HIDP_PREPARSED_DATA to read, i.e.: the value of PHIDP_PREPARSED_DATA, + * as returned by HidD_GetPreparsedData WinAPI function. + * @param buf Pointer to the buffer where the report descriptor should be stored. + * @param[in] buf_size Size of the buffer. The recommended size for the buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes. + * + * @return Returns size of reconstructed report descriptor if successful, -1 for error. + */ + int HID_API_EXPORT_CALL hid_winapi_descriptor_reconstruct_pp_data(void *hidp_preparsed_data, unsigned char *buf, size_t buf_size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/hidapi/wscript b/libs/hidapi/wscript index cf04057d9b..70ea8b158c 100644 --- a/libs/hidapi/wscript +++ b/libs/hidapi/wscript @@ -50,6 +50,7 @@ def build(bld): obj.source = 'mac/hid.c' obj.framework = [ 'IOKit', 'CoreFoundation' ] else: + # with '-strict' this needs "-std=gnu99" to compile w/o warnings obj.source = 'linux/hid.c' if re.search ("linux", sys.platform) != None: obj.uselib = 'UDEV'