Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

set_output_report (2nd version) #162

Merged
merged 4 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ extern "C" {
data: *mut c_uchar,
length: size_t,
) -> c_int;
pub fn hid_send_output_report(
device: *mut HidDevice,
data: *const c_uchar,
length: size_t,
) -> c_int;
pub fn hid_close(device: *mut HidDevice);
pub fn hid_get_manufacturer_string(
device: *mut HidDevice,
Expand Down
18 changes: 18 additions & 0 deletions src/hidapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,24 @@ impl HidDeviceBackendBase for HidDevice {
self.check_size(res)
}

fn send_output_report(&self, data: &[u8]) -> HidResult<()> {
if data.is_empty() {
return Err(HidError::InvalidZeroSizeData);
}
let res = unsafe {
ffi::hid_send_output_report(self._hid_device, data.as_ptr(), data.len() as size_t)
};
let res = self.check_size(res)?;
if res != data.len() {
Err(HidError::IncompleteSendError {
sent: res,
all: data.len(),
})
} else {
Ok(())
}
}

fn set_blocking_mode(&self, blocking: bool) -> HidResult<()> {
let res = unsafe {
ffi::hid_set_nonblocking(self._hid_device, if blocking { 0i32 } else { 1i32 })
Expand Down
18 changes: 18 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ trait HidDeviceBackendBase {
fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> HidResult<usize>;
fn send_feature_report(&self, data: &[u8]) -> HidResult<()>;
fn get_feature_report(&self, buf: &mut [u8]) -> HidResult<usize>;
fn send_output_report(&self, data: &[u8]) -> HidResult<()>;
fn set_blocking_mode(&self, blocking: bool) -> HidResult<()>;
fn get_device_info(&self) -> HidResult<DeviceInfo>;
fn get_manufacturer_string(&self) -> HidResult<Option<String>>;
Expand Down Expand Up @@ -594,6 +595,23 @@ impl HidDevice {
self.inner.get_feature_report(buf)
}

// Send a Output report to the device.
//
// Output reports are sent over the Control endpoint as a Set_Report
// transfer. The first byte of 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.
pub fn send_output_report(&self, data: &[u8]) -> HidResult<()> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to:

#[cfg(hidapi)]
pub fn send_output_report(&self, data: &[u8]) -> HidResult<()> {

Copy link
Contributor Author

@marek-g marek-g Dec 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for that, I didn't realize at first that you support also windows-native and linux-native.

Maybe I will wait with adding the #[cfg(hidapi)]. You already provided linux-native implementation and I can try to write windows-native, too. I have such device (Redragon k585 keyboard uses send_output_report to control RGB leds) and I will be able to test it.

But please be patient, because testing it on Windows will take me more time and I'm also very short of it, too :/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like there is not actually a release of https://github.com/libusb/hidapi yet with the feature. Also, your PR only makes the new set_output_report work with the hidapi backend.

I only don't know what to do with that.

Will you be ok to use unreleased version like in my PR?

Or do you see any other option? Like adding new feature (hidapi_experimental?). But I don't know what's the best way to conditionally use different commit for git submodule for that feature (additional git checkout in build.rs or having two git submodules - hidapi & hidapi_experimental?)....

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am totally ok with updating the module on the git master branch of my repo. Not yet sure if I want to make a crates.io release yet, but I probably will. Anyways, its not a problem for this pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx! So, I will add linux-native and windows-native implementations for completeness.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

self.inner.send_output_report(data)
}

/// Set the device handle to be in blocking or in non-blocking mode. In
/// non-blocking mode calls to `read()` will return immediately with an empty
/// slice if there is no data to be read. In blocking mode, `read()` will
Expand Down
24 changes: 23 additions & 1 deletion src/linux_native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ use nix::{
};

use super::{BusType, DeviceInfo, HidDeviceBackendBase, HidError, HidResult, WcharString};
use ioctl::{hidraw_ioc_get_feature, hidraw_ioc_grdescsize, hidraw_ioc_set_feature};
use ioctl::{
hidraw_ioc_get_feature, hidraw_ioc_grdescsize, hidraw_ioc_set_feature, hidraw_ioc_set_output,
};

// Bus values from linux/input.h
const BUS_USB: u16 = 0x03;
Expand Down Expand Up @@ -556,6 +558,26 @@ impl HidDeviceBackendBase for HidDevice {
Ok(res)
}

fn send_output_report(&self, buf: &[u8]) -> HidResult<()> {
let res = match unsafe { hidraw_ioc_set_output(self.fd.as_raw_fd(), buf) } {
Ok(n) => n,
Err(e) => {
return Err(HidError::HidApiError {
message: format!("ioctl (SOUTPUT): {e}"),
});
}
};

if res as usize != buf.len() {
return Err(HidError::IncompleteSendError {
sent: res as usize,
all: buf.len(),
});
}

Ok(())
}

fn set_blocking_mode(&self, blocking: bool) -> HidResult<()> {
self.blocking.set(blocking);
Ok(())
Expand Down
7 changes: 7 additions & 0 deletions src/linux_native/ioctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const HIDRAW_IOC_MAGIC: u8 = b'H';
const HIDRAW_IOC_GRDESCSIZE: u8 = 0x01;
const HIDRAW_SET_FEATURE: u8 = 0x06;
const HIDRAW_GET_FEATURE: u8 = 0x07;
const HIDRAW_SET_OUTPUT: u8 = 0x0b;

ioctl_read!(
hidraw_ioc_grdescsize,
Expand All @@ -27,3 +28,9 @@ ioctl_read_buf!(
HIDRAW_GET_FEATURE,
u8
);
ioctl_write_buf!(
hidraw_ioc_set_output,
HIDRAW_IOC_MAGIC,
HIDRAW_SET_OUTPUT,
u8
);
18 changes: 17 additions & 1 deletion src/windows_native/mod.rs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use crate::windows_native::types::{Handle, Overlapped};
use crate::{DeviceInfo, HidDeviceBackendBase, HidDeviceBackendWindows, HidError, HidResult};
use windows_sys::core::GUID;
use windows_sys::Win32::Devices::HumanInterfaceDevice::{
HidD_GetIndexedString, HidD_SetFeature, HidD_SetNumInputBuffers,
HidD_GetIndexedString, HidD_SetFeature, HidD_SetNumInputBuffers, HidD_SetOutputReport,
};
use windows_sys::Win32::Devices::Properties::{
DEVPKEY_Device_ContainerId, DEVPKEY_Device_InstanceId,
Expand Down Expand Up @@ -276,6 +276,22 @@ impl HidDeviceBackendBase for HidDevice {
Ok(bytes_returned as usize)
}

fn send_output_report(&self, data: &[u8]) -> HidResult<()> {
ensure!(!data.is_empty(), Err(HidError::InvalidZeroSizeData));
let mut state = self.feature_state.borrow_mut();
state.fill_buffer(data);

check_boolean(unsafe {
HidD_SetOutputReport(
self.device_handle.as_raw(),
state.buffer_ptr() as _,
state.buffer_len() as u32,
)
})?;

Ok(())
}

fn set_blocking_mode(&self, blocking: bool) -> HidResult<()> {
self.blocking.set(blocking);
Ok(())
Expand Down
Loading