diff --git a/src-tauri/src/application_focus.rs b/src-tauri/src/application_focus.rs index 292370580..be929ce70 100644 --- a/src-tauri/src/application_focus.rs +++ b/src-tauri/src/application_focus.rs @@ -66,7 +66,7 @@ pub fn observe(app: &tauri::AppHandle) { } } -fn application_active(app: &tauri::AppHandle) -> bool { +pub(crate) fn application_active(app: &tauri::AppHandle) -> bool { // A settings window, utility window or native dialog is still this application. #[cfg(target_os = "macos")] { diff --git a/src-tauri/src/events.rs b/src-tauri/src/events.rs index ef2576f85..2fbdc44a9 100644 --- a/src-tauri/src/events.rs +++ b/src-tauri/src/events.rs @@ -1,5 +1,5 @@ use ide_core::{EventSink, IdeEvent}; -use tauri::{AppHandle, Emitter}; +use tauri::{AppHandle, Emitter, Manager, UserAttentionType}; /// Name of the single backend → UI event. Payload: [`IdeEvent`]. pub const IDE_EVENT: &str = "ide://event"; @@ -38,8 +38,31 @@ pub struct TauriSink { pub window: Option, } +/// A debug session stopped on a breakpoint: bounce/flash the taskbar icon +/// (IDEA) unless the user is already looking at an OpenIDEA window. The focus +/// check and the request both touch the windowing system, so they run on the +/// main thread; `emit` itself can come from any debug worker thread. +fn flash_taskbar_unless_focused(app: &AppHandle) { + let handle = app.clone(); + let _ = app.run_on_main_thread(move || { + if crate::application_focus::application_active(&handle) { + return; + } + for window in handle.webview_windows().values() { + let _ = window.request_user_attention(Some(UserAttentionType::Informational)); + } + }); +} + impl EventSink for TauriSink { fn emit(&self, event: IdeEvent) { + if matches!( + &event, + IdeEvent::Debug { event: ide_core::event::DebugEvent::Stopped { reason, .. }, .. } + if matches!(reason.as_str(), "breakpoint" | "function breakpoint") + ) { + flash_taskbar_unless_focused(&self.app); + } let result = match &self.window { Some(label) => { for target in crate::windows::document_windows(&self.app, label) {