Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cosmic-applet-network/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ nm-secret-agent-manager = { git = "https://github.com/pop-os/dbus-settings-bindi
indexmap = "2.13.0"
secure-string = "0.3.0"
uuid = { version = "1.19.0", features = ["v4"] }
nmrs = "1.3.5"


[dependencies.cosmic-settings-network-manager-subscription]
Expand Down
240 changes: 146 additions & 94 deletions cosmic-applet-network/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use cosmic_settings_network_manager_subscription::{
use indexmap::IndexMap;
use rustc_hash::FxHashSet;
use secure_string::SecureString;
use nmrs::{ConnectionError, EapMethod, EapOptions, Phase2, WifiSecurity};
use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet},
Expand Down Expand Up @@ -162,6 +163,7 @@ struct CosmicNetworkApplet {
nm_task: Option<tokio::sync::oneshot::Sender<()>>,
secret_tx: Option<tokio::sync::mpsc::Sender<nm_secret_agent::Request>>,
nm_state: MyNetworkState,
nm: Option<nmrs::NetworkManager>,

// UI state
show_visible_networks: bool,
Expand Down Expand Up @@ -502,6 +504,7 @@ pub(crate) enum Message {
ConnectVPNWithPassword,
VPNPasswordUpdate(SecureString),
CancelVPNConnection,
NmrsReady(Option<nmrs::NetworkManager>),
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -841,56 +844,39 @@ impl cosmic::Application for CosmicNetworkApplet {
}
}
Message::SelectWirelessAccessPoint(access_point) => {
let Some(tx) = self.nm_sender.as_ref() else {
return Task::none();
let Some(nm) = self.nm.clone() else {
return cosmic::task::message(Message::Error(
"Network manager not initialized".to_string()
)).map(cosmic::Action::App);
};


// Open networks - connect immediately
if matches!(access_point.network_type, NetworkType::Open) {
if let Err(err) =
tx.unbounded_send(network_manager::Request::SelectAccessPoint(
access_point.ssid.clone(),
access_point.hw_address,
access_point.network_type,
self.secret_tx.clone(),
))
{
if err.is_disconnected() {
return system_conn().map(cosmic::Action::App);
}

tracing::error!("{err:?}");
}
let ssid = access_point.ssid.to_string();
self.new_connection = Some(NewConnectionState::Waiting(access_point));
} else {
if self
.nm_state
.nm_state
.known_access_points
.contains(&access_point)
{
if let Err(err) =
tx.unbounded_send(network_manager::Request::SelectAccessPoint(
access_point.ssid.clone(),
access_point.hw_address,
access_point.network_type,
self.secret_tx.clone(),
))
{
if err.is_disconnected() {
return system_conn().map(cosmic::Action::App);

return cosmic::task::future(async move {
match nm.connect(&ssid, WifiSecurity::Open).await {
Ok(()) => {
tracing::info!("Connected to open network {}", ssid);
Message::Refresh
}
Err(e) => {
tracing::error!("Failed to connect to {}: {}", ssid, e);
Message::Error(format!("Failed to connect to '{}': {}", ssid, e))
}

tracing::error!("{err:?}");
}
}
self.new_connection = Some(NewConnectionState::EnterPassword {
access_point,
description: None,
identity: String::new(),
password: String::new().into(),
password_hidden: true,
});
}).map(cosmic::Action::App);
}

// Secured networks - show password dialog
self.new_connection = Some(NewConnectionState::EnterPassword {
access_point,
description: None,
identity: String::new(),
password: String::new().into(),
password_hidden: true,
});
}
Message::ToggleVisibleNetworks => {
self.new_connection = None;
Expand Down Expand Up @@ -978,19 +964,27 @@ impl cosmic::Application for CosmicNetworkApplet {
tracing::warn!("Failed to find known access point with ssid: {}", ssid);
return Task::none();
};
if let Some(tx) = self.nm_sender.as_ref() {
if let Err(err) =
tx.unbounded_send(network_manager::Request::Forget(ssid.into()))
{
if err.is_disconnected() {
return system_conn().map(cosmic::Action::App);

let Some(nm) = self.nm.clone() else {
tracing::warn!("nmrs not initialized for forget network");
return Task::none();
};

self.show_visible_networks = true;
let ssid_clone = ssid.clone();

return cosmic::task::future(async move {
match nm.forget(&ssid_clone).await {
Ok(()) => {
tracing::info!("Forgot network {}", ssid_clone);
Message::SelectWirelessAccessPoint(ap)
}
Err(e) => {
tracing::error!("Failed to forget network {}: {}", ssid_clone, e);
Message::Error(format!("Failed to forget network '{}'", ssid_clone))
}

tracing::error!("{err:?}");
}
self.show_visible_networks = true;
return self.update(Message::SelectWirelessAccessPoint(ap));
}
}).map(cosmic::Action::App);
}
Message::Surface(a) => {
return cosmic::task::message(cosmic::Action::Cosmic(
Expand Down Expand Up @@ -1046,35 +1040,73 @@ impl cosmic::Application for CosmicNetworkApplet {
}
}
Message::ConnectWithPassword => {
// save password
let Some(tx) = self.nm_sender.as_ref() else {
return Task::none();
};

if let Some(NewConnectionState::EnterPassword {
let Some(NewConnectionState::EnterPassword {
password,
access_point,
identity,
..
}) = self.new_connection.take()
{
let is_enterprise: bool = matches!(access_point.network_type, NetworkType::EAP);

if let Err(err) = tx.unbounded_send(network_manager::Request::Authenticate {
ssid: access_point.ssid.to_string(),
identity: is_enterprise.then(|| identity.clone()),
password,
hw_address: access_point.hw_address,
secret_tx: self.secret_tx.clone(),
}) {
if err.is_disconnected() {
return system_conn().map(cosmic::Action::App);
}) = self.new_connection.take() else {
return Task::none();
};

let Some(nm) = self.nm.clone() else {
return cosmic::task::message(Message::Error(
"Network manager not initialized".to_string()
)).map(cosmic::Action::App);
};

let ssid = access_point.ssid.to_string();
let password_str = password.unsecure().to_string();

self.new_connection = Some(NewConnectionState::Waiting(access_point.clone()));

return cosmic::task::future(async move {
let security = match access_point.network_type {
NetworkType::Open => WifiSecurity::Open,
NetworkType::EAP => {
WifiSecurity::WpaEap {
opts: EapOptions {
identity: identity.clone(),
password: password_str,
anonymous_identity: None,
domain_suffix_match: None,
ca_cert_path: None,
system_ca_certs: true,
method: EapMethod::Peap,
phase2: Phase2::Mschapv2,
}
}
}
_ => {
// All other types (including secured networks) use WPA-PSK
WifiSecurity::WpaPsk {
psk: password_str,
}
}
};

match nm.connect(&ssid, security).await {
Ok(()) => {
tracing::info!("Connected to {}", ssid);
Message::Refresh
}
Err(e) => {
tracing::error!("Connection to {} failed: {}", ssid, e);
let error_msg = match e {
ConnectionError::AuthFailed =>
format!("Wrong password for '{}'", ssid),
ConnectionError::NotFound =>
format!("Network '{}' out of range", ssid),
ConnectionError::Timeout =>
format!("Connection to '{}' timed out", ssid),
ConnectionError::DhcpFailed =>
format!("Connected but failed to get IP address"),
_ => format!("Failed to connect: {}", e),
};
Message::Error(error_msg)
}
tracing::error!("Failed to authenticate with network manager");
}
self.new_connection
.replace(NewConnectionState::Waiting(access_point));
}
}).map(cosmic::Action::App);
}
Message::ConnectionSettings(btree_map) => {
self.nm_state.ssid_to_uuid = btree_map;
Expand Down Expand Up @@ -1250,10 +1282,25 @@ impl cosmic::Application for CosmicNetworkApplet {
} => {}
},
Message::NetworkManagerConnect(connection) => {
// Initialize nmrs in a separate task
let init_task = cosmic::task::future(async {
match nmrs::NetworkManager::new().await {
Ok(nm) => {
tracing::info!("nmrs NetworkManager initialized");
Message::NmrsReady(Some(nm))
}
Err(e) => {
tracing::warn!("Failed to initialize nmrs: {}", e);
Message::NmrsReady(None)
}
}
});

return cosmic::task::batch(vec![
self.connect(connection.clone()),
connection_settings(connection),
]);
init_task,
]).map(cosmic::Action::App);
}
Message::PasswordUpdate(entered_pw) => {
if let Some(NewConnectionState::EnterPassword { password, .. }) =
Expand All @@ -1269,24 +1316,23 @@ impl cosmic::Application for CosmicNetworkApplet {
self.nm_state.devices = device_infos.into_iter().map(Arc::new).collect();
}
Message::WiFiEnable(enable) => {
if let Some(sender) = self.nm_sender.as_mut() {
if let Err(err) =
sender.unbounded_send(network_manager::Request::SetWiFi(enable))
{
if err.is_disconnected() {
return system_conn().map(cosmic::Action::App);
let Some(nm) = self.nm.clone() else {
tracing::warn!("nmrs not initialized for WiFi toggle");
return Task::none();
};

return cosmic::task::future(async move {
match nm.set_wifi_enabled(enable).await {
Ok(()) => {
tracing::info!("WiFi {}", if enable { "enabled" } else { "disabled" });
Message::Refresh
}

tracing::error!("{err:?}");
}
if let Err(err) = sender.unbounded_send(network_manager::Request::Reload) {
if err.is_disconnected() {
return system_conn().map(cosmic::Action::App);
Err(e) => {
tracing::error!("Failed to {} WiFi: {}", if enable { "enable" } else { "disable" }, e);
Message::Error(format!("Failed to {} WiFi", if enable { "enable" } else { "disable" }))
}

tracing::error!("{err:?}");
}
}
}).map(cosmic::Action::App);
}
Message::SecretAgent(agent_event) => match agent_event {
nm_secret_agent::Event::RequestSecret {
Expand Down Expand Up @@ -1376,6 +1422,12 @@ impl cosmic::Application for CosmicNetworkApplet {
Message::CancelVPNConnection => {
self.nm_state.requested_vpn = None;
}
Message::NmrsReady(nm) => {
self.nm = nm;
if self.nm.is_some() {
tracing::info!("nmrs ready for WiFi connections");
}
}
}
Task::none()
}
Expand Down