package main import ( "fmt" "log" "os" "runtime" "strconv" "sync/atomic" "time" "unsafe" "github.com/getlantern/systray" "github.com/jchv/go-webview2" ) var zodiacItems []*systray.MenuItem var cityItems []*systray.MenuItem func onSystrayReady() { systray.SetIcon(generateIcon()) systray.SetTooltip("动态壁纸引擎") mPause := systray.AddMenuItem("暂停", "暂停/继续") systray.AddSeparator() mZodiac := systray.AddMenuItem("星座设置", "") zodiacs := []string{ "白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座", "水瓶座", "双鱼座", } cfg := loadConfig() for _, z := range zodiacs { item := mZodiac.AddSubMenuItem(z, z) if z == cfg.Zodiac { item.Check() } zodiacItems = append(zodiacItems, item) } systray.AddSeparator() mCity := systray.AddMenuItem("城市设置", "") for _, c := range cities { item := mCity.AddSubMenuItem(c.Name, c.Adm1+" "+c.Name) if cfg.City == c.ID { item.Check() } cityItems = append(cityItems, item) } systray.AddSeparator() mQuit := systray.AddMenuItem("退出", "退出程序") // 星座选择监听 for i, item := range zodiacItems { go func(idx int, mi *systray.MenuItem) { name := zodiacs[idx] log.Printf("星座监听启动: %s", name) for { <-mi.ClickedCh log.Printf("星座点击: %s", name) cfg := loadConfig() cfg.Zodiac = name saveConfig(cfg) for _, it := range zodiacItems { it.Uncheck() } mi.Check() evalJS(fmt.Sprintf(`window.userZodiac = %q; if(window.updateTime) updateTime();`, name)) } }(i, item) } // 城市选择监听 for i, item := range cityItems { go func(idx int, mi *systray.MenuItem) { log.Printf("城市监听启动: %s", cities[idx].Name) for { <-mi.ClickedCh city := cities[idx] log.Printf("城市点击: %s", city.Name) cfg := loadConfig() cfg.City = city.ID saveConfig(cfg) for _, it := range cityItems { it.Uncheck() } mi.Check() go fetchAndPushWeather(city) } }(i, item) } // 暂停 go func() { log.Println("暂停监听启动") for { <-mPause.ClickedCh newVal := 1 - atomic.LoadInt32(&paused) atomic.StoreInt32(&paused, newVal) isPaused := newVal == 1 log.Printf("暂停切换: paused=%v", isPaused) if isPaused { mPause.SetTitle("继续") } else { mPause.SetTitle("暂停") } evalJS("if(window.setPaused) setPaused(" + strconv.FormatBool(isPaused) + ")") } }() // 退出 go func() { <-mQuit.ClickedCh os.Exit(0) }() go startWebView() go weatherLoop() } func startWebView() { runtime.LockOSThread() workerw := findWorkerW() if workerw == 0 { log.Fatal("WorkerW not found") } screenW, screenH := getScreenSize() log.Printf("Screen: %dx%d", screenW, screenH) wv = webview2.NewWithOptions(webview2.WebViewOptions{ AutoFocus: false, WindowOptions: webview2.WindowOptions{ Title: "动态壁纸", Width: uint(screenW), Height: uint(screenH), }, }) if wv == nil { log.Fatal("WebView2 create failed") } htmlData, err := fs.ReadFile("web/wallpaper.html") if err != nil { log.Fatal("读取 wallpaper.html 失败:", err) } wv.Bind("setZodiacFromGo", func(zodiac string) error { cfg := loadConfig() cfg.Zodiac = zodiac return saveConfig(cfg) }) wv.SetHtml(string(htmlData)) time.Sleep(1 * time.Second) wvHwnd = uintptr(wv.Window()) procSetWindowLongPtrW.Call(wvHwnd, uintptr(0xFFFFFFF0), uintptr(0x80000000|0x10000000|0x02000000)) procShowWindow.Call(wvHwnd, 5) procMoveWindow.Call(wvHwnd, uintptr(^uint(0)), uintptr(^uint(0)), uintptr(screenW+2), uintptr(screenH+2), 1) log.Printf("壁纸已嵌入: HWND=0x%x, %dx%d", wvHwnd, screenW, screenH) // 注入配置 go func() { time.Sleep(500 * time.Millisecond) cfg := loadConfig() evalJS(fmt.Sprintf(`window.userZodiac = %q;`, cfg.Zodiac)) log.Printf("配置已注入: zodiac=%s", cfg.Zodiac) }() go fullscreenMonitor() // 延迟嵌入 WorkerW go func() { time.Sleep(3 * time.Second) workerw := findWorkerW() if workerw != 0 { oldParent, _, _ := procSetParent.Call(wvHwnd, workerw) log.Printf("SetParent: 0x%x -> 0x%x (old=0x%x)", wvHwnd, workerw, oldParent) procMoveWindow.Call(wvHwnd, uintptr(^uint(0)), uintptr(^uint(0)), uintptr(screenW+2), uintptr(screenH+2), 1) } }() // 消息循环 type msg struct { hwnd uintptr message uint32 wParam uintptr lParam uintptr time uint32 pt struct{ x, y int32 } } var m msg log.Println("启动自定义消息循环...") 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 } } } nextMsg: procTranslateMessage.Call(uintptr(unsafe.Pointer(&m))) procDispatchMessageW.Call(uintptr(unsafe.Pointer(&m))) } } func fullscreenMonitor() { type rect struct{ Left, Top, Right, Bottom int32 } var lastState string for { if atomic.LoadInt32(&paused) == 0 && wv != nil { 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) } }