From ea41eb1bce111cebac0b97254ac180bae65edf9b Mon Sep 17 00:00:00 2001 From: Norbert Takacs Date: Sat, 23 Mar 2024 19:57:47 +0100 Subject: [PATCH 1/6] hidapi: remove binary version Signed-off-by: Norbert Takacs --- 3rdparty/hidapi/CMakeLists.txt | 12 - 3rdparty/hidapi/headers/hidapi.h | 624 ------------------------------- 3rdparty/hidapi/lib/hidapi.dll | Bin 162304 -> 0 bytes 3rdparty/hidapi/lib/hidapi.lib | Bin 6850 -> 0 bytes 4 files changed, 636 deletions(-) delete mode 100644 3rdparty/hidapi/CMakeLists.txt delete mode 100644 3rdparty/hidapi/headers/hidapi.h delete mode 100644 3rdparty/hidapi/lib/hidapi.dll delete mode 100644 3rdparty/hidapi/lib/hidapi.lib diff --git a/3rdparty/hidapi/CMakeLists.txt b/3rdparty/hidapi/CMakeLists.txt deleted file mode 100644 index fc4256b..0000000 --- a/3rdparty/hidapi/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -if (WIN32) - add_library(hidapi::hidapi SHARED IMPORTED GLOBAL) - set_target_properties(hidapi::hidapi PROPERTIES - IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/lib/hidapi.dll - IMPORTED_IMPLIB ${CMAKE_CURRENT_SOURCE_DIR}/lib/hidapi.lib - ) - target_include_directories(hidapi::hidapi INTERFACE headers) - - install(FILES lib/hidapi.dll DESTINATION ${PLUGIN_INSTALL_DIR}) -else() - find_package(hidapi REQUIRED) -endif() diff --git a/3rdparty/hidapi/headers/hidapi.h b/3rdparty/hidapi/headers/hidapi.h deleted file mode 100644 index 744ceb0..0000000 --- a/3rdparty/hidapi/headers/hidapi.h +++ /dev/null @@ -1,624 +0,0 @@ -/******************************************************* - HIDAPI - Multi-Platform library for - communication with HID devices. - - Alan Ott - Signal 11 Software - - libusb/hidapi Team - - Copyright 2023, 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 - */ - -#ifndef HIDAPI_H__ -#define HIDAPI_H__ - -#include - -/* #480: this is to be refactored properly for v1.0 */ -#ifdef _WIN32 - #ifndef HID_API_NO_EXPORT_DEFINE - #define HID_API_EXPORT __declspec(dllexport) - #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 14 -/** @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 */ - char *path; - /** Device Vendor ID */ - unsigned short vendor_id; - /** Device Product ID */ - unsigned short product_id; - /** Serial Number */ - wchar_t *serial_number; - /** Device Release Number in binary-coded decimal, - also known as Device Version Number */ - unsigned short release_number; - /** Manufacturer String */ - wchar_t *manufacturer_string; - /** Product string */ - wchar_t *product_string; - /** Usage Page for this Device/Interface - (Windows/Mac/hidraw only) */ - unsigned short usage_page; - /** Usage for this Device/Interface - (Windows/Mac/hidraw only) */ - unsigned short usage; - /** The USB interface which this logical device - 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; - }; - - - /** @brief Initialize the HIDAPI library. - - This function initializes the HIDAPI library. Calling it is not - strictly necessary, as it will be called automatically by - hid_enumerate() and any of the hid_open_*() functions if it is - 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); - - /** @brief Finalize the HIDAPI library. - - This function frees all of the static data associated with - HIDAPI. It should be called at the end of execution to avoid - memory leaks. - - @ingroup API - - @returns - This function returns 0 on success and -1 on error. - */ - int HID_API_EXPORT HID_API_CALL hid_exit(void); - - /** @brief Enumerate the HID Devices. - - This function returns a linked list of all the HID devices - attached to the system which match vendor_id and product_id. - If @p vendor_id is set to 0 then any vendor matches. - If @p product_id is set to 0 then any product matches. - If @p vendor_id and @p product_id are both set to 0, then - all HID devices will be returned. - - @ingroup API - @param vendor_id The Vendor ID (VID) of the types of device - to open. - @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_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(). - - @ingroup API - @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); - - /** @brief Open a HID device using a Vendor ID (VID), Product ID - (PID) and optionally a serial number. - - If @p serial_number is NULL, the first device with the - specified VID and PID is opened. - - @ingroup API - @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). - - @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); - - /** @brief Open a HID device by its path name. - - The path name be determined by calling hid_enumerate(), or a - platform-specific path name can be used (eg: /dev/hidraw0 on - Linux). - - @ingroup API - @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); - - /** @brief Write an Output report to a HID device. - - 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_write() 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_write(), the Report ID (or 0x0, for devices with a - 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). - - @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. - - @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 *dev, const unsigned char *data, size_t length); - - /** @brief Read an Input report from a HID device with timeout. - - Input reports are returned - 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 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 - the report number. - @param milliseconds timeout in milliseconds or -1 for blocking wait. - - @returns - This function returns the actual number of bytes read and - -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); - - /** @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 - contain the Report number if the device uses numbered reports. - - @ingroup API - @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 - the report number. - - @returns - This function returns the actual number of bytes 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 *dev, unsigned char *data, size_t length); - - /** @brief Set the device handle to be non-blocking. - - In non-blocking mode calls to hid_read() will return - immediately with a value of 0 if there is no data to be - read. In blocking mode, hid_read() will wait (block) until - there is data to read before returning. - - Nonblocking can be turned on and off at any time. - - @ingroup API - @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 *dev, int nonblock); - - /** @brief Send a Feature report to the device. - - Feature 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_feature_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_feature_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. - - @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. - Call hid_error(dev) to get the failure reason. - */ - 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. - - 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_feature_report(hid_device *dev, 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 dev A device handle returned from hid_open(). - */ - void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev); - - /** @brief Get The Manufacturer String from a HID device. - - @ingroup API - @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 *dev, wchar_t *string, size_t maxlen); - - /** @brief Get The Product String from a HID device. - - @ingroup API - @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 *dev, wchar_t *string, size_t maxlen); - - /** @brief Get The Serial Number String from a HID device. - - @ingroup API - @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 *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 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 *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 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 - A string describing the last error (if any). - */ - 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/3rdparty/hidapi/lib/hidapi.dll b/3rdparty/hidapi/lib/hidapi.dll deleted file mode 100644 index 4eded6eeb17e7dee808e4ef77b902532d9cffa69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162304 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 z 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 diff --git a/3rdparty/hidapi/lib/hidapi.lib b/3rdparty/hidapi/lib/hidapi.lib deleted file mode 100644 index 76560045e701b44f65c1d104033f38af45ebed1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6850 zcmcIpU2GIp6h7SoEnC`d|69OV5CbNN(C*Sg3|X`$Qhrm44}>r*J5xHkompq6&Bp_CdLHtoSC_E@66mSyPG=6>AiD) z=DR=Vo^$SY(bE|#JGw6ZRCn-sa#ir1y{+^*t9tuZyWKr;07wDU4Fc5X0UAyMbnSL2 zaS?#1^^{9pH<6Fje9NO+Y?0cQyVN>>dPpr7Txz@zK-99!rRE9LN9sTfqR#m)wO<1u z>R9X2eAFju&v}Fx?MO|>TuP`oE~KWUOKm3*AF1=MN0Zo2D$0h<+qMo3D+AAMe|BiY zw&ATq%BZep@;WHWmK~cmMdfhVRdB__&+pi>Q5jf2ydDkou{>ET2Xq6!pr~3QYw3C0 zv=mFrnnuC4#bfB? zgZd*{hQ{FSu5S@ybDM%@s7gpmz9!>~R<>f8#@?KneTi*i0rytqacxV>xDMiUf_%l+ z$24=?c1B8apTYGGSi0@5klW)DGp`v+K4XtM=KL+F;R}URH)3g;qM?guR>sy%!_kQ_ z`FEJAI}ZLPXiVttEHvtHzHWGYr^EUCHLHNYj3ig z04Ey&c6R|>OaPo}1-Oa*Tg?EqNXy#*29S551>k-oz%FbjkbklRBWWkVwRV8D*x!W_ zH;4UNq+?A0Dt;@8?FqEMi+Yntm<*r_Vo(pAFdMp{1Li^_)ItNygF2W4ahMP7&<4%W z0tsk>g|Gl-K@GH4U{Q@LUw)M4>m5FW(w%nVDz^!3_%vQbC%Smt3LQfZAPJjcVbOpv zeej_#PaD^wSt5x1kZ>CEc{-uO(U2Y3>0$(jE-yTBJMWMQV~`)a(vmX&Zn3l|X!t>l z2Yl{a6mo@h%|c1Ul}In_M2r`5AI1(PR?Ze&hB0N%!ek*gVeF`bFp1PPNa#mI#~p~n z9f95g`NGc!B#~{q%Ma!r;<2q3`hKh}ZF|YG*v2|CXG@D!c&LGO-3m3AbK~{HD(K`I zR!i@Rt4FHg=(~g0<5d`0F?!OoLe=(;?_09neFI2i=XLhtwP{E^vjTDL%#t1^!XOS< zKEss9sKz!8E6;d(t35WxVvO`iQ;dOa8oJNKux-MKO;4OpYALiNYLIo=5n!1Qi{aeC z=FNK6G7IL2-Mwt+iSA7swrup|frhYX97KSzo_d%#Fgs9>gWw3V=2#wwdhTFV)q@A3 z1*!s#Ee34HX#i)mqm1Ls&BgI_lB9!1;OnF~hxm`uAMc^B1N3`u)Wl#L{;ed#%#@e( zB-eO1F7lEXOdV>wblMpijZf|+c(DmQf|v9%0eJ~tcmfZ3$^WtYrR?b=IFh25D!~m) zJ6k>dNnzEh;m6w6zkfgkcZcPxHqF_hg2s91Y|_~hpERV|*-A1#>0M}79Ia};jl~~6vz)5cPj_B46oW`?_c?v;!VAW%WCqO#2Ae0zYs=)hr!DIiZLX4VA0`)e^4H1 z6fpc>gcwlSJ0)AO*sF&w(QFbP61$}^kg|8BKk9=Y{(JQ&%Fj&#hIj$06e}fJxsNA% zZ#;%r%^p^fyA^{hdks^PjPU9EZ+%M{p~c6H+}kR~OZGkAO4Er`SJn-ydAN#^vz&NuH+0ov|im3)`2coZo~UWmQ%`xNDcj^HRL c#fSedP%*Boy6pS%>=#sxbuwJ`vx<-MA1Dzvi2wiq From 92a799e25fbd9086102be7f6cc80ff753b296aea Mon Sep 17 00:00:00 2001 From: Norbert Takacs Date: Sat, 23 Mar 2024 19:58:43 +0100 Subject: [PATCH 2/6] Add hidapi source file to project Signed-off-by: Norbert Takacs --- 3rdparty/hidapi/hidapi.h | 603 +++++++++ 3rdparty/hidapi/linux/hid.c | 1251 ++++++++++++++++++ 3rdparty/hidapi/mac/hid.c | 1456 +++++++++++++++++++++ 3rdparty/hidapi/mac/hidapi_darwin.h | 98 ++ 3rdparty/hidapi/windows/hid.c | 1325 +++++++++++++++++++ 3rdparty/hidapi/windows/hidapi.rc | 35 + 3rdparty/hidapi/windows/hidapi_cfgmgr32.h | 69 + 3rdparty/hidapi/windows/hidapi_hidclass.h | 38 + 3rdparty/hidapi/windows/hidapi_hidpi.h | 65 + 3rdparty/hidapi/windows/hidapi_hidsdi.h | 60 + 3rdparty/hidapi/windows/hidapi_winapi.h | 58 + 11 files changed, 5058 insertions(+) create mode 100644 3rdparty/hidapi/hidapi.h create mode 100644 3rdparty/hidapi/linux/hid.c create mode 100644 3rdparty/hidapi/mac/hid.c create mode 100644 3rdparty/hidapi/mac/hidapi_darwin.h create mode 100644 3rdparty/hidapi/windows/hid.c create mode 100644 3rdparty/hidapi/windows/hidapi.rc create mode 100644 3rdparty/hidapi/windows/hidapi_cfgmgr32.h create mode 100644 3rdparty/hidapi/windows/hidapi_hidclass.h create mode 100644 3rdparty/hidapi/windows/hidapi_hidpi.h create mode 100644 3rdparty/hidapi/windows/hidapi_hidsdi.h create mode 100644 3rdparty/hidapi/windows/hidapi_winapi.h diff --git a/3rdparty/hidapi/hidapi.h b/3rdparty/hidapi/hidapi.h new file mode 100644 index 0000000..22b3cd6 --- /dev/null +++ b/3rdparty/hidapi/hidapi.h @@ -0,0 +1,603 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + */ + +#ifndef HIDAPI_H__ +#define HIDAPI_H__ + +#include + +#ifdef _WIN32 + #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 + +#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 13 +/** @brief Static/compile-time patch version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_PATCH 1 + +/* 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: + #if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + @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 + struct hid_api_version { + int major; + int minor; + int patch; + }; + + 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 */ + char *path; + /** Device Vendor ID */ + unsigned short vendor_id; + /** Device Product ID */ + unsigned short product_id; + /** Serial Number */ + wchar_t *serial_number; + /** Device Release Number in binary-coded decimal, + also known as Device Version Number */ + unsigned short release_number; + /** Manufacturer String */ + wchar_t *manufacturer_string; + /** Product string */ + wchar_t *product_string; + /** Usage Page for this Device/Interface + (Windows/Mac/hidraw only) */ + unsigned short usage_page; + /** Usage for this Device/Interface + (Windows/Mac/hidraw only) */ + unsigned short usage; + /** The USB interface which this logical device + represents. + + * Valid on both Linux implementations in all cases. + * Valid on the Windows implementation only if the device + contains more than one interface. + * Valid on the Mac implementation if and only if the device + is a USB HID device. */ + 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; + }; + + + /** @brief Initialize the HIDAPI library. + + This function initializes the HIDAPI library. Calling it is not + strictly necessary, as it will be called automatically by + hid_enumerate() and any of the hid_open_*() functions if it is + 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); + + /** @brief Finalize the HIDAPI library. + + This function frees all of the static data associated with + HIDAPI. It should be called at the end of execution to avoid + memory leaks. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_exit(void); + + /** @brief Enumerate the HID Devices. + + This function returns a linked list of all the HID devices + attached to the system which match vendor_id and product_id. + If @p vendor_id is set to 0 then any vendor matches. + If @p product_id is set to 0 then any product matches. + If @p vendor_id and @p product_id are both set to 0, then + all HID devices will be returned. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the types of device + to open. + @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_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(). + + @ingroup API + @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); + + /** @brief Open a HID device using a Vendor ID (VID), Product ID + (PID) and optionally a serial number. + + If @p serial_number is NULL, the first device with the + specified VID and PID is opened. + + @ingroup API + @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). + + @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); + + /** @brief Open a HID device by its path name. + + The path name be determined by calling hid_enumerate(), or a + platform-specific path name can be used (eg: /dev/hidraw0 on + Linux). + + @ingroup API + @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); + + /** @brief Write an Output report to a HID device. + + 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_write() 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_write(), the Report ID (or 0x0, for devices with a + 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). + + @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. + + @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 *dev, const unsigned char *data, size_t length); + + /** @brief Read an Input report from a HID device with timeout. + + Input reports are returned + 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 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 + the report number. + @param milliseconds timeout in milliseconds or -1 for blocking wait. + + @returns + This function returns the actual number of bytes read and + -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); + + /** @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 + contain the Report number if the device uses numbered reports. + + @ingroup API + @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 + the report number. + + @returns + This function returns the actual number of bytes 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 *dev, unsigned char *data, size_t length); + + /** @brief Set the device handle to be non-blocking. + + In non-blocking mode calls to hid_read() will return + immediately with a value of 0 if there is no data to be + read. In blocking mode, hid_read() will wait (block) until + there is data to read before returning. + + Nonblocking can be turned on and off at any time. + + @ingroup API + @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 *dev, int nonblock); + + /** @brief Send a Feature report to the device. + + Feature 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_feature_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_feature_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. + + @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. + Call hid_error(dev) to get the failure reason. + */ + 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. + + 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_feature_report(hid_device *dev, 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 dev A device handle returned from hid_open(). + */ + void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev); + + /** @brief Get The Manufacturer String from a HID device. + + @ingroup API + @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 *dev, wchar_t *string, size_t maxlen); + + /** @brief Get The Product String from a HID device. + + @ingroup API + @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 *dev, wchar_t *string, size_t maxlen); + + /** @brief Get The Serial Number String from a HID device. + + @ingroup API + @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 *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 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 *dev, int string_index, wchar_t *string, size_t maxlen); + + /** @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 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 + A string describing the last error (if any). + */ + 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/3rdparty/hidapi/linux/hid.c b/3rdparty/hidapi/linux/hid.c new file mode 100644 index 0000000..1b1d2c0 --- /dev/null +++ b/3rdparty/hidapi/linux/hid.c @@ -0,0 +1,1251 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +/* C */ +#include +#include +#include +#include +#include + +/* Unix */ +#include +#include +#include +#include +#include +#include +#include + +/* Linux */ +#include +#include +#include +#include + +#include "hidapi.h" + +#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 +#ifndef HIDIOCGFEATURE +#define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len) +#endif + +#endif + + +// HIDIOCGINPUT is not defined in Linux kernel headers < 5.11. +// This definition is 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 + +struct hid_device_ { + int device_handle; + int blocking; + 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 wchar_t *last_global_error_str = NULL; + + +static hid_device *new_hid_device(void) +{ + hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + if (dev == NULL) { + return NULL; + } + + dev->device_handle = -1; + dev->blocking = 1; + dev->last_error_str = NULL; + dev->device_info = NULL; + + return dev; +} + + +/* 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; +} + + +/* 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) +{ + return utf8_to_wchar_t(udev_device_get_sysattr_value(dev, udev_name)); +} + +/* + * 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(__u8 *report_descriptor, unsigned int pos, __u32 size, int *data_len, int *key_size) +{ + int key = report_descriptor[pos]; + int size_code; + + /* + * 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; + } + + /* + * 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(__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; +} + +/* + * 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(__u8 *report_descriptor, __u32 size, unsigned int *pos, unsigned short *usage_page, unsigned short *usage) +{ + int data_len, key_size; + int initial = *pos == 0; /* Used to handle case where no top-level application collection is defined */ + int usage_pair_ready = 0; + + /* Usage is a Local Item, it must be set before each Main Item (Collection) before a pair is returned */ + int usage_found = 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, *pos, size, &data_len, &key_size)) + return -1; /* malformed report */ + + switch (key_cmd) { + case 0x4: /* Usage Page 6.2.2.7 (Global) */ + *usage_page = get_hid_report_bytes(report_descriptor, size, data_len, *pos); + break; + + case 0x8: /* Usage 6.2.2.8 (Local) */ + *usage = get_hid_report_bytes(report_descriptor, size, data_len, *pos); + usage_found = 1; + break; + + case 0xa0: /* Collection 6.2.2.4 (Main) */ + /* A Usage Item (Local) must be found for the pair to be valid */ + if (usage_found) + usage_pair_ready = 1; + + /* Usage is a Local Item, unset it */ + usage_found = 0; + break; + + case 0x80: /* Input 6.2.2.4 (Main) */ + case 0x90: /* Output 6.2.2.4 (Main) */ + case 0xb0: /* Feature 6.2.2.4 (Main) */ + case 0xc0: /* End Collection 6.2.2.4 (Main) */ + /* Usage is a Local Item, unset it */ + usage_found = 0; + break; + } + + /* Skip over this key and its associated data */ + *pos += data_len + key_size; + + /* Return usage pair */ + if (usage_pair_ready) + return 0; + } + + /* 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) + return 0; /* success */ + + return 1; /* finished processing */ +} + +/* + * Retrieves the hidraw report descriptor from a file. + * When using this form, /device/report_descriptor, elevated priviledges 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 | O_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 | O_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)); + 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; +} + +/* + * 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, unsigned *bus_type, + unsigned short *vendor_id, unsigned short *product_id, + char **serial_number_utf8, char **product_name_utf8) +{ + 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; + + int found_id = 0; + int found_serial = 0; + int found_name = 0; + + 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) { + found_id = 1; + } + } else if (strcmp(key, "HID_NAME") == 0) { + /* The caller has to free the product name */ + *product_name_utf8 = strdup(value); + found_name = 1; + } else if (strcmp(key, "HID_UNIQ") == 0) { + /* The caller has to free the serial number */ + *serial_number_utf8 = strdup(value); + found_serial = 1; + } + +next_line: + line = strtok_r(NULL, "\n", &saveptr); + } + + return (found_id && found_name && found_serial); +} + + +static struct hid_device_info * create_device_info_for_device(struct udev_device *raw_dev) +{ + struct hid_device_info *root = NULL; + struct hid_device_info *cur_dev = NULL; + + 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; + } + + 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 end; + } + + /* 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; + } + + cur_dev->manufacturer_string = copy_udev_string(usb_dev, "manufacturer"); + cur_dev->product_string = copy_udev_string(usb_dev, "product"); + + 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; + unsigned int pos = 0; + /* + * Parse the first usage and usage page + * out of the report descriptor. + */ + if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &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, &pos, &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); + + 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); + udev_unref(udev); + + return root; +} + +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() +{ + return &api_version; +} + +HID_API_EXPORT const char* HID_API_CALL hid_version_str() +{ + return HID_API_VERSION_STR; +} + +int HID_API_EXPORT hid_init(void) +{ + 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, ""); + + return 0; +} + +int HID_API_EXPORT hid_exit(void) +{ + /* 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; + struct udev_enumerate *enumerate; + struct udev_list_entry *devices, *dev_list_entry; + + struct hid_device_info *root = NULL; /* return object */ + struct hid_device_info *cur_dev = NULL; + + hid_init(); + /* register_global_error: global error is reset by hid_init */ + + /* Create the udev object */ + udev = udev_new(); + if (!udev) { + register_global_error("Couldn't create udev context"); + return NULL; + } + + /* Create a list of the devices in the 'hidraw' subsystem. */ + enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(enumerate, "hidraw"); + udev_enumerate_scan_devices(enumerate); + devices = udev_enumerate_get_list_entry(enumerate); + /* For each item, see if it matches the vid/pid, and if so + create a udev_device record for it */ + udev_list_entry_foreach(dev_list_entry, devices) { + const char *sysfs_path; + 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 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); + if (!raw_dev) + continue; + + tmp = create_device_info_for_device(raw_dev); + if (tmp) { + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + /* move the pointer to the tail of returnd list */ + while (cur_dev->next != NULL) { + cur_dev = cur_dev->next; + } + } + + udev_device_unref(raw_dev); + } + /* 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; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + +hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + 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 && + cur_dev->product_id == product_id) { + if (serial_number) { + if (wcscmp(serial_number, cur_dev->serial_number) == 0) { + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open); + } else { + register_global_error("Device with requested VID/PID/(SerialNumber) not found"); + } + + hid_free_enumeration(devs); + + return handle; +} + +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; + } + + dev->device_handle = open(path, O_RDWR | O_CLOEXEC); + + if (dev->device_handle >= 0) { + int res, desc_size = 0; + + /* Make sure this is a HIDRAW device - responds to HIDIOCGRDESCSIZE */ + res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); + if (res < 0) { + hid_close(dev); + register_device_error_format(dev, "ioctl(GRDESCSIZE) error for '%s', not a HIDRAW device?: %s", path, strerror(errno)); + return NULL; + } + + return dev; + } + else { + /* 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; + } +} + + +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + 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) { + /* Milliseconds is either 0 (non-blocking) or > 0 (contains + a valid timeout). In both cases we want to call poll() + and wait for data to arrive. Don't rely on non-blocking + operation (O_NONBLOCK) since some kernels don't seem to + properly report device disconnection through read() when + in non-blocking mode. */ + int ret; + struct pollfd fds; + + fds.fd = dev->device_handle; + fds.events = POLLIN; + fds.revents = 0; + ret = poll(&fds, 1, milliseconds); + 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)) { + // 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) { + if (errno == EAGAIN || errno == EINPROGRESS) + bytes_read = 0; + else + register_device_error(dev, strerror(errno)); + } + + return bytes_read; +} + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + /* Do all non-blocking in userspace using poll(), since it looks + like there's a bug in the kernel in some versions where + read() will not return -1 on disconnection of the USB device */ + + dev->blocking = !nonblock; + return 0; /* Success */ +} + + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + int res; + + register_device_error(dev, NULL); + + res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data); + if (res < 0) + register_device_error_format(dev, "ioctl (SFEATURE): %s", strerror(errno)); + + return res; +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + int res; + + register_device_error(dev, NULL); + + res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data); + if (res < 0) + register_device_error_format(dev, "ioctl (GFEATURE): %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) +{ + 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) +{ + 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) +{ + 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; +} + + +/* 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) +{ + 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/3rdparty/hidapi/mac/hid.c b/3rdparty/hidapi/mac/hid.c new file mode 100644 index 0000000..fbdc6fe --- /dev/null +++ b/3rdparty/hidapi/mac/hid.c @@ -0,0 +1,1456 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +/* See Apple Technical Note TN2187 for details on IOHidManager. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hidapi_darwin.h" + +/* As defined in AppKit.h, but we don't need the entire AppKit for a single constant. */ +extern const double NSAppKitVersionNumber; + +/* Barrier implementation because Mac OSX doesn't have pthread_barrier. + It also doesn't have clock_gettime(). So much for POSIX and SUSv2. + This implementation came from Brent Priddy and was posted on + StackOverflow. It is used with his permission. */ +typedef int pthread_barrierattr_t; +typedef struct pthread_barrier { + pthread_mutex_t mutex; + pthread_cond_t cond; + int count; + int trip_count; +} pthread_barrier_t; + +static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) +{ + (void) attr; + + if(count == 0) { + errno = EINVAL; + return -1; + } + + if(pthread_mutex_init(&barrier->mutex, 0) < 0) { + return -1; + } + if(pthread_cond_init(&barrier->cond, 0) < 0) { + pthread_mutex_destroy(&barrier->mutex); + return -1; + } + barrier->trip_count = count; + barrier->count = 0; + + return 0; +} + +static int pthread_barrier_destroy(pthread_barrier_t *barrier) +{ + pthread_cond_destroy(&barrier->cond); + pthread_mutex_destroy(&barrier->mutex); + return 0; +} + +static int pthread_barrier_wait(pthread_barrier_t *barrier) +{ + pthread_mutex_lock(&barrier->mutex); + ++(barrier->count); + if(barrier->count >= barrier->trip_count) + { + barrier->count = 0; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->mutex); + return 1; + } + else + { + pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + pthread_mutex_unlock(&barrier->mutex); + return 0; + } +} + +static int return_data(hid_device *dev, unsigned char *data, size_t length); + +/* Linked List of input reports received from the device. */ +struct input_report { + uint8_t *data; + size_t len; + 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 disconnected; + CFStringRef run_loop_mode; + CFRunLoopRef run_loop; + CFRunLoopSourceRef source; + 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 */ + pthread_cond_t condition; + 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 = (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->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); + pthread_cond_init(&dev->condition, NULL); + pthread_barrier_init(&dev->barrier, NULL, 2); + pthread_barrier_init(&dev->shutdown_barrier, NULL, 2); + + return dev; +} + +static void free_hid_device(hid_device *dev) +{ + if (!dev) + return; + + /* Delete any input reports still left over. */ + struct input_report *rpt = dev->input_reports; + while (rpt) { + struct input_report *next = rpt->next; + free(rpt->data); + free(rpt); + rpt = next; + } + + /* Free the string and the report buffer. The check for NULL + is necessary here as CFRelease() doesn't handle NULL like + free() and others do. */ + if (dev->run_loop_mode) + CFRelease(dev->run_loop_mode); + 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); + pthread_barrier_destroy(&dev->barrier); + pthread_cond_destroy(&dev->condition); + pthread_mutex_destroy(&dev->mutex); + + /* Free the structure itself. */ + free(dev); +} + + +/* 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; +} + + +/* 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; + + ref = IOHIDDeviceGetProperty(device, key); + if (ref) { + if (CFGetTypeID(ref) == CFNumberGetTypeID()) { + CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &value); + return value; + } + } + return 0; +} + +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)); +} + +static unsigned short get_product_id(IOHIDDeviceRef device) +{ + return get_int_property(device, CFSTR(kIOHIDProductIDKey)); +} + +static int32_t get_max_report_length(IOHIDDeviceRef device) +{ + return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey)); +} + +static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len) +{ + CFStringRef str; + + if (!len) + return 0; + + str = (CFStringRef) IOHIDDeviceGetProperty(device, prop); + + buf[0] = 0; + + if (str) { + CFIndex str_len = CFStringGetLength(str); + CFRange range; + CFIndex used_buf_len; + CFIndex chars_copied; + + len --; + + range.location = 0; + range.length = ((size_t) str_len > len)? len: (size_t) str_len; + chars_copied = CFStringGetBytes(str, + range, + kCFStringEncodingUTF32LE, + (char) '?', + FALSE, + (UInt8*)buf, + len * sizeof(wchar_t), + &used_buf_len); + + if (chars_copied <= 0) + buf[0] = 0; + else + buf[chars_copied] = 0; + + return 0; + } + else + return -1; + +} + +static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len); +} + +static int get_manufacturer_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDManufacturerKey), buf, len); +} + +static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDProductKey), buf, len); +} + + +/* Implementation of wcsdup() for Mac. */ +static wchar_t *dup_wcs(const wchar_t *s) +{ + size_t len = wcslen(s); + wchar_t *ret = (wchar_t*) malloc((len+1)*sizeof(wchar_t)); + wcscpy(ret, s); + + return ret; +} + +/* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */ +static int init_hid_manager(void) +{ + /* Initialize all the HID Manager Objects */ + hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (hid_mgr) { + IOHIDManagerSetDeviceMatching(hid_mgr, NULL); + IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + return 0; + } + + register_global_error("Failed to create IOHIDManager"); + return -1; +} + +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() +{ + return &api_version; +} + +HID_API_EXPORT const char* HID_API_CALL hid_version_str() +{ + return HID_API_VERSION_STR; +} + +/* 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 = (NSAppKitVersionNumber >= 1343); /* NSAppKitVersionNumber10_10 */ + hid_darwin_set_open_exclusive(1); /* Backward compatibility */ + return init_hid_manager(); + } + + /* Already initialized. */ + return 0; +} + +int HID_API_EXPORT hid_exit(void) +{ + if (hid_mgr) { + /* Close the HID manager. */ + IOHIDManagerClose(hid_mgr, kIOHIDOptionsTypeNone); + CFRelease(hid_mgr); + hid_mgr = NULL; + } + + /* Free global error message */ + register_global_error(NULL); + + return 0; +} + +static void process_pending_events(void) { + SInt32 res; + do { + res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, FALSE); + } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); +} + +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_object_t iokit_dev; + 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; + iokit_dev = IOHIDDeviceGetService(dev); + if (iokit_dev != MACH_PORT_NULL) { + res = IORegistryEntryGetRegistryEntryID(iokit_dev, &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 */ + cur_dev->path = calloc(1, 32); + if (cur_dev->path != NULL) { + /* Yes, compiler, we know that snprintf is preferable, + but we're not ready to abandon older macOS-es/SDKs where it is not yet available */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + sprintf(cur_dev->path, "DevSrvsID:%llu", entry_id); +#pragma GCC diagnostic pop + } + } + + 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. + * IOKit always seems to return 0 when querying a standard USB device + * for its interface. */ + int is_usb_hid = get_int_property(dev, CFSTR(kUSBInterfaceClass)) == kUSBHIDClass; + if (is_usb_hid) { + /* Get the interface number */ + cur_dev->interface_number = get_int_property(dev, CFSTR(kUSBInterfaceNumber)); + } else { + 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) { + cur_dev->bus_type = HID_API_BUS_USB; + /* 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 */ + struct hid_device_info *cur_dev = NULL; + CFIndex num_devices; + int i; + + /* Set up the HID Manager if it hasn't been done */ + 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 */ + 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); + + 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++) { + + IOHIDDeviceRef dev = device_array[i]; + if (!dev) { + continue; + } + + struct hid_device_info *tmp = create_device_info(dev); + if (tmp == NULL) { + continue; + } + + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + /* move the pointer to the tail of returnd list */ + while (cur_dev->next != NULL) { + cur_dev = cur_dev->next; + } + } + + free(device_array); + 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; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + /* This function is identical to the Linux version. Platform independent. */ + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + +hid_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 && + cur_dev->product_id == product_id) { + if (serial_number) { + if (wcscmp(serial_number, cur_dev->serial_number) == 0) { + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + handle = hid_open_path(path_to_open); + } else { + register_global_error("Device with requested VID/PID/(SerialNumber) not found"); + } + + hid_free_enumeration(devs); + + return handle; +} + +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 = (hid_device*) context; + + d->disconnected = 1; + CFRunLoopStop(d->run_loop); +} + +/* The Run Loop calls this function for each input report received. + This function puts the data into a linked list to be picked up by + hid_read(). */ +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 = (hid_device*) context; + + /* Make a new Input Report object */ + 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; + + /* Lock this section */ + pthread_mutex_lock(&dev->mutex); + + /* Attach the new report object to the end of the list. */ + if (dev->input_reports == NULL) { + /* The list is empty. Put it at the root. */ + dev->input_reports = rpt; + } + else { + /* Find the end of the list and attach. */ + struct input_report *cur = dev->input_reports; + int num_queued = 0; + while (cur->next != NULL) { + cur = cur->next; + num_queued++; + } + cur->next = rpt; + + /* Pop one off if we've reached 30 in the queue. This + way we don't grow forever if the user never reads + anything from the device. */ + if (num_queued > 30) { + return_data(dev, NULL, 0); + } + } + + /* Signal a waiting thread that there is data. */ + pthread_cond_signal(&dev->condition); + + /* Unlock */ + pthread_mutex_unlock(&dev->mutex); + +} + +/* This gets called when the read_thread's run loop gets signaled by + hid_close(), and serves to stop the read_thread's run loop. */ +static void perform_signal_callback(void *context) +{ + hid_device *dev = (hid_device*) context; + CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/ +} + +static void *read_thread(void *param) +{ + hid_device *dev = (hid_device*) param; + SInt32 code; + + /* Move the device's run loop to this thread. */ + IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode); + + /* Create the RunLoopSource which is used to signal the + event loop to stop when hid_close() is called. */ + CFRunLoopSourceContext ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.version = 0; + ctx.info = dev; + ctx.perform = &perform_signal_callback; + dev->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx); + CFRunLoopAddSource(CFRunLoopGetCurrent(), dev->source, dev->run_loop_mode); + + /* Store off the Run Loop so it can be stopped from hid_close() + and on device disconnection. */ + dev->run_loop = CFRunLoopGetCurrent(); + + /* Notify the main thread that the read thread is up and running. */ + pthread_barrier_wait(&dev->barrier); + + /* Run the Event Loop. CFRunLoopRunInMode() will dispatch HID input + reports into the hid_report_callback(). */ + 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) { + dev->disconnected = 1; + break; + } + + + /* Break if The Run Loop returns Finished or Stopped. */ + if (code != kCFRunLoopRunTimedOut && + code != kCFRunLoopRunHandledSource) { + /* There was some kind of error. Setting + shutdown seems to make sense, but + there may be something else more appropriate */ + dev->shutdown_thread = 1; + break; + } + } + + /* Now that the read thread is stopping, Wake any threads which are + waiting on data (in hid_read_timeout()). Do this under a mutex to + make sure that a thread which is about to go to sleep waiting on + the condition actually will go to sleep before the condition is + signaled. */ + pthread_mutex_lock(&dev->mutex); + pthread_cond_broadcast(&dev->condition); + pthread_mutex_unlock(&dev->mutex); + + /* Wait here until hid_close() is called and makes it past + the call to CFRunLoopWakeUp(). This thread still needs to + be valid when that function is called on the other thread. */ + pthread_barrier_wait(&dev->shutdown_barrier); + + return NULL; +} + +/* \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; + IOReturn ret = kIOReturnInvalid; + char str[32]; + + /* Set up the HID Manager if it hasn't been done */ + if (hid_init() < 0) { + goto return_error; + } + /* 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 = 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; + } + + /* Create an IOHIDDevice for the entry */ + 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 */ + 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)); + + +/* Yes, compiler, we know that snprintf is preferable, + but we're not ready to abandon older macOS-es/SDKs where it is not yet available */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + /* Create the Run Loop Mode for this device. + printing the reference seems to work. */ + sprintf(str, "HIDAPI_%p", (void*) dev->device_handle); +#pragma GCC diagnostic pop + + 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); + + if (entry != MACH_PORT_NULL) + IOObjectRelease(entry); + + free_hid_device(dev); + return NULL; +} + +static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length) +{ + const unsigned char *data_to_send = data; + CFIndex length_to_send = length; + IOReturn res; + unsigned char report_id; + + register_device_error(dev, NULL); + + if (!data || (length == 0)) { + register_device_error(dev, strerror(EINVAL)); + return -1; + } + + 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; + } + + /* Avoid crash if the device has been unplugged. */ + if (dev->disconnected) { + register_device_error(dev, "Device is disconnected"); + return -1; + } + + res = IOHIDDeviceSetReport(dev->device_handle, + type, + report_id, + data_to_send, length_to_send); + + if (res != kIOReturnSuccess) { + register_device_error_format(dev, "IOHIDDeviceSetReport failed: (0x%08X) %s", res, mach_error_string(res)); + 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) +{ + return set_report(dev, kIOHIDReportTypeOutput, data, length); +} + +/* Helper function, so that this isn't duplicated in hid_read(). */ +static int return_data(hid_device *dev, unsigned char *data, size_t length) +{ + /* Copy the data out of the linked list item (rpt) into the + 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); + dev->input_reports = rpt->next; + free(rpt->data); + free(rpt); + return (int) len; +} + +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); + if (res != 0) + 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 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) { + return -1; + } + } + + return 0; +} + +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); + if (res != 0) + 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 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) { + return -1; + } + } + + return 0; + +} + +int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) +{ + int bytes_read = -1; + + /* Lock the access to the report list. */ + pthread_mutex_lock(&dev->mutex); + + /* There's an input report queued up. Return it. */ + if (dev->input_reports) { + /* Return the first one */ + bytes_read = return_data(dev, data, length); + goto ret; + } + + /* Return if the device has been disconnected. */ + if (dev->disconnected) { + bytes_read = -1; + register_device_error(dev, "hid_read_timeout: device disconnected"); + goto ret; + } + + if (dev->shutdown_thread) { + /* This means the device has been closed (or there + 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; + } + + /* There is no data. Go to sleep and wait for data. */ + + if (milliseconds == -1) { + /* Blocking */ + int res; + res = cond_wait(dev, &dev->condition, &dev->mutex); + if (res == 0) + 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; + } + } + else if (milliseconds > 0) { + /* Non-blocking, but called with timeout. */ + int res; + struct timespec ts; + struct timeval tv; + gettimeofday(&tv, NULL); + TIMEVAL_TO_TIMESPEC(&tv, &ts); + ts.tv_sec += milliseconds / 1000; + ts.tv_nsec += (milliseconds % 1000) * 1000000; + if (ts.tv_nsec >= 1000000000L) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000L; + } + + res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts); + if (res == 0) { + bytes_read = return_data(dev, data, length); + } else if (res == ETIMEDOUT) { + bytes_read = 0; + } else { + register_device_error(dev, "hid_read_timeout: error waiting for more data"); + bytes_read = -1; + } + } + else { + /* Purely non-blocking */ + bytes_read = 0; + } + +ret: + /* Unlock */ + pthread_mutex_unlock(&dev->mutex); + return bytes_read; +} + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + /* All Nonblocking operation is handled by the library. */ + dev->blocking = !nonblock; + + return 0; +} + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, kIOHIDReportTypeFeature, data, length); +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + return get_report(dev, kIOHIDReportTypeFeature, 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. + 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); + IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev); + IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode); + IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode); + } + + /* Cause read_thread() to stop. */ + dev->shutdown_thread = 1; + + /* Wake up the run thread's event loop so that the thread can exit. */ + CFRunLoopSourceSignal(dev->source); + CFRunLoopWakeUp(dev->run_loop); + + /* Notify the read thread that it can shut down now. */ + pthread_barrier_wait(&dev->shutdown_barrier); + + /* Wait for read_thread() to end. */ + pthread_join(dev->thread, NULL); + + /* 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. + + 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. */ + pthread_mutex_lock(&dev->mutex); + while (dev->input_reports) { + return_data(dev, NULL, 0); + } + pthread_mutex_unlock(&dev->mutex); + CFRelease(dev->device_handle); + + free_hid_device(dev); +} + +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t 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) +{ + 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) +{ + 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) +{ + (void) dev; + (void) string_index; + (void) string; + (void) maxlen; + + 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; +} + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + if (dev) { + if (dev->last_error_str == NULL) + return L"Success"; + return dev->last_error_str; + } + + if (last_global_error_str == NULL) + return L"Success"; + return last_global_error_str; +} diff --git a/3rdparty/hidapi/mac/hidapi_darwin.h b/3rdparty/hidapi/mac/hidapi_darwin.h new file mode 100644 index 0000000..34c30a0 --- /dev/null +++ b/3rdparty/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/3rdparty/hidapi/windows/hid.c b/3rdparty/hidapi/windows/hid.c new file mode 100644 index 0000000..b63699c --- /dev/null +++ b/3rdparty/hidapi/windows/hid.c @@ -0,0 +1,1325 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +/* Do not warn about wcsncpy usage. + https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt */ +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include "hidapi.h" + +#include + +#ifndef _NTDEF_ +typedef LONG NTSTATUS; +#endif + +#ifdef __MINGW32__ +#include +#include +#define WC_ERR_INVALID_CHARS 0x00000080 +#endif + +#ifdef __CYGWIN__ +#include +#include +#define _wcsdup wcsdup +#endif + +/*#define HIDAPI_USE_DDK*/ + +#include +#include "hidapi_cfgmgr32.h" +#include "hidapi_hidclass.h" +#include "hidapi_hidsdi.h" + +#include +#include +#include + +#ifdef MIN +#undef MIN +#endif +#define MIN(x,y) ((x) < (y)? (x): (y)) + +/* MAXIMUM_USB_STRING_LENGTH from usbspec.h is 255 */ +/* BLUETOOTH_DEVICE_NAME_SIZE from bluetoothapis.h is 256 */ +#define MAX_STRING_WCHARS 256 + +static struct hid_api_version api_version = { + .major = HID_API_VERSION_MAJOR, + .minor = HID_API_VERSION_MINOR, + .patch = HID_API_VERSION_PATCH +}; + +#ifndef HIDAPI_USE_DDK +/* Since we're not building with the DDK, and the HID header + files aren't part of the Windows SDK, we define what we need ourselves. + In lookup_functions(), the function pointers + defined below are set. */ + +static HidD_GetHidGuid_ HidD_GetHidGuid; +static HidD_GetAttributes_ HidD_GetAttributes; +static HidD_GetSerialNumberString_ HidD_GetSerialNumberString; +static HidD_GetManufacturerString_ HidD_GetManufacturerString; +static HidD_GetProductString_ HidD_GetProductString; +static HidD_SetFeature_ HidD_SetFeature; +static HidD_GetFeature_ HidD_GetFeature; +static HidD_GetInputReport_ HidD_GetInputReport; +static HidD_GetIndexedString_ HidD_GetIndexedString; +static HidD_GetPreparsedData_ HidD_GetPreparsedData; +static HidD_FreePreparsedData_ HidD_FreePreparsedData; +static HidP_GetCaps_ HidP_GetCaps; +static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers; + +static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL; +static CM_Get_Parent_ CM_Get_Parent = NULL; +static CM_Get_DevNode_PropertyW_ CM_Get_DevNode_PropertyW = NULL; +static CM_Get_Device_Interface_PropertyW_ CM_Get_Device_Interface_PropertyW = NULL; +static CM_Get_Device_Interface_List_SizeW_ CM_Get_Device_Interface_List_SizeW = NULL; +static CM_Get_Device_Interface_ListW_ CM_Get_Device_Interface_ListW = NULL; + +static HMODULE hid_lib_handle = NULL; +static HMODULE cfgmgr32_lib_handle = NULL; +static BOOLEAN hidapi_initialized = FALSE; + +static void free_library_handles() +{ + if (hid_lib_handle) + FreeLibrary(hid_lib_handle); + hid_lib_handle = NULL; + if (cfgmgr32_lib_handle) + FreeLibrary(cfgmgr32_lib_handle); + cfgmgr32_lib_handle = NULL; +} + +static int lookup_functions() +{ + hid_lib_handle = LoadLibraryW(L"hid.dll"); + if (hid_lib_handle == NULL) { + goto err; + } + + cfgmgr32_lib_handle = LoadLibraryW(L"cfgmgr32.dll"); + if (cfgmgr32_lib_handle == NULL) { + goto err; + } + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-function-type" +#endif +#define RESOLVE(lib_handle, x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) goto err; + + RESOLVE(hid_lib_handle, HidD_GetHidGuid); + RESOLVE(hid_lib_handle, HidD_GetAttributes); + RESOLVE(hid_lib_handle, HidD_GetSerialNumberString); + RESOLVE(hid_lib_handle, HidD_GetManufacturerString); + RESOLVE(hid_lib_handle, HidD_GetProductString); + RESOLVE(hid_lib_handle, HidD_SetFeature); + RESOLVE(hid_lib_handle, HidD_GetFeature); + RESOLVE(hid_lib_handle, HidD_GetInputReport); + RESOLVE(hid_lib_handle, HidD_GetIndexedString); + RESOLVE(hid_lib_handle, HidD_GetPreparsedData); + RESOLVE(hid_lib_handle, HidD_FreePreparsedData); + RESOLVE(hid_lib_handle, HidP_GetCaps); + RESOLVE(hid_lib_handle, HidD_SetNumInputBuffers); + + RESOLVE(cfgmgr32_lib_handle, CM_Locate_DevNodeW); + RESOLVE(cfgmgr32_lib_handle, CM_Get_Parent); + RESOLVE(cfgmgr32_lib_handle, CM_Get_DevNode_PropertyW); + RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_PropertyW); + RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_List_SizeW); + RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_ListW); + +#undef RESOLVE +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + return 0; + +err: + free_library_handles(); + return -1; +} + +#endif /* HIDAPI_USE_DDK */ + +struct hid_device_ { + HANDLE device_handle; + BOOL blocking; + USHORT output_report_length; + unsigned char *write_buf; + size_t input_report_length; + USHORT feature_report_length; + unsigned char *feature_buf; + wchar_t *last_error_str; + BOOL read_pending; + char *read_buf; + OVERLAPPED ol; + OVERLAPPED write_ol; + struct hid_device_info* device_info; +}; + +static hid_device *new_hid_device() +{ + hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + + if (dev == NULL) { + return NULL; + } + + dev->device_handle = INVALID_HANDLE_VALUE; + dev->blocking = TRUE; + dev->output_report_length = 0; + dev->write_buf = NULL; + dev->input_report_length = 0; + dev->feature_report_length = 0; + dev->feature_buf = NULL; + dev->last_error_str = NULL; + dev->read_pending = FALSE; + dev->read_buf = NULL; + memset(&dev->ol, 0, sizeof(dev->ol)); + dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL); + memset(&dev->write_ol, 0, sizeof(dev->write_ol)); + dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL); + dev->device_info = NULL; + + return dev; +} + +static void free_hid_device(hid_device *dev) +{ + CloseHandle(dev->ol.hEvent); + CloseHandle(dev->write_ol.hEvent); + CloseHandle(dev->device_handle); + free(dev->last_error_str); + dev->last_error_str = NULL; + free(dev->write_buf); + free(dev->feature_buf); + free(dev->read_buf); + hid_free_enumeration(dev->device_info); + free(dev); +} + +static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR *op) +{ + free(*error_buffer); + *error_buffer = NULL; + + /* Only clear out error messages if NULL is passed into op */ + if (!op) { + return; + } + + WCHAR system_err_buf[1024]; + DWORD error_code = GetLastError(); + + DWORD system_err_len = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + system_err_buf, ARRAYSIZE(system_err_buf), + NULL); + + DWORD op_len = (DWORD)wcslen(op); + + DWORD op_prefix_len = + op_len + + 15 /*: (0x00000000) */ + ; + DWORD msg_len = + + op_prefix_len + + system_err_len + ; + + *error_buffer = (WCHAR *)calloc(msg_len + 1, sizeof (WCHAR)); + WCHAR *msg = *error_buffer; + + if (!msg) + return; + + int printf_written = swprintf(msg, msg_len + 1, L"%.*ls: (0x%08X) %.*ls", op_len, op, error_code, system_err_len, system_err_buf); + + if (printf_written < 0) + { + /* Highly unlikely */ + msg[0] = L'\0'; + return; + } + + /* Get rid of the CR and LF that FormatMessage() sticks at the + end of the message. Thanks Microsoft! */ + while(msg[msg_len-1] == L'\r' || msg[msg_len-1] == L'\n' || msg[msg_len-1] == L' ') + { + msg[msg_len-1] = L'\0'; + msg_len--; + } +} + +static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR *string_error) +{ + free(*error_buffer); + *error_buffer = NULL; + + if (string_error) { + *error_buffer = _wcsdup(string_error); + } +} + +static void register_winapi_error(hid_device *dev, const WCHAR *op) +{ + register_winapi_error_to_buffer(&dev->last_error_str, op); +} + +static void register_string_error(hid_device *dev, const WCHAR *string_error) +{ + register_string_error_to_buffer(&dev->last_error_str, string_error); +} + +static wchar_t *last_global_error_str = NULL; + +static void register_global_winapi_error(const WCHAR *op) +{ + register_winapi_error_to_buffer(&last_global_error_str, op); +} + +static void register_global_error(const WCHAR *string_error) +{ + register_string_error_to_buffer(&last_global_error_str, string_error); +} + +static HANDLE open_device(const wchar_t *path, BOOL open_rw) +{ + HANDLE handle; + DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0; + DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE; + + handle = CreateFileW(path, + desired_access, + share_mode, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/ + 0); + + return handle; +} + +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() +{ + return &api_version; +} + +HID_API_EXPORT const char* HID_API_CALL hid_version_str() +{ + return HID_API_VERSION_STR; +} + +int HID_API_EXPORT hid_init(void) +{ + register_global_error(NULL); +#ifndef HIDAPI_USE_DDK + if (!hidapi_initialized) { + if (lookup_functions() < 0) { + register_global_winapi_error(L"resolve DLL functions"); + return -1; + } + hidapi_initialized = TRUE; + } +#endif + return 0; +} + +int HID_API_EXPORT hid_exit(void) +{ +#ifndef HIDAPI_USE_DDK + free_library_handles(); + hidapi_initialized = FALSE; +#endif + register_global_error(NULL); + return 0; +} + +static void* hid_internal_get_devnode_property(DEVINST dev_node, const DEVPROPKEY* property_key, DEVPROPTYPE expected_property_type) +{ + ULONG len = 0; + CONFIGRET cr; + DEVPROPTYPE property_type; + PBYTE property_value = NULL; + + cr = CM_Get_DevNode_PropertyW(dev_node, property_key, &property_type, NULL, &len, 0); + if (cr != CR_BUFFER_SMALL || property_type != expected_property_type) + return NULL; + + property_value = (PBYTE)calloc(len, sizeof(BYTE)); + cr = CM_Get_DevNode_PropertyW(dev_node, property_key, &property_type, property_value, &len, 0); + if (cr != CR_SUCCESS) { + free(property_value); + return NULL; + } + + return property_value; +} + +static void* hid_internal_get_device_interface_property(const wchar_t* interface_path, const DEVPROPKEY* property_key, DEVPROPTYPE expected_property_type) +{ + ULONG len = 0; + CONFIGRET cr; + DEVPROPTYPE property_type; + PBYTE property_value = NULL; + + cr = CM_Get_Device_Interface_PropertyW(interface_path, property_key, &property_type, NULL, &len, 0); + if (cr != CR_BUFFER_SMALL || property_type != expected_property_type) + return NULL; + + property_value = (PBYTE)calloc(len, sizeof(BYTE)); + cr = CM_Get_Device_Interface_PropertyW(interface_path, property_key, &property_type, property_value, &len, 0); + if (cr != CR_SUCCESS) { + free(property_value); + return NULL; + } + + return property_value; +} + +static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node) +{ + wchar_t *manufacturer_string, *serial_number, *product_string; + /* Manufacturer String */ + manufacturer_string = (wchar_t*)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; + } + + /* Serial Number String (MAC Address) */ + serial_number = (wchar_t*)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; + } + + /* Get devnode grandparent to reach out Bluetooth LE device node */ + if (CM_Get_Parent(&dev_node, dev_node, 0) != CR_SUCCESS) + return; + + /* Product String */ + product_string = (wchar_t*)hid_internal_get_devnode_property(dev_node, &DEVPKEY_NAME, DEVPROP_TYPE_STRING); + if (product_string) { + free(dev->product_string); + dev->product_string = product_string; + } +} + +/* USB Device Interface Number. + It can be parsed out of the Hardware ID if a USB device is has multiple interfaces (composite device). + See https://docs.microsoft.com/windows-hardware/drivers/hid/hidclass-hardware-ids-for-top-level-collections + and https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers + + hardware_id is always expected to be uppercase. +*/ +static int hid_internal_get_interface_number(const wchar_t* hardware_id) +{ + int interface_number; + wchar_t *startptr, *endptr; + const wchar_t *interface_token = L"&MI_"; + + startptr = (wchar_t*)wcsstr(hardware_id, interface_token); + if (!startptr) + return -1; + + startptr += wcslen(interface_token); + interface_number = wcstol(startptr, &endptr, 16); + if (endptr == startptr) + return -1; + + return interface_number; +} + +static void hid_internal_get_info(const wchar_t* interface_path, struct hid_device_info* dev) +{ + wchar_t *device_id = NULL, *compatible_ids = NULL, *hardware_ids = NULL; + CONFIGRET cr; + DEVINST dev_node; + + /* Get the device id from interface path */ + device_id = (wchar_t*)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 the hardware ids from devnode */ + hardware_ids = (wchar_t*)hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST); + if (!hardware_ids) + goto end; + + /* Search for interface number in hardware ids */ + for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) { + /* Normalize to upper case */ + for (wchar_t* p = hardware_id; *p; ++p) *p = towupper(*p); + + dev->interface_number = hid_internal_get_interface_number(hardware_id); + + if (dev->interface_number != -1) + break; + } + + /* 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 = (wchar_t*)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 */ + for (wchar_t* p = compatible_id; *p; ++p) *p = towupper(*p); + + /* 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) { + dev->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) { + dev->bus_type = HID_API_BUS_BLUETOOTH; + break; + } + + /* Bluetooth LE devices */ + if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) { + /* 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 */ + hid_internal_get_ble_info(dev, dev_node); + + dev->bus_type = HID_API_BUS_BLUETOOTH; + 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) { + dev->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) { + dev->bus_type = HID_API_BUS_SPI; + break; + } + } +end: + free(device_id); + free(hardware_ids); + free(compatible_ids); +} + +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]; + + /* Create the record. */ + dev = (struct hid_device_info*)calloc(1, sizeof(struct hid_device_info)); + + /* Fill out the record */ + dev->next = NULL; + dev->path = hid_internal_UTF16toUTF8(path); + + 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); + } + + /* Serial Number */ + string[0] = L'\0'; + HidD_GetSerialNumberString(handle, string, sizeof(string)); + string[MAX_STRING_WCHARS - 1] = L'\0'; + dev->serial_number = _wcsdup(string); + + /* Manufacturer String */ + string[0] = L'\0'; + HidD_GetManufacturerString(handle, string, sizeof(string)); + string[MAX_STRING_WCHARS - 1] = L'\0'; + dev->manufacturer_string = _wcsdup(string); + + /* Product String */ + string[0] = L'\0'; + HidD_GetProductString(handle, string, sizeof(string)); + string[MAX_STRING_WCHARS - 1] = L'\0'; + dev->product_string = _wcsdup(string); + + hid_internal_get_info(path, dev); + + return dev; +} + +struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + struct hid_device_info *root = NULL; /* return object */ + struct hid_device_info *cur_dev = NULL; + GUID interface_class_guid; + CONFIGRET cr; + wchar_t* device_interface_list = NULL; + DWORD len; + + if (hid_init() < 0) { + /* register_global_error: global error is reset by hid_init */ + return NULL; + } + + /* Retrieve HID Interface Class GUID + https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */ + HidD_GetHidGuid(&interface_class_guid); + + /* Get the list of all device interfaces belonging to the HID class. */ + /* Retry in case of list was changed between calls to + CM_Get_Device_Interface_List_SizeW and CM_Get_Device_Interface_ListW */ + do { + cr = CM_Get_Device_Interface_List_SizeW(&len, &interface_class_guid, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + if (cr != CR_SUCCESS) { + register_global_error(L"Failed to get size of HID device interface list"); + break; + } + + if (device_interface_list != NULL) { + free(device_interface_list); + } + + device_interface_list = (wchar_t*)calloc(len, sizeof(wchar_t)); + if (device_interface_list == NULL) { + register_global_error(L"Failed to allocate memory for HID device interface list"); + return NULL; + } + cr = CM_Get_Device_Interface_ListW(&interface_class_guid, NULL, device_interface_list, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + if (cr != CR_SUCCESS && cr != CR_BUFFER_SMALL) { + register_global_error(L"Failed to get HID device interface list"); + } + } while (cr == CR_BUFFER_SMALL); + + if (cr != CR_SUCCESS) { + goto end_of_function; + } + + /* Iterate over each device interface in the HID class, looking for the right one. */ + for (wchar_t* device_interface = device_interface_list; *device_interface; device_interface += wcslen(device_interface) + 1) { + HANDLE device_handle = INVALID_HANDLE_VALUE; + HIDD_ATTRIBUTES attrib; + + /* Open read-only handle to the device */ + device_handle = open_device(device_interface, FALSE); + + /* Check validity of device_handle. */ + if (device_handle == INVALID_HANDLE_VALUE) { + /* Unable to open the device. */ + continue; + } + + /* Get the Vendor ID and Product ID for this device. */ + attrib.Size = sizeof(HIDD_ATTRIBUTES); + if (!HidD_GetAttributes(device_handle, &attrib)) { + goto cont_close; + } + + /* Check the VID/PID to see if we should add this + device to the enumeration list. */ + if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) && + (product_id == 0x0 || attrib.ProductID == product_id)) { + + /* VID/PID match. Create the record. */ + struct hid_device_info *tmp = hid_internal_get_device_info(device_interface, device_handle); + + if (tmp == NULL) { + goto cont_close; + } + + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + } + +cont_close: + CloseHandle(device_handle); + } + + if (root == NULL) { + if (vendor_id == 0 && product_id == 0) { + register_global_error(L"No HID devices found in the system."); + } else { + register_global_error(L"No HID devices with requested VID/PID found in the system."); + } + } + +end_of_function: + free(device_interface_list); + + return root; +} + +void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) +{ + /* TODO: Merge this with the Linux version. This function is platform-independent. */ + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + +HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + /* TODO: Merge this functions with the Linux version. This function should be platform independent. */ + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + /* register_global_error: global error is reset by hid_enumerate/hid_init */ + devs = hid_enumerate(vendor_id, product_id); + if (!devs) { + /* register_global_error: global error is already set by hid_enumerate */ + return NULL; + } + + cur_dev = devs; + while (cur_dev) { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) { + if (serial_number) { + if (cur_dev->serial_number && wcscmp(serial_number, cur_dev->serial_number) == 0) { + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open); + } else { + register_global_error(L"Device with requested VID/PID/(SerialNumber) not found"); + } + + hid_free_enumeration(devs); + + return handle; +} + +HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) +{ + hid_device *dev = NULL; + wchar_t* interface_path = NULL; + HANDLE device_handle = INVALID_HANDLE_VALUE; + PHIDP_PREPARSED_DATA pp_data = NULL; + HIDP_CAPS caps; + + if (hid_init() < 0) { + /* register_global_error: global error is reset by hid_init */ + goto end_of_function; + } + + interface_path = hid_internal_UTF8toUTF16(path); + if (!interface_path) { + register_string_error(dev, L"Path conversion failure"); + goto end_of_function; + } + + /* Open a handle to the device */ + device_handle = open_device(interface_path, TRUE); + + /* Check validity of write_handle. */ + if (device_handle == INVALID_HANDLE_VALUE) { + /* System devices, such as keyboards and mice, cannot be opened in + read-write mode, because the system takes exclusive control over + them. This is to prevent keyloggers. However, feature reports + can still be sent and received. Retry opening the device, but + without read/write access. */ + device_handle = open_device(interface_path, FALSE); + + /* Check the validity of the limited device_handle. */ + if (device_handle == INVALID_HANDLE_VALUE) { + register_global_winapi_error(L"open_device"); + goto end_of_function; + } + } + + /* Set the Input Report buffer size to 64 reports. */ + if (!HidD_SetNumInputBuffers(device_handle, 64)) { + register_global_winapi_error(L"set input buffers"); + goto end_of_function; + } + + /* Get the Input Report length for the device. */ + if (!HidD_GetPreparsedData(device_handle, &pp_data)) { + register_global_winapi_error(L"get preparsed data"); + goto end_of_function; + } + + if (HidP_GetCaps(pp_data, &caps) != HIDP_STATUS_SUCCESS) { + register_global_error(L"HidP_GetCaps"); + goto end_of_function; + } + + dev = new_hid_device(); + + if (dev == NULL) { + register_global_error(L"hid_device allocation error"); + goto end_of_function; + } + + dev->device_handle = device_handle; + device_handle = INVALID_HANDLE_VALUE; + + dev->output_report_length = caps.OutputReportByteLength; + dev->input_report_length = caps.InputReportByteLength; + dev->feature_report_length = caps.FeatureReportByteLength; + dev->read_buf = (char*) malloc(dev->input_report_length); + dev->device_info = hid_internal_get_device_info(interface_path, dev->device_handle); + +end_of_function: + free(interface_path); + CloseHandle(device_handle); + + if (pp_data) { + HidD_FreePreparsedData(pp_data); + } + + return dev; +} + +int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + DWORD bytes_written = 0; + int function_result = -1; + BOOL res; + BOOL overlapped = FALSE; + + unsigned char *buf; + + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return function_result; + } + + register_string_error(dev, NULL); + + /* Make sure the right number of bytes are passed to WriteFile. Windows + expects the number of bytes which are in the _longest_ report (plus + one for the report number) bytes even if the data is a report + which is shorter than that. Windows gives us this value in + caps.OutputReportByteLength. If a user passes in fewer bytes than this, + use cached temporary buffer which is the proper size. */ + if (length >= dev->output_report_length) { + /* The user passed the right number of bytes. Use the buffer as-is. */ + buf = (unsigned char *) data; + } else { + if (dev->write_buf == NULL) + dev->write_buf = (unsigned char *) malloc(dev->output_report_length); + buf = dev->write_buf; + memcpy(buf, data, length); + memset(buf + length, 0, dev->output_report_length - length); + length = dev->output_report_length; + } + + res = WriteFile(dev->device_handle, buf, (DWORD) length, NULL, &dev->write_ol); + + if (!res) { + if (GetLastError() != ERROR_IO_PENDING) { + /* WriteFile() failed. Return error. */ + register_winapi_error(dev, L"WriteFile"); + goto end_of_function; + } + overlapped = TRUE; + } + + if (overlapped) { + /* Wait for the transaction to complete. This makes + hid_write() synchronous. */ + res = WaitForSingleObject(dev->write_ol.hEvent, 1000); + if (res != WAIT_OBJECT_0) { + /* There was a Timeout. */ + register_winapi_error(dev, L"hid_write/WaitForSingleObject"); + goto end_of_function; + } + + /* Get the result. */ + res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*wait*/); + if (res) { + function_result = bytes_written; + } + else { + /* The Write operation failed. */ + register_winapi_error(dev, L"hid_write/GetOverlappedResult"); + goto end_of_function; + } + } + +end_of_function: + return function_result; +} + + +int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) +{ + DWORD bytes_read = 0; + size_t copy_len = 0; + BOOL res = FALSE; + BOOL overlapped = FALSE; + + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + register_string_error(dev, NULL); + + /* Copy the handle for convenience. */ + HANDLE ev = dev->ol.hEvent; + + if (!dev->read_pending) { + /* Start an Overlapped I/O read. */ + dev->read_pending = TRUE; + memset(dev->read_buf, 0, dev->input_report_length); + ResetEvent(ev); + res = ReadFile(dev->device_handle, dev->read_buf, (DWORD) dev->input_report_length, &bytes_read, &dev->ol); + + if (!res) { + if (GetLastError() != ERROR_IO_PENDING) { + /* ReadFile() has failed. + Clean up and return error. */ + register_winapi_error(dev, L"ReadFile"); + CancelIo(dev->device_handle); + dev->read_pending = FALSE; + goto end_of_function; + } + overlapped = TRUE; + } + } + else { + overlapped = TRUE; + } + + if (overlapped) { + if (milliseconds >= 0) { + /* See if there is any data yet. */ + res = WaitForSingleObject(ev, milliseconds); + if (res != WAIT_OBJECT_0) { + /* There was no data this time. Return zero bytes available, + but leave the Overlapped I/O running. */ + return 0; + } + } + + /* Either WaitForSingleObject() told us that ReadFile has completed, or + we are in non-blocking mode. Get the number of bytes read. The actual + data has been copied to the data[] array which was passed to ReadFile(). */ + res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/); + } + /* Set pending back to false, even if GetOverlappedResult() returned error. */ + dev->read_pending = FALSE; + + if (res && bytes_read > 0) { + if (dev->read_buf[0] == 0x0) { + /* If report numbers aren't being used, but Windows sticks a report + number (0x0) on the beginning of the report anyway. To make this + work like the other platforms, and to make it work more like the + HID spec, we'll skip over this byte. */ + bytes_read--; + copy_len = length > bytes_read ? bytes_read : length; + memcpy(data, dev->read_buf+1, copy_len); + } + else { + /* Copy the whole buffer, report number and all. */ + copy_len = length > bytes_read ? bytes_read : length; + memcpy(data, dev->read_buf, copy_len); + } + } + if (!res) { + register_winapi_error(dev, L"hid_read_timeout/GetOverlappedResult"); + } + +end_of_function: + if (!res) { + return -1; + } + + return (int) copy_len; +} + +int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); +} + +int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) +{ + dev->blocking = !nonblock; + return 0; /* Success */ +} + +int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + BOOL res = FALSE; + unsigned char *buf; + size_t length_to_send; + + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + register_string_error(dev, NULL); + + /* Windows expects at least caps.FeatureReportByteLength bytes passed + to HidD_SetFeature(), even if the report is shorter. Any less sent and + the function fails with error ERROR_INVALID_PARAMETER set. Any more + and HidD_SetFeature() silently truncates the data sent in the report + to caps.FeatureReportByteLength. */ + if (length >= dev->feature_report_length) { + buf = (unsigned char *) data; + length_to_send = length; + } else { + if (dev->feature_buf == NULL) + dev->feature_buf = (unsigned char *) malloc(dev->feature_report_length); + buf = dev->feature_buf; + memcpy(buf, data, length); + memset(buf + length, 0, dev->feature_report_length - length); + length_to_send = dev->feature_report_length; + } + + res = HidD_SetFeature(dev->device_handle, (PVOID)buf, (DWORD) length_to_send); + + if (!res) { + register_winapi_error(dev, L"HidD_SetFeature"); + return -1; + } + + return (int) length; +} + +static int hid_get_report(hid_device *dev, DWORD report_type, unsigned char *data, size_t length) +{ + BOOL res; + DWORD bytes_returned = 0; + + OVERLAPPED ol; + memset(&ol, 0, sizeof(ol)); + + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + register_string_error(dev, NULL); + + res = DeviceIoControl(dev->device_handle, + report_type, + data, (DWORD) length, + data, (DWORD) length, + &bytes_returned, &ol); + + if (!res) { + if (GetLastError() != ERROR_IO_PENDING) { + /* DeviceIoControl() failed. Return error. */ + register_winapi_error(dev, L"Get Input/Feature Report DeviceIoControl"); + return -1; + } + } + + /* Wait here until the write is done. This makes + hid_get_feature_report() synchronous. */ + res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/); + if (!res) { + /* The operation failed. */ + register_winapi_error(dev, L"Get Input/Feature Report GetOverLappedResult"); + return -1; + } + + /* When numbered reports aren't used, + bytes_returned seem to include only what is actually received from the device + (not including the first byte with 0, as an indication "no numbered reports"). */ + if (data[0] == 0x0) { + bytes_returned++; + } + + return bytes_returned; +} + +int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + /* We could use HidD_GetFeature() instead, but it doesn't give us an actual length, unfortunately */ + return hid_get_report(dev, IOCTL_HID_GET_FEATURE, data, length); +} + +int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) +{ + /* We could use HidD_GetInputReport() instead, but it doesn't give us an actual length, unfortunately */ + return hid_get_report(dev, IOCTL_HID_GET_INPUT_REPORT, data, length); +} + +void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) +{ + if (!dev) + return; + + CancelIo(dev->device_handle); + free_hid_device(dev); +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); + return -1; + } + + wcsncpy(string, dev->device_info->manufacturer_string, maxlen); + string[maxlen - 1] = L'\0'; + + register_string_error(dev, NULL); + + return 0; +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); + return -1; + } + + wcsncpy(string, dev->device_info->product_string, maxlen); + string[maxlen - 1] = L'\0'; + + register_string_error(dev, NULL); + + return 0; +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); + return -1; + } + + wcsncpy(string, dev->device_info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + + register_string_error(dev, NULL); + + return 0; +} + +HID_API_EXPORT struct hid_device_info * HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) + { + register_string_error(dev, L"NULL device info"); + return NULL; + } + + return dev->device_info; +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) +{ + BOOL res; + + res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); + if (!res) { + register_winapi_error(dev, L"HidD_GetIndexedString"); + return -1; + } + + register_string_error(dev, NULL); + + return 0; +} + +int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id) +{ + wchar_t *interface_path = NULL, *device_id = NULL; + CONFIGRET cr = CR_FAILURE; + DEVINST dev_node; + DEVPROPTYPE property_type; + ULONG len; + + if (!container_id) { + register_string_error(dev, L"Invalid Container ID"); + return -1; + } + + register_string_error(dev, NULL); + + interface_path = hid_internal_UTF8toUTF16(dev->device_info->path); + if (!interface_path) { + register_string_error(dev, L"Path conversion failure"); + goto end; + } + + /* Get the device id from interface path */ + device_id = (wchar_t*)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; +} + + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + if (dev) { + if (dev->last_error_str == NULL) + return L"Success"; + return (wchar_t*)dev->last_error_str; + } + + if (last_global_error_str == NULL) + return L"Success"; + return last_global_error_str; +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/3rdparty/hidapi/windows/hidapi.rc b/3rdparty/hidapi/windows/hidapi.rc new file mode 100644 index 0000000..530917e --- /dev/null +++ b/3rdparty/hidapi/windows/hidapi.rc @@ -0,0 +1,35 @@ +#include "winresrc.h" + +#include "hidapi.h" + +// English +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT +VS_VERSION_INFO VERSIONINFO + FILEVERSION HID_API_VERSION_MAJOR,HID_API_VERSION_MINOR,HID_API_VERSION_PATCH,0 + PRODUCTVERSION HID_API_VERSION_MAJOR,HID_API_VERSION_MINOR,HID_API_VERSION_PATCH,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0 +#ifdef _DEBUG + | VS_FF_DEBUG +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "04090000" + BEGIN + VALUE "CompanyName", "libusb/hidapi Team" + VALUE "FileDescription", "A multi-platform library to interface with HID devices (USB, Bluetooth, etc.)" + VALUE "FileVersion", HID_API_VERSION_STR + VALUE "ProductName", "HIDAPI" + VALUE "ProductVersion", HID_API_VERSION_STR + VALUE "Licence", "https://github.com/libusb/hidapi/blob/master/LICENSE.txt" + VALUE "Comments", "https://github.com/libusb/hidapi" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0 + END +END diff --git a/3rdparty/hidapi/windows/hidapi_cfgmgr32.h b/3rdparty/hidapi/windows/hidapi_cfgmgr32.h new file mode 100644 index 0000000..720906e --- /dev/null +++ b/3rdparty/hidapi/windows/hidapi_cfgmgr32.h @@ -0,0 +1,69 @@ +/******************************************************* + 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 */ + +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 +static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, {0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac} }, 10 }; // DEVPROP_TYPE_STRING +static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, {0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57} }, 256 }; // DEVPROP_TYPE_STRING +static DEVPROPKEY DEVPKEY_Device_HardwareIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 3 }; // DEVPROP_TYPE_STRING_LIST +static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 4 }; // DEVPROP_TYPE_STRING_LIST +static DEVPROPKEY DEVPKEY_Device_ContainerId = { { 0x8c7ed206, 0x3f8a, 0x4827, {0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c} }, 2 }; // DEVPROP_TYPE_GUID + +// from propkey.h +static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 1 }; // DEVPROP_TYPE_STRING +static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 4 }; // DEVPROP_TYPE_STRING + +#endif + +#endif /* HIDAPI_CFGMGR32_H */ diff --git a/3rdparty/hidapi/windows/hidapi_hidclass.h b/3rdparty/hidapi/windows/hidapi_hidclass.h new file mode 100644 index 0000000..13bd6f2 --- /dev/null +++ b/3rdparty/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/3rdparty/hidapi/windows/hidapi_hidpi.h b/3rdparty/hidapi/windows/hidapi_hidpi.h new file mode 100644 index 0000000..343ddeb --- /dev/null +++ b/3rdparty/hidapi/windows/hidapi_hidpi.h @@ -0,0 +1,65 @@ +/******************************************************* + 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 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/3rdparty/hidapi/windows/hidapi_hidsdi.h b/3rdparty/hidapi/windows/hidapi_hidsdi.h new file mode 100644 index 0000000..453f899 --- /dev/null +++ b/3rdparty/hidapi/windows/hidapi_hidsdi.h @@ -0,0 +1,60 @@ +/******************************************************* + 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 struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA; + +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_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/3rdparty/hidapi/windows/hidapi_winapi.h b/3rdparty/hidapi/windows/hidapi_winapi.h new file mode 100644 index 0000000..884712a --- /dev/null +++ b/3rdparty/hidapi/windows/hidapi_winapi.h @@ -0,0 +1,58 @@ +/******************************************************* + 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 "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 guid 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); + +#ifdef __cplusplus +} +#endif + +#endif From 6a8cbb582b48cffdc47e1554e4859dcee1bf9510 Mon Sep 17 00:00:00 2001 From: Norbert Takacs Date: Sat, 23 Mar 2024 20:00:19 +0100 Subject: [PATCH 3/6] hidapi: update cmake to build hidapi on linux Signed-off-by: Norbert Takacs --- CMakeLists.txt | 1 - src/CMakeLists.txt | 24 ++++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 34790c7..90432f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,6 @@ endif() include(CTest) -add_subdirectory(3rdparty/hidapi) add_subdirectory(3rdparty/LUA) add_subdirectory(3rdparty/SDK) add_subdirectory(3rdparty/FIP-SDK) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5076bfd..3231176 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,15 @@ + +if (WIN32) + list(APPEND XPANEL_SRCS ../3rdparty/hidapi/windows/hid.c) +elseif (APPLE) + list(APPEND XPANEL_SRCS ../3rdparty/hidapi/mac/hid.c) +elseif (UNIX) + list(APPEND XPANEL_SRCS ../3rdparty/hidapi/linux/hid.c) +endif() +set_source_files_properties(${XPANEL_SRCS} PROPERTIES LANGUAGE CXX) + add_library(xpanel SHARED + ${XPANEL_SRCS} core/Action.cpp core/AgeingCounter.cpp core/IniFileParser.cpp @@ -30,8 +41,17 @@ add_library(xpanel SHARED devices/trc-1000/TRC1000Audio.cpp ) -target_include_directories(xpanel PRIVATE ./ core devices config-ui) -target_link_libraries(xpanel PRIVATE xpsdk::xpsdk hidapi::hidapi Lua::Lua FIPSDK::FIPSDK) +target_include_directories(xpanel PRIVATE ./ core devices config-ui ../3rdparty/hidapi) + +if (UNIX) + include(FindPkgConfig) + pkg_check_modules(libudev REQUIRED IMPORTED_TARGET libudev) + find_library(HIDAPI_LIBRARY NAMES udev) + target_link_libraries(xpanel PkgConfig::libudev) +endif() + +target_link_libraries(xpanel xpsdk::xpsdk Lua::Lua FIPSDK::FIPSDK) + set_target_properties(xpanel PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN TRUE) target_compile_definitions(xpanel PRIVATE PLUGIN_SIGNATURE="${PROJECT_NAME_LOWERCASE}" PLUGIN_VERSION="${PROJECT_VERSION}") From 661547dfaf508f8135c38c595f99ec94fd6b5f4a Mon Sep 17 00:00:00 2001 From: Norbert Takacs Date: Sat, 23 Mar 2024 20:03:43 +0100 Subject: [PATCH 4/6] github actions: update dependencies Signed-off-by: Norbert Takacs --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b5e16b2..35fe6ce 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -61,7 +61,7 @@ jobs: - name: Install dependencies if: matrix.os == 'ubuntu-22.04' run: | - sudo apt-get update -qq && sudo apt-get install -y build-essential cmake liblua5.4-dev libhidapi-dev + sudo apt-get update -qq && sudo apt-get install -y build-essential cmake liblua5.4-dev libudev-dev libhidapi-dev echo 'XPANEL_INSTALL_DEPS_FLAG=-DINSTALL_DEPS=ON' >> $GITHUB_ENV From b4a5cc47053fd5b0abea8fe03d9d29c9112a8fb7 Mon Sep 17 00:00:00 2001 From: Norbert Takacs Date: Sat, 23 Mar 2024 21:07:05 +0100 Subject: [PATCH 5/6] Visual Studio: update hidapi build Signed-off-by: Norbert Takacs --- XPanel.vcxproj | 9 +++++---- XPanel.vcxproj.filters | 6 ++++++ test/test.vcxproj | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/XPanel.vcxproj b/XPanel.vcxproj index 4b98666..39514d0 100644 --- a/XPanel.vcxproj +++ b/XPanel.vcxproj @@ -125,7 +125,7 @@ Level3 WINVER=0x0601;_CRT_SECURE_NO_WARNINGS=1;_WIN32_WINNT=0x0601;_WIN32_WINDOWS=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;SIMDATA_EXPORTS;IBM=1;XPLM200=1;XPLM210=1;XPLM300=1;XPLM301=1;XPLM302=1;XPLM303=1;XPLM400=1;PLUGIN_SIGNATURE="xpanel";PLUGIN_VERSION="1.8";%(PreprocessorDefinitions) true - $(SolutionDir)src;$(SolutionDir)src\devices;$(SolutionDir)3rdparty\hidapi\headers;$(SolutionDir)3rdparty\LUA\include;$(SolutionDir)3rdparty\SDK\CHeaders\XPLM;$(SolutionDir)3rdparty\SDK\CHeaders\Widgets;$(SolutionDir)3rdparty\FIP-SDK\include;%(AdditionalIncludeDirectories) + $(SolutionDir)src;$(SolutionDir)src\devices;$(SolutionDir)3rdparty\hidapi;$(SolutionDir)3rdparty\LUA\include;$(SolutionDir)3rdparty\SDK\CHeaders\XPLM;$(SolutionDir)3rdparty\SDK\CHeaders\Widgets;$(SolutionDir)3rdparty\FIP-SDK\include;%(AdditionalIncludeDirectories) OldStyle false stdcpp17 @@ -134,7 +134,7 @@ Console true $(SolutionDir)3rdparty\hidapi\lib;$(SolutionDir)3rdparty\LUA;$(SolutionDir)3rdparty\SDK\Libraries\Win;%(AdditionalLibraryDirectories) - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;XPLM_64.lib;XPWidgets_64.lib;liblua54.a;hidapi.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;XPLM_64.lib;XPWidgets_64.lib;liblua54.a;%(AdditionalDependencies) @@ -148,7 +148,7 @@ NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS=1;IBM=1;XPLM200=1;XPLM210=1;XPLM300=1;XPLM301=1;XPLM302=1;XPLM303=1;XPLM400=1;PLUGIN_SIGNATURE="xpanel";PLUGIN_VERSION="1.8";%(PreprocessorDefinitions) true stdcpp17 - $(SolutionDir)src;$(SolutionDir)src\devices;$(SolutionDir)3rdparty\FIP-SDK\include;$(SolutionDir)3rdparty\hidapi\headers;$(SolutionDir)3rdparty\LUA\include;$(SolutionDir)3rdparty\SDK\CHeaders\XPLM;$(SolutionDir)3rdparty\SDK\CHeaders\Widgets;%(AdditionalIncludeDirectories) + $(SolutionDir)src;$(SolutionDir)src\devices;$(SolutionDir)3rdparty\FIP-SDK\include;$(SolutionDir)3rdparty\hidapi;$(SolutionDir)3rdparty\LUA\include;$(SolutionDir)3rdparty\SDK\CHeaders\XPLM;$(SolutionDir)3rdparty\SDK\CHeaders\Widgets;%(AdditionalIncludeDirectories) .\Release\64\ .\Release\64\ None @@ -160,10 +160,11 @@ true true $(SolutionDir)3rdparty\hidapi\lib;$(SolutionDir)3rdparty\LUA;$(SolutionDir)3rdparty\SDK\Libraries\Win;%(AdditionalLibraryDirectories) - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;XPLM_64.lib;XPWidgets_64.lib;liblua54.a;hidapi.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;XPLM_64.lib;XPWidgets_64.lib;liblua54.a;%(AdditionalDependencies) + diff --git a/XPanel.vcxproj.filters b/XPanel.vcxproj.filters index de4d7c4..26dccee 100644 --- a/XPanel.vcxproj.filters +++ b/XPanel.vcxproj.filters @@ -88,6 +88,9 @@ config-ui + + 3rdparty + @@ -206,5 +209,8 @@ {8b3a86f0-3f54-49eb-b6a8-bc8f47fdbfde} + + {f69a815f-0429-4561-8c7d-d29c1db15b22} + \ No newline at end of file diff --git a/test/test.vcxproj b/test/test.vcxproj index 26dc5b9..6eeaf99 100644 --- a/test/test.vcxproj +++ b/test/test.vcxproj @@ -95,7 +95,7 @@ NotUsing Level3 true - $(SolutionDir)src;$(SolutionDir)src\devices;$(SolutionDir)3rdparty\hidapi\headers;$(SolutionDir)3rdparty\LUA\include;$(SolutionDir)3rdparty\SDK\CHeaders\XPLM;$(SolutionDir)3rdparty\SDK\CHeaders\Widgets;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + $(SolutionDir)src;$(SolutionDir)src\devices;$(SolutionDir)3rdparty\hidapi;$(SolutionDir)3rdparty\LUA\include;$(SolutionDir)3rdparty\SDK\CHeaders\XPLM;$(SolutionDir)3rdparty\SDK\CHeaders\Widgets;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) WINVER=0x0601;_CRT_SECURE_NO_WARNINGS=1;_WIN32_WINNT=0x0601;_WIN32_WINDOWS=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;SIMDATA_EXPORTS;IBM=1;XPLM200=1;XPLM210=1;XPLM300=1;XPLM301=1;XPLM302=1;XPLM303=1;XPLM400=1;PLUGIN_SIGNATURE="xpanel";PLUGIN_VERSION="1.7";%(PreprocessorDefinitions) true pch.h @@ -158,7 +158,7 @@ xcopy /y /d "$(SolutionDir)3rdparty\FIP-SDK\fonts\fip-fonts.bmp" "$(OutDir)" true true false - $(SolutionDir)src;$(SolutionDir)src\devices;$(SolutionDir)3rdparty\hidapi\headers;$(SolutionDir)3rdparty\LUA\include;$(SolutionDir)3rdparty\SDK\CHeaders\XPLM;$(SolutionDir)3rdparty\SDK\CHeaders\Widgets;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + $(SolutionDir)src;$(SolutionDir)src\devices;$(SolutionDir)3rdparty\hidapi;$(SolutionDir)3rdparty\LUA\include;$(SolutionDir)3rdparty\SDK\CHeaders\XPLM;$(SolutionDir)3rdparty\SDK\CHeaders\Widgets;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) WINVER=0x0601;_CRT_SECURE_NO_WARNINGS=1;_WIN32_WINNT=0x0601;_WIN32_WINDOWS=0x0601;WIN32;_WINDOWS;_USRDLL;SIMDATA_EXPORTS;IBM=1;XPLM200=1;XPLM210=1;XPLM300=1;XPLM301=1;XPLM302=1;XPLM303=1;XPLM400=1;NDEBUG;PLUGIN_SIGNATURE="xpanel";PLUGIN_VERSION="1.7";%(PreprocessorDefinitions) true pch.h From debd4b3d60f8805a0f8ee335d64bb88e19dbbf7e Mon Sep 17 00:00:00 2001 From: Norbert Takacs Date: Sat, 23 Mar 2024 22:01:58 +0100 Subject: [PATCH 6/6] release script: remove hidapi windows dll Signed-off-by: Norbert Takacs --- .github/workflows/release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d33582..1d52484 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,6 @@ jobs: - name: Copy 3rd party dlls (Windows) run: | cp 3rdparty/LUA/lua54.dll built-plugin-windows/plugins/XPanel/64/ - cp 3rdparty/hidapi/lib/hidapi.dll built-plugin-windows/plugins/XPanel/64/ cp 3rdparty/FIP-SDK/lib/DirectOutput.dll built-plugin-windows/plugins/XPanel/64/ - name: Copy board-config.ini