From d5427f97e6c9efab78cad1ef65d3cdce1ec7a655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20LIARD?= Date: Mon, 6 Jul 2026 20:34:15 +0200 Subject: [PATCH] =?UTF-8?q?fix(raise):=20activation=20fen=C3=AAtre-seule?= =?UTF-8?q?=20=E2=80=94=20le=20raise=20ramenait=20toutes=20les=20fen=C3=AA?= =?UTF-8?q?tres=20de=20l'app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le chemin Raise (raise_element, expose_target_window, préparation des commandes de menu) faisait suivre l'AXRaise fenêtre-scopé d'un osascript « set frontmost of process to true » : activation processus qui remonte toutes les fenêtres de l'app, sur tous les écrans, par-dessus le premier plan de l'utilisateur. Remplacé par NSRunningApplication activateWithOptions sans NSApplicationActivateAllWindows : seule la fenêtre key (celle de l'AXRaise) remonte. Même piège déjà retiré de set_focused_field_text ; borrow_target_frontmost reste en place pour le sélecteur de fichiers où l'activation complète est voulue. --- crates/dunst-platform/Cargo.toml | 6 +++- crates/dunst-platform/src/macos/ax_backend.rs | 29 +++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/crates/dunst-platform/Cargo.toml b/crates/dunst-platform/Cargo.toml index 29e2efd..04a858a 100644 --- a/crates/dunst-platform/Cargo.toml +++ b/crates/dunst-platform/Cargo.toml @@ -21,7 +21,11 @@ core-graphics = { version = "0.24", features = ["elcapitan", "highsierra"] } foreign-types = "0.5" objc2 = "0.6.4" objc2-foundation = "0.3.2" -objc2-app-kit = { version = "0.3.2", features = ["NSEvent", "NSGraphicsContext"] } +objc2-app-kit = { version = "0.3.2", features = [ + "NSEvent", + "NSGraphicsContext", + "NSRunningApplication", +] } [dev-dependencies] serde_json = { workspace = true } diff --git a/crates/dunst-platform/src/macos/ax_backend.rs b/crates/dunst-platform/src/macos/ax_backend.rs index a917234..8765f2c 100644 --- a/crates/dunst-platform/src/macos/ax_backend.rs +++ b/crates/dunst-platform/src/macos/ax_backend.rs @@ -264,7 +264,7 @@ pub(super) fn perform_on_element( SemanticAction::Raise => { let element = require_ax_element(element)?; perform_ax_action(element, kAXRaiseAction)?; - activate_process_for_raise(target, node) + activate_process_for_raise(target) .map_err(|err| ActionFailure::Execution(err.to_string())) } SemanticAction::Focus => { @@ -289,14 +289,25 @@ pub(super) fn perform_on_element( } } -fn activate_process_for_raise(target: &Target, node: &SceneNode) -> Result<()> { - let window = WindowRef { - pid: target.pid, - window_id: target.window_id, - app_name: String::new(), - title: node.label.clone().unwrap_or_default(), - }; - crate::borrow_target_frontmost(&window).map(|_| ()) +fn activate_process_for_raise(target: &Target) -> Result<()> { + // Window-scoped activation: the kAXRaiseAction just performed made the + // target the app's key window, and activating WITHOUT + // `NSApplicationActivateAllWindows` only brings that key window forward. + // The previous osascript variant (`set frontmost of process to true`) was + // process-level: it raised every window of the app, on every display, + // over the user's foreground. + let app = + objc2_app_kit::NSRunningApplication::runningApplicationWithProcessIdentifier(target.pid) + .ok_or_else(|| { + DunstError::Execution(format!("no running application with pid {}", target.pid)) + })?; + if app.activateWithOptions(objc2_app_kit::NSApplicationActivationOptions::empty()) { + Ok(()) + } else { + Err(DunstError::Execution( + "AppKit refused the window-scoped activation of the target app".into(), + )) + } } pub(super) fn require_ax_element(