package main import ( _ "embed" "encoding/json" "fmt" "log" "os" "path/filepath" "runtime" "sync" "unsafe" "github.com/jchv/go-webview2" "golang.org/x/sys/windows/registry" ) //go:embed web/settings.html var settingsHTML string var ( settingsMu sync.Mutex settingsOpen bool settingsHwnd uintptr ) func isSystemLightTheme() bool { k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE) if err != nil { return false } defer k.Close() v, _, err := k.GetIntegerValue("AppsUseLightTheme") if err != nil { return false } return v == 1 } var themeNames = []struct { Name ThemeName Label string }{ {ThemeAurora, "极光"}, {ThemeStar, "星空"}, {ThemeGradient, "渐变"}, {ThemeParticle, "粒子"}, {ThemeFractal, "极光流体"}, {ThemeText, "文字"}, } func openSettingsWindow() { settingsMu.Lock() if settingsOpen && settingsHwnd != 0 { settingsMu.Unlock() procShowWindow.Call(settingsHwnd, 9) procGetForegroundWindow.Call(settingsHwnd) return } settingsOpen = true settingsMu.Unlock() go func() { runtime.LockOSThread() defer func() { settingsMu.Lock() settingsOpen = false settingsHwnd = 0 settingsMu.Unlock() runtime.UnlockOSThread() }() dataDir := filepath.Join(os.TempDir(), "u-desktop-settings") os.MkdirAll(dataDir, 0755) w := webview2.NewWithOptions(webview2.WebViewOptions{ AutoFocus: true, DataPath: dataDir, WindowOptions: webview2.WindowOptions{ Title: "桌面设置", Width: 760, Height: 1350, }, }) if w == nil { log.Println("设置窗口: 创建失败") return } w.Bind("loadAllSettings", func() string { cfg := loadConfig() seen := map[string]bool{} var provinces []string citiesByProv := map[string][]map[string]string{} for _, c := range cities { if !seen[c.Adm1] { seen[c.Adm1] = true provinces = append(provinces, c.Adm1) } citiesByProv[c.Adm1] = append(citiesByProv[c.Adm1], map[string]string{ "id": c.ID, "name": c.Name, }) } type themeJSON struct { Value string `json:"value"` Label string `json:"label"` } var tl []themeJSON for _, t := range themeNames { tl = append(tl, themeJSON{Value: string(t.Name), Label: t.Label}) } data, _ := json.Marshal(map[string]interface{}{ "lightTheme": isSystemLightTheme(), "wallpaper": !cfg.HideWallpaper, "time": !cfg.HideTime, "weather": !cfg.HideWeather, "zodiacCard": !cfg.HideZodiac, "knowledgeCard": !cfg.HideKnowledge, "layout": string(cfg.Layout), "zodiac": cfg.Zodiac, "city": cfg.City, "provinces": provinces, "citiesByProv": citiesByProv, "wallpaperType": string(cfg.WallpaperType), "theme": string(cfg.Theme), "themes": tl, "color1": cfg.Color1, "color2": cfg.Color2, "colorGradient": cfg.ColorGradient, "savedColors": cfg.SavedColors, "bingAutoRefresh": cfg.BingAutoRefresh, "knowledgeKeyword": cfg.KnowledgeKeyword, "knowledgePrompt": cfg.KnowledgePrompt, "wallpaperText": cfg.WallpaperText, "imagePath": cfg.ImagePath, "showSeconds": cfg.ShowSeconds, "ainewsCard": !cfg.HideAINews, }) return string(data) }) w.Bind("saveToggles", func(jsonStr string) string { var data map[string]bool if json.Unmarshal([]byte(jsonStr), &data) != nil { return "" } cfg := loadConfig() if v, ok := data["wallpaper"]; ok { cfg.HideWallpaper = !v evalJS(fmt.Sprintf("if(window.setWallpaperVisible) setWallpaperVisible(%v)", v)) } if v, ok := data["time"]; ok { cfg.HideTime = !v evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('time',%v)", v)) } if v, ok := data["weather"]; ok { cfg.HideWeather = !v evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('weather',%v)", v)) } if v, ok := data["zodiacCard"]; ok { cfg.HideZodiac = !v evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('zodiac',%v)", v)) } if v, ok := data["knowledgeCard"]; ok { cfg.HideKnowledge = !v evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('knowledge',%v)", v)) } if v, ok := data["ainewsCard"]; ok { cfg.HideAINews = !v evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('ainews',%v)", v)) } if v, ok := data["showSeconds"]; ok { cfg.ShowSeconds = v evalJS(fmt.Sprintf("if(window.setShowSeconds) setShowSeconds(%v)", v)) } saveConfig(cfg) return "" }) w.Bind("saveLayout", func(layout string) string { cfg := loadConfig() cfg.Layout = Layout(layout) saveConfig(cfg) reloadWallpaper() return "" }) w.Bind("saveZodiac", func(zodiac string) string { cfg := loadConfig() cfg.Zodiac = zodiac saveConfig(cfg) evalJS(fmt.Sprintf(`window.userZodiac = %q; if(window.updateTime) updateTime();`, zodiac)) triggerHoroscopeRefresh(zodiac) return "" }) w.Bind("saveCity", func(cityID string) string { cfg := loadConfig() cfg.City = cityID saveConfig(cfg) for _, c := range cities { if c.ID == cityID { go fetchAndPushWeather(c) break } } return "" }) w.Bind("saveWallpaperType", func(wpType, theme string) string { cfg := loadConfig() cfg.WallpaperType = WallpaperType(wpType) cfg.Theme = ThemeName(theme) saveConfig(cfg) reloadWallpaper() return "" }) w.Bind("pickLocalImage", func() string { hwnd := uintptr(w.Window()) path := openFileDialog(hwnd) if path == "" { return "" } cfg := loadConfig() cfg.WallpaperType = WPImage cfg.ImagePath = path saveConfig(cfg) reloadWallpaper() return path }) w.Bind("enableBing", func() string { cfg := loadConfig() cfg.WallpaperType = WPBing saveConfig(cfg) reloadWallpaper() go fetchBingHistory() return "" }) w.Bind("bingPrev", func() string { bingPrev() return bingCurrentState() }) w.Bind("bingNext", func() string { bingNext() return bingCurrentState() }) w.Bind("bingToggleFavorite", func() string { bingToggleFavorite() return bingCurrentState() }) w.Bind("getBingInfo", func() string { return bingCurrentState() }) w.Bind("saveBingAutoRefresh", func(val bool) string { cfg := loadConfig() cfg.BingAutoRefresh = val saveConfig(cfg) return "" }) w.Bind("getBingFavorites", func() string { return bingFavoritesJSON() }) w.Bind("bingSetByIdx", func(idx int) string { return bingSetByIdx(idx) }) w.Bind("bingThumbDataURI", func(filename string) string { return bingThumbDataURI(filename) }) w.Bind("pickSolidColor", func() string { hwnd := uintptr(w.Window()) color := colorPickerDialog(hwnd, "") if color == "" { return "" } cfg := loadConfig() cfg.WallpaperType = WPColor cfg.Color1 = color cfg.ColorGradient = false saveConfig(cfg) reloadWallpaper() return color }) w.Bind("pickGradientColor", func() string { hwnd := uintptr(w.Window()) c1 := colorPickerDialog(hwnd, "") if c1 == "" { return "" } c2 := colorPickerDialog(hwnd, "") if c2 == "" { c2 = "#16213e" } cfg := loadConfig() cfg.WallpaperType = WPColor cfg.Color1 = c1 cfg.Color2 = c2 cfg.ColorGradient = true saveConfig(cfg) reloadWallpaper() return c1 + "," + c2 }) w.Bind("addSavedColor", func(c1, c2 string, gradient bool) string { cfg := loadConfig() cfg.SavedColors = append(cfg.SavedColors, SavedColor{Color1: c1, Color2: c2, Gradient: gradient}) saveConfig(cfg) return "" }) w.Bind("removeSavedColor", func(idx int) string { cfg := loadConfig() if idx >= 0 && idx < len(cfg.SavedColors) { cfg.SavedColors = append(cfg.SavedColors[:idx], cfg.SavedColors[idx+1:]...) saveConfig(cfg) } return "" }) w.Bind("applySavedColor", func(idx int) string { cfg := loadConfig() if idx >= 0 && idx < len(cfg.SavedColors) { sc := cfg.SavedColors[idx] cfg.WallpaperType = WPColor cfg.Color1 = sc.Color1 cfg.Color2 = sc.Color2 cfg.ColorGradient = sc.Gradient saveConfig(cfg) reloadWallpaper() } return "" }) w.Bind("saveWallpaperText", func(text string) string { cfg := loadConfig() cfg.WallpaperText = text saveConfig(cfg) reloadWallpaper() return "" }) w.Bind("saveKnowledgeKeyword", func(keyword string) string { cfg := loadConfig() cfg.KnowledgeKeyword = keyword saveConfig(cfg) if keyword != "" { triggerKnowledgeRefresh() } else { pushKnowledgePlaceholder() } return "" }) w.Bind("saveKnowledgePrompt", func(prompt string) string { cfg := loadConfig() cfg.KnowledgePrompt = prompt saveConfig(cfg) return "" }) w.SetHtml(settingsHTML) hwnd := uintptr(w.Window()) // disable resize style, _, _ := procGetWindowLongPtrW.Call(hwnd, uintptr(0xFFFFFFF0)) procSetWindowLongPtrW.Call(hwnd, uintptr(0xFFFFFFF0), style & ^uintptr(0x00040000|0x00010000)) // resizeToFit: JS measures content, Go adjusts window frame w.Bind("resizeToFit", func(contentW, contentH int) string { type rect struct{ Left, Top, Right, Bottom int32 } var wr, cr rect procGetWindowRect.Call(hwnd, uintptr(unsafe.Pointer(&wr))) procGetClientRect.Call(hwnd, uintptr(unsafe.Pointer(&cr))) frameW := int(wr.Right-wr.Left) - int(cr.Right-cr.Left) frameH := int(wr.Bottom-wr.Top) - int(cr.Bottom-cr.Top) winW := contentW + frameW winH := contentH + frameH screenW, screenH := getScreenSize() if winH > int(screenH)-60 { winH = int(screenH) - 60 } if winW > int(screenW)-60 { winW = int(screenW) - 60 } x := (int(screenW) - winW) / 2 y := (int(screenH) - winH) / 2 procMoveWindow.Call(hwnd, uintptr(x), uintptr(y), uintptr(winW), uintptr(winH), 1) return "" }) settingsMu.Lock() settingsHwnd = hwnd settingsMu.Unlock() log.Println("设置窗口已打开") w.Run() log.Println("设置窗口已关闭") }() }