diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index 744ceb0b0..06656442a 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -307,9 +307,9 @@ extern "C" { single report), followed by the report data (16 bytes). In this example, the length passed in would be 17. - hid_write() will send the data on the first OUT endpoint, if - one exists. If it does not, it will send the data through - the Control Endpoint (Endpoint 0). + hid_write() will send the data on the first interrupt OUT + endpoint, if one exists. If it does not the behaviour is as + @ref hid_send_output_report @ingroup API @param dev A device handle returned from hid_open(). @@ -445,6 +445,40 @@ extern "C" { */ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length); + /** @brief Send a Output report to the device. + + Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) + + Output reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_output_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_output_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + + This function sets the return value of hid_error(). + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + + @returns + This function returns the actual number of bytes written and + -1 on error. + + @see @ref hid_write + */ + int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device* dev, const unsigned char* data, size_t length); + /** @brief Get a input report from a HID device. Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0) diff --git a/libusb/hid.c b/libusb/hid.c index 2a5f8a476..ff3d60021 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -1409,6 +1409,11 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t int report_number; int skipped_report_id = 0; + if (dev->output_endpoint <= 0) { + /* No interrupt out endpoint. Use the Control Endpoint */ + return hid_send_output_report(dev, data, length); + } + if (!data || (length ==0)) { return -1; } @@ -1421,42 +1426,21 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t skipped_report_id = 1; } + /* Use the interrupt out endpoint */ + int actual_length; + res = libusb_interrupt_transfer(dev->device_handle, + dev->output_endpoint, + (unsigned char*)data, + length, + &actual_length, 1000); - if (dev->output_endpoint <= 0) { - /* No interrupt out endpoint. Use the Control Endpoint */ - res = libusb_control_transfer(dev->device_handle, - LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, - 0x09/*HID Set_Report*/, - (2/*HID output*/ << 8) | report_number, - dev->interface, - (unsigned char *)data, length, - 1000/*timeout millis*/); - - if (res < 0) - return -1; - - if (skipped_report_id) - length++; - - return length; - } - else { - /* Use the interrupt out endpoint */ - int actual_length; - res = libusb_interrupt_transfer(dev->device_handle, - dev->output_endpoint, - (unsigned char*)data, - length, - &actual_length, 1000); - - if (res < 0) - return -1; + if (res < 0) + return -1; - if (skipped_report_id) - actual_length++; + if (skipped_report_id) + actual_length++; - return actual_length; - } + return actual_length; } /* Helper function, to simplify hid_read(). @@ -1638,6 +1622,36 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, return res; } +int HID_API_EXPORT hid_send_output_report(hid_device *dev, const unsigned char *data, size_t length) +{ + int res = -1; + int skipped_report_id = 0; + int report_number = data[0]; + + if (report_number == 0x0) { + data++; + length--; + skipped_report_id = 1; + } + + res = libusb_control_transfer(dev->device_handle, + LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, + 0x09/*HID set_report*/, + (2/*HID output*/ << 8) | report_number, + dev->interface, + (unsigned char *)data, length, + 1000/*timeout millis*/); + + if (res < 0) + return -1; + + /* Account for the report ID */ + if (skipped_report_id) + length++; + + return length; +} + int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) { int res = -1; diff --git a/linux/hid.c b/linux/hid.c index afe12cdde..228fdeeca 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -61,12 +61,15 @@ #endif -// HIDIOCGINPUT is not defined in Linux kernel headers < 5.11. -// This definition is from hidraw.h in Linux >= 5.11. +// HIDIOCGINPUT and HIDIOCSOUTPUT are not defined in Linux kernel headers < 5.11. +// These definitions are from hidraw.h in Linux >= 5.11. // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f43d3870cafa2a0f3854c1819c8385733db8f9ae #ifndef HIDIOCGINPUT #define HIDIOCGINPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len) #endif +#ifndef HIDIOCSOUTPUT +#define HIDIOCSOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0B, len) +#endif struct hid_device_ { int device_handle; @@ -1196,6 +1199,19 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, return res; } +int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device *dev, const unsigned char *data, size_t length) +{ + int res; + + register_device_error(dev, NULL); + + res = ioctl(dev->device_handle, HIDIOCSOUTPUT(length), data); + if (res < 0) + register_device_error_format(dev, "ioctl (SOUTPUT): %s", strerror(errno)); + + return res; +} + int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) { int res; diff --git a/mac/hid.c b/mac/hid.c index 50606d9d8..1e5e17a08 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -1341,6 +1341,11 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, return get_report(dev, kIOHIDReportTypeFeature, data, length); } +int HID_API_EXPORT hid_send_output_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, kIOHIDReportTypeOutput, data, length); +} + int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) { return get_report(dev, kIOHIDReportTypeInput, data, length); diff --git a/netbsd/hid.c b/netbsd/hid.c index a9b841f81..9aed28f38 100644 --- a/netbsd/hid.c +++ b/netbsd/hid.c @@ -934,6 +934,11 @@ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned return get_report(dev, data, length, UHID_FEATURE_REPORT); } +int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, data, length, UHID_OUTPUT_REPORT); +} + int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) { return get_report(dev, data, length, UHID_INPUT_REPORT); diff --git a/windows/hid.c b/windows/hid.c index a359656bc..35c2de04e 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -97,6 +97,7 @@ static HidD_GetManufacturerString_ HidD_GetManufacturerString; static HidD_GetProductString_ HidD_GetProductString; static HidD_SetFeature_ HidD_SetFeature; static HidD_GetFeature_ HidD_GetFeature; +static HidD_SetOutputReport_ HidD_SetOutputReport; static HidD_GetInputReport_ HidD_GetInputReport; static HidD_GetIndexedString_ HidD_GetIndexedString; static HidD_GetPreparsedData_ HidD_GetPreparsedData; @@ -150,6 +151,7 @@ static int lookup_functions() RESOLVE(hid_lib_handle, HidD_GetProductString); RESOLVE(hid_lib_handle, HidD_SetFeature); RESOLVE(hid_lib_handle, HidD_GetFeature); + RESOLVE(hid_lib_handle, HidD_SetOutputReport); RESOLVE(hid_lib_handle, HidD_GetInputReport); RESOLVE(hid_lib_handle, HidD_GetIndexedString); RESOLVE(hid_lib_handle, HidD_GetPreparsedData); @@ -1313,6 +1315,45 @@ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned return hid_get_report(dev, IOCTL_HID_GET_FEATURE, data, length); } +int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device* dev, const unsigned char* data, size_t length) +{ + BOOL res = FALSE; + unsigned char *buf; + size_t length_to_send; + + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + register_string_error(dev, NULL); + + /* Windows expects at least caps.OutputeportByteLength bytes passed + to HidD_SetOutputReport(), even if the report is shorter. Any less sent and + the function fails with error ERROR_INVALID_PARAMETER set. Any more + and HidD_SetOutputReport() silently truncates the data sent in the report + to caps.OutputReportByteLength. */ + if (length >= dev->output_report_length) { + buf = (unsigned char *) data; + length_to_send = length; + } else { + if (dev->write_buf == NULL) + dev->write_buf = (unsigned char *) malloc(dev->output_report_length); + buf = dev->write_buf; + memcpy(buf, data, length); + memset(buf + length, 0, dev->output_report_length - length); + length_to_send = dev->output_report_length; + } + + res = HidD_SetOutputReport(dev->device_handle, (PVOID)buf, (DWORD) length_to_send); + if (!res) { + register_string_error(dev, L"HidD_SetOutputReport"); + return -1; + } + + return (int) length; +} + int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) { /* We could use HidD_GetInputReport() instead, but it doesn't give us an actual length, unfortunately */ diff --git a/windows/hidapi_hidsdi.h b/windows/hidapi_hidsdi.h index 4da6b91d8..ffed5b2f8 100644 --- a/windows/hidapi_hidsdi.h +++ b/windows/hidapi_hidsdi.h @@ -47,6 +47,7 @@ typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID bu typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length); typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length); +typedef BOOLEAN (__stdcall* HidD_SetOutputReport_)(HANDLE handle, PVOID data, ULONG length); typedef BOOLEAN (__stdcall *HidD_GetInputReport_)(HANDLE handle, PVOID data, ULONG length); typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len); typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data);