Files
go_win_template/internal/tray/win/tray.go
Geliebte 1ea8c9da9c feat: 初始化 Wails + React + Go 桌面应用模板
- Wails v2.16.0 + React 19 + TypeScript + Vite 7
- 插件系统(build tag 控制编译,plugin.json 运行时配置)
- 支付插件(授权、账号、订单、退款、账单)
- 高并发基础设施(任务池、批量处理器、限流器、环形缓冲、管道)
- Windows 系统托盘(Win32 API)
- CodeGraph 索引 + .memory 记忆包
- 默认图标 assets/icon.png
2026-09-17 19:31:17 +08:00

168 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 := &notifyIconData{
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 := &notifyIconData{
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 := &notifyIconData{
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
}