6 Commits
Author SHA1 Message Date
Karim Jerbi 44ffb1ca5a fix(macos): forward launcher CLI arguments and preserve MCP stdio 2026-10-09 14:53:12 +01:00
Hakan Seven 8922a5a25b Merge updated PR #1055 2026-09-07 19:10:55 +03:00
Hakan Seven ceaf9ebe5b Merge PR #1055: handle macOS Finder document opens 2026-09-07 18:53:29 +03:00
Felix RiestraandClaude Sonnet 5 a39c7b8f71 macOS launcher: re-assert Accessory policy after handling an event
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 <[email protected]>
2026-09-07 17:51:23 +02:00
Felix RiestraandClaude Sonnet 5 24e9a7663d macOS launcher: handle reopen while already running with no window
Quitting the real GUI binary (Cmd+Q) leaves the launcher itself still
running — that's by design, so it can keep relaying opens for the
session. But reopening the app afterward (Dock icon, double-clicking
it in Finder again) reactivates that same long-lived launcher process
rather than triggering a fresh cold launch, since Launch Services
still considers the bundle running. Neither of the delegate methods
already handled covered that: applicationDidFinishLaunching: only
fires once, at the launcher's own original cold start, and
application:openURLs: only fires when a document was named. The net
effect: the Dock icon would appear, but no window ever showed.

applicationShouldHandleReopen:hasVisibleWindows: is exactly the
callback AppKit fires for this reactivation case. Reproduced and
verified the fix against the real sequence (cold launch, kill only the
GUI process to simulate Cmd+Q, reopen) rather than assuming it from
the delegate method's docs alone.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-07 17:34:57 +02:00
Felix RiestraandClaude Sonnet 5 e767507850 macOS: add a launcher shim so a cold Finder open also works (#1039)
The previous commit's Apple Event handler only ever catches an open
sent to an already-running process. A cold double-click (app not
running yet) still failed: every registration point tried inside the
real binary — before app::run(), on the Subscription's first poll,
even the literal first statement of main() — reliably caught a warm
open but never the original cold-launch event. A raw
AEInstallEventHandler in a separate, minimal, non-AppKit process (no
winit/iced, just a manual CFRunLoopRunInMode pump) still missed it
too, ruling out "the real binary's heavy dependency graph loading
before main() wins the race" as the explanation.

What does reliably catch a cold launch, verified against real
Finder-equivalent opens: a from-scratch NSApplication with its own
delegate implementing application:openURLs: — in a process where we
own setDelegate: from the start, with no contention over the single
delegate slot winit already claims. That has to be a separate process
from the real GUI binary (only one -[NSApplication run] call can own a
process), so src/bin/ocs_launcher.rs is now the .app's actual
CFBundleExecutable: a small, dependency-light binary (no iced, wgpu,
or cadkernel) whose only job is to catch the open and relay it.

Relaying by replacing this process's image (exec, same PID) or by
spawning a child and exiting turned out to both be dead ends: Launch
Services tracks "the running instance of this bundle" by the
Application Serial Number it handed out to whichever process it
actually launched — this launcher, not whatever it execs into or
spawns. Once the launcher's own ASN stopped pointing at a live,
listening process, every later open Finder tried to deliver silently
went nowhere (measured with lsappinfo against a real second "Open
With", not assumed). So the launcher now stays running for the app's
session — invisible, via NSApplicationActivationPolicyAccessory, since
it owns no window — and relays every open (cold or warm) over the same
channel a second CLI invocation already uses: io::single_instance.

That relay only worked once single_instance's rendezvous key stopped
being computed from the running executable's own path. That was fine
when one binary always did the job, but the launcher and the real
binary are different executables and independently computed different
ports, so they could never find each other. Anchor the key on the
enclosing .app bundle instead, shared by both.

packaging/build_macos_signed.sh and .github/workflows/release.yml now
build ocs_launcher and place it at Contents/MacOS/OpenCADStudio (the
CFBundleExecutable Info.plist already names), with the real GUI binary
renamed to Contents/MacOS/OpenCADStudio-App alongside it.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-07 16:45:02 +02:00