// Package tray Windows 系统托盘实现(使用 win32 API) package tray import ( "log" "sync" "unsafe" "golang.org/x/sys/windows" ) // Hook 托盘图标句柄 var hook windows.Handle // MenuHandle 菜单句柄 type MenuHandle uintptr // Tray 系统托盘管理器 type Tray struct { mu sync.Mutex visible bool callback uintptr // WNDPROC 回调 hWnd windows.Handle } // MenuItem 菜单项 type MenuItem struct { ID uint32 Text string } // NewTray 创建系统托盘 func NewTray(iconData []byte, tooltip string) *Tray { t := &Tray{} if err := t.init(iconData, tooltip); err != nil { log.Printf("[tray] init failed: %v", err) return nil } return t } func (t *Tray) init(iconData []byte, tooltip string) error { // 获取当前进程窗口 hWnd, _ := windows.GetForegroundWindow() if hWnd == 0 { return windows.ERROR_ACCESS_DENIED } t.hWnd = hWnd // 注册托盘图标 // NOTIFYICONDATA structure // https://docs.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataa data := ¬ifyIconData{ CbSize: uint32(unsafe.Sizeof(notifyIconData{})), HWnd: uint32(hWnd), UID: 1000, UFlags: NIF_MESSAGE | NIF_ICON | NIF_TIP, UCallbackMessage: WM_USER + 1000, HIcon: 0, // 使用默认图标 } // 设置 tooltip for i := 0; i < len(tooltip) && i < 128; i++ { data.Tip[i] = uint8(tooltip[i]) } data.Tip[len(tooltip)] = 0 // 添加托盘图标 ret, _, _ := procShellNotifyIcon.Call( SHM_ADD, uintptr(unsafe.Pointer(data)), ) if ret == 0 { return windows.GetLastError() } t.visible = true return nil } // Destroy 销毁托盘图标 func (t *Tray) Destroy() { t.mu.Lock() defer t.mu.Unlock() if !t.visible { return } data := ¬ifyIconData{ CbSize: uint32(unsafe.Sizeof(notifyIconData{})), HWnd: uint32(t.hWnd), UID: 1000, UFlags: NIF_MESSAGE, } procShellNotifyIcon.Call(SHM_DELETE, uintptr(unsafe.Pointer(data))) t.visible = false } // Show 显示托盘 func (t *Tray) Show(iconData []byte, tooltip string) error { t.mu.Lock() defer t.mu.Unlock() if t.visible { return nil } return t.init(iconData, tooltip) } // Hide 隐藏托盘 func (t *Tray) Hide() { t.mu.Lock() defer t.mu.Unlock() if !t.visible { return } data := ¬ifyIconData{ CbSize: uint32(unsafe.Sizeof(notifyIconData{})), HWnd: uint32(t.hWnd), UID: 1000, UFlags: NIF_MESSAGE, } procShellNotifyIcon.Call(SHM_DELETE, uintptr(unsafe.Pointer(data))) t.visible = false } // IsVisible 是否可见 func (t *Tray) IsVisible() bool { t.mu.Lock() defer t.mu.Unlock() return t.visible } // ───────────────────────────────────────────── // Win32 API 定义 // ───────────────────────────────────────────── const ( WM_USER = 0x0400 SHM_ADD uintptr = 0x00000000 SHM_DELETE uintptr = 0x00000001 NIF_MESSAGE = 0x00000001 NIF_ICON = 0x00000002 NIF_TIP = 0x00000004 ) // NOTIFYICONDATA struct type notifyIconData struct { CbSize uint32 HWnd uint32 UID uint32 UFlags uint32 UCallbackMessage uint32 HIcon uintptr Tip [128]uint8 } var ( procShellNotifyIcon = mustLoad("shell32.dll").MustFindProc("Shell_NotifyIconA") ) func mustLoad(dllName string) *windows.DynLib { dll, err := windows.LoadDLL(dllName) if err != nil { log.Fatalf("[tray] failed to load %s: %v", dllName, err) } return dll }