Files
u-desktop/systray.go
T

260 lines
5.8 KiB
Go

package main
import (
"log"
"os"
"os/exec"
"runtime"
"strconv"
"sync/atomic"
"time"
"unsafe"
"github.com/getlantern/systray"
"github.com/jchv/go-webview2"
)
func onSystrayReady() {
systray.SetIcon(trayIcon)
systray.SetTooltip("动态壁纸引擎")
// 左键点击托盘图标 → 打开设置(防抖: 连续点击只响应一次)
go func() {
for range systray.TrayClickCh {
openSettingsWindow()
// 消费后续积压的点击
for {
select {
case <-systray.TrayClickCh:
default:
goto done
}
}
done:
}
}()
mSettings := systray.AddMenuItem("桌面设置", "打开设置窗口")
mCheckUpdate := systray.AddMenuItem("检查更新", "检查是否有新版本")
mRestart := systray.AddMenuItem("重启", "重启程序")
mQuit := systray.AddMenuItem("退出", "退出程序")
// 检查更新
go func() {
for {
<-mCheckUpdate.ClickedCh
manualCheckUpdate()
}
}()
// 设置窗口
go func() {
for {
<-mSettings.ClickedCh
openSettingsWindow()
}
}()
// 退出
go func() {
<-mQuit.ClickedCh
os.Exit(0)
}()
// 重启
go func() {
for {
<-mRestart.ClickedCh
exe, _ := os.Executable()
exec.Command(exe).Start()
os.Exit(0)
}
}()
go startWebView()
go weatherLoop()
go horoscopeLoop()
go aiNewsLoop()
go bingWallpaperLoop()
go knowledgeLoop()
go startPhotoLoop()
go runAutoUpdateCheck()
// 音频可视化:启动后检查是否需要音频捕获
go func() {
time.Sleep(2 * time.Second)
cfg := loadConfig()
if cfg.WallpaperType == WPTheme && cfg.Theme == ThemeAudioViz {
startAudioCapture()
}
}()
}
func startWebView() {
runtime.LockOSThread()
log.Println("startWebView: 开始")
workerw := findWorkerW()
if workerw == 0 {
log.Println("ERROR: WorkerW not found")
showError("无法找到桌面窗口(WorkerW),程序无法启动。")
os.Exit(1)
}
log.Printf("WorkerW: 0x%x", workerw)
screenW, screenH := getScreenSize()
log.Printf("Screen: %dx%d", screenW, screenH)
log.Println("创建 WebView2...")
wv = webview2.NewWithOptions(webview2.WebViewOptions{
AutoFocus: false,
WindowOptions: webview2.WindowOptions{
Title: "动态壁纸",
Width: uint(screenW),
Height: uint(screenH),
},
})
if wv == nil {
showError("WebView2 创建失败,请确保已安装 WebView2 Runtime。")
os.Exit(1)
}
log.Println("WebView2 创建成功")
// 立即获取 HWND 并隐藏窗口,避免闪烁
wvHwnd = uintptr(wv.Window())
procShowWindow.Call(wvHwnd, 0) // SW_HIDE
procSetWindowLongPtrW.Call(wvHwnd, gwlStyle, wsPopup|wsVisible|wsChild)
procSetWindowLongPtrW.Call(wvHwnd, gwlExStyle, wsExLayered)
// 立即嵌入 WorkerW
embedWorkerW(workerw, screenW, screenH)
log.Printf("已嵌入 WorkerW: HWND=0x%x, %dx%d", wvHwnd, screenW, screenH)
wv.Bind("setZodiacFromGo", func(zodiac string) error {
cfg := loadConfig()
cfg.Zodiac = zodiac
return saveConfig(cfg)
})
wv.Bind("refreshKnowledge", func() string {
go fetchAndPushKnowledge()
return ""
})
wv.Bind("setInteractiveRect", func(left, top, right, bottom float64) string {
overlayHitRect.Left = int32(left)
overlayHitRect.Top = int32(top)
overlayHitRect.Right = int32(right)
overlayHitRect.Bottom = int32(bottom)
overlayHasHitRect = true
return ""
})
log.Println("设置 HTML...")
cfg := loadConfig()
wv.SetHtml(buildWallpaperHTML(cfg))
time.Sleep(500 * time.Millisecond)
// 嵌入完成后再显示
procShowWindow.Call(wvHwnd, 5)
log.Println("壁纸窗口已显示")
// 子类化:WM_NCHITTEST 动态穿透(WebView2 初始化完成后)
subclassOverlayWindows()
// 全局鼠标钩子:桌面图标层拦截了 WM_NCHITTEST,用钩子直接捕获点击
installMouseHook()
go func() {
time.Sleep(500 * time.Millisecond)
reloadAllCards()
}()
go fullscreenMonitor()
type msg struct {
hwnd uintptr
message uint32
wParam uintptr
lParam uintptr
time uint32
pt struct{ x, y int32 }
}
var m msg
for {
ret, _, _ := procGetMessageW.Call(
uintptr(unsafe.Pointer(&m)),
0, 0, 0,
)
if ret == 0 {
break
}
if m.message == wmEvalJS {
for {
select {
case js := <-jsQueue:
wv.Eval(js)
default:
goto nextMsg
}
}
}
if m.message == wmSetHtml {
select {
case html := <-htmlQueue:
wv.SetHtml(html)
default:
}
goto nextMsg
}
nextMsg:
procTranslateMessage.Call(uintptr(unsafe.Pointer(&m)))
procDispatchMessageW.Call(uintptr(unsafe.Pointer(&m)))
}
}
func embedWorkerW(workerw uintptr, screenW, screenH int32) {
procSetParent.Call(wvHwnd, workerw)
procMoveWindow.Call(wvHwnd, uintptr(^uint(0)), uintptr(^uint(0)), uintptr(screenW+2), uintptr(screenH+2), 1)
}
func fullscreenMonitor() {
type rect struct{ Left, Top, Right, Bottom int32 }
var lastState string
var lastParent uintptr
var parentTick int
for {
if atomic.LoadInt32(&paused) == 0 && wv != nil {
// 每 5 轮(10s)检测一次父窗口,极轻量系统调用
parentTick++
if parentTick >= 5 {
parentTick = 0
parent, _, _ := procGetParent.Call(wvHwnd)
if parent != lastParent {
lastParent = parent
ww := findWorkerW()
if ww != 0 && ww != parent {
screenW, screenH := getScreenSize()
embedWorkerW(ww, screenW, screenH)
procShowWindow.Call(wvHwnd, 5)
log.Printf("WorkerW 变更,已重新嵌入: 0x%x → 0x%x", parent, ww)
}
}
}
fg, _, _ := procGetForegroundWindow.Call()
if fg != 0 {
var r rect
procGetWindowRect.Call(fg, uintptr(unsafe.Pointer(&r)))
screenW, screenH := getScreenSize()
isFull := (r.Right-r.Left >= screenW) && (r.Bottom-r.Top >= screenH)
state := strconv.FormatBool(isFull)
if state != lastState {
lastState = state
evalJS("if(window.setFullscreen) setFullscreen(" + state + ")")
}
}
}
time.Sleep(2 * time.Second)
}
}