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