diff --git a/cosmic-applet-network/src/app.rs b/cosmic-applet-network/src/app.rs index f055467f..5d0f6128 100644 --- a/cosmic-applet-network/src/app.rs +++ b/cosmic-applet-network/src/app.rs @@ -20,7 +20,7 @@ use cosmic::{ widget::{button, divider, icon, toggler}, Element, Theme, }; -use cosmic_dbus_networkmanager::interface::enums::DeviceState; +use cosmic_dbus_networkmanager::interface::enums::{ActiveConnectionState, DeviceState}; use futures::channel::mpsc::UnboundedSender; use crate::network_manager::NetworkManagerState; @@ -47,12 +47,26 @@ enum NewConnectionState { Failure(AccessPoint), } +impl NewConnectionState { + pub fn ssid(&self) -> &str { + &match self { + NewConnectionState::EnterPassword { + access_point, + password: _, + } => access_point, + NewConnectionState::Waiting(ap) => ap, + NewConnectionState::Failure(ap) => ap, + } + .ssid + } +} + impl Into for NewConnectionState { fn into(self) -> AccessPoint { match self { NewConnectionState::EnterPassword { access_point, - password, + password: _, } => access_point, NewConnectionState::Waiting(access_point) => access_point, NewConnectionState::Failure(access_point) => access_point, @@ -100,6 +114,7 @@ impl CosmicNetworkApplet { #[derive(Debug, Clone)] enum Message { ActivateKnownWifi(String), + Disconnect(String), TogglePopup, ToggleAirplaneMode(bool), ToggleWiFi(bool), @@ -137,6 +152,7 @@ impl Application for CosmicNetworkApplet { match message { Message::TogglePopup => { if let Some(p) = self.popup.take() { + self.show_visible_networks = false; return destroy_popup(p); } else { // TODO request update of state maybe @@ -184,17 +200,14 @@ impl Application for CosmicNetworkApplet { self.nm_state = state; self.update_icon_name(); } - NetworkManagerEvent::WiFiEnabled(enabled) => { - if !enabled { - self.nm_state.clear(); - } - self.nm_state.wifi_enabled = enabled; + NetworkManagerEvent::WiFiEnabled(state) => { + self.nm_state = state; } - NetworkManagerEvent::WirelessAccessPoints(access_points) => { - self.nm_state.wireless_access_points = access_points; + NetworkManagerEvent::WirelessAccessPoints(state) => { + self.nm_state = state; } - NetworkManagerEvent::ActiveConns(conns) => { - self.nm_state.active_conns = conns; + NetworkManagerEvent::ActiveConns(state) => { + self.nm_state = state; self.update_icon_name(); } NetworkManagerEvent::RequestResponse { @@ -204,26 +217,21 @@ impl Application for CosmicNetworkApplet { } => { if success { match req { - NetworkManagerRequest::SetAirplaneMode(_) - | NetworkManagerRequest::SetWiFi(_) => {} - NetworkManagerRequest::SelectAccessPoint(_) - | NetworkManagerRequest::Password(_, _) => { - self.new_connection.take(); - self.show_visible_networks = false; + NetworkManagerRequest::SelectAccessPoint(ssid) + | NetworkManagerRequest::Password(ssid, _) => { + if self + .new_connection + .as_ref() + .map(|c| c.ssid() == ssid) + .unwrap_or_default() + { + self.new_connection.take(); + } } + _ => {} } } else { match req { - NetworkManagerRequest::SetAirplaneMode(_) - | NetworkManagerRequest::SetWiFi(_) => {} - NetworkManagerRequest::SelectAccessPoint(_) => { - if let Some(NewConnectionState::Waiting(access_point)) = - self.new_connection.as_ref() - { - self.new_connection - .replace(NewConnectionState::Failure(access_point.clone())); - } - } NetworkManagerRequest::Password(_, _) => { if let Some(NewConnectionState::EnterPassword { access_point, @@ -234,6 +242,7 @@ impl Application for CosmicNetworkApplet { .replace(NewConnectionState::Failure(access_point.clone())); } } + _ => {} } } self.nm_state = state; @@ -292,6 +301,13 @@ impl Application for CosmicNetworkApplet { } Message::ActivateKnownWifi(ssid) => { let tx = if let Some(tx) = self.nm_sender.as_ref() { + self.nm_state + .known_access_points + .iter_mut() + .find(|c| c.ssid == ssid) + .map(|ap| { + ap.working = true; + }); tx } else { return Command::none(); @@ -301,6 +317,24 @@ impl Application for CosmicNetworkApplet { Message::CancelNewConnection => { self.new_connection.take(); } + Message::Disconnect(ssid) => { + let tx = if let Some(tx) = self.nm_sender.as_ref() { + self.nm_state + .active_conns + .iter_mut() + .find(|c| c.name() == ssid) + .map(|ap| match ap { + ActiveConnectionInfo::WiFi { state, .. } => { + *state = ActiveConnectionState::Deactivating; + } + _ => {} + }); + tx + } else { + return Command::none(); + }; + let _ = tx.unbounded_send(NetworkManagerRequest::Disconnect(ssid)); + } } Command::none() } @@ -341,7 +375,7 @@ impl Application for CosmicNetworkApplet { } ActiveConnectionInfo::Wired { name, - hw_address, + hw_address: _, speed, ip_addresses, } => { @@ -366,7 +400,10 @@ impl Application for CosmicNetworkApplet { ); } ActiveConnectionInfo::WiFi { - name, ip_addresses, .. + name, + ip_addresses, + state, + .. } => { let mut ipv4 = Vec::with_capacity(ip_addresses.len()); for addr in ip_addresses { @@ -376,52 +413,81 @@ impl Application for CosmicNetworkApplet { .into(), ); } - known_wifi = known_wifi.push(column![button(Button::Secondary) - .custom(vec![ - icon("network-wireless-symbolic", 24) - .style(Svg::Custom(|theme| svg::Appearance { - color: Some(theme.palette().text), - })) - .width(Length::Units(24)) - .height(Length::Units(24)) - .into(), - column![text(name).size(14), Column::with_children(ipv4)] - .into(), + let mut btn_content = vec![ + icon("network-wireless-symbolic", 24) + .style(Svg::Custom(|theme| svg::Appearance { + color: Some(theme.palette().text), + })) + .width(Length::Units(24)) + .height(Length::Units(24)) + .into(), + column![text(name).size(14), Column::with_children(ipv4)] + .width(Length::Fill) + .into(), + ]; + match state { + ActiveConnectionState::Activating + | ActiveConnectionState::Deactivating => { + btn_content.push( + icon("process-working-symbolic", 24) + .style(Svg::Custom(|theme| svg::Appearance { + color: Some(theme.palette().text), + })) + .width(Length::Units(24)) + .height(Length::Units(24)) + .into(), + ); + } + ActiveConnectionState::Activated => btn_content.push( text(format!("{}", fl!("connected"))) .size(14) - .width(Length::Fill) - .height(Length::Units(24)) .horizontal_alignment(Horizontal::Right) .vertical_alignment(Vertical::Center) - .into() - ]) - .padding([8, 24]) - .style(button_style.clone())]); + .into(), + ), + _ => {} + }; + known_wifi = known_wifi.push( + column![button(Button::Secondary) + .custom(btn_content) + .padding([8, 24]) + .style(button_style.clone()) + .on_press(Message::Disconnect(name.clone()))] + .align_items(Alignment::Center), + ); } }; } for known in &self.nm_state.known_access_points { - let mut btn = button(Button::Secondary) - .custom(vec![ - icon("network-wireless-symbolic", 24) + let mut btn_content = vec![ + icon("network-wireless-symbolic", 24) + .style(Svg::Custom(|theme| svg::Appearance { + color: Some(theme.palette().text), + })) + .width(Length::Units(24)) + .height(Length::Units(24)) + .into(), + text(&known.ssid).size(14).width(Length::Fill).into(), + ]; + + if known.working { + btn_content.push( + icon("process-working-symbolic", 24) .style(Svg::Custom(|theme| svg::Appearance { color: Some(theme.palette().text), })) .width(Length::Units(24)) .height(Length::Units(24)) .into(), - text(&known.ssid).size(14).into(), - ]) + ); + } + + let mut btn = button(Button::Secondary) + .custom(btn_content) .padding([8, 24]) .width(Length::Fill) .style(button_style.clone()); btn = match known.state { - // DeviceState::Prepare => todo!(), - // DeviceState::Config => todo!(), - // DeviceState::NeedAuth => todo!(), - // DeviceState::IpConfig => todo!(), - // DeviceState::IpCheck => todo!(), - // DeviceState::Secondaries => todo!(), DeviceState::Failed | DeviceState::Unknown | DeviceState::Unmanaged @@ -429,6 +495,9 @@ impl Application for CosmicNetworkApplet { | DeviceState::NeedAuth => { btn.on_press(Message::ActivateKnownWifi(known.ssid.clone())) } + DeviceState::Activated => { + btn.on_press(Message::Disconnect(known.ssid.clone())) + } _ => btn, }; known_wifi = known_wifi.push(row![btn].align_items(Alignment::Center)); diff --git a/cosmic-applet-network/src/network_manager/available_wifi.rs b/cosmic-applet-network/src/network_manager/available_wifi.rs index 62e90e86..c966fd0b 100644 --- a/cosmic-applet-network/src/network_manager/available_wifi.rs +++ b/cosmic-applet-network/src/network_manager/available_wifi.rs @@ -39,6 +39,7 @@ pub async fn handle_wireless_device(device: WirelessDevice<'_>) -> zbus::Result< ssid, strength, state: state, + working: false, }, ); } @@ -55,4 +56,5 @@ pub struct AccessPoint { pub ssid: String, pub strength: u8, pub state: DeviceState, + pub working: bool, } diff --git a/cosmic-applet-network/src/network_manager/current_networks.rs b/cosmic-applet-network/src/network_manager/current_networks.rs index 9024770f..a347e0d3 100644 --- a/cosmic-applet-network/src/network_manager/current_networks.rs +++ b/cosmic-applet-network/src/network_manager/current_networks.rs @@ -3,7 +3,7 @@ use cosmic_dbus_networkmanager::{ active_connection::ActiveConnection, device::SpecificDevice, - interface::enums::{ApFlags, ApSecurityFlags}, + interface::enums::{ActiveConnectionState, ApFlags, ApSecurityFlags}, }; use std::net::Ipv4Addr; @@ -19,6 +19,10 @@ pub async fn active_connections( .await .unwrap_or_default(); let addresses: Vec<_> = ipv4.iter().map(|d| d.address).collect(); + let state = connection + .state() + .await + .unwrap_or_else(|_| ActiveConnectionState::Unknown); if connection.vpn().await.unwrap_or_default() { info.push(ActiveConnectionInfo::Vpn { @@ -51,6 +55,7 @@ pub async fn active_connections( flags: access_point.flags().await?, rsn_flags: access_point.rsn_flags().await?, wpa_flags: access_point.wpa_flags().await?, + state, }); } } @@ -92,6 +97,7 @@ pub enum ActiveConnectionInfo { flags: ApFlags, rsn_flags: ApSecurityFlags, wpa_flags: ApSecurityFlags, + state: ActiveConnectionState, }, Vpn { name: String, diff --git a/cosmic-applet-network/src/network_manager/mod.rs b/cosmic-applet-network/src/network_manager/mod.rs index 79eb869a..72ac3707 100644 --- a/cosmic-applet-network/src/network_manager/mod.rs +++ b/cosmic-applet-network/src/network_manager/mod.rs @@ -5,21 +5,13 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, ops::Deref, time::Durati use cosmic::iced::{self, subscription}; use cosmic_dbus_networkmanager::{ - active_connection::ActiveConnection, device::SpecificDevice, - interface::{ - active_connection::ActiveConnectionProxy, enums, enums::DeviceType, - settings::connection::ConnectionSettingsProxy, - }, - nm::{self, NetworkManager}, - settings::{ - connection::{ConnectionSettings, Secrets, Settings}, - NetworkManagerSettings, - }, + interface::{active_connection::ActiveConnectionProxy, enums, enums::DeviceType}, + nm::NetworkManager, + settings::{connection::Settings, NetworkManagerSettings}, }; use futures::{ channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, - future::ok, FutureExt, StreamExt, }; use tokio::{process::Command, time::timeout}; @@ -91,6 +83,22 @@ async fn start_listening( 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; + } + } + } + (Some((id, + NetworkManagerEvent::RequestResponse { + req: NetworkManagerRequest::Disconnect(ssid.clone()), + success, + state: NetworkManagerState::new(&conn).await.unwrap_or_default(), + })), false) + } Some(NetworkManagerRequest::SetAirplaneMode(airplane_mode)) => { // wifi let mut success = network_manager.set_wireless_enabled(!airplane_mode).await.is_ok(); @@ -103,7 +111,7 @@ async fn start_listening( .is_ok(); let response = NetworkManagerEvent::RequestResponse { req: NetworkManagerRequest::SetAirplaneMode(airplane_mode), - success: true, + success, state: NetworkManagerState::new(&conn).await.unwrap_or_default(), }; (Some((id, response)), false) @@ -256,9 +264,6 @@ async fn start_listening( // find known connection with matching ssid and activate let mut status = (None, false); - let devices = network_manager.devices().await.ok().unwrap_or_default(); - - for c in s.list_connections().await.unwrap_or_default() { let settings = match c.get_settings().await.ok() { Some(s) => s, @@ -279,17 +284,15 @@ async fn start_listening( 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 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() + 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 { - state + break; } - } else { - state }; - matches!(s, enums::ActiveConnectionState::Activated) + matches!(state, enums::ActiveConnectionState::Activated) } else { false }; @@ -301,59 +304,6 @@ async fn start_listening( break; } - let mut ap = None; - - for d in &devices { - if let Ok(Some(SpecificDevice::Wireless(wireless_device))) = - d.downcast_to_device().await { - for a in wireless_device.access_points().await.ok().unwrap_or_default() { - if String::from_utf8(a.ssid().await.unwrap_or_default()).unwrap_or_default() == ssid { - ap = Some(a); - break; - } - } - } - }; - 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())), - ])), - ]); - let success = if let Ok((_, path)) = network_manager.add_and_activate_connection(conn_settings, device.path(), &ap.as_ref().map(|ap| ap.path().clone()).unwrap_or_else(||ObjectPath::try_from("/").unwrap().into_owned())).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::SelectAccessPoint(ssid.clone()), - success, - state: NetworkManagerState::new(&conn).await.unwrap_or_default(), - })), false); - - break; - } - } - } if status.0.is_none() { status = (Some((id, NetworkManagerEvent::RequestResponse { @@ -369,9 +319,7 @@ async fn start_listening( } }} _ = active_conns_changed.next().boxed().fuse() => { - let active_conns = active_connections(network_manager.active_connections().await.unwrap_or_default()).await.unwrap_or_default(); - - (Some((id, NetworkManagerEvent::ActiveConns(active_conns))), false) + (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(); @@ -388,11 +336,16 @@ async fn start_listening( for f in wireless_access_point_futures { wireless_access_points.append(&mut f.await); } - (Some((id, NetworkManagerEvent::WirelessAccessPoints(wireless_access_points))), 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 { - update.get().await.ok().map(|update| (id, NetworkManagerEvent::WiFiEnabled(update))) + if let Ok(_) = update.get().await { + Some((id, NetworkManagerEvent::WiFiEnabled(NetworkManagerState::new(&conn).await.unwrap_or_default()))) + } + else { + None + } } else { None }; @@ -420,6 +373,7 @@ pub enum NetworkManagerRequest { SetAirplaneMode(bool), SetWiFi(bool), SelectAccessPoint(String), + Disconnect(String), Password(String, String), } @@ -434,9 +388,9 @@ pub enum NetworkManagerEvent { sender: UnboundedSender, state: NetworkManagerState, }, - WiFiEnabled(bool), - WirelessAccessPoints(Vec), - ActiveConns(Vec), + WiFiEnabled(NetworkManagerState), + WirelessAccessPoints(NetworkManagerState), + ActiveConns(NetworkManagerState), } #[derive(Debug, Clone, Default)]