cosmic-applets/cosmic-applet-network/src/network_manager/mod.rs
2023-01-10 14:53:27 -05:00

526 lines
27 KiB
Rust

pub mod available_wifi;
pub mod current_networks;
use std::{collections::HashMap, fmt::Debug, hash::Hash, ops::Deref, time::Duration};
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,
},
};
use futures::{
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
future::ok,
FutureExt, StreamExt,
};
use tokio::process::Command;
use zbus::{
zvariant::{self, ObjectPath, Value},
Connection,
};
use self::{
available_wifi::{handle_wireless_device, AccessPoint},
current_networks::{active_connections, ActiveConnectionInfo},
};
// TODO subscription for wifi list & selection of wifi
// TODO subscription & channel for enabling / disabling wifi
// TODO subscription for displaying active connections & devices
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))
}
#[derive(Debug)]
pub enum State {
Ready,
Waiting(Connection, UnboundedReceiver<NetworkManagerRequest>),
Finished,
}
async fn start_listening<I: Copy + Debug>(
id: I,
state: State,
) -> (Option<(I, NetworkManagerEvent)>, State) {
match state {
State::Ready => {
let conn = match Connection::system().await {
Ok(c) => c,
Err(_) => return (None, State::Finished),
};
let (tx, rx) = unbounded();
let nm_state = NetworkManagerState::new(&conn).await.unwrap_or_default();
return (
Some((
id,
NetworkManagerEvent::Init {
sender: tx,
state: nm_state,
},
)),
State::Waiting(conn, rx),
);
}
State::Waiting(conn, mut rx) => {
let network_manager = match NetworkManager::new(&conn).await {
Ok(n) => n,
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::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: true,
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()
.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 {
enums::ActiveConnectionState::from(active.receive_state_changed().await.next().await.unwrap().get().await.unwrap_or_default())
} 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 {
enums::ActiveConnectionState::from(active.receive_state_changed().await.next().await.unwrap().get().await.unwrap_or_default())
} 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);
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,
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 state = enums::ActiveConnectionState::from(active.state().await.unwrap_or_default());
let s = if let enums::ActiveConnectionState::Activating = state {
enums::ActiveConnectionState::from(active.receive_state_changed().await.next().await.unwrap().get().await.unwrap_or_default())
} else {
state
};
matches!(s, enums::ActiveConnectionState::Activated)
} else {
false
};
// dbg!(&success);
status = (Some((id, NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::SelectAccessPoint(ssid.clone()),
success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
})), false);
break;
}
// dbg!(&status);
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 {
enums::ActiveConnectionState::from(active.receive_state_changed().await.next().await.unwrap().get().await.unwrap_or_default())
} 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;
}
}
}
// dbg!(&status);
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() => {
let active_conns = active_connections(network_manager.active_connections().await.unwrap_or_default()).await.unwrap_or_default();
(Some((id, NetworkManagerEvent::ActiveConns(active_conns))), 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);
}
(Some((id, NetworkManagerEvent::WirelessAccessPoints(wireless_access_points))), 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)))
} else {
None
};
(update, false)
}
};
drop(active_conns_changed);
drop(wireless_enabled_changed);
drop(req);
(
update,
if should_exit {
State::Finished
} else {
State::Waiting(conn, rx)
},
)
}
State::Finished => iced::futures::future::pending().await,
}
}
#[derive(Debug, Clone)]
pub enum NetworkManagerRequest {
SetAirplaneMode(bool),
SetWiFi(bool),
SelectAccessPoint(String),
Password(String, String),
}
#[derive(Debug, Clone)]
pub enum NetworkManagerEvent {
RequestResponse {
req: NetworkManagerRequest,
state: NetworkManagerState,
success: bool,
},
Init {
sender: UnboundedSender<NetworkManagerRequest>,
state: NetworkManagerState,
},
WiFiEnabled(bool),
WirelessAccessPoints(Vec<AccessPoint>),
ActiveConns(Vec<ActiveConnectionInfo>),
}
#[derive(Debug, Clone, Default)]
pub struct NetworkManagerState {
pub wireless_access_points: Vec<AccessPoint>,
pub active_conns: Vec<ActiveConnectionInfo>,
pub known_access_points: Vec<AccessPoint>,
pub wifi_enabled: bool,
pub airplane_mode: bool,
}
impl NetworkManagerState {
pub async fn new(conn: &Connection) -> anyhow::Result<Self> {
let network_manager = NetworkManager::new(&conn).await?;
let mut _self = Self::default();
// airplane mode
let airplaine_mode = Command::new("rfkill")
.arg("list")
.arg("bluetooth")
.output()
.await?;
let airplane_mode = std::str::from_utf8(&airplaine_mode.stdout).unwrap_or_default();
let bluetooth_disabled = airplane_mode.contains("Soft blocked: yes");
if !network_manager.wireless_enabled().await.unwrap_or_default() {
_self.airplane_mode = bluetooth_disabled;
return Ok(_self);
} else {
_self.wifi_enabled = true;
};
let s = NetworkManagerSettings::new(&conn).await?;
let known_conns = s.list_connections().await.unwrap_or_default();
let mut active_conns = active_connections(
network_manager
.active_connections()
.await
.unwrap_or_default(),
)
.await
.unwrap_or_default();
active_conns.sort_by(|a, b| {
let helper = |conn: &ActiveConnectionInfo| match conn {
ActiveConnectionInfo::Vpn { name, .. } => format!("0{name}"),
ActiveConnectionInfo::Wired { name, .. } => format!("1{name}"),
ActiveConnectionInfo::WiFi { name, .. } => format!("2{name}"),
};
helper(a).cmp(&helper(b))
});
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 {
let mut access_points = f.await;
wireless_access_points.append(&mut access_points);
}
let mut known_ssid = Vec::with_capacity(known_conns.len());
for c in known_conns {
let s = c.get_settings().await.unwrap();
let s = Settings::new(s);
if let Some(cur_ssid) = s
.wifi
.clone()
.and_then(|w| w.ssid)
.and_then(|ssid| String::from_utf8(ssid).ok())
{
known_ssid.push(cur_ssid);
}
}
let known_access_points: Vec<_> = wireless_access_points
.iter()
.filter(|a| {
known_ssid.contains(&a.ssid) && !active_conns.iter().any(|ac| ac.name() == a.ssid)
})
.cloned()
.collect();
wireless_access_points.sort_by(|a, b| b.strength.cmp(&a.strength));
_self.wireless_access_points = wireless_access_points;
_self.active_conns = active_conns;
_self.known_access_points = known_access_points;
Ok(_self)
}
}