自动升级: - 版本变量注入 (-ldflags -X main.version) - 远程 version.json 检查 + 弹窗提示(非强制右下角/强制居中) - updater.exe 嵌入主程序, 运行时释放到临时目录 - 下载+SHA256校验+备份+替换+回滚 全流程 - 托盘菜单"检查更新" 稳定性: - WorkerW 重建自动检测(每10s)并重新嵌入 - 左键托盘点击防抖 - 设置窗口已打开时正确前置 优化: - 知识卡片prompt改为面向开发者的硬核内容 - 配置目录统一到 ~/.u-desktop/ - 设置窗口WebView2数据目录改为configDir下
232 lines
5.0 KiB
Go
232 lines
5.0 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()
|
||
}
|
||
|
||
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)
|
||
}
|
||
}
|