Skip to content

Commit

Permalink
Add hid_send_output_report() (#677)
Browse files Browse the repository at this point in the history
`hid_send_output_report` sends report over control pipe, unlike existing `hid_write` which would send the data on the first interrupt OUT endpoint, if one exists and only falls back to control pipe.
  • Loading branch information
mbcinergy authored Jun 12, 2024
1 parent 6c2de30 commit 4578ea2
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 38 deletions.
40 changes: 37 additions & 3 deletions hidapi/hidapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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().
Expand Down Expand Up @@ -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)
Expand Down
80 changes: 47 additions & 33 deletions libusb/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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().
Expand Down Expand Up @@ -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;
Expand Down
20 changes: 18 additions & 2 deletions linux/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions mac/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions netbsd/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
41 changes: 41 additions & 0 deletions windows/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 */
Expand Down
1 change: 1 addition & 0 deletions windows/hidapi_hidsdi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 4578ea2

Please sign in to comment.