From d2bf521641f20e744dbdb0a2ff7cdac2de4f969c Mon Sep 17 00:00:00 2001 From: Ali Al Dallal Date: Sun, 30 Aug 2026 21:26:45 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20the=20app=20launches=20again=20=E2=80=94?= =?UTF-8?q?=20promise-view=20attach=20deferred=20to=20runtime-ready=20(goa?= =?UTF-8?q?l=200256=20P0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The beta carrying #534 crashed at launch: AttachFilePromiseReceiver ran InvokeSync from main.go's wiring, BEFORE app.Run() -- and the toolkit's main-thread dispatcher doesn't exist yet, so the dispatch dereferenced nil (SIGSEGV, reproduced on a local desktop launch and pinned to the exact frame). The native attach now waits for the window's first RuntimeReady event, which by construction fires only once the run loop is live; the callback registration itself stays immediate. Verified: the rebuilt desktop binary launches and survives where the previous one died in under a second. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_012im1JxQQV2ahnXzZDdVmZq --- .../adapters/windowing/filepromise_darwin.go | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/internal/adapters/windowing/filepromise_darwin.go b/internal/adapters/windowing/filepromise_darwin.go index 0357f8db..4c033386 100644 --- a/internal/adapters/windowing/filepromise_darwin.go +++ b/internal/adapters/windowing/filepromise_darwin.go @@ -13,6 +13,9 @@ import "C" import ( "sync" "unsafe" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" ) // promiseDropFn holds the one registered promise-drop callback -- a @@ -49,15 +52,25 @@ func millFilePromiseDropped(paths **C.char, count C.int, x C.int, y C.int) { // the toolkit's own file-drop event). One callback app-wide; the last // registration wins. See filepromise_darwin.m's own view comment for // why this cannot interfere with ordinary file drops. +// +// The NATIVE attach is deferred to the window's first RuntimeReady +// event: this method is called from main.go's wiring, BEFORE +// app.Run() -- and InvokeSync before the run loop exists dereferences +// the toolkit's not-yet-initialized dispatcher (a launch-time SIGSEGV, +// reproduced live). RuntimeReady fires only once the app is running, +// so the marshal inside is always legal by construction. func (win *Window) AttachFilePromiseReceiver(fn func(paths []string, x, y int)) { promiseDropMu.Lock() promiseDropFn = fn promiseDropMu.Unlock() - runMainThreadAction("AttachFilePromiseReceiver", func() { - // NativeWindow is a stored-pointer read (nil when the window - // is destroyed or not yet realized -- the C side guards nil); - // read inside the marshal so the attach sees the freshest - // window state. - C.millAttachPromiseView(win.w.NativeWindow()) + var once sync.Once + win.w.OnWindowEvent(events.Common.WindowRuntimeReady, func(*application.WindowEvent) { + once.Do(func() { + runMainThreadAction("AttachFilePromiseReceiver", func() { + // NativeWindow is a stored-pointer read (nil when the + // window is destroyed -- the C side guards nil). + C.millAttachPromiseView(win.w.NativeWindow()) + }) + }) }) }