diff options
| author | jamie <git@skilet.ro> | 2026-08-29 21:58:43 +0100 |
|---|---|---|
| committer | jamie <git@skilet.ro> | 2026-08-29 22:06:00 +0100 |
| commit | c1110ac81f87ba374696e419f3807f48a3a06d6d (patch) | |
| tree | 6d03e2747657c1b2e30baba309d4620ce3227eaf /src/bin/probe.rs | |
| parent | 3061396dbb60b5b294c1c0c62c25da8de5c87ffb (diff) | |
feat(daemon): link-state hooks for headset connect/disconnect
Diffstat (limited to 'src/bin/probe.rs')
| -rw-r--r-- | src/bin/probe.rs | 103 |
1 files changed, 61 insertions, 42 deletions
diff --git a/src/bin/probe.rs b/src/bin/probe.rs index b2e35d4..e77e6cd 100644 --- a/src/bin/probe.rs +++ b/src/bin/probe.rs @@ -17,8 +17,9 @@ fn main() -> ExitCode { Some("send") => cmd_send(&args[1..]), Some("sweep") => cmd_sweep(&args[1..]), Some("status") => cmd_status_dump(), + Some("selenum") => cmd_sel_enum(&args[1..]), _ => { - eprintln!("usage: mmx150-probe <list|scan|send|sweep|status>"); + eprintln!("usage: mmx150-probe <list|scan|send|sweep|status|selenum>"); ExitCode::FAILURE } } @@ -154,43 +155,9 @@ fn send_and_read(hid: &mut Hidraw, data: &[u8]) -> Option<Vec<u8>> { return None; } } - read_one_response(hid, Duration::from_millis(400)) -} - -/// Wait up to `timeout` for a single input report and return it. -fn read_one_response(hid: &mut Hidraw, timeout: Duration) -> Option<Vec<u8>> { - let deadline = std::time::Instant::now() + timeout; - loop { - let now = std::time::Instant::now(); - if now >= deadline { - return None; - } - let mut set = unsafe { std::mem::zeroed::<libc::fd_set>() }; - unsafe { libc::FD_ZERO(&mut set) }; - unsafe { libc::FD_SET(hid.as_raw_fd(), &mut set) }; - let remaining = deadline.saturating_duration_since(now).as_millis(); - let mut to = libc::timeval { - tv_sec: (remaining / 1000) as i64, - tv_usec: ((remaining % 1000) * 1000) as i64, - }; - let n = unsafe { - libc::select( - hid.as_raw_fd() + 1, - &mut set, - std::ptr::null_mut(), - std::ptr::null_mut(), - &mut to, - ) - }; - if n <= 0 { - return None; - } - let mut buf = [0u8; 256]; - match hid.read(&mut buf) { - Ok(len) => return Some(buf[..len].to_vec()), - Err(_) => return None, - } - } + hid.read_with_timeout(Duration::from_millis(400)) + .ok() + .flatten() } /// Sweep a set of candidate commands against the dongle, printing responses @@ -274,8 +241,60 @@ fn cmd_status_dump() -> ExitCode { ExitCode::SUCCESS } +/// Enumerate many sub-command selectors across a report family, dumping any +/// response whose payload carries a real data byte (not just an echo of the +/// selector / a constant). This surfaces battery and state registers. +/// +/// usage: mmx150-probe selenum [report_id] (default: 0x3a) +fn cmd_sel_enum(rest: &[String]) -> ExitCode { + let report_id = rest + .first() + .and_then(|s| u8::from_str_radix(s.trim_start_matches("0x"), 16).ok()) + .unwrap_or(0x3a); + + let Some(dev) = mmx150::hid::find_mmx150().unwrap_or(None) else { + eprintln!("MMX 150 dongle not found"); + return ExitCode::FAILURE; + }; + let hid = match Hidraw::open(&dev.devnode) { + Ok(h) => h, + Err(e) => { + eprintln!("open failed: {e}"); + return ExitCode::FAILURE; + } + }; + let mut hid = hid; + + println!("sweeping report 0x{report_id:02x}, selector range 0x00..0xff"); + for sel in 0x00u8..=0xffu8 { + let mut payload = vec![0u8; 60]; + payload[0] = 0x09; + payload[1] = 0x22; + payload[2] = sel; + let mut frame = vec![report_id]; + frame.extend(payload); + let resp = send_and_read(&mut hid, &frame); + + // Only report responses containing a "real" data byte beyond the + // id/echo/constant region that could be battery/state. + if let Some(b) = resp { + if b.len() < 6 { + continue; + } + // b[1] is the echo of sel, b[5] the constant ack. Look for any + // non-zero byte among the remaining payload that isn't the ack. + let interesting = b[6..].iter().any(|&x| x != 0x00); + if interesting { + println!("sel={sel:02x} resp={}", hex(&b)); + } + std::thread::sleep(Duration::from_millis(8)); + } + } + ExitCode::SUCCESS +} + fn cmd_sweep(rest: &[String]) -> ExitCode { - if rest.len() < 1 { + if rest.is_empty() { eprintln!("usage: mmx150-probe sweep <report_id> <payload_len>"); return ExitCode::FAILURE; } @@ -301,7 +320,7 @@ fn cmd_sweep(rest: &[String]) -> ExitCode { // Baseline: all-zero command. let base = { let mut cmd = vec![report_id]; - cmd.extend(std::iter::repeat(0u8).take(plen)); + cmd.extend(vec![0u8; plen]); send_and_read(&mut hid, &cmd) }; println!( @@ -316,7 +335,7 @@ fn cmd_sweep(rest: &[String]) -> ExitCode { for idx in 1..plen.clamp(1, 12) { for val in [0x01u8, 0x22, 0x2c, 0x2f, 0x80, 0x5a, 0xff] { let mut cmd = vec![report_id]; - cmd.extend(std::iter::repeat(0u8).take(plen)); + cmd.extend(vec![0u8; plen]); cmd[idx] = val; match send_and_read(&mut hid, &cmd) { Some(resp) if Some(&resp) != base.as_ref() => { @@ -339,7 +358,7 @@ fn hex(bytes: &[u8]) -> String { fn parse_hex(s: &str) -> Vec<u8> { let s = s.trim_start_matches("0x"); - if s.len() % 2 != 0 { + if !s.len().is_multiple_of(2) { return Vec::new(); } s.as_bytes() |
