mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-28 09:07:14 +00:00
linux-rust: parse single battery of AirPods Max
This commit is contained in:
@@ -215,6 +215,7 @@ pub enum AudioSourceType {
|
|||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum BatteryComponent {
|
pub enum BatteryComponent {
|
||||||
|
Headphone = 1,
|
||||||
Left = 4,
|
Left = 4,
|
||||||
Right = 2,
|
Right = 2,
|
||||||
Case = 8
|
Case = 8
|
||||||
@@ -476,6 +477,7 @@ impl AACPManager {
|
|||||||
let base_index = 3 + i * 5;
|
let base_index = 3 + i * 5;
|
||||||
batteries.push(BatteryInfo {
|
batteries.push(BatteryInfo {
|
||||||
component: match payload[base_index] {
|
component: match payload[base_index] {
|
||||||
|
0x01 => BatteryComponent::Headphone,
|
||||||
0x02 => BatteryComponent::Right,
|
0x02 => BatteryComponent::Right,
|
||||||
0x04 => BatteryComponent::Left,
|
0x04 => BatteryComponent::Left,
|
||||||
0x08 => BatteryComponent::Case,
|
0x08 => BatteryComponent::Case,
|
||||||
|
|||||||
@@ -170,6 +170,10 @@ impl AirPodsDevice {
|
|||||||
handle.update(|tray: &mut MyTray| {
|
handle.update(|tray: &mut MyTray| {
|
||||||
for b in &battery_info {
|
for b in &battery_info {
|
||||||
match b.component as u8 {
|
match b.component as u8 {
|
||||||
|
0x01 => {
|
||||||
|
tray.battery_headphone = Some(b.level);
|
||||||
|
tray.battery_headphone_status = Some(b.status);
|
||||||
|
}
|
||||||
0x02 => {
|
0x02 => {
|
||||||
tray.battery_r = Some(b.level);
|
tray.battery_r = Some(b.level);
|
||||||
tray.battery_r_status = Some(b.status);
|
tray.battery_r_status = Some(b.status);
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ async fn async_main(
|
|||||||
} else {
|
} else {
|
||||||
let tray = MyTray {
|
let tray = MyTray {
|
||||||
conversation_detect_enabled: None,
|
conversation_detect_enabled: None,
|
||||||
|
battery_headphone: None,
|
||||||
|
battery_headphone_status: None,
|
||||||
battery_l: None,
|
battery_l: None,
|
||||||
battery_l_status: None,
|
battery_l_status: None,
|
||||||
battery_r: None,
|
battery_r: None,
|
||||||
|
|||||||
@@ -4,24 +4,26 @@ use ab_glyph::{Font, ScaleFont};
|
|||||||
use ksni::{Icon, ToolTip};
|
use ksni::{Icon, ToolTip};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use crate::bluetooth::aacp::ControlCommandIdentifiers;
|
use crate::bluetooth::aacp::{BatteryStatus, ControlCommandIdentifiers};
|
||||||
use crate::ui::messages::BluetoothUIMessage;
|
use crate::ui::messages::BluetoothUIMessage;
|
||||||
use crate::utils::get_app_settings_path;
|
use crate::utils::get_app_settings_path;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct MyTray {
|
pub struct MyTray {
|
||||||
pub(crate) conversation_detect_enabled: Option<bool>,
|
pub conversation_detect_enabled: Option<bool>,
|
||||||
pub(crate) battery_l: Option<u8>,
|
pub battery_headphone: Option<u8>,
|
||||||
pub(crate) battery_l_status: Option<crate::bluetooth::aacp::BatteryStatus>,
|
pub battery_headphone_status: Option<BatteryStatus>,
|
||||||
pub(crate) battery_r: Option<u8>,
|
pub battery_l: Option<u8>,
|
||||||
pub(crate) battery_r_status: Option<crate::bluetooth::aacp::BatteryStatus>,
|
pub battery_l_status: Option<BatteryStatus>,
|
||||||
pub(crate) battery_c: Option<u8>,
|
pub battery_r: Option<u8>,
|
||||||
pub(crate) battery_c_status: Option<crate::bluetooth::aacp::BatteryStatus>,
|
pub battery_r_status: Option<BatteryStatus>,
|
||||||
pub(crate) connected: bool,
|
pub battery_c: Option<u8>,
|
||||||
pub(crate) listening_mode: Option<u8>,
|
pub battery_c_status: Option<BatteryStatus>,
|
||||||
pub(crate) allow_off_option: Option<u8>,
|
pub connected: bool,
|
||||||
pub(crate) command_tx: Option<UnboundedSender<(ControlCommandIdentifiers, Vec<u8>)>>,
|
pub listening_mode: Option<u8>,
|
||||||
pub(crate) ui_tx: Option<UnboundedSender<BluetoothUIMessage>>,
|
pub allow_off_option: Option<u8>,
|
||||||
|
pub command_tx: Option<UnboundedSender<(ControlCommandIdentifiers, Vec<u8>)>>,
|
||||||
|
pub ui_tx: Option<UnboundedSender<BluetoothUIMessage>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ksni::Tray for MyTray {
|
impl ksni::Tray for MyTray {
|
||||||
@@ -34,21 +36,27 @@ impl ksni::Tray for MyTray {
|
|||||||
fn icon_pixmap(&self) -> Vec<Icon> {
|
fn icon_pixmap(&self) -> Vec<Icon> {
|
||||||
let text = {
|
let text = {
|
||||||
let mut levels: Vec<u8> = Vec::new();
|
let mut levels: Vec<u8> = Vec::new();
|
||||||
|
if let Some(h) = self.battery_headphone {
|
||||||
|
if self.battery_headphone_status != Some(BatteryStatus::Disconnected) {
|
||||||
|
levels.push(h);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
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(BatteryStatus::Disconnected) {
|
||||||
levels.push(l);
|
levels.push(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(r) = self.battery_r {
|
if let Some(r) = self.battery_r {
|
||||||
if self.battery_r_status != Some(crate::bluetooth::aacp::BatteryStatus::Disconnected) {
|
if self.battery_r_status != Some(BatteryStatus::Disconnected) {
|
||||||
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(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)
|
||||||
@@ -56,8 +64,8 @@ impl ksni::Tray for MyTray {
|
|||||||
"?".to_string()
|
"?".to_string()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
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(BatteryStatus::Charging))
|
||||||
|| matches!(self.battery_r_status, Some(crate::bluetooth::aacp::BatteryStatus::Charging));
|
|| matches!(self.battery_r_status, Some(BatteryStatus::Charging));
|
||||||
let app_settings_path = get_app_settings_path();
|
let app_settings_path = get_app_settings_path();
|
||||||
let settings = std::fs::read_to_string(&app_settings_path)
|
let settings = std::fs::read_to_string(&app_settings_path)
|
||||||
.ok()
|
.ok()
|
||||||
@@ -70,12 +78,12 @@ impl ksni::Tray for MyTray {
|
|||||||
vec![icon]
|
vec![icon]
|
||||||
}
|
}
|
||||||
fn tool_tip(&self) -> ToolTip {
|
fn tool_tip(&self) -> ToolTip {
|
||||||
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<BatteryStatus>| -> String {
|
||||||
match status {
|
match status {
|
||||||
Some(crate::bluetooth::aacp::BatteryStatus::Disconnected) => format!("{}: -", label),
|
Some(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(BatteryStatus::Charging) {
|
||||||
"⚡"
|
"⚡"
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
|
|||||||
@@ -610,6 +610,17 @@ impl App {
|
|||||||
match self.device_states.get(mac) {
|
match self.device_states.get(mac) {
|
||||||
Some(DeviceState::AirPods(state)) => {
|
Some(DeviceState::AirPods(state)) => {
|
||||||
let b = &state.battery;
|
let b = &state.battery;
|
||||||
|
let headphone = b.iter().find(|x| x.component == BatteryComponent::Headphone)
|
||||||
|
.map(|x| x.level);
|
||||||
|
// if headphones is not None, use only that
|
||||||
|
if let Some(level) = headphone {
|
||||||
|
let charging = b.iter().find(|x| x.component == BatteryComponent::Headphone)
|
||||||
|
.map(|x| x.status == BatteryStatus::Charging).unwrap_or(false);
|
||||||
|
format!(
|
||||||
|
" {}%{}",
|
||||||
|
level, if charging {"\u{1002E6}"} else {""}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
let left = b.iter().find(|x| x.component == BatteryComponent::Left)
|
let left = b.iter().find(|x| x.component == BatteryComponent::Left)
|
||||||
.map(|x| x.level).unwrap_or_default();
|
.map(|x| x.level).unwrap_or_default();
|
||||||
let right = b.iter().find(|x| x.component == BatteryComponent::Right)
|
let right = b.iter().find(|x| x.component == BatteryComponent::Right)
|
||||||
@@ -627,6 +638,7 @@ impl App {
|
|||||||
left, if left_charging {"\u{1002E6}"} else {""}, right, if right_charging {"\u{1002E6}"} else {""}, case, if case_charging {"\u{1002E6}"} else {""}
|
left, if left_charging {"\u{1002E6}"} else {""}, right, if right_charging {"\u{1002E6}"} else {""}, case, if case_charging {"\u{1002E6}"} else {""}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_ => "Connected".to_string(),
|
_ => "Connected".to_string(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user