From 4d56b2accc2a26a25570e6e5d201c19ba53bd340 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 6 Jan 2026 20:22:34 -0500 Subject: [PATCH] improv(network): focus the secure input --- .../src/pages/networking/vpn/mod.rs | 29 ++++++++++++++++- cosmic-settings/src/pages/networking/wifi.rs | 32 +++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/cosmic-settings/src/pages/networking/vpn/mod.rs b/cosmic-settings/src/pages/networking/vpn/mod.rs index 6446ae2..8ec7e1d 100644 --- a/cosmic-settings/src/pages/networking/vpn/mod.rs +++ b/cosmic-settings/src/pages/networking/vpn/mod.rs @@ -4,10 +4,12 @@ pub mod nmcli; use std::collections::HashMap; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use anyhow::Context; use cosmic::dialog::file_chooser::FileFilter; +use cosmic::task; +use cosmic::widget::text_input::focus; use cosmic::{ Apply, Element, Task, iced::{Alignment, Length}, @@ -26,6 +28,8 @@ use tokio::sync::Mutex; use crate::pages::networking::SecretSender; +pub static SECURE_INPUT_VPN: LazyLock = LazyLock::new(widget::Id::unique); + pub type ConnectionId = Arc; pub type InterfaceId = String; @@ -47,6 +51,8 @@ pub enum Message { Deactivate(ConnectionId), /// An error occurred. Error(ErrorKind, String), + /// Focus the secure input + FocusSecureInput, /// VPN connection error. VpnDialogError(VpnDialog), /// Update the list of known connections. @@ -252,6 +258,7 @@ impl page::Page for Page { Some(Message::TogglePasswordVisibility), *password_hidden, ) + .id(SECURE_INPUT_VPN.clone()) .on_input(|input| Message::PasswordUpdate(SecureString::from(input))) .on_submit(|_| { if error.is_some() { @@ -518,6 +525,7 @@ impl Page { error: None, tx: Arc::new(Mutex::new(None)), }); + return task::message(Message::FocusSecureInput); } _ => { let connection_name = settings.id.clone(); @@ -796,6 +804,7 @@ impl Page { tx, error: None, }); + return task::message(Message::FocusSecureInput); } nm_secret_agent::Event::CancelGetSecrets { uuid: _, name: _ } => { self.dialog = self @@ -824,9 +833,27 @@ impl Page { password_hidden: true, tx: Arc::new(Mutex::new(None)), }); + return task::message(Message::FocusSecureInput); } } }, + Message::FocusSecureInput => { + // retry until the widget is in the tree and focused or the dialog is removed. + if matches!(self.dialog, Some(VpnDialog::Password { .. })) { + return cosmic::iced_runtime::task::widget( + cosmic::iced_core::widget::operation::focusable::find_focused(), + ) + .collect() + .then(|id| { + if id.get(0).is_some_and(|id| *id == SECURE_INPUT_VPN.clone()) { + Task::none() + } else { + focus(SECURE_INPUT_VPN.clone()) + .chain(task::message(Message::FocusSecureInput)) + } + }); + } + } } Task::none() diff --git a/cosmic-settings/src/pages/networking/wifi.rs b/cosmic-settings/src/pages/networking/wifi.rs index fb21b80..f4b068f 100644 --- a/cosmic-settings/src/pages/networking/wifi.rs +++ b/cosmic-settings/src/pages/networking/wifi.rs @@ -3,7 +3,7 @@ use std::{ collections::{BTreeMap, BTreeSet}, - sync::Arc, + sync::{Arc, LazyLock}, }; use anyhow::Context; @@ -13,7 +13,8 @@ use cosmic::{ iced::{Alignment, Length}, iced_core::text::Wrapping, iced_widget::focus_next, - widget::{self, column, icon}, + task, + widget::{self, column, icon, text_input::focus}, }; use cosmic_settings_network_manager_subscription::{ self as network_manager, NetworkManagerState, @@ -29,6 +30,8 @@ use tokio::sync::Mutex; use crate::pages::networking::SecretSender; +pub static SECURE_INPUT_WIFI: LazyLock = LazyLock::new(widget::Id::unique); + #[derive(Clone, Debug)] pub enum Message { /// Add a network connection with nm-connection-editor @@ -47,6 +50,8 @@ pub enum Message { Error(String), /// Identity update from the dialog IdentityUpdate(String), + /// Focus the secure input + FocusSecureInput, /// Create a dialog to ask for confirmation on forgetting a connection. ForgetRequest(network_manager::SSID), /// Forget a known access point. @@ -178,6 +183,7 @@ impl page::Page for Page { Some(Message::TogglePasswordVisibility), *password_hidden, ) + .id(SECURE_INPUT_WIFI.clone()) .on_input(|input| Message::PasswordUpdate(SecureString::from(input))) .on_submit(|_| Message::ConnectWithPassword); @@ -365,6 +371,7 @@ impl Page { password_hidden: true, tx: Arc::new(Mutex::new(None)), }); + return task::message(Message::FocusSecureInput); } } @@ -386,6 +393,7 @@ impl Page { password_hidden: true, tx: Arc::new(Mutex::new(None)), }); + return task::message(Message::FocusSecureInput); } } @@ -525,6 +533,7 @@ impl Page { password_hidden: true, tx: Arc::new(Mutex::new(None)), }); + return task::message(Message::FocusSecureInput); } } Message::PasswordUpdate(pass) => { @@ -713,6 +722,7 @@ impl Page { identity: matches!(ap.network_type, NetworkType::EAP).then(String::new), tx, }); + return task::message(Message::FocusSecureInput); } nm_secret_agent::Event::CancelGetSecrets { uuid: _, name: _ } => { self.dialog = self @@ -738,9 +748,27 @@ impl Page { identity, hw_address, }); + return task::message(Message::FocusSecureInput); } } }, + Message::FocusSecureInput => { + // retry until the widget is in the tree and focused or the dialog is removed. + if matches!(self.dialog, Some(WiFiDialog::Password { .. })) { + return cosmic::iced_runtime::task::widget( + cosmic::iced_core::widget::operation::focusable::find_focused(), + ) + .collect() + .then(|id| { + if id.get(0).is_some_and(|id| *id == SECURE_INPUT_WIFI.clone()) { + Task::none() + } else { + focus(SECURE_INPUT_WIFI.clone()) + .chain(task::message(Message::FocusSecureInput)) + } + }); + } + } } Task::none()