Files
arcrun-collector/cmd/arcrun-app/main.go
T
Leo a02eed539a t194 三修:右鍵選單——正解是「**少寫**一段」,不是再補一段
leo 實測:右鍵仍然沒有 quit 選單。

## 真兇:我把函式庫本來就會做對的事,換成一個更脆弱的版本
讀 systray_darwin.go 的 systray_on_rclick():

    func systray_on_rclick() {
        if onRClick != nil { onRClick(st) } else { C.show_menu() }
    }

⇒ **沒註冊 SetOnRClick 時,函式庫自己就會把選單叫出來**;
   而且 C 層的 show_menu 內部已經處理好整套:
   create_menu() → [statusItem.button performClick:nil] → set_menu_nil()

我卻註冊了自己的 handler、還加 `if menu != nil` 防呆
⇒ 等於用一個判斷把「本來會動的預設行為」擋掉。

修:**刪掉 SetOnRClick 註冊**。左鍵仍自訂(要開視窗),右鍵交還函式庫。

## 判準(已寫進 mistakes)
用第三方函式庫時,**先讀它的預設行為再決定要不要覆寫**。
「多註冊一個 handler」看起來是加功能,實際上常是**關掉**原本正確的行為。

## 閘也跟著改
check-tray.sh 原本驗「SetOnRClick 有註冊且順序對」——**那是在守一個錯的寫法**。
改成反向:**出現 systray.SetOnRClick 就擋下**,並說明原因。
實跑 7 項全過;App 活著 pid 14081、crash 0。
2026-08-05 02:27:23 +08:00

119 lines
5.2 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()
installSignalHandler() // 收到 TERM/INT 要能正常退出(不必強制結束)
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() {
// ① icon**必須是 template icon**——選單列規格是 16-22pt、純黑+alpha
// 由系統依深淺色自動上色。先前塞 1024x1024 的彩色 app icon
// ⇒ 縮成一坨方塊(leo 實測)。改用 CIS 的 double chevron 渲成 44x44 純黑。
systray.SetTemplateIcon(trayIcon, trayIcon)
systray.SetTooltip("Arcrun — 你的知識庫同步小幫手")
// 🔴 ②③ 的真兇(energye/systray 原始碼註解寫得很清楚):
// 「該方法主動調用後 如果托盤菜單已創建則添加進去, **之後鼠標事件失效**」
// ⇒ 只要用 AddMenuItem 建了選單,左右鍵 handler **全部失效**
// ⇒ leo 實測「右鍵沒跳出 quit」「第一次能開之後就不再跳」。
// 正解:SetMenuNil() 移除選單讓滑鼠事件生效;右鍵則交給函式庫預設行為(見下)。
quit := systray.AddMenuItem("結束 Arcrun", "停止同步並關閉")
quit.Click(func() { app.Quit() })
systray.SetMenuNil() // ← 沒有這行,SetOnClick 不會被呼叫
// 左鍵:立刻展開主視窗(leo:「點擊托盤的 icon 就立刻展開界面」)
systray.SetOnClick(func(menu systray.IMenu) { app.ShowWindow() })
// 🔴 右鍵:**故意不註冊 handler**。
// 看 systray_darwin.go 的 systray_on_rclick()
// if onRClick != nil { onRClick(st) } else { C.show_menu() }
// ⇒ **沒註冊時,函式庫自己就會把選單叫出來**(而且 show_menu 內部
// 已經處理好 create_menu → performClick → set_menu_nil 整套)。
// 我先前註冊了自己的 handler 並加 `if menu != nil` 防呆,
// 反而把「函式庫本來就會做對的事」換成一個更脆弱的版本
// ⇒ leo 實測「右鍵沒跳出 quit」。少寫這段才是正解。
}, nil)
}