mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-03-17 19:52:31 +00:00
linux-rust: show battery info from LE in tray
should probably have an indication when not connected
This commit is contained in:
@@ -11,6 +11,8 @@ use futures::StreamExt;
|
|||||||
use hex;
|
use hex;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use crate::bluetooth::aacp::BatteryStatus;
|
||||||
|
use crate::ui::tray::MyTray;
|
||||||
|
|
||||||
fn get_proximity_keys_path() -> PathBuf {
|
fn get_proximity_keys_path() -> PathBuf {
|
||||||
let data_dir = std::env::var("XDG_DATA_HOME")
|
let data_dir = std::env::var("XDG_DATA_HOME")
|
||||||
@@ -66,7 +68,7 @@ fn verify_rpa(addr: &str, irk: &[u8; 16]) -> bool {
|
|||||||
hash == computed_hash
|
hash == computed_hash
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_le_monitor() -> bluer::Result<()> {
|
pub async fn start_le_monitor(tray_handle: Option<ksni::Handle<MyTray>>) -> bluer::Result<()> {
|
||||||
let session = Session::new().await?;
|
let session = Session::new().await?;
|
||||||
let adapter = session.default_adapter().await?;
|
let adapter = session.default_adapter().await?;
|
||||||
adapter.set_powered(true).await?;
|
adapter.set_powered(true).await?;
|
||||||
@@ -125,6 +127,7 @@ pub async fn start_le_monitor() -> bluer::Result<()> {
|
|||||||
|
|
||||||
if verified_macs.contains(&addr) {
|
if verified_macs.contains(&addr) {
|
||||||
let mut events = dev.events().await?;
|
let mut events = dev.events().await?;
|
||||||
|
let tray_handle_clone = tray_handle.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(ev) = events.next().await {
|
while let Some(ev) = events.next().await {
|
||||||
match ev {
|
match ev {
|
||||||
@@ -170,6 +173,17 @@ pub async fn start_le_monitor() -> bluer::Result<()> {
|
|||||||
(case_byte & 0x7F, (case_byte & 0x80) != 0)
|
(case_byte & 0x7F, (case_byte & 0x80) != 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(handle) = &tray_handle_clone {
|
||||||
|
handle.update(|tray: &mut MyTray| {
|
||||||
|
tray.battery_l = if left_byte == 0xff { None } else { Some(left_battery as u8) };
|
||||||
|
tray.battery_l_status = if left_byte == 0xff { Some(BatteryStatus::Disconnected) } else if left_charging { Some(BatteryStatus::Charging) } else { Some(BatteryStatus::NotCharging) };
|
||||||
|
tray.battery_r = if right_byte == 0xff { None } else { Some(right_battery as u8) };
|
||||||
|
tray.battery_r_status = if right_byte == 0xff { Some(BatteryStatus::Disconnected) } else if right_charging { Some(BatteryStatus::Charging) } else { Some(BatteryStatus::NotCharging) };
|
||||||
|
tray.battery_c = if case_byte == 0xff { None } else { Some(case_battery as u8) };
|
||||||
|
tray.battery_c_status = if case_byte == 0xff { Some(BatteryStatus::Disconnected) } else if case_charging { Some(BatteryStatus::Charging) } else { Some(BatteryStatus::NotCharging) };
|
||||||
|
}).await;
|
||||||
|
}
|
||||||
|
|
||||||
info!("Battery status: Left: {}, Right: {}, Case: {}, InEar: L:{} R:{}",
|
info!("Battery status: Left: {}, Right: {}, Case: {}, InEar: L:{} R:{}",
|
||||||
if left_byte == 0xff { "disconnected".to_string() } else { format!("{}% (charging: {})", left_battery, left_charging) },
|
if left_byte == 0xff { "disconnected".to_string() } else { format!("{}% (charging: {})", left_battery, left_charging) },
|
||||||
if right_byte == 0xff { "disconnected".to_string() } else { format!("{}% (charging: {})", right_battery, right_charging) },
|
if right_byte == 0xff { "disconnected".to_string() } else { format!("{}% (charging: {})", right_battery, right_charging) },
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ async fn main() -> bluer::Result<()> {
|
|||||||
let adapter = session.default_adapter().await?;
|
let adapter = session.default_adapter().await?;
|
||||||
adapter.set_powered(true).await?;
|
adapter.set_powered(true).await?;
|
||||||
|
|
||||||
// Start LE monitor for Apple devices
|
let le_tray_clone = tray_handle.clone();
|
||||||
tokio::spawn(async {
|
tokio::spawn(async move {
|
||||||
info!("Starting LE monitor...");
|
info!("Starting LE monitor...");
|
||||||
if let Err(e) = start_le_monitor().await {
|
if let Err(e) = start_le_monitor(le_tray_clone).await {
|
||||||
log::error!("LE monitor error: {}", e);
|
log::error!("LE monitor error: {}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ impl ksni::Tray for MyTray {
|
|||||||
"AirPods".into()
|
"AirPods".into()
|
||||||
}
|
}
|
||||||
fn icon_pixmap(&self) -> Vec<Icon> {
|
fn icon_pixmap(&self) -> Vec<Icon> {
|
||||||
let text = if self.connected {
|
let text = {
|
||||||
let mut levels: Vec<u8> = Vec::new();
|
let mut levels: Vec<u8> = Vec::new();
|
||||||
if let Some(l) = self.battery_l {
|
if let Some(l) = self.battery_l {
|
||||||
if self.battery_l_status != Some(crate::bluetooth::aacp::BatteryStatus::Disconnected) {
|
if self.battery_l_status != Some(crate::bluetooth::aacp::BatteryStatus::Disconnected) {
|
||||||
@@ -40,19 +40,17 @@ impl ksni::Tray for MyTray {
|
|||||||
levels.push(r);
|
levels.push(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(c) = self.battery_c {
|
// if let Some(c) = self.battery_c {
|
||||||
if self.battery_c_status != Some(crate::bluetooth::aacp::BatteryStatus::Disconnected) {
|
// if self.battery_c_status != Some(crate::bluetooth::aacp::BatteryStatus::Disconnected) {
|
||||||
levels.push(c);
|
// levels.push(c);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
let min_battery = levels.iter().min().copied();
|
let min_battery = levels.iter().min().copied();
|
||||||
if let Some(b) = min_battery {
|
if let Some(b) = min_battery {
|
||||||
format!("{}", b)
|
format!("{}", b)
|
||||||
} else {
|
} else {
|
||||||
"?".to_string()
|
"?".to_string()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
"".into()
|
|
||||||
};
|
};
|
||||||
let any_bud_charging = matches!(self.battery_l_status, Some(crate::bluetooth::aacp::BatteryStatus::Charging))
|
let any_bud_charging = matches!(self.battery_l_status, Some(crate::bluetooth::aacp::BatteryStatus::Charging))
|
||||||
|| matches!(self.battery_r_status, Some(crate::bluetooth::aacp::BatteryStatus::Charging));
|
|| matches!(self.battery_r_status, Some(crate::bluetooth::aacp::BatteryStatus::Charging));
|
||||||
@@ -60,39 +58,30 @@ impl ksni::Tray for MyTray {
|
|||||||
vec![icon]
|
vec![icon]
|
||||||
}
|
}
|
||||||
fn tool_tip(&self) -> ToolTip {
|
fn tool_tip(&self) -> ToolTip {
|
||||||
if self.connected {
|
let format_component = |label: &str, level: Option<u8>, status: Option<crate::bluetooth::aacp::BatteryStatus>| -> String {
|
||||||
let format_component = |label: &str, level: Option<u8>, status: Option<crate::bluetooth::aacp::BatteryStatus>| -> String {
|
match status {
|
||||||
match status {
|
Some(crate::bluetooth::aacp::BatteryStatus::Disconnected) => format!("{}: -", label),
|
||||||
Some(crate::bluetooth::aacp::BatteryStatus::Disconnected) => format!("{}: -", label),
|
_ => {
|
||||||
_ => {
|
let pct = level.map(|b| format!("{}%", b)).unwrap_or("?".to_string());
|
||||||
let pct = level.map(|b| format!("{}%", b)).unwrap_or("?".to_string());
|
let suffix = if status == Some(crate::bluetooth::aacp::BatteryStatus::Charging) {
|
||||||
let suffix = if status == Some(crate::bluetooth::aacp::BatteryStatus::Charging) {
|
"⚡"
|
||||||
"⚡"
|
} else {
|
||||||
} else {
|
""
|
||||||
""
|
};
|
||||||
};
|
format!("{}: {}{}", label, pct, suffix)
|
||||||
format!("{}: {}{}", label, pct, suffix)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let l = format_component("L", self.battery_l, self.battery_l_status);
|
|
||||||
let r = format_component("R", self.battery_r, self.battery_r_status);
|
|
||||||
let c = format_component("C", self.battery_c, self.battery_c_status);
|
|
||||||
|
|
||||||
ToolTip {
|
|
||||||
icon_name: "".to_string(),
|
|
||||||
icon_pixmap: vec![],
|
|
||||||
title: "Battery Status".to_string(),
|
|
||||||
description: format!("{} {} {}", l, r, c),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ToolTip {
|
|
||||||
icon_name: "".to_string(),
|
|
||||||
icon_pixmap: vec![],
|
|
||||||
title: "Not Connected".to_string(),
|
|
||||||
description: "Device is not connected.".to_string(),
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let l = format_component("L", self.battery_l, self.battery_l_status);
|
||||||
|
let r = format_component("R", self.battery_r, self.battery_r_status);
|
||||||
|
let c = format_component("C", self.battery_c, self.battery_c_status);
|
||||||
|
|
||||||
|
ToolTip {
|
||||||
|
icon_name: "".to_string(),
|
||||||
|
icon_pixmap: vec![],
|
||||||
|
title: "Battery Status".to_string(),
|
||||||
|
description: format!("{} {} {}", l, r, c),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
|
fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
|
||||||
|
|||||||
Reference in New Issue
Block a user