mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-29 01:29:49 +00:00
linux-rust: handle disconnections and fix rename
This commit is contained in:
@@ -28,7 +28,7 @@ pub mod opcodes {
|
|||||||
pub const EAR_DETECTION: u8 = 0x06;
|
pub const EAR_DETECTION: u8 = 0x06;
|
||||||
pub const CONVERSATION_AWARENESS: u8 = 0x4B;
|
pub const CONVERSATION_AWARENESS: u8 = 0x4B;
|
||||||
pub const INFORMATION: u8 = 0x1D;
|
pub const INFORMATION: u8 = 0x1D;
|
||||||
pub const RENAME: u8 = 0x1E;
|
pub const RENAME: u8 = 0x1A;
|
||||||
pub const PROXIMITY_KEYS_REQ: u8 = 0x30;
|
pub const PROXIMITY_KEYS_REQ: u8 = 0x30;
|
||||||
pub const PROXIMITY_KEYS_RSP: u8 = 0x31;
|
pub const PROXIMITY_KEYS_RSP: u8 = 0x31;
|
||||||
pub const STEM_PRESS: u8 = 0x19;
|
pub const STEM_PRESS: u8 = 0x19;
|
||||||
@@ -959,9 +959,10 @@ impl AACPManager {
|
|||||||
pub async fn send_rename_packet(&self, name: &str) -> Result<()> {
|
pub async fn send_rename_packet(&self, name: &str) -> Result<()> {
|
||||||
let name_bytes = name.as_bytes();
|
let name_bytes = name.as_bytes();
|
||||||
let size = name_bytes.len();
|
let size = name_bytes.len();
|
||||||
let mut packet = Vec::with_capacity(5 + size);
|
let mut packet = Vec::with_capacity(6 + size);
|
||||||
packet.push(opcodes::RENAME);
|
packet.push(opcodes::RENAME);
|
||||||
packet.push(0x00);
|
packet.push(0x00);
|
||||||
|
packet.push(0x01);
|
||||||
packet.push(size as u8);
|
packet.push(size as u8);
|
||||||
packet.push(0x00);
|
packet.push(0x00);
|
||||||
packet.extend_from_slice(name_bytes);
|
packet.extend_from_slice(name_bytes);
|
||||||
@@ -1215,8 +1216,8 @@ async fn recv_thread(manager: AACPManager, sp: Arc<SeqPacket>) {
|
|||||||
manager.receive_packet(data).await;
|
manager.receive_packet(data).await;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Read error: {}", e);
|
debug!("Read error: {}", e);
|
||||||
debug!(
|
info!(
|
||||||
"We have probably disconnected, clearing state variables (owns=false, connected_devices=empty, control_command_status_list=empty)."
|
"We have probably disconnected, clearing state variables (owns=false, connected_devices=empty, control_command_status_list=empty)."
|
||||||
);
|
);
|
||||||
let mut state = manager.state.lock().await;
|
let mut state = manager.state.lock().await;
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ use ksni::Handle;
|
|||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::time::{Duration, sleep};
|
use tokio::time::{Duration, sleep};
|
||||||
|
use crate::utils::get_app_settings_path;
|
||||||
|
|
||||||
pub struct AirPodsDevice {
|
pub struct AirPodsDevice {
|
||||||
pub mac_address: Address,
|
pub mac_address: Address,
|
||||||
@@ -25,7 +25,6 @@ impl AirPodsDevice {
|
|||||||
mac_address: Address,
|
mac_address: Address,
|
||||||
tray_handle: Option<Handle<MyTray>>,
|
tray_handle: Option<Handle<MyTray>>,
|
||||||
ui_tx: tokio::sync::mpsc::UnboundedSender<BluetoothUIMessage>,
|
ui_tx: tokio::sync::mpsc::UnboundedSender<BluetoothUIMessage>,
|
||||||
stem_control: Arc<AtomicBool>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
info!("Creating new AirPodsDevice for {}", mac_address);
|
info!("Creating new AirPodsDevice for {}", mac_address);
|
||||||
let mut aacp_manager = AACPManager::new();
|
let mut aacp_manager = AACPManager::new();
|
||||||
@@ -82,7 +81,17 @@ impl AirPodsDevice {
|
|||||||
error!("Failed to request proximity keys: {}", e);
|
error!("Failed to request proximity keys: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if stem_control.load(Ordering::Relaxed) {
|
let app_settings_path = get_app_settings_path();
|
||||||
|
let settings = std::fs::read_to_string(&app_settings_path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok());
|
||||||
|
let stem_control = settings
|
||||||
|
.clone()
|
||||||
|
.and_then(|v| v.get("stem_control").cloned())
|
||||||
|
.and_then(|s| serde_json::from_value(s).ok())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if stem_control {
|
||||||
// Enable stem press detection (double and triple tap)
|
// Enable stem press detection (double and triple tap)
|
||||||
// StemConfig bitmask for the control command: single=0x01, double=0x02, triple=0x04, long=0x08
|
// StemConfig bitmask for the control command: single=0x01, double=0x02, triple=0x04, long=0x08
|
||||||
// We want double and triple: 0x02 | 0x04 = 0x06
|
// We want double and triple: 0x02 | 0x04 = 0x06
|
||||||
@@ -222,7 +231,6 @@ impl AirPodsDevice {
|
|||||||
let local_mac_events = local_mac.clone();
|
let local_mac_events = local_mac.clone();
|
||||||
let ui_tx_clone = ui_tx.clone();
|
let ui_tx_clone = ui_tx.clone();
|
||||||
let command_tx_clone = command_tx.clone();
|
let command_tx_clone = command_tx.clone();
|
||||||
let stem_control_clone = stem_control.clone();
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
let event_clone = event.clone();
|
let event_clone = event.clone();
|
||||||
@@ -348,7 +356,7 @@ impl AirPodsDevice {
|
|||||||
"Received Stem Press: {:?} on {:?}",
|
"Received Stem Press: {:?} on {:?}",
|
||||||
press_type, bud_type
|
press_type, bud_type
|
||||||
);
|
);
|
||||||
if stem_control_clone.load(Ordering::Relaxed) {
|
if stem_control {
|
||||||
let controller = mc_clone.lock().await;
|
let controller = mc_clone.lock().await;
|
||||||
match press_type {
|
match press_type {
|
||||||
StemPressType::DoublePress => {
|
StemPressType::DoublePress => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use dbus::blocking::stdintf::org_freedesktop_dbus::Properties;
|
|||||||
use dbus::message::MatchRule;
|
use dbus::message::MatchRule;
|
||||||
use devices::airpods::AirPodsDevice;
|
use devices::airpods::AirPodsDevice;
|
||||||
use ksni::TrayMethods;
|
use ksni::TrayMethods;
|
||||||
use log::{info, warn};
|
use log::{debug, info, warn};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::sync::atomic::{AtomicBool};
|
use std::sync::atomic::{AtomicBool};
|
||||||
@@ -44,12 +44,7 @@ struct Args {
|
|||||||
)]
|
)]
|
||||||
le_debug: bool,
|
le_debug: bool,
|
||||||
#[arg(long, short = 'v', help = "Show application version and exit")]
|
#[arg(long, short = 'v', help = "Show application version and exit")]
|
||||||
version: bool,
|
version: bool
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
help = "Disable stem press track control (use this if your environment already handles AirPods AVRCP commands natively)"
|
|
||||||
)]
|
|
||||||
no_stem_control: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
@@ -88,40 +83,28 @@ fn main() -> iced::Result {
|
|||||||
Arc::new(RwLock::new(HashMap::new()));
|
Arc::new(RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
// Load stem_control initial value from settings JSON, then apply CLI override.
|
// Load stem_control initial value from settings JSON, then apply CLI override.
|
||||||
let app_settings_path = get_app_settings_path();
|
|
||||||
let saved_stem_control = std::fs::read_to_string(&app_settings_path)
|
|
||||||
.ok()
|
|
||||||
.and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
|
|
||||||
.and_then(|v| v.get("stem_control").and_then(|b| b.as_bool()))
|
|
||||||
.unwrap_or(true);
|
|
||||||
// CLI --no-stem-control overrides the saved setting.
|
|
||||||
let stem_control_initial = if args.no_stem_control { false } else { saved_stem_control };
|
|
||||||
let stem_control: Arc<AtomicBool> = Arc::new(AtomicBool::new(stem_control_initial));
|
|
||||||
|
|
||||||
if args.no_tray {
|
if args.no_tray {
|
||||||
// Run headless without UI
|
// Run headless without UI
|
||||||
info!("Running in headless mode (no GUI)");
|
info!("Running in headless mode (no GUI)");
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
rt.block_on(async_main(ui_tx, device_managers, stem_control)).unwrap();
|
rt.block_on(async_main(ui_tx, device_managers)).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
// Run with UI
|
// Run with UI
|
||||||
let device_managers_clone = device_managers.clone();
|
let device_managers_clone = device_managers.clone();
|
||||||
let stem_control_clone = stem_control.clone();
|
|
||||||
std::thread::spawn(|| {
|
std::thread::spawn(|| {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
rt.block_on(async_main(ui_tx, device_managers_clone, stem_control_clone))
|
rt.block_on(async_main(ui_tx, device_managers_clone))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
ui::window::start_ui(ui_rx, args.start_minimized, device_managers, stem_control)
|
ui::window::start_ui(ui_rx, args.start_minimized, device_managers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn async_main(
|
async fn async_main(
|
||||||
ui_tx: tokio::sync::mpsc::UnboundedSender<BluetoothUIMessage>,
|
ui_tx: tokio::sync::mpsc::UnboundedSender<BluetoothUIMessage>,
|
||||||
device_managers: Arc<RwLock<HashMap<String, DeviceManagers>>>,
|
device_managers: Arc<RwLock<HashMap<String, DeviceManagers>>>,
|
||||||
stem_control: Arc<AtomicBool>,
|
|
||||||
) -> bluer::Result<()> {
|
) -> bluer::Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
@@ -189,7 +172,7 @@ async fn async_main(
|
|||||||
.unwrap_or_else(|| "Unknown".to_string());
|
.unwrap_or_else(|| "Unknown".to_string());
|
||||||
info!("Found connected AirPods: {}, initializing.", name);
|
info!("Found connected AirPods: {}, initializing.", name);
|
||||||
let airpods_device =
|
let airpods_device =
|
||||||
AirPodsDevice::new(device.address(), tray_handle.clone(), ui_tx.clone(), stem_control.clone()).await;
|
AirPodsDevice::new(device.address(), tray_handle.clone(), ui_tx.clone()).await;
|
||||||
|
|
||||||
let mut managers = device_managers.write().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_both(airpods_device.aacp_manager.clone(), airpods_device.att_manager.clone());
|
||||||
@@ -278,9 +261,6 @@ async fn async_main(
|
|||||||
let Some(is_connected) = connected_var.0.as_ref().as_u64() else {
|
let Some(is_connected) = connected_var.0.as_ref().as_u64() else {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if is_connected == 0 {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
let proxy = conn.with_proxy("org.bluez", path, std::time::Duration::from_millis(5000));
|
let proxy = conn.with_proxy("org.bluez", path, std::time::Duration::from_millis(5000));
|
||||||
let Ok(uuids) = proxy.get::<Vec<String>>("org.bluez.Device1", "UUIDs") else {
|
let Ok(uuids) = proxy.get::<Vec<String>>("org.bluez.Device1", "UUIDs") else {
|
||||||
return true;
|
return true;
|
||||||
@@ -293,7 +273,12 @@ async fn async_main(
|
|||||||
let Ok(addr) = addr_str.parse::<Address>() else {
|
let Ok(addr) = addr_str.parse::<Address>() else {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
if is_connected==0 {
|
||||||
|
if let Err(e) = ui_tx.send(BluetoothUIMessage::DeviceDisconnected(addr_str.clone())) {
|
||||||
|
warn!("Failed to send DeviceConnected UI message: {:?}", e);
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
if managed_devices_mac.contains(&addr_str) {
|
if managed_devices_mac.contains(&addr_str) {
|
||||||
info!("Managed device connected: {}, initializing", addr_str);
|
info!("Managed device connected: {}, initializing", addr_str);
|
||||||
let type_ = devices_list.get(&addr_str).unwrap().type_.clone();
|
let type_ = devices_list.get(&addr_str).unwrap().type_.clone();
|
||||||
@@ -327,9 +312,8 @@ async fn async_main(
|
|||||||
let handle_clone = tray_handle.clone();
|
let handle_clone = tray_handle.clone();
|
||||||
let ui_tx_clone = ui_tx.clone();
|
let ui_tx_clone = ui_tx.clone();
|
||||||
let device_managers = device_managers.clone();
|
let device_managers = device_managers.clone();
|
||||||
let stem_control_arc = stem_control.clone();
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let airpods_device = AirPodsDevice::new(addr, handle_clone, ui_tx_clone.clone(), stem_control_arc.clone()).await;
|
let airpods_device = AirPodsDevice::new(addr, handle_clone, ui_tx_clone.clone()).await;
|
||||||
let mut managers = device_managers.write().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_both(airpods_device.aacp_manager.clone(), airpods_device.att_manager.clone());
|
||||||
let dev_managers = DeviceManagers::with_aacp(airpods_device.aacp_manager.clone());
|
let dev_managers = DeviceManagers::with_aacp(airpods_device.aacp_manager.clone());
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::ui::airpods::airpods_view;
|
|||||||
use crate::ui::messages::BluetoothUIMessage;
|
use crate::ui::messages::BluetoothUIMessage;
|
||||||
use crate::ui::nothing::nothing_view;
|
use crate::ui::nothing::nothing_view;
|
||||||
use crate::utils::{MyTheme, get_app_settings_path, get_devices_path};
|
use crate::utils::{MyTheme, get_app_settings_path, get_devices_path};
|
||||||
use bluer::{Address, Session};
|
use bluer::{Address};
|
||||||
use iced::border::Radius;
|
use iced::border::Radius;
|
||||||
use iced::overlay::menu;
|
use iced::overlay::menu;
|
||||||
use iced::widget::button::Style;
|
use iced::widget::button::Style;
|
||||||
@@ -19,7 +19,7 @@ use iced::widget::{
|
|||||||
Space, button, column, combo_box, container, pane_grid, row, rule, scrollable, text,
|
Space, button, column, combo_box, container, pane_grid, row, rule, scrollable, text,
|
||||||
text_input, toggler
|
text_input, toggler
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Center, Element, Font, Length, Padding, Size, Subscription, Task, Theme, daemon, window, Settings};
|
use iced::{Background, Border, Center, Element, Font, Length, Padding, Size, Subscription, Task, Theme, daemon, window, Settings, Program};
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
@@ -31,7 +31,7 @@ pub fn start_ui(
|
|||||||
ui_rx: UnboundedReceiver<BluetoothUIMessage>,
|
ui_rx: UnboundedReceiver<BluetoothUIMessage>,
|
||||||
start_minimized: bool,
|
start_minimized: bool,
|
||||||
device_managers: Arc<RwLock<HashMap<String, DeviceManagers>>>,
|
device_managers: Arc<RwLock<HashMap<String, DeviceManagers>>>,
|
||||||
stem_control: Arc<AtomicBool>,
|
// stem_control: Arc<AtomicBool>,
|
||||||
) -> iced::Result {
|
) -> iced::Result {
|
||||||
let ui_rx = Arc::new(Mutex::new(ui_rx));
|
let ui_rx = Arc::new(Mutex::new(ui_rx));
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ pub fn start_ui(
|
|||||||
Arc::clone(&ui_rx),
|
Arc::clone(&ui_rx),
|
||||||
start_minimized,
|
start_minimized,
|
||||||
Arc::clone(&device_managers),
|
Arc::clone(&device_managers),
|
||||||
Arc::clone(&stem_control),
|
// Arc::clone(&stem_control),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
App::update,
|
App::update,
|
||||||
@@ -50,6 +50,7 @@ pub fn start_ui(
|
|||||||
)
|
)
|
||||||
.subscription(App::subscription)
|
.subscription(App::subscription)
|
||||||
.theme(App::theme)
|
.theme(App::theme)
|
||||||
|
.title(App::title)
|
||||||
.settings(Settings {
|
.settings(Settings {
|
||||||
id: Some("librepods".to_string()),
|
id: Some("librepods".to_string()),
|
||||||
fonts: vec![include_bytes!("../../assets/font/sf_pro.otf").as_slice().into()],
|
fonts: vec![include_bytes!("../../assets/font/sf_pro.otf").as_slice().into()],
|
||||||
@@ -74,7 +75,7 @@ pub struct App {
|
|||||||
device_type_state: combo_box::State<DeviceType>,
|
device_type_state: combo_box::State<DeviceType>,
|
||||||
selected_device_type: Option<DeviceType>,
|
selected_device_type: Option<DeviceType>,
|
||||||
tray_text_mode: bool,
|
tray_text_mode: bool,
|
||||||
stem_control: Arc<AtomicBool>,
|
stem_control: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BluetoothState {
|
pub struct BluetoothState {
|
||||||
@@ -98,7 +99,7 @@ pub enum Message {
|
|||||||
ThemeSelected(MyTheme),
|
ThemeSelected(MyTheme),
|
||||||
CopyToClipboard(String),
|
CopyToClipboard(String),
|
||||||
BluetoothMessage(BluetoothUIMessage),
|
BluetoothMessage(BluetoothUIMessage),
|
||||||
ShowNewDialogTab,
|
// ShowNewDialogTab,
|
||||||
GotPairedDevices(HashMap<String, Address>),
|
GotPairedDevices(HashMap<String, Address>),
|
||||||
StartAddDevice(String, Address),
|
StartAddDevice(String, Address),
|
||||||
SelectDeviceType(DeviceType),
|
SelectDeviceType(DeviceType),
|
||||||
@@ -127,7 +128,7 @@ impl App {
|
|||||||
ui_rx: Arc<Mutex<UnboundedReceiver<BluetoothUIMessage>>>,
|
ui_rx: Arc<Mutex<UnboundedReceiver<BluetoothUIMessage>>>,
|
||||||
start_minimized: bool,
|
start_minimized: bool,
|
||||||
device_managers: Arc<RwLock<HashMap<String, DeviceManagers>>>,
|
device_managers: Arc<RwLock<HashMap<String, DeviceManagers>>>,
|
||||||
stem_control: Arc<AtomicBool>,
|
// stem_control: Arc<AtomicBool>,
|
||||||
) -> (Self, Task<Message>) {
|
) -> (Self, Task<Message>) {
|
||||||
let (mut panes, first_pane) = pane_grid::State::new(Pane::Sidebar);
|
let (mut panes, first_pane) = pane_grid::State::new(Pane::Sidebar);
|
||||||
let split = panes.split(pane_grid::Axis::Vertical, first_pane, Pane::Content);
|
let split = panes.split(pane_grid::Axis::Vertical, first_pane, Pane::Content);
|
||||||
@@ -160,6 +161,11 @@ impl App {
|
|||||||
.and_then(|v| v.get("tray_text_mode").cloned())
|
.and_then(|v| v.get("tray_text_mode").cloned())
|
||||||
.and_then(|ttm| serde_json::from_value(ttm).ok())
|
.and_then(|ttm| serde_json::from_value(ttm).ok())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
let stem_control = settings
|
||||||
|
.clone()
|
||||||
|
.and_then(|v| v.get("stem_control").cloned())
|
||||||
|
.and_then(|s| serde_json::from_value(s).ok())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
let bluetooth_state = BluetoothState::new();
|
let bluetooth_state = BluetoothState::new();
|
||||||
|
|
||||||
@@ -246,7 +252,7 @@ impl App {
|
|||||||
let settings = serde_json::json!({
|
let settings = serde_json::json!({
|
||||||
"theme": self.selected_theme,
|
"theme": self.selected_theme,
|
||||||
"tray_text_mode": self.tray_text_mode,
|
"tray_text_mode": self.tray_text_mode,
|
||||||
"stem_control": self.stem_control.load(Ordering::Relaxed),
|
"stem_control": self.stem_control,
|
||||||
});
|
});
|
||||||
debug!(
|
debug!(
|
||||||
"Writing settings to {}: {}",
|
"Writing settings to {}: {}",
|
||||||
@@ -404,7 +410,16 @@ impl App {
|
|||||||
let wait_task = Task::perform(wait_for_message(ui_rx), |msg| msg);
|
let wait_task = Task::perform(wait_for_message(ui_rx), |msg| msg);
|
||||||
debug!("Device disconnected: {}", mac);
|
debug!("Device disconnected: {}", mac);
|
||||||
|
|
||||||
|
self.bluetooth_state
|
||||||
|
.connected_devices
|
||||||
|
.retain(|device| device != &mac);
|
||||||
|
|
||||||
self.device_states.remove(&mac);
|
self.device_states.remove(&mac);
|
||||||
|
|
||||||
|
if matches!(&self.selected_tab, Tab::Device(selected_mac) if selected_mac == &mac) {
|
||||||
|
self.selected_tab = Tab::Device("none".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
Task::batch(vec![wait_task])
|
Task::batch(vec![wait_task])
|
||||||
}
|
}
|
||||||
BluetoothUIMessage::AACPUIEvent(mac, event) => {
|
BluetoothUIMessage::AACPUIEvent(mac, event) => {
|
||||||
@@ -520,11 +535,11 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ShowNewDialogTab => {
|
// Message::ShowNewDialogTab => {
|
||||||
debug!("switching to Add Device tab");
|
// debug!("switching to Add Device tab");
|
||||||
self.selected_tab = Tab::AddDevice;
|
// self.selected_tab = Tab::AddDevice;
|
||||||
Task::perform(load_paired_devices(), Message::GotPairedDevices)
|
// Task::perform(load_paired_devices(), Message::GotPairedDevices)
|
||||||
}
|
// }
|
||||||
Message::GotPairedDevices(map) => {
|
Message::GotPairedDevices(map) => {
|
||||||
self.paired_devices = map;
|
self.paired_devices = map;
|
||||||
Task::none()
|
Task::none()
|
||||||
@@ -615,7 +630,7 @@ impl App {
|
|||||||
let settings = serde_json::json!({
|
let settings = serde_json::json!({
|
||||||
"theme": self.selected_theme,
|
"theme": self.selected_theme,
|
||||||
"tray_text_mode": self.tray_text_mode,
|
"tray_text_mode": self.tray_text_mode,
|
||||||
"stem_control": self.stem_control.load(Ordering::Relaxed),
|
"stem_control": self.stem_control,
|
||||||
});
|
});
|
||||||
debug!(
|
debug!(
|
||||||
"Writing settings to {}: {}",
|
"Writing settings to {}: {}",
|
||||||
@@ -626,12 +641,12 @@ impl App {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::StemControlChanged(is_enabled) => {
|
Message::StemControlChanged(is_enabled) => {
|
||||||
self.stem_control.store(is_enabled, Ordering::Relaxed);
|
self.stem_control = is_enabled;
|
||||||
let app_settings_path = get_app_settings_path();
|
let app_settings_path = get_app_settings_path();
|
||||||
let settings = serde_json::json!({
|
let settings = serde_json::json!({
|
||||||
"theme": self.selected_theme,
|
"theme": self.selected_theme,
|
||||||
"tray_text_mode": self.tray_text_mode,
|
"tray_text_mode": self.tray_text_mode,
|
||||||
"stem_control": self.stem_control.load(Ordering::Relaxed),
|
"stem_control": self.stem_control,
|
||||||
});
|
});
|
||||||
debug!(
|
debug!(
|
||||||
"Writing settings to {}: {}",
|
"Writing settings to {}: {}",
|
||||||
@@ -1040,7 +1055,7 @@ impl App {
|
|||||||
]
|
]
|
||||||
.spacing(12);
|
.spacing(12);
|
||||||
|
|
||||||
let stem_control_value = self.stem_control.load(Ordering::Relaxed);
|
let stem_control_value = self.stem_control;
|
||||||
let stem_control_toggle = container(
|
let stem_control_toggle = container(
|
||||||
row![
|
row![
|
||||||
column![
|
column![
|
||||||
@@ -1300,25 +1315,26 @@ async fn wait_for_message(ui_rx: Arc<Mutex<UnboundedReceiver<BluetoothUIMessage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn load_paired_devices() -> HashMap<String, Address> {
|
|
||||||
let mut devices = HashMap::new();
|
|
||||||
|
|
||||||
let session = Session::new().await.ok().unwrap();
|
// async fn load_paired_devices() -> HashMap<String, Address> {
|
||||||
let adapter = session.default_adapter().await.ok().unwrap();
|
// let mut devices = HashMap::new();
|
||||||
let addresses = adapter.device_addresses().await.ok().unwrap();
|
//
|
||||||
for addr in addresses {
|
// let session = Session::new().await.ok().unwrap();
|
||||||
let device = adapter.device(addr).ok().unwrap();
|
// let adapter = session.default_adapter().await.ok().unwrap();
|
||||||
let paired = device.is_paired().await.ok().unwrap();
|
// let addresses = adapter.device_addresses().await.ok().unwrap();
|
||||||
if paired {
|
// for addr in addresses {
|
||||||
let name = device
|
// let device = adapter.device(addr).ok().unwrap();
|
||||||
.name()
|
// let paired = device.is_paired().await.ok().unwrap();
|
||||||
.await
|
// if paired {
|
||||||
.ok()
|
// let name = device
|
||||||
.flatten()
|
// .name()
|
||||||
.unwrap_or_else(|| "Unknown".to_string());
|
// .await
|
||||||
devices.insert(name, addr);
|
// .ok()
|
||||||
}
|
// .flatten()
|
||||||
}
|
// .unwrap_or_else(|| "Unknown".to_string());
|
||||||
|
// devices.insert(name, addr);
|
||||||
devices
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// devices
|
||||||
|
// }
|
||||||
|
|||||||
@@ -15,18 +15,40 @@ pub fn get_devices_path() -> PathBuf {
|
|||||||
|
|
||||||
pub fn get_preferences_path() -> PathBuf {
|
pub fn get_preferences_path() -> PathBuf {
|
||||||
let config_dir = std::env::var("XDG_CONFIG_HOME")
|
let config_dir = std::env::var("XDG_CONFIG_HOME")
|
||||||
.unwrap_or_else(|_| format!("{}/.local/share", std::env::var("HOME").unwrap_or_default()));
|
.unwrap_or_else(|_| format!("{}/.config", std::env::var("HOME").unwrap_or_default()));
|
||||||
PathBuf::from(config_dir)
|
PathBuf::from(config_dir)
|
||||||
.join("librepods")
|
.join("librepods")
|
||||||
.join("preferences.json")
|
.join("preferences.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_app_settings_path() -> PathBuf {
|
pub fn get_app_settings_path() -> PathBuf {
|
||||||
|
let home = std::env::var("HOME").unwrap_or_default();
|
||||||
|
|
||||||
let config_dir = std::env::var("XDG_CONFIG_HOME")
|
let config_dir = std::env::var("XDG_CONFIG_HOME")
|
||||||
.unwrap_or_else(|_| format!("{}/.local/share", std::env::var("HOME").unwrap_or_default()));
|
.unwrap_or_else(|_| format!("{}/.config", home));
|
||||||
PathBuf::from(config_dir)
|
|
||||||
|
let data_dir = std::env::var("XDG_DATA_HOME")
|
||||||
|
.unwrap_or_else(|_| format!("{}/.local/share", home));
|
||||||
|
|
||||||
|
let new_path = PathBuf::from(&config_dir)
|
||||||
.join("librepods")
|
.join("librepods")
|
||||||
.join("app_settings.json")
|
.join("app_settings.json");
|
||||||
|
|
||||||
|
let old_path = PathBuf::from(&data_dir)
|
||||||
|
.join("app_settings.json");
|
||||||
|
|
||||||
|
// migrate if needed
|
||||||
|
if old_path.exists() && !new_path.exists() {
|
||||||
|
if let Some(parent) = new_path.parent() {
|
||||||
|
let _ = std::fs::create_dir_all(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if std::fs::copy(&old_path, &new_path).is_ok() {
|
||||||
|
let _ = std::fs::remove_file(&old_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn e(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
fn e(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
||||||
|
|||||||
Reference in New Issue
Block a user