feat(network): improve state handling & add disconnect option

This commit is contained in:
Ashley Wulber 2023-02-14 20:08:10 -05:00
parent d621fb8936
commit a5fe9e8d77
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
4 changed files with 173 additions and 142 deletions

View file

@ -20,7 +20,7 @@ use cosmic::{
widget::{button, divider, icon, toggler}, widget::{button, divider, icon, toggler},
Element, Theme, Element, Theme,
}; };
use cosmic_dbus_networkmanager::interface::enums::DeviceState; use cosmic_dbus_networkmanager::interface::enums::{ActiveConnectionState, DeviceState};
use futures::channel::mpsc::UnboundedSender; use futures::channel::mpsc::UnboundedSender;
use crate::network_manager::NetworkManagerState; use crate::network_manager::NetworkManagerState;
@ -47,12 +47,26 @@ enum NewConnectionState {
Failure(AccessPoint), 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<AccessPoint> for NewConnectionState { impl Into<AccessPoint> for NewConnectionState {
fn into(self) -> AccessPoint { fn into(self) -> AccessPoint {
match self { match self {
NewConnectionState::EnterPassword { NewConnectionState::EnterPassword {
access_point, access_point,
password, password: _,
} => access_point, } => access_point,
NewConnectionState::Waiting(access_point) => access_point, NewConnectionState::Waiting(access_point) => access_point,
NewConnectionState::Failure(access_point) => access_point, NewConnectionState::Failure(access_point) => access_point,
@ -100,6 +114,7 @@ impl CosmicNetworkApplet {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message { enum Message {
ActivateKnownWifi(String), ActivateKnownWifi(String),
Disconnect(String),
TogglePopup, TogglePopup,
ToggleAirplaneMode(bool), ToggleAirplaneMode(bool),
ToggleWiFi(bool), ToggleWiFi(bool),
@ -137,6 +152,7 @@ impl Application for CosmicNetworkApplet {
match message { match message {
Message::TogglePopup => { Message::TogglePopup => {
if let Some(p) = self.popup.take() { if let Some(p) = self.popup.take() {
self.show_visible_networks = false;
return destroy_popup(p); return destroy_popup(p);
} else { } else {
// TODO request update of state maybe // TODO request update of state maybe
@ -184,17 +200,14 @@ impl Application for CosmicNetworkApplet {
self.nm_state = state; self.nm_state = state;
self.update_icon_name(); self.update_icon_name();
} }
NetworkManagerEvent::WiFiEnabled(enabled) => { NetworkManagerEvent::WiFiEnabled(state) => {
if !enabled { self.nm_state = state;
self.nm_state.clear();
}
self.nm_state.wifi_enabled = enabled;
} }
NetworkManagerEvent::WirelessAccessPoints(access_points) => { NetworkManagerEvent::WirelessAccessPoints(state) => {
self.nm_state.wireless_access_points = access_points; self.nm_state = state;
} }
NetworkManagerEvent::ActiveConns(conns) => { NetworkManagerEvent::ActiveConns(state) => {
self.nm_state.active_conns = conns; self.nm_state = state;
self.update_icon_name(); self.update_icon_name();
} }
NetworkManagerEvent::RequestResponse { NetworkManagerEvent::RequestResponse {
@ -204,26 +217,21 @@ impl Application for CosmicNetworkApplet {
} => { } => {
if success { if success {
match req { match req {
NetworkManagerRequest::SetAirplaneMode(_) NetworkManagerRequest::SelectAccessPoint(ssid)
| NetworkManagerRequest::SetWiFi(_) => {} | NetworkManagerRequest::Password(ssid, _) => {
NetworkManagerRequest::SelectAccessPoint(_) if self
| NetworkManagerRequest::Password(_, _) => { .new_connection
self.new_connection.take(); .as_ref()
self.show_visible_networks = false; .map(|c| c.ssid() == ssid)
.unwrap_or_default()
{
self.new_connection.take();
}
} }
_ => {}
} }
} else { } else {
match req { 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(_, _) => { NetworkManagerRequest::Password(_, _) => {
if let Some(NewConnectionState::EnterPassword { if let Some(NewConnectionState::EnterPassword {
access_point, access_point,
@ -234,6 +242,7 @@ impl Application for CosmicNetworkApplet {
.replace(NewConnectionState::Failure(access_point.clone())); .replace(NewConnectionState::Failure(access_point.clone()));
} }
} }
_ => {}
} }
} }
self.nm_state = state; self.nm_state = state;
@ -292,6 +301,13 @@ impl Application for CosmicNetworkApplet {
} }
Message::ActivateKnownWifi(ssid) => { Message::ActivateKnownWifi(ssid) => {
let tx = if let Some(tx) = self.nm_sender.as_ref() { 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 tx
} else { } else {
return Command::none(); return Command::none();
@ -301,6 +317,24 @@ impl Application for CosmicNetworkApplet {
Message::CancelNewConnection => { Message::CancelNewConnection => {
self.new_connection.take(); 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() Command::none()
} }
@ -341,7 +375,7 @@ impl Application for CosmicNetworkApplet {
} }
ActiveConnectionInfo::Wired { ActiveConnectionInfo::Wired {
name, name,
hw_address, hw_address: _,
speed, speed,
ip_addresses, ip_addresses,
} => { } => {
@ -366,7 +400,10 @@ impl Application for CosmicNetworkApplet {
); );
} }
ActiveConnectionInfo::WiFi { ActiveConnectionInfo::WiFi {
name, ip_addresses, .. name,
ip_addresses,
state,
..
} => { } => {
let mut ipv4 = Vec::with_capacity(ip_addresses.len()); let mut ipv4 = Vec::with_capacity(ip_addresses.len());
for addr in ip_addresses { for addr in ip_addresses {
@ -376,52 +413,81 @@ impl Application for CosmicNetworkApplet {
.into(), .into(),
); );
} }
known_wifi = known_wifi.push(column![button(Button::Secondary) let mut btn_content = vec![
.custom(vec![ icon("network-wireless-symbolic", 24)
icon("network-wireless-symbolic", 24) .style(Svg::Custom(|theme| svg::Appearance {
.style(Svg::Custom(|theme| svg::Appearance { color: Some(theme.palette().text),
color: Some(theme.palette().text), }))
})) .width(Length::Units(24))
.width(Length::Units(24)) .height(Length::Units(24))
.height(Length::Units(24)) .into(),
.into(), column![text(name).size(14), Column::with_children(ipv4)]
column![text(name).size(14), Column::with_children(ipv4)] .width(Length::Fill)
.into(), .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"))) text(format!("{}", fl!("connected")))
.size(14) .size(14)
.width(Length::Fill)
.height(Length::Units(24))
.horizontal_alignment(Horizontal::Right) .horizontal_alignment(Horizontal::Right)
.vertical_alignment(Vertical::Center) .vertical_alignment(Vertical::Center)
.into() .into(),
]) ),
.padding([8, 24]) _ => {}
.style(button_style.clone())]); };
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 { for known in &self.nm_state.known_access_points {
let mut btn = button(Button::Secondary) let mut btn_content = vec![
.custom(vec![ icon("network-wireless-symbolic", 24)
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 { .style(Svg::Custom(|theme| svg::Appearance {
color: Some(theme.palette().text), color: Some(theme.palette().text),
})) }))
.width(Length::Units(24)) .width(Length::Units(24))
.height(Length::Units(24)) .height(Length::Units(24))
.into(), .into(),
text(&known.ssid).size(14).into(), );
]) }
let mut btn = button(Button::Secondary)
.custom(btn_content)
.padding([8, 24]) .padding([8, 24])
.width(Length::Fill) .width(Length::Fill)
.style(button_style.clone()); .style(button_style.clone());
btn = match known.state { 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::Failed
| DeviceState::Unknown | DeviceState::Unknown
| DeviceState::Unmanaged | DeviceState::Unmanaged
@ -429,6 +495,9 @@ impl Application for CosmicNetworkApplet {
| DeviceState::NeedAuth => { | DeviceState::NeedAuth => {
btn.on_press(Message::ActivateKnownWifi(known.ssid.clone())) btn.on_press(Message::ActivateKnownWifi(known.ssid.clone()))
} }
DeviceState::Activated => {
btn.on_press(Message::Disconnect(known.ssid.clone()))
}
_ => btn, _ => btn,
}; };
known_wifi = known_wifi.push(row![btn].align_items(Alignment::Center)); known_wifi = known_wifi.push(row![btn].align_items(Alignment::Center));

View file

@ -39,6 +39,7 @@ pub async fn handle_wireless_device(device: WirelessDevice<'_>) -> zbus::Result<
ssid, ssid,
strength, strength,
state: state, state: state,
working: false,
}, },
); );
} }
@ -55,4 +56,5 @@ pub struct AccessPoint {
pub ssid: String, pub ssid: String,
pub strength: u8, pub strength: u8,
pub state: DeviceState, pub state: DeviceState,
pub working: bool,
} }

View file

@ -3,7 +3,7 @@
use cosmic_dbus_networkmanager::{ use cosmic_dbus_networkmanager::{
active_connection::ActiveConnection, active_connection::ActiveConnection,
device::SpecificDevice, device::SpecificDevice,
interface::enums::{ApFlags, ApSecurityFlags}, interface::enums::{ActiveConnectionState, ApFlags, ApSecurityFlags},
}; };
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
@ -19,6 +19,10 @@ pub async fn active_connections(
.await .await
.unwrap_or_default(); .unwrap_or_default();
let addresses: Vec<_> = ipv4.iter().map(|d| d.address).collect(); 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() { if connection.vpn().await.unwrap_or_default() {
info.push(ActiveConnectionInfo::Vpn { info.push(ActiveConnectionInfo::Vpn {
@ -51,6 +55,7 @@ pub async fn active_connections(
flags: access_point.flags().await?, flags: access_point.flags().await?,
rsn_flags: access_point.rsn_flags().await?, rsn_flags: access_point.rsn_flags().await?,
wpa_flags: access_point.wpa_flags().await?, wpa_flags: access_point.wpa_flags().await?,
state,
}); });
} }
} }
@ -92,6 +97,7 @@ pub enum ActiveConnectionInfo {
flags: ApFlags, flags: ApFlags,
rsn_flags: ApSecurityFlags, rsn_flags: ApSecurityFlags,
wpa_flags: ApSecurityFlags, wpa_flags: ApSecurityFlags,
state: ActiveConnectionState,
}, },
Vpn { Vpn {
name: String, name: String,

View file

@ -5,21 +5,13 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, ops::Deref, time::Durati
use cosmic::iced::{self, subscription}; use cosmic::iced::{self, subscription};
use cosmic_dbus_networkmanager::{ use cosmic_dbus_networkmanager::{
active_connection::ActiveConnection,
device::SpecificDevice, device::SpecificDevice,
interface::{ interface::{active_connection::ActiveConnectionProxy, enums, enums::DeviceType},
active_connection::ActiveConnectionProxy, enums, enums::DeviceType, nm::NetworkManager,
settings::connection::ConnectionSettingsProxy, settings::{connection::Settings, NetworkManagerSettings},
},
nm::{self, NetworkManager},
settings::{
connection::{ConnectionSettings, Secrets, Settings},
NetworkManagerSettings,
},
}; };
use futures::{ use futures::{
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
future::ok,
FutureExt, StreamExt, FutureExt, StreamExt,
}; };
use tokio::{process::Command, time::timeout}; use tokio::{process::Command, time::timeout};
@ -91,6 +83,22 @@ async fn start_listening<I: Copy + Debug>(
let (update, should_exit) = futures::select! { let (update, should_exit) = futures::select! {
req = req => { req = req => {
match 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)) => { Some(NetworkManagerRequest::SetAirplaneMode(airplane_mode)) => {
// wifi // wifi
let mut success = network_manager.set_wireless_enabled(!airplane_mode).await.is_ok(); let mut success = network_manager.set_wireless_enabled(!airplane_mode).await.is_ok();
@ -103,7 +111,7 @@ async fn start_listening<I: Copy + Debug>(
.is_ok(); .is_ok();
let response = NetworkManagerEvent::RequestResponse { let response = NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::SetAirplaneMode(airplane_mode), req: NetworkManagerRequest::SetAirplaneMode(airplane_mode),
success: true, success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(), state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
}; };
(Some((id, response)), false) (Some((id, response)), false)
@ -256,9 +264,6 @@ async fn start_listening<I: Copy + Debug>(
// find known connection with matching ssid and activate // find known connection with matching ssid and activate
let mut status = (None, false); 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() { for c in s.list_connections().await.unwrap_or_default() {
let settings = match c.get_settings().await.ok() { let settings = match c.get_settings().await.ok() {
Some(s) => s, Some(s) => s,
@ -279,17 +284,15 @@ async fn start_listening<I: Copy + Debug>(
let success = if let Ok(path) = network_manager.deref().activate_connection(c.deref().path(), &ObjectPath::try_from("/").unwrap(), &ObjectPath::try_from("/").unwrap()).await { 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 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 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 mut state = enums::ActiveConnectionState::from(active.state().await.unwrap_or_default());
let s = if let enums::ActiveConnectionState::Activating = state { while let enums::ActiveConnectionState::Activating = state {
if let Ok(Some(s)) = timeout(Duration::from_secs(10), active.receive_state_changed().await.next()).await { if let Ok(Some(s)) = timeout(Duration::from_secs(20), active.receive_state_changed().await.next()).await {
s.get().await.unwrap_or_default().into() state = s.get().await.unwrap_or_default().into();
} else { } else {
state break;
} }
} else {
state
}; };
matches!(s, enums::ActiveConnectionState::Activated) matches!(state, enums::ActiveConnectionState::Activated)
} else { } else {
false false
}; };
@ -301,59 +304,6 @@ async fn start_listening<I: Copy + Debug>(
break; 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() { if status.0.is_none() {
status = (Some((id, NetworkManagerEvent::RequestResponse { status = (Some((id, NetworkManagerEvent::RequestResponse {
@ -369,9 +319,7 @@ async fn start_listening<I: Copy + Debug>(
} }
}} }}
_ = active_conns_changed.next().boxed().fuse() => { _ = 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(NetworkManagerState::new(&conn).await.unwrap_or_default()))), false)
(Some((id, NetworkManagerEvent::ActiveConns(active_conns))), false)
} }
_ = devices_changed.next().boxed().fuse() => { _ = devices_changed.next().boxed().fuse() => {
let devices = network_manager.devices().await.ok().unwrap_or_default(); let devices = network_manager.devices().await.ok().unwrap_or_default();
@ -388,11 +336,16 @@ async fn start_listening<I: Copy + Debug>(
for f in wireless_access_point_futures { for f in wireless_access_point_futures {
wireless_access_points.append(&mut f.await); 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() => { enabled = wireless_enabled_changed.next().boxed().fuse() => {
let update = if let Some(update) = enabled { 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 { } else {
None None
}; };
@ -420,6 +373,7 @@ pub enum NetworkManagerRequest {
SetAirplaneMode(bool), SetAirplaneMode(bool),
SetWiFi(bool), SetWiFi(bool),
SelectAccessPoint(String), SelectAccessPoint(String),
Disconnect(String),
Password(String, String), Password(String, String),
} }
@ -434,9 +388,9 @@ pub enum NetworkManagerEvent {
sender: UnboundedSender<NetworkManagerRequest>, sender: UnboundedSender<NetworkManagerRequest>,
state: NetworkManagerState, state: NetworkManagerState,
}, },
WiFiEnabled(bool), WiFiEnabled(NetworkManagerState),
WirelessAccessPoints(Vec<AccessPoint>), WirelessAccessPoints(NetworkManagerState),
ActiveConns(Vec<ActiveConnectionInfo>), ActiveConns(NetworkManagerState),
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]