fix(network): add connection if it doesn't exist when it is selected

This commit is contained in:
Ashley Wulber 2024-04-16 14:06:50 -04:00 committed by Michael Murphy
parent 8900b93c85
commit db47edf9b6
3 changed files with 155 additions and 202 deletions

View file

@ -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),

View file

@ -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<Vec<AccessPoint>> {
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>,
}

View file

@ -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<NetworkManagerEvent> = 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::<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 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::<HashMap<_, _>>(),
);
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"))
}
}