Repository: GyulyVGC/sniffnet (language: rust)

Issue:
Confirm stopping capture when window is closed
### Is there an existing issue for this?

- [X] I have searched the existing issues.

### Describe the solution you'd like

if you accidentally click the window close X when doing a network capture it doesn't ask for confirmation and just exits the application unlike when you use the left arrow icon and it asks whether you wish to quit the capture.

### Is your feature request related to a problem?

_No response_


Discussion / hints:
Hi @simmonsm, this is actually a really nice idea.
I think I will implement it for the next release of the app.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79dcb432e..0a5604745 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ All Sniffnet releases with the relative changes are documented in this file.
 ## [UNRELEASED]
 - Added CLI argument `--adapter [<NAME>]` to allow immediately starting the capture from a given network interface ([#643](https://github.com/GyulyVGC/sniffnet/pull/643) — fixes [#636](https://github.com/GyulyVGC/sniffnet/issues/636))
 - Added Vietnamese translation 🇻🇳 ([#577](https://github.com/GyulyVGC/sniffnet/pull/577))
+- Ask for quit confirmation before stopping an ongoing analysis ([#652](https://github.com/GyulyVGC/sniffnet/pull/652) — fixes [#570](https://github.com/GyulyVGC/sniffnet/issues/570))
 - Redirect `stderr` and `stdout` to file on Windows release builds ([#645](https://github.com/GyulyVGC/sniffnet/pull/645) — fixes [#578](https://github.com/GyulyVGC/sniffnet/issues/578))
 - Updated some of the existing translations to v1.3: 
   - Chinese ([#575](https://github.com/GyulyVGC/sniffnet/pull/575))
diff --git a/src/gui/components/modal.rs b/src/gui/components/modal.rs
index 7ea21f685..a7037f289 100644
--- a/src/gui/components/modal.rs
+++ b/src/gui/components/modal.rs
@@ -18,12 +18,13 @@ use crate::translations::translations::{
 use crate::{Language, StyleType};
 
 pub fn get_exit_overlay<'a>(
+    message: Message,
     color_gradient: GradientType,
     font: Font,
     font_headers: Font,
     language: Language,
 ) -> Container<'a, Message, StyleType> {
-    let row_buttons = confirm_button_row(language, font, Message::Reset);
+    let row_buttons = confirm_button_row(language, font, message);
 
     let content = Column::new()
         .align_x(Alignment::Center)
diff --git a/src/gui/components/types/my_modal.rs b/src/gui/components/types/my_modal.rs
index b4ae1f0e9..5c19619cd 100644
--- a/src/gui/components/types/my_modal.rs
+++ b/src/gui/components/types/my_modal.rs
@@ -1,8 +1,10 @@
 use crate::networking::types::address_port_pair::AddressPortPair;
 
 /// This enum defines the currently displayed modal.
-#[derive(PartialEq, Eq, Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
 pub enum MyModal {
+    /// Reset modal.
+    Reset,
     /// Quit modal.
     Quit,
     /// Clear all modal.
diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs
index 1f16689a8..dbd9bba58 100644
--- a/src/gui/sniffer.rs
+++ b/src/gui/sniffer.rs
@@ -180,7 +180,7 @@ impl Sniffer {
                     modifiers: Modifiers::COMMAND,
                     ..
                 }) => match key.as_ref() {
-                    Key::Character("q") => Some(Message::CloseRequested),
+                    Key::Character("q") => Some(Message::QuitWrapper),
                     Key::Character("t") => Some(Message::CtrlTPressed),
                     _ => None,
                 },
@@ -190,7 +190,7 @@ impl Sniffer {
             iced::event::listen_with(|event, _, _| match event {
                 Keyboard(Event::KeyPressed { key, modifiers, .. }) => match modifiers {
                     Modifiers::COMMAND => match key.as_ref() {
-                        Key::Character("q") => Some(Message::CloseRequested),
+                        Key::Character("q") => Some(Message::QuitWrapper),
                         Key::Character("t") => Some(Message::CtrlTPressed),
                         Key::Character(",") => Some(Message::OpenLastSettings),
                         Key::Named(Named::Backspace) => Some(Message::ResetButtonPressed),
@@ -244,7 +244,7 @@ impl Sniffer {
             Window(window::Event::Resized(Size { width, height })) => {
                 Some(Message::WindowResized(width, height))
             }
-            Window(window::Event::CloseRequested) => Some(Message::CloseRequested),
+            Window(window::Event::CloseRequested) => Some(Message::QuitWrapper),
             _ => None,
         })
     }
@@ -430,7 +430,8 @@ impl Sniffer {
                     .clone_from(&db);
                 self.asn_mmdb_reader = Arc::new(MmdbReader::from(&db, ASN_MMDB));
             }
-            Message::CloseRequested => {
+            Message::QuitWrapper => return self.quit_wrapper(),
+            Message::Quit => {
                 self.configs.lock().unwrap().clone().store();
                 return window::close(self.id.unwrap_or(Id::unique()));
             }
@@ -579,7 +580,20 @@ impl Sniffer {
             }
             Some(m) => {
                 let overlay: Element<Message, StyleType> = match m {
-                    MyModal::Quit => get_exit_overlay(color_gradient, font, font_headers, language),
+                    MyModal::Reset => get_exit_overlay(
+                        Message::Reset,
+                        color_gradient,
+                        font,
+                        font_headers,
+                        language,
+                    ),
+                    MyModal::Quit => get_exit_overlay(
+                        Message::Quit,
+                        color_gradient,
+                        font,
+                        font_headers,
+                        language,
+                    ),
                     MyModal::ClearAll => {
                         get_clear_all_overlay(color_gradient, font, font_headers, language)
                     }
@@ -842,8 +856,10 @@ impl Sniffer {
             if self.filters.are_valid() {
                 return self.update(Message::Start);
             }
-        } else if self.modal.eq(&Some(MyModal::Quit)) {
+        } else if self.modal.eq(&Some(MyModal::Reset)) {
             return self.update(Message::Reset);
+        } else if self.modal.eq(&Some(MyModal::Quit)) {
+            return self.update(Message::Quit);
         } else if self.modal.eq(&Some(MyModal::ClearAll)) {
             return self.update(Message::ClearAllNotifications);
         }
@@ -859,7 +875,7 @@ impl Sniffer {
         Task::none()
     }
 
-    // also called when backspace key is pressed on a running state
+    // also called when the backspace shortcut is pressed
     fn reset_button_pressed(&mut self) -> Task<Message> {
         if self.running_page.ne(&RunningPage::Init) {
             return if self.info_traffic.lock().unwrap().all_packets == 0
@@ -867,12 +883,29 @@ impl Sniffer {
             {
                 self.update(Message::Reset)
             } else {
-                self.update(Message::ShowModal(MyModal::Quit))
+                self.update(Message::ShowModal(MyModal::Reset))
             };
         }
         Task::none()
     }
 
+    fn quit_wrapper(&mut self) -> Task<Message> {
+        if self.running_page.eq(&RunningPage::Init)
+            || self.info_traffic.lock().unwrap().all_packets == 0
+        {
+            self.update(Message::Quit)
+        } else if self.thumbnail {
+            // TODO: uncomment once issue #653 is fixed
+            // self.update(Message::ToggleThumbnail(false))
+            //     .chain(self.update(Message::ShowModal(MyModal::Quit)))
+            self.update(Message::Quit)
+        } else {
+            self.update(Message::HideModal)
+                .chain(self.update(Message::CloseSettings))
+                .chain(self.update(Message::ShowModal(MyModal::Quit)))
+        }
+    }
+
     fn shortcut_ctrl_d(&mut self) -> Task<Message> {
         if self.running_page.eq(&RunningPage::Notifications)
             && !self.runtime_data.logged_notifications.is_empty()
@@ -1914,7 +1947,7 @@ mod tests {
         sniffer.update(Message::ChangeVolume(100));
 
         // quit the app by sending a CloseRequested message
-        sniffer.update(Message::CloseRequested);
+        sniffer.update(Message::Quit);
 
         assert!(path.exists());
 
@@ -1978,7 +2011,7 @@ mod tests {
         sniffer.update(Message::WindowMoved(40.0, 40.0));
 
         // quit the app by sending a CloseRequested message
-        sniffer.update(Message::CloseRequested);
+        sniffer.update(Message::Quit);
 
         assert!(path.exists());
 
diff --git a/src/gui/types/message.rs b/src/gui/types/message.rs
index 1b4dfec04..1963dab9b 100644
--- a/src/gui/types/message.rs
+++ b/src/gui/types/message.rs
@@ -12,7 +12,7 @@ use crate::{ChartType, IpVersion, Language, Protocol, ReportSortType, StyleType}
 use iced::window;
 
 #[derive(Debug, Clone)]
-/// Messages types that permit to react to application interactions/subscriptions
+/// Messages types that permit reacting to application interactions/subscriptions
 pub enum Message {
     /// Every 5 seconds
     TickInit,
@@ -100,8 +100,10 @@ pub enum Message {
     CustomCountryDb(String),
     /// The ASN MMDB custom path has been updated
     CustomAsnDb(String),
+    /// Wrapper around the Quit message
+    QuitWrapper,
     /// Save the configurations of the app and quit
-    CloseRequested,
+    Quit,
     /// Copies the given string to clipboard
     CopyIp(String),
     /// Launch a new file dialog


Test patch: