- Wails v2.16.0 + React 19 + TypeScript + Vite 7 - 插件系统(build tag 控制编译,plugin.json 运行时配置) - 支付插件(授权、账号、订单、退款、账单) - 高并发基础设施(任务池、批量处理器、限流器、环形缓冲、管道) - Windows 系统托盘(Win32 API) - CodeGraph 索引 + .memory 记忆包 - 默认图标 assets/icon.png
168 lines
3.5 KiB
Go
168 lines
3.5 KiB
Go
// 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
|
||
}
|