wip: update libcosmic (#93)

* wip: update libcosmic

* fix: damge issue resolved by updating iced

* fix: high cpu usage by time applet and app-list

* refactor subscriptions to produce fewer events

* refactor network applet to use less cpu

* fix: text size

* refactor: i18n for audio applet

* refactor: power applet i18n setup

* fix (battery): always send profile update

* fix (battery): set toggler width to layout correctly

* fix (app-list): backoff for restarts of toplevel subscription

* fix (network): alignment

* feat: ask for comfirmation before applying power applet actions

* wip: integrate cosmic-config

* update zbus

* feat: update to use latest libcosmic

* update iced

* udpate deps

* update deps

* refactor: move applet helpers to this repo, outside of libcosmic.

this should help alleviate some dependency hell

* chore update deps

* update deps

* cleanup
This commit is contained in:
Ashley Wulber 2023-06-01 12:23:12 -04:00 committed by GitHub
parent 8b46cc209f
commit 9ebd9b511a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 2841 additions and 1681 deletions

View file

@ -1,15 +1,14 @@
use cosmic::iced_style;
use cosmic::iced_widget::Row;
use cosmic::{
applet::CosmicAppletHelper,
iced::{
wayland::popup::{destroy_popup, get_popup},
widget::{column, container, row, scrollable, text, text_input, Column},
Alignment, Application, Color, Command, Length, Subscription,
},
iced_native::{
iced_runtime::core::{
alignment::{Horizontal, Vertical},
layout::Limits,
renderer::BorderRadius,
window,
},
iced_style::{application, button::StyleSheet},
@ -17,9 +16,14 @@ use cosmic::{
widget::{button, divider, icon, toggler},
Element, Theme,
};
use cosmic_applet::CosmicAppletHelper;
use cosmic_dbus_networkmanager::interface::enums::{ActiveConnectionState, DeviceState};
use futures::channel::mpsc::UnboundedSender;
use zbus::Connection;
use crate::network_manager::active_conns::active_conns_subscription;
use crate::network_manager::devices::devices_subscription;
use crate::network_manager::wireless_enabled::wireless_enabled_subscription;
use crate::network_manager::NetworkManagerState;
use crate::{
config, fl,
@ -77,13 +81,14 @@ struct CosmicNetworkApplet {
icon_name: String,
theme: Theme,
popup: Option<window::Id>,
id_ctr: u32,
id_ctr: u128,
applet_helper: CosmicAppletHelper,
nm_state: NetworkManagerState,
// UI state
nm_sender: Option<UnboundedSender<NetworkManagerRequest>>,
show_visible_networks: bool,
new_connection: Option<NewConnectionState>,
conn: Option<Connection>,
}
impl CosmicNetworkApplet {
@ -110,7 +115,7 @@ impl CosmicNetworkApplet {
}
#[derive(Debug, Clone)]
enum Message {
pub(crate) enum Message {
ActivateKnownWifi(String),
Disconnect(String),
TogglePopup,
@ -155,11 +160,11 @@ impl Application for CosmicNetworkApplet {
} else {
// TODO request update of state maybe
self.id_ctr += 1;
let new_id = window::Id::new(self.id_ctr);
let new_id = window::Id(self.id_ctr);
self.popup.replace(new_id);
let mut popup_settings = self.applet_helper.get_popup_settings(
window::Id::new(0),
window::Id(0),
new_id,
None,
None,
@ -167,10 +172,10 @@ impl Application for CosmicNetworkApplet {
);
popup_settings.positioner.size_limits = Limits::NONE
.min_height(1)
.min_width(1)
.max_height(800)
.max_width(400);
.min_height(1.0)
.min_width(1.0)
.max_height(800.0)
.max_width(400.0);
return get_popup(popup_settings);
}
}
@ -193,10 +198,15 @@ impl Application for CosmicNetworkApplet {
}
}
Message::NetworkManagerEvent(event) => match event {
NetworkManagerEvent::Init { sender, state } => {
NetworkManagerEvent::Init {
conn,
sender,
state,
} => {
self.nm_sender.replace(sender);
self.nm_state = state;
self.update_icon_name();
self.conn = Some(conn);
}
NetworkManagerEvent::WiFiEnabled(state) => {
self.nm_state = state;
@ -337,17 +347,17 @@ impl Application for CosmicNetworkApplet {
Command::none()
}
fn view(&self, id: window::Id) -> Element<Message> {
let button_style = Button::Custom {
active: |t| iced_style::button::Appearance {
border_radius: BorderRadius::from(0.0),
let button_style = || Button::Custom {
active: Box::new(|t| iced_style::button::Appearance {
border_radius: 0.0,
..t.active(&Button::Text)
},
hover: |t| iced_style::button::Appearance {
border_radius: BorderRadius::from(0.0),
}),
hover: Box::new(|t| iced_style::button::Appearance {
border_radius: 0.0,
..t.hovered(&Button::Text)
},
}),
};
if id == window::Id::new(0) {
if id == window::Id(0) {
self.applet_helper
.icon_button(&self.icon_name)
.on_press(Message::TogglePopup)
@ -362,7 +372,7 @@ impl Application for CosmicNetworkApplet {
for addr in ip_addresses {
ipv4.push(
text(format!("{}: {}", fl!("ipv4"), addr.to_string()))
.size(12)
.size(10)
.into(),
);
}
@ -412,8 +422,6 @@ impl Application for CosmicNetworkApplet {
let mut btn_content = vec![
icon("network-wireless-symbolic", 24)
.style(Svg::Symbolic)
.width(Length::Units(24))
.height(Length::Units(24))
.into(),
column![text(name).size(14), Column::with_children(ipv4)]
.width(Length::Fill)
@ -425,8 +433,6 @@ impl Application for CosmicNetworkApplet {
btn_content.push(
icon("process-working-symbolic", 24)
.style(Svg::Symbolic)
.width(Length::Units(24))
.height(Length::Units(24))
.into(),
);
}
@ -441,9 +447,12 @@ impl Application for CosmicNetworkApplet {
};
known_wifi = known_wifi.push(
column![button(Button::Secondary)
.custom(btn_content)
.custom(vec![Row::with_children(btn_content)
.align_items(Alignment::Center)
.spacing(8)
.into()])
.padding([8, 24])
.style(button_style.clone())
.style(button_style())
.on_press(Message::Disconnect(name.clone()))]
.align_items(Alignment::Center),
);
@ -454,8 +463,6 @@ impl Application for CosmicNetworkApplet {
let mut btn_content = vec![
icon("network-wireless-symbolic", 24)
.style(Svg::Symbolic)
.width(Length::Units(24))
.height(Length::Units(24))
.into(),
text(&known.ssid).size(14).width(Length::Fill).into(),
];
@ -464,17 +471,18 @@ impl Application for CosmicNetworkApplet {
btn_content.push(
icon("process-working-symbolic", 24)
.style(Svg::Symbolic)
.width(Length::Units(24))
.height(Length::Units(24))
.into(),
);
}
let mut btn = button(Button::Secondary)
.custom(btn_content)
.custom(vec![Row::with_children(btn_content)
.align_items(Alignment::Center)
.spacing(8)
.into()])
.padding([8, 24])
.width(Length::Fill)
.style(button_style.clone());
.style(button_style());
btn = match known.state {
DeviceState::Failed
| DeviceState::Unknown
@ -495,6 +503,7 @@ impl Application for CosmicNetworkApplet {
toggler(fl!("airplane-mode"), self.nm_state.airplane_mode, |m| {
Message::ToggleAirplaneMode(m)
})
.text_size(14)
.width(Length::Fill)
)
.padding([0, 12]),
@ -503,6 +512,7 @@ impl Application for CosmicNetworkApplet {
toggler(fl!("wifi"), self.nm_state.wifi_enabled, |m| {
Message::ToggleWiFi(m)
})
.text_size(14)
.width(Length::Fill)
)
.padding([0, 12]),
@ -523,25 +533,20 @@ impl Application for CosmicNetworkApplet {
text(fl!("visible-wireless-networks"))
.size(14)
.width(Length::Fill)
.height(Length::Units(24))
.height(Length::Fixed(24.0))
.vertical_alignment(Vertical::Center)
.into(),
container(
icon(dropdown_icon, 14)
.style(Svg::Symbolic)
.width(Length::Units(14))
.height(Length::Units(14)),
)
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
.width(Length::Units(24))
.height(Length::Units(24))
.into(),
container(icon(dropdown_icon, 14).style(Svg::Symbolic))
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
.width(Length::Fixed(24.0))
.height(Length::Fixed(24.0))
.into(),
]
.into(),
)
.padding([8, 24])
.style(button_style.clone())
.style(button_style())
.on_press(Message::ToggleVisibleNetworks);
content = content.push(available_connections_btn);
if self.show_visible_networks {
@ -552,10 +557,7 @@ impl Application for CosmicNetworkApplet {
password,
} => {
let id = row![
icon("network-wireless-symbolic", 24)
.style(Svg::Symbolic)
.width(Length::Units(24))
.height(Length::Units(24)),
icon("network-wireless-symbolic", 24).style(Svg::Symbolic),
text(&access_point.ssid).size(14),
]
.align_items(Alignment::Center)
@ -565,7 +567,9 @@ impl Application for CosmicNetworkApplet {
content = content.push(id);
let col = column![
text(fl!("enter-password")),
text_input("", password, Message::Password)
text_input("", password)
.on_input(Message::Password)
.on_paste(Message::Password)
.on_submit(Message::SubmitPassword)
.password(),
container(text(fl!("router-wps-button"))).padding(8),
@ -590,10 +594,7 @@ impl Application for CosmicNetworkApplet {
}
NewConnectionState::Waiting(access_point) => {
let id = row![
icon("network-wireless-symbolic", 24)
.style(Svg::Symbolic)
.width(Length::Units(24))
.height(Length::Units(24)),
icon("network-wireless-symbolic", 24).style(Svg::Symbolic),
text(&access_point.ssid).size(14),
]
.align_items(Alignment::Center)
@ -601,10 +602,7 @@ impl Application for CosmicNetworkApplet {
.spacing(12);
let connecting = row![
id,
icon("process-working-symbolic", 24)
.style(Svg::Symbolic)
.width(Length::Units(24))
.height(Length::Units(24)),
icon("process-working-symbolic", 24).style(Svg::Symbolic),
]
.spacing(8)
.padding([0, 24]);
@ -612,10 +610,7 @@ impl Application for CosmicNetworkApplet {
}
NewConnectionState::Failure(access_point) => {
let id = row![
icon("network-wireless-symbolic", 24)
.style(Svg::Symbolic)
.width(Length::Units(24))
.height(Length::Units(24)),
icon("network-wireless-symbolic", 24).style(Svg::Symbolic),
text(&access_point.ssid).size(14),
]
.align_items(Alignment::Center)
@ -660,15 +655,12 @@ impl Application for CosmicNetworkApplet {
{
continue;
}
let button = button(button_style)
let button = button(button_style())
.custom(vec![row![
icon("network-wireless-symbolic", 16)
.style(Svg::Symbolic)
.width(Length::Units(16))
.height(Length::Units(16)),
icon("network-wireless-symbolic", 16).style(Svg::Symbolic),
text(&ap.ssid)
.size(14)
.height(Length::Units(24))
.height(Length::Fixed(24.0))
.vertical_alignment(Vertical::Center)
]
.align_items(Alignment::Center)
@ -680,7 +672,7 @@ impl Application for CosmicNetworkApplet {
list_col.push(button.into());
}
content = content.push(
scrollable(Column::with_children(list_col)).height(Length::Units(300)),
scrollable(Column::with_children(list_col)).height(Length::Fixed(300.0)),
);
}
}
@ -689,11 +681,25 @@ impl Application for CosmicNetworkApplet {
}
fn subscription(&self) -> Subscription<Message> {
network_manager_subscription(0).map(|(_, event)| Message::NetworkManagerEvent(event))
let network_sub =
network_manager_subscription(0).map(|e| Message::NetworkManagerEvent(e.1));
if let Some(conn) = self.conn.as_ref() {
Subscription::batch(vec![
network_sub,
active_conns_subscription(0, conn.clone())
.map(|e| Message::NetworkManagerEvent(e.1)),
devices_subscription(0, conn.clone()).map(|e| Message::NetworkManagerEvent(e.1)),
wireless_enabled_subscription(0, conn.clone())
.map(|e| Message::NetworkManagerEvent(e.1)),
])
} else {
network_sub
}
}
fn theme(&self) -> Theme {
self.theme
self.theme.clone()
}
fn close_requested(&self, _id: window::Id) -> Self::Message {
@ -701,9 +707,11 @@ impl Application for CosmicNetworkApplet {
}
fn style(&self) -> <Self::Theme as application::StyleSheet>::Style {
<Self::Theme as application::StyleSheet>::Style::Custom(|theme| application::Appearance {
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
text_color: theme.cosmic().on_bg_color().into(),
})
<Self::Theme as application::StyleSheet>::Style::Custom(Box::new(|theme| {
application::Appearance {
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
text_color: theme.cosmic().on_bg_color().into(),
}
}))
}
}

View file

@ -0,0 +1,56 @@
use super::{NetworkManagerEvent, NetworkManagerState};
use cosmic::iced::{self, subscription};
use cosmic_dbus_networkmanager::nm::NetworkManager;
use futures::StreamExt;
use log::error;
use std::fmt::Debug;
use std::hash::Hash;
use zbus::Connection;
pub fn active_conns_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
id: I,
conn: Connection,
) -> iced::Subscription<(I, NetworkManagerEvent)> {
subscription::unfold(id, State::Continue(conn), move |mut state| async move {
loop {
let (update, new_state) = start_listening(id, state).await;
state = new_state;
if let Some(update) = update {
return (update, state);
}
}
})
}
#[derive(Debug, Clone)]
pub enum State {
Continue(Connection),
Error,
}
async fn start_listening<I: Copy + Debug>(
id: I,
state: State,
) -> (Option<(I, NetworkManagerEvent)>, State) {
let conn = match state {
State::Continue(conn) => conn,
State::Error => iced::futures::future::pending().await,
};
let network_manager = match NetworkManager::new(&conn).await {
Ok(n) => n,
Err(e) => {
error!("Failed to connect to NetworkManager: {}", e);
return (None, State::Error);
}
};
let mut active_conns_changed = network_manager.receive_active_connections_changed().await;
active_conns_changed.next().await;
let new_state = NetworkManagerState::new(&conn).await.unwrap_or_default();
(
Some((id, NetworkManagerEvent::ActiveConns(new_state))),
State::Continue(conn),
)
}

View file

@ -0,0 +1,59 @@
use super::{NetworkManagerEvent, NetworkManagerState};
use cosmic::iced::{self, subscription};
use cosmic_dbus_networkmanager::nm::NetworkManager;
use log::error;
use std::fmt::Debug;
use std::hash::Hash;
use zbus::Connection;
use futures::StreamExt;
pub fn devices_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
id: I,
conn: Connection,
) -> iced::Subscription<(I, NetworkManagerEvent)> {
subscription::unfold(id, State::Continue(conn), move |mut state| async move {
loop {
let (update, new_state) = start_listening(id, state).await;
state = new_state;
if let Some(update) = update {
return (update, state);
}
}
})
}
#[derive(Debug, Clone)]
pub enum State {
Continue(Connection),
Error,
}
async fn start_listening<I: Copy + Debug>(
id: I,
state: State,
) -> (Option<(I, NetworkManagerEvent)>, State) {
let conn = match state {
State::Continue(conn) => conn,
State::Error => iced::futures::future::pending().await,
};
let network_manager = match NetworkManager::new(&conn).await {
Ok(n) => n,
Err(e) => {
error!("Failed to connect to NetworkManager: {}", e);
return (None, State::Error);
}
};
let mut devices_changed = network_manager.receive_devices_changed().await;
devices_changed.next().await;
let new_state = NetworkManagerState::new(&conn).await.unwrap_or_default();
(
Some((
id,
NetworkManagerEvent::WirelessAccessPoints(new_state),
)),
State::Continue(conn),
)
}

View file

@ -1,5 +1,8 @@
pub mod active_conns;
pub mod available_wifi;
pub mod current_networks;
pub mod devices;
pub mod wireless_enabled;
use std::{collections::HashMap, fmt::Debug, hash::Hash, ops::Deref, time::Duration};
@ -32,7 +35,9 @@ use self::{
pub fn network_manager_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
id: I,
) -> iced::Subscription<(I, NetworkManagerEvent)> {
subscription::unfold(id, State::Ready, move |state| start_listening(id, state))
subscription::unfold(id, State::Ready, move |state| {
start_listening_loop(id, state)
})
}
#[derive(Debug)]
@ -42,6 +47,19 @@ pub enum State {
Finished,
}
pub async fn start_listening_loop<I: Copy + Debug>(
id: I,
mut state: State,
) -> ((I, NetworkManagerEvent), State) {
loop {
let (update, new_state) = start_listening(id, state).await;
state = new_state;
if let Some(update) = update {
return (update, state);
}
}
}
async fn start_listening<I: Copy + Debug>(
id: I,
state: State,
@ -59,6 +77,7 @@ async fn start_listening<I: Copy + Debug>(
Some((
id,
NetworkManagerEvent::Init {
conn: conn.clone(),
sender: tx,
state: nm_state,
},
@ -72,289 +91,402 @@ async fn start_listening<I: Copy + Debug>(
Err(_) => return (None, State::Finished),
};
let mut active_conns_changed = tokio::time::sleep(Duration::from_secs(5))
.then(|_| async { network_manager.receive_active_connections_changed().await })
.await;
let mut devices_changed = network_manager.receive_devices_changed().await;
let mut wireless_enabled_changed =
network_manager.receive_wireless_enabled_changed().await;
let mut req = rx.next().boxed().fuse();
let (update, should_exit) = futures::select! {
req = req => {
match req {
Some(NetworkManagerRequest::Disconnect(ssid)) => {
let mut success = false;
for c in network_manager.active_connections().await.unwrap_or_default() {
if c.id().await.unwrap_or_default() == ssid {
if let Ok(_) = network_manager.deactivate_connection(&c).await {
success = true;
}
}
let (update, should_exit) = match rx.next().await {
Some(NetworkManagerRequest::Disconnect(ssid)) => {
let mut success = false;
for c in network_manager
.active_connections()
.await
.unwrap_or_default()
{
if c.id().await.unwrap_or_default() == ssid {
if let Ok(_) = network_manager.deactivate_connection(&c).await {
success = true;
}
(Some((id,
NetworkManagerEvent::RequestResponse {
}
}
(
Some((
id,
NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::Disconnect(ssid.clone()),
success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
})), false)
},
)),
false,
)
}
Some(NetworkManagerRequest::SetAirplaneMode(airplane_mode)) => {
// wifi
let mut success = network_manager
.set_wireless_enabled(!airplane_mode)
.await
.is_ok();
// bluetooth
success = success
&& Command::new("rfkill")
.arg(if airplane_mode { "block" } else { "unblock" })
.arg("bluetooth")
.output()
.await
.is_ok();
let response = NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::SetAirplaneMode(airplane_mode),
success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
};
(Some((id, response)), false)
}
Some(NetworkManagerRequest::SetWiFi(enabled)) => {
let success = network_manager.set_wireless_enabled(enabled).await.is_ok();
let response = NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::SetAirplaneMode(enabled),
success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
};
(Some((id, response)), false)
}
Some(NetworkManagerRequest::Password(ssid, password)) => {
let s = match NetworkManagerSettings::new(&conn).await {
Ok(s) => s,
Err(_) => return (None, State::Finished),
};
let mut status = (None, false);
// First try known connections
// TODO more convenient methods of managing settings
for c in s.list_connections().await.unwrap_or_default() {
let mut settings = match c.get_settings().await.ok() {
Some(s) => s,
None => continue,
};
let cur_ssid = settings
.get("802-11-wireless")
.and_then(|w| w.get("ssid"))
.cloned()
.and_then(|ssid| ssid.try_into().ok())
.and_then(|ssid| String::from_utf8(ssid).ok());
if cur_ssid.as_ref() != Some(&ssid) {
continue;
}
Some(NetworkManagerRequest::SetAirplaneMode(airplane_mode)) => {
// wifi
let mut success = network_manager.set_wireless_enabled(!airplane_mode).await.is_ok();
// bluetooth
success = success && Command::new("rfkill")
.arg(if airplane_mode { "block" } else { "unblock" })
.arg("bluetooth")
.output()
.await
.is_ok();
let response = NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::SetAirplaneMode(airplane_mode),
success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
};
(Some((id, response)), false)
}
Some(NetworkManagerRequest::SetWiFi(enabled)) => {
let success = network_manager.set_wireless_enabled(enabled).await.is_ok();
let response = NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::SetAirplaneMode(enabled),
success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
};
(Some((id, response)), false)
}
Some(NetworkManagerRequest::Password(ssid, password)) => {
let s = match NetworkManagerSettings::new(&conn).await {
Ok(s) => s,
Err(_) => return (None, State::Finished),
};
let mut status = (None, false);
// First try known connections
// TODO more convenient methods of managing settings
for c in s.list_connections().await.unwrap_or_default() {
let mut settings = match c.get_settings().await.ok() {
Some(s) => s,
None => continue,
};
let cur_ssid = settings
.get("802-11-wireless")
.and_then(|w| w.get("ssid"))
.cloned()
.and_then(|ssid| ssid.try_into().ok())
.and_then(|ssid| String::from_utf8(ssid).ok());
if cur_ssid.as_ref() != Some(&ssid) {
continue;
}
let mut secrets = match
c.get_secrets("802-11-wireless-security")
.await {
Ok(s) => s,
_ => HashMap::from([("802-11-wireless-security".into(), HashMap::from([
("psk".into(), Value::Str(password.as_str().into()).to_owned()),
("key-mgmt".into(), Value::Str("wpa-psk".into()).to_owned())
]))]),
};
if let Some(s) = secrets.get_mut("802-11-wireless-security") {
s.insert("psk".into(), Value::Str(password.clone().into()).to_owned());
drop(s);
settings.extend(secrets.into_iter());
let settings: HashMap<_, _> = settings.iter().map(|(k, v)| {
let map = (k.as_str(), v.iter()
let mut secrets = match c.get_secrets("802-11-wireless-security").await {
Ok(s) => s,
_ => HashMap::from([(
"802-11-wireless-security".into(),
HashMap::from([
(
"psk".into(),
Value::Str(password.as_str().into()).to_owned(),
),
("key-mgmt".into(), Value::Str("wpa-psk".into()).to_owned()),
]),
)]),
};
if let Some(s) = secrets.get_mut("802-11-wireless-security") {
s.insert("psk".into(), Value::Str(password.clone().into()).to_owned());
drop(s);
settings.extend(secrets.into_iter());
let settings: HashMap<_, _> = settings
.iter()
.map(|(k, v)| {
let map = (
k.as_str(),
v.iter()
.map(|(k, v)| (k.as_str(), v.into()))
.collect::<HashMap<_, _>>());
map
}).collect();
let updated = c.update(settings).await;
if updated.is_ok() {
let success = if let Ok(path) = network_manager.deref().activate_connection(c.deref().path(), &ObjectPath::try_from("/").unwrap(), &ObjectPath::try_from("/").unwrap()).await {
// let active_conn = ActiveConnection::from(ActiveConnectionProxy::from(conn.1));
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
let active = ActiveConnectionProxy::builder(&conn).path(path).unwrap().destination(dummy.destination()).unwrap().interface(dummy.interface()).unwrap().build().await.unwrap();
let state = enums::ActiveConnectionState::from(active.state().await.unwrap_or_default());
let s = if let enums::ActiveConnectionState::Activating = state {
if let Ok(Some(s)) = timeout(Duration::from_secs(10), active.receive_state_changed().await.next()).await {
s.get().await.unwrap_or_default().into()
} else {
state
}
} else {
state
};
matches!(s, enums::ActiveConnectionState::Activated)
} else {
false
};
status = (Some((id, NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::Password(ssid.clone(), password.clone()),
success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
})), false);
}
break;
}
}
// create a connection
if status.0.is_none() {
for device in network_manager.devices().await.ok().unwrap_or_default() {
if matches!(device.device_type().await.unwrap_or(DeviceType::Other), DeviceType::Wifi) {
let conn_settings: HashMap<&str, HashMap<&str, zvariant::Value>> = HashMap::from([
("802-11-wireless".into(), HashMap::from([
("ssid".into(), Value::Array(ssid.as_bytes().into())),
])),
("connection".into(), HashMap::from([
("id".into(), Value::Str(ssid.as_str().into())),
("type".into(), Value::Str("802-11-wireless".into())),
])),
("802-11-wireless-security".into(), HashMap::from([
("psk".into(), Value::Str(password.as_str().into())),
("key-mgmt".into(), Value::Str("wpa-psk".into()))
]))
]);
let success = if let Ok((_, path)) = network_manager.add_and_activate_connection(conn_settings, device.path(), &ObjectPath::try_from("/").unwrap()).await {
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
let active = ActiveConnectionProxy::builder(&conn).path(path).unwrap().destination(dummy.destination()).unwrap().interface(dummy.interface()).unwrap().build().await.unwrap();
let state = enums::ActiveConnectionState::from(active.state().await.unwrap_or_default());
let s = if let enums::ActiveConnectionState::Activating = state {
if let Ok(Some(s)) = timeout(Duration::from_secs(10), active.receive_state_changed().await.next()).await {
s.get().await.unwrap_or_default().into()
} else {
state
}
} else {
state
};
matches!(s, enums::ActiveConnectionState::Activated)
} else {
false
};
status = (Some((id, NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::Password(ssid.clone(), password.clone()),
success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
})), false);
break;
}
}
}
if status.0.is_none() {
status = (Some((id, NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::Password(ssid, password),
success: false,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
})), false);
}
status
}
Some(NetworkManagerRequest::SelectAccessPoint(ssid)) => {
let s = match NetworkManagerSettings::new(&conn).await {
Ok(s) => s,
Err(_) => return (None, State::Finished),
};
// find known connection with matching ssid and activate
let mut status = (None, false);
for c in s.list_connections().await.unwrap_or_default() {
let settings = match c.get_settings().await.ok() {
Some(s) => s,
None => continue,
};
let cur_ssid = settings
.get("802-11-wireless")
.and_then(|w| w.get("ssid"))
.cloned()
.and_then(|ssid| ssid.try_into().ok())
.and_then(|ssid| String::from_utf8(ssid).ok());
if cur_ssid.as_ref() != Some(&ssid) {
continue;
}
let success = if let Ok(path) = network_manager.deref().activate_connection(c.deref().path(), &ObjectPath::try_from("/").unwrap(), &ObjectPath::try_from("/").unwrap()).await {
.collect::<HashMap<_, _>>(),
);
map
})
.collect();
let updated = c.update(settings).await;
if updated.is_ok() {
let success = if let Ok(path) = network_manager
.deref()
.activate_connection(
c.deref().path(),
&ObjectPath::try_from("/").unwrap(),
&ObjectPath::try_from("/").unwrap(),
)
.await
{
// let active_conn = ActiveConnection::from(ActiveConnectionProxy::from(conn.1));
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
let active = ActiveConnectionProxy::builder(&conn).path(path).unwrap().destination(dummy.destination()).unwrap().interface(dummy.interface()).unwrap().build().await.unwrap();
let mut state = enums::ActiveConnectionState::from(active.state().await.unwrap_or_default());
while let enums::ActiveConnectionState::Activating = state {
if let Ok(Some(s)) = timeout(Duration::from_secs(20), active.receive_state_changed().await.next()).await {
state = s.get().await.unwrap_or_default().into();
let active = ActiveConnectionProxy::builder(&conn)
.path(path)
.unwrap()
.destination(dummy.destination())
.unwrap()
.interface(dummy.interface())
.unwrap()
.build()
.await
.unwrap();
let state = enums::ActiveConnectionState::from(
active.state().await.unwrap_or_default(),
);
let s = if let enums::ActiveConnectionState::Activating = state
{
if let Ok(Some(s)) = timeout(
Duration::from_secs(10),
active.receive_state_changed().await.next(),
)
.await
{
s.get().await.unwrap_or_default().into()
} else {
break;
state
}
} else {
state
};
matches!(state, enums::ActiveConnectionState::Activated)
matches!(s, enums::ActiveConnectionState::Activated)
} else {
false
};
status = (Some((id, NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::SelectAccessPoint(ssid.clone()),
success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
})), false);
status = (
Some((
id,
NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::Password(
ssid.clone(),
password.clone(),
),
success,
state: NetworkManagerState::new(&conn)
.await
.unwrap_or_default(),
},
)),
false,
);
}
break;
}
}
// create a connection
if status.0.is_none() {
for device in network_manager.devices().await.ok().unwrap_or_default() {
if matches!(
device.device_type().await.unwrap_or(DeviceType::Other),
DeviceType::Wifi
) {
let conn_settings: HashMap<&str, HashMap<&str, zvariant::Value>> =
HashMap::from([
(
"802-11-wireless".into(),
HashMap::from([(
"ssid".into(),
Value::Array(ssid.as_bytes().into()),
)]),
),
(
"connection".into(),
HashMap::from([
("id".into(), Value::Str(ssid.as_str().into())),
(
"type".into(),
Value::Str("802-11-wireless".into()),
),
]),
),
(
"802-11-wireless-security".into(),
HashMap::from([
(
"psk".into(),
Value::Str(password.as_str().into()),
),
("key-mgmt".into(), Value::Str("wpa-psk".into())),
]),
),
]);
let success = if let Ok((_, path)) = network_manager
.add_and_activate_connection(
conn_settings,
device.path(),
&ObjectPath::try_from("/").unwrap(),
)
.await
{
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
let active = ActiveConnectionProxy::builder(&conn)
.path(path)
.unwrap()
.destination(dummy.destination())
.unwrap()
.interface(dummy.interface())
.unwrap()
.build()
.await
.unwrap();
let state = enums::ActiveConnectionState::from(
active.state().await.unwrap_or_default(),
);
let s = if let enums::ActiveConnectionState::Activating = state
{
if let Ok(Some(s)) = timeout(
Duration::from_secs(10),
active.receive_state_changed().await.next(),
)
.await
{
s.get().await.unwrap_or_default().into()
} else {
state
}
} else {
state
};
matches!(s, enums::ActiveConnectionState::Activated)
} else {
false
};
status = (
Some((
id,
NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::Password(
ssid.clone(),
password.clone(),
),
success,
state: NetworkManagerState::new(&conn)
.await
.unwrap_or_default(),
},
)),
false,
);
break;
}
}
}
if status.0.is_none() {
status = (Some((id, NetworkManagerEvent::RequestResponse {
if status.0.is_none() {
status = (
Some((
id,
NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::Password(ssid, password),
success: false,
state: NetworkManagerState::new(&conn)
.await
.unwrap_or_default(),
},
)),
false,
);
}
status
}
Some(NetworkManagerRequest::SelectAccessPoint(ssid)) => {
let s = match NetworkManagerSettings::new(&conn).await {
Ok(s) => s,
Err(_) => return (None, State::Finished),
};
// find known connection with matching ssid and activate
let mut status = (None, false);
for c in s.list_connections().await.unwrap_or_default() {
let settings = match c.get_settings().await.ok() {
Some(s) => s,
None => continue,
};
let cur_ssid = settings
.get("802-11-wireless")
.and_then(|w| w.get("ssid"))
.cloned()
.and_then(|ssid| ssid.try_into().ok())
.and_then(|ssid| String::from_utf8(ssid).ok());
if cur_ssid.as_ref() != Some(&ssid) {
continue;
}
let success = if let Ok(path) = network_manager
.deref()
.activate_connection(
c.deref().path(),
&ObjectPath::try_from("/").unwrap(),
&ObjectPath::try_from("/").unwrap(),
)
.await
{
let dummy = ActiveConnectionProxy::new(&conn).await.unwrap();
let active = ActiveConnectionProxy::builder(&conn)
.path(path)
.unwrap()
.destination(dummy.destination())
.unwrap()
.interface(dummy.interface())
.unwrap()
.build()
.await
.unwrap();
let mut state = enums::ActiveConnectionState::from(
active.state().await.unwrap_or_default(),
);
while let enums::ActiveConnectionState::Activating = state {
if let Ok(Some(s)) = timeout(
Duration::from_secs(20),
active.receive_state_changed().await.next(),
)
.await
{
state = s.get().await.unwrap_or_default().into();
} else {
break;
}
}
matches!(state, enums::ActiveConnectionState::Activated)
} else {
false
};
status = (
Some((
id,
NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::SelectAccessPoint(ssid.clone()),
success,
state: NetworkManagerState::new(&conn)
.await
.unwrap_or_default(),
},
)),
false,
);
break;
}
if status.0.is_none() {
status = (
Some((
id,
NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::SelectAccessPoint(ssid.clone()),
success: false,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
})), false);
}
status
}
None => {
(None, true)
}
}}
_ = active_conns_changed.next().boxed().fuse() => {
(Some((id, NetworkManagerEvent::ActiveConns(NetworkManagerState::new(&conn).await.unwrap_or_default()))), false)
}
_ = devices_changed.next().boxed().fuse() => {
let devices = network_manager.devices().await.ok().unwrap_or_default();
let wireless_access_point_futures: Vec<_> = devices.into_iter().map(|device| async move {
if let Ok(Some(SpecificDevice::Wireless(wireless_device))) =
device.downcast_to_device().await
{
handle_wireless_device(wireless_device).await.unwrap_or_default()
} else {
Vec::new()
}
}).collect();
let mut wireless_access_points = Vec::with_capacity(wireless_access_point_futures.len());
for f in wireless_access_point_futures {
wireless_access_points.append(&mut f.await);
state: NetworkManagerState::new(&conn)
.await
.unwrap_or_default(),
},
)),
false,
);
}
(Some((id, NetworkManagerEvent::WirelessAccessPoints(NetworkManagerState::new(&conn).await.unwrap_or_default()))), false)
}
enabled = wireless_enabled_changed.next().boxed().fuse() => {
let update = if let Some(update) = enabled {
if let Ok(_) = update.get().await {
Some((id, NetworkManagerEvent::WiFiEnabled(NetworkManagerState::new(&conn).await.unwrap_or_default())))
}
else {
None
}
} else {
None
};
(update, false)
status
}
None => (None, true),
};
drop(active_conns_changed);
drop(wireless_enabled_changed);
drop(req);
(
update,
if should_exit {
@ -385,6 +517,7 @@ pub enum NetworkManagerEvent {
success: bool,
},
Init {
conn: Connection,
sender: UnboundedSender<NetworkManagerRequest>,
state: NetworkManagerState,
},

View file

@ -0,0 +1,56 @@
use super::{NetworkManagerEvent, NetworkManagerState};
use cosmic::iced::{self, subscription};
use cosmic_dbus_networkmanager::nm::NetworkManager;
use futures::StreamExt;
use log::error;
use std::fmt::Debug;
use std::hash::Hash;
use zbus::Connection;
pub fn wireless_enabled_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
id: I,
conn: Connection,
) -> iced::Subscription<(I, NetworkManagerEvent)> {
subscription::unfold(id, State::Continue(conn), move |mut state| async move {
loop {
let (update, new_state) = start_listening(id, state).await;
state = new_state;
if let Some(update) = update {
return (update, state);
}
}
})
}
#[derive(Debug, Clone)]
pub enum State {
Continue(Connection),
Error,
}
async fn start_listening<I: Copy + Debug>(
id: I,
state: State,
) -> (Option<(I, NetworkManagerEvent)>, State) {
let conn = match state {
State::Continue(conn) => conn,
State::Error => iced::futures::future::pending().await,
};
let network_manager = match NetworkManager::new(&conn).await {
Ok(n) => n,
Err(e) => {
error!("Failed to connect to NetworkManager: {}", e);
return (None, State::Error);
}
};
let mut wireless_enabled_changed = network_manager.receive_wireless_enabled_changed().await;
wireless_enabled_changed.next().await;
let new_state = NetworkManagerState::new(&conn).await.unwrap_or_default();
(
Some((id, NetworkManagerEvent::WiFiEnabled(new_state))),
State::Continue(conn),
)
}