342 lines
8.1 KiB
Go
342 lines
8.1 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/lxn/walk"
|
|
)
|
|
|
|
const appName = "GoWallpaper"
|
|
|
|
type App struct {
|
|
mw *walk.MainWindow
|
|
notifyIcon *walk.NotifyIcon
|
|
configPath string
|
|
cacheDir string
|
|
config Config
|
|
cache *CacheIndex
|
|
downloader *Downloader
|
|
autoAction *walk.Action
|
|
refreshAction *walk.Action
|
|
previousAction *walk.Action
|
|
nextAction *walk.Action
|
|
settingsAction *walk.Action
|
|
timerMu sync.Mutex
|
|
timer *time.Timer
|
|
busy atomic.Bool
|
|
closing atomic.Bool
|
|
}
|
|
|
|
func newApp(mw *walk.MainWindow, appDir string, cfg Config, cache *CacheIndex) (*App, error) {
|
|
ni, err := walk.NewNotifyIcon(mw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
icon, err := walk.NewIconFromSysDLL("shell32.dll", 4)
|
|
if err != nil {
|
|
ni.Dispose()
|
|
return nil, fmt.Errorf("加载托盘图标: %w", err)
|
|
}
|
|
if err := ni.SetIcon(icon); err != nil {
|
|
ni.Dispose()
|
|
return nil, err
|
|
}
|
|
if err := ni.SetToolTip(appName + " - 右键打开菜单"); err != nil {
|
|
ni.Dispose()
|
|
return nil, err
|
|
}
|
|
|
|
a := &App{
|
|
mw: mw,
|
|
notifyIcon: ni,
|
|
configPath: filepath.Join(appDir, configFileName),
|
|
cacheDir: filepath.Join(appDir, "cache"),
|
|
config: cfg,
|
|
cache: cache,
|
|
downloader: newDownloader(),
|
|
}
|
|
if err := a.createMenu(); err != nil {
|
|
ni.Dispose()
|
|
return nil, err
|
|
}
|
|
if err := ni.SetVisible(true); err != nil {
|
|
ni.Dispose()
|
|
return nil, err
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func (a *App) createMenu() error {
|
|
var err error
|
|
if a.refreshAction, err = a.addAction("刷新壁纸", func() { a.refresh(false) }); err != nil {
|
|
return err
|
|
}
|
|
if a.previousAction, err = a.addAction("上一张", func() { a.moveHistory(-1) }); err != nil {
|
|
return err
|
|
}
|
|
if a.nextAction, err = a.addAction("下一张", func() { a.moveHistory(1) }); err != nil {
|
|
return err
|
|
}
|
|
if err := a.notifyIcon.ContextMenu().Actions().Add(walk.NewSeparatorAction()); err != nil {
|
|
return err
|
|
}
|
|
if a.autoAction, err = a.addAction("自动切换壁纸", a.toggleAutoChange); err != nil {
|
|
return err
|
|
}
|
|
if err := a.autoAction.SetCheckable(true); err != nil {
|
|
return err
|
|
}
|
|
if err := a.autoAction.SetChecked(a.config.AutoChangeEnabled); err != nil {
|
|
return err
|
|
}
|
|
if a.settingsAction, err = a.addAction("设置...", a.showSettings); err != nil {
|
|
return err
|
|
}
|
|
if _, err = a.addAction("打开缓存目录", a.openCacheDirectory); err != nil {
|
|
return err
|
|
}
|
|
if err := a.notifyIcon.ContextMenu().Actions().Add(walk.NewSeparatorAction()); err != nil {
|
|
return err
|
|
}
|
|
if _, err = a.addAction("关于", func() {
|
|
walk.MsgBox(a.mw, "关于 GoWallpaper", "GoWallpaper\n\n通过随机图片 HTTP API 手动或定时更换 Windows 壁纸。", walk.MsgBoxIconInformation)
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
_, err = a.addAction("退出", a.close)
|
|
return err
|
|
}
|
|
|
|
func (a *App) addAction(text string, handler func()) (*walk.Action, error) {
|
|
action := walk.NewAction()
|
|
if err := action.SetText(text); err != nil {
|
|
return nil, err
|
|
}
|
|
action.Triggered().Attach(handler)
|
|
if err := a.notifyIcon.ContextMenu().Actions().Add(action); err != nil {
|
|
return nil, err
|
|
}
|
|
return action, nil
|
|
}
|
|
|
|
func (a *App) start(firstRun bool) {
|
|
a.scheduleTimer()
|
|
_ = a.notifyIcon.ShowInfo(appName, "软件已经运行,可右键托盘图标进行操作。")
|
|
if firstRun || a.config.APIURL == "" {
|
|
time.AfterFunc(300*time.Millisecond, func() {
|
|
a.mw.Synchronize(func() { a.showSettings() })
|
|
})
|
|
}
|
|
}
|
|
|
|
func (a *App) close() {
|
|
if !a.closing.CompareAndSwap(false, true) {
|
|
return
|
|
}
|
|
a.stopTimer()
|
|
a.notifyIcon.SetVisible(false)
|
|
a.notifyIcon.Dispose()
|
|
walk.App().Exit(0)
|
|
}
|
|
|
|
func (a *App) setBusy(busy bool) {
|
|
a.refreshAction.SetEnabled(!busy)
|
|
a.previousAction.SetEnabled(!busy)
|
|
a.nextAction.SetEnabled(!busy)
|
|
a.settingsAction.SetEnabled(!busy)
|
|
}
|
|
|
|
func (a *App) refresh(automatic bool) {
|
|
if err := a.config.validate(true); err != nil {
|
|
if !automatic {
|
|
a.notifyWarning(err.Error())
|
|
a.showSettings()
|
|
} else {
|
|
a.scheduleTimer()
|
|
}
|
|
return
|
|
}
|
|
if !a.busy.CompareAndSwap(false, true) {
|
|
if !automatic {
|
|
a.notifyWarning("正在刷新壁纸,请稍候。")
|
|
} else {
|
|
a.scheduleTimer()
|
|
}
|
|
return
|
|
}
|
|
a.setBusy(true)
|
|
cfg := a.config
|
|
go func() {
|
|
path := uniqueCachePath(a.cacheDir)
|
|
wallpaperSet := false
|
|
err := a.downloader.download(context.Background(), cfg.APIURL, path)
|
|
if err == nil {
|
|
err = setWallpaper(path, cfg.WallpaperStyle)
|
|
wallpaperSet = err == nil
|
|
}
|
|
if err == nil {
|
|
err = a.cache.add(path, cfg.MaxCacheCount)
|
|
}
|
|
if err != nil && !wallpaperSet {
|
|
_ = os.Remove(path)
|
|
}
|
|
a.mw.Synchronize(func() {
|
|
a.busy.Store(false)
|
|
a.setBusy(false)
|
|
if err != nil {
|
|
a.notifyError("刷新壁纸失败:" + err.Error())
|
|
} else {
|
|
_ = a.notifyIcon.ShowInfo(appName, "壁纸已刷新。")
|
|
}
|
|
if automatic {
|
|
a.scheduleTimer()
|
|
}
|
|
})
|
|
}()
|
|
}
|
|
|
|
func (a *App) moveHistory(direction int) {
|
|
oldCurrent := a.cache.Current
|
|
var path string
|
|
var moved bool
|
|
var err error
|
|
if direction < 0 {
|
|
path, moved, err = a.cache.previous()
|
|
} else {
|
|
path, moved, err = a.cache.next()
|
|
}
|
|
if err != nil {
|
|
a.notifyError(err.Error())
|
|
return
|
|
}
|
|
if !moved {
|
|
if direction < 0 {
|
|
a.notifyWarning("已经是缓存中最旧的壁纸。")
|
|
} else {
|
|
a.notifyWarning("已经是缓存中最新的壁纸。")
|
|
}
|
|
return
|
|
}
|
|
if err := setWallpaper(path, a.config.WallpaperStyle); err != nil {
|
|
a.cache.Current = oldCurrent
|
|
_ = a.cache.save()
|
|
a.notifyError("切换壁纸失败:" + err.Error())
|
|
return
|
|
}
|
|
_ = a.notifyIcon.ShowInfo(appName, "已切换到缓存壁纸。")
|
|
}
|
|
|
|
func (a *App) toggleAutoChange() {
|
|
old := a.config.AutoChangeEnabled
|
|
a.config.AutoChangeEnabled = !old
|
|
if err := saveConfig(a.configPath, a.config); err != nil {
|
|
a.config.AutoChangeEnabled = old
|
|
_ = a.autoAction.SetChecked(old)
|
|
a.notifyError(err.Error())
|
|
return
|
|
}
|
|
_ = a.autoAction.SetChecked(a.config.AutoChangeEnabled)
|
|
a.scheduleTimer()
|
|
if a.config.AutoChangeEnabled {
|
|
a.notifyInfo(fmt.Sprintf("自动切换已开启,每 %d 分钟刷新一次。", a.config.IntervalMinutes))
|
|
} else {
|
|
a.notifyInfo("自动切换已关闭。")
|
|
}
|
|
}
|
|
|
|
func (a *App) scheduleTimer() {
|
|
a.timerMu.Lock()
|
|
defer a.timerMu.Unlock()
|
|
if a.timer != nil {
|
|
a.timer.Stop()
|
|
a.timer = nil
|
|
}
|
|
if a.config.AutoChangeEnabled && !a.closing.Load() {
|
|
duration := time.Duration(a.config.IntervalMinutes) * time.Minute
|
|
a.timer = time.AfterFunc(duration, func() {
|
|
if !a.closing.Load() {
|
|
a.mw.Synchronize(func() { a.refresh(true) })
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (a *App) stopTimer() {
|
|
a.timerMu.Lock()
|
|
defer a.timerMu.Unlock()
|
|
if a.timer != nil {
|
|
a.timer.Stop()
|
|
a.timer = nil
|
|
}
|
|
}
|
|
|
|
func (a *App) openCacheDirectory() {
|
|
if err := exec.Command("explorer.exe", a.cacheDir).Start(); err != nil {
|
|
a.notifyError("无法打开缓存目录:" + err.Error())
|
|
}
|
|
}
|
|
|
|
func (a *App) applyConfig(cfg Config) error {
|
|
if err := cfg.validate(false); err != nil {
|
|
return err
|
|
}
|
|
old := a.config
|
|
if err := setStartWithWindows(cfg.StartWithWindows); err != nil {
|
|
return err
|
|
}
|
|
if err := saveConfig(a.configPath, cfg); err != nil {
|
|
_ = setStartWithWindows(old.StartWithWindows)
|
|
return err
|
|
}
|
|
a.config = cfg
|
|
if err := a.cache.trim(cfg.MaxCacheCount); err != nil {
|
|
a.config = old
|
|
_ = saveConfig(a.configPath, old)
|
|
_ = setStartWithWindows(old.StartWithWindows)
|
|
return err
|
|
}
|
|
_ = a.autoAction.SetChecked(cfg.AutoChangeEnabled)
|
|
a.scheduleTimer()
|
|
return nil
|
|
}
|
|
|
|
func (a *App) notifyInfo(message string) {
|
|
_ = a.notifyIcon.ShowInfo(appName, message)
|
|
}
|
|
|
|
func (a *App) notifyWarning(message string) {
|
|
_ = a.notifyIcon.ShowWarning(appName, message)
|
|
}
|
|
|
|
func (a *App) notifyError(message string) {
|
|
_ = a.notifyIcon.ShowError(appName, message)
|
|
}
|
|
|
|
func appDirectory() (string, error) {
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
dir := filepath.Dir(exe)
|
|
probe, err := os.CreateTemp(dir, ".gowallpaper-write-test-*")
|
|
if err != nil {
|
|
return "", fmt.Errorf("程序目录不可写:%w", err)
|
|
}
|
|
name := probe.Name()
|
|
probe.Close()
|
|
if err := os.Remove(name); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
return "", err
|
|
}
|
|
return dir, nil
|
|
}
|