use anyhow::{Context, Result};
use clap::Args;
use crate::cmd::diag::select_device;
#[derive(Debug, Args)]
pub struct DpiArgs {
#[arg(long)]
pub target: Option<u16>,
#[arg(long, value_name = "NAME")]
pub device: Option<String>,
}
pub async fn run(args: DpiArgs) -> Result<()> {
let (route, name) = select_device(args.device.as_deref(), &[0x2201]).await?;
println!("device: {name} ({route})");
let info = openlogi_hid::get_dpi_info(&route)
.await
.context("read DPI capabilities")?;
let before = info.current;
println!(" current DPI: {before}");
println!(" supported DPI: {}", summarize_dpi(&info.capabilities));
let target = match args.target {
Some(target) => {
if !info.capabilities.contains(target) {
anyhow::bail!(
"target {target} is not in the device-reported DPI list ({})",
summarize_dpi(&info.capabilities)
);
}
target
}
None => info
.capabilities
.adjacent_test_target(before)
.context("device reports fewer than two DPI values; pass --target to choose one")?,
};
if target == before {
println!(
" target {target} equals current — pick a different --target to exercise the write"
);
return Ok(());
}
println!(" writing DPI: {target}");
openlogi_hid::set_dpi(&route, target)
.await
.context("write DPI")?;
let after = openlogi_hid::get_dpi(&route)
.await
.context("read DPI after write")?;
println!(" read-back DPI: {after}");
if after == before {
anyhow::bail!("DPI write failed: requested {target}, device still reports {before}");
}
if after != target {
if info.capabilities.contains(after) {
println!(" note: device snapped {target} → {after}");
} else {
anyhow::bail!(
"DPI write failed: requested {target}, device reports {after} \
which is not in its supported list"
);
}
}
println!(" restoring DPI: {before}");
openlogi_hid::set_dpi(&route, before)
.await
.context("restore DPI")?;
println!("✓ DPI round-trip OK");
Ok(())
}
fn summarize_dpi(capabilities: &openlogi_hid::DpiCapabilities) -> String {
let values = capabilities.values();
let step = capabilities.step_hint();
if values.len() <= 12 {
return values
.iter()
.map(u16::to_string)
.collect::<Vec<_>>()
.join(", ");
}
format!(
"{}..{} (step ≈ {step}, {} values)",
capabilities.min(),
capabilities.max(),
values.len()
)
}
#[cfg(test)]
#[allow(clippy::expect_used, reason = "expect/unwrap are idiomatic in tests")]
mod summarize_dpi_tests {
use openlogi_hid::DpiCapabilities;
use super::summarize_dpi;
#[test]
fn lists_values_verbatim_at_the_twelve_value_boundary() {
let values: Vec<u16> = (1..=12).map(|n| n * 100).collect();
let caps = DpiCapabilities::new(values).expect("non-empty");
assert_eq!(
summarize_dpi(&caps),
"100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200"
);
}
#[test]
fn switches_to_a_range_summary_past_twelve_values() {
let values: Vec<u16> = (1..=13).map(|n| n * 100).collect();
let caps = DpiCapabilities::new(values).expect("non-empty");
assert_eq!(summarize_dpi(&caps), "100..1300 (step ≈ 100, 13 values)");
}
}