Skip to content

Commit

Permalink
migrate bluetooth
Browse files Browse the repository at this point in the history
  • Loading branch information
MalpenZibo committed Sep 23, 2024
1 parent 0b52786 commit 565765f
Show file tree
Hide file tree
Showing 8 changed files with 481 additions and 411 deletions.
85 changes: 7 additions & 78 deletions src/modules/settings/bluetooth.rs
Original file line number Diff line number Diff line change
@@ -1,93 +1,26 @@
use super::{quick_setting_button, sub_menu_wrapper, Message, SubMenu};
use crate::{
components::icons::{icon, Icons},
config::SettingsModuleConfig,
menu::Menu,
services::{
bluetooth::{BluetoothData, BluetoothService, BluetoothState},
ServiceEvent,
},
style::GhostButtonStyle,
utils::{bluetooth::BluetoothCommand, Commander},
};
use iced::{
theme::Button,
widget::{button, column, container, horizontal_rule, row, text, Column, Row},
Command, Element, Length, Subscription, Theme,
Element, Length, Theme,
};
use log::debug;

#[derive(PartialEq, Eq, Debug, Clone)]
pub enum BluetoothState {
Unavailable,
Active,
Inactive,
}

#[derive(Debug, Clone)]
pub struct Device {
pub name: String,
pub battery: Option<u8>,
}

#[derive(Debug, Clone)]
pub enum BluetoothMessage {
Status(BluetoothState),
DeviceList(Vec<Device>),
Event(ServiceEvent<BluetoothService>),
Toggle,
More,
}

pub struct Bluetooth {
commander: Commander<BluetoothCommand>,
state: BluetoothState,
devices: Vec<Device>,
}

