Files
lmvpn_client/internal/ui/tray.go
T
kevin 96b1a8839c
Release / build-macos (push) Canceled after 0s
Release / build-windows (push) Canceled after 0s
Release / release (push) Canceled after 0s
feat: 单实例锁与窗口恢复机制
- GUI 单实例锁: 新增 internal/instance 包,首实例监听专用端点,
  第二实例发送 focus 信号后静默退出 (Unix: gui.sock, Win: 18924)
- 守护进程弱锁修复: NewServer() 在 os.Remove 前先 net.Dial 探测,
  有活进程时拒绝启动,避免静默抢占 socket 导致孤儿进程
- macOS .app 重开恢复: 通过 class_addMethod 给 GLFWApplicationDelegate
  注册 applicationShouldHandleReopen:hasVisibleWindows: 回调,
  绕过 GLFW 直接用 Cocoa makeKeyAndOrderFront: 显示窗口
- 三平台覆盖: macOS/Linux 用 unix socket, Windows 用 TCP 端口
2026-07-09 00:29:21 +08:00

83 lines
2.1 KiB
Go

package ui
import (
_ "embed"
"lmvpn/internal/i18n"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/driver/desktop"
)
//go:embed tray-icon.png
var trayIconBytes []byte
// setupTray configures the system tray menu (desktop only).
func (a *App) setupTray() {
deskApp, ok := a.fyneApp.(desktop.App)
if !ok {
return // not a desktop app, skip tray
}
// Language submenu — labels for English and 中文 are always shown in
// their native script so the user can recognise them regardless of
// the active language.
autoItem := fyne.NewMenuItem(i18n.T("TrayLanguageAuto"), func() {
a.changeLanguage(i18n.LangAuto)
})
enItem := fyne.NewMenuItem("English", func() {
a.changeLanguage(i18n.LangEn)
})
zhItem := fyne.NewMenuItem("中文", func() {
a.changeLanguage(i18n.LangZhHans)
})
// Mark the currently selected option.
switch {
case a.langSetting == "" || a.langSetting == i18n.LangAuto:
autoItem.Checked = true
case a.langSetting == i18n.LangEn:
enItem.Checked = true
case a.langSetting == i18n.LangZhHans:
zhItem.Checked = true
}
langItem := fyne.NewMenuItem(i18n.T("TrayLanguage"), nil)
langItem.ChildMenu = fyne.NewMenu(i18n.T("TrayLanguage"),
autoItem, enItem, zhItem,
)
connectItem := fyne.NewMenuItem(i18n.T("TrayConnect"), func() {
a.onConnect()
})
disconnectItem := fyne.NewMenuItem(i18n.T("TrayDisconnect"), func() {
a.onDisconnect()
})
if a.connectBtn != nil {
connectItem.Disabled = a.connectBtn.Disabled()
}
if a.disconnectBtn != nil {
disconnectItem.Disabled = a.disconnectBtn.Disabled()
}
menu := fyne.NewMenu(i18n.T("WindowTitle"),
fyne.NewMenuItem(i18n.T("TrayShowWindow"), func() {
a.windowHidden = false
showAndActivate()
a.window.Show()
a.window.RequestFocus()
}),
fyne.NewMenuItemSeparator(),
connectItem,
disconnectItem,
fyne.NewMenuItemSeparator(),
langItem,
fyne.NewMenuItemSeparator(),
fyne.NewMenuItem(i18n.T("TrayQuit"), func() {
a.quit()
}),
)
deskApp.SetSystemTrayMenu(menu)
trayIcon := fyne.NewStaticResource("tray-icon.png", trayIconBytes)
deskApp.SetSystemTrayIcon(trayIcon)
}