diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index cbc3107dd..c3c2502de 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -423,6 +423,44 @@ extern "C" { */ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); + /** @brief Upper bound for hid_set_input_report_buffer_size(). + + Values passed above this limit are rejected by + hid_set_input_report_buffer_size(). Guards against + memory-exhaustion via unbounded input report queue growth. + */ + #define HID_API_MAX_INPUT_REPORT_BUFFER_SIZE 1024 + + /** @brief Set the size of the input report buffer/queue. + + Some HID devices emit input reports in bursts at rates + that exceed the default internal queue capacity, causing + silent report drops on macOS and the libusb Linux backend. + This function allows callers to resize the per-device + input report buffer. + + Defaults per backend: + - macOS: 30 reports + - Linux libusb: 30 reports + - Windows: 64 reports (via HidD_SetNumInputBuffers) + - Linux hidraw: kernel-managed, no userspace queue + - NetBSD: kernel-managed, no userspace queue + + Call after hid_open() and before the first hid_read() + to avoid losing reports buffered at open time. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param buffer_size The desired buffer size in reports. + Must be in range [1, HID_API_MAX_INPUT_REPORT_BUFFER_SIZE]. + + @returns + 0 on success, -1 on failure (invalid parameters or + backend-specific error). Call hid_error(dev) for + details where supported. + */ + int HID_API_EXPORT HID_API_CALL hid_set_input_report_buffer_size(hid_device *dev, int buffer_size); + /** @brief Send a Feature report to the device. Feature reports are sent over the Control endpoint as a diff --git a/libusb/hid.c b/libusb/hid.c index d2ceef5d3..b42f558e1 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -110,6 +110,9 @@ struct hid_device_ { /* Whether blocking reads are used */ int blocking; /* boolean */ + /* Maximum number of input reports to queue before dropping oldest. */ + int input_report_buffer_size; + /* Read thread objects */ hidapi_thread_state thread_state; int shutdown_thread; @@ -143,6 +146,7 @@ static hid_device *new_hid_device(void) return NULL; dev->blocking = 1; + dev->input_report_buffer_size = 30; hidapi_thread_state_init(&dev->thread_state); @@ -985,7 +989,7 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer) /* Pop one off if we've reached 30 in the queue. This way we don't grow forever if the user never reads anything from the device. */ - if (num_queued > 30) { + if (num_queued > dev->input_report_buffer_size) { return_data(dev, NULL, 0); } } @@ -1574,6 +1578,21 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) } + +int HID_API_EXPORT hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) +{ + /* Note: libusb backend currently has no error reporting infrastructure + (hid_error returns a fixed string). This function returns -1 on + invalid arguments but cannot provide a descriptive error message + until the backend gains error registration. */ + if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) + return -1; + hidapi_thread_mutex_lock(&dev->thread_state); + dev->input_report_buffer_size = buffer_size; + hidapi_thread_mutex_unlock(&dev->thread_state); + return 0; +} + int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) { dev->blocking = !nonblock; diff --git a/linux/hid.c b/linux/hid.c index a4dc26f4e..549247bec 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -1199,6 +1199,21 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) return dev->last_read_error_str; } + +int HID_API_EXPORT hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) +{ + if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { + register_error_str(&dev->last_error_str, "buffer_size out of range"); + return -1; + } + /* No-op on Linux hidraw and BSD backends: the kernel manages input + report buffering and there is no userspace queue to resize. The + call is accepted (returns 0) to preserve a consistent cross-platform + API so callers do not need per-backend conditional code. */ + (void)buffer_size; + return 0; +} + int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) { /* Do all non-blocking in userspace using poll(), since it looks diff --git a/mac/hid.c b/mac/hid.c index a91bc1902..7002a7a3c 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -127,6 +127,7 @@ struct hid_device_ { IOOptionBits open_options; int blocking; int disconnected; + int input_report_buffer_size; CFStringRef run_loop_mode; CFRunLoopRef run_loop; CFRunLoopSourceRef source; @@ -156,6 +157,7 @@ static hid_device *new_hid_device(void) dev->open_options = device_open_options; dev->blocking = 1; dev->disconnected = 0; + dev->input_report_buffer_size = 30; dev->run_loop_mode = NULL; dev->run_loop = NULL; dev->source = NULL; @@ -908,7 +910,7 @@ static void hid_report_callback(void *context, IOReturn result, void *sender, /* Pop one off if we've reached 30 in the queue. This way we don't grow forever if the user never reads anything from the device. */ - if (num_queued > 30) { + if (num_queued > dev->input_report_buffer_size) { return_data(dev, NULL, 0); } } @@ -1347,6 +1349,19 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) return dev->last_read_error_str; } + +int HID_API_EXPORT hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) +{ + if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { + register_error_str(&dev->last_error_str, "buffer_size out of range"); + return -1; + } + pthread_mutex_lock(&dev->mutex); + dev->input_report_buffer_size = buffer_size; + pthread_mutex_unlock(&dev->mutex); + return 0; +} + int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) { /* All Nonblocking operation is handled by the library. */ diff --git a/netbsd/hid.c b/netbsd/hid.c index a9fca67c5..b970e724c 100644 --- a/netbsd/hid.c +++ b/netbsd/hid.c @@ -955,6 +955,21 @@ HID_API_EXPORT const wchar_t* HID_API_CALL hid_read_error(hid_device *dev) return dev->last_read_error_str; } + +int HID_API_EXPORT HID_API_CALL hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) +{ + if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { + register_error_str(&dev->last_error_str, "buffer_size out of range"); + return -1; + } + /* No-op on Linux hidraw and BSD backends: the kernel manages input + report buffering and there is no userspace queue to resize. The + call is accepted (returns 0) to preserve a consistent cross-platform + API so callers do not need per-backend conditional code. */ + (void)buffer_size; + return 0; +} + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) { dev->blocking = !nonblock; diff --git a/windows/hid.c b/windows/hid.c index 1e27f10a4..c96e2a30a 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1257,6 +1257,20 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) return dev->last_read_error_str; } + +int HID_API_EXPORT HID_API_CALL hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) +{ + if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { + register_string_error(dev, L"buffer_size out of range"); + return -1; + } + if (!HidD_SetNumInputBuffers(dev->device_handle, (ULONG)buffer_size)) { + register_winapi_error(dev, L"HidD_SetNumInputBuffers"); + return -1; + } + return 0; +} + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) { dev->blocking = !nonblock;