a02eed539a
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。
119 lines
5.2 KiB
Go
119 lines
5.2 KiB
Go
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 不出現 icon:Wails 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)
|
||
}
|