From 5dcee4077d44ab442e8e6b5949196b26824947da Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Wed, 10 Dec 2025 20:54:45 -0500 Subject: [PATCH 1/3] fix(#111): re-align refresh button --- nmrs-ui/src/ui/header.rs | 1 - nmrs-ui/src/ui/mod.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/nmrs-ui/src/ui/header.rs b/nmrs-ui/src/ui/header.rs index 2f7e3d33..57b3dd23 100644 --- a/nmrs-ui/src/ui/header.rs +++ b/nmrs-ui/src/ui/header.rs @@ -103,7 +103,6 @@ pub fn build_header( let refresh_btn = gtk::Button::from_icon_name("view-refresh-symbolic"); refresh_btn.add_css_class("refresh-btn"); - refresh_btn.set_valign(gtk::Align::Start); header.pack_end(&refresh_btn); refresh_btn.connect_clicked(clone!( #[weak] diff --git a/nmrs-ui/src/ui/mod.rs b/nmrs-ui/src/ui/mod.rs index 6906bab0..28a16b92 100644 --- a/nmrs-ui/src/ui/mod.rs +++ b/nmrs-ui/src/ui/mod.rs @@ -19,7 +19,7 @@ type CallbackCell = Rc>>; pub fn build_ui(app: &Application) { let win = ApplicationWindow::new(app); win.set_title(Some("")); - win.set_default_size(400, 600); + win.set_default_size(100, 600); if let Some(key) = crate::theme_config::load_theme() && let Some(theme) = THEMES.iter().find(|t| t.key == key.as_str()) From 3f3748a987f0d86001113d0253c1ca8f6670ae1e Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Wed, 10 Dec 2025 21:08:32 -0500 Subject: [PATCH 2/3] fix(#80): extract polling logic into unified helper --- nmrs-core/src/connection.rs | 125 +----------------------------------- nmrs-core/src/lib.rs | 3 +- nmrs-core/src/state_wait.rs | 106 ++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 123 deletions(-) create mode 100644 nmrs-core/src/state_wait.rs diff --git a/nmrs-core/src/connection.rs b/nmrs-core/src/connection.rs index 2233f65a..1576c71c 100644 --- a/nmrs-core/src/connection.rs +++ b/nmrs-core/src/connection.rs @@ -5,9 +5,10 @@ use zvariant::OwnedObjectPath; use crate::connection_settings::{delete_connection, get_saved_connection_path}; use crate::constants::{device_state, device_type, retries, timeouts}; -use crate::models::{ConnectionOptions, DeviceState, WifiSecurity}; +use crate::models::{ConnectionOptions, WifiSecurity}; use crate::network_info::current_ssid; use crate::proxies::{NMAccessPointProxy, NMDeviceProxy, NMProxy, NMWirelessProxy}; +use crate::state_wait::wait_for_connection_state; use crate::utils::decode_ssid_or_empty; use crate::wifi_builders::build_wifi_connection; @@ -263,127 +264,7 @@ pub(crate) async fn connect(conn: &Connection, ssid: &str, creds: WifiSecurity) eprintln!("Dev state after connect(): {initial_state:?}"); eprintln!("Waiting for connection to complete..."); - let mut connected = false; - let mut config_state_count = 0; - for i in 0..retries::CONNECTION_MAX_RETRIES { - Delay::new(timeouts::connection_poll_interval()).await; - match dev_proxy.state().await { - Ok(state) => { - let device_state = DeviceState::from(state); - eprintln!("Connection progress {i}: state = {device_state:?} ({state})"); - - if state == device_state::ACTIVATED { - eprintln!("✓ Connection activated successfully!"); - connected = true; - break; - } else if state == device_state::FAILED { - eprintln!("✗ Connection failed!"); - - if let Ok(reason) = dev_proxy.state_reason().await { - eprintln!("Failure reason code: {reason:?}"); - let reason_str = match reason.1 { - 0 => "Unknown", - 1 => "None", - 2 => "User disconnected", - 3 => "Device disconnected", - 4 => "Carrier changed", - 7 => "Supplicant disconnected", - 8 => "Supplicant config failed", - 9 => "Supplicant failed", - 10 => "Supplicant timeout", - 11 => "PPP start failed", - 15 => "DHCP start failed", - 16 => "DHCP error", - 17 => "DHCP failed", - 24 => "Modem connection failed", - 25 => "Modem init failed", - 42 => "Infiniband mode", - 43 => "Dependency failed", - 44 => "BR2684 failed", - 45 => "Mode set failed", - 46 => "GSM APN select failed", - 47 => "GSM not searching", - 48 => "GSM registration denied", - 49 => "GSM registration timeout", - 50 => "GSM registration failed", - 51 => "GSM PIN check failed", - 52 => "Firmware missing", - 53 => "Device removed", - 54 => "Sleeping", - 55 => "Connection removed", - 56 => "User requested", - 57 => "Carrier", - 58 => "Connection assumed", - 59 => "Supplicant available", - 60 => "Modem not found", - 61 => "Bluetooth failed", - 62 => "GSM SIM not inserted", - 63 => "GSM SIM PIN required", - 64 => "GSM SIM PUK required", - 65 => "GSM SIM wrong", - 66 => "InfiniBand mode", - 67 => "Dependency failed", - 68 => "BR2684 failed", - 69 => "Modem manager unavailable", - 70 => "SSID not found", - 71 => "Secondary connection failed", - 72 => "DCB or FCoE setup failed", - 73 => "Teamd control failed", - 74 => "Modem failed or no longer available", - 75 => "Modem now ready and available", - 76 => "SIM PIN was incorrect", - 77 => "New connection activation enqueued", - 78 => "Parent device unreachable", - 79 => "Parent device changed", - _ => "Unknown reason", - }; - eprintln!("Failure details: {reason_str}"); - } - - return Err(zbus::Error::Failure( - "Connection failed - authentication or network issue".into(), - )); - } else if state == device_state::CONFIG { - config_state_count += 1; - if config_state_count > retries::CONNECTION_CONFIG_STUCK_THRESHOLD { - eprintln!( - "✗ Connection stuck in Config state - likely authentication failure" - ); - return Err(zbus::Error::Failure( - "Connection failed - authentication failed".into(), - )); - } - } else { - config_state_count = 0; - } - - if i > retries::CONNECTION_STUCK_CHECK_START && state == device_state::DISCONNECTED - { - eprintln!("✗ Connection stuck in disconnected state"); - return Err(zbus::Error::Failure( - "Connection failed - stuck in disconnected state".into(), - )); - } - } - Err(e) => { - eprintln!("Failed to check device state: {e}"); - break; - } - } - } - - if !connected { - let final_state = dev_proxy.state().await.unwrap_or(0); - eprintln!("✗ Connection did not complete. Final state: {final_state}"); - if final_state == device_state::CONFIG { - return Err(zbus::Error::Failure( - "Connection failed - authentication failed".into(), - )); - } - return Err(zbus::Error::Failure( - "Connection failed - timeout waiting for activation".into(), - )); - } + wait_for_connection_state(&dev_proxy).await?; eprintln!("---Connection request for '{ssid}' submitted successfully---"); diff --git a/nmrs-core/src/lib.rs b/nmrs-core/src/lib.rs index f01950a0..e35e95fe 100644 --- a/nmrs-core/src/lib.rs +++ b/nmrs-core/src/lib.rs @@ -1,4 +1,4 @@ -// Internal implementation modules (not exposed to external users) +// Internal implementation modules mod connection; mod connection_settings; mod constants; @@ -6,6 +6,7 @@ mod device; mod network_info; mod proxies; mod scan; +mod state_wait; mod utils; // Public API modules diff --git a/nmrs-core/src/state_wait.rs b/nmrs-core/src/state_wait.rs new file mode 100644 index 00000000..40bd156a --- /dev/null +++ b/nmrs-core/src/state_wait.rs @@ -0,0 +1,106 @@ +use futures_timer::Delay; +use zbus::Result; + +use crate::constants::{device_state, retries, timeouts}; +use crate::proxies::NMDeviceProxy; + +pub async fn wait_for_connection_state(dev: &NMDeviceProxy<'_>) -> Result<()> { + let mut config_stuck = 0; + + for i in 0..retries::CONNECTION_MAX_RETRIES { + Delay::new(timeouts::connection_poll_interval()).await; + + let raw = dev.state().await?; + + if raw == device_state::ACTIVATED { + return Ok(()); + } + + if raw == device_state::FAILED { + if let Ok((_, code)) = dev.state_reason().await { + let reason = decode_reason(code); + return Err(zbus::Error::Failure(format!( + "connection failed: reason={reason}" + ))); + } + return Err(zbus::Error::Failure("connection failed".into())); + } + + if raw == device_state::CONFIG { + config_stuck += 1; + if config_stuck > retries::CONNECTION_CONFIG_STUCK_THRESHOLD { + return Err(zbus::Error::Failure("connection stuck in config".into())); + } + } else { + config_stuck = 0; + } + + if i > retries::CONNECTION_STUCK_CHECK_START && raw == device_state::DISCONNECTED { + return Err(zbus::Error::Failure("connection stuck disconnected".into())); + } + } + + let final_state = dev.state().await.unwrap_or(0); + Err(zbus::Error::Failure(format!( + "timeout waiting for activation, final_state={final_state}" + ))) +} + +fn decode_reason(code: u32) -> &'static str { + match code { + 0 => "Unknown", + 1 => "None", + 2 => "User disconnected", + 3 => "Device disconnected", + 4 => "Carrier changed", + 7 => "Supplicant disconnected", + 8 => "Supplicant config failed", + 9 => "Supplicant failed", + 10 => "Supplicant timeout", + 11 => "PPP start failed", + 15 => "DHCP start failed", + 16 => "DHCP error", + 17 => "DHCP failed", + 24 => "Modem connection failed", + 25 => "Modem init failed", + 42 => "Infiniband mode", + 43 => "Dependency failed", + 44 => "BR2684 failed", + 45 => "Mode set failed", + 46 => "GSM APN select failed", + 47 => "GSM not searching", + 48 => "GSM registration denied", + 49 => "GSM registration timeout", + 50 => "GSM registration failed", + 51 => "GSM PIN check failed", + 52 => "Firmware missing", + 53 => "Device removed", + 54 => "Sleeping", + 55 => "Connection removed", + 56 => "User requested", + 57 => "Carrier", + 58 => "Connection assumed", + 59 => "Supplicant available", + 60 => "Modem not found", + 61 => "Bluetooth failed", + 62 => "GSM SIM not inserted", + 63 => "GSM SIM PIN required", + 64 => "GSM SIM PUK required", + 65 => "GSM SIM wrong", + 66 => "InfiniBand mode", + 67 => "Dependency failed", + 68 => "BR2684 failed", + 69 => "Modem manager unavailable", + 70 => "SSID not found", + 71 => "Secondary connection failed", + 72 => "DCB or FCoE setup failed", + 73 => "Teamd control failed", + 74 => "Modem failed or no longer available", + 75 => "Modem now ready and available", + 76 => "SIM PIN was incorrect", + 77 => "New connection activation enqueued", + 78 => "Parent device unreachable", + 79 => "Parent device changed", + _ => "Unknown reason", + } +} From 8614275ceccb50853178575511b43246261b06a1 Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Wed, 10 Dec 2025 22:50:04 -0500 Subject: [PATCH 3/3] fix(#81): decomposed `connect()` --- nmrs-core/src/connection.rs | 470 +++++++++++++++++++----------------- 1 file changed, 255 insertions(+), 215 deletions(-) diff --git a/nmrs-core/src/connection.rs b/nmrs-core/src/connection.rs index 1576c71c..c8b6cb18 100644 --- a/nmrs-core/src/connection.rs +++ b/nmrs-core/src/connection.rs @@ -12,6 +12,11 @@ use crate::state_wait::wait_for_connection_state; use crate::utils::decode_ssid_or_empty; use crate::wifi_builders::build_wifi_connection; +enum SavedDecision { + UseSaved(OwnedObjectPath), + RebuildFresh, +} + pub(crate) async fn connect(conn: &Connection, ssid: &str, creds: WifiSecurity) -> Result<()> { println!( "Connecting to '{}' | secured={} is_psk={} is_eap={}", @@ -23,60 +28,11 @@ pub(crate) async fn connect(conn: &Connection, ssid: &str, creds: WifiSecurity) let nm = NMProxy::new(conn).await?; - let saved_conn_path = get_saved_connection_path(conn, ssid).await?; - - let use_saved_connection = if let Some(conn_path) = &saved_conn_path { - // If PSK is empty, we're trying to use saved credentials - if creds.is_psk() { - if let WifiSecurity::WpaPsk { psk } = &creds { - if psk.trim().is_empty() { - eprintln!("Using saved connection at: {}", conn_path.as_str()); - true - } else { - eprintln!( - "Have saved connection but new password provided, deleting old and creating new" - ); - let _ = delete_connection(conn, conn_path.clone()).await; - false - } - } else { - false - } - } else { - // For open or EAP, use saved if available - eprintln!("Using saved connection at: {}", conn_path.as_str()); - true - } - } else { - // No saved connection - if creds.is_psk() - && let WifiSecurity::WpaPsk { psk } = &creds - && psk.trim().is_empty() - { - return Err(zbus::Error::Failure( - "No saved connection and PSK is empty".into(), - )); - } - - false - }; + let saved_raw = get_saved_connection_path(conn, ssid).await?; + let decision = decide_saved_connection(saved_raw, &creds)?; - let devices = nm.get_devices().await?; - let mut wifi_device: Option = None; - - for dp in devices { - let dev = NMDeviceProxy::builder(conn) - .path(dp.clone())? - .build() - .await?; - if dev.device_type().await? == device_type::WIFI { - wifi_device = Some(dp.clone()); - eprintln!(" Found WiFi device: {dp}"); - break; - } - } - - let wifi_device = wifi_device.ok_or(zbus::Error::Failure("no Wi-Fi device found".into()))?; + let wifi_device = find_wifi_device(conn, &nm).await?; + eprintln!("Found WiFi device: {}", wifi_device.as_str()); let wifi = NMWirelessProxy::builder(conn) .path(wifi_device.clone())? @@ -93,176 +49,21 @@ pub(crate) async fn connect(conn: &Connection, ssid: &str, creds: WifiSecurity) eprintln!("Not currently connected to any network"); } - match wifi.request_scan(HashMap::new()).await { - Ok(_) => eprintln!("Scan requested successfully"), - Err(e) => eprintln!("Scan request FAILED: {e}"), - } - Delay::new(timeouts::scan_wait()).await; - eprintln!("Scan wait complete"); - - let mut ap_path: Option = None; - for ap in wifi.get_all_access_points().await? { - let apx = NMAccessPointProxy::builder(conn) - .path(ap.clone())? - .build() - .await?; - let ssid_bytes = apx.ssid().await?; - let ap_ssid = decode_ssid_or_empty(&ssid_bytes); - eprintln!("Found AP: '{ap_ssid}'"); - if ap_ssid == ssid { - ap_path = Some(ap.clone()); - eprintln!("Matched target SSID"); - break; - } - } - - if ap_path.is_none() { - return Err(zbus::Error::Failure(format!("Network '{ssid}' not found"))); - } - - let specific_object = ap_path.unwrap(); - - if use_saved_connection { - if let Some(active) = current_ssid(conn).await { - eprintln!("Disconnecting from {active}"); - if let Ok(conns) = nm.active_connections().await { - for conn_path in conns { - let _ = nm.deactivate_connection(conn_path).await; - } - } - disconnect_wifi_device(conn, &wifi_device).await? - } - - let conn_path = saved_conn_path.unwrap(); - eprintln!("Activating saved connection: {}", conn_path.as_str()); - - match nm - .activate_connection( - conn_path.clone(), - wifi_device.clone(), - specific_object.clone(), - ) - .await - { - Ok(active_conn) => { - eprintln!( - "activate_connection() succeeded, active connection: {}", - active_conn.as_str() - ); - - Delay::new(timeouts::disconnect_final_delay()).await; - - let dev_check = NMDeviceProxy::builder(conn) - .path(wifi_device.clone())? - .build() - .await?; - - let check_state = dev_check.state().await?; - - if check_state == device_state::DISCONNECTED { - eprintln!("Connection activated but device stuck in Disconnected state"); - eprintln!("Saved connection has invalid settings, deleting and retrying"); - - let _ = nm.deactivate_connection(active_conn).await; - - let _ = delete_connection(conn, conn_path).await; + let specific_object = scan_and_resolve_ap(conn, &wifi, ssid).await?; - let opts = ConnectionOptions { - autoconnect: true, - autoconnect_priority: None, - autoconnect_retries: None, - }; - - let settings = build_wifi_connection(ssid, &creds, &opts); - - eprintln!("Creating fresh connection with corrected settings"); - match nm - .add_and_activate_connection(settings, wifi_device.clone(), specific_object) - .await - { - Ok(_) => eprintln!("Fresh connection created successfully"), - Err(e) => { - eprintln!("Fresh connection also failed: {e}"); - return Err(e); - } - } - } - } - Err(e) => { - eprintln!("activate_connection() failed: {e}"); - eprintln!( - "Saved connection may be corrupted, deleting and retrying with fresh connection" - ); - - let _ = delete_connection(conn, conn_path).await; - - let opts = ConnectionOptions { - autoconnect: true, - autoconnect_priority: None, - autoconnect_retries: None, - }; - - let settings = build_wifi_connection(ssid, &creds, &opts); - - eprintln!("Creating fresh connection after saved connection failed"); - return match nm - .add_and_activate_connection(settings, wifi_device.clone(), specific_object) - .await - { - Ok(_) => { - eprintln!("Successfully created fresh connection"); - Ok(()) - } - Err(e) => { - eprintln!("Fresh connection also failed: {e}"); - Err(e) - } - }; - } - } - } else { - let opts = ConnectionOptions { - autoconnect: true, - autoconnect_priority: None, - autoconnect_retries: None, - }; - - let settings = build_wifi_connection(ssid, &creds, &opts); - - println!("Creating new connection, settings: \n{settings:#?}"); - - if let Some(active) = current_ssid(conn).await { - eprintln!("Disconnecting from {active}."); - if let Ok(conns) = nm.active_connections().await { - for conn_path in conns { - let _ = nm.deactivate_connection(conn_path).await; - } - } - disconnect_wifi_device(conn, &wifi_device).await?; + match decision { + SavedDecision::UseSaved(saved) => { + ensure_disconnected(conn, &nm, &wifi_device).await?; + connect_via_saved(conn, &nm, &wifi_device, &specific_object, &creds, saved).await?; } - - match nm - .add_and_activate_connection(settings, wifi_device.clone(), specific_object) - .await - { - Ok(_) => eprintln!("add_and_activate_connection() succeeded"), - Err(e) => { - eprintln!("add_and_activate_connection() failed: {e}"); - return Err(e); - } + SavedDecision::RebuildFresh => { + build_and_activate_new(conn, &nm, &wifi_device, &specific_object, ssid, creds).await?; } } - - Delay::new(timeouts::disconnect_poll_interval()).await; - let dev_proxy = NMDeviceProxy::builder(conn) .path(wifi_device.clone())? .build() .await?; - - let initial_state = dev_proxy.state().await?; - eprintln!("Dev state after connect(): {initial_state:?}"); - eprintln!("Waiting for connection to complete..."); wait_for_connection_state(&dev_proxy).await?; @@ -270,6 +71,7 @@ pub(crate) async fn connect(conn: &Connection, ssid: &str, creds: WifiSecurity) Ok(()) } + pub(crate) async fn forget(conn: &Connection, ssid: &str) -> zbus::Result<()> { use std::collections::HashMap; use zvariant::{OwnedObjectPath, Value}; @@ -461,3 +263,241 @@ pub(crate) async fn disconnect_wifi_device( Err(e) => Err(e), } } + +async fn find_wifi_device(conn: &Connection, nm: &NMProxy<'_>) -> Result { + let devices = nm.get_devices().await?; + + for dp in devices { + let dev = NMDeviceProxy::builder(conn) + .path(dp.clone())? + .build() + .await?; + if dev.device_type().await? == device_type::WIFI { + return Ok(dp); + } + } + Err(zbus::Error::Failure("no Wi-Fi device found".into())) +} + +async fn find_ap( + conn: &Connection, + wifi: &NMWirelessProxy<'_>, + target_ssid: &str, +) -> Result { + let access_points = wifi.get_all_access_points().await?; + + for ap_path in access_points { + let ap = NMAccessPointProxy::builder(conn) + .path(ap_path.clone())? + .build() + .await?; + + let ssid_bytes = ap.ssid().await?; + let ssid = decode_ssid_or_empty(&ssid_bytes); + + if ssid == target_ssid { + return Ok(ap_path); + } + } + + Err(zbus::Error::Failure(format!( + "Network '{target_ssid}' not found" + ))) +} + +async fn ensure_disconnected( + conn: &Connection, + nm: &NMProxy<'_>, + wifi_device: &OwnedObjectPath, +) -> Result<()> { + if let Some(active) = current_ssid(conn).await { + eprintln!("Disconnecting from {active}"); + + if let Ok(conns) = nm.active_connections().await { + for conn_path in conns { + let _ = nm.deactivate_connection(conn_path).await; + } + } + + disconnect_wifi_device(conn, wifi_device).await?; + } + + Ok(()) +} + +async fn connect_via_saved( + conn: &Connection, + nm: &NMProxy<'_>, + wifi_device: &OwnedObjectPath, + ap: &OwnedObjectPath, + creds: &WifiSecurity, + saved: OwnedObjectPath, +) -> Result<()> { + eprintln!("Activating saved connection: {}", saved.as_str()); + + match nm + .activate_connection(saved.clone(), wifi_device.clone(), ap.clone()) + .await + { + Ok(active_conn) => { + eprintln!( + "activate_connection() succeeded, active connection: {}", + active_conn.as_str() + ); + + Delay::new(timeouts::disconnect_final_delay()).await; + + let dev_check = NMDeviceProxy::builder(conn) + .path(wifi_device.clone())? + .build() + .await?; + + let check_state = dev_check.state().await?; + + if check_state == device_state::DISCONNECTED { + eprintln!("Connection activated but device stuck in Disconnected state"); + eprintln!("Saved connection has invalid settings, deleting and retrying"); + + let _ = nm.deactivate_connection(active_conn).await; + + let _ = delete_connection(conn, saved.clone()).await; + + let opts = ConnectionOptions { + autoconnect: true, + autoconnect_priority: None, + autoconnect_retries: None, + }; + + let settings = build_wifi_connection(ap.as_str(), creds, &opts); + + eprintln!("Creating fresh connection with corrected settings"); + nm.add_and_activate_connection(settings, wifi_device.clone(), ap.clone()) + .await + .map_err(|e| { + eprintln!("Fresh connection also failed. SOL: {e}"); + e + })?; + } + } + + Err(e) => { + eprintln!("activate_connection() failed: {e}"); + eprintln!( + "Saved connection may be corrupted, deleting and retrying with fresh connection" + ); + + let _ = delete_connection(conn, saved.clone()).await; + + let opts = ConnectionOptions { + autoconnect: true, + autoconnect_priority: None, + autoconnect_retries: None, + }; + + let settings = build_wifi_connection(ap.as_str(), creds, &opts); + + nm.add_and_activate_connection(settings, wifi_device.clone(), ap.clone()) + .await + .map_err(|e| { + eprintln!("Fresh connection also failed. SOL: {e}"); + e + })?; + } + } + + Ok(()) +} + +async fn build_and_activate_new( + conn: &Connection, + nm: &NMProxy<'_>, + wifi_device: &OwnedObjectPath, + ap: &OwnedObjectPath, + ssid: &str, + creds: WifiSecurity, +) -> Result<()> { + let opts = ConnectionOptions { + autoconnect: true, + autoconnect_retries: None, + autoconnect_priority: None, + }; + + let settings = build_wifi_connection(ssid, &creds, &opts); + + eprintln!("Creating new connetion, settings: \n{settings:#?}"); + + ensure_disconnected(conn, nm, wifi_device).await?; + + match nm + .add_and_activate_connection(settings, wifi_device.clone(), ap.clone()) + .await + { + Ok(_) => eprintln!("add_and_activate_connection() succeeded"), + Err(e) => { + eprintln!("add_and_activate_connection() failed: {e}"); + return Err(e); + } + } + + Delay::new(timeouts::disconnect_poll_interval()).await; + + let dev_proxy = NMDeviceProxy::builder(conn) + .path(wifi_device.clone())? + .build() + .await?; + + let initial_state = dev_proxy.state().await?; + eprintln!("Dev state after build_and_activate_new(): {initial_state:?}"); + eprintln!("Waiting for connection to complete..."); + wait_for_connection_state(&dev_proxy).await?; + + eprintln!("---Connection request for '{ssid}' submitted successfully---"); + + Ok(()) +} + +async fn scan_and_resolve_ap( + conn: &Connection, + wifi: &NMWirelessProxy<'_>, + ssid: &str, +) -> Result { + match wifi.request_scan(HashMap::new()).await { + Ok(_) => eprintln!("Scan requested successfully"), + Err(e) => eprintln!("Scan request failed: {e}"), + } + + Delay::new(timeouts::scan_wait()).await; + eprintln!("Scan wait complete"); + + let ap = find_ap(conn, wifi, ssid).await?; + eprintln!("Matched target SSID '{ssid}'"); + Ok(ap) +} + +fn decide_saved_connection( + saved: Option, + creds: &WifiSecurity, +) -> Result { + if let Some(path) = saved { + if creds.is_psk() + && let WifiSecurity::WpaPsk { psk } = creds + { + if psk.trim().is_empty() { + return Ok(SavedDecision::UseSaved(path)); + } + return Ok(SavedDecision::RebuildFresh); + } + return Ok(SavedDecision::UseSaved(path)); + } + + if creds.is_psk() + && let WifiSecurity::WpaPsk { psk } = creds + && psk.trim().is_empty() + { + return Err(zbus::Error::Failure( + "No saved connection and psk is empty".into(), + )); + } + + Ok(SavedDecision::RebuildFresh) +}