From e83bc02563e063c11b8c64d4c11da4dd67ccb632 Mon Sep 17 00:00:00 2001 From: rootiest Date: Fri, 10 Apr 2026 14:52:58 -0400 Subject: [PATCH] fix(volume): average all channels instead of returning the first Previously returned the first channel percentage found, which is incorrect when channels are unbalanced. Average across all channels matches what OS volume sliders display and avoids a misleading reading in edge cases such as hard-panned mono (e.g. left=100%, right=0% would have incorrectly reported 100% rather than 50%). Add a test covering the unbalanced case. --- src/volume.rs | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) 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);