From 0a8c2f8e2fa03d891220f5cb481d30dce4229e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Tue, 28 Apr 2026 14:14:45 +0200 Subject: [PATCH 1/2] fix: ignore window close while a flash is in progress Closing the window mid-flash dropped the OpenOCD child without giving it a chance to complete, leaving the target in a half-flashed state that needed a manual recovery on the next attempt. Add a guard in the CloseRequested handler that bails out as a no-op when either tab is currently running a flash or a file-browse, leaving the existing save-and-close path intact for the idle case. Expose the gate as a small `is_busy()` accessor on each tab; the field driving it (`is_readonly`) was already used to gray out the controls during a flash, so we're reusing the same notion. A SIGTERM remains the escape hatch if the flash genuinely hangs. The opacity overlay continues to signal the busy state visually, so the X simply doing nothing is consistent with the rest of the UI being uninteractable at that point. Closes #6. --- src/ui/main_window.rs | 13 +++++++++++++ src/ui/tab_daplink.rs | 7 +++++++ src/ui/tab_wireless_stack.rs | 7 +++++++ 3 files changed, 27 insertions(+) diff --git a/src/ui/main_window.rs b/src/ui/main_window.rs index 33e078a..acd158d 100644 --- a/src/ui/main_window.rs +++ b/src/ui/main_window.rs @@ -56,6 +56,19 @@ impl MainWindow { return Task::none(); } iced::window::Event::CloseRequested => { + // Suppress the close while a flash is in progress so + // OpenOCD (or the FUS upgrade) isn't killed mid-write. + // The opacity overlay already signals the busy state + // visually; the user can SIGTERM if they really need + // out, but a misclick on the X shouldn't brick the + // device. + if self.tab_daplink.is_busy() || self.tab_ws.is_busy() { + eprintln!( + "Close request ignored: a flash is in progress." + ); + return Task::none(); + } + match dirs::get_settings_dir() { Ok(settings_dir) => { let fields_file = settings_dir.join(SETTINGS_FILE); diff --git a/src/ui/tab_daplink.rs b/src/ui/tab_daplink.rs index c630117..4225401 100644 --- a/src/ui/tab_daplink.rs +++ b/src/ui/tab_daplink.rs @@ -38,6 +38,13 @@ pub struct TabDaplink { } impl TabDaplink { + /// True while a flash sequence (or a file browse) is running. Used by + /// `MainWindow` to suppress window-close requests during a flash so the + /// OpenOCD child isn't killed mid-operation. + pub fn is_busy(&self) -> bool { + self.is_readonly + } + pub fn update(&mut self, message: TabDaplinkMessage) -> Task { match message { TabDaplinkMessage::LogMessage(log) => self.log_widget.push(log), diff --git a/src/ui/tab_wireless_stack.rs b/src/ui/tab_wireless_stack.rs index 42bccf6..0935fd8 100644 --- a/src/ui/tab_wireless_stack.rs +++ b/src/ui/tab_wireless_stack.rs @@ -103,6 +103,13 @@ const ALL_STACK: [WirelessStackFile; 21] = [ ]; impl TabWirelessStack { + /// True while a flash sequence is running. Used by `MainWindow` to + /// suppress window-close requests so the OpenOCD child or the FUS + /// upgrade isn't killed mid-operation. + pub fn is_busy(&self) -> bool { + self.is_readonly + } + pub fn view(&self) -> Element { let grid_fields = grid!( grid_row!( From c1ba41af633140197819362cdb22acb53981777a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Tue, 28 Apr 2026 14:28:08 +0200 Subject: [PATCH 2/2] fix: address Copilot review on close-guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reword the guard's log message and comment to "operation in progress" rather than "flash in progress". TabDaplink::is_busy() also covers the brief file-browse window (is_readonly is shared with that path), so the message would have been technically misleading there. - Drop the SIGTERM mention from the comment and use the platform-neutral "force-quit / end task" phrasing — the project supports Windows where SIGTERM doesn't apply. - Update both is_busy() doc comments to match the broader semantics. Per Copilot review on PR #17. --- src/ui/main_window.rs | 14 +++++++------- src/ui/tab_daplink.rs | 7 ++++--- src/ui/tab_wireless_stack.rs | 6 +++--- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/ui/main_window.rs b/src/ui/main_window.rs index acd158d..47ba0d5 100644 --- a/src/ui/main_window.rs +++ b/src/ui/main_window.rs @@ -56,15 +56,15 @@ impl MainWindow { return Task::none(); } iced::window::Event::CloseRequested => { - // Suppress the close while a flash is in progress so - // OpenOCD (or the FUS upgrade) isn't killed mid-write. - // The opacity overlay already signals the busy state - // visually; the user can SIGTERM if they really need - // out, but a misclick on the X shouldn't brick the - // device. + // Suppress the close while an operation is in progress + // so OpenOCD (or the FUS upgrade) isn't interrupted + // mid-action. The opacity overlay already signals the + // busy state visually; the user can force-quit / end + // task the app if they really need out, but a misclick + // on the X shouldn't brick the device. if self.tab_daplink.is_busy() || self.tab_ws.is_busy() { eprintln!( - "Close request ignored: a flash is in progress." + "Close request ignored: an operation is in progress." ); return Task::none(); } diff --git a/src/ui/tab_daplink.rs b/src/ui/tab_daplink.rs index 4225401..8b703bf 100644 --- a/src/ui/tab_daplink.rs +++ b/src/ui/tab_daplink.rs @@ -38,9 +38,10 @@ pub struct TabDaplink { } impl TabDaplink { - /// True while a flash sequence (or a file browse) is running. Used by - /// `MainWindow` to suppress window-close requests during a flash so the - /// OpenOCD child isn't killed mid-operation. + /// True while an operation that should not be interrupted is running: + /// a flash sequence, or a (modal) file-browse. Used by `MainWindow` to + /// suppress window-close requests so the OpenOCD child isn't killed + /// mid-write. pub fn is_busy(&self) -> bool { self.is_readonly } diff --git a/src/ui/tab_wireless_stack.rs b/src/ui/tab_wireless_stack.rs index 0935fd8..62fd210 100644 --- a/src/ui/tab_wireless_stack.rs +++ b/src/ui/tab_wireless_stack.rs @@ -103,9 +103,9 @@ const ALL_STACK: [WirelessStackFile; 21] = [ ]; impl TabWirelessStack { - /// True while a flash sequence is running. Used by `MainWindow` to - /// suppress window-close requests so the OpenOCD child or the FUS - /// upgrade isn't killed mid-operation. + /// True while an operation that should not be interrupted is running. + /// Used by `MainWindow` to suppress window-close requests so neither + /// the OpenOCD child nor an in-flight FUS upgrade gets cut mid-action. pub fn is_busy(&self) -> bool { self.is_readonly }