package main import ( "bufio" "fmt" "io" "os" "os/signal" "syscall" "github.com/getlantern/systray" ) var ( sendPipe *os.File receivePipe *os.File logFile *os.File ) func initLog() { var err error logFile, err = os.OpenFile("systray_debug.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { // failsafe return } } func logMsg(format string, args ...interface{}) { if logFile != nil { fmt.Fprintf(logFile, format+"\n", args...) } } func main() { initLog() logMsg("Systray process started") // 使用 Stdin 和 Stdout 与主进程通信 // 主进程将 cmd.Stdin 连接到 pipes,将 cmd.Stdout 连接到 pipes // 所以这里我们直接使用 os.Stdin 读取,os.Stdout 发送 sendPipe = os.Stdout receivePipe = os.Stdin // 启动系统托盘 logMsg("Calling systray.Run") systray.Run(onReady, onExit) } func onReady() { logMsg("systray onReady called") // 设置托盘图标 - 使用简单的图标 systray.SetIcon(getIcon()) systray.SetTitle("Bidding Looker") systray.SetTooltip("Bidding Looker - 招标信息查看器") // 显示窗口菜单项 mShow := systray.AddMenuItem("显示窗口", "显示主窗口") mQuit := systray.AddMenuItem("退出", "退出程序") // 监听来自主进程的消息 go monitorPipe() // 处理菜单点击 go func() { for { select { case <-mShow.ClickedCh: logMsg("Show menu clicked") // 发送显示窗口消息到主进程 sendMessage("SHOW") case <-mQuit.ClickedCh: logMsg("Quit menu clicked") // 发送退出消息到主进程 sendMessage("QUIT") systray.Quit() } } }() // 监听系统信号 sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) go func() { <-sigCh logMsg("Received signal, quitting") systray.Quit() }() } func onExit() { logMsg("systray onExit called") // 清理资源 } func monitorPipe() { logMsg("Starting monitorPipe") reader := bufio.NewReader(receivePipe) for { line, err := reader.ReadString('\n') if err != nil { if err == io.EOF { logMsg("Pipe EOF, quitting") // 主进程关闭了管道(可能退出了),我们也退出 systray.Quit() return } logMsg("Pipe read error: %v", err) fmt.Fprintf(os.Stderr, "Pipe read error: %v\n", err) return } // 处理来自主进程的消息 (日志输出到 Stderr,避免污染 Stdout) logMsg("Received from main: %s", line) fmt.Fprintf(os.Stderr, "Received from main: %s", line) } } func sendMessage(message string) { logMsg("Sending message: %s", message) _, err := sendPipe.WriteString(message + "\n") if err != nil { logMsg("Failed to send message: %v", err) fmt.Fprintf(os.Stderr, "Failed to send message: %v\n", err) } } // 简单的图标数据(16x16 像素的简单图标) // 使用 PNG 格式,这是 systray 库在 Windows 上的推荐格式 func getIcon() []byte { // PNG 文件签名 // IHDR chunk (图像头): 16x16, 8-bit RGBA // IDAT chunk (图像数据): 简单的蓝色方块图标 // IEND chunk (文件结束) return []byte{ // PNG 签名 (8 bytes) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // IHDR chunk 0x00, 0x00, 0x00, 0x0D, // Length: 13 bytes 0x49, 0x48, 0x44, 0x52, // Type: IHDR 0x00, 0x00, 0x00, 0x10, // Width: 16 0x00, 0x00, 0x00, 0x10, // Height: 16 0x08, // Bit depth: 8 0x06, // Color type: RGBA 0x00, 0x00, 0x00, // Compression, Filter, Interlace 0x5D, 0x8A, 0x7F, 0xD4, // CRC // IDAT chunk (使用最简单的 PNG 编码) 0x00, 0x00, 0x01, 0x6D, // Length: 365 bytes 0x49, 0x44, 0x41, 0x54, // Type: IDAT // 压缩数据 (zlib 格式) 0x78, 0x9C, // zlib header (default compression) // Deflate 压缩块 0x63, 0x18, 0x19, 0x60, 0x28, 0x55, 0xF6, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0A, 0x8C, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x4B, 0x8C, 0x36, // CRC // IEND chunk 0x00, 0x00, 0x00, 0x00, // Length: 0 bytes 0x49, 0x45, 0x4E, 0x44, // Type: IEND 0xAE, 0x42, 0x60, 0x82, // CRC } }