diff --git a/bing.go b/bing.go
index b1ba3a4..ae09dba 100644
--- a/bing.go
+++ b/bing.go
@@ -2,25 +2,71 @@ package main
import (
"encoding/json"
+ "fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
+ "sync"
"time"
)
-const bingAPI = "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN"
+const bingAPI = "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=zh-CN"
type bingResponse struct {
Images []struct {
+ StartDate string `json:"startdate"`
URL string `json:"url"`
URLBase string `json:"urlbase"`
Copyright string `json:"copyright"`
} `json:"images"`
}
-func fetchBingWallpaper() {
+type BingRecord struct {
+ Date string `json:"date"`
+ URLBase string `json:"urlbase"`
+ Copyright string `json:"copyright"`
+ Filename string `json:"filename"`
+ Favorited bool `json:"favorited"`
+}
+
+type BingHistory struct {
+ Records []BingRecord `json:"records"`
+ CurrentIdx int `json:"currentIdx"`
+}
+
+var bingMu sync.Mutex
+
+func bingDir() string {
+ return filepath.Join(configDir(), "bing")
+}
+
+func bingHistoryPath() string {
+ return filepath.Join(configDir(), "bing_history.json")
+}
+
+func loadBingHistory() *BingHistory {
+ data, err := os.ReadFile(bingHistoryPath())
+ if err != nil {
+ return &BingHistory{}
+ }
+ var h BingHistory
+ if json.Unmarshal(data, &h) != nil {
+ return &BingHistory{}
+ }
+ return &h
+}
+
+func saveBingHistory(h *BingHistory) error {
+ data, _ := json.MarshalIndent(h, "", " ")
+ return os.WriteFile(bingHistoryPath(), data, 0644)
+}
+
+func fetchBingHistory() {
+ bingMu.Lock()
+ defer bingMu.Unlock()
+
resp, err := httpClient.Get(bingAPI)
if err != nil {
log.Println("Bing API 请求失败:", err)
@@ -37,38 +83,160 @@ func fetchBingWallpaper() {
return
}
- imgURL := br.Images[0].URL
- if !strings.HasPrefix(imgURL, "http") {
- imgURL = "https://www.bing.com" + imgURL
+ os.MkdirAll(bingDir(), 0755)
+
+ existing := loadBingHistory()
+ existingMap := make(map[string]BingRecord)
+ for _, r := range existing.Records {
+ existingMap[r.Date] = r
}
- imgResp, err := httpClient.Get(imgURL)
- if err != nil {
- log.Println("Bing 图片下载失败:", err)
- return
+ for _, img := range br.Images {
+ date := img.StartDate
+ if date == "" {
+ continue
+ }
+ if _, exists := existingMap[date]; exists {
+ continue
+ }
+
+ imgURL := img.URL
+ if !strings.HasPrefix(imgURL, "http") {
+ imgURL = "https://www.bing.com" + imgURL
+ }
+
+ imgResp, err := httpClient.Get(imgURL)
+ if err != nil {
+ log.Printf("Bing 图片下载失败 (%s): %v", date, err)
+ continue
+ }
+ imgData, _ := io.ReadAll(imgResp.Body)
+ imgResp.Body.Close()
+ if len(imgData) == 0 {
+ continue
+ }
+
+ filename := date + ".jpg"
+ localPath := filepath.Join(bingDir(), filename)
+ if err := os.WriteFile(localPath, imgData, 0644); err != nil {
+ log.Printf("Bing 图片保存失败 (%s): %v", date, err)
+ continue
+ }
+ log.Printf("Bing 壁纸已下载: %s (%d bytes)", filename, len(imgData))
+
+ existingMap[date] = BingRecord{
+ Date: date,
+ URLBase: img.URLBase,
+ Copyright: img.Copyright,
+ Filename: filename,
+ }
}
- defer imgResp.Body.Close()
- imgData, err := io.ReadAll(imgResp.Body)
- if err != nil {
+
+ var records []BingRecord
+ for _, img := range br.Images {
+ if r, ok := existingMap[img.StartDate]; ok {
+ records = append(records, r)
+ }
+ }
+ if len(records) == 0 {
return
}
- bingPath := filepath.Join(configDir(), "bing_wallpaper.jpg")
- if err := os.WriteFile(bingPath, imgData, 0644); err != nil {
- log.Println("Bing 壁纸缓存失败:", err)
- return
+ history := &BingHistory{
+ Records: records,
+ CurrentIdx: 0,
}
- log.Printf("Bing 壁纸已缓存: %s (%d bytes)", bingPath, len(imgData))
+ if existing.CurrentIdx < len(records) {
+ history.CurrentIdx = existing.CurrentIdx
+ }
+ saveBingHistory(history)
reloadWallpaper()
}
+func getCurrentBingPath() string {
+ h := loadBingHistory()
+ if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) {
+ return ""
+ }
+ return filepath.Join(bingDir(), h.Records[h.CurrentIdx].Filename)
+}
+
+func getCurrentBingRecord() *BingRecord {
+ h := loadBingHistory()
+ if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) {
+ return nil
+ }
+ return &h.Records[h.CurrentIdx]
+}
+
+func bingPrev() {
+ bingMu.Lock()
+ defer bingMu.Unlock()
+
+ h := loadBingHistory()
+ if len(h.Records) == 0 {
+ return
+ }
+ if h.CurrentIdx < len(h.Records)-1 {
+ h.CurrentIdx++
+ saveBingHistory(h)
+ log.Printf("Bing 壁纸: 上一个 (idx=%d, date=%s)", h.CurrentIdx, h.Records[h.CurrentIdx].Date)
+ reloadWallpaper()
+ }
+}
+
+func bingNext() {
+ bingMu.Lock()
+ defer bingMu.Unlock()
+
+ h := loadBingHistory()
+ if h.CurrentIdx > 0 {
+ h.CurrentIdx--
+ saveBingHistory(h)
+ log.Printf("Bing 壁纸: 下一个 (idx=%d, date=%s)", h.CurrentIdx, h.Records[h.CurrentIdx].Date)
+ reloadWallpaper()
+ }
+}
+
+func bingToggleFavorite() string {
+ bingMu.Lock()
+ defer bingMu.Unlock()
+
+ h := loadBingHistory()
+ if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) {
+ return ""
+ }
+ r := &h.Records[h.CurrentIdx]
+ r.Favorited = !r.Favorited
+ saveBingHistory(h)
+ state := "收藏"
+ if r.Favorited {
+ state = "取消收藏"
+ }
+ log.Printf("Bing 壁纸: %s (date=%s)", state, r.Date)
+ if r.Favorited {
+ return "☆ 取消收藏"
+ }
+ return "★ 收藏当前壁纸"
+}
+
+func bingCopyrightInfo() string {
+ r := getCurrentBingRecord()
+ if r != nil {
+ return fmt.Sprintf("%s (%s)", r.Copyright, r.Date)
+ }
+ return ""
+}
+
func bingWallpaperLoop() {
cfg := loadConfig()
if cfg.WallpaperType == WPBing {
- bingPath := filepath.Join(configDir(), "bing_wallpaper.jpg")
- if _, err := os.Stat(bingPath); err != nil {
- fetchBingWallpaper()
+ h := loadBingHistory()
+ if len(h.Records) == 0 {
+ fetchBingHistory()
+ } else {
+ reloadWallpaper()
}
}
@@ -76,7 +244,7 @@ func bingWallpaperLoop() {
for range ticker.C {
cfg := loadConfig()
if cfg.WallpaperType == WPBing {
- fetchBingWallpaper()
+ fetchBingHistory()
}
}
}
diff --git a/config.go b/config.go
index 79024ad..1c83b19 100644
--- a/config.go
+++ b/config.go
@@ -27,6 +27,8 @@ const (
ThemeStar ThemeName = "starfield"
ThemeGradient ThemeName = "gradient"
ThemeParticle ThemeName = "particles"
+ ThemeFractal ThemeName = "fractal"
+ ThemeText ThemeName = "text"
)
type Config struct {
@@ -38,6 +40,7 @@ type Config struct {
Color1 string `json:"color1"`
Color2 string `json:"color2"`
ColorGradient bool `json:"colorGradient"`
+ WallpaperText string `json:"wallpaperText"`
}
const defaultZodiac = "射手座"
diff --git a/systray.go b/systray.go
index c8b39cc..0ccb2b9 100644
--- a/systray.go
+++ b/systray.go
@@ -4,6 +4,7 @@ import (
"fmt"
"log"
"os"
+ "os/exec"
"runtime"
"strconv"
"sync/atomic"
@@ -26,6 +27,8 @@ var themeNames = []struct {
{ThemeStar, "星空"},
{ThemeGradient, "渐变"},
{ThemeParticle, "粒子"},
+ {ThemeFractal, "极光流体"},
+ {ThemeText, "文字"},
}
func onSystrayReady() {
@@ -47,7 +50,11 @@ func onSystrayReady() {
themeItems = append(themeItems, item)
}
mLocalImage := systray.AddMenuItem("本地图片", "选择本地图片作为壁纸")
- mBingDaily := systray.AddMenuItem("Bing 每日壁纸", "使用 Bing 每日壁纸")
+ mBingMenu := systray.AddMenuItem("Bing 每日壁纸", "")
+ mBingEnable := mBingMenu.AddSubMenuItem("启用 Bing 壁纸", "")
+ mBingPrev := mBingMenu.AddSubMenuItem("◀ 上一个", "")
+ mBingNext := mBingMenu.AddSubMenuItem("下一个 ▶", "")
+ mBingFav := mBingMenu.AddSubMenuItem("★ 收藏当前壁纸", "")
mSolidColor := systray.AddMenuItem("纯色壁纸", "选择纯色壁纸")
mGradientColor := systray.AddMenuItem("渐变壁纸", "选择渐变壁纸")
@@ -82,6 +89,7 @@ func onSystrayReady() {
}
systray.AddSeparator()
+ mRestart := systray.AddMenuItem("重启", "重启程序")
mQuit := systray.AddMenuItem("退出", "退出程序")
// 主题切换监听
@@ -123,10 +131,10 @@ func onSystrayReady() {
}
}()
- // Bing 每日
+ // Bing 启用
go func() {
for {
- <-mBingDaily.ClickedCh
+ <-mBingEnable.ClickedCh
cfg := loadConfig()
cfg.WallpaperType = WPBing
saveConfig(cfg)
@@ -134,7 +142,34 @@ func onSystrayReady() {
it.Uncheck()
}
log.Println("切换 Bing 壁纸")
- go fetchBingWallpaper()
+ go fetchBingHistory()
+ }
+ }()
+
+ // Bing 上一个
+ go func() {
+ for {
+ <-mBingPrev.ClickedCh
+ bingPrev()
+ }
+ }()
+
+ // Bing 下一个
+ go func() {
+ for {
+ <-mBingNext.ClickedCh
+ bingNext()
+ }
+ }()
+
+ // Bing 收藏
+ go func() {
+ for {
+ <-mBingFav.ClickedCh
+ title := bingToggleFavorite()
+ if title != "" {
+ mBingFav.SetTitle(title)
+ }
}
}()
@@ -243,6 +278,16 @@ func onSystrayReady() {
os.Exit(0)
}()
+ // 重启
+ go func() {
+ for {
+ <-mRestart.ClickedCh
+ exe, _ := os.Executable()
+ exec.Command(exe).Start()
+ os.Exit(0)
+ }
+ }()
+
go startWebView()
go weatherLoop()
go bingWallpaperLoop()
diff --git a/wallpaper.go b/wallpaper.go
index 1dbc59d..7073f84 100644
--- a/wallpaper.go
+++ b/wallpaper.go
@@ -26,11 +26,19 @@ var themeGradient string
//go:embed web/themes/particles.html
var themeParticles string
+//go:embed web/themes/fractal.html
+var themeFractal string
+
+//go:embed web/themes/text.html
+var themeText string
+
var themeMap = map[ThemeName]string{
ThemeAurora: themeAurora,
ThemeStar: themeStarfield,
ThemeGradient: themeGradient,
ThemeParticle: themeParticles,
+ ThemeFractal: themeFractal,
+ ThemeText: themeText,
}
func buildWallpaperHTML(cfg *Config) string {
@@ -51,11 +59,12 @@ func buildWallpaperHTML(cfg *Config) string {
}
}
case WPBing:
- bingPath := filepath.Join(configDir(), "bing_wallpaper.jpg")
- if _, err := os.Stat(bingPath); err == nil {
- src := imageToDataURI(bingPath)
- if src != "" {
- bg = fmt.Sprintf(``, src)
+ if p := getCurrentBingPath(); p != "" {
+ if _, err := os.Stat(p); err == nil {
+ src := imageToDataURI(p)
+ if src != "" {
+ bg = fmt.Sprintf(`
`, src)
+ }
}
}
case WPColor:
@@ -69,7 +78,16 @@ func buildWallpaperHTML(cfg *Config) string {
if bg == "" {
bg = themeAurora
}
- return strings.Replace(overlayHTML, "{{BACKGROUND}}", bg, 1)
+ html := strings.Replace(overlayHTML, "{{BACKGROUND}}", bg, 1)
+
+ // 注入自定义文字
+ if cfg.WallpaperType == WPTheme && cfg.Theme == ThemeText && cfg.WallpaperText != "" {
+ escaped := strings.ReplaceAll(cfg.WallpaperText, `\`, `\\`)
+ escaped = strings.ReplaceAll(escaped, `"`, `\"`)
+ html = strings.Replace(html, "", `window.wallpaperText = "`+escaped+`";`, 1)
+ }
+
+ return html
}
func imageToDataURI(path string) string {
@@ -107,6 +125,9 @@ func reloadWallpaper() {
go func() {
time.Sleep(1 * time.Second)
evalJS(fmt.Sprintf(`window.userZodiac = %q;`, cfg.Zodiac))
+ if cfg.Theme == ThemeText && cfg.WallpaperText != "" {
+ evalJS(fmt.Sprintf(`window.wallpaperText = %q; var el=document.getElementById("wallpaper-text"); if(el){el.textContent=%q;}`, cfg.WallpaperText, cfg.WallpaperText))
+ }
city := getCurrentCity()
go fetchAndPushWeather(city)
}()
diff --git a/web/overlay.html b/web/overlay.html
index 59ce403..e267da0 100644
--- a/web/overlay.html
+++ b/web/overlay.html
@@ -1,3 +1,4 @@
+