From 960bc60b200e05a0ed7a014961ba4428ac5e86d1 Mon Sep 17 00:00:00 2001 From: nevaforget Date: Thu, 9 Apr 2026 17:13:41 +0200 Subject: [PATCH] feat: show sidetone level in Waybar tooltip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tooltip now shows "HS80: 42% — Discharging | Sidetone: 10/23" when the ALSA sidetone control is available. Falls back gracefully to battery-only tooltip when sidetone cannot be read. Bump version to 0.1.1. --- Cargo.toml | 2 +- src/main.rs | 3 ++- src/output.rs | 9 +++++++-- tests/output_test.rs | 40 ++++++++++++++++++++++++++++------------ 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0ff9cdd..d810efb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "corsairctl" -version = "0.1.0" +version = "0.1.1" edition = "2024" description = "CLI tool for Corsair Bragi-protocol devices (HS80, etc.)" license = "MIT" diff --git a/src/main.rs b/src/main.rs index 18bbf96..3c4facf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,7 +58,8 @@ fn run() -> error::Result<()> { let device = bragi::BragiDevice::open_with_verbose(cli.verbose)?; let level = device.battery_level()?; let status = device.battery_status()?; - println!("{}", output::format_waybar_json(level, &status)); + let sidetone = sidetone::get_level().ok(); + println!("{}", output::format_waybar_json(level, &status, sidetone)); } Command::Udev => { diff --git a/src/output.rs b/src/output.rs index c02e065..6ec686e 100644 --- a/src/output.rs +++ b/src/output.rs @@ -26,7 +26,7 @@ pub fn format_info(vid: u16, pid: u16, fw_app: u16, fw_build: u16) -> String { /// Formatiert Waybar-kompatibles JSON. /// /// Waybar erwartet: {"text": "...", "tooltip": "...", "class": "...", "percentage": N} -pub fn format_waybar_json(level: f32, status: &BatteryStatus) -> String { +pub fn format_waybar_json(level: f32, status: &BatteryStatus, sidetone: Option) -> String { let icon = status.icon(); let label = status.label(); let percentage = level.round() as u32; @@ -41,9 +41,14 @@ pub fn format_waybar_json(level: f32, status: &BatteryStatus) -> String { BatteryStatus::Unknown(_) => "unknown", }; + let tooltip = match sidetone { + Some(st) => format!("HS80: {percentage}% — {label} | Sidetone: {st}/23"), + None => format!("HS80: {percentage}% — {label}"), + }; + let json = serde_json::json!({ "text": format!("{icon} {percentage}%"), - "tooltip": format!("HS80: {percentage}% — {label}"), + "tooltip": tooltip, "class": class, "percentage": percentage, }); diff --git a/tests/output_test.rs b/tests/output_test.rs index 9c2323b..dba4ce9 100644 --- a/tests/output_test.rs +++ b/tests/output_test.rs @@ -45,14 +45,14 @@ fn format_info_values() { #[test] fn waybar_json_is_valid_json() { - let json_str = output::format_waybar_json(50.0, &BatteryStatus::Discharging); + let json_str = output::format_waybar_json(50.0, &BatteryStatus::Discharging, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).expect("muss valides JSON sein"); assert!(parsed.is_object()); } #[test] fn waybar_json_has_required_fields() { - let json_str = output::format_waybar_json(50.0, &BatteryStatus::Discharging); + let json_str = output::format_waybar_json(50.0, &BatteryStatus::Discharging, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); assert!(parsed["text"].is_string(), "text fehlt"); assert!(parsed["tooltip"].is_string(), "tooltip fehlt"); @@ -62,56 +62,56 @@ fn waybar_json_has_required_fields() { #[test] fn waybar_json_percentage_matches_level() { - let json_str = output::format_waybar_json(73.5, &BatteryStatus::Discharging); + let json_str = output::format_waybar_json(73.5, &BatteryStatus::Discharging, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); assert_eq!(parsed["percentage"], 74); // 73.5 rounded } #[test] fn waybar_json_class_charging() { - let json_str = output::format_waybar_json(50.0, &BatteryStatus::Charging); + let json_str = output::format_waybar_json(50.0, &BatteryStatus::Charging, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); assert_eq!(parsed["class"], "charging"); } #[test] fn waybar_json_class_fully_charged() { - let json_str = output::format_waybar_json(100.0, &BatteryStatus::FullyCharged); + let json_str = output::format_waybar_json(100.0, &BatteryStatus::FullyCharged, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); assert_eq!(parsed["class"], "charging"); } #[test] fn waybar_json_class_low() { - let json_str = output::format_waybar_json(20.0, &BatteryStatus::Low); + let json_str = output::format_waybar_json(20.0, &BatteryStatus::Low, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); assert_eq!(parsed["class"], "low"); } #[test] fn waybar_json_class_critical_when_discharging_below_15() { - let json_str = output::format_waybar_json(10.0, &BatteryStatus::Discharging); + let json_str = output::format_waybar_json(10.0, &BatteryStatus::Discharging, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); assert_eq!(parsed["class"], "critical"); } #[test] fn waybar_json_class_warning_when_discharging_below_30() { - let json_str = output::format_waybar_json(25.0, &BatteryStatus::Discharging); + let json_str = output::format_waybar_json(25.0, &BatteryStatus::Discharging, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); assert_eq!(parsed["class"], "warning"); } #[test] fn waybar_json_class_normal_when_discharging_above_30() { - let json_str = output::format_waybar_json(50.0, &BatteryStatus::Discharging); + let json_str = output::format_waybar_json(50.0, &BatteryStatus::Discharging, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); assert_eq!(parsed["class"], "normal"); } #[test] fn waybar_json_class_at_boundary_15() { - let json_str = output::format_waybar_json(15.0, &BatteryStatus::Discharging); + let json_str = output::format_waybar_json(15.0, &BatteryStatus::Discharging, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); // 15.0 ist <= 15.0 → critical assert_eq!(parsed["class"], "critical"); @@ -119,7 +119,7 @@ fn waybar_json_class_at_boundary_15() { #[test] fn waybar_json_class_at_boundary_30() { - let json_str = output::format_waybar_json(30.0, &BatteryStatus::Discharging); + let json_str = output::format_waybar_json(30.0, &BatteryStatus::Discharging, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); // 30.0 ist <= 30.0 → warning assert_eq!(parsed["class"], "warning"); @@ -127,7 +127,23 @@ fn waybar_json_class_at_boundary_30() { #[test] fn waybar_json_offline() { - let json_str = output::format_waybar_json(0.0, &BatteryStatus::Offline); + let json_str = output::format_waybar_json(0.0, &BatteryStatus::Offline, None); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); assert_eq!(parsed["class"], "offline"); } + +#[test] +fn waybar_json_tooltip_without_sidetone() { + let json_str = output::format_waybar_json(50.0, &BatteryStatus::Discharging, None); + let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); + let tooltip = parsed["tooltip"].as_str().unwrap(); + assert!(!tooltip.contains("Sidetone"), "tooltip should not contain sidetone when None"); +} + +#[test] +fn waybar_json_tooltip_with_sidetone() { + let json_str = output::format_waybar_json(50.0, &BatteryStatus::Discharging, Some(10)); + let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); + let tooltip = parsed["tooltip"].as_str().unwrap(); + assert!(tooltip.contains("Sidetone: 10/23"), "tooltip should contain sidetone value"); +}