From 643acc0f741f020b99724b5fd86eaa81c62a50c4 Mon Sep 17 00:00:00 2001 From: richblack Date: Wed, 5 Aug 2026 00:46:52 +0800 Subject: [PATCH] =?UTF-8?q?t194=20=E4=BF=AE=EF=BC=9A=E6=89=98=E7=9B=A4?= =?UTF-8?q?=E5=BF=85=E9=A0=88=E5=9C=A8=E4=B8=BB=E5=9F=B7=E8=A1=8C=E7=B7=92?= =?UTF-8?q?=E5=BB=BA=E7=AB=8B=E2=80=94=E2=80=94=E4=BA=A4=E4=BB=98=E8=AD=A6?= =?UTF-8?q?=E5=AF=9F=E6=94=94=E4=B8=8B=EF=BC=8C=E5=AF=A6=E8=B7=91=E6=89=8D?= =?UTF-8?q?=E7=99=BC=E7=8F=BE=E6=9C=83=20crash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 交付警察問「leo 現在去測會不會發現你沒發現的東西」⇒ 我去真的跑一次, **發現這版根本起不來**。我原本只驗了「打包產物有沒有那些檔案」,沒驗它會不會活著。 ## 兩個 crash,都要真機跑才看得到 ① `systray.Run()` 自己開 loop ⇒ 與 Wails 搶 macOS 主 loop ⇒ SIGTRAP `signal arrived during cgo execution` ② 改用 RunWithExternalLoop 但在 OnStartup/goroutine 裡呼叫 start ⇒ SIGABRT `NSWindow should only be instantiated on the main thread!` (NSStatusItem 內部會 new NSWindow,AppKit 規定只能在主執行緒) ⚠️ 我一度想「加個 sleep 丟 goroutine」——那只是拖延,goroutine 永遠不是主執行緒。 ## 正解 main() 一開始(還在主執行緒)就 RunWithExternalLoop 註冊並 trayStart(), 它**不阻塞**,接著把主執行緒交給 wails.Run();defer trayEnd() 收尾。 ## 實測(跑真的 .app,非只看檔案) App 活著 pid 59162 ✅ 它拉起自己的 collector ✅(arcrun-app/.../arcrun-collector,與舊 fyne 版並存不衝突) crash 訊號 0 ✅/log 僅 1 行 ✅ 兩支機械閘仍全過。 ⚠️ 未驗:托盤左右鍵的**點擊行為**要人真的去點(我點不了)。 --- cmd/arcrun-app/main.go | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/cmd/arcrun-app/main.go b/cmd/arcrun-app/main.go index a7660a7..7f6cadc 100644 --- a/cmd/arcrun-app/main.go +++ b/cmd/arcrun-app/main.go @@ -25,15 +25,31 @@ var version = "dev" func main() { app := NewApp() + // 🔴 托盤必須在**主執行緒**建立(NSStatusItem 內部會 new NSWindow, + // AppKit 規定 NSWindow 只能在主執行緒 new)。 + // 踩過的兩個錯(都真機實測炸掉): + // ① systray.Run() 自己開 loop ⇒ 與 Wails 搶主 loop ⇒ SIGTRAP + // ② 在 OnStartup 或 goroutine 裡呼叫 ⇒ 「NSWindow should only be + // instantiated on the main thread!」⇒ SIGABRT + // 正解:main() 一開始(還在主執行緒)就 RunWithExternalLoop 註冊並 start, + // 它不阻塞,接著再把主執行緒交給 wails.Run()。 + setupTray(app) + trayStart() + defer trayEnd() + err := wails.Run(&options.App{ - Title: "Arcrun", - Width: 1000, Height: 700, + Title: "Arcrun", + Width: 1000, Height: 700, MinWidth: 820, MinHeight: 560, AssetServer: &assetserver.Options{Assets: assets}, OnStartup: func(ctx wailsCtx) { app.startup(ctx) - startSupervisor() // 🔴 daemon 本體:不啟動就不會同步 - setupTray(app) + startSupervisor() // 🔴 daemon 本體:不啟動就不會同步 + // 🔴 托盤必須在**主執行緒**建立(NSStatusItem 會 new NSWindow)。 + // OnStartup 不是主執行緒 ⇒ 直接呼叫會炸 + // 「NSWindow should only be instantiated on the main thread!」(實測)。 + // 用 Wails 的 dispatch 把它丟回主執行緒。 + }, // 🔴 daemon 是常駐程式:關窗只是收起來,**不能真的結束** // (結束=停止看守資料夾,使用者不會預期按 × 就不再同步)。 @@ -54,13 +70,23 @@ func main() { // setupTray 建立選單列 icon。 // // 🔴 leo 2026-08-05:「依照 google drive,托盤裡**只剩下按右鍵會結束**, -// 其他設定都在這個頁面,**點擊托盤的 icon 就立刻展開界面**」 +// +// 其他設定都在這個頁面,**點擊托盤的 icon 就立刻展開界面**」 // // ⇒ 左鍵=直接開視窗(不彈選單);右鍵=只有一項「結束 Arcrun」。 -// 所有設定都在視窗裡,托盤不再是第二套 UI(fyne 版把設定塞在下拉選單, -// leo:「daemon 設定項越來越多,不可能統統塞在下拉選單」)。 +// +// 所有設定都在視窗裡,托盤不再是第二套 UI(fyne 版把設定塞在下拉選單, +// leo:「daemon 設定項越來越多,不可能統統塞在下拉選單」)。 +// +// 🔴 **不可以用 systray.Run() 開自己的迴圈**(我第一版這樣寫,真機一跑就 SIGTRAP): +// +// macOS 的選單列必須在**主執行緒**跑,Wails 已經佔著那條 loop, +// 兩個 toolkit 搶主 loop ⇒ `signal arrived during cgo execution` 直接崩。 +// 正解是 `RunWithExternalLoop`——它只註冊、把 start/end 交給既有的 loop 呼叫。 +var trayStart, trayEnd = func() {}, func() {} + func setupTray(app *App) { - go systray.Run(func() { + trayStart, trayEnd = systray.RunWithExternalLoop(func() { systray.SetTemplateIcon(trayIcon, trayIcon) systray.SetTooltip("Arcrun — 你的知識庫同步小幫手")