diff --git a/src/volume.rs b/src/volume.rs index 2e1b793..94e0f69 100644 --- a/src/volume.rs +++ b/src/volume.rs @@ -65,18 +65,27 @@ fn query_volume() -> Option { parse_volume(&String::from_utf8_lossy(&out.stdout)) } -/// Parse the first percentage value from pactl output such as: +/// Parse the average percentage across all channels from pactl output such as: /// `"Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% ..."` +/// +/// Averaging across channels matches what OS volume sliders display and avoids +/// a misleading reading when channels are unbalanced (e.g. hard-panned mono). fn parse_volume(s: &str) -> Option { - for part in s.split('/') { - let trimmed = part.trim(); - if let Some(stripped) = trimmed.strip_suffix('%') { - if let Ok(n) = stripped.trim().parse::() { - return Some(n); - } - } + let levels: Vec = s + .split('/') + .filter_map(|part| { + part.trim() + .strip_suffix('%') + .and_then(|n| n.trim().parse::().ok()) + }) + .collect(); + + if levels.is_empty() { + return None; } - None + + let avg = levels.iter().sum::() / levels.len() as u32; + Some(avg.min(100) as u8) } fn broadcast(pkt: Packet, keyboards: &[Keyboard]) { @@ -99,17 +108,24 @@ mod tests { use super::parse_volume; #[test] - fn parses_stereo_output() { + fn parses_stereo_equal() { let s = "Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB"; assert_eq!(parse_volume(s), Some(100)); } #[test] - fn parses_partial_volume() { + fn parses_stereo_equal_partial() { let s = "Volume: front-left: 45875 / 70% / -9.03 dB, front-right: 45875 / 70% / -9.03 dB"; assert_eq!(parse_volume(s), Some(70)); } + #[test] + fn averages_unbalanced_channels() { + // left=80%, right=20% → average=50% + let s = "Volume: front-left: 52429 / 80% / -5.08 dB, front-right: 13107 / 20% / -13.98 dB"; + assert_eq!(parse_volume(s), Some(50)); + } + #[test] fn returns_none_for_garbage() { assert_eq!(parse_volume("no percentage here"), None);