- 星座运势: 天聚数行API集成,5维进度条+幸运标签+今日概述 - AI资讯: 天聚数行API,图文布局5条展示,文件缓存2小时刷新 - 知识卡片: AI生成,关键字+提示词配置,30分钟刷新 - 桌面设置: 独立WebView2窗口,760x1350,含壁纸/布局/城市/颜色等配置 - 显示控制: 壁纸/时间/天气/星座/知识/AI资讯独立开关,秒显示开关 - 文件缓存: 星座运势+AI资讯缓存到本地,启动即显示上次数据 - initDone防抖: 防止设置窗口初始化触发卡片重载
406 lines
10 KiB
Go
406 lines
10 KiB
Go
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("设置窗口已关闭")
|
|
}()
|
|
}
|