From 9e351780b798f4d0443a6b655154c821e4a6c76f Mon Sep 17 00:00:00 2001 From: santoshxshrestha Date: Sat, 3 Jan 2026 21:28:19 +0545 Subject: [PATCH 01/11] change: used the RwLock in the scan_networks taht will write the contents mutably --- src/utils/scan.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/scan.rs b/src/utils/scan.rs index 0c8961b..359a0a8 100644 --- a/src/utils/scan.rs +++ b/src/utils/scan.rs @@ -1,13 +1,13 @@ use crate::WifiNetwork; use crate::utils::saved_connection::saved_connections; use std::process::Command; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, RwLock}; use std::thread; -pub fn scan_networks(wifi_list: Arc>>) { +pub fn scan_networks(wifi_list: Arc>>) { // // nmcli -t -f IN-USE,SSID,SECURITY device wifi list thread::spawn(move || { - let mut wifi_list_lock = wifi_list.lock().unwrap(); + let mut wifi_list_lock = wifi_list.write().unwrap(); let output = Command::new("nmcli") .args(["-t", "-f", "IN-USE,SSID,SECURITY", "device", "wifi", "list"]) .output() From 1d95ad034114d21e562a8efae5af376a76fa2b7c Mon Sep 17 00:00:00 2001 From: santoshxshrestha Date: Sat, 3 Jan 2026 21:30:25 +0545 Subject: [PATCH 02/11] type: changed to RW_lock --- src/apps/core.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/apps/core.rs b/src/apps/core.rs index 215c014..f69deb2 100644 --- a/src/apps/core.rs +++ b/src/apps/core.rs @@ -20,11 +20,12 @@ use std::io; use std::sync::{Arc, Mutex}; mod delete_handler; mod help_handlers; +use std::sync::RwLock; #[derive(Debug)] pub struct App { wifi_credentials: WifiInputState, - wifi_list: Arc>>, + wifi_list: Arc>>, selected: usize, app_state: AppState, saved_connection: SavedConnections, @@ -45,7 +46,7 @@ impl Default for App { /// assert!(app.wifi_list.lock().unwrap().is_empty()); /// ``` fn default() -> Self { - let wifi_list = Arc::new(Mutex::new(Vec::new())); + let wifi_list = Arc::new(RwLock::new(Vec::new())); scan_networks(wifi_list.clone()); Self { wifi_credentials: WifiInputState::default(), From 18789c466620d97b29df8808808d82e15ebd6b77 Mon Sep 17 00:00:00 2001 From: santoshxshrestha Date: Sat, 3 Jan 2026 21:46:05 +0545 Subject: [PATCH 03/11] implemented to work with rwlock --- src/apps/core.rs | 6 +++--- src/apps/core/delete_handler.rs | 6 +++++- src/apps/core/widget.rs | 2 +- src/main.rs | 2 +- src/utils/disconnect_connection.rs | 6 +++--- src/utils/scan.rs | 2 +- src/utils/tui.rs | 2 +- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/apps/core.rs b/src/apps/core.rs index f69deb2..8d0f50b 100644 --- a/src/apps/core.rs +++ b/src/apps/core.rs @@ -17,7 +17,7 @@ use crossterm::execute; use ratatui::Frame; use ratatui::layout::Position; use std::io; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; mod delete_handler; mod help_handlers; use std::sync::RwLock; @@ -109,7 +109,7 @@ impl App { } fn prepare_to_connect(&mut self) { - match self.wifi_list.try_lock() { + match self.wifi_list.write() { Ok(wifi_list) => { // if the selected network is already in use, do nothing if wifi_list[self.selected].in_use { @@ -159,7 +159,7 @@ impl App { } fn update_selected_network(&mut self, direction: isize) { - if let Ok(wifi_list) = self.wifi_list.try_lock() { + if let Ok(wifi_list) = self.wifi_list.read() { let len = wifi_list.len(); if len > 0 { self.selected = diff --git a/src/apps/core/delete_handler.rs b/src/apps/core/delete_handler.rs index dc85d22..88be6c0 100644 --- a/src/apps/core/delete_handler.rs +++ b/src/apps/core/delete_handler.rs @@ -120,7 +120,11 @@ impl App { self.saved_connection.fetch_saved_connections(); } else { // this one will delete the connection from the wifi list - delete_connection(self.wifi_list.lock().unwrap()[self.selected].ssid.clone()); + delete_connection( + self.wifi_list.read().expect("WifiNetworks lock poisoned")[self.selected] + .ssid + .clone(), + ); self.reset_selection(); scan_networks(self.wifi_list.clone()); } diff --git a/src/apps/core/widget.rs b/src/apps/core/widget.rs index 02a7465..11a418a 100644 --- a/src/apps/core/widget.rs +++ b/src/apps/core/widget.rs @@ -63,7 +63,7 @@ impl Widget for &App { ); let mut rows = Vec::new(); - match self.wifi_list.try_lock() { + match self.wifi_list.read() { Ok(wifi_list) => { for (i, network) in wifi_list.iter().enumerate() { let ssid = if network.in_use { diff --git a/src/main.rs b/src/main.rs index 7bebf3d..8e792f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,6 @@ struct AppState { fn main() -> Result<()> { color_eyre::install()?; - tui().unwrap(); + tui().expect("Failed to run TUI application"); Ok(()) } diff --git a/src/utils/disconnect_connection.rs b/src/utils/disconnect_connection.rs index 586d479..d32126a 100644 --- a/src/utils/disconnect_connection.rs +++ b/src/utils/disconnect_connection.rs @@ -1,11 +1,11 @@ use crate::{WifiNetwork, apps::handlers::status::Status}; use std::{ process::{Command, ExitStatus}, - sync::{Arc, Mutex}, + sync::{Arc, RwLock}, }; -pub fn disconnect_connected_network(wifi_list: Arc>>) -> Status { - let list = wifi_list.lock().expect("WifiNetworks lock poisoned"); +pub fn disconnect_connected_network(wifi_list: Arc>>) -> Status { + let list = wifi_list.read().expect("WifiNetworks lock poisoned"); for network in list.iter() { if network.in_use { diff --git a/src/utils/scan.rs b/src/utils/scan.rs index 359a0a8..0ee8fb1 100644 --- a/src/utils/scan.rs +++ b/src/utils/scan.rs @@ -7,7 +7,7 @@ use std::thread; pub fn scan_networks(wifi_list: Arc>>) { // // nmcli -t -f IN-USE,SSID,SECURITY device wifi list thread::spawn(move || { - let mut wifi_list_lock = wifi_list.write().unwrap(); + let mut wifi_list_lock = wifi_list.write().expect("WifiNetworks lock poisoned"); let output = Command::new("nmcli") .args(["-t", "-f", "IN-USE,SSID,SECURITY", "device", "wifi", "list"]) .output() diff --git a/src/utils/tui.rs b/src/utils/tui.rs index f459247..f589a0d 100644 --- a/src/utils/tui.rs +++ b/src/utils/tui.rs @@ -2,6 +2,6 @@ use crate::apps::core::App; pub fn tui() -> Result<(), Box> { let mut terminal = ratatui::init(); let app_result = App::default().run(&mut terminal); - ratatui::try_restore().unwrap(); + ratatui::try_restore().expect("Failed to restore terminal"); app_result } From 95591fdd95f1ea5390a7888ff5bdbb8b87cf632a Mon Sep 17 00:00:00 2001 From: santoshxshrestha Date: Sat, 3 Jan 2026 23:22:18 +0545 Subject: [PATCH 04/11] flags: added a flag for state of the scannign --- src/apps/core.rs | 13 +++++++++++-- src/apps/handlers/flags.rs | 10 +++++++--- src/utils/scan.rs | 7 +++++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/apps/core.rs b/src/apps/core.rs index 8d0f50b..2a0613d 100644 --- a/src/apps/core.rs +++ b/src/apps/core.rs @@ -21,6 +21,7 @@ use std::sync::Arc; mod delete_handler; mod help_handlers; use std::sync::RwLock; +use std::sync::atomic::AtomicBool; #[derive(Debug)] pub struct App { @@ -47,14 +48,22 @@ impl Default for App { /// ``` fn default() -> Self { let wifi_list = Arc::new(RwLock::new(Vec::new())); - scan_networks(wifi_list.clone()); + + // Setting up scanning flag + let is_scanning = Arc::new(AtomicBool::new(true)); + scan_networks(wifi_list.clone(), is_scanning.clone()); Self { wifi_credentials: WifiInputState::default(), wifi_list, selected: 0, app_state: AppState::default(), saved_connection: SavedConnections::default(), - flags: Flags::default(), + flags: { + Flags { + is_scanning, + ..Default::default() + } + }, } } } diff --git a/src/apps/handlers/flags.rs b/src/apps/handlers/flags.rs index 08ae65d..08eaa53 100644 --- a/src/apps/handlers/flags.rs +++ b/src/apps/handlers/flags.rs @@ -1,10 +1,14 @@ +use std::sync::Arc; +use std::sync::atomic::AtomicBool; + #[derive(Debug, Default)] pub struct Flags { pub is_hidden: bool, + pub is_scanning: Arc, + pub show_delete_confirmation: bool, + pub show_help: bool, pub show_password_popup: bool, + pub show_saved: bool, pub show_ssid_popup: bool, pub show_status_popup: bool, - pub show_delete_confirmation: bool, - pub show_saved: bool, - pub show_help: bool, } diff --git a/src/utils/scan.rs b/src/utils/scan.rs index 0ee8fb1..bda6901 100644 --- a/src/utils/scan.rs +++ b/src/utils/scan.rs @@ -1,13 +1,13 @@ use crate::WifiNetwork; use crate::utils::saved_connection::saved_connections; use std::process::Command; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use std::thread; -pub fn scan_networks(wifi_list: Arc>>) { +pub fn scan_networks(wifi_list: Arc>>, is_scanning: Arc) { // // nmcli -t -f IN-USE,SSID,SECURITY device wifi list thread::spawn(move || { - let mut wifi_list_lock = wifi_list.write().expect("WifiNetworks lock poisoned"); let output = Command::new("nmcli") .args(["-t", "-f", "IN-USE,SSID,SECURITY", "device", "wifi", "list"]) .output() @@ -55,6 +55,9 @@ pub fn scan_networks(wifi_list: Arc>>) { ssid: "Connect to Hidden network".to_string(), security: "?".to_string(), }); + + let mut wifi_list_lock = wifi_list.write().expect("WifiNetworks lock poisoned"); *wifi_list_lock = networks; + is_scanning.store(false, Ordering::SeqCst); }); } From 7dc5415ec3b19a08c419119e02904b67c2b9022a Mon Sep 17 00:00:00 2001 From: santoshxshrestha Date: Sat, 3 Jan 2026 23:25:38 +0545 Subject: [PATCH 05/11] refactor: adapting the new flag --- src/apps/core.rs | 6 +++--- src/apps/core/delete_handler.rs | 2 +- src/apps/core/event_handlers.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/apps/core.rs b/src/apps/core.rs index 2a0613d..8c50df6 100644 --- a/src/apps/core.rs +++ b/src/apps/core.rs @@ -130,7 +130,7 @@ impl App { self.wifi_credentials.flags.show_status_popup = true; // refresh the network list after connection attempt - scan_networks(self.wifi_list.clone()); + scan_networks(self.wifi_list.clone(), self.flags.is_scanning.clone()); } // if the selected network is hidden network option // the show status popup will be handled by the password input listener @@ -152,7 +152,7 @@ impl App { self.wifi_credentials.status = status; self.wifi_credentials.flags.show_status_popup = true; // refresh the network list after connection attempt - scan_networks(self.wifi_list.clone()); + scan_networks(self.wifi_list.clone(), self.flags.is_scanning.clone()); } // else show the password popup else { @@ -181,7 +181,7 @@ impl App { fn disconnect(&mut self) { self.wifi_credentials.status = disconnect_connected_network(self.wifi_list.clone()); self.wifi_credentials.flags.show_status_popup = true; - scan_networks(self.wifi_list.clone()); + scan_networks(self.wifi_list.clone(), self.flags.is_scanning.clone()); } fn reset_selection(&mut self) { diff --git a/src/apps/core/delete_handler.rs b/src/apps/core/delete_handler.rs index 88be6c0..4b207c5 100644 --- a/src/apps/core/delete_handler.rs +++ b/src/apps/core/delete_handler.rs @@ -126,7 +126,7 @@ impl App { .clone(), ); self.reset_selection(); - scan_networks(self.wifi_list.clone()); + scan_networks(self.wifi_list.clone(), self.flags.is_scanning.clone()); } self.flags.show_delete_confirmation = false; } diff --git a/src/apps/core/event_handlers.rs b/src/apps/core/event_handlers.rs index 99f2450..88a0d86 100644 --- a/src/apps/core/event_handlers.rs +++ b/src/apps/core/event_handlers.rs @@ -65,7 +65,7 @@ impl App { kind: Press, .. }) => { - scan_networks(self.wifi_list.clone()); + scan_networks(self.wifi_list.clone(), self.flags.is_scanning.clone()); } Event::Key(KeyEvent { code: KeyCode::Enter, From 7a34f189b7acb2cd269b1317c5c6b9c29548eebe Mon Sep 17 00:00:00 2001 From: santoshxshrestha Date: Sat, 3 Jan 2026 23:53:29 +0545 Subject: [PATCH 06/11] fix: fix the scanning message --- src/apps/core/widget.rs | 62 +++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/apps/core/widget.rs b/src/apps/core/widget.rs index 11a418a..4dcd4b0 100644 --- a/src/apps/core/widget.rs +++ b/src/apps/core/widget.rs @@ -8,6 +8,7 @@ use ratatui::{ text::Line, widgets::{Block, Paragraph, Row, Table, TableState, Widget}, }; +use std::sync::atomic::Ordering; const INFO_TEXT: [&str; 2] = [ "[Esc] quit | (Ctrl+R) scan for networks | (h) help ", @@ -63,31 +64,44 @@ impl Widget for &App { ); let mut rows = Vec::new(); - match self.wifi_list.read() { - Ok(wifi_list) => { - for (i, network) in wifi_list.iter().enumerate() { - let ssid = if network.in_use { - format!("* {}", network.ssid) - } else { - network.ssid.clone() - }; - let mut row = Row::new(vec![ - ssid, - network.security.clone(), - network.is_saved.to_string(), - ]); - if i == self.selected { - row = row.style( - ratatui::style::Style::default() - .fg(ratatui::style::Color::Black) - .bg(ratatui::style::Color::White), - ); - } - rows.push(row); + + if self.flags.is_scanning.load(Ordering::SeqCst) { + // TODO: add a spinner or some animation + // Showing scanning message + let scanning_row = Row::new(vec!["Scanning for networks...", "", ""]).style( + ratatui::style::Style::default() + .fg(ratatui::style::Color::LightBlue) + .italic(), + ); + rows.push(scanning_row); + } else { + // This will not panic untill the thread holding the write lock panics so we can just unwrap here + let wifi_list = self.wifi_list.read().expect("WifiNetworks lock poisoned"); + + // populate the rows of the table that displays the wifi networks and highlights the selected one + // This will never be empty because we always add the "Connect to Hidden network" entry + // untill I implement a better way to handle the addition of hidden networks + + // TODO: implement the case if there are not networks found with a message to the user + for (i, network) in wifi_list.iter().enumerate() { + let ssid = if network.in_use { + format!("* {}", network.ssid) + } else { + network.ssid.clone() + }; + let mut row = Row::new(vec![ + ssid, + network.security.clone(), + network.is_saved.to_string(), + ]); + if i == self.selected { + row = row.style( + ratatui::style::Style::default() + .fg(ratatui::style::Color::Black) + .bg(ratatui::style::Color::White), + ); } - } - Err(_) => { - rows.push(Row::new(vec!["Scanning...", ""])); + rows.push(row); } } From 297f3a399199b0cc488ba9e952c9ad4db737ad61 Mon Sep 17 00:00:00 2001 From: santoshxshrestha Date: Sun, 4 Jan 2026 00:04:39 +0545 Subject: [PATCH 07/11] rice: added some animation with some time logic --- src/apps/core/widget.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/apps/core/widget.rs b/src/apps/core/widget.rs index 4dcd4b0..483db88 100644 --- a/src/apps/core/widget.rs +++ b/src/apps/core/widget.rs @@ -9,6 +9,7 @@ use ratatui::{ widgets::{Block, Paragraph, Row, Table, TableState, Widget}, }; use std::sync::atomic::Ordering; +use std::time::{self, SystemTime}; const INFO_TEXT: [&str; 2] = [ "[Esc] quit | (Ctrl+R) scan for networks | (h) help ", @@ -66,14 +67,26 @@ impl Widget for &App { let mut rows = Vec::new(); if self.flags.is_scanning.load(Ordering::SeqCst) { - // TODO: add a spinner or some animation - // Showing scanning message - let scanning_row = Row::new(vec!["Scanning for networks...", "", ""]).style( - ratatui::style::Style::default() - .fg(ratatui::style::Color::LightBlue) - .italic(), - ); - rows.push(scanning_row); + let spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + + // Use some time-based value for the index + // this will select a different spinner character every 100ms + // and push it to teh rows vector + // as this function is called every frame during the scanning process + // this will create the illusion of a spinner animation + let index = SystemTime::now() + .duration_since(time::UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() + / 100; + + let spinner_char = spinner[index as usize % spinner.len()]; + + rows.push(Row::new(vec![ + format!("{} Scanning...", spinner_char), + "".into(), + "".into(), + ])); } else { // This will not panic untill the thread holding the write lock panics so we can just unwrap here let wifi_list = self.wifi_list.read().expect("WifiNetworks lock poisoned"); From 32182fd8b5e17a7dc3c6ca1edb72c64aa3065e4e Mon Sep 17 00:00:00 2001 From: santoshxshrestha Date: Sun, 4 Jan 2026 00:23:23 +0545 Subject: [PATCH 08/11] fix: fix the issue regarding the appearance of the delete conformation for the unsaved connection from the main list --- src/apps/core/event_handlers.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/apps/core/event_handlers.rs b/src/apps/core/event_handlers.rs index 88a0d86..7a9f8cd 100644 --- a/src/apps/core/event_handlers.rs +++ b/src/apps/core/event_handlers.rs @@ -115,7 +115,14 @@ impl App { kind: Press, .. }) => { - self.flags.show_delete_confirmation = true; + if self + .wifi_list + .read() + .expect("Wifi list lock poisoned while deleting")[self.selected] + .is_saved + { + self.flags.show_delete_confirmation = true; + } } Event::Key(KeyEvent { code: KeyCode::Char('s'), From 7af8109fe7a4fbe7d59969b2584735598dd0672d Mon Sep 17 00:00:00 2001 From: santoshxshrestha Date: Sun, 4 Jan 2026 00:23:36 +0545 Subject: [PATCH 09/11] docs: update the changelog --- CHANGELOG.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4708d1b..1fc99fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,20 @@ All notable changes to this project will be documented in this file. ### Fixed -- Fixed the rerendering issue of the cursor by removing it from the render method and placing it to the draw method. +- Fixed the re-rendering issue of the cursor by removing it from the render method and placing it to the draw method. +- Added a flag to indicate whether the app is scanning the networks or not. +- Fixed the issue of pop-up for pressing 'd' key in the unsaved connection from the main list. + +### Changed + +- Changed the type of list that was send to the scan_networks function from main App `struct` to be `RwLock` wrapped type rather than Arc wrapped type. +- Changed the render method to render the scanning status with small animation in accordance with the scanning flag rather then the `.try_lock()` method that was used earlier. ## [1.1.0] - 2025-01-01 ### Added -- Listner for disconnecting the current connection +- Listener for disconnecting the current connection - Helper function to disconnect from the current connection - Utility function that will actually disconnect from the current connection @@ -24,7 +31,7 @@ All notable changes to this project will be documented in this file. - Fixed the issue in the saved-connection list where when we try to delete a connection, it was doing the deletion from the index of the main list instead of the saved connection list. - Fixed the refresh issue of the list after the deletion of a saved connection. -- Fixed the outo of bound issue when the list gets refreshed after deletion. +- Fixed the out of bound issue when the list gets refreshed after deletion. ## [1.0.0] - 2025-12-30 @@ -33,8 +40,8 @@ All notable changes to this project will be documented in this file. - documentation and comments - help menu - list for saved connections, -- delete connection confirmation popup, -- listeners for all the popups/menus +- delete connection confirmation pop-up, +- listeners for all the pop-ups/menus ### Changed From aef68a0f031430065bb4b708c032c4bc00f3f01a Mon Sep 17 00:00:00 2001 From: Santosh Shrestha Date: Sun, 4 Jan 2026 00:28:46 +0545 Subject: [PATCH 10/11] Update CHANGELOG.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc99fc..0667db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,18 @@ All notable changes to this project will be documented in this file. ### Fixed +### Fixed + - Fixed the re-rendering issue of the cursor by removing it from the render method and placing it to the draw method. -- Added a flag to indicate whether the app is scanning the networks or not. - Fixed the issue of pop-up for pressing 'd' key in the unsaved connection from the main list. ### Changed +- Added a flag to indicate whether the app is scanning the networks or not. +- Changed the type of list that was send to the scan_networks function from main App `struct` to be `RwLock` wrapped type rather than Arc wrapped type. + +### Changed + - Changed the type of list that was send to the scan_networks function from main App `struct` to be `RwLock` wrapped type rather than Arc wrapped type. - Changed the render method to render the scanning status with small animation in accordance with the scanning flag rather then the `.try_lock()` method that was used earlier. From 1d0e0fb6d0854ea9f71b5a8f5ca5d89d10ca58c2 Mon Sep 17 00:00:00 2001 From: santoshxshrestha Date: Sun, 4 Jan 2026 00:34:32 +0545 Subject: [PATCH 11/11] fix: should be setting up the flag when it is about to scan the networks --- src/utils/scan.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/scan.rs b/src/utils/scan.rs index bda6901..d053471 100644 --- a/src/utils/scan.rs +++ b/src/utils/scan.rs @@ -6,6 +6,7 @@ use std::sync::{Arc, RwLock}; use std::thread; pub fn scan_networks(wifi_list: Arc>>, is_scanning: Arc) { + is_scanning.store(true, Ordering::SeqCst); // // nmcli -t -f IN-USE,SSID,SECURITY device wifi list thread::spawn(move || { let output = Command::new("nmcli")