impl Bluetooth {
pub fn new() -> Self {
Self {
commander: Commander::new(),
state: BluetoothState::Unavailable,
devices: Vec::new(),
}
}

pub fn update(
&mut self,
msg: BluetoothMessage,
menu: &mut Menu<crate::app::Message>,
sub_menu: &mut Option<SubMenu>,
config: &SettingsModuleConfig,
) -> Command<crate::app::Message> {
match msg {
BluetoothMessage::Status(state) => {
debug!("Bluetooth state: {:?}", state);
self.state = state;

if self.state != BluetoothState::Active && *sub_menu == Some(SubMenu::Bluetooth) {
*sub_menu = None;
}

Command::none()
}
BluetoothMessage::DeviceList(devices) => {
self.devices = devices;

Command::none()
}
BluetoothMessage::Toggle => {
let _ = self.commander.send(BluetoothCommand::TogglePower);

Command::none()
}
BluetoothMessage::More => {
if let Some(cmd) = &config.bluetooth_more_cmd {
crate::utils::launcher::execute_command(cmd.to_string());
menu.close()
} else {
Command::none()
}
}
}
}

impl BluetoothData {
pub fn get_quick_setting_button(
&self,
sub_menu: Option<SubMenu>,
Expand Down Expand Up @@ -180,8 +113,4 @@ impl Bluetooth {
})
.into()
}

pub fn subscription(&self) -> Subscription<BluetoothMessage> {
crate::utils::bluetooth::subscription(self.commander.give_receiver())
}
}
76 changes: 58 additions & 18 deletions src/modules/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
modules::settings::power::power_menu,
password_dialog,
services::{
bluetooth::{BluetoothCommand, BluetoothService},
network::{NetworkCommand, NetworkEvent, NetworkService},
upower::{PowerProfileCommand, UPowerService},
ReadOnlyService, Service, ServiceEvent,
Expand All @@ -22,7 +23,6 @@ use crate::{
},
utils::idle_inhibitor::WaylandIdleInhibitor,
};
use bluetooth::Bluetooth;
use brightness::{Brightness, BrightnessMessage};
use iced::{
alignment::{Horizontal, Vertical},
Expand All @@ -45,7 +45,7 @@ pub struct Settings {
audio: Audio,
brightness: Brightness,
network: Option<NetworkService>,
bluetooth: Bluetooth,
bluetooth: Option<BluetoothService>,
idle_inhibitor: Option<WaylandIdleInhibitor>,
sub_menu: Option<SubMenu>,
upower: Option<UPowerService>,
Expand Down Expand Up @@ -83,7 +83,7 @@ impl Settings {
audio: Audio::new(),
brightness: Brightness::new(),
network: None,
bluetooth: Bluetooth::new(),
bluetooth: None,
idle_inhibitor: WaylandIdleInhibitor::new().ok(),
sub_menu: None,
upower: None,
Expand Down Expand Up @@ -222,24 +222,61 @@ impl Settings {
}
_ => Command::none(),
},
Message::Bluetooth(msg) => self.bluetooth.update(msg, menu, &mut self.sub_menu, config),
Message::Bluetooth(msg) => match msg {
BluetoothMessage::Event(event) => match event {
ServiceEvent::Init(service) => {
self.bluetooth = Some(service);
Command::none()
}
ServiceEvent::Update(data) => {
if let Some(bluetooth) = self.bluetooth.as_mut() {
bluetooth.update(data);
}
Command::none()
}
_ => Command::none(),
},
BluetoothMessage::Toggle => {
if let Some(bluetooth) = self.bluetooth.as_ref() {
bluetooth.command(BluetoothCommand::Toggle).map(|event| {
crate::app::Message::Settings(Message::Bluetooth(
BluetoothMessage::Event(event),
))
})
} else {
Command::none()
}
}
BluetoothMessage::More => {
if let Some(cmd) = &config.bluetooth_more_cmd {
crate::utils::launcher::execute_command(cmd.to_string());
menu.close()
} else {
Command::none()
}
}
},
Message::Audio(msg) => self.audio.update(msg, menu, config),
Message::Brightness(msg) => self.brightness.update(msg),
Message::ToggleSubMenu(menu_type) => {
if self.sub_menu == Some(menu_type) {
self.sub_menu.take();
} else {
// match menu_type {
// SubMenu::Vpn => {
// self.net.get_vpn_connections();
// }
// SubMenu::Wifi => {
// self.net.get_nearby_wifi();
// }
// _ => {}
// };
self.sub_menu.replace(menu_type);

if menu_type == SubMenu::Wifi {
if let Some(network) = self.network.as_ref() {
return network
.command(NetworkCommand::ScanNearByWiFi)
.map(|event| {
crate::app::Message::Settings(Message::Network(
NetworkMessage::Event(event),
))
});
}
}
}

Command::none()
}
Message::ToggleInhibitIdle => {
Expand Down Expand Up @@ -413,10 +450,12 @@ impl Settings {
let quick_settings = quick_settings_section(
vec![
wifi_setting_button,
self.bluetooth.get_quick_setting_button(
self.sub_menu,
config.bluetooth_more_cmd.is_some(),
),
self.bluetooth.as_ref().and_then(|b| {
b.get_quick_setting_button(
self.sub_menu,
config.bluetooth_more_cmd.is_some(),
)
}),
self.network.as_ref().map(|n| {
n.get_vpn_quick_setting_button(self.sub_menu, config.vpn_more_cmd.is_some())
}),
Expand Down Expand Up @@ -491,7 +530,8 @@ impl Settings {
self.audio.subscription().map(Message::Audio),
self.brightness.subscription().map(Message::Brightness),
NetworkService::subscribe().map(|event| Message::Network(NetworkMessage::Event(event))),
self.bluetooth.subscription().map(Message::Bluetooth),
BluetoothService::subscribe()
.map(|event| Message::Bluetooth(BluetoothMessage::Event(event))),
])
}
}
Expand Down
147 changes: 147 additions & 0 deletions src/services/bluetooth/dbus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use std::collections::HashMap;

use zbus::{
proxy,
zvariant::{OwnedObjectPath, OwnedValue},
};

use super::{BluetoothDevice, BluetoothState};

type ManagedObjects = HashMap<OwnedObjectPath, HashMap<String, HashMap<String, OwnedValue>>>;

pub struct BluetoothDbus<'a> {
pub bluez: BluezObjectManagerProxy<'a>,
pub adapter: Option<AdapterProxy<'a>>,
}

impl<'a> BluetoothDbus<'a> {
pub async fn new(conn: &zbus::Connection) -> anyhow::Result<Self> {
let bluez = BluezObjectManagerProxy::new(conn).await?;
let adapter = bluez
.get_managed_objects()
.await?
.into_iter()
.filter_map(|(key, item)| {
if item.contains_key("org.bluez.Adapter1") {
Some(key)
} else {
None
}
})
.next();

let adapter = if let Some(adapter) = adapter {
Some(AdapterProxy::builder(conn).path(adapter)?.build().await?)
} else {
None
};

Ok(Self { bluez, adapter })
}

pub async fn set_powered(&self, value: bool) -> zbus::Result<()> {
if let Some(adapter) = &self.adapter {
adapter.set_powered(value).await?;
}

Ok(())
}

pub async fn state(&self) -> zbus::Result<BluetoothState> {
if let Some(adapter) = &self.adapter {
if adapter.powered().await? {
Ok(BluetoothState::Active)
} else {
Ok(BluetoothState::Inactive)
}
} else {
Ok(BluetoothState::Unavailable)
}
}

pub async fn devices(&self) -> anyhow::Result<Vec<BluetoothDevice>> {
let devices_proxy = self
.bluez
.get_managed_objects()
.await?
.into_iter()
.filter_map(|(key, item)| {
if item.contains_key("org.bluez.Device1") {
Some(key)
} else {
None
}
})
.collect::<Vec<_>>();

let mut devices = Vec::new();
for device_path in devices_proxy {
let device = DeviceProxy::builder(self.bluez.inner().connection())
.path(device_path.clone())?
.build()
.await?;

let name = device.name().await?;
let connected = device.connected().await?;

if connected {
let battery = BatteryProxy::builder(self.bluez.inner().connection())
.path(&device_path)?
.build()
.await?;
let battery = battery.percentage().await?;

devices.push(BluetoothDevice {
name,
battery: Some(battery),
path: device_path,
});
}
}

Ok(devices)
}
}

#[proxy(
default_service = "org.bluez",
default_path = "/",
interface = "org.freedesktop.DBus.ObjectManager"
)]
trait BluezObjectManager {
fn get_managed_objects(&self) -> zbus::Result<ManagedObjects>;

#[zbus(signal)]
fn interfaces_added(&self) -> Result<()>;

#[zbus(signal)]
fn interfaces_removed(&self) -> Result<()>;
}

#[proxy(
default_service = "org.bluez",
default_path = "/org/bluez/hci0",
interface = "org.bluez.Adapter1"
)]
trait Adapter {
#[zbus(property)]
fn powered(&self) -> zbus::Result<bool>;

#[zbus(property)]
fn set_powered(&self, value: bool) -> zbus::Result<()>;
}

#[proxy(default_service = "org.bluez", interface = "org.bluez.Device1")]
trait Device {
#[zbus(property)]
fn name(&self) -> zbus::Result<String>;

#[zbus(property)]
fn connected(&self) -> zbus::Result<bool>;
}

#[proxy(default_service = "org.bluez", interface = "org.bluez.Battery1")]
trait Battery {
#[zbus(property)]
fn percentage(&self) -> zbus::Result<u8>;
}
Loading

0 comments on commit 565765f

Please sign in to comment.