Files
u-desktop/systray.go
绝尘 f5a473c4b8 新增: 自动升级体系 + WorkerW重建检测 + 知识卡片优化
自动升级:
- 版本变量注入 (-ldflags -X main.version)
- 远程 version.json 检查 + 弹窗提示(非强制右下角/强制居中)
- updater.exe 嵌入主程序, 运行时释放到临时目录
- 下载+SHA256校验+备份+替换+回滚 全流程
- 托盘菜单"检查更新"

稳定性:
- WorkerW 重建自动检测(每10s)并重新嵌入
- 左键托盘点击防抖
- 设置窗口已打开时正确前置

优化:
- 知识卡片prompt改为面向开发者的硬核内容
- 配置目录统一到 ~/.u-desktop/
- 设置窗口WebView2数据目录改为configDir下
2026-05-28 19:06:26 +08:00

232 lines
5.0 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 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()
}
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, wsExTransparent|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)
})
log.Println("设置 HTML...")
cfg := loadConfig()
wv.SetHtml(buildWallpaperHTML(cfg))
time.Sleep(500 * time.Millisecond)
// 嵌入完成后再显示
procShowWindow.Call(wvHwnd, 5)
log.Println("壁纸窗口已显示")
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)
}
}