first commit

This commit is contained in:
Alexander Daichendt 2026-05-17 21:33:41 +02:00
commit 2a1252cc7a
69 changed files with 7559 additions and 0 deletions

View file

@ -0,0 +1,166 @@
fn try_connect_summary(
summary: DeviceSummary,
logs: std::sync::Arc<Mutex<Vec<DebugLogEntry>>>,
) -> Result<(ConnectedDevice, DeviceState), String> {
let file = OpenOptions::new()
.read(true)
.write(true)
.open(&summary.path)
.map_err(|err| format!("Could not open. Check hidraw permissions or udev rules: {err}"))?;
let mut device = ConnectedDevice { file, summary, logs };
let snapshot = device.snapshot("direct")?;
Ok((device, snapshot))
}
fn connection_candidates(
discovered: &[DeviceSummary],
selected: &DeviceSummary,
) -> Vec<DeviceSummary> {
let mut candidates: Vec<DeviceSummary> = discovered
.iter()
.filter(|device| {
device.vendor_id == selected.vendor_id
&& device.product_id == selected.product_id
&& device.probe_group == selected.probe_group
})
.cloned()
.collect();
candidates.sort_by_key(|device| {
(
device.path != selected.path,
interface_probe_priority(device.interface_number),
device.path.clone(),
)
});
candidates
}
fn interface_probe_priority(interface_number: Option<u8>) -> u8 {
match interface_number {
Some(3) => 0,
Some(2) => 1,
Some(1) => 2,
Some(0) => 3,
Some(other) => 4 + other,
None => u8::MAX,
}
}
fn discover_devices() -> io::Result<Vec<DeviceSummary>> {
let mut devices = Vec::new();
let hidraw_root = Path::new("/sys/class/hidraw");
if !hidraw_root.exists() {
return Ok(devices);
}
for entry in fs::read_dir(hidraw_root)? {
let entry = entry?;
let name = entry.file_name().to_string_lossy().to_string();
let Some((vendor_id, product_id, product_name, serial)) =
read_hidraw_identity(&entry.path())?
else {
continue;
};
let Some(supported) = SUPPORTED_DEVICES
.iter()
.find(|device| device.product_id == product_id)
else {
continue;
};
let interface_number = read_interface_number(&entry.path())?;
let probe_group = read_probe_group(&entry.path())?;
let manufacturer = read_ancestor_file(&entry.path(), "manufacturer")?;
let product_usb_string = read_ancestor_file(&entry.path(), "product")?;
devices.push(DeviceSummary {
path: format!("/dev/{name}"),
vendor_id,
product_id,
product_name: product_usb_string.unwrap_or(product_name),
manufacturer,
serial,
interface_number,
supported_name: supported.name.to_string(),
probe_group,
});
}
devices.sort_by_key(|device| {
(
device.probe_group.clone(),
interface_probe_priority(device.interface_number),
device.path.clone(),
)
});
Ok(devices)
}
fn read_hidraw_identity(
hidraw_path: &Path,
) -> io::Result<Option<(u16, u16, String, Option<String>)>> {
let uevent = fs::read_to_string(hidraw_path.join("device/uevent"))?;
let mut vendor_id = None;
let mut product_id = None;
let mut product_name = None;
let mut serial = None;
for line in uevent.lines() {
if let Some(value) = line.strip_prefix("HID_ID=") {
let parts: Vec<&str> = value.split(':').collect();
if parts.len() == 3 {
vendor_id = u16::from_str_radix(parts[1].trim_start_matches("0000"), 16).ok();
product_id = u16::from_str_radix(parts[2].trim_start_matches("0000"), 16).ok();
}
} else if let Some(value) = line.strip_prefix("HID_NAME=") {
product_name = Some(value.to_string());
} else if let Some(value) = line.strip_prefix("HID_UNIQ=") {
if !value.is_empty() {
serial = Some(value.to_string());
}
}
}
match (vendor_id, product_id) {
(Some(RAZER_VENDOR_ID), Some(product_id)) => Ok(Some((
RAZER_VENDOR_ID,
product_id,
product_name.unwrap_or_else(|| "Razer HID device".to_string()),
serial,
))),
_ => Ok(None),
}
}
fn read_interface_number(hidraw_path: &Path) -> io::Result<Option<u8>> {
let Some(value) = read_ancestor_file(hidraw_path, "bInterfaceNumber")? else {
return Ok(None);
};
Ok(u8::from_str_radix(value.trim(), 16).ok())
}
fn read_probe_group(hidraw_path: &Path) -> io::Result<String> {
let canonical = fs::canonicalize(hidraw_path)?;
let interface_path = canonical
.parent()
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "hidraw device has no parent"))?;
let usb_device = interface_path.parent().ok_or_else(|| {
io::Error::new(io::ErrorKind::NotFound, "HID interface has no USB parent")
})?;
Ok(usb_device.to_string_lossy().to_string())
}
fn read_ancestor_file(path: &Path, file_name: &str) -> io::Result<Option<String>> {
let mut current = fs::canonicalize(path)?;
for _ in 0..8 {
let candidate = current.join(file_name);
if candidate.exists() {
return Ok(Some(fs::read_to_string(candidate)?.trim().to_string()));
}
if !current.pop() {
break;
}
}
Ok(None)
}