hidapi 0.1.5 → 0.1.6
raw patch · 7 files changed
+1266/−498 lines, 7 files
Files
- README.md +12/−0
- System/HIDAPI.hsc +21/−0
- cbits/hidapi/hidapi.h +137/−30
- cbits/hidapi/linux/hid.c +459/−146
- cbits/hidapi/mac/hid.c +406/−233
- cbits/hidapi/windows/hid.c +223/−83
- hidapi.cabal +8/−6
+ README.md view
@@ -0,0 +1,12 @@+## hidapi [](https://travis-ci.org/chpatrick/haskell-hidapi)++Haskell FFI bindings to the HIDAPI library++### Vendored HIDAPI++hidapi bundles C source from [the libusb project][vendor0-libusb]. The current+version of the HIDAPI library bundled with this library is [libusb/hidapi@`f8a47b5`][vendor1-commit].++[vendor0-libusb]:https://libusb.info+[vendor1-commit]:https://github.com/libusb/hidapi/tree/3cd17427375df568aaeff8aba51b6d200f8a47b5+
System/HIDAPI.hsc view
@@ -8,6 +8,8 @@ , open, openPath, openDeviceInfo , close , System.HIDAPI.read+ , System.HIDAPI.readTimeout+ , System.HIDAPI.setBlocking , System.HIDAPI.write , System.HIDAPI.getFeatureReport , System.HIDAPI.sendFeatureReport@@ -234,6 +236,9 @@ foreign import ccall unsafe "hidapi/hidapi.h hid_read" hid_read :: Device -> Ptr CChar -> CSize -> IO CInt +foreign import ccall unsafe "hidapi/hidapi.h hid_read_timeout"+ hid_read_timeout :: Device -> Ptr CChar -> CSize -> CInt -> IO CInt+ foreign import ccall unsafe "hidapi/hidapi.h hid_write" hid_write :: Device -> Ptr CChar -> CSize -> IO CInt @@ -243,10 +248,26 @@ foreign import ccall unsafe "hidapi/hidapi.h hid_send_feature_report" hid_send_feature_report :: Device -> Ptr CChar -> CSize -> IO CInt +foreign import ccall unsafe "hidapi/hidapi.h hid_set_nonblocking"+ hid_set_nonblocking :: Device -> CInt -> IO CInt++setBlocking :: Device -- USB Device to act on, see open, openPath or openDeviceInfo+ -> Bool -- True -> read blocks; False -> read may return immediately with 0 bytes read+ -> IO ()+setBlocking dev blocking = do+ n' <- hid_set_nonblocking dev $ if blocking then 0 else 1+ checkWithHidError (n' /= -1) dev "Unable to set blocking mode" "hid_set_nonblocking returned -1"+ read :: Device -> Int -> IO ByteString read dev n = allocaBytes n $ \b -> do n' <- hid_read dev b (fromIntegral n) checkWithHidError (n' /= -1) dev "Read failed" "hid_read returned -1"+ packCStringLen ( b, fromIntegral n' )++readTimeout :: Device -> Int -> Int -> IO ByteString+readTimeout dev n time = allocaBytes n $ \b -> do+ n' <- hid_read_timeout dev b (fromIntegral n) (fromIntegral time)+ checkWithHidError (n' /= -1) dev "Read failed" "hid_read_timeout returned -1" packCStringLen ( b, fromIntegral n' ) write :: Device -> ByteString -> IO Int
cbits/hidapi/hidapi.h view
@@ -17,7 +17,7 @@ files located at the root of the source distribution. These files may also be found in the public source code repository located at:- http://github.com/signal11/hidapi .+ https://github.com/libusb/hidapi . ********************************************************/ /** @file@@ -39,9 +39,42 @@ #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 10+/** @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 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)+ #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 */ @@ -63,15 +96,19 @@ /** Product string */ wchar_t *product_string; /** Usage Page for this Device/Interface- (Windows/Mac only). */+ (Windows/Mac/hidraw only) */ unsigned short usage_page; /** Usage for this Device/Interface- (Windows/Mac only).*/+ (Windows/Mac/hidraw only) */ unsigned short usage; /** The USB interface which this logical device- represents. Valid on both Linux implementations- in all cases, and valid on the Windows implementation- only if the device contains more than one interface. */+ represents.++ * Valid 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 */@@ -87,7 +124,7 @@ 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@@ -125,7 +162,7 @@ @returns This function returns a pointer to a linked list of type- struct #hid_device, containing information about the HID devices+ struct #hid_device_info, containing information about the HID devices attached to the system, or NULL in the case of failure. Free this linked list by calling hid_free_enumeration(). */@@ -147,6 +184,8 @@ If @p serial_number is NULL, the first device with the specified VID and PID is opened. + This function sets the return value of hid_error().+ @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.@@ -165,6 +204,8 @@ platform-specific path name can be used (eg: /dev/hidraw0 on Linux). + This function sets the return value of hid_error().+ @ingroup API @param path The path name of the device to open @@ -190,8 +231,10 @@ one exists. If it does not, it will send the data through the Control Endpoint (Endpoint 0). + This function sets the return value of hid_error().+ @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(). @param data The data to send, including the report number as the first byte. @param length The length in bytes of the data to send.@@ -200,7 +243,7 @@ This function returns the actual number of bytes written and -1 on error. */- int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length);+ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length); /** @brief Read an Input report from a HID device with timeout. @@ -208,8 +251,10 @@ to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number if the device uses numbered reports. + This function sets the return value of hid_error().+ @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into. @param length The number of bytes to read. For devices with multiple reports, make sure to read an extra byte for@@ -229,8 +274,10 @@ to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number if the device uses numbered reports. + This function sets the return value of hid_error().+ @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into. @param length The number of bytes to read. For devices with multiple reports, make sure to read an extra byte for@@ -241,7 +288,7 @@ -1 on error. If no packet was available to be read and the handle is in non-blocking mode, this function returns 0. */- int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length);+ int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length); /** @brief Set the device handle to be non-blocking. @@ -253,7 +300,7 @@ Nonblocking can be turned on and off at any time. @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(). @param nonblock enable or not the nonblocking reads - 1 to enable nonblocking - 0 to disable nonblocking.@@ -261,7 +308,7 @@ @returns This function returns 0 on success and -1 on error. */- int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock);+ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); /** @brief Send a Feature report to the device. @@ -278,8 +325,10 @@ report data (16 bytes). In this example, the length passed in would be 17. + This function sets the return value of hid_error().+ @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(). @param data The data to send, including the report number as the first byte. @param length The length in bytes of the data to send, including@@ -289,7 +338,7 @@ This function returns the actual number of bytes written and -1 on error. */- int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length);+ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length); /** @brief Get a feature report from a HID device. @@ -299,7 +348,34 @@ still contain the Report ID, and the report data will start in data[1]. + This function sets the return value of hid_error().+ @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.+ */+ 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.++ 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 device 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@@ -314,55 +390,57 @@ one for the report ID (which is still in the first byte), or -1 on error. */- int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length);+ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length); /** @brief Close a HID device. + This function sets the return value of hid_error().+ @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(). */- void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device);+ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev); /** @brief Get The Manufacturer String from a HID device. @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(). @param string A wide string buffer to put the data into. @param maxlen The length of the buffer in multiples of wchar_t. @returns This function returns 0 on success and -1 on error. */- int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen);+ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen); /** @brief Get The Product String from a HID device. @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(). @param string A wide string buffer to put the data into. @param maxlen The length of the buffer in multiples of wchar_t. @returns This function returns 0 on success and -1 on error. */- int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen);+ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen); /** @brief Get The Serial Number String from a HID device. @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(). @param string A wide string buffer to put the data into. @param maxlen The length of the buffer in multiples of wchar_t. @returns This function returns 0 on success and -1 on error. */- int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen);+ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); /** @brief Get a string from a HID device, based on its string index. @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(). @param string_index The index of the string to get. @param string A wide string buffer to put the data into. @param maxlen The length of the buffer in multiples of wchar_t.@@ -370,18 +448,47 @@ @returns This function returns 0 on success and -1 on error. */- int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen);+ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen); /** @brief Get a string describing the last error which occurred. + Whether a function sets the last error is noted in its+ documentation. These functions will reset the last error+ to NULL before their execution.++ Strings returned from hid_error() must not be freed by the user!++ This function is thread-safe, and error messages are thread-local.+ @ingroup API- @param device A device handle returned from hid_open().+ @param dev A device handle returned from hid_open(),+ or NULL to get the last non-device-specific error+ (e.g. for errors in hid_open() itself). @returns This function returns a string containing the last error which occurred or NULL if none has occurred. */- HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device);+ HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev);++ /** @brief Get a runtime version of the library.++ @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.++ @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 }
cbits/hidapi/linux/hid.c view
@@ -18,7 +18,7 @@ files located at the root of the source distribution. These files may also be found in the public source code repository located at:- http://github.com/signal11/hidapi .+ https://github.com/libusb/hidapi . ********************************************************/ /* C */@@ -45,16 +45,7 @@ #include "hidapi.h" -/* Definitions from linux/hidraw.h. Since these are new, some distros- may not have header files which contain them. */-#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 - /* USB HID device property names */ const char *device_string_names[] = { "manufacturer",@@ -75,38 +66,26 @@ int device_handle; int blocking; int uses_numbered_reports;+ wchar_t *last_error_str; }; --static __u32 kernel_version = 0;--static __u32 detect_kernel_version(void)-{- struct utsname name;- int major, minor, release;- int ret;-- uname(&name);- ret = sscanf(name.release, "%d.%d.%d", &major, &minor, &release);- if (ret == 3) {- return KERNEL_VERSION(major, minor, release);- }-- ret = sscanf(name.release, "%d.%d", &major, &minor);- if (ret == 2) {- return KERNEL_VERSION(major, minor, 0);- }+static struct hid_api_version api_version = {+ .major = HID_API_VERSION_MAJOR,+ .minor = HID_API_VERSION_MINOR,+ .patch = HID_API_VERSION_PATCH+}; - printf("Couldn't determine kernel version from version string \"%s\"\n", name.release);- return 0;-}+/* Global error message that is not specific to a device, e.g. for+ hid_open(). It is thread-local like errno. */+__thread wchar_t *last_global_error_str = NULL; static hid_device *new_hid_device(void) {- hid_device *dev = calloc(1, sizeof(hid_device));+ hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); dev->device_handle = -1; dev->blocking = 1; dev->uses_numbered_reports = 0;+ dev->last_error_str = NULL; return dev; }@@ -122,7 +101,7 @@ if ((size_t) -1 == wlen) { return wcsdup(L""); }- ret = calloc(wlen+1, sizeof(wchar_t));+ ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); mbstowcs(ret, utf8, wlen+1); ret[wlen] = 0x0000; }@@ -130,6 +109,61 @@ return ret; } ++/* 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)+{+ if (last_global_error_str)+ free(last_global_error_str);++ last_global_error_str = utf8_to_wchar_t(msg);+}++/* See register_global_error, but you can pass a format string into this function. */+static void register_global_error_format(const char *format, ...)+{+ va_list args;+ va_start(args, format);++ char msg[100];+ vsnprintf(msg, sizeof(msg), format, args);++ va_end(args);++ register_global_error(msg);+}++/* Set the last error for a device to be reported by hid_error(device).+ * 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_device_error(device, NULL) to indicate "no error". */+static void register_device_error(hid_device *dev, const char *msg)+{+ if (dev->last_error_str)+ free(dev->last_error_str);++ dev->last_error_str = utf8_to_wchar_t(msg);+}++/* See 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);++ char msg[100];+ vsnprintf(msg, sizeof(msg), format, args);++ va_end(args);++ register_device_error(dev, msg);+}+ /* 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)@@ -137,11 +171,67 @@ 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;+}+ /* uses_numbered_reports() returns 1 if report_descriptor describes a device which contains numbered reports. */ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { unsigned int i = 0;- int size_code; int data_len, key_size; while (i < size) {@@ -154,42 +244,9 @@ return 1; } - //printf("key: %02hhx\n", key);-- if ((key & 0xf0) == 0xf0) {- /* This is a Long Item. The next byte contains the- length of the data section (value) for this key.- See the HID specification, version 1.11, section- 6.2.2.3, titled "Long Items." */- if (i+1 < size)- data_len = report_descriptor[i+1];- else- data_len = 0; /* malformed report */- key_size = 3;- }- else {- /* This is a Short Item. The bottom two bits of the- key contain the size code for the data section- (value) for this key. Refer to the HID- specification, version 1.11, section 6.2.2.2,- titled "Short Items." */- size_code = key & 0x3;- switch (size_code) {- case 0:- case 1:- case 2:- data_len = size_code;- break;- case 3:- data_len = 4;- break;- default:- /* Can't ever happen since size_code is & 0x3 */- data_len = 0;- break;- };- key_size = 1;- }+ /* Determine data_len and key_size */+ if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size))+ return 0; /* malformed report */ /* Skip over this key and it's associated data */ i += data_len + key_size;@@ -200,11 +257,162 @@ } /*+ * 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 it's 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, <sysfs_path>/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);+ 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;+}++static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc)+{+ int res = -1;+ /* Construct <sysfs_path>/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;+}++/* * The caller is responsible for free()ing the (newly-allocated) character * strings pointed to by serial_number_utf8 and product_name_utf8 after use. */ static int-parse_uevent_info(const char *uevent, int *bus_type,+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) {@@ -263,18 +471,20 @@ struct udev_device *udev_dev, *parent, *hid_dev; struct stat s; int ret = -1;- char *serial_number_utf8 = NULL;- char *product_name_utf8 = NULL;+ char *serial_number_utf8 = NULL;+ char *product_name_utf8 = NULL; /* Create the udev object */ udev = udev_new(); if (!udev) {- printf("Can't create udev\n");+ register_global_error("Couldn't create udev context"); return -1; } /* Get the dev_t (major/minor numbers) from the file handle. */- fstat(dev->device_handle, &s);+ ret = fstat(dev->device_handle, &s);+ if (-1 == ret)+ return ret; /* Open a udev device from the dev_t. 'c' means character device. */ udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); if (udev_dev) {@@ -285,7 +495,7 @@ if (hid_dev) { unsigned short dev_vid; unsigned short dev_pid;- int bus_type;+ unsigned bus_type; size_t retm; ret = parse_uevent_info(@@ -296,27 +506,8 @@ &serial_number_utf8, &product_name_utf8); - if (bus_type == BUS_BLUETOOTH) {- switch (key) {- case DEVICE_STRING_MANUFACTURER:- wcsncpy(string, L"", maxlen);- ret = 0;- break;- case DEVICE_STRING_PRODUCT:- retm = mbstowcs(string, product_name_utf8, maxlen);- ret = (retm == (size_t)-1)? -1: 0;- break;- case DEVICE_STRING_SERIAL:- retm = mbstowcs(string, serial_number_utf8, maxlen);- ret = (retm == (size_t)-1)? -1: 0;- break;- case DEVICE_STRING_COUNT:- default:- ret = -1;- break;- }- }- else {+ /* Standard USB device */+ if (bus_type == BUS_USB) { /* This is a USB device. Find its parent USB Device node. */ parent = udev_device_get_parent_with_subsystem_devtype( udev_dev,@@ -338,16 +529,46 @@ /* Convert the string from UTF-8 to wchar_t */ retm = mbstowcs(string, str, maxlen); ret = (retm == (size_t)-1)? -1: 0;- goto end; }++ /* USB information parsed */+ goto end; }+ else {+ /* Correctly handled below */+ } }++ /* USB information not available (uhid) or another type of HID bus */+ switch (bus_type) {+ case BUS_BLUETOOTH:+ case BUS_I2C:+ case BUS_USB:+ switch (key) {+ case DEVICE_STRING_MANUFACTURER:+ wcsncpy(string, L"", maxlen);+ ret = 0;+ break;+ case DEVICE_STRING_PRODUCT:+ retm = mbstowcs(string, product_name_utf8, maxlen);+ ret = (retm == (size_t)-1)? -1: 0;+ break;+ case DEVICE_STRING_SERIAL:+ retm = mbstowcs(string, serial_number_utf8, maxlen);+ ret = (retm == (size_t)-1)? -1: 0;+ break;+ case DEVICE_STRING_COUNT:+ default:+ ret = -1;+ break;+ }+ } } } end:- free(serial_number_utf8);- free(product_name_utf8);+ free(serial_number_utf8);+ free(product_name_utf8); udev_device_unref(udev_dev); /* parent and hid_dev don't need to be (and can't be) unref'd.@@ -357,6 +578,16 @@ return ret; } +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;@@ -366,14 +597,14 @@ if (!locale) setlocale(LC_CTYPE, ""); - kernel_version = detect_kernel_version();- return 0; } int HID_API_EXPORT hid_exit(void) {- /* Nothing to do for this in the Linux/hidraw implementation. */+ /* Free global error message */+ register_global_error(NULL);+ return 0; } @@ -393,7 +624,7 @@ /* Create the udev object */ udev = udev_new(); if (!udev) {- printf("Can't create udev\n");+ register_global_error("Couldn't create udev context"); return NULL; } @@ -416,8 +647,9 @@ unsigned short dev_pid; char *serial_number_utf8 = NULL; char *product_name_utf8 = NULL;- int bus_type;+ unsigned bus_type; int result;+ struct hidraw_report_descriptor report_desc; /* Get the filename of the /sys entry for the device and create a udev_device object (dev) representing it */@@ -448,9 +680,15 @@ goto next; } - if (bus_type != BUS_USB && bus_type != BUS_BLUETOOTH) {- /* We only know how to handle USB and BT devices. */- goto next;+ /* Filter out unhandled devices right away */+ switch (bus_type) {+ case BUS_BLUETOOTH:+ case BUS_I2C:+ case BUS_USB:+ break;++ default:+ goto next; } /* Check the VID/PID against the arguments */@@ -459,7 +697,7 @@ struct hid_device_info *tmp; /* VID/PID match. Create the record. */- tmp = malloc(sizeof(struct hid_device_info));+ tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); if (cur_dev) { cur_dev->next = tmp; }@@ -499,22 +737,14 @@ "usb", "usb_device"); + /* uhid USB devices+ Since this is a virtual hid interface, no USB information will+ be available. */ if (!usb_dev) {- /* Free this device */- free(cur_dev->serial_number);- free(cur_dev->path);- free(cur_dev);-- /* Take it off the device list. */- if (prev_dev) {- prev_dev->next = NULL;- cur_dev = prev_dev;- }- else {- cur_dev = root = NULL;- }-- goto next;+ /* Manufacturer and Product strings */+ cur_dev->manufacturer_string = wcsdup(L"");+ cur_dev->product_string = utf8_to_wchar_t(product_name_utf8);+ break; } /* Manufacturer and Product strings */@@ -538,6 +768,7 @@ break; case BUS_BLUETOOTH:+ case BUS_I2C: /* Manufacturer and Product strings */ cur_dev->manufacturer_string = wcsdup(L""); cur_dev->product_string = utf8_to_wchar_t(product_name_utf8);@@ -549,6 +780,45 @@ * 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 */+ tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));+ cur_dev->next = tmp;+ prev_dev = cur_dev;+ cur_dev = tmp;++ /* Update fields */+ cur_dev->path = strdup(dev_path);+ 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;+ }+ } } next:@@ -582,6 +852,9 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) {+ /* Set global error to none */+ register_global_error(NULL);+ struct hid_device_info *devs, *cur_dev; const char *path_to_open = NULL; hid_device *handle = NULL;@@ -608,6 +881,8 @@ if (path_to_open) { /* Open the device */ handle = hid_open_path(path_to_open);+ } else {+ register_global_error("No such device"); } hid_free_enumeration(devs);@@ -617,6 +892,9 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) {+ /* Set global error to none */+ register_global_error(NULL);+ hid_device *dev = NULL; hid_init();@@ -627,7 +905,9 @@ dev->device_handle = open(path, O_RDWR); /* If we have a good handle, return it. */- if (dev->device_handle > 0) {+ if (dev->device_handle >= 0) {+ /* Set device error to none */+ register_device_error(dev, NULL); /* Get the report descriptor */ int res, desc_size = 0;@@ -638,14 +918,13 @@ /* Get Report Descriptor Size */ res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); if (res < 0)- perror("HIDIOCGRDESCSIZE");-+ register_device_error_format(dev, "ioctl (GRDESCSIZE): %s", strerror(errno)); /* Get Report Descriptor */ rpt_desc.size = desc_size; res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc); if (res < 0) {- perror("HIDIOCGRDESC");+ register_device_error_format(dev, "ioctl (GRDESC): %s", strerror(errno)); } else { /* Determine if this device uses numbered reports. */ dev->uses_numbered_reports =@@ -657,6 +936,7 @@ } else { /* Unable to open any devices. */+ register_global_error(strerror(errno)); free(dev); return NULL; }@@ -669,12 +949,17 @@ 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) {@@ -691,29 +976,30 @@ fds.events = POLLIN; fds.revents = 0; ret = poll(&fds, 1, milliseconds);- if (ret == -1 || ret == 0) {- /* Error or timeout */+ if (ret == 0) {+ /* Timeout */ return ret; }+ if (ret == -1) {+ /* Error */+ register_device_error(dev, strerror(errno));+ return ret;+ } else { /* Check for errors on the file descriptor. This will indicate a device disconnection. */ if (fds.revents & (POLLERR | POLLHUP | POLLNVAL))+ // We cannot use strerror() here as no -1 was returned from poll(). return -1; } } bytes_read = read(dev->device_handle, data, length);- if (bytes_read < 0 && (errno == EAGAIN || errno == EINPROGRESS))- bytes_read = 0;-- if (bytes_read >= 0 &&- kernel_version != 0 &&- kernel_version < KERNEL_VERSION(2,6,34) &&- dev->uses_numbered_reports) {- /* Work around a kernel bug. Chop off the first byte. */- memmove(data, data+1, bytes_read);- bytes_read--;+ if (bytes_read < 0) {+ if (errno == EAGAIN || errno == EINPROGRESS)+ bytes_read = 0;+ else+ register_device_error(dev, strerror(errno)); } return bytes_read;@@ -741,7 +1027,7 @@ res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data); if (res < 0)- perror("ioctl (SFEATURE)");+ register_device_error_format(dev, "ioctl (SFEATURE): %s", strerror(errno)); return res; }@@ -752,18 +1038,32 @@ res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data); if (res < 0)- perror("ioctl (GFEATURE)");-+ register_device_error_format(dev, "ioctl (GFEATURE): %s", strerror(errno)); return res; } +// Not supported by Linux HidRaw yet+int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)+{+ (void)dev;+ (void)data;+ (void)length;+ return -1;+} void HID_API_EXPORT hid_close(hid_device *dev) { if (!dev) return;- close(dev->device_handle);++ int ret = close(dev->device_handle);++ register_global_error((ret == -1)? strerror(errno): NULL);++ /* Free the device error message */+ register_device_error(dev, NULL);+ free(dev); } @@ -785,11 +1085,24 @@ 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; 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) {- return NULL;+ if (dev) {+ if (dev->last_error_str == NULL)+ return L"Success";+ return dev->last_error_str;+ }++ if (last_global_error_str == NULL)+ return L"Success";+ return last_global_error_str; }
cbits/hidapi/mac/hid.c view
@@ -17,22 +17,28 @@ files located at the root of the source distribution. These files may also be found in the public source code repository located at:- http://github.com/signal11/hidapi .+ https://github.com/libusb/hidapi . ********************************************************/ /* See Apple Technical Note TN2187 for details on IOHidManager. */ #include <IOKit/hid/IOHIDManager.h> #include <IOKit/hid/IOHIDKeys.h>+#include <IOKit/IOKitLib.h>+#include <IOKit/usb/USBSpec.h> #include <CoreFoundation/CoreFoundation.h> #include <wchar.h> #include <locale.h> #include <pthread.h> #include <sys/time.h> #include <unistd.h>+#include <dlfcn.h> #include "hidapi.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@@ -47,6 +53,8 @@ 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;@@ -122,7 +130,7 @@ static hid_device *new_hid_device(void) {- hid_device *dev = calloc(1, sizeof(hid_device));+ hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); dev->device_handle = NULL; dev->blocking = 1; dev->uses_numbered_reports = 0;@@ -176,16 +184,32 @@ free(dev); } +static struct hid_api_version api_version = {+ .major = HID_API_VERSION_MAJOR,+ .minor = HID_API_VERSION_MINOR,+ .patch = HID_API_VERSION_PATCH+};+ static IOHIDManagerRef hid_mgr = 0x0;+static int is_macos_10_10_or_greater = 0; #if 0-static void register_error(hid_device *device, const char *op)+static void register_error(hid_device *dev, const char *op) { } #endif +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) {@@ -202,6 +226,11 @@ 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));@@ -212,11 +241,6 @@ return get_int_property(device, CFSTR(kIOHIDProductIDKey)); } -static int32_t get_location_id(IOHIDDeviceRef device)-{- return get_int_property(device, CFSTR(kIOHIDLocationIDKey));-}- static int32_t get_max_report_length(IOHIDDeviceRef device) { return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey));@@ -229,7 +253,7 @@ if (!len) return 0; - str = IOHIDDeviceGetProperty(device, prop);+ str = (CFStringRef) IOHIDDeviceGetProperty(device, prop); buf[0] = 0; @@ -242,18 +266,18 @@ len --; range.location = 0;- range.length = ((size_t)str_len > len)? len: (size_t)str_len;+ range.length = ((size_t) str_len > len)? len: (size_t) str_len; chars_copied = CFStringGetBytes(str, range, kCFStringEncodingUTF32LE,- (char)'?',+ (char) '?', FALSE, (UInt8*)buf, len * sizeof(wchar_t), &used_buf_len); - if (chars_copied == len)- buf[len] = 0; /* len is decremented above */+ if (chars_copied <= 0)+ buf[0] = 0; else buf[chars_copied] = 0; @@ -264,46 +288,6 @@ } -static int get_string_property_utf8(IOHIDDeviceRef device, CFStringRef prop, char *buf, size_t len)-{- CFStringRef str;- if (!len)- return 0;-- str = IOHIDDeviceGetProperty(device, prop);-- buf[0] = 0;-- if (str) {- len--;-- CFIndex str_len = CFStringGetLength(str);- CFRange range;- range.location = 0;- range.length = str_len;- CFIndex used_buf_len;- CFIndex chars_copied;- chars_copied = CFStringGetBytes(str,- range,- kCFStringEncodingUTF8,- (char)'?',- FALSE,- (UInt8*)buf,- len,- &used_buf_len);-- if (used_buf_len == len)- buf[len] = 0; /* len is decremented above */- else- buf[used_buf_len] = 0;-- return used_buf_len;- }- else- return 0;-}-- static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len) { return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len);@@ -324,39 +308,68 @@ static wchar_t *dup_wcs(const wchar_t *s) { size_t len = wcslen(s);- wchar_t *ret = malloc((len+1)*sizeof(wchar_t));+ wchar_t *ret = (wchar_t*) malloc((len+1)*sizeof(wchar_t)); wcscpy(ret, s); return ret; } --static int make_path(IOHIDDeviceRef device, char *buf, size_t len)+/* hidapi_IOHIDDeviceGetService()+ *+ * Return the io_service_t corresponding to a given IOHIDDeviceRef, either by:+ * - on OS X 10.6 and above, calling IOHIDDeviceGetService()+ * - on OS X 10.5, extract it from the IOHIDDevice struct+ */+static io_service_t hidapi_IOHIDDeviceGetService(IOHIDDeviceRef device) {- int res;- unsigned short vid, pid;- char transport[32];- int32_t location;-- buf[0] = '\0';-- res = get_string_property_utf8(- device, CFSTR(kIOHIDTransportKey),- transport, sizeof(transport));-- if (!res)- return -1;+ static void *iokit_framework = NULL;+ typedef io_service_t (*dynamic_IOHIDDeviceGetService_t)(IOHIDDeviceRef device);+ static dynamic_IOHIDDeviceGetService_t dynamic_IOHIDDeviceGetService = NULL; - location = get_location_id(device);- vid = get_vendor_id(device);- pid = get_product_id(device);+ /* Use dlopen()/dlsym() to get a pointer to IOHIDDeviceGetService() if it exists.+ * If any of these steps fail, dynamic_IOHIDDeviceGetService will be left NULL+ * and the fallback method will be used.+ */+ if (iokit_framework == NULL) {+ iokit_framework = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_LAZY); - res = snprintf(buf, len, "%s_%04hx_%04hx_%x",- transport, vid, pid, location);+ if (iokit_framework != NULL)+ dynamic_IOHIDDeviceGetService = (dynamic_IOHIDDeviceGetService_t) dlsym(iokit_framework, "IOHIDDeviceGetService");+ } + if (dynamic_IOHIDDeviceGetService != NULL) {+ /* Running on OS X 10.6 and above: IOHIDDeviceGetService() exists */+ return dynamic_IOHIDDeviceGetService(device);+ }+ else+ {+ /* Running on OS X 10.5: IOHIDDeviceGetService() doesn't exist.+ *+ * Be naughty and pull the service out of the IOHIDDevice.+ * IOHIDDevice is an opaque struct not exposed to applications, but its+ * layout is stable through all available versions of OS X.+ * Tested and working on OS X 10.5.8 i386, x86_64, and ppc.+ */+ struct IOHIDDevice_internal {+ /* The first field of the IOHIDDevice struct is a+ * CFRuntimeBase (which is a private CF struct).+ *+ * a, b, and c are the 3 fields that make up a CFRuntimeBase.+ * See http://opensource.apple.com/source/CF/CF-476.18/CFRuntime.h+ *+ * The second field of the IOHIDDevice is the io_service_t we're looking for.+ */+ uintptr_t a;+ uint8_t b[4];+#if __LP64__+ uint32_t c;+#endif+ io_service_t service;+ };+ struct IOHIDDevice_internal *tmp = (struct IOHIDDevice_internal *) device; - buf[len-1] = '\0';- return res+1;+ return tmp->service;+ } } /* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */@@ -373,12 +386,23 @@ 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) { if (!hid_mgr) {+ is_macos_10_10_or_greater = (NSAppKitVersionNumber >= 1343); /* NSAppKitVersionNumber10_10 */ return init_hid_manager(); } @@ -405,6 +429,123 @@ } 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];++ struct hid_device_info *cur_dev;+ io_object_t iokit_dev;+ kern_return_t res;+ io_string_t path;++ 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 (IOService plane) */+ iokit_dev = hidapi_IOHIDDeviceGetService(dev);+ res = IORegistryEntryGetPath(iokit_dev, kIOServicePlane, path);+ if (res == KERN_SUCCESS)+ cur_dev->path = strdup(path);+ else+ cur_dev->path = strdup("");++ /* Serial Number */+ get_serial_number(dev, buf, BUF_LEN);+ cur_dev->serial_number = dup_wcs(buf);++ /* Manufacturer and Product strings */+ get_manufacturer_string(dev, buf, BUF_LEN);+ cur_dev->manufacturer_string = dup_wcs(buf);+ get_product_string(dev, buf, BUF_LEN);+ cur_dev->product_string = dup_wcs(buf);++ /* VID/PID */+ cur_dev->vendor_id = dev_vid;+ cur_dev->product_id = dev_pid;++ /* Release Number */+ cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey));++ /* Interface Number */+ /* 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;+ }++ 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 */@@ -420,74 +561,61 @@ process_pending_events(); /* Get a list of the Devices */- IOHIDManagerSetDeviceMatching(hid_mgr, NULL);+ CFMutableDictionaryRef matching = NULL;+ if (vendor_id != 0 || product_id != 0) {+ matching = CFDictionaryCreateMutable(kCFAllocatorDefault, kIOHIDOptionsTypeNone, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);++ if (matching && vendor_id != 0) {+ CFNumberRef v = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &vendor_id);+ CFDictionarySetValue(matching, CFSTR(kIOHIDVendorIDKey), v);+ CFRelease(v);+ }++ if (matching && product_id != 0) {+ CFNumberRef p = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &product_id);+ CFDictionarySetValue(matching, CFSTR(kIOHIDProductIDKey), p);+ CFRelease(p);+ }+ }+ IOHIDManagerSetDeviceMatching(hid_mgr, matching);+ if (matching != NULL) {+ CFRelease(matching);+ }+ CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr);+ if (device_set == NULL) {+ return NULL;+ } /* Convert the list into a C array so we can iterate easily. */ num_devices = CFSetGetCount(device_set);- IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef));+ IOHIDDeviceRef *device_array = (IOHIDDeviceRef*) calloc(num_devices, sizeof(IOHIDDeviceRef)); CFSetGetValues(device_set, (const void **) device_array); /* Iterate over each device, making an entry for it. */ for (i = 0; i < num_devices; i++) {- unsigned short dev_vid;- unsigned short dev_pid;- #define BUF_LEN 256- wchar_t buf[BUF_LEN];- char cbuf[BUF_LEN]; IOHIDDeviceRef dev = device_array[i];-- if (!dev) {- continue;- }- dev_vid = get_vendor_id(dev);- dev_pid = get_product_id(dev);-- /* Check the VID/PID against the arguments */- if ((vendor_id == 0x0 || vendor_id == dev_vid) &&- (product_id == 0x0 || product_id == dev_pid)) {- struct hid_device_info *tmp;- size_t len;-- /* VID/PID match. Create the record. */- tmp = malloc(sizeof(struct hid_device_info));- if (cur_dev) {- cur_dev->next = tmp;- }- else {- root = tmp;- }- cur_dev = tmp;-- /* Get the Usage Page and Usage for this device. */- cur_dev->usage_page = get_int_property(dev, CFSTR(kIOHIDPrimaryUsagePageKey));- cur_dev->usage = get_int_property(dev, CFSTR(kIOHIDPrimaryUsageKey));-- /* Fill out the record */- cur_dev->next = NULL;- len = make_path(dev, cbuf, sizeof(cbuf));- cur_dev->path = strdup(cbuf);-- /* 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);+ if (!dev) {+ continue;+ } - /* VID/PID */- cur_dev->vendor_id = dev_vid;- cur_dev->product_id = dev_pid;+ struct hid_device_info *tmp = create_device_info(dev);+ if (tmp == NULL) {+ continue;+ } - /* Release Number */- cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey));+ if (cur_dev) {+ cur_dev->next = tmp;+ }+ else {+ root = tmp;+ }+ cur_dev = tmp; - /* Interface Number (Unsupported on Mac)*/- cur_dev->interface_number = -1;+ /* move the pointer to the tail of returnd list */+ while (cur_dev->next != NULL) {+ cur_dev = cur_dev->next; } } @@ -551,8 +679,11 @@ static void hid_device_removal_callback(void *context, IOReturn result, void *sender) {+ (void) result;+ (void) sender;+ /* Stop the Run Loop for this device. */- hid_device *d = context;+ hid_device *d = (hid_device*) context; d->disconnected = 1; CFRunLoopStop(d->run_loop);@@ -565,12 +696,17 @@ IOHIDReportType report_type, uint32_t report_id, uint8_t *report, CFIndex report_length) {+ (void) result;+ (void) sender;+ (void) report_type;+ (void) report_id;+ struct input_report *rpt;- hid_device *dev = context;+ hid_device *dev = (hid_device*) context; /* Make a new Input Report object */- rpt = calloc(1, sizeof(struct input_report));- rpt->data = calloc(1, report_length);+ rpt = (struct input_report*) calloc(1, sizeof(struct input_report));+ rpt->data = (uint8_t*) calloc(1, report_length); memcpy(rpt->data, report, report_length); rpt->len = report_length; rpt->next = NULL;@@ -609,17 +745,17 @@ } -/* This gets called when the read_thred's run loop gets signaled by+/* 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 = context;+ hid_device *dev = (hid_device*) context; CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/ } static void *read_thread(void *param) {- hid_device *dev = param;+ hid_device *dev = (hid_device*) param; SInt32 code; /* Move the device's run loop to this thread. */@@ -667,7 +803,7 @@ /* 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 acutally will go to sleep before the condition is+ the condition actually will go to sleep before the condition is signaled. */ pthread_mutex_lock(&dev->mutex); pthread_cond_broadcast(&dev->condition);@@ -681,114 +817,142 @@ return NULL; } +/* hid_open_path()+ *+ * path must be a valid path to an IOHIDDevice in the IOService plane+ * Example: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"+ */ hid_device * HID_API_EXPORT hid_open_path(const char *path) {- int i; hid_device *dev = NULL;- CFIndex num_devices;-- dev = new_hid_device();+ io_registry_entry_t entry = MACH_PORT_NULL;+ IOReturn ret = kIOReturnInvalid; /* Set up the HID Manager if it hasn't been done */ if (hid_init() < 0)- return NULL;-- /* give the IOHIDManager a chance to update itself */- process_pending_events();+ goto return_error; - CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr);+ dev = new_hid_device(); - num_devices = CFSetGetCount(device_set);- IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef));- CFSetGetValues(device_set, (const void **) device_array);- for (i = 0; i < num_devices; i++) {- char cbuf[BUF_LEN];- size_t len;- IOHIDDeviceRef os_dev = device_array[i];+ /* Get the IORegistry entry for the given path */+ entry = IORegistryEntryFromPath(kIOMasterPortDefault, path);+ if (entry == MACH_PORT_NULL) {+ /* Path wasn't valid (maybe device was removed?) */+ goto return_error;+ } - len = make_path(os_dev, cbuf, sizeof(cbuf));- if (!strcmp(cbuf, path)) {- /* Matched Paths. Open this Device. */- IOReturn ret = IOHIDDeviceOpen(os_dev, kIOHIDOptionsTypeSeizeDevice);- if (ret == kIOReturnSuccess) {- char str[32];+ /* Create an IOHIDDevice for the entry */+ dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry);+ if (dev->device_handle == NULL) {+ /* Error creating the HID device */+ goto return_error;+ } - free(device_array);- CFRetain(os_dev);- CFRelease(device_set);- dev->device_handle = os_dev;+ /* Open the IOHIDDevice */+ ret = IOHIDDeviceOpen(dev->device_handle, kIOHIDOptionsTypeSeizeDevice);+ if (ret == kIOReturnSuccess) {+ char str[32]; - /* Create the buffers for receiving data */- dev->max_input_report_len = (CFIndex) get_max_report_length(os_dev);- dev->input_report_buf = calloc(dev->max_input_report_len, sizeof(uint8_t));+ /* Create the buffers for receiving data */+ dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle);+ dev->input_report_buf = (uint8_t*) calloc(dev->max_input_report_len, sizeof(uint8_t)); - /* Create the Run Loop Mode for this device.- printing the reference seems to work. */- sprintf(str, "HIDAPI_%p", os_dev);- dev->run_loop_mode =- CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);+ /* Create the Run Loop Mode for this device.+ printing the reference seems to work. */+ sprintf(str, "HIDAPI_%p", (void*) dev->device_handle);+ dev->run_loop_mode =+ CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); - /* Attach the device to a Run Loop */- IOHIDDeviceRegisterInputReportCallback(- os_dev, dev->input_report_buf, dev->max_input_report_len,- &hid_report_callback, dev);- IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev);+ /* 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);+ /* 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);+ /* Wait here for the read thread to be initialized. */+ pthread_barrier_wait(&dev->barrier); - return dev;- }- else {- goto return_error;- }- }+ IOObjectRelease(entry);+ return dev; }+ else {+ goto return_error;+ } return_error:- free(device_array);- CFRelease(device_set);+ 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;- size_t length_to_send;+ const unsigned char *data_to_send = data;+ CFIndex length_to_send = length; IOReturn res;-- /* Return if the device has been disconnected. */- if (dev->disconnected)- return -1;+ const unsigned char report_id = data[0]; - if (data[0] == 0x0) {+ if (report_id == 0x0) { /* Not using numbered Reports. Don't send the report number. */ data_to_send = data+1; length_to_send = length-1; }- else {- /* Using numbered Reports.- Send the Report Number */- data_to_send = data;- length_to_send = length;++ /* Avoid crash if the device has been unplugged. */+ if (dev->disconnected) {+ return -1; } - if (!dev->disconnected) {- res = IOHIDDeviceSetReport(dev->device_handle,- type,- data[0], /* Report ID*/- data_to_send, length_to_send);+ res = IOHIDDeviceSetReport(dev->device_handle,+ type,+ report_id,+ data_to_send, length_to_send); - if (res == kIOReturnSuccess) {- return length;+ if (res == kIOReturnSuccess) {+ return length;+ }++ return -1;+}++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];++ 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) {+ return -1;+ }++ res = IOHIDDeviceGetReport(dev->device_handle,+ type,+ report_id,+ report, &report_length);++ if (res == kIOReturnSuccess) {+ if (report_id == 0x0) { /* 0 report number still present at the beginning */+ report_length++; }- else- return -1;+ return report_length; } return -1;@@ -821,7 +985,7 @@ return res; /* A res of 0 means we may have been signaled or it may- be a spurious wakeup. Check to see that there's acutally+ be a spurious wakeup. Check to see that there's actually data in the queue before returning, and if not, go back to sleep. See the pthread_cond_timedwait() man page for details. */@@ -841,7 +1005,7 @@ return res; /* A res of 0 means we may have been signaled or it may- be a spurious wakeup. Check to see that there's acutally+ be a spurious wakeup. Check to see that there's actually data in the queue before returning, and if not, go back to sleep. See the pthread_cond_timedwait() man page for details. */@@ -948,31 +1112,23 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) {- CFIndex len = length;- IOReturn res;-- /* Return if the device has been unplugged. */- if (dev->disconnected)- return -1;-- res = IOHIDDeviceGetReport(dev->device_handle,- kIOHIDReportTypeFeature,- data[0], /* Report ID */- data, &len);- if (res == kIOReturnSuccess)- return len;- else- return -1;+ return get_report(dev, kIOHIDReportTypeFeature, data, length); } +int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)+{ + return get_report(dev, kIOHIDReportTypeInput, data, length);+} void HID_API_EXPORT hid_close(hid_device *dev) { if (!dev) return; - /* Disconnect the report callback before close. */- if (!dev->disconnected) {+ /* Disconnect the report callback before close.+ See comment below.+ */+ if (is_macos_10_10_or_greater || !dev->disconnected) { IOHIDDeviceRegisterInputReportCallback( dev->device_handle, dev->input_report_buf, dev->max_input_report_len, NULL, dev);@@ -996,8 +1152,14 @@ /* Close the OS handle to the device, but only if it's not been unplugged. If it's been unplugged, then calling- IOHIDDeviceClose() will crash. */- if (!dev->disconnected) {+ IOHIDDeviceClose() 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, kIOHIDOptionsTypeSeizeDevice); } @@ -1029,6 +1191,11 @@ 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;+ /* TODO: */ return 0;@@ -1037,9 +1204,10 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) {+ (void) dev; /* TODO: */ - return NULL;+ return L"hid_error is not implemented yet"; } @@ -1049,6 +1217,11 @@ #if 0+static int32_t get_location_id(IOHIDDeviceRef device)+{+ return get_int_property(device, CFSTR(kIOHIDLocationIDKey));+}+ static int32_t get_usage(IOHIDDeviceRef device) { int32_t res;
cbits/hidapi/windows/hid.c view
@@ -17,7 +17,7 @@ files located at the root of the source distribution. These files may also be found in the public source code repository located at:- http://github.com/signal11/hidapi .+ https://github.com/libusb/hidapi . ********************************************************/ #include <windows.h>@@ -36,6 +36,10 @@ #define _wcsdup wcsdup #endif +/* The maximum number of characters that can be passed into the+ HidD_Get*String() functions without it failing.*/+#define MAX_STRING_WCHARS 0xFFF+ /*#define HIDAPI_USE_DDK*/ #ifdef __cplusplus@@ -51,6 +55,7 @@ #define HID_OUT_CTL_CODE(id) \ CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100)+ #define IOCTL_HID_GET_INPUT_REPORT HID_OUT_CTL_CODE(104) #ifdef __cplusplus } /* extern "C" */@@ -62,6 +67,9 @@ #include "hidapi.h" +#undef MIN+#define MIN(x,y) ((x) < (y)? (x): (y))+ #ifdef _MSC_VER /* Thanks Microsoft, but I know how to use strncpy(). */ #pragma warning(disable:4996)@@ -71,6 +79,12 @@ extern "C" { #endif +static struct hid_api_version api_version = {+ .major = HID_API_VERSION_MAJOR,+ .minor = HID_API_VERSION_MINOR,+ .patch = HID_API_VERSION_PATCH+};+ #ifndef HIDAPI_USE_DDK /* Since we're not building with the DDK, and the HID header files aren't part of the SDK, we have to define all this@@ -102,6 +116,7 @@ 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);@@ -114,6 +129,7 @@ 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;@@ -129,11 +145,14 @@ BOOL blocking; USHORT output_report_length; size_t input_report_length;+ USHORT feature_report_length;+ unsigned char *feature_buf; void *last_error_str; DWORD last_error_num; BOOL read_pending; char *read_buf; OVERLAPPED ol;+ OVERLAPPED write_ol; }; static hid_device *new_hid_device()@@ -143,12 +162,16 @@ dev->blocking = TRUE; dev->output_report_length = 0; dev->input_report_length = 0;+ dev->feature_report_length = 0;+ dev->feature_buf = NULL; dev->last_error_str = NULL; dev->last_error_num = 0; dev->read_pending = FALSE; dev->read_buf = NULL; memset(&dev->ol, 0, sizeof(dev->ol));- dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL);+ dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL);+ memset(&dev->write_ol, 0, sizeof(dev->write_ol));+ dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL); return dev; }@@ -156,23 +179,25 @@ static void free_hid_device(hid_device *dev) { CloseHandle(dev->ol.hEvent);+ CloseHandle(dev->write_ol.hEvent); CloseHandle(dev->device_handle); LocalFree(dev->last_error_str);+ free(dev->feature_buf); free(dev->read_buf); free(dev); } -static void register_error(hid_device *device, const char *op)+static void register_error(hid_device *dev, const char *op) { WCHAR *ptr, *msg;-+ (void)op; // unreferenced param FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),- (LPVOID)&msg, 0/*sz*/,+ (LPWSTR)&msg, 0/*sz*/, NULL); /* Get rid of the CR and LF that FormatMessage() sticks at the@@ -188,8 +213,8 @@ /* Store the message off in the Device entry so that the hid_error() function can pick it up. */- LocalFree(device->last_error_str);- device->last_error_str = msg;+ LocalFree(dev->last_error_str);+ dev->last_error_str = msg; } #ifndef HIDAPI_USE_DDK@@ -197,6 +222,10 @@ { lib_handle = LoadLibraryA("hid.dll"); if (lib_handle) {+#if defined(__GNUC__)+# pragma GCC diagnostic push+# pragma GCC diagnostic ignored "-Wcast-function-type"+#endif #define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1; RESOLVE(HidD_GetAttributes); RESOLVE(HidD_GetSerialNumberString);@@ -204,12 +233,16 @@ RESOLVE(HidD_GetProductString); RESOLVE(HidD_SetFeature); RESOLVE(HidD_GetFeature);+ RESOLVE(HidD_GetInputReport); RESOLVE(HidD_GetIndexedString); RESOLVE(HidD_GetPreparsedData); RESOLVE(HidD_FreePreparsedData); RESOLVE(HidP_GetCaps); RESOLVE(HidD_SetNumInputBuffers); #undef RESOLVE+#if defined(__GNUC__)+# pragma GCC diagnostic pop+#endif } else return -1;@@ -218,13 +251,11 @@ } #endif -static HANDLE open_device(const char *path, BOOL enumerate)+static HANDLE open_device(const char *path, BOOL open_rw) { HANDLE handle;- DWORD desired_access = (enumerate)? 0: (GENERIC_WRITE | GENERIC_READ);- DWORD share_mode = (enumerate)?- FILE_SHARE_READ|FILE_SHARE_WRITE:- FILE_SHARE_READ;+ DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0;+ DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE; handle = CreateFileA(path, desired_access,@@ -237,6 +268,16 @@ return handle; } +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version()+{+ return &api_version;+}++HID_API_EXPORT const char* HID_API_CALL hid_version_str()+{+ return HID_API_VERSION_STR;+}+ int HID_API_EXPORT hid_init(void) { #ifndef HIDAPI_USE_DDK@@ -268,14 +309,16 @@ struct hid_device_info *root = NULL; /* return object */ struct hid_device_info *cur_dev = NULL; - /* Windows objects for interacting with the driver. */+ /* Hard-coded GUID retreived by HidD_GetHidGuid */ GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} };++ /* Windows objects for interacting with the driver. */ SP_DEVINFO_DATA devinfo_data; SP_DEVICE_INTERFACE_DATA device_interface_data; SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL; HDEVINFO device_info_set = INVALID_HANDLE_VALUE;+ char driver_name[256]; int device_index = 0;- int i; if (hid_init() < 0) return NULL;@@ -337,35 +380,23 @@ goto cont; } - /* Make sure this device is of Setup Class "HIDClass" and has a- driver bound to it. */- for (i = 0; ; i++) {- char driver_name[256];+ /* Populate devinfo_data. This function will return failure+ when the device with such index doesn't exist. We've already checked it does. */+ res = SetupDiEnumDeviceInfo(device_info_set, device_index, &devinfo_data);+ if (!res)+ goto cont; - /* Populate devinfo_data. This function will return failure- when there are no more interfaces left. */- res = SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data);- if (!res)- goto cont; - res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,- SPDRP_CLASS, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);- if (!res)- goto cont;-- if (strcmp(driver_name, "HIDClass") == 0) {- /* See if there's a driver bound. */- res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,- SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);- if (res)- break;- }- }+ /* Make sure this device has a driver bound to it. */+ res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,+ SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);+ if (!res)+ goto cont; //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath); /* Open a handle to the device */- write_handle = open_device(device_interface_detail_data->DevicePath, TRUE);+ write_handle = open_device(device_interface_detail_data->DevicePath, FALSE); /* Check validity of write_handle. */ if (write_handle == INVALID_HANDLE_VALUE) {@@ -390,7 +421,6 @@ struct hid_device_info *tmp; PHIDP_PREPARSED_DATA pp_data = NULL; HIDP_CAPS caps;- BOOLEAN res; NTSTATUS nt_res; wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */ size_t len;@@ -430,6 +460,7 @@ cur_dev->path = NULL; /* Serial Number */+ wstr[0]= 0x0000; res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr)); wstr[WSTR_LEN-1] = 0x0000; if (res) {@@ -437,6 +468,7 @@ } /* Manufacturer String */+ wstr[0]= 0x0000; res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr)); wstr[WSTR_LEN-1] = 0x0000; if (res) {@@ -444,6 +476,7 @@ } /* Product String */+ wstr[0]= 0x0000; res = HidD_GetProductString(write_handle, wstr, sizeof(wstr)); wstr[WSTR_LEN-1] = 0x0000; if (res) {@@ -523,7 +556,7 @@ if (cur_dev->vendor_id == vendor_id && cur_dev->product_id == product_id) { if (serial_number) {- if (wcscmp(serial_number, cur_dev->serial_number) == 0) {+ if (cur_dev->serial_number && wcscmp(serial_number, cur_dev->serial_number) == 0) { path_to_open = cur_dev->path; break; }@@ -561,13 +594,23 @@ dev = new_hid_device(); /* Open a handle to the device */- dev->device_handle = open_device(path, FALSE);+ dev->device_handle = open_device(path, TRUE); /* Check validity of write_handle. */ if (dev->device_handle == INVALID_HANDLE_VALUE) {- /* Unable to open the device. */- register_error(dev, "CreateFile");- goto err;+ /* System devices, such as keyboards and mice, cannot be opened in+ read-write mode, because the system takes exclusive control over+ them. This is to prevent keyloggers. However, feature reports+ can still be sent and received. Retry opening the device, but+ without read/write access. */+ dev->device_handle = open_device(path, FALSE);++ /* Check the validity of the limited device_handle. */+ if (dev->device_handle == INVALID_HANDLE_VALUE) {+ /* Unable to open the device, even without read-write mode. */+ register_error(dev, "CreateFile");+ goto err;+ } } /* Set the Input Report buffer size to 64 reports. */@@ -590,6 +633,7 @@ } dev->output_report_length = caps.OutputReportByteLength; dev->input_report_length = caps.InputReportByteLength;+ dev->feature_report_length = caps.FeatureReportByteLength; HidD_FreePreparsedData(pp_data); dev->read_buf = (char*) malloc(dev->input_report_length);@@ -605,12 +649,12 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length) {- DWORD bytes_written;+ DWORD bytes_written = 0;+ int function_result = -1; BOOL res;+ BOOL overlapped = FALSE; - OVERLAPPED ol; unsigned char *buf;- memset(&ol, 0, sizeof(ol)); /* Make sure the right number of bytes are passed to WriteFile. Windows expects the number of bytes which are in the _longest_ report (plus@@ -630,32 +674,44 @@ length = dev->output_report_length; } - res = WriteFile(dev->device_handle, buf, length, NULL, &ol);+ res = WriteFile(dev->device_handle, buf, (DWORD) length, NULL, &dev->write_ol); if (!res) { if (GetLastError() != ERROR_IO_PENDING) { /* WriteFile() failed. Return error. */ register_error(dev, "WriteFile");- bytes_written = -1; goto end_of_function; }+ overlapped = TRUE; } - /* Wait here until the write is done. This makes- hid_write() synchronous. */- res = GetOverlappedResult(dev->device_handle, &ol, &bytes_written, TRUE/*wait*/);- if (!res) {- /* The Write operation failed. */- register_error(dev, "WriteFile");- bytes_written = -1;- goto end_of_function;+ if (overlapped) {+ /* Wait for the transaction to complete. This makes+ hid_write() synchronous. */+ res = WaitForSingleObject(dev->write_ol.hEvent, 1000);+ if (res != WAIT_OBJECT_0) {+ /* There was a Timeout. */+ register_error(dev, "WriteFile/WaitForSingleObject Timeout");+ goto end_of_function;+ }++ /* Get the result. */+ res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*wait*/);+ if (res) {+ function_result = bytes_written;+ }+ else {+ /* The Write operation failed. */+ register_error(dev, "WriteFile");+ goto end_of_function;+ } } end_of_function: if (buf != data) free(buf); - return bytes_written;+ return function_result; } @@ -663,7 +719,8 @@ { DWORD bytes_read = 0; size_t copy_len = 0;- BOOL res;+ BOOL res = FALSE;+ BOOL overlapped = FALSE; /* Copy the handle for convenience. */ HANDLE ev = dev->ol.hEvent;@@ -673,7 +730,7 @@ dev->read_pending = TRUE; memset(dev->read_buf, 0, dev->input_report_length); ResetEvent(ev);- res = ReadFile(dev->device_handle, dev->read_buf, dev->input_report_length, &bytes_read, &dev->ol);+ res = ReadFile(dev->device_handle, dev->read_buf, (DWORD) dev->input_report_length, &bytes_read, &dev->ol); if (!res) { if (GetLastError() != ERROR_IO_PENDING) {@@ -683,24 +740,29 @@ dev->read_pending = FALSE; goto end_of_function; }- }+ overlapped = TRUE; + } }+ else {+ overlapped = TRUE; + } - 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;+ 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*/);- + /* 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; @@ -727,7 +789,7 @@ return -1; } - return copy_len;+ return (int) copy_len; } int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length)@@ -743,13 +805,35 @@ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) {- BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, length);+ BOOL res = FALSE;+ unsigned char *buf;+ size_t length_to_send;++ /* Windows expects at least caps.FeatureReportByteLength bytes passed+ to HidD_SetFeature(), even if the report is shorter. Any less sent and+ the function fails with error ERROR_INVALID_PARAMETER set. Any more+ and HidD_SetFeature() silently truncates the data sent in the report+ to caps.FeatureReportByteLength. */+ if (length >= dev->feature_report_length) {+ buf = (unsigned char *) data;+ length_to_send = length;+ } else {+ if (dev->feature_buf == NULL)+ dev->feature_buf = (unsigned char *) malloc(dev->feature_report_length);+ buf = dev->feature_buf;+ memcpy(buf, data, length);+ memset(buf + length, 0, dev->feature_report_length - length);+ length_to_send = dev->feature_report_length;+ }++ res = HidD_SetFeature(dev->device_handle, (PVOID)buf, (DWORD) length_to_send);+ if (!res) { register_error(dev, "HidD_SetFeature"); return -1; } - return length;+ return (int) length; } @@ -771,8 +855,8 @@ res = DeviceIoControl(dev->device_handle, IOCTL_HID_GET_FEATURE,- data, length,- data, length,+ data, (DWORD) length,+ data, (DWORD) length, &bytes_returned, &ol); if (!res) {@@ -801,6 +885,55 @@ #endif } ++int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)+{+ BOOL res;+#if 0+ res = HidD_GetInputReport(dev->device_handle, data, length);+ if (!res) {+ register_error(dev, "HidD_GetInputReport");+ return -1;+ }+ return length;+#else+ DWORD bytes_returned;++ OVERLAPPED ol;+ memset(&ol, 0, sizeof(ol));++ res = DeviceIoControl(dev->device_handle,+ IOCTL_HID_GET_INPUT_REPORT,+ data, (DWORD) length,+ data, (DWORD) length,+ &bytes_returned, &ol);++ if (!res) {+ if (GetLastError() != ERROR_IO_PENDING) {+ /* DeviceIoControl() failed. Return error. */+ register_error(dev, "Send Input Report DeviceIoControl");+ return -1;+ }+ }++ /* Wait here until the write is done. This makes+ hid_get_feature_report() synchronous. */+ res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/);+ if (!res) {+ /* The operation failed. */+ register_error(dev, "Send Input Report GetOverLappedResult");+ return -1;+ }++ /* bytes_returned does not include the first byte which contains the+ report ID. The data buffer actually contains one more byte than+ bytes_returned. */+ bytes_returned++;++ return bytes_returned;+#endif+}+ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) { if (!dev)@@ -813,7 +946,7 @@ { BOOL res; - res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * maxlen);+ res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); if (!res) { register_error(dev, "HidD_GetManufacturerString"); return -1;@@ -826,7 +959,7 @@ { BOOL res; - res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * maxlen);+ res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); if (!res) { register_error(dev, "HidD_GetProductString"); return -1;@@ -839,7 +972,7 @@ { BOOL res; - res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * maxlen);+ res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); if (!res) { register_error(dev, "HidD_GetSerialNumberString"); return -1;@@ -852,7 +985,7 @@ { BOOL res; - res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * maxlen);+ res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); if (!res) { register_error(dev, "HidD_GetIndexedString"); return -1;@@ -864,7 +997,14 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) {- return (wchar_t*)dev->last_error_str;+ if (dev) {+ if (dev->last_error_str == NULL)+ return L"Success";+ return (wchar_t*)dev->last_error_str;+ }++ // Global error messages are not (yet) implemented on Windows.+ return L"hid_error for global errors is not implemented yet"; }
hidapi.cabal view
@@ -1,27 +1,28 @@ name: hidapi-version: 0.1.5+version: 0.1.6 build-type: Simple-cabal-version: >= 1.6+cabal-version: >= 1.10 category: Hardware author: Patrick Chilton <chpatrick@gmail.com>, Niklas Hambüchen <mail@nh2.me> maintainer: Patrick Chilton <chpatrick@gmail.com>-homepage: https://github.com/vahokif/haskell-hidapi-bug-reports: https://github.com/vahokif/haskell-hidapi/issues+homepage: https://github.com/chpatrick/haskell-hidapi+bug-reports: https://github.com/chpatrick/haskell-hidapi/issues synopsis: Haskell bindings to HIDAPI-description: Haskell bindings to the HIDAPI library (<http://www.signal11.us/oss/hidapi/>).+description: Haskell bindings to the HIDAPI library (<https://github.com/libusb/hidapi>). . Note you need need to have the corresponding low-level library installed for your OS, e.g. libudev-dev on Debian/Ubuntu, or just udev on distributions that don't split dev libraries. license: MIT license-file: LICENSE+extra-source-files: README.md extra-source-files: cbits/hidapi/hidapi.h source-repository head type: git- location: git://github.com/vahokif/haskell-hidapi.git+ location: git://github.com/chpatrick/haskell-hidapi.git library exposed-modules: System.HIDAPI@@ -50,3 +51,4 @@ else c-sources: cbits/hidapi/linux/hid.c extra-libraries: udev+ default-language: Haskell2010