// Windows系统托盘管理 //go:build windows // +build windows package tray import ( _ "embed" "fmt" "os/exec" "runtime" "github.com/getlantern/systray" "go.uber.org/zap" ) //go:embed favicon.ico var trayIcon []byte // TrayManager 托盘管理器 type TrayManager struct { logger *zap.Logger webPort int onExit func() mQuit *systray.MenuItem mOpenWeb *systray.MenuItem mStatus *systray.MenuItem mRestart *systray.MenuItem } // NewTrayManager 创建托盘管理器 func NewTrayManager(logger *zap.Logger, webPort int, onExit func()) *TrayManager { return &TrayManager{ logger: logger, webPort: webPort, onExit: onExit, } } // Run 运行托盘(阻塞) func (t *TrayManager) Run() { systray.Run(t.onReady, t.onExit) } // onReady 托盘初始化 func (t *TrayManager) onReady() { // 设置托盘图标和提示 systray.SetIcon(trayIcon) systray.SetTooltip("MeshRay - 智能组网工具") // 添加菜单项 t.mStatus = systray.AddMenuItem("🟢 状态:运行中", "MeshRay运行状态") t.mStatus.Disable() systray.AddSeparator() t.mOpenWeb = systray.AddMenuItem("🌐 打开管理界面", "打开Web管理界面") _ = systray.AddMenuItem("ℹ️ 关于", "关于MeshRay") systray.AddSeparator() _ = systray.AddMenuItem("❓ 帮助", "查看帮助") _ = systray.AddMenuItem("🔄 检查更新", "检查新版本") systray.AddSeparator() t.mRestart = systray.AddMenuItem("🔁 重启服务", "重启MeshRay服务") t.mQuit = systray.AddMenuItem("❌ 退出", "退出MeshRay") // 处理菜单点击 go t.handleMenuEvents() } // handleMenuEvents 处理菜单事件 func (t *TrayManager) handleMenuEvents() { for { select { case <-t.mOpenWeb.ClickedCh: t.openWebInterface() case <-t.mRestart.ClickedCh: t.restartService() case <-t.mQuit.ClickedCh: t.quit() } } } // openWebInterface 打开Web管理界面 func (t *TrayManager) openWebInterface() { url := fmt.Sprintf("http://localhost:%d", t.webPort) t.logger.Info("打开Web管理界面", zap.String("url", url)) var cmd *exec.Cmd switch runtime.GOOS { case "windows": cmd = exec.Command("cmd", "/c", "start", url) case "darwin": cmd = exec.Command("open", url) default: cmd = exec.Command("xdg-open", url) } if err := cmd.Start(); err != nil { t.logger.Error("打开浏览器失败", zap.Error(err)) } } // restartService 重启服务 func (t *TrayManager) restartService() { t.logger.Info("重启服务...") // ✅ P3 阶段 - 简单重启:先退出,然后由外部监控进程重启 // 完整实现需要 Windows 服务控制或守护进程 t.quit() } // quit 退出程序 func (t *TrayManager) quit() { t.logger.Info("退出MeshRay...") systray.Quit() if t.onExit != nil { t.onExit() } } // updateStatus 更新状态 func (t *TrayManager) updateStatus() { // ✅ P3 阶段 - 暂不实现动态状态更新 // 未来可以检查服务运行状态,更新托盘菜单(如:运行中/已停止) } // MeshRayIcon 托盘图标(ICO格式,16x16) // 简单的 "MR" 文字图标 var MeshRayIcon = []byte{ // ICO 头部 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x10, 0x10, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x68, 0x04, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 图标数据 - 蓝色背景上的白色 "MR" 文字 // 第1行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第2行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第3行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第4行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第5行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第6行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第7行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第8行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第9行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第10行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第11行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第12行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第13行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第14行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第15行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, // 第16行 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, 0x1b, 0x6b, 0xa2, 0xff, }