packages feed

hidapi 0.1.5 → 0.1.6

raw patch · 7 files changed

+1266/−498 lines, 7 files

Files

+ README.md view
@@ -0,0 +1,12 @@+## hidapi [![Build Status](https://travis-ci.org/chpatrick/haskell-hidapi.svg?branch=master)](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