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
This commit is contained in:
167
internal/tray/win/tray.go
Normal file
167
internal/tray/win/tray.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user