mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-02-01 07:39:11 +00:00
linux-rust: add listening mode picker for airpods
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use crate::bluetooth::aacp::{AACPManager, ProximityKeyType, AACPEvent, AirPodsLEKeys};
|
||||
use crate::bluetooth::aacp::ControlCommandIdentifiers;
|
||||
// use crate::bluetooth::att::ATTManager;
|
||||
use crate::bluetooth::att::ATTManager;
|
||||
use crate::media_controller::MediaController;
|
||||
use bluer::Address;
|
||||
use log::{debug, info, error};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::fmt::Display;
|
||||
use iced::widget::{combo_box, ComboBox};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::devices::airpods::AirPodsInformation;
|
||||
use crate::devices::nothing::NothingInformation;
|
||||
@@ -53,8 +54,50 @@ impl Display for DeviceState {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AirPodsState {
|
||||
pub device_name: String,
|
||||
pub noise_control_mode: AirPodsNoiseControlMode,
|
||||
pub noise_control_state: combo_box::State<AirPodsNoiseControlMode>,
|
||||
pub conversation_awareness_enabled: bool,
|
||||
pub personalized_volume_enabled: bool,
|
||||
pub allow_off_mode: bool
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AirPodsNoiseControlMode {
|
||||
Off,
|
||||
NoiseCancellation,
|
||||
Transparency,
|
||||
Adaptive
|
||||
}
|
||||
|
||||
impl Display for AirPodsNoiseControlMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AirPodsNoiseControlMode::Off => write!(f, "Off"),
|
||||
AirPodsNoiseControlMode::NoiseCancellation => write!(f, "Noise Cancellation"),
|
||||
AirPodsNoiseControlMode::Transparency => write!(f, "Transparency"),
|
||||
AirPodsNoiseControlMode::Adaptive => write!(f, "Adaptive"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AirPodsNoiseControlMode {
|
||||
pub fn from_byte(value: &u8) -> Self {
|
||||
match value {
|
||||
0x01 => AirPodsNoiseControlMode::Off,
|
||||
0x02 => AirPodsNoiseControlMode::NoiseCancellation,
|
||||
0x03 => AirPodsNoiseControlMode::Transparency,
|
||||
0x04 => AirPodsNoiseControlMode::Adaptive,
|
||||
_ => AirPodsNoiseControlMode::Off,
|
||||
}
|
||||
}
|
||||
pub fn to_byte(&self) -> u8 {
|
||||
match self {
|
||||
AirPodsNoiseControlMode::Off => 0x01,
|
||||
AirPodsNoiseControlMode::NoiseCancellation => 0x02,
|
||||
AirPodsNoiseControlMode::Transparency => 0x03,
|
||||
AirPodsNoiseControlMode::Adaptive => 0x04,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -123,18 +123,17 @@ async fn async_main(
|
||||
Ok(device) => {
|
||||
let name = device.name().await?.unwrap_or_else(|| "Unknown".to_string());
|
||||
info!("Found connected AirPods: {}, initializing.", name);
|
||||
let ui_tx_clone = ui_tx.clone();
|
||||
ui_tx_clone.send(BluetoothUIMessage::DeviceConnected(device.address().to_string())).unwrap();
|
||||
let airpods_device = AirPodsDevice::new(device.address(), tray_handle.clone(), ui_tx_clone).await;
|
||||
let airpods_device = AirPodsDevice::new(device.address(), tray_handle.clone(), ui_tx.clone()).await;
|
||||
|
||||
let mut managers = device_managers.write().await;
|
||||
// let dev_managers = DeviceManagers::with_both(airpods_device.aacp_manager.clone(), airpods_device.att_manager.clone());
|
||||
let dev_managers = DeviceManagers::with_aacp(airpods_device.aacp_manager.clone());
|
||||
managers
|
||||
.entry(device.address().to_string())
|
||||
.or_insert(dev_managers)
|
||||
.set_aacp(airpods_device.aacp_manager)
|
||||
;
|
||||
drop(managers)
|
||||
.set_aacp(airpods_device.aacp_manager);
|
||||
drop(managers);
|
||||
ui_tx.send(BluetoothUIMessage::DeviceConnected(device.address().to_string())).unwrap();
|
||||
}
|
||||
Err(_) => {
|
||||
info!("No connected AirPods found.");
|
||||
@@ -150,16 +149,16 @@ async fn async_main(
|
||||
let ui_tx_clone = ui_tx.clone();
|
||||
let device_managers = device_managers.clone();
|
||||
tokio::spawn(async move {
|
||||
ui_tx_clone.send(BluetoothUIMessage::DeviceConnected(addr_str.clone())).unwrap();
|
||||
let mut managers = device_managers.write().await;
|
||||
match type_ {
|
||||
devices::enums::DeviceType::Nothing => {
|
||||
let dev = devices::nothing::NothingDevice::new(device.address(), ui_tx_clone).await;
|
||||
let dev = devices::nothing::NothingDevice::new(device.address(), ui_tx_clone.clone()).await;
|
||||
let dev_managers = DeviceManagers::with_att(dev.att_manager.clone());
|
||||
managers
|
||||
.entry(addr_str)
|
||||
.entry(addr_str.clone())
|
||||
.or_insert(dev_managers)
|
||||
.set_att(dev.att_manager);
|
||||
ui_tx_clone.send(BluetoothUIMessage::DeviceConnected(addr_str)).unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -212,14 +211,14 @@ async fn async_main(
|
||||
let device_managers = device_managers.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut managers = device_managers.write().await;
|
||||
ui_tx_clone.send(BluetoothUIMessage::DeviceConnected(addr_str.clone())).unwrap();
|
||||
let dev = devices::nothing::NothingDevice::new(addr, ui_tx_clone).await;
|
||||
let dev = devices::nothing::NothingDevice::new(addr, ui_tx_clone.clone()).await;
|
||||
let dev_managers = DeviceManagers::with_att(dev.att_manager.clone());
|
||||
managers
|
||||
.entry(addr_str)
|
||||
.entry(addr_str.clone())
|
||||
.or_insert(dev_managers)
|
||||
.set_att(dev.att_manager);
|
||||
drop(managers);
|
||||
ui_tx_clone.send(BluetoothUIMessage::DeviceConnected(addr_str.clone())).unwrap();
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
@@ -236,15 +235,16 @@ async fn async_main(
|
||||
let ui_tx_clone = ui_tx.clone();
|
||||
let device_managers = device_managers.clone();
|
||||
tokio::spawn(async move {
|
||||
ui_tx_clone.send(BluetoothUIMessage::DeviceConnected(addr_str.clone())).unwrap();
|
||||
let airpods_device = AirPodsDevice::new(addr, handle_clone, ui_tx_clone).await;
|
||||
let airpods_device = AirPodsDevice::new(addr, handle_clone, ui_tx_clone.clone()).await;
|
||||
let mut managers = device_managers.write().await;
|
||||
// let dev_managers = DeviceManagers::with_both(airpods_device.aacp_manager.clone(), airpods_device.att_manager.clone());
|
||||
let dev_managers = DeviceManagers::with_aacp(airpods_device.aacp_manager.clone());
|
||||
managers
|
||||
.entry(addr_str)
|
||||
.entry(addr_str.clone())
|
||||
.or_insert(dev_managers)
|
||||
.set_aacp(airpods_device.aacp_manager);
|
||||
drop(managers);
|
||||
ui_tx_clone.send(BluetoothUIMessage::DeviceConnected(addr_str.clone())).unwrap();
|
||||
});
|
||||
true
|
||||
})?;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use iced::widget::{button, column, container, row, rule, text, text_input, toggler, Rule, Space};
|
||||
use iced::widget::{button, column, combo_box, container, row, rule, text, text_input, toggler, Rule, Space};
|
||||
use iced::{Background, Border, Center, Color, Length, Padding, Theme};
|
||||
use iced::Alignment::End;
|
||||
use iced::border::Radius;
|
||||
use iced::overlay::menu;
|
||||
use iced::widget::button::Style;
|
||||
use iced::widget::rule::FillMode;
|
||||
use log::error;
|
||||
use tokio::runtime::Runtime;
|
||||
use crate::bluetooth::aacp::{AACPManager, ControlCommandIdentifiers};
|
||||
// use crate::bluetooth::att::ATTManager;
|
||||
use crate::devices::enums::{AirPodsState, DeviceData, DeviceInformation, DeviceState};
|
||||
use crate::ui::window::Message;
|
||||
|
||||
@@ -17,11 +19,11 @@ pub fn airpods_view<'a>(
|
||||
mac: &'a str,
|
||||
devices_list: &HashMap<String, DeviceData>,
|
||||
state: &'a AirPodsState,
|
||||
aacp_manager: Arc<AACPManager>
|
||||
aacp_manager: Arc<AACPManager>,
|
||||
// att_manager: Arc<ATTManager>
|
||||
) -> iced::widget::Container<'a, Message> {
|
||||
|
||||
let mac = mac.to_string();
|
||||
// order: name, noise control, press and hold config, call controls (not sure if why it might be needed, adding it just in case), audio (personalized volume, conversational awareness, adaptive audio slider), connection settings, microphone, head gestures (not adding this), off listening mode, device information
|
||||
|
||||
let aacp_manager_for_rename = aacp_manager.clone();
|
||||
let rename_input = container(
|
||||
row![
|
||||
@@ -57,19 +59,23 @@ pub fn airpods_view<'a>(
|
||||
}
|
||||
)
|
||||
.align_x(End)
|
||||
.on_input( move |new_name| {
|
||||
let aacp_manager = aacp_manager_for_rename.clone();
|
||||
run_async_in_thread(
|
||||
{
|
||||
let new_name = new_name.clone();
|
||||
async move {
|
||||
aacp_manager.send_rename_packet(&new_name).await.expect("Failed to send rename packet");
|
||||
.on_input({
|
||||
let mac = mac.clone();
|
||||
let state = state.clone();
|
||||
move|new_name| {
|
||||
let aacp_manager = aacp_manager_for_rename.clone();
|
||||
run_async_in_thread(
|
||||
{
|
||||
let new_name = new_name.clone();
|
||||
async move {
|
||||
aacp_manager.send_rename_packet(&new_name).await.expect("Failed to send rename packet");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
let mut state = state.clone();
|
||||
state.device_name = new_name.clone();
|
||||
Message::StateChanged(mac.to_string(), DeviceState::AirPods(state))
|
||||
);
|
||||
let mut state = state.clone();
|
||||
state.device_name = new_name.clone();
|
||||
Message::StateChanged(mac.to_string(), DeviceState::AirPods(state))
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
@@ -92,6 +98,104 @@ pub fn airpods_view<'a>(
|
||||
}
|
||||
);
|
||||
|
||||
let listening_mode = container(row![
|
||||
text("Listening Mode").size(16).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().text);
|
||||
style
|
||||
}
|
||||
),
|
||||
Space::with_width(Length::Fill),
|
||||
{
|
||||
let state_clone = state.clone();
|
||||
let mac = mac.clone();
|
||||
// this combo_box doesn't go really well with the design, but I am not writing my own dropdown menu for this
|
||||
combo_box(
|
||||
&state.noise_control_state,
|
||||
"Select Listening Mode",
|
||||
Some(&state.noise_control_mode.clone()),
|
||||
{
|
||||
let aacp_manager = aacp_manager.clone();
|
||||
move |selected_mode| {
|
||||
let aacp_manager = aacp_manager.clone();
|
||||
let selected_mode_c = selected_mode.clone();
|
||||
run_async_in_thread(
|
||||
async move {
|
||||
aacp_manager.send_control_command(
|
||||
ControlCommandIdentifiers::ListeningMode,
|
||||
&[selected_mode_c.to_byte()]
|
||||
).await.expect("Failed to send Noise Control Mode command");
|
||||
}
|
||||
);
|
||||
let mut state = state_clone.clone();
|
||||
state.noise_control_mode = selected_mode.clone();
|
||||
Message::StateChanged(mac.to_string(), DeviceState::AirPods(state))
|
||||
}
|
||||
}
|
||||
)
|
||||
.width(Length::from(200))
|
||||
.input_style(
|
||||
|theme: &Theme, _status| {
|
||||
text_input::Style {
|
||||
background: Background::Color(theme.palette().primary.scale_alpha(0.2)),
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
color: theme.palette().text.scale_alpha(0.3),
|
||||
radius: Radius::from(4.0)
|
||||
},
|
||||
icon: Default::default(),
|
||||
placeholder: theme.palette().text,
|
||||
value: theme.palette().text,
|
||||
selection: Default::default(),
|
||||
}
|
||||
}
|
||||
)
|
||||
.padding(Padding{
|
||||
top: 5.0,
|
||||
bottom: 5.0,
|
||||
left: 10.0,
|
||||
right: 10.0,
|
||||
})
|
||||
.menu_style(
|
||||
|theme: &Theme| {
|
||||
menu::Style {
|
||||
background: Background::Color(theme.palette().background),
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
color: theme.palette().text,
|
||||
radius: Radius::from(4.0)
|
||||
},
|
||||
text_color: theme.palette().text,
|
||||
selected_text_color: theme.palette().text,
|
||||
selected_background: Background::Color(theme.palette().primary.scale_alpha(0.3)),
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
]
|
||||
.align_y(Center)
|
||||
)
|
||||
.padding(Padding{
|
||||
top: 5.0,
|
||||
bottom: 5.0,
|
||||
left: 18.0,
|
||||
right: 18.0,
|
||||
})
|
||||
.style(
|
||||
|theme: &Theme| {
|
||||
let mut style = container::Style::default();
|
||||
style.background = Some(Background::Color(theme.palette().primary.scale_alpha(0.1)));
|
||||
let mut border = Border::default();
|
||||
border.color = theme.palette().primary.scale_alpha(0.5);
|
||||
style.border = border.rounded(16);
|
||||
style
|
||||
}
|
||||
);
|
||||
|
||||
let mac_audio = mac.clone();
|
||||
let mac_information = mac.clone();
|
||||
|
||||
let audio_settings_col = column![
|
||||
container(
|
||||
text("Audio Settings").size(18).style(
|
||||
@@ -126,20 +230,27 @@ pub fn airpods_view<'a>(
|
||||
],
|
||||
Space::with_width(Length::Fill),
|
||||
toggler(state.personalized_volume_enabled)
|
||||
.on_toggle(move |is_enabled| {
|
||||
let aacp_manager = aacp_manager_pv.clone();
|
||||
run_async_in_thread(
|
||||
async move {
|
||||
aacp_manager.send_control_command(
|
||||
ControlCommandIdentifiers::AdaptiveVolumeConfig,
|
||||
if is_enabled { &[0x01] } else { &[0x02] }
|
||||
).await.expect("Failed to send Personalized Volume command");
|
||||
}
|
||||
);
|
||||
let mut state = state.clone();
|
||||
state.personalized_volume_enabled = is_enabled;
|
||||
Message::StateChanged(mac.to_string(), DeviceState::AirPods(state))
|
||||
})
|
||||
.on_toggle(
|
||||
{
|
||||
let mac = mac_audio.clone();
|
||||
let state = state.clone();
|
||||
move |is_enabled| {
|
||||
let aacp_manager = aacp_manager_pv.clone();
|
||||
let mac = mac.clone();
|
||||
run_async_in_thread(
|
||||
async move {
|
||||
aacp_manager.send_control_command(
|
||||
ControlCommandIdentifiers::AdaptiveVolumeConfig,
|
||||
if is_enabled { &[0x01] } else { &[0x02] }
|
||||
).await.expect("Failed to send Personalized Volume command");
|
||||
}
|
||||
);
|
||||
let mut state = state.clone();
|
||||
state.personalized_volume_enabled = is_enabled;
|
||||
Message::StateChanged(mac, DeviceState::AirPods(state))
|
||||
}
|
||||
}
|
||||
)
|
||||
.spacing(0)
|
||||
.size(20)
|
||||
]
|
||||
@@ -182,7 +293,7 @@ pub fn airpods_view<'a>(
|
||||
);
|
||||
let mut state = state.clone();
|
||||
state.conversation_awareness_enabled = is_enabled;
|
||||
Message::StateChanged(mac.to_string(), DeviceState::AirPods(state))
|
||||
Message::StateChanged(mac_audio.to_string(), DeviceState::AirPods(state))
|
||||
})
|
||||
.spacing(0)
|
||||
.size(20)
|
||||
@@ -211,9 +322,61 @@ pub fn airpods_view<'a>(
|
||||
)
|
||||
];
|
||||
|
||||
let off_listening_mode_toggle = {
|
||||
let aacp_manager_olm = aacp_manager.clone();
|
||||
let mac = mac.clone();
|
||||
container(row![
|
||||
column![
|
||||
text("Off Listening Mode").size(16),
|
||||
text("When this is on, AIrPods listening modes will include an Off option. Loud sound levels are not reduced when listening mode is set to Off.").size(12).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().text.scale_alpha(0.7));
|
||||
style
|
||||
}
|
||||
)
|
||||
],
|
||||
Space::with_width(Length::Fill),
|
||||
toggler(state.allow_off_mode)
|
||||
.on_toggle(move |is_enabled| {
|
||||
let aacp_manager = aacp_manager_olm.clone();
|
||||
run_async_in_thread(
|
||||
async move {
|
||||
aacp_manager.send_control_command(
|
||||
ControlCommandIdentifiers::AllowOffOption,
|
||||
if is_enabled { &[0x01] } else { &[0x02] }
|
||||
).await.expect("Failed to send Off Listening Mode command");
|
||||
}
|
||||
);
|
||||
let mut state = state.clone();
|
||||
state.allow_off_mode = is_enabled;
|
||||
Message::StateChanged(mac.to_string(), DeviceState::AirPods(state))
|
||||
})
|
||||
.spacing(0)
|
||||
.size(20)
|
||||
]
|
||||
.align_y(Center)
|
||||
)
|
||||
.padding(Padding{
|
||||
top: 5.0,
|
||||
bottom: 5.0,
|
||||
left: 18.0,
|
||||
right: 18.0,
|
||||
})
|
||||
.style(
|
||||
|theme: &Theme| {
|
||||
let mut style = container::Style::default();
|
||||
style.background = Some(Background::Color(theme.palette().primary.scale_alpha(0.1)));
|
||||
let mut border = Border::default();
|
||||
border.color = theme.palette().primary.scale_alpha(0.5);
|
||||
style.border = border.rounded(16);
|
||||
style
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let mut information_col = column![];
|
||||
let mac = mac.to_string();
|
||||
if let Some(device) = devices_list.get(mac.as_str()) {
|
||||
if let Some(device) = devices_list.get(mac_information.as_str()) {
|
||||
if let Some(DeviceInformation::AirPods(ref airpods_info)) = device.information {
|
||||
let info_rows = column![
|
||||
row![
|
||||
@@ -378,7 +541,7 @@ pub fn airpods_view<'a>(
|
||||
)
|
||||
];
|
||||
} else {
|
||||
error!("Expected AirPodsInformation for device {}, got something else", mac);
|
||||
error!("Expected AirPodsInformation for device {}, got something else", mac.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,8 +549,12 @@ pub fn airpods_view<'a>(
|
||||
column![
|
||||
rename_input,
|
||||
Space::with_height(Length::from(20)),
|
||||
listening_mode,
|
||||
Space::with_height(Length::from(20)),
|
||||
audio_settings_col,
|
||||
Space::with_height(Length::from(10)),
|
||||
Space::with_height(Length::from(20)),
|
||||
off_listening_mode_toggle,
|
||||
Space::with_height(Length::from(20)),
|
||||
information_col
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use iced::{Background, Border, Length, Theme};
|
||||
use iced::widget::{container, text, column, row, Space, combo_box};
|
||||
use iced::widget::{container, text, column, row, Space};
|
||||
use crate::bluetooth::att::ATTManager;
|
||||
use crate::devices::enums::{DeviceData, DeviceInformation, NothingState};
|
||||
use crate::ui::window::Message;
|
||||
|
||||
@@ -12,7 +12,7 @@ use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use crate::bluetooth::aacp::{AACPEvent, ControlCommandIdentifiers};
|
||||
use crate::bluetooth::managers::DeviceManagers;
|
||||
use crate::devices::enums::{AirPodsState, DeviceData, DeviceState, DeviceType, NothingAncMode, NothingState};
|
||||
use crate::devices::enums::{AirPodsNoiseControlMode, AirPodsState, DeviceData, DeviceState, DeviceType, NothingAncMode, NothingState};
|
||||
use crate::ui::messages::BluetoothUIMessage;
|
||||
use crate::utils::{get_devices_path, get_app_settings_path, MyTheme};
|
||||
use crate::ui::airpods::airpods_view;
|
||||
@@ -301,6 +301,29 @@ impl App {
|
||||
};
|
||||
self.device_states.insert(mac.clone(), DeviceState::AirPods(AirPodsState {
|
||||
device_name,
|
||||
noise_control_mode: state.control_command_status_list.iter().find_map(|status| {
|
||||
if status.identifier == ControlCommandIdentifiers::ListeningMode {
|
||||
status.value.get(0).map(|b| AirPodsNoiseControlMode::from_byte(b))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).unwrap_or(AirPodsNoiseControlMode::Transparency),
|
||||
noise_control_state: combo_box::State::new(
|
||||
{
|
||||
let mut modes = vec![
|
||||
AirPodsNoiseControlMode::Transparency,
|
||||
AirPodsNoiseControlMode::NoiseCancellation,
|
||||
AirPodsNoiseControlMode::Adaptive
|
||||
];
|
||||
if state.control_command_status_list.iter().any(|status| {
|
||||
status.identifier == ControlCommandIdentifiers::AllowOffOption &&
|
||||
matches!(status.value.as_slice(), [0x01])
|
||||
}) {
|
||||
modes.insert(0, AirPodsNoiseControlMode::Off);
|
||||
}
|
||||
modes
|
||||
}
|
||||
),
|
||||
conversation_awareness_enabled: state.control_command_status_list.iter().any(|status| {
|
||||
status.identifier == ControlCommandIdentifiers::ConversationDetectConfig &&
|
||||
matches!(status.value.as_slice(), [0x01])
|
||||
@@ -309,6 +332,10 @@ impl App {
|
||||
status.identifier == ControlCommandIdentifiers::AdaptiveVolumeConfig &&
|
||||
matches!(status.value.as_slice(), [0x01])
|
||||
}),
|
||||
allow_off_mode: state.control_command_status_list.iter().any(|status| {
|
||||
status.identifier == ControlCommandIdentifiers::AllowOffOption &&
|
||||
matches!(status.value.as_slice(), [0x01])
|
||||
}),
|
||||
}));
|
||||
}
|
||||
Some(DeviceType::Nothing) => {
|
||||
@@ -346,6 +373,12 @@ impl App {
|
||||
match event {
|
||||
AACPEvent::ControlCommand(status) => {
|
||||
match status.identifier {
|
||||
ControlCommandIdentifiers::ListeningMode => {
|
||||
let mode = status.value.get(0).map(|b| AirPodsNoiseControlMode::from_byte(b)).unwrap_or(AirPodsNoiseControlMode::Transparency);
|
||||
if let Some(DeviceState::AirPods(state)) = self.device_states.get_mut(&mac) {
|
||||
state.noise_control_mode = mode;
|
||||
}
|
||||
}
|
||||
ControlCommandIdentifiers::ConversationDetectConfig => {
|
||||
let is_enabled = match status.value.as_slice() {
|
||||
[0x01] => true,
|
||||
@@ -372,6 +405,32 @@ impl App {
|
||||
state.personalized_volume_enabled = is_enabled;
|
||||
}
|
||||
}
|
||||
ControlCommandIdentifiers::AllowOffOption => {
|
||||
let is_enabled = match status.value.as_slice() {
|
||||
[0x01] => true,
|
||||
[0x02] => false,
|
||||
_ => {
|
||||
error!("Unknown Allow Off Option value: {:?}", status.value);
|
||||
false
|
||||
}
|
||||
};
|
||||
if let Some(DeviceState::AirPods(state)) = self.device_states.get_mut(&mac) {
|
||||
state.allow_off_mode = is_enabled;
|
||||
state.noise_control_state = combo_box::State::new(
|
||||
{
|
||||
let mut modes = vec![
|
||||
AirPodsNoiseControlMode::Transparency,
|
||||
AirPodsNoiseControlMode::NoiseCancellation,
|
||||
AirPodsNoiseControlMode::Adaptive
|
||||
];
|
||||
if is_enabled {
|
||||
modes.insert(0, AirPodsNoiseControlMode::Off);
|
||||
}
|
||||
modes
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug!("Unhandled Control Command Status: {:?}", status);
|
||||
}
|
||||
@@ -449,7 +508,39 @@ impl App {
|
||||
Task::none()
|
||||
}
|
||||
Message::StateChanged(mac, state) => {
|
||||
self.device_states.insert(mac, state);
|
||||
self.device_states.insert(mac.clone(), state);
|
||||
// if airpods, update the noise control state combo box based on allow off mode
|
||||
let type_ = {
|
||||
let devices_json = std::fs::read_to_string(get_devices_path()).unwrap_or_else(|e| {
|
||||
error!("Failed to read devices file: {}", e);
|
||||
"{}".to_string()
|
||||
});
|
||||
let devices_list: HashMap<String, DeviceData> = serde_json::from_str(&devices_json).unwrap_or_else(|e| {
|
||||
error!("Deserialization failed: {}", e);
|
||||
HashMap::new()
|
||||
});
|
||||
devices_list.get(&mac).map(|d| d.type_.clone())
|
||||
};
|
||||
match type_ {
|
||||
Some(DeviceType::AirPods) => {
|
||||
if let Some(DeviceState::AirPods(state)) = self.device_states.get_mut(&mac) {
|
||||
state.noise_control_state = combo_box::State::new(
|
||||
{
|
||||
let mut modes = vec![
|
||||
AirPodsNoiseControlMode::Transparency,
|
||||
AirPodsNoiseControlMode::NoiseCancellation,
|
||||
AirPodsNoiseControlMode::Adaptive
|
||||
];
|
||||
if state.allow_off_mode {
|
||||
modes.insert(0, AirPodsNoiseControlMode::Off);
|
||||
}
|
||||
modes
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
@@ -622,33 +713,34 @@ impl App {
|
||||
debug!("Rendering device view for {}: type={:?}, state={:?}", id, device_type, device_state);
|
||||
match device_type {
|
||||
Some(DeviceType::AirPods) => {
|
||||
if let Some(DeviceState::AirPods(state)) = device_state {
|
||||
if let Some(device_managers) = device_managers.get(id) {
|
||||
if let Some(aacp_manager) = device_managers.get_aacp() {
|
||||
airpods_view(id, &devices_list, state, aacp_manager.clone())
|
||||
} else {
|
||||
error!("No AACP manager found for AirPods device {}", id);
|
||||
container(
|
||||
text("No valid AACP manager found for this AirPods device").size(16)
|
||||
)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
let view = device_state.as_ref().and_then(|state| {
|
||||
match state {
|
||||
DeviceState::AirPods(state) => {
|
||||
device_managers.get(id).and_then(|managers| {
|
||||
managers.get_aacp().and_then(|aacp_manager| {
|
||||
// managers.get_att().map(|att_manager| {
|
||||
Some(airpods_view(
|
||||
id,
|
||||
&devices_list,
|
||||
state,
|
||||
aacp_manager.clone()
|
||||
),
|
||||
// att_manager.clone(),
|
||||
)
|
||||
// })
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
error!("No manager found for AirPods device {}", id);
|
||||
container(
|
||||
text("No manager found for this AirPods device").size(16)
|
||||
)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
}).unwrap_or_else(|| {
|
||||
container(
|
||||
text("No state available for this AirPods device").size(16)
|
||||
text("Required managers or state not available for this AirPods device").size(16)
|
||||
)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
}
|
||||
});
|
||||
view
|
||||
}
|
||||
Some(DeviceType::Nothing) => {
|
||||
if let Some(DeviceState::Nothing(state)) = device_state {
|
||||
@@ -725,7 +817,7 @@ impl App {
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
color: theme.palette().text,
|
||||
radius: Radius::from(8.0)
|
||||
radius: Radius::from(4.0)
|
||||
},
|
||||
text_color: theme.palette().text,
|
||||
selected_text_color: theme.palette().text,
|
||||
|
||||
Reference in New Issue
Block a user