From a39c7b8f71af1e995ec6c1962294756d12b527b1 Mon Sep 17 00:00:00 2001 From: Felix Riestra Date: Mon, 7 Sep 2026 17:51:23 +0200 Subject: [PATCH] macOS launcher: re-assert Accessory policy after handling an event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Handling a document-open Apple Event (a warm "Open With" while the app is already running) silently promotes this process from NSApplicationActivationPolicyAccessory to a normal foreground app — confirmed with lsappinfo, not assumed: its own entry flips from type="UIElement" to type="Foreground" the instant it handles a second Apple Event (a cold launch's own first event doesn't trigger this). That gave the launcher its own, contentless Dock icon sitting next to the real editor's, both apparently "the same app" to the user but actually two separate Dock entries. Re-set Accessory explicitly right after relaying, in the one place every open (cold fallback, document open, and reopen-while-running) already funnels through. Co-Authored-By: Claude Sonnet 5 --- src/bin/ocs_launcher.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/bin/ocs_launcher.rs b/src/bin/ocs_launcher.rs index 9e2c4aab7..ed8e461b4 100644 --- a/src/bin/ocs_launcher.rs +++ b/src/bin/ocs_launcher.rs @@ -103,13 +103,32 @@ fn real_binary_path() -> PathBuf { /// running or about to claim it on its own. fn deliver_or_launch(files: &[String]) { let paths: Vec = files.iter().map(PathBuf::from).collect(); - if let Some(stream) = single_instance::try_connect_existing() { - if single_instance::handoff(stream, &paths) { - return; + let delivered = single_instance::try_connect_existing() + .map(|stream| single_instance::handoff(stream, &paths)) + .unwrap_or(false); + if !delivered { + if let Err(err) = std::process::Command::new(real_binary_path()).args(files).spawn() { + eprintln!("OpenCADStudio launcher: failed to launch the real binary: {err}"); } } - if let Err(err) = std::process::Command::new(real_binary_path()).args(files).spawn() { - eprintln!("OpenCADStudio launcher: failed to launch the real binary: {err}"); + reassert_accessory_policy(); +} + +/// AppKit activates this process — promoting it from +/// NSApplicationActivationPolicyAccessory (no Dock icon, set at startup) to +/// a normal foreground app with its own Dock icon — as a side effect of +/// handling an open-document Apple Event, even though nothing here ever +/// shows a window. Measured via `lsappinfo`, not assumed: the launcher's own +/// entry flips from type="UIElement" to type="Foreground" the moment it +/// handles a *second* Apple Event (a fresh cold launch's own first event +/// doesn't trigger this — the policy is presumably still settling at that +/// point). Set it back immediately after relaying, so a warm "Open With" +/// doesn't leave a second, contentless Dock icon sitting next to the real +/// editor's. +fn reassert_accessory_policy() { + if let Some(mtm) = MainThreadMarker::new() { + NSApplication::sharedApplication(mtm) + .setActivationPolicy(NSApplicationActivationPolicy::Accessory); } }