feat(networking): replace custom NetworkManager backend with nmrs
Switch the networking pages to use nmrs 3.4.0 for NetworkManager state, device lists, Wi-Fi operations, VPN imports/activation, and secret-agent handling. Remove the old cosmic-settings-network-manager-subscription crate, drop the direct dbus-settings NetworkManager dependencies, and delete the unused nmcli VPN helper. Keep nm-connection-editor for profile creation/editing where the UI still depends on it. Carry Wi-Fi security metadata through the password flow so SAE/WPA3 personal networks can be activated with SAE key management, and make failed/cancelled auth attempts clear pending connection state
This commit is contained in:
parent
fff1485876
commit
04d5f46d0d
18 changed files with 2134 additions and 3504 deletions
|
|
@ -21,8 +21,6 @@ color-eyre = "0.6.5"
|
|||
cosmic-bg-config.workspace = true
|
||||
cosmic-comp-config = { workspace = true, optional = true }
|
||||
cosmic-config.workspace = true
|
||||
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
nm-secret-agent-manager = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
cosmic-idle-config.workspace = true
|
||||
cosmic-panel-config = { workspace = true, optional = true }
|
||||
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", optional = true }
|
||||
|
|
@ -34,7 +32,6 @@ cosmic-settings-accessibility-subscription = { path = "../subscriptions/accessib
|
|||
cosmic-settings-a11y-manager-subscription = { path = "../subscriptions/a11y-manager", optional = true }
|
||||
cosmic-settings-airplane-mode-subscription = { path = "../subscriptions/airplane-mode", optional = true }
|
||||
cosmic-settings-bluetooth-subscription = { path = "../subscriptions/bluetooth", optional = true }
|
||||
cosmic-settings-network-manager-subscription = { path = "../subscriptions/network-manager", optional = true }
|
||||
cosmic-settings-sound = { path = "../pages/sound", optional = true }
|
||||
cosmic-settings-upower-subscription = { path = "../subscriptions/upower", optional = true }
|
||||
cosmic-settings-wallpaper = { path = "../pages/wallpapers" }
|
||||
|
|
@ -57,11 +54,13 @@ locale1 = { git = "https://github.com/pop-os/dbus-settings-bindings", optional =
|
|||
sysinfo = { version = "=0.38.0", optional = true }
|
||||
mime-apps = { package = "cosmic-mime-apps", git = "https://github.com/pop-os/cosmic-mime-apps", features = ["tokio"], optional = true }
|
||||
notify = "8.2.0"
|
||||
nmrs = { version = "3.4.0", optional = true }
|
||||
regex = "1.12.3"
|
||||
ron = "0.12"
|
||||
rust-embed = "8.11.0"
|
||||
sctk = { workspace = true, optional = true }
|
||||
secure-string = "0.3.0"
|
||||
secret-service = { version = "5.1.0", features = ["rt-tokio-crypto-rust"], optional = true }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
slab = "0.4.12"
|
||||
slotmap = "1.1.1"
|
||||
|
|
@ -169,10 +168,9 @@ page-input = [
|
|||
]
|
||||
page-legacy-applications = ["cosmic-comp-config", "dep:cosmic-randr"]
|
||||
page-networking = [
|
||||
"dep:cosmic-settings-network-manager-subscription",
|
||||
"xdg-portal",
|
||||
"dep:cosmic-dbus-networkmanager",
|
||||
"dep:nm-secret-agent-manager",
|
||||
"dep:nmrs",
|
||||
"dep:secret-service",
|
||||
"dep:zbus",
|
||||
]
|
||||
page-power = ["dep:upower_dbus", "dep:zbus"]
|
||||
|
|
|
|||
1812
cosmic-settings/src/pages/networking/backend.rs
Normal file
1812
cosmic-settings/src/pages/networking/backend.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,24 +1,24 @@
|
|||
// Copyright 2024 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod backend;
|
||||
pub mod vpn;
|
||||
pub mod wifi;
|
||||
pub mod wired;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use cosmic::{Apply, Element, Task, widget};
|
||||
use cosmic_dbus_networkmanager::interface::enums::{DeviceState, DeviceType};
|
||||
use cosmic_dbus_networkmanager::nm::NetworkManager;
|
||||
use cosmic_settings_network_manager_subscription as network_manager;
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use secure_string::SecureString;
|
||||
use slotmap::SlotMap;
|
||||
|
||||
use self::backend as network_manager;
|
||||
use self::backend::devices::{DeviceState, DeviceType};
|
||||
|
||||
pub type SecretSender = Arc<tokio::sync::Mutex<Option<tokio::sync::oneshot::Sender<SecureString>>>>;
|
||||
|
||||
static NM_CONNECTION_EDITOR: &str = "nm-connection-editor";
|
||||
|
|
@ -37,8 +37,8 @@ pub struct Page {
|
|||
pub enum Message {
|
||||
/// An error occurred.
|
||||
Error(String),
|
||||
/// Successfully connected to the system dbus.
|
||||
NetworkManagerConnect(zbus::Connection),
|
||||
/// Successfully connected to NetworkManager.
|
||||
NetworkManagerConnect(nmrs::NetworkManager),
|
||||
/// Open the wifi settings page with the selected device.
|
||||
OpenPage {
|
||||
page: page::Entity,
|
||||
|
|
@ -143,6 +143,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
fl!("network-device-state", "unavailable")
|
||||
}
|
||||
DeviceState::Unknown => fl!("network-device-state", "unknown"),
|
||||
DeviceState::Other(_) => fl!("network-device-state", "unknown"),
|
||||
DeviceState::Unmanaged => fl!("network-device-state", "unmanaged"),
|
||||
},
|
||||
"preferences-wireless-symbolic",
|
||||
|
|
@ -190,6 +191,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
fl!("network-device-state", "unplugged")
|
||||
}
|
||||
DeviceState::Unknown => fl!("network-device-state", "unknown"),
|
||||
DeviceState::Other(_) => fl!("network-device-state", "unknown"),
|
||||
DeviceState::Unmanaged => fl!("network-device-state", "unmanaged"),
|
||||
},
|
||||
"preferences-wired-symbolic",
|
||||
|
|
@ -225,9 +227,9 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
fn on_enter(&mut self) -> cosmic::Task<crate::pages::Message> {
|
||||
if self.nm_task.is_none() {
|
||||
return cosmic::Task::future(async move {
|
||||
zbus::Connection::system()
|
||||
nmrs::NetworkManager::new()
|
||||
.await
|
||||
.context("failed to create system dbus connection")
|
||||
.context("failed to connect to NetworkManager")
|
||||
.map_or_else(
|
||||
|why| Message::Error(why.to_string()),
|
||||
Message::NetworkManagerConnect,
|
||||
|
|
@ -310,33 +312,47 @@ impl Page {
|
|||
Task::none()
|
||||
}
|
||||
|
||||
fn connect(&mut self, conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
fn connect(&mut self, nm: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
if self.nm_task.is_none() {
|
||||
let (canceller, task) =
|
||||
crate::utils::forward_event_loop(move |mut sender| async move {
|
||||
let network_manager = match NetworkManager::new(&conn).await {
|
||||
Ok(n) => n,
|
||||
Err(why) => {
|
||||
tracing::error!(
|
||||
why = why.to_string(),
|
||||
"failed to connect to network_manager"
|
||||
);
|
||||
|
||||
return futures::future::pending().await;
|
||||
match network_manager::devices::list(&nm, |_| true).await {
|
||||
Ok(devices) => {
|
||||
_ = sender
|
||||
.send(crate::pages::Message::Networking(Message::UpdateDevices(
|
||||
devices.into_iter().map(Arc::new).collect(),
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
};
|
||||
Err(why) => {
|
||||
_ = sender
|
||||
.send(crate::pages::Message::Networking(Message::Error(
|
||||
why.to_string(),
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
let mut devices_changed =
|
||||
std::pin::pin!(network_manager.receive_devices_changed().await.then(
|
||||
|_| async {
|
||||
match network_manager::devices::list(&conn, |_| true).await {
|
||||
Ok(devices) => Message::UpdateDevices(
|
||||
let Ok(events) = nm.network_events().await else {
|
||||
return futures::future::pending().await;
|
||||
};
|
||||
let devices_changed = events.filter_map(|event| async {
|
||||
match event {
|
||||
Ok(nmrs::NetworkEvent::DeviceChanged { .. })
|
||||
| Ok(nmrs::NetworkEvent::SettingsChanged(_))
|
||||
| Ok(nmrs::NetworkEvent::NetworkManagerRestarted) => {
|
||||
match network_manager::devices::list(&nm, |_| true).await {
|
||||
Ok(devices) => Some(Message::UpdateDevices(
|
||||
devices.into_iter().map(Arc::new).collect(),
|
||||
),
|
||||
Err(why) => Message::Error(why.to_string()),
|
||||
)),
|
||||
Err(why) => Some(Message::Error(why.to_string())),
|
||||
}
|
||||
}
|
||||
));
|
||||
Ok(_) => None,
|
||||
Err(why) => Some(Message::Error(why.to_string())),
|
||||
}
|
||||
});
|
||||
futures::pin_mut!(devices_changed);
|
||||
|
||||
while let Some(message) = devices_changed.next().await {
|
||||
_ = sender
|
||||
|
|
@ -353,16 +369,6 @@ impl Page {
|
|||
}
|
||||
}
|
||||
|
||||
async fn nm_add_vpn_file<P: AsRef<OsStr>>(type_: &str, path: P) -> Result<(), String> {
|
||||
tokio::process::Command::new("nmcli")
|
||||
.args(["connection", "import", "type", type_, "file"])
|
||||
.arg(path)
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.await
|
||||
.apply(crate::utils::map_stderr_output)
|
||||
}
|
||||
|
||||
async fn nm_add_wired() -> Result<(), String> {
|
||||
nm_connection_editor(&["--type=802-3-ethernet", "-c"]).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright 2024 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod nmcli;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
|
|
@ -14,17 +12,16 @@ use cosmic::widget::space::horizontal as horizontal_space;
|
|||
use cosmic::widget::text_input::focus;
|
||||
use cosmic::widget::{self, icon};
|
||||
use cosmic::{Apply, Element, Task, task};
|
||||
use cosmic_settings_network_manager_subscription::current_networks::ActiveConnectionInfo;
|
||||
use cosmic_settings_network_manager_subscription::nm_secret_agent::{self, PasswordFlag};
|
||||
use cosmic_settings_network_manager_subscription::{
|
||||
self as network_manager, NetworkManagerState, UUID,
|
||||
};
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use indexmap::IndexMap;
|
||||
use secure_string::SecureString;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::backend as network_manager;
|
||||
use super::backend::current_networks::ActiveConnectionInfo;
|
||||
use super::backend::nm_secret_agent::{self, PasswordFlag};
|
||||
use super::backend::{NetworkManagerState, UUID};
|
||||
use crate::pages::networking::SecretSender;
|
||||
|
||||
pub static SECURE_INPUT_VPN: LazyLock<widget::Id> = LazyLock::new(widget::Id::unique);
|
||||
|
|
@ -60,8 +57,8 @@ pub enum Message {
|
|||
NetworkManager(network_manager::Event),
|
||||
/// An update from the secret agent
|
||||
SecretAgent(network_manager::nm_secret_agent::Event),
|
||||
/// Successfully connected to the system dbus.
|
||||
NetworkManagerConnect(zbus::Connection),
|
||||
/// Successfully connected to NetworkManager.
|
||||
NetworkManagerConnect(nmrs::NetworkManager),
|
||||
/// Updates the password text input
|
||||
PasswordUpdate(SecureString),
|
||||
/// Refresh devices and their connection profiles
|
||||
|
|
@ -181,7 +178,7 @@ pub enum VpnDialog {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct NmState {
|
||||
conn: zbus::Connection,
|
||||
conn: nmrs::NetworkManager,
|
||||
sender: futures::channel::mpsc::UnboundedSender<network_manager::Request>,
|
||||
active_conns: Vec<ActiveConnectionInfo>,
|
||||
devices: Vec<network_manager::devices::DeviceInfo>,
|
||||
|
|
@ -371,20 +368,22 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
let (tx, rx) = tokio::sync::mpsc::channel(4);
|
||||
self.secret_tx = Some(tx);
|
||||
if self.nm_task.is_none() {
|
||||
return cosmic::Task::batch([cosmic::task::future(async move {
|
||||
zbus::Connection::system()
|
||||
.await
|
||||
.context("failed to create system dbus connection")
|
||||
.map_or_else(
|
||||
|why| Message::Error(ErrorKind::DbusConnection, why.to_string()),
|
||||
Message::NetworkManagerConnect,
|
||||
)
|
||||
}),
|
||||
cosmic::Task::stream(
|
||||
cosmic_settings_network_manager_subscription::nm_secret_agent::secret_agent_stream("com.system76.CosmicSettings.VPN.NetworkManager.SecretAgent", rx),
|
||||
)
|
||||
.map(|m| crate::pages::Message::Vpn(Message::SecretAgent(m))),
|
||||
]);
|
||||
return cosmic::Task::batch([
|
||||
cosmic::task::future(async move {
|
||||
nmrs::NetworkManager::new()
|
||||
.await
|
||||
.context("failed to connect to NetworkManager")
|
||||
.map_or_else(
|
||||
|why| Message::Error(ErrorKind::DbusConnection, why.to_string()),
|
||||
Message::NetworkManagerConnect,
|
||||
)
|
||||
}),
|
||||
cosmic::Task::stream(nm_secret_agent::secret_agent_stream(
|
||||
"com.system76.CosmicSettings.VPN.NetworkManager.SecretAgent",
|
||||
rx,
|
||||
))
|
||||
.map(|m| crate::pages::Message::Vpn(Message::SecretAgent(m))),
|
||||
]);
|
||||
}
|
||||
|
||||
cosmic::Task::none()
|
||||
|
|
@ -495,9 +494,8 @@ impl Page {
|
|||
Message::WireGuardConfig => {
|
||||
if let Some(VpnDialog::WireGuardName(device, filename, path)) = self.dialog.take() {
|
||||
return cosmic::task::future(async move {
|
||||
let new_path = path.replace(&filename, &device);
|
||||
_ = std::fs::rename(&path, &new_path);
|
||||
match super::nm_add_vpn_file("wireguard", new_path).await {
|
||||
let _ = filename;
|
||||
match network_manager::import_wireguard(path, &device).await {
|
||||
Ok(_) => Message::Refresh,
|
||||
Err(why) => Message::Error(ErrorKind::Config, why.to_string()),
|
||||
}
|
||||
|
|
@ -510,18 +508,12 @@ impl Page {
|
|||
if let Some(settings) = self.known_connections.get(&uuid) {
|
||||
let settings = match settings {
|
||||
ConnectionSettings::Vpn(settings) => settings,
|
||||
ConnectionSettings::Wireguard { id } => {
|
||||
let connection_name = id.clone();
|
||||
return cosmic::task::future(async move {
|
||||
if let Err(why) = nmcli::connect(&connection_name).await {
|
||||
return Message::Error(
|
||||
ErrorKind::Connect,
|
||||
format!("failed to connect to WireGuard VPN: {why}"),
|
||||
);
|
||||
}
|
||||
|
||||
Message::Refresh
|
||||
});
|
||||
ConnectionSettings::Wireguard { .. } => {
|
||||
if let Some(NmState { ref sender, .. }) = self.nm_state {
|
||||
_ = sender
|
||||
.unbounded_send(network_manager::Request::ActivateVpn(uuid));
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -541,28 +533,10 @@ impl Page {
|
|||
return task::message(Message::FocusSecureInput);
|
||||
}
|
||||
_ => {
|
||||
let connection_name = settings.id.clone();
|
||||
let username = settings.username.clone();
|
||||
return cosmic::task::future(async move {
|
||||
if let Err(why) = nmcli::connect(&connection_name).await {
|
||||
return Message::VpnDialogError(VpnDialog::Password {
|
||||
error: Some((
|
||||
ErrorKind::Connect,
|
||||
format!("failed to connect to VPN: {why}"),
|
||||
)),
|
||||
id: connection_name.clone(),
|
||||
uuid,
|
||||
username: username.clone(),
|
||||
description: None,
|
||||
password: SecureString::from(""),
|
||||
password_hidden: true,
|
||||
// TODO grab from the current dialog
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
|
||||
Message::Refresh
|
||||
});
|
||||
if let Some(NmState { ref sender, .. }) = self.nm_state {
|
||||
_ = sender
|
||||
.unbounded_send(network_manager::Request::ActivateVpn(uuid));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -629,89 +603,55 @@ impl Page {
|
|||
};
|
||||
|
||||
if let VpnDialog::Password {
|
||||
id,
|
||||
id: _,
|
||||
uuid,
|
||||
username,
|
||||
password,
|
||||
tx,
|
||||
..
|
||||
} = dialog
|
||||
&& let Some(NmState { ref sender, .. }) = self.nm_state
|
||||
{
|
||||
let username_unwrapped = username.clone().unwrap_or_default();
|
||||
let task = self.activate_with_password(
|
||||
id.clone(),
|
||||
uuid.clone(),
|
||||
username_unwrapped.clone(),
|
||||
password.clone(),
|
||||
);
|
||||
let sec_tx = self.secret_tx.clone();
|
||||
return task
|
||||
.then(move |_| {
|
||||
let sec_tx = sec_tx.clone();
|
||||
let uuid = uuid.clone();
|
||||
let username = username.clone();
|
||||
let password = password.clone();
|
||||
let tx = tx.clone();
|
||||
let id = id.clone();
|
||||
Task::future(async move {
|
||||
let mut guard = tx.lock().await;
|
||||
if let Some(sender) = guard.take() {
|
||||
let _ = sender.send(password);
|
||||
} else {
|
||||
// apply password and username then
|
||||
if let Some(sec_tx) = sec_tx {
|
||||
let (applied_tx, applied_rx) =
|
||||
tokio::sync::oneshot::channel();
|
||||
if let Err(err) = sec_tx
|
||||
.send(nm_secret_agent::Request::SetSecrets {
|
||||
setting_name: "vpn".to_string(),
|
||||
uuid: uuid.to_string(),
|
||||
secrets: HashMap::from_iter([
|
||||
// username and password
|
||||
(
|
||||
"username".to_string(),
|
||||
username.clone().unwrap_or_default().into(),
|
||||
),
|
||||
("password".to_string(), password.clone()),
|
||||
]),
|
||||
applied_tx,
|
||||
})
|
||||
.await
|
||||
{
|
||||
tracing::error!(%err, "failed to apply secret");
|
||||
}
|
||||
// wait max 1s for the applied signal
|
||||
if let Err(err) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(1),
|
||||
applied_rx,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(%err, "failed to apply secret");
|
||||
}
|
||||
}
|
||||
// activate
|
||||
if let Err(why) = nmcli::connect(&id).await {
|
||||
return Message::VpnDialogError(VpnDialog::Password {
|
||||
error: Some((
|
||||
ErrorKind::Connect,
|
||||
format!("failed to connect to VPN: {why}"),
|
||||
)),
|
||||
id: id.clone(),
|
||||
uuid,
|
||||
username: username.clone(),
|
||||
description: None,
|
||||
password,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
let nm_sender = sender.clone();
|
||||
return Task::future(async move {
|
||||
let mut guard = tx.lock().await;
|
||||
if let Some(sender) = guard.take() {
|
||||
let _ = sender.send(password);
|
||||
} else {
|
||||
if let Some(sec_tx) = sec_tx {
|
||||
let (applied_tx, applied_rx) = tokio::sync::oneshot::channel();
|
||||
if let Err(err) = sec_tx
|
||||
.send(nm_secret_agent::Request::SetSecrets {
|
||||
setting_name: "vpn".to_string(),
|
||||
uuid: uuid.to_string(),
|
||||
secrets: HashMap::from_iter([
|
||||
("username".to_string(), username_unwrapped.into()),
|
||||
("password".to_string(), password),
|
||||
]),
|
||||
applied_tx,
|
||||
})
|
||||
.await
|
||||
{
|
||||
tracing::error!(%err, "failed to apply secret");
|
||||
}
|
||||
if let Err(err) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(1),
|
||||
applied_rx,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(%err, "failed to apply secret");
|
||||
}
|
||||
}
|
||||
_ = nm_sender
|
||||
.unbounded_send(network_manager::Request::ActivateVpn(uuid));
|
||||
}
|
||||
|
||||
Message::Refresh
|
||||
})
|
||||
})
|
||||
.map(crate::app::Message::from);
|
||||
Message::Refresh
|
||||
})
|
||||
.map(crate::app::Message::from);
|
||||
}
|
||||
}
|
||||
Message::RetryWithPassword => {
|
||||
|
|
@ -720,53 +660,39 @@ impl Page {
|
|||
};
|
||||
|
||||
if let VpnDialog::Password {
|
||||
id,
|
||||
id: _,
|
||||
uuid,
|
||||
username,
|
||||
password,
|
||||
..
|
||||
} = dialog
|
||||
&& let Some(NmState { ref sender, .. }) = self.nm_state
|
||||
{
|
||||
let username_unwrapped = username.unwrap_or_default();
|
||||
let sec_tx = self.secret_tx.clone();
|
||||
let task = self.activate_with_password(
|
||||
id.clone(),
|
||||
uuid.clone(),
|
||||
username_unwrapped.clone(),
|
||||
password.clone(),
|
||||
);
|
||||
return task
|
||||
.then(move |_| {
|
||||
let sec_tx = sec_tx.clone();
|
||||
let uuid = uuid.clone();
|
||||
let username = username_unwrapped.clone();
|
||||
let password = password.clone();
|
||||
Task::future(async move {
|
||||
if let Some(sec_tx) = sec_tx {
|
||||
let (applied_tx, applied_rx) = tokio::sync::oneshot::channel();
|
||||
let _ = sec_tx
|
||||
.send(nm_secret_agent::Request::SetSecrets {
|
||||
setting_name: "vpn".to_string(),
|
||||
uuid: uuid.to_string(),
|
||||
secrets: HashMap::from_iter([
|
||||
// username and password
|
||||
("username".to_string(), username.clone().into()),
|
||||
("password".to_string(), password.clone()),
|
||||
]),
|
||||
applied_tx,
|
||||
})
|
||||
.await;
|
||||
// wait max 1s for the applied signal
|
||||
let _ = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(1),
|
||||
applied_rx,
|
||||
)
|
||||
let nm_sender = sender.clone();
|
||||
return Task::future(async move {
|
||||
if let Some(sec_tx) = sec_tx {
|
||||
let (applied_tx, applied_rx) = tokio::sync::oneshot::channel();
|
||||
let _ = sec_tx
|
||||
.send(nm_secret_agent::Request::SetSecrets {
|
||||
setting_name: "vpn".to_string(),
|
||||
uuid: uuid.to_string(),
|
||||
secrets: HashMap::from_iter([
|
||||
("username".to_string(), username_unwrapped.into()),
|
||||
("password".to_string(), password),
|
||||
]),
|
||||
applied_tx,
|
||||
})
|
||||
.await;
|
||||
let _ =
|
||||
tokio::time::timeout(std::time::Duration::from_secs(1), applied_rx)
|
||||
.await;
|
||||
}
|
||||
Message::Activate(uuid)
|
||||
})
|
||||
})
|
||||
.map(crate::app::Message::from);
|
||||
}
|
||||
_ = nm_sender.unbounded_send(network_manager::Request::ActivateVpn(uuid));
|
||||
Message::Refresh
|
||||
})
|
||||
.map(crate::app::Message::from);
|
||||
}
|
||||
}
|
||||
Message::UsernameUpdate(user) => {
|
||||
|
|
@ -873,56 +799,14 @@ impl Page {
|
|||
Task::none()
|
||||
}
|
||||
|
||||
fn activate_with_password(
|
||||
&mut self,
|
||||
connection_name: String,
|
||||
uuid: Arc<str>,
|
||||
username: String,
|
||||
password: SecureString,
|
||||
) -> Task<Message> {
|
||||
cosmic::task::future(async move {
|
||||
if let Err(why) = nmcli::set_username(&connection_name, &username).await {
|
||||
return Message::VpnDialogError(VpnDialog::Password {
|
||||
error: Some((ErrorKind::WithPassword("username"), why.to_string())),
|
||||
id: connection_name.clone(),
|
||||
uuid,
|
||||
username: Some(username),
|
||||
description: None,
|
||||
password,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
|
||||
if let Err(why) = nmcli::add_fallback(&connection_name).await {
|
||||
return Message::VpnDialogError(VpnDialog::Password {
|
||||
error: Some((ErrorKind::Config, why.to_string())),
|
||||
id: connection_name.clone(),
|
||||
uuid,
|
||||
username: Some(username),
|
||||
password,
|
||||
description: None,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
|
||||
Message::Refresh
|
||||
})
|
||||
}
|
||||
|
||||
fn connect(&mut self, conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
fn connect(&mut self, conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
if self.nm_task.is_none() {
|
||||
let (canceller, task) =
|
||||
crate::utils::forward_event_loop(move |mut sender| async move {
|
||||
let (tx, mut rx) = futures::channel::mpsc::channel(1);
|
||||
|
||||
let watchers = std::pin::pin!(async move {
|
||||
futures::join!(
|
||||
network_manager::watch(conn.clone(), tx.clone()),
|
||||
network_manager::active_conns::watch(conn.clone(), tx.clone()),
|
||||
network_manager::devices::watch(conn, true, tx)
|
||||
)
|
||||
network_manager::watch(conn, tx).await;
|
||||
});
|
||||
|
||||
let forwarder = std::pin::pin!(async move {
|
||||
|
|
@ -1141,7 +1025,7 @@ fn popup_button(message: Message, text: &str) -> Element<'_, Message> {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn update_state(conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
fn update_state(conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
cosmic::task::future(async move {
|
||||
match NetworkManagerState::new(&conn).await {
|
||||
Ok(state) => Message::UpdateState(state),
|
||||
|
|
@ -1150,7 +1034,7 @@ fn update_state(conn: zbus::Connection) -> Task<crate::app::Message> {
|
|||
})
|
||||
}
|
||||
|
||||
fn update_devices(conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
fn update_devices(conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
cosmic::task::future(async move {
|
||||
let filter =
|
||||
|device_type| matches!(device_type, network_manager::devices::DeviceType::WireGuard);
|
||||
|
|
@ -1208,7 +1092,9 @@ fn add_network() -> Task<crate::app::Message> {
|
|||
);
|
||||
};
|
||||
|
||||
super::nm_add_vpn_file("openvpn", path).await
|
||||
network_manager::import_openvpn(path)
|
||||
.await
|
||||
.map_err(|why| why.to_string())
|
||||
};
|
||||
|
||||
match result {
|
||||
|
|
@ -1223,108 +1109,96 @@ fn add_network() -> Task<crate::app::Message> {
|
|||
.apply(cosmic::task::future)
|
||||
}
|
||||
|
||||
fn connection_settings(conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
let settings = async move {
|
||||
let settings = network_manager::dbus::settings::NetworkManagerSettings::new(&conn).await?;
|
||||
|
||||
_ = settings.load_connections(&[]).await;
|
||||
|
||||
let settings = settings
|
||||
// Get a list of known connections.
|
||||
.list_connections()
|
||||
.await?
|
||||
// Prepare for wrapping in a concurrent stream.
|
||||
.into_iter()
|
||||
.map(|conn| async move { conn })
|
||||
// Create a concurrent stream for each connection.
|
||||
.apply(futures::stream::FuturesOrdered::from_iter)
|
||||
// Concurrently fetch settings for each connection, and filter for VPN.
|
||||
.filter_map(|conn| async move {
|
||||
let settings = conn.get_settings().await.ok()?;
|
||||
|
||||
let connection = settings.get("connection")?;
|
||||
|
||||
match connection
|
||||
.get("type")?
|
||||
.downcast_ref::<String>()
|
||||
.ok()?
|
||||
.as_str()
|
||||
{
|
||||
"vpn" => (),
|
||||
|
||||
"wireguard" => {
|
||||
let id = connection.get("id")?.downcast_ref::<String>().ok()?;
|
||||
let uuid = connection.get("uuid")?.downcast_ref::<String>().ok()?;
|
||||
return Some((Arc::from(uuid), ConnectionSettings::Wireguard { id }));
|
||||
}
|
||||
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
let vpn = settings.get("vpn")?;
|
||||
let id = connection.get("id")?.downcast_ref::<String>().ok()?;
|
||||
let uuid = connection.get("uuid")?.downcast_ref::<String>().ok()?;
|
||||
|
||||
let (connection_type, username, password_flag) = vpn
|
||||
.get("data")
|
||||
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok())
|
||||
.map(|dict| {
|
||||
let (mut connection_type, mut password_flag) = (None, None);
|
||||
let mut username = vpn
|
||||
.get("user-name")
|
||||
.and_then(|u| u.downcast_ref::<String>().ok());
|
||||
if dict
|
||||
.get::<String, String>(&String::from("connection-type"))
|
||||
.ok()
|
||||
.flatten()
|
||||
.as_deref()
|
||||
// may be "password" or "password-tls"
|
||||
.is_some_and(|p| p.starts_with("password"))
|
||||
{
|
||||
connection_type = Some(ConnectionType::Password);
|
||||
username = Some(username.unwrap_or_default());
|
||||
|
||||
password_flag = dict
|
||||
.get::<String, String>(&String::from("password-flags"))
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|value| match value.as_str() {
|
||||
"0" => Some(PasswordFlag::None),
|
||||
"1" => Some(PasswordFlag::AgentOwned),
|
||||
"2" => Some(PasswordFlag::NotSaved),
|
||||
"4" => Some(PasswordFlag::NotRequired),
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
|
||||
(connection_type, username, password_flag)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Some((
|
||||
Arc::from(uuid),
|
||||
ConnectionSettings::Vpn(VpnConnectionSettings {
|
||||
id,
|
||||
connection_type,
|
||||
password_flag,
|
||||
username,
|
||||
}),
|
||||
))
|
||||
})
|
||||
// Reduce the settings list into
|
||||
.fold(IndexMap::new(), |mut set, (uuid, data)| async move {
|
||||
set.insert(uuid, data);
|
||||
set
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok::<_, zbus::Error>(settings)
|
||||
};
|
||||
|
||||
fn connection_settings(conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
cosmic::task::future(async move {
|
||||
let settings = async move {
|
||||
let vpns = conn.list_vpn_connections().await?;
|
||||
let mut settings = IndexMap::new();
|
||||
|
||||
for vpn in vpns {
|
||||
let uuid = Arc::from(vpn.uuid.as_str());
|
||||
let data = match vpn.vpn_type {
|
||||
nmrs::VpnType::WireGuard { .. } => ConnectionSettings::Wireguard { id: vpn.id },
|
||||
nmrs::VpnType::OpenVpn {
|
||||
connection_type,
|
||||
user_name,
|
||||
password_flags,
|
||||
..
|
||||
} => ConnectionSettings::Vpn(VpnConnectionSettings {
|
||||
id: vpn.id,
|
||||
username: user_name,
|
||||
connection_type: connection_type
|
||||
.filter(|ct| {
|
||||
matches!(
|
||||
ct,
|
||||
nmrs::OpenVpnConnectionType::Password
|
||||
| nmrs::OpenVpnConnectionType::PasswordTls
|
||||
)
|
||||
})
|
||||
.map(|_| ConnectionType::Password),
|
||||
password_flag: Some(password_flag(password_flags.0)),
|
||||
}),
|
||||
nmrs::VpnType::OpenConnect {
|
||||
user_name,
|
||||
password_flags,
|
||||
..
|
||||
}
|
||||
| nmrs::VpnType::StrongSwan {
|
||||
user_name,
|
||||
password_flags,
|
||||
..
|
||||
}
|
||||
| nmrs::VpnType::Pptp {
|
||||
user_name,
|
||||
password_flags,
|
||||
..
|
||||
}
|
||||
| nmrs::VpnType::L2tp {
|
||||
user_name,
|
||||
password_flags,
|
||||
..
|
||||
} => ConnectionSettings::Vpn(VpnConnectionSettings {
|
||||
id: vpn.id,
|
||||
username: user_name,
|
||||
connection_type: Some(ConnectionType::Password),
|
||||
password_flag: Some(password_flag(password_flags.0)),
|
||||
}),
|
||||
nmrs::VpnType::Generic {
|
||||
user_name,
|
||||
password_flags,
|
||||
..
|
||||
} => ConnectionSettings::Vpn(VpnConnectionSettings {
|
||||
id: vpn.id,
|
||||
username: user_name,
|
||||
connection_type: Some(ConnectionType::Password),
|
||||
password_flag: Some(password_flag(password_flags.0)),
|
||||
}),
|
||||
_ => ConnectionSettings::Vpn(VpnConnectionSettings {
|
||||
id: vpn.id,
|
||||
username: None,
|
||||
connection_type: None,
|
||||
password_flag: None,
|
||||
}),
|
||||
};
|
||||
settings.insert(uuid, data);
|
||||
}
|
||||
|
||||
Ok::<_, nmrs::ConnectionError>(settings)
|
||||
};
|
||||
|
||||
settings.await.map_or_else(
|
||||
|why| Message::Error(ErrorKind::ConnectionSettings, why.to_string()),
|
||||
Message::KnownConnections,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn password_flag(value: u32) -> PasswordFlag {
|
||||
match value {
|
||||
0 => PasswordFlag::None,
|
||||
1 => PasswordFlag::AgentOwned,
|
||||
2 => PasswordFlag::NotSaved,
|
||||
4 => PasswordFlag::NotRequired,
|
||||
_ => PasswordFlag::AgentOwned,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright 2024 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::Apply;
|
||||
use std::process::Stdio;
|
||||
|
||||
pub async fn set_username(connection_name: &str, username: &str) -> Result<(), String> {
|
||||
tokio::process::Command::new("nmcli")
|
||||
.args(["con", "mod", connection_name, "vpn.user-name", username])
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.await
|
||||
.apply(crate::utils::map_stderr_output)
|
||||
}
|
||||
|
||||
pub async fn add_fallback(connection_name: &str) -> Result<(), String> {
|
||||
tokio::process::Command::new("nmcli")
|
||||
.args([
|
||||
"con",
|
||||
"mod",
|
||||
connection_name,
|
||||
"+vpn.data",
|
||||
"data-ciphers=AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-256-CBC:AES-128-CBC",
|
||||
])
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.await
|
||||
.apply(crate::utils::map_stderr_output)
|
||||
}
|
||||
|
||||
pub async fn connect(connection_name: &str) -> Result<(), String> {
|
||||
tokio::process::Command::new("nmcli")
|
||||
.args(["con", "up", connection_name])
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.await
|
||||
.apply(crate::utils::map_stderr_output)
|
||||
}
|
||||
|
|
@ -13,16 +13,15 @@ use cosmic::widget::space::horizontal;
|
|||
use cosmic::widget::text_input::focus;
|
||||
use cosmic::widget::{self, column, icon};
|
||||
use cosmic::{Apply, Element, Task, task};
|
||||
use cosmic_settings_network_manager_subscription::available_wifi::{AccessPoint, NetworkType};
|
||||
use cosmic_settings_network_manager_subscription::current_networks::ActiveConnectionInfo;
|
||||
use cosmic_settings_network_manager_subscription::{
|
||||
self as network_manager, NetworkManagerState, nm_secret_agent,
|
||||
};
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use secure_string::SecureString;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::backend as network_manager;
|
||||
use super::backend::available_wifi::{AccessPoint, NetworkType};
|
||||
use super::backend::current_networks::ActiveConnectionInfo;
|
||||
use super::backend::{NetworkManagerState, nm_secret_agent};
|
||||
use crate::pages::networking::SecretSender;
|
||||
|
||||
pub static SECURE_INPUT_WIFI: LazyLock<widget::Id> = LazyLock::new(widget::Id::unique);
|
||||
|
|
@ -55,8 +54,8 @@ pub enum Message {
|
|||
NetworkManager(network_manager::Event),
|
||||
/// An update from the secret agent
|
||||
SecretAgent(network_manager::nm_secret_agent::Event),
|
||||
/// Successfully connected to the system dbus.
|
||||
NetworkManagerConnect(zbus::Connection),
|
||||
/// Successfully connected to NetworkManager.
|
||||
NetworkManagerConnect(nmrs::NetworkManager),
|
||||
/// Request an auth dialog
|
||||
PasswordRequest(network_manager::SSID),
|
||||
/// Update the password from the dialog
|
||||
|
|
@ -102,6 +101,7 @@ enum WiFiDialog {
|
|||
ssid: network_manager::SSID,
|
||||
identity: Option<String>,
|
||||
password: SecureString,
|
||||
network_type: NetworkType,
|
||||
password_hidden: bool,
|
||||
tx: SecretSender,
|
||||
},
|
||||
|
|
@ -141,7 +141,7 @@ pub struct Page {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct NmState {
|
||||
conn: zbus::Connection,
|
||||
conn: nmrs::NetworkManager,
|
||||
sender: futures::channel::mpsc::UnboundedSender<network_manager::Request>,
|
||||
state: network_manager::NetworkManagerState,
|
||||
devices: Vec<network_manager::devices::DeviceInfo>,
|
||||
|
|
@ -294,19 +294,23 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
let (tx, rx) = tokio::sync::mpsc::channel(4);
|
||||
self.secret_tx = Some(tx);
|
||||
if self.nm_task.is_none() {
|
||||
return Task::batch(vec![cosmic::Task::future(async move {
|
||||
zbus::Connection::system()
|
||||
.await
|
||||
.context("failed to create system dbus connection")
|
||||
.map_or_else(
|
||||
|why| Message::Error(why.to_string()),
|
||||
Message::NetworkManagerConnect,
|
||||
)
|
||||
.apply(crate::pages::Message::WiFi)
|
||||
}), cosmic::Task::stream(
|
||||
cosmic_settings_network_manager_subscription::nm_secret_agent::secret_agent_stream("com.system76.CosmicSettings.WiFi.NetworkManager.SecretAgent", rx),
|
||||
)
|
||||
.map(|m| crate::pages::Message::WiFi(Message::SecretAgent(m)))]);
|
||||
return Task::batch(vec![
|
||||
cosmic::Task::future(async move {
|
||||
nmrs::NetworkManager::new()
|
||||
.await
|
||||
.context("failed to connect to NetworkManager")
|
||||
.map_or_else(
|
||||
|why| Message::Error(why.to_string()),
|
||||
Message::NetworkManagerConnect,
|
||||
)
|
||||
.apply(crate::pages::Message::WiFi)
|
||||
}),
|
||||
cosmic::Task::stream(nm_secret_agent::secret_agent_stream(
|
||||
"com.system76.CosmicSettings.WiFi.NetworkManager.SecretAgent",
|
||||
rx,
|
||||
))
|
||||
.map(|m| crate::pages::Message::WiFi(Message::SecretAgent(m))),
|
||||
]);
|
||||
}
|
||||
|
||||
Task::none()
|
||||
|
|
@ -352,15 +356,22 @@ impl Page {
|
|||
}
|
||||
|
||||
match req {
|
||||
network_manager::Request::Authenticate { ssid, identity, .. } => {
|
||||
network_manager::Request::Authenticate {
|
||||
ssid,
|
||||
identity,
|
||||
network_type,
|
||||
..
|
||||
} => {
|
||||
if success {
|
||||
self.connecting.remove(ssid.as_str());
|
||||
} else {
|
||||
self.connecting.remove(ssid.as_str());
|
||||
// Request to retry
|
||||
self.dialog = Some(WiFiDialog::Password {
|
||||
ssid: ssid.into(),
|
||||
identity,
|
||||
password: SecureString::from(""),
|
||||
network_type,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
|
|
@ -377,11 +388,13 @@ impl Page {
|
|||
if success || matches!(network_type, NetworkType::Open) {
|
||||
self.connecting.remove(ssid.as_ref());
|
||||
} else {
|
||||
self.connecting.remove(ssid.as_ref());
|
||||
self.dialog = Some(WiFiDialog::Password {
|
||||
ssid,
|
||||
identity: matches!(network_type, NetworkType::EAP)
|
||||
.then(String::new),
|
||||
password: SecureString::from(""),
|
||||
network_type,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
|
|
@ -449,6 +462,7 @@ impl Page {
|
|||
let qr_string = if let Some(ref pass) = password {
|
||||
let security = match security_type {
|
||||
NetworkType::PskOrSae => "WPA",
|
||||
NetworkType::Sae => "WPA",
|
||||
NetworkType::EAP => "WPA",
|
||||
NetworkType::Open => "",
|
||||
};
|
||||
|
|
@ -521,6 +535,7 @@ impl Page {
|
|||
ssid,
|
||||
identity: matches!(ap.network_type, NetworkType::EAP).then(String::new),
|
||||
password: SecureString::from(""),
|
||||
network_type: ap.network_type,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
|
|
@ -544,6 +559,7 @@ impl Page {
|
|||
ssid,
|
||||
identity,
|
||||
password,
|
||||
network_type,
|
||||
tx,
|
||||
..
|
||||
} = dialog
|
||||
|
|
@ -562,6 +578,7 @@ impl Page {
|
|||
ssid: ssid.to_string(),
|
||||
identity,
|
||||
password,
|
||||
network_type,
|
||||
secret_tx,
|
||||
interface,
|
||||
});
|
||||
|
|
@ -614,6 +631,7 @@ impl Page {
|
|||
}
|
||||
Message::Disconnect(ssid) => {
|
||||
self.close_popup_and_apply_updates();
|
||||
self.connecting.remove(ssid.as_ref());
|
||||
if let Some(nm) = self.nm_state.as_mut() {
|
||||
_ = nm
|
||||
.sender
|
||||
|
|
@ -627,6 +645,7 @@ impl Page {
|
|||
Message::Forget(ssid) => {
|
||||
self.dialog = None;
|
||||
self.close_popup_and_apply_updates();
|
||||
self.connecting.remove(ssid.as_ref());
|
||||
if let Some(nm) = self.nm_state.as_mut() {
|
||||
_ = nm
|
||||
.sender
|
||||
|
|
@ -648,6 +667,9 @@ impl Page {
|
|||
}
|
||||
}
|
||||
Message::WiFiEnable(enable) => {
|
||||
if !enable {
|
||||
self.connecting.clear();
|
||||
}
|
||||
if let Some(nm) = self.nm_state.as_mut() {
|
||||
_ = nm
|
||||
.sender
|
||||
|
|
@ -656,7 +678,9 @@ impl Page {
|
|||
}
|
||||
}
|
||||
Message::CancelDialog => {
|
||||
self.dialog = None;
|
||||
if let Some(WiFiDialog::Password { ssid, .. }) = self.dialog.take() {
|
||||
self.connecting.remove(ssid.as_ref());
|
||||
}
|
||||
}
|
||||
Message::Error(why) => {
|
||||
tracing::error!(why);
|
||||
|
|
@ -715,15 +739,20 @@ impl Page {
|
|||
password: previous,
|
||||
password_hidden: true,
|
||||
identity: matches!(ap.network_type, NetworkType::EAP).then(String::new),
|
||||
network_type: ap.network_type,
|
||||
tx,
|
||||
});
|
||||
return task::message(Message::FocusSecureInput);
|
||||
}
|
||||
nm_secret_agent::Event::CancelGetSecrets { uuid: _, name: _ } => {
|
||||
self.dialog = self
|
||||
.dialog
|
||||
.take()
|
||||
.filter(|d| !matches!(d, &WiFiDialog::Password { .. }));
|
||||
match self.dialog.take() {
|
||||
Some(WiFiDialog::Password { ssid, .. }) => {
|
||||
self.connecting.remove(ssid.as_ref());
|
||||
}
|
||||
other => {
|
||||
self.dialog = other;
|
||||
}
|
||||
}
|
||||
}
|
||||
nm_secret_agent::Event::Failed(error) => {
|
||||
tracing::error!(%error, "secret agent failure");
|
||||
|
|
@ -731,15 +760,18 @@ impl Page {
|
|||
ssid,
|
||||
password,
|
||||
identity,
|
||||
network_type,
|
||||
..
|
||||
}) = self.dialog.take()
|
||||
{
|
||||
self.connecting.remove(ssid.as_ref());
|
||||
self.dialog = Some(WiFiDialog::Password {
|
||||
password,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
ssid,
|
||||
identity,
|
||||
network_type,
|
||||
});
|
||||
return task::message(Message::FocusSecureInput);
|
||||
}
|
||||
|
|
@ -770,19 +802,14 @@ impl Page {
|
|||
Task::none()
|
||||
}
|
||||
|
||||
fn connect(&mut self, conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
fn connect(&mut self, conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
if self.nm_task.is_none() {
|
||||
let (canceller, task) =
|
||||
crate::utils::forward_event_loop(move |mut sender| async move {
|
||||
let (tx, mut rx) = futures::channel::mpsc::channel(1);
|
||||
|
||||
let watchers = std::pin::pin!(async move {
|
||||
futures::join!(
|
||||
network_manager::watch(conn.clone(), tx.clone()),
|
||||
network_manager::active_conns::watch(conn.clone(), tx.clone()),
|
||||
network_manager::wireless_enabled::watch(conn.clone(), tx.clone()),
|
||||
network_manager::watch_connections_changed(conn, tx)
|
||||
);
|
||||
network_manager::watch(conn, tx).await;
|
||||
});
|
||||
|
||||
let forwarder = std::pin::pin!(async move {
|
||||
|
|
@ -832,6 +859,12 @@ impl Page {
|
|||
|
||||
/// Withholds updates if the view more popup is displayed.
|
||||
fn update_state(&mut self, state: NetworkManagerState) {
|
||||
for active in &state.active_conns {
|
||||
if let ActiveConnectionInfo::WiFi { name, .. } = active {
|
||||
self.connecting.remove(name.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut nm_state) = self.nm_state {
|
||||
if self.view_more_popup.is_some() {
|
||||
self.withheld_state = Some(state);
|
||||
|
|
@ -1271,51 +1304,9 @@ fn popup_button(message: Message, text: &str) -> Element<'_, Message> {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn connection_settings(conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
let settings = async move {
|
||||
let settings = network_manager::dbus::settings::NetworkManagerSettings::new(&conn).await?;
|
||||
|
||||
_ = settings.load_connections(&[]).await;
|
||||
|
||||
let settings = settings
|
||||
// Get a list of known connections.
|
||||
.list_connections()
|
||||
.await?
|
||||
// Prepare for wrapping in a concurrent stream.
|
||||
.into_iter()
|
||||
.map(|conn| async move { conn })
|
||||
// Create a concurrent stream for each connection.
|
||||
.apply(futures::stream::FuturesOrdered::from_iter)
|
||||
// Concurrently fetch settings for each connection.
|
||||
.filter_map(|conn| async move {
|
||||
conn.get_settings()
|
||||
.await
|
||||
.map(network_manager::Settings::new)
|
||||
.ok()
|
||||
})
|
||||
// Reduce the settings list into a SSID->UUID map.
|
||||
.fold(BTreeMap::new(), |mut set, settings| async move {
|
||||
if let Some(ref wifi) = settings.wifi
|
||||
&& let Some(ssid) = wifi
|
||||
.ssid
|
||||
.clone()
|
||||
.and_then(|ssid| String::from_utf8(ssid).ok())
|
||||
&& let Some(ref connection) = settings.connection
|
||||
&& let Some(uuid) = connection.uuid.clone()
|
||||
{
|
||||
set.insert(ssid.into(), uuid.into());
|
||||
return set;
|
||||
}
|
||||
|
||||
set
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok::<_, zbus::Error>(settings)
|
||||
};
|
||||
|
||||
fn connection_settings(conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
cosmic::task::future(async move {
|
||||
settings
|
||||
network_manager::wifi_connection_settings(conn)
|
||||
.await
|
||||
.context("failed to get connection settings")
|
||||
.map_or_else(
|
||||
|
|
@ -1326,7 +1317,7 @@ fn connection_settings(conn: zbus::Connection) -> Task<crate::app::Message> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn update_state(conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
pub fn update_state(conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
cosmic::task::future(async move {
|
||||
match NetworkManagerState::new(&conn).await {
|
||||
Ok(state) => Message::UpdateState(state),
|
||||
|
|
@ -1335,7 +1326,7 @@ pub fn update_state(conn: zbus::Connection) -> Task<crate::app::Message> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn update_devices(conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
pub fn update_devices(conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
cosmic::task::future(async move {
|
||||
let filter =
|
||||
|device_type| matches!(device_type, network_manager::devices::DeviceType::Wifi);
|
||||
|
|
|
|||
|
|
@ -10,12 +10,14 @@ use cosmic::iced::{Alignment, Length};
|
|||
use cosmic::widget::space::horizontal as horizontal_space;
|
||||
use cosmic::widget::{self, icon};
|
||||
use cosmic::{Apply, Element, Task};
|
||||
use cosmic_dbus_networkmanager::interface::enums::DeviceState;
|
||||
use cosmic_settings_network_manager_subscription::current_networks::ActiveConnectionInfo;
|
||||
use cosmic_settings_network_manager_subscription::{self as network_manager, NetworkManagerState};
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
|
||||
use super::backend as network_manager;
|
||||
use super::backend::NetworkManagerState;
|
||||
use super::backend::current_networks::ActiveConnectionInfo;
|
||||
use super::backend::devices::DeviceState;
|
||||
|
||||
pub type ConnectionId = Arc<str>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -32,8 +34,8 @@ pub enum Message {
|
|||
Error(String),
|
||||
/// An update from the network manager daemon
|
||||
NetworkManager(network_manager::Event),
|
||||
/// Successfully connected to the system dbus.
|
||||
NetworkManagerConnect(zbus::Connection),
|
||||
/// Successfully connected to NetworkManager.
|
||||
NetworkManagerConnect(nmrs::NetworkManager),
|
||||
/// Refresh devices and their connection profiles
|
||||
Refresh,
|
||||
/// Create a dialog to ask for confirmation of removal.
|
||||
|
|
@ -91,7 +93,7 @@ pub struct Page {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct NmState {
|
||||
conn: zbus::Connection,
|
||||
conn: nmrs::NetworkManager,
|
||||
sender: futures::channel::mpsc::UnboundedSender<network_manager::Request>,
|
||||
active_conns: Vec<ActiveConnectionInfo>,
|
||||
devices: Vec<Arc<network_manager::devices::DeviceInfo>>,
|
||||
|
|
@ -154,9 +156,9 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
fn on_enter(&mut self) -> cosmic::Task<crate::pages::Message> {
|
||||
if self.nm_task.is_none() {
|
||||
return cosmic::task::future(async move {
|
||||
zbus::Connection::system()
|
||||
nmrs::NetworkManager::new()
|
||||
.await
|
||||
.context("failed to create system dbus connection")
|
||||
.context("failed to connect to NetworkManager")
|
||||
.map_or_else(
|
||||
|why| Message::Error(why.to_string()),
|
||||
Message::NetworkManagerConnect,
|
||||
|
|
@ -356,18 +358,14 @@ impl Page {
|
|||
Task::none()
|
||||
}
|
||||
|
||||
fn connect(&mut self, conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
fn connect(&mut self, conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
if self.nm_task.is_none() {
|
||||
let (canceller, task) =
|
||||
crate::utils::forward_event_loop(move |mut sender| async move {
|
||||
let (tx, mut rx) = futures::channel::mpsc::channel(1);
|
||||
|
||||
let watchers = std::pin::pin!(async move {
|
||||
futures::join!(
|
||||
network_manager::watch(conn.clone(), tx.clone()),
|
||||
network_manager::active_conns::watch(conn.clone(), tx.clone()),
|
||||
network_manager::devices::watch(conn, true, tx)
|
||||
)
|
||||
network_manager::watch(conn, tx).await;
|
||||
});
|
||||
|
||||
let forwarder = std::pin::pin!(async move {
|
||||
|
|
@ -622,7 +620,7 @@ fn popup_button(message: Message, text: &str) -> Element<'_, Message> {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn update_state(conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
fn update_state(conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
cosmic::task::future(async move {
|
||||
match NetworkManagerState::new(&conn).await {
|
||||
Ok(state) => Message::UpdateState(state),
|
||||
|
|
@ -631,7 +629,7 @@ fn update_state(conn: zbus::Connection) -> Task<crate::app::Message> {
|
|||
})
|
||||
}
|
||||
|
||||
fn update_devices(conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||
fn update_devices(conn: nmrs::NetworkManager) -> Task<crate::app::Message> {
|
||||
cosmic::task::future(async move {
|
||||
let filter =
|
||||
|device_type| matches!(device_type, network_manager::devices::DeviceType::Ethernet);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue