use windows::Win32::Devices::DeviceAndDriverInstallation::*;
use windows::Win32::Devices::HumanInterfaceDevice::GUID_DEVINTERFACE_HID;
use windows::Win32::Foundation::{ERROR_INSUFFICIENT_BUFFER, GetLastError, HANDLE};
use windows::Win32::UI::Input::*;
use zng_txt::{Txt, formatx};
use zng_view_api::raw_input::{InputDeviceCapability, InputDeviceInfo};
fn device_name(h_device: HANDLE) -> Option<String> {
let mut size = 0u32;
let r = unsafe { GetRawInputDeviceInfoW(Some(h_device), RIDI_DEVICENAME, None, &mut size) };
if r == 0 && size > 0 {
let mut buffer: Vec<u16> = vec![0; size as usize];
let r = unsafe { GetRawInputDeviceInfoW(Some(h_device), RIDI_DEVICENAME, Some(buffer.as_mut_ptr() as *mut _), &mut size) };
if r > 0 {
if let Some(pos) = buffer.iter().position(|&c| c == 0) {
buffer.truncate(pos);
}
return Some(String::from_utf16_lossy(&buffer));
}
}
None
}
fn device_info_by_handle(h_device: HANDLE) -> Option<RID_DEVICE_INFO> {
let mut device_info = RID_DEVICE_INFO {
cbSize: std::mem::size_of::<RID_DEVICE_INFO>() as u32,
..Default::default()
};
let mut cb_size = device_info.cbSize;
let r = unsafe {
GetRawInputDeviceInfoW(
Some(h_device),
RIDI_DEVICEINFO,
Some(&mut device_info as *mut _ as *mut _),
&mut cb_size,
)
};
if r > 0 { Some(device_info) } else { None }
}
fn device_info(device_path: &str) -> Option<RID_DEVICE_INFO> {
let mut num_devices = 5u32;
let mut devices: Vec<RAWINPUTDEVICELIST> = vec![Default::default(); num_devices as usize];
for _ in 0..5 {
let r = unsafe {
GetRawInputDeviceList(
Some(devices.as_mut_ptr()),
&mut num_devices,
std::mem::size_of::<RAWINPUTDEVICELIST>() as u32,
)
};
if r == -1i32 as u32 {
let e = unsafe { GetLastError() };
if e != ERROR_INSUFFICIENT_BUFFER {
tracing::error!("GetRawInputDeviceList error, {}", e.to_hresult());
continue; }
devices.resize(num_devices as usize, Default::default());
continue;
} else if r > 0 {
if r > num_devices {
tracing::error!("GetRawInputDeviceList unexpected return, {r} > num_devices");
continue; }
num_devices = r;
devices.truncate(num_devices as usize);
break;
} else {
break;
}
}
for dev in devices {
let h_device = dev.hDevice;
if let Some(name) = device_name(h_device)
&& name.eq_ignore_ascii_case(device_path)
{
return device_info_by_handle(h_device);
}
}
None
}
pub fn get(device_path: &str) -> InputDeviceInfo {
if let Some(info) = device_info(device_path) {
match info.dwType {
RIM_TYPEKEYBOARD => {
let name = if device_path.starts_with(r"\\?\HID#")
&& let Some(name) = hid_description(device_path)
{
name
} else {
Txt::from_static("Keyboard")
};
return InputDeviceInfo::new(name, InputDeviceCapability::KEY);
}
RIM_TYPEMOUSE => {
let mut cap = InputDeviceCapability::POINTER_MOTION;
cap |= InputDeviceCapability::AXIS_MOTION;
cap |= InputDeviceCapability::SCROLL_MOTION;
let mouse = unsafe { info.Anonymous.mouse };
if mouse.dwNumberOfButtons > 0 {
cap |= InputDeviceCapability::BUTTON;
}
let name = if device_path.starts_with(r"\\?\HID#")
&& let Some(name) = hid_description(device_path)
{
name
} else {
Txt::from_static("Mouse")
};
return InputDeviceInfo::new(name, cap);
}
RIM_TYPEHID => {
let hid = unsafe { info.Anonymous.hid };
let usage_page = hid.usUsagePage;
let usage = hid.usUsage;
let name = if let Some(name) = hid_description(device_path) {
name
} else {
formatx!("HID-0x{usage_page:X}:0x{usage:X}")
};
return InputDeviceInfo::new(name, hid_capabilities(usage_page, usage));
}
_ => {}
};
}
InputDeviceInfo::new("Unknown Device", InputDeviceCapability::empty())
}
fn hid_description(device_path: &str) -> Option<Txt> {
let h_dev_info = match unsafe { SetupDiGetClassDevsW(Some(&GUID_DEVINTERFACE_HID), None, None, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT) }
{
Ok(h) => h,
Err(e) => {
tracing::error!("`SetupDiGetClassDevsW` error, {e}");
return None;
}
};
let mut index = 0;
let mut dev_data = SP_DEVICE_INTERFACE_DATA {
cbSize: std::mem::size_of::<SP_DEVICE_INTERFACE_DATA>() as u32,
..Default::default()
};
let mut out = None::<Txt>;
while unsafe { SetupDiEnumDeviceInterfaces(h_dev_info, None, &GUID_DEVINTERFACE_HID, index, &mut dev_data) }.is_ok() {
let mut required_size = 0;
unsafe { SetupDiGetDeviceInterfaceDetailW(h_dev_info, &dev_data, None, 0, Some(&mut required_size), None) }.ok();
if required_size == 0 {
index += 1;
continue;
}
let mut detail_data_buffer = vec![0u8; required_size as usize];
let detail_data = detail_data_buffer.as_mut_ptr() as *mut SP_DEVICE_INTERFACE_DETAIL_DATA_W;
unsafe {
(*detail_data).cbSize = std::mem::size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>() as u32;
}
let mut dev_info_data = SP_DEVINFO_DATA {
cbSize: std::mem::size_of::<SP_DEVINFO_DATA>() as u32,
..Default::default()
};
if unsafe {
SetupDiGetDeviceInterfaceDetailW(
h_dev_info,
&dev_data,
Some(detail_data),
required_size,
None,
Some(&mut dev_info_data),
)
}
.is_err()
{
index += 1;
continue;
}
let current_device_path_wide = unsafe {
let path_ptr = std::ptr::addr_of!((*detail_data).DevicePath) as *const u16;
let mut len = 0;
while *path_ptr.add(len) != 0 {
len += 1;
}
std::slice::from_raw_parts(path_ptr, len)
};
let current_device_path = String::from_utf16_lossy(current_device_path_wide);
if current_device_path.eq_ignore_ascii_case(device_path) {
let mut prop_required_size = 0;
unsafe {
SetupDiGetDeviceRegistryPropertyW(
h_dev_info,
&dev_info_data,
SPDRP_DEVICEDESC,
None,
None,
Some(&mut prop_required_size),
)
}
.ok();
if prop_required_size > 0 {
let mut prop_buffer = vec![0u8; prop_required_size as usize];
if unsafe {
SetupDiGetDeviceRegistryPropertyW(h_dev_info, &dev_info_data, SPDRP_DEVICEDESC, None, Some(&mut prop_buffer), None)
}
.is_ok()
{
let desc_wide =
unsafe { std::slice::from_raw_parts(prop_buffer.as_ptr() as *const u16, (prop_required_size / 2) as usize) };
let description = String::from_utf16_lossy(desc_wide).trim_end_matches('\0').to_string();
out = Some(description.into());
break;
}
} else {
break;
}
}
index += 1;
}
unsafe { SetupDiDestroyDeviceInfoList(h_dev_info) }.ok();
out
}
fn hid_capabilities(usage_page: u16, usage: u16) -> InputDeviceCapability {
let mut out = InputDeviceCapability::empty();
match (usage_page, usage) {
(0x01, 0x02) => {
out |= InputDeviceCapability::BUTTON;
out |= InputDeviceCapability::AXIS_MOTION;
out |= InputDeviceCapability::SCROLL_MOTION;
out |= InputDeviceCapability::POINTER_MOTION;
}
(0x01, 0x06) => {
out = InputDeviceCapability::KEY;
}
(0x01, 0x04) => {
out |= InputDeviceCapability::BUTTON;
out |= InputDeviceCapability::AXIS_MOTION;
}
(0x01, 0x05) => {
out |= InputDeviceCapability::BUTTON;
out |= InputDeviceCapability::AXIS_MOTION;
}
(0x0C, 0x01) => {
out = InputDeviceCapability::KEY;
}
(0x0D, _) => {
out = InputDeviceCapability::POINTER_MOTION;
}
(0x0F, _) => {
out = InputDeviceCapability::BUTTON;
}
(0x01, 0x80) => {
out = InputDeviceCapability::KEY;
}
_ => {}
}
out
}