// Package tray 系统托盘管理器,基于 getlantern/systray 跨平台实现。 // Windows 下图标需为 .ico 数据,macOS/Linux 下可为 .png/.jpg/.ico。 package tray import ( "context" "sync" "github.com/getlantern/systray" "github.com/wailsapp/wails/v2/pkg/runtime" ) // Options 托盘初始化参数 type Options struct { // Icon 托盘图标数据 Icon []byte // Tooltip 悬停提示文本 Tooltip string // OnShow 点击"显示窗口"时回调,为空时默认调用 runtime.WindowShow OnShow func() // OnHide 点击"隐藏窗口"时回调,为空时默认调用 runtime.WindowHide OnHide func() // OnQuit 点击"退出"时回调,为空时默认调用 runtime.Quit OnQuit func() } // Manager 系统托盘管理器 type Manager struct { mu sync.Mutex opts Options started bool ready chan struct{} quitItem *systray.MenuItem } // New 创建托盘管理器,Run 之前不会显示图标 func New(opts Options) *Manager { return &Manager{ opts: opts, ready: make(chan struct{}), } } // Run 在后台 goroutine 中启动托盘事件循环(非阻塞)。 // ctx 用于窗口显示/隐藏/退出。 func (m *Manager) Run(ctx context.Context) { m.mu.Lock() if m.started { m.mu.Unlock() return } m.started = true m.mu.Unlock() go func() { systray.Run(func() { m.onReady(ctx) close(m.ready) }, func() { m.mu.Lock() m.started = false m.mu.Unlock() }) }() } // WaitReady 阻塞直到托盘初始化完成(可选) func (m *Manager) WaitReady() { <-m.ready } // Active 托盘是否已成功启动 func (m *Manager) Active() bool { m.mu.Lock() defer m.mu.Unlock() return m.started } // Quit 移除托盘图标并停止托盘 goroutine func (m *Manager) Quit() { systray.Quit() } func (m *Manager) onReady(ctx context.Context) { if len(m.opts.Icon) > 0 { systray.SetIcon(m.opts.Icon) } if m.opts.Tooltip != "" { systray.SetTooltip(m.opts.Tooltip) } show := systray.AddMenuItem("显示窗口", "Show the main window") hide := systray.AddMenuItem("隐藏窗口", "Hide the main window") systray.AddSeparator() quitItem := systray.AddMenuItem("退出", "Quit the application") m.quitItem = quitItem m.handleMenuClicks(ctx, show, hide, quitItem) } func (m *Manager) handleMenuClicks(ctx context.Context, show, hide, quit *systray.MenuItem) { for { select { case <-show.ClickedCh: if m.opts.OnShow != nil { m.opts.OnShow() } else { runtime.WindowShow(ctx) } case <-hide.ClickedCh: if m.opts.OnHide != nil { m.opts.OnHide() } else { runtime.WindowHide(ctx) } case <-quit.ClickedCh: if m.opts.OnQuit != nil { m.opts.OnQuit() } else { runtime.Quit(ctx) } } } }