Files
arcrun-collector/cmd/arcrun-app/main.go
T
Leo 5860ef72df t194 四修:托盤改用原生 NSStatusItem——energye/systray 在 Wails 之下根本不建托盤
leo:右鍵仍然沒用。這次先做**最小重現**才動手,結果推翻了我前兩次的判斷。

## 真兇(實測,非推論)
systray.go:83 的 setInternalLoop(true) **只在 systray.Run() 裡呼叫**;
RunWithExternalLoop 沒有 ⇒ registerSystray() 第一行就
`if (!internalLoop) return;` ⇒ **delegate 從沒建立**
⇒ onReady 不會被呼叫、enable_on_click 不會執行、左右鍵事件根本不存在。

最小重現(scratchpad/traytest):RunWithExternalLoop + onReady 印 "READY"
⇒ **READY 從未印出**。托盤自始至終沒被建立過。
(左鍵之所以「第一次能開」是 Wails 自己開的窗,與托盤無關。)

## 正解:原生 NSStatusItem(tray_darwin.m + tray_darwin.go)
左鍵=開視窗;右鍵=暫時掛選單、performClick、再拿掉(不常駐 setMenu:,
否則按鈕 action 不會被呼叫、左鍵也會變成彈選單——與 systray 那個坑同源)。

## 過程中踩到、也寫進閘的三個坑
① dispatch_async(main_queue) **無效**:Wails 佔住主執行緒後不跑標準 run loop,
   排進去的 block 永遠不執行(log 只印到 "dispatching…")⇒ 改 performSelectorOnMainThread
② NSStatusBar 需要 NSApp 已初始化:在 wails.Run() 之前呼叫 ⇒ 靜默失敗、icon 不出現
③ 🔴 **我一度對著 `go build` 產的 stub 除錯**——沒帶 Wails build tags 的執行檔
   一跑就印 "Wails applications will not build without the correct build tags" 並退出,
   我卻以為是「托盤沒建起來」,白繞一圈。**驗 Wails App 一定要用 wails build 的產物。**

## 實測證據(AppleScript 從 UI 層查,最貼近使用者看到的)
  menu bar 數=2,status item 數=1   ← 選單列 icon 真的存在
  collector 同步啟動、正常結束無孤兒
三支機械閘全過(閘也改成驗原生實作:不可 dispatch_async/必須
performSelectorOnMainThread/不可常駐 setMenu/不可再依賴 energye/systray)。

⚠️ 仍未驗:左右鍵的**實際點擊行為**要 leo 手動點。
2026-08-05 02:56:41 +08:00

88 lines
3.5 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/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()。
// 🔴 托盤必須在**主執行緒**建立(NSStatusItem 內部會 new NSWindow)。
// 改用原生 NSStatusItem(見 tray_darwin.m)——實測證明 energye/systray
// 在 RunWithExternalLoop 之下**根本不會建立托盤**(onReady 從未觸發)。
setupTray(app)
installSignalHandler() // 收到 TERM/INT 要能正常退出(不必強制結束)
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() {}