diff --git a/cosmic-applet-network/src/app.rs b/cosmic-applet-network/src/app.rs index f7546d45..ec61955d 100644 --- a/cosmic-applet-network/src/app.rs +++ b/cosmic-applet-network/src/app.rs @@ -293,8 +293,7 @@ impl cosmic::Application for CosmicNetworkApplet { { self.new_connection = None; } - } - if let NetworkManagerRequest::Password(ssid, _) = &req { + } else if let NetworkManagerRequest::Password(ssid, _) = &req { if let Some( NewConnectionState::EnterPassword { access_point, .. } | NewConnectionState::Waiting(access_point), diff --git a/cosmic-applet-network/src/network_manager/available_wifi.rs b/cosmic-applet-network/src/network_manager/available_wifi.rs index 9bf8ad2a..e8927f70 100644 --- a/cosmic-applet-network/src/network_manager/available_wifi.rs +++ b/cosmic-applet-network/src/network_manager/available_wifi.rs @@ -5,6 +5,7 @@ use cosmic_dbus_networkmanager::{device::wireless::WirelessDevice, interface::en use futures_util::StreamExt; use itertools::Itertools; use std::collections::HashMap; +use zbus::zvariant::ObjectPath; pub async fn handle_wireless_device(device: WirelessDevice<'_>) -> zbus::Result> { device.request_scan(HashMap::new()).await?; @@ -40,6 +41,7 @@ pub async fn handle_wireless_device(device: WirelessDevice<'_>) -> zbus::Result< strength, state, working: false, + path: ap.path().to_owned(), }, ); } @@ -56,4 +58,5 @@ pub struct AccessPoint { pub strength: u8, pub state: DeviceState, pub working: bool, + pub path: ObjectPath<'static>, } diff --git a/cosmic-applet-network/src/network_manager/mod.rs b/cosmic-applet-network/src/network_manager/mod.rs index ecc03c85..36c2eb63 100644 --- a/cosmic-applet-network/src/network_manager/mod.rs +++ b/cosmic-applet-network/src/network_manager/mod.rs @@ -23,7 +23,7 @@ use futures::{ }; use tokio::{process::Command, time::timeout}; use zbus::{ - zvariant::{self, ObjectPath, Value}, + zvariant::{self, ObjectPath, OwnedObjectPath, Value}, Connection, }; @@ -167,205 +167,18 @@ async fn start_listening( _ = output.send(response).await; } Some(NetworkManagerRequest::Password(ssid, password)) => { - let s = match NetworkManagerSettings::new(&conn).await { - Ok(s) => s, - Err(_) => return State::Finished, - }; + // Create a connection + let nm_state = NetworkManagerState::new(&conn).await.unwrap_or_default(); + let success = nm_state + .connect_wifi(&conn, &ssid, Some(&password)) + .await + .is_ok(); - let mut status: Option = None; - - // 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()); - 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::>(), - ); - 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 matches!(state, enums::ActiveConnectionState::Activating) - { - if let Ok(Some(s)) = timeout( - Duration::from_secs(10), - 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(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::Password( - ssid.clone(), - password.clone(), - ), - success, - state: NetworkManagerState::new(&conn) - .await - .unwrap_or_default(), - }); - } - - break; - } - } - - // create a connection - if status.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", - HashMap::from([( - "ssid", - Value::Array(ssid.as_bytes().into()), - )]), - ), - ( - "connection", - HashMap::from([ - ("id", Value::Str(ssid.as_str().into())), - ("type", Value::Str("802-11-wireless".into())), - ]), - ), - ( - "802-11-wireless-security", - HashMap::from([ - ("psk", Value::Str(password.as_str().into())), - ("key-mgmt", 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 mut state = enums::ActiveConnectionState::from( - active.state().await.unwrap_or_default(), - ); - while matches!(state, enums::ActiveConnectionState::Activating) - { - if let Ok(Some(s)) = timeout( - Duration::from_secs(10), - 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(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::Password( - ssid.clone(), - password.clone(), - ), - success, - state: NetworkManagerState::new(&conn) - .await - .unwrap_or_default(), - }); - - break; - } - } - } + let status = Some(NetworkManagerEvent::RequestResponse { + req: NetworkManagerRequest::Password(ssid.clone(), password.clone()), + success, + state: NetworkManagerState::new(&conn).await.unwrap_or_default(), + }); if let Some(e) = status { _ = output.send(e).await; @@ -452,10 +265,14 @@ async fn start_listening( return State::Waiting(conn, rx); } + + let state = NetworkManagerState::new(&conn).await.unwrap_or_default(); + let success = state.connect_wifi(&conn, &ssid, None).await.is_ok(); + _ = output .send(NetworkManagerEvent::RequestResponse { req: NetworkManagerRequest::SelectAccessPoint(ssid.clone()), - success: false, + success, state: NetworkManagerState::new(&conn).await.unwrap_or_default(), }) .await; @@ -619,4 +436,138 @@ impl NetworkManagerState { self.known_access_points = Vec::new(); self.wireless_access_points = Vec::new(); } + + async fn connect_wifi<'a>( + &self, + conn: &Connection, + ssid: &str, + password: Option<&str>, + ) -> anyhow::Result<()> { + let nm = NetworkManager::new(conn).await?; + + for c in nm.active_connections().await.unwrap_or_default() { + if self + .wireless_access_points + .iter() + .any(|w| Ok(Some(w.ssid.clone())) == c.cached_id()) + { + _ = nm.deactivate_connection(&c).await; + } + } + + let Some(ap) = self + .wireless_access_points + .iter() + .find(|ap| ap.ssid == ssid) + else { + return Err(anyhow::anyhow!("Access point not found")); + }; + + let mut conn_settings: HashMap<&str, HashMap<&str, zvariant::Value>> = HashMap::from([ + ( + "802-11-wireless", + HashMap::from([("ssid", Value::Array(ssid.as_bytes().into()))]), + ), + ( + "connection", + HashMap::from([ + ("id", Value::Str(ssid.into())), + ("type", Value::Str("802-11-wireless".into())), + ]), + ), + ]); + + if let Some(pass) = password { + conn_settings.insert( + "802-11-wireless-security", + HashMap::from([ + ("psk", Value::Str(pass.into())), + ("key-mgmt", Value::Str("wpa-psk".into())), + ]), + ); + } + + let devices = nm.devices().await?; + for device in devices { + if !matches!( + device.device_type().await.unwrap_or(DeviceType::Other), + DeviceType::Wifi + ) { + continue; + } + + // first check if the conn exists + let s = NetworkManagerSettings::new(conn).await?; + let known_conns = s.list_connections().await.unwrap_or_default(); + let mut known_conn = None; + for c in known_conns { + let settings = c.get_settings().await.ok().unwrap_or_default(); + let s = Settings::new(settings); + if let Some(cur_ssid) = s + .wifi + .clone() + .and_then(|w| w.ssid) + .and_then(|ssid| String::from_utf8(ssid).ok()) + { + if cur_ssid == ssid { + known_conn = Some(c); + break; + } + } + } + let active_conn = if let Some(known_conn) = known_conn.as_ref() { + // update settings if needed + let mut settings = known_conn.get_settings().await.ok().unwrap_or_default(); + settings.extend(conn_settings.iter().map(|s| { + let map = ( + s.0.to_string(), + s.1.iter() + .map(|(k, v)| (k.to_string(), v.clone().into())) + .collect::>(), + ); + map + })); + + known_conn.update(conn_settings).await?; + + nm.activate_connection(known_conn, &device).await? + } else { + nm.add_and_activate_connection(conn_settings, device.path(), &ap.path) + .await?; + let active_conns = nm.active_connections().await.unwrap_or_default(); + let Some(active_conn) = active_conns + .into_iter() + .find(|ac| ac.cached_id() == Ok(Some(ssid.to_string()))) + else { + return Err(anyhow::anyhow!("No active connection found")); + }; + active_conn + }; + let mut state = + enums::ActiveConnectionState::from(active_conn.state().await.unwrap_or_default()); + return match state { + ActiveConnectionState::Activating => { + if let Ok(Some(s)) = tokio::time::timeout( + Duration::from_secs(20), + active_conn.receive_state_changed().await.next(), + ) + .await + { + state = s.get().await.unwrap_or_default().into(); + if matches!(state, enums::ActiveConnectionState::Activated) { + Ok(()) + } else { + Err(anyhow::anyhow!("Failed to activate connection")) + } + } else { + Err(anyhow::anyhow!("Failed to activate connection")) + } + } + ActiveConnectionState::Activated => Ok(()), + _ => Err(anyhow::anyhow!("Failed to activate connection")), + }; + } + + Err(anyhow::anyhow!("No wifi device found")) + } }