macro_rules! ensure {
($cond:expr, $result:expr) => {
if !($cond) {
return $result;
}
};
}
mod descriptor;
mod dev_node;
mod device_info;
mod error;
mod hid;
mod interfaces;
mod string;
mod types;
mod utils;
use std::cell::{Cell, RefCell};
use std::ptr::{null, null_mut};
use std::{
ffi::CStr,
fmt::{self, Debug},
};
use crate::windows_native::dev_node::DevNode;
use crate::windows_native::device_info::get_device_info;
use crate::windows_native::error::{check_boolean, Win32Error, WinError, WinResult};
use crate::windows_native::hid::{get_hid_attributes, PreparsedData};
use crate::windows_native::interfaces::Interface;
use crate::windows_native::string::{U16Str, U16String};
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_SetOutputReport,
};
use windows_sys::Win32::Devices::Properties::{
DEVPKEY_Device_ContainerId, DEVPKEY_Device_InstanceId,
};
use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE, TRUE};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, ReadFile, WriteFile, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE,
OPEN_EXISTING,
};
use windows_sys::Win32::System::Threading::ResetEvent;
use windows_sys::Win32::System::IO::{CancelIoEx, DeviceIoControl};
const STRING_BUF_LEN: usize = 128;
pub struct HidApiBackend;
impl HidApiBackend {
pub fn get_hid_device_info_vector(vid: u16, pid: u16) -> HidResult<Vec<DeviceInfo>> {
Ok(enumerate_devices(vid, pid)?)
}
pub fn open(vid: u16, pid: u16) -> HidResult<HidDevice> {
open(vid, pid, None)
}
pub fn open_serial(vid: u16, pid: u16, sn: &str) -> HidResult<HidDevice> {
open(vid, pid, Some(sn))
}
pub fn open_path(device_path: &CStr) -> HidResult<HidDevice> {
open_path(device_path)
}
}
pub struct HidDevice {
device_handle: Handle,
device_info: DeviceInfo,
read_pending: Cell<bool>,
blocking: Cell<bool>,
read_state: RefCell<AsyncState>,
write_state: RefCell<AsyncState>,
feature_state: RefCell<AsyncState>,
}
unsafe impl Send for HidDevice {}
struct AsyncState {
overlapped: Box<Overlapped>,
buffer: Vec<u8>,
}
impl AsyncState {
fn new(report_size: usize) -> Self {
Self {
overlapped: Default::default(),
buffer: vec![0u8; report_size],
}
}
fn clear_buffer(&mut self) {
self.buffer.fill(0)
}
fn fill_buffer(&mut self, data: &[u8]) {
let data_size = data.len().min(self.buffer.len());
self.buffer[..data_size].copy_from_slice(&data[..data_size]);
if data_size < self.buffer.len() {
self.buffer[data_size..].fill(0);
}
}
fn buffer_len(&self) -> usize {
self.buffer.len()
}
fn buffer_ptr(&mut self) -> *mut u8 {
self.buffer.as_mut_ptr()
}
}
impl Debug for HidDevice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HidDevice").finish()
}
}
impl HidDeviceBackendBase for HidDevice {
fn write(&self, data: &[u8]) -> HidResult<usize> {
ensure!(!data.is_empty(), Err(HidError::InvalidZeroSizeData));
let mut state = self.write_state.borrow_mut();
state.fill_buffer(data);
let res = unsafe {
WriteFile(
self.device_handle.as_raw(),
state.buffer_ptr(),
state.buffer_len() as u32,
null_mut(),
state.overlapped.as_raw(),
)
};
if res != TRUE {
let err = Win32Error::last();
ensure!(err == Win32Error::IoPending, Err(err.into()));
Ok(state
.overlapped
.get_result(&self.device_handle, Some(1000))?)
} else {
Ok(0)
}
}
fn read(&self, buf: &mut [u8]) -> HidResult<usize> {
self.read_timeout(buf, if self.blocking.get() { -1 } else { 0 })
}
fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> HidResult<usize> {
ensure!(!buf.is_empty(), Err(HidError::InvalidZeroSizeData));
let mut bytes_read = 0;
let mut io_runnig = false;
let mut state = self.read_state.borrow_mut();
if !self.read_pending.get() {
self.read_pending.set(true);
state.clear_buffer();
let res = unsafe {
ResetEvent(state.overlapped.event_handle());
ReadFile(
self.device_handle.as_raw(),
state.buffer_ptr() as _,
state.buffer_len() as u32,
&mut bytes_read,
state.overlapped.as_raw(),
)
};
if res != TRUE {
let err = Win32Error::last();
if err != Win32Error::IoPending {
unsafe { CancelIoEx(self.device_handle.as_raw(), state.overlapped.as_raw()) };
self.read_pending.set(false);
return Err(err.into());
}
io_runnig = true;
}
} else {
io_runnig = true;
}
if io_runnig {
let res = state
.overlapped
.get_result(&self.device_handle, u32::try_from(timeout).ok());
bytes_read = match res {
Ok(written) => written as u32,
Err(WinError::WaitTimedOut) => return Ok(0),
Err(err) => {
self.read_pending.set(false);
return Err(err.into());
}
};
}
self.read_pending.set(false);
let mut copy_len = 0;
if bytes_read > 0 {
if state.buffer[0] == 0x0 {
bytes_read -= 1;
copy_len = usize::min(bytes_read as usize, buf.len());
buf[..copy_len].copy_from_slice(&state.buffer[1..(1 + copy_len)]);
} else {
copy_len = usize::min(bytes_read as usize, buf.len());
buf[..copy_len].copy_from_slice(&state.buffer[0..copy_len]);
}
}
Ok(copy_len)
}
fn send_feature_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_SetFeature(
self.device_handle.as_raw(),
state.buffer_ptr() as _,
state.buffer_len() as u32,
)
})?;
Ok(())
}
fn get_feature_report(&self, buf: &mut [u8]) -> HidResult<usize> {
#[allow(clippy::identity_op, clippy::double_parens)]
const IOCTL_HID_GET_FEATURE: u32 = ((0x0000000b) << 16) | ((0) << 14) | ((100) << 2) | (2);
ensure!(!buf.is_empty(), Err(HidError::InvalidZeroSizeData));
let mut state = self.feature_state.borrow_mut();
let mut bytes_returned = 0;
let res = unsafe {
ResetEvent(state.overlapped.event_handle());
DeviceIoControl(
self.device_handle.as_raw(),
IOCTL_HID_GET_FEATURE,
buf.as_mut_ptr() as _,
buf.len() as u32,
buf.as_mut_ptr() as _,
buf.len() as u32,
&mut bytes_returned,
state.overlapped.as_raw(),
)
};
if res != TRUE {
let err = Win32Error::last();
ensure!(err == Win32Error::IoPending, Err(err.into()))
}
bytes_returned = state.overlapped.get_result(&self.device_handle, None)? as u32;
if buf[0] == 0x0 {
bytes_returned += 1;
}
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(())
}
fn get_manufacturer_string(&self) -> HidResult<Option<String>> {
Ok(self.device_info.manufacturer_string().map(String::from))
}
fn get_product_string(&self) -> HidResult<Option<String>> {
Ok(self.device_info.product_string().map(String::from))
}
fn get_serial_number_string(&self) -> HidResult<Option<String>> {
Ok(self.device_info.serial_number().map(String::from))
}
fn get_indexed_string(&self, index: i32) -> HidResult<Option<String>> {
let mut buf = [0u16; STRING_BUF_LEN];
let res = unsafe {
HidD_GetIndexedString(
self.device_handle.as_raw(),
index as u32,
buf.as_mut_ptr() as _,
STRING_BUF_LEN as u32,
)
};
check_boolean(res)?;
Ok(buf.split(|c| *c == 0).map(String::from_utf16_lossy).next())
}
fn get_device_info(&self) -> HidResult<DeviceInfo> {
Ok(self.device_info.clone())
}
fn get_report_descriptor(&self, buf: &mut [u8]) -> HidResult<usize> {
let desc = descriptor::get_descriptor(&PreparsedData::load(&self.device_handle)?)?;
let size = buf.len().min(desc.len());
buf[..size].copy_from_slice(&desc[..size]);
Ok(size)
}
}
impl HidDeviceBackendWindows for HidDevice {
fn get_container_id(&self) -> HidResult<GUID> {
let path =
U16String::try_from(self.device_info.path()).expect("device path is not valid unicode");
let device_id: U16String = Interface::get_property(&path, DEVPKEY_Device_InstanceId)?;
let dev_node = DevNode::from_device_id(&device_id)?;
let guid = dev_node.get_property(DEVPKEY_Device_ContainerId)?;
Ok(guid)
}
}
impl Drop for HidDevice {
fn drop(&mut self) {
unsafe {
for state in [
&mut self.read_state,
&mut self.write_state,
&mut self.feature_state,
] {
let mut state = state.borrow_mut();
if CancelIoEx(self.device_handle.as_raw(), state.overlapped.as_raw()) > 0 {
_ = state.overlapped.get_result(&self.device_handle, None);
}
}
}
}
}
fn enumerate_devices(vendor_id: u16, product_id: u16) -> WinResult<Vec<DeviceInfo>> {
Ok(Interface::get_interface_list()?
.iter()
.filter_map(|device_interface| {
let device_handle = open_device(device_interface, false).ok()?;
let attrib = get_hid_attributes(&device_handle);
((vendor_id == 0 || attrib.VendorID == vendor_id)
&& (product_id == 0 || attrib.ProductID == product_id))
.then(|| get_device_info(device_interface, &device_handle))
})
.collect())
}
fn open_device(path: &U16Str, open_rw: bool) -> WinResult<Handle> {
let handle = unsafe {
CreateFileW(
path.as_ptr(),
match open_rw {
true => GENERIC_WRITE | GENERIC_READ,
false => 0,
},
FILE_SHARE_READ | FILE_SHARE_WRITE,
null(),
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
null_mut(),
)
};
ensure!(
handle != INVALID_HANDLE_VALUE,
Err(Win32Error::last().into())
);
Ok(Handle::from_raw(handle))
}
fn open(vid: u16, pid: u16, sn: Option<&str>) -> HidResult<HidDevice> {
let dev = enumerate_devices(vid, pid)?
.into_iter()
.filter(|dev| dev.vendor_id == vid && dev.product_id == pid)
.find(|dev| sn.map_or(true, |sn| dev.serial_number().is_some_and(|n| sn == n)))
.ok_or(HidError::HidApiErrorEmpty)?;
open_path(dev.path())
}
fn open_path(device_path: &CStr) -> HidResult<HidDevice> {
let device_path = U16String::try_from(device_path).unwrap();
let handle = open_device(&device_path, true)
.or_else(|_| open_device(&device_path, false))?;
check_boolean(unsafe { HidD_SetNumInputBuffers(handle.as_raw(), 64) })?;
let caps = PreparsedData::load(&handle)?.get_caps()?;
let device_info = get_device_info(&device_path, &handle);
let dev = HidDevice {
device_handle: handle,
blocking: Cell::new(true),
read_pending: Cell::new(false),
read_state: RefCell::new(AsyncState::new(caps.InputReportByteLength as usize)),
write_state: RefCell::new(AsyncState::new(caps.OutputReportByteLength as usize)),
feature_state: RefCell::new(AsyncState::new(caps.FeatureReportByteLength as usize)),
device_info,
};
Ok(dev)
}