use std::ffi::c_void;
use std::mem::size_of;
use std::ptr;
type Handle = *mut c_void;
const INVALID_HANDLE_VALUE: Handle = -1isize as Handle;
const DIGCF_PRESENT: u32 = 0x0000_0002;
const DIGCF_DEVICEINTERFACE: u32 = 0x0000_0010;
const DETAIL_CB_SIZE: usize = 8;
const DETAIL_PATH_OFFSET: usize = 4;
const SPDRP_DEVICEDESC: u32 = 0x0000_0000;
const SPDRP_FRIENDLYNAME: u32 = 0x0000_000C;
#[repr(C)]
pub struct Guid {
pub data1: u32,
pub data2: u16,
pub data3: u16,
pub data4: [u8; 8],
}
pub const GUID_DEVCLASS_BLUETOOTH: Guid = Guid {
data1: 0xe0cb_f06c,
data2: 0xcd8b,
data3: 0x4647,
data4: [0xbb, 0x8a, 0x26, 0x3b, 0x43, 0xf0, 0xf9, 0x74],
};
pub const KSCATEGORY_VIDEO_CAMERA: Guid = Guid {
data1: 0xe532_3777,
data2: 0xf976,
data3: 0x4f5b,
data4: [0x9b, 0x55, 0xb9, 0x46, 0x99, 0xc4, 0x6e, 0x44],
};
#[repr(C)]
pub struct DevPropKey {
pub fmtid: Guid,
pub pid: u32,
}
pub const DEVPKEY_DEVICE_CONNECTED: DevPropKey = DevPropKey {
fmtid: Guid {
data1: 0x83da_6326,
data2: 0x97a6,
data3: 0x4088,
data4: [0x94, 0x53, 0xa1, 0x92, 0x3f, 0x57, 0x3b, 0x29],
},
pid: 15,
};
const DEVPROP_TYPE_BOOLEAN: u32 = 0x0000_0011;
pub struct PresentDevice {
pub instance_id: String,
pub name: Option<String>,
pub connected: Option<bool>,
}
#[repr(C)]
struct SpDevinfoData {
cb_size: u32,
class_guid: Guid,
dev_inst: u32,
reserved: usize,
}
#[repr(C)]
struct SpDeviceInterfaceData {
cb_size: u32,
interface_class_guid: Guid,
flags: u32,
reserved: usize,
}
#[link(name = "setupapi")]
extern "system" {
fn SetupDiGetClassDevsW(
class_guid: *const Guid,
enumerator: *const u16,
hwnd_parent: Handle,
flags: u32,
) -> Handle;
fn SetupDiEnumDeviceInfo(dev_info: Handle, index: u32, data: *mut SpDevinfoData) -> i32;
fn SetupDiGetDeviceRegistryPropertyW(
dev_info: Handle,
data: *const SpDevinfoData,
property: u32,
property_reg_data_type: *mut u32,
property_buffer: *mut u8,
property_buffer_size: u32,
required_size: *mut u32,
) -> i32;
fn SetupDiGetDevicePropertyW(
dev_info: Handle,
data: *const SpDevinfoData,
prop_key: *const DevPropKey,
prop_type: *mut u32,
prop_buffer: *mut u8,
prop_buffer_size: u32,
required_size: *mut u32,
flags: u32,
) -> i32;
fn SetupDiGetDeviceInstanceIdW(
dev_info: Handle,
data: *const SpDevinfoData,
buffer: *mut u16,
buffer_size: u32,
required_size: *mut u32,
) -> i32;
fn SetupDiDestroyDeviceInfoList(dev_info: Handle) -> i32;
fn SetupDiEnumDeviceInterfaces(
dev_info: Handle,
dev_info_data: *mut SpDevinfoData,
interface_class_guid: *const Guid,
member_index: u32,
interface_data: *mut SpDeviceInterfaceData,
) -> i32;
fn SetupDiGetDeviceInterfaceDetailW(
dev_info: Handle,
interface_data: *mut SpDeviceInterfaceData,
detail_data: *mut u8,
detail_data_size: u32,
required_size: *mut u32,
device_info_data: *mut SpDevinfoData,
) -> i32;
}
fn wide_to_string(buf: &[u16]) -> Option<String> {
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
let s = String::from_utf16_lossy(&buf[..len]).trim().to_string();
if s.is_empty() {
None
} else {
Some(s)
}
}
fn device_name(dev_info: Handle, data: &SpDevinfoData) -> Option<String> {
for prop in [SPDRP_FRIENDLYNAME, SPDRP_DEVICEDESC] {
let mut buf = [0u16; 512];
let mut required = 0u32;
let ok = unsafe {
SetupDiGetDeviceRegistryPropertyW(
dev_info,
data,
prop,
ptr::null_mut(),
buf.as_mut_ptr() as *mut u8,
(buf.len() * 2) as u32,
&mut required,
)
};
if ok != 0 {
if let Some(name) = wide_to_string(&buf) {
return Some(name);
}
}
}
None
}
fn enumerate_names(class_guid: &Guid, flags: u32) -> Vec<String> {
let mut names = Vec::new();
unsafe {
let dev_info = SetupDiGetClassDevsW(class_guid, ptr::null(), ptr::null_mut(), flags);
if dev_info == INVALID_HANDLE_VALUE {
return names;
}
let mut index = 0u32;
loop {
let mut data: SpDevinfoData = std::mem::zeroed();
data.cb_size = size_of::<SpDevinfoData>() as u32;
if SetupDiEnumDeviceInfo(dev_info, index, &mut data) == 0 {
break;
}
index += 1;
if let Some(name) = device_name(dev_info, &data) {
names.push(name);
}
}
SetupDiDestroyDeviceInfoList(dev_info);
}
names
}
fn device_instance_id(dev_info: Handle, data: &SpDevinfoData) -> Option<String> {
let mut buf = [0u16; 512];
let mut required = 0u32;
let ok = unsafe {
SetupDiGetDeviceInstanceIdW(
dev_info,
data,
buf.as_mut_ptr(),
buf.len() as u32,
&mut required,
)
};
if ok == 0 {
return None;
}
wide_to_string(&buf)
}
fn device_connected(dev_info: Handle, data: &SpDevinfoData) -> Option<bool> {
let mut prop_type = 0u32;
let mut value = 0u8;
let mut required = 0u32;
let ok = unsafe {
SetupDiGetDevicePropertyW(
dev_info,
data,
&DEVPKEY_DEVICE_CONNECTED,
&mut prop_type,
&mut value,
1,
&mut required,
0,
)
};
if ok == 0 || prop_type != DEVPROP_TYPE_BOOLEAN {
return None;
}
Some(value != 0)
}
pub fn present_devices(class_guid: &Guid) -> Vec<PresentDevice> {
let mut devices = Vec::new();
unsafe {
let dev_info =
SetupDiGetClassDevsW(class_guid, ptr::null(), ptr::null_mut(), DIGCF_PRESENT);
if dev_info == INVALID_HANDLE_VALUE {
return devices;
}
let mut index = 0u32;
loop {
let mut data: SpDevinfoData = std::mem::zeroed();
data.cb_size = size_of::<SpDevinfoData>() as u32;
if SetupDiEnumDeviceInfo(dev_info, index, &mut data) == 0 {
break;
}
index += 1;
let Some(instance_id) = device_instance_id(dev_info, &data) else {
continue;
};
devices.push(PresentDevice {
instance_id,
name: device_name(dev_info, &data),
connected: device_connected(dev_info, &data),
});
}
SetupDiDestroyDeviceInfoList(dev_info);
}
devices
}
pub fn present_device_names(class_guid: &Guid) -> Vec<String> {
enumerate_names(class_guid, DIGCF_PRESENT)
}
pub fn present_interface_device_names(interface_guid: &Guid) -> Vec<String> {
enumerate_names(interface_guid, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)
}
pub fn present_interface_device_paths(interface_guid: &Guid) -> Vec<String> {
let mut paths = Vec::new();
unsafe {
let dev_info = SetupDiGetClassDevsW(
interface_guid,
ptr::null(),
ptr::null_mut(),
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE,
);
if dev_info.is_null() || dev_info == INVALID_HANDLE_VALUE {
return paths;
}
let mut index = 0u32;
loop {
let mut data = SpDeviceInterfaceData {
cb_size: size_of::<SpDeviceInterfaceData>() as u32,
interface_class_guid: Guid {
data1: 0,
data2: 0,
data3: 0,
data4: [0; 8],
},
flags: 0,
reserved: 0,
};
if SetupDiEnumDeviceInterfaces(
dev_info,
ptr::null_mut(),
interface_guid,
index,
&mut data,
) == 0
{
break;
}
index += 1;
let mut needed = 0u32;
SetupDiGetDeviceInterfaceDetailW(
dev_info,
&mut data,
ptr::null_mut(),
0,
&mut needed,
ptr::null_mut(),
);
if needed as usize <= DETAIL_PATH_OFFSET {
continue;
}
let mut detail = vec![0u8; needed as usize];
detail[0..4].copy_from_slice(&(DETAIL_CB_SIZE as u32).to_ne_bytes());
if SetupDiGetDeviceInterfaceDetailW(
dev_info,
&mut data,
detail.as_mut_ptr(),
needed,
&mut needed,
ptr::null_mut(),
) == 0
{
continue;
}
let wide: Vec<u16> = detail[DETAIL_PATH_OFFSET..]
.as_chunks::<2>()
.0
.iter()
.map(|c| u16::from_ne_bytes(*c))
.collect();
if let Some(path) = wide_to_string(&wide) {
paths.push(path);
}
}
SetupDiDestroyDeviceInfoList(dev_info);
}
paths
}
const DIGCF_ALLCLASSES: u32 = 0x0000_0004;
const SPDRP_HARDWAREID: u32 = 0x0000_0001;
pub const GUID_DEVCLASS_HIDCLASS: Guid = Guid {
data1: 0x745a_17a0,
data2: 0x74d3,
data3: 0x11d0,
data4: [0xb6, 0xfe, 0x00, 0xa0, 0xc9, 0x0f, 0x57, 0xda],
};
pub struct PnpDevice {
pub class_guid: Guid,
pub name: Option<String>,
pub hardware_ids: Vec<String>,
}
impl PnpDevice {
pub fn in_class(&self, class: &Guid) -> bool {
self.class_guid.data1 == class.data1
&& self.class_guid.data2 == class.data2
&& self.class_guid.data3 == class.data3
&& self.class_guid.data4 == class.data4
}
}
fn split_multi_sz(buf: &[u16]) -> Vec<String> {
buf.split(|&c| c == 0)
.take_while(|s| !s.is_empty())
.map(String::from_utf16_lossy)
.collect()
}
fn device_multi_sz(dev_info: Handle, data: &SpDevinfoData, prop: u32) -> Vec<String> {
let mut buf = vec![0u16; 512];
for _ in 0..2 {
let mut required = 0u32;
let ok = unsafe {
SetupDiGetDeviceRegistryPropertyW(
dev_info,
data,
prop,
ptr::null_mut(),
buf.as_mut_ptr() as *mut u8,
(buf.len() * 2) as u32,
&mut required,
)
};
if ok != 0 {
return split_multi_sz(&buf);
}
let needed = (required as usize).div_ceil(2);
if needed <= buf.len() {
break;
}
buf = vec![0u16; needed];
}
Vec::new()
}
pub fn present_devices_all_classes() -> Vec<PnpDevice> {
let mut devices = Vec::new();
unsafe {
let dev_info = SetupDiGetClassDevsW(
ptr::null(),
ptr::null(),
ptr::null_mut(),
DIGCF_PRESENT | DIGCF_ALLCLASSES,
);
if dev_info == INVALID_HANDLE_VALUE {
return devices;
}
let mut index = 0u32;
loop {
let mut data: SpDevinfoData = std::mem::zeroed();
data.cb_size = size_of::<SpDevinfoData>() as u32;
if SetupDiEnumDeviceInfo(dev_info, index, &mut data) == 0 {
break;
}
index += 1;
let name = device_name(dev_info, &data);
let hardware_ids = device_multi_sz(dev_info, &data, SPDRP_HARDWAREID);
devices.push(PnpDevice {
class_guid: data.class_guid,
name,
hardware_ids,
});
}
SetupDiDestroyDeviceInfoList(dev_info);
}
devices
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sp_devinfo_data_layout() {
assert_eq!(size_of::<SpDevinfoData>(), 32);
assert_eq!(size_of::<Guid>(), 16);
}
#[test]
fn test_split_multi_sz() {
let wide = |s: &str| s.encode_utf16().collect::<Vec<u16>>();
let mut buf = wide("HID\\VID_045E&UP:0001_U:0080");
buf.push(0);
buf.extend(wide("HID_DEVICE_SYSTEM_CONTROL"));
buf.extend([0, 0, 0, 0]);
assert_eq!(
split_multi_sz(&buf),
vec![
"HID\\VID_045E&UP:0001_U:0080".to_string(),
"HID_DEVICE_SYSTEM_CONTROL".to_string()
]
);
let mut trailing = wide("A");
trailing.extend([0, 0]);
trailing.extend(wide("STALE"));
assert_eq!(split_multi_sz(&trailing), vec!["A".to_string()]);
assert!(split_multi_sz(&[0, 0]).is_empty());
assert!(split_multi_sz(&[]).is_empty());
}
#[test]
fn test_dev_prop_key_layout() {
assert_eq!(size_of::<DevPropKey>(), 20);
}
}