Files
arcrun-collector/cmd/arcrun-app/main.go
T
Leo 643acc0f74 t194 修:托盤必須在主執行緒建立——交付警察攔下,實跑才發現會 crash
交付警察問「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 行 
兩支機械閘仍全過。

⚠️ 未驗:托盤左右鍵的**點擊行為**要人真的去點(我點不了)。
2026-08-05 00:46:52 +08:00

102 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"context"
"embed"
"github.com/energye/systray"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)
//go:embed all:frontend/dist
var assets embed.FS
//go:embed build/trayicon.png
var trayIcon []byte
type wailsCtx = context.Context
// 版本號由 build 時 -ldflags 注入(同 arcrun-tray 的規矩)。
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,
MinWidth: 820, MinHeight: 560,
AssetServer: &assetserver.Options{Assets: assets},
OnStartup: func(ctx wailsCtx) {
app.startup(ctx)
startSupervisor() // 🔴 daemon 本體:不啟動就不會同步
// 🔴 托盤必須在**主執行緒**建立(NSStatusItem 會 new NSWindow)。
// OnStartup 不是主執行緒 ⇒ 直接呼叫會炸
// 「NSWindow should only be instantiated on the main thread!」(實測)。
// 用 Wails 的 dispatch 把它丟回主執行緒。
},
// 🔴 daemon 是常駐程式:關窗只是收起來,**不能真的結束**
// (結束=停止看守資料夾,使用者不會預期按 × 就不再同步)。
OnBeforeClose: func(ctx wailsCtx) bool {
wruntime.WindowHide(ctx)
return true // true=攔下關閉
},
HideWindowOnClose: true,
// Dock 不出現 iconWails v2.13 的 mac.ActivationPolicy 還沒開放(原始碼裡是註解掉的),
// 改走 Info.plist 的 LSUIElement(與 fyne 版同一個做法,見 build/darwin/Info.plist)。
Bind: []interface{}{app},
})
if err != nil {
println("Error:", err.Error())
}
}
// setupTray 建立選單列 icon。
//
// 🔴 leo 2026-08-05:「依照 google drive,托盤裡**只剩下按右鍵會結束**,
//
// 其他設定都在這個頁面,**點擊托盤的 icon 就立刻展開界面**」
//
// ⇒ 左鍵=直接開視窗(不彈選單);右鍵=只有一項「結束 Arcrun」。
//
// 所有設定都在視窗裡,托盤不再是第二套 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) {
trayStart, trayEnd = systray.RunWithExternalLoop(func() {
systray.SetTemplateIcon(trayIcon, trayIcon)
systray.SetTooltip("Arcrun — 你的知識庫同步小幫手")
// 左鍵:立刻展開主視窗
systray.SetOnClick(func(menu systray.IMenu) { app.ShowWindow() })
// 右鍵:只有「結束」一項
quit := systray.AddMenuItem("結束 Arcrun", "停止同步並關閉")
quit.Click(func() { app.Quit() })
systray.SetOnRClick(func(menu systray.IMenu) { _ = menu.ShowMenu() })
}, nil)
}