414 lines
8.6 KiB
Go
414 lines
8.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strconv"
|
|
"sync/atomic"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/getlantern/systray"
|
|
"github.com/jchv/go-webview2"
|
|
)
|
|
|
|
var zodiacItems []*systray.MenuItem
|
|
var cityItems []*systray.MenuItem
|
|
var themeItems []*systray.MenuItem
|
|
|
|
var themeNames = []struct {
|
|
Name ThemeName
|
|
Label string
|
|
}{
|
|
{ThemeAurora, "极光"},
|
|
{ThemeStar, "星空"},
|
|
{ThemeGradient, "渐变"},
|
|
{ThemeParticle, "粒子"},
|
|
{ThemeFractal, "极光流体"},
|
|
{ThemeText, "文字"},
|
|
}
|
|
|
|
func onSystrayReady() {
|
|
systray.SetIcon(generateIcon())
|
|
systray.SetTooltip("动态壁纸引擎")
|
|
|
|
cfg := loadConfig()
|
|
|
|
mPause := systray.AddMenuItem("暂停", "暂停/继续")
|
|
systray.AddSeparator()
|
|
|
|
// 壁纸主题
|
|
mTheme := systray.AddMenuItem("壁纸主题", "")
|
|
for _, t := range themeNames {
|
|
item := mTheme.AddSubMenuItem(t.Label, t.Label)
|
|
if cfg.WallpaperType == WPTheme && cfg.Theme == t.Name {
|
|
item.Check()
|
|
}
|
|
themeItems = append(themeItems, item)
|
|
}
|
|
mLocalImage := systray.AddMenuItem("本地图片", "选择本地图片作为壁纸")
|
|
mBingMenu := systray.AddMenuItem("Bing 每日壁纸", "")
|
|
mBingEnable := mBingMenu.AddSubMenuItem("启用 Bing 壁纸", "")
|
|
mBingPrev := mBingMenu.AddSubMenuItem("◀ 上一个", "")
|
|
mBingNext := mBingMenu.AddSubMenuItem("下一个 ▶", "")
|
|
mBingFav := mBingMenu.AddSubMenuItem("★ 收藏当前壁纸", "")
|
|
mSolidColor := systray.AddMenuItem("纯色壁纸", "选择纯色壁纸")
|
|
mGradientColor := systray.AddMenuItem("渐变壁纸", "选择渐变壁纸")
|
|
|
|
systray.AddSeparator()
|
|
|
|
// 星座
|
|
mZodiac := systray.AddMenuItem("星座设置", "")
|
|
zodiacs := []string{
|
|
"白羊座", "金牛座", "双子座",
|
|
"巨蟹座", "狮子座", "处女座",
|
|
"天秤座", "天蝎座", "射手座",
|
|
"摩羯座", "水瓶座", "双鱼座",
|
|
}
|
|
for _, z := range zodiacs {
|
|
item := mZodiac.AddSubMenuItem(z, z)
|
|
if z == cfg.Zodiac {
|
|
item.Check()
|
|
}
|
|
zodiacItems = append(zodiacItems, item)
|
|
}
|
|
|
|
systray.AddSeparator()
|
|
|
|
// 城市
|
|
mCity := systray.AddMenuItem("城市设置", "")
|
|
for _, c := range cities {
|
|
item := mCity.AddSubMenuItem(c.Name, c.Adm1+" "+c.Name)
|
|
if cfg.City == c.ID {
|
|
item.Check()
|
|
}
|
|
cityItems = append(cityItems, item)
|
|
}
|
|
|
|
systray.AddSeparator()
|
|
mRestart := systray.AddMenuItem("重启", "重启程序")
|
|
mQuit := systray.AddMenuItem("退出", "退出程序")
|
|
|
|
// 主题切换监听
|
|
for i, item := range themeItems {
|
|
go func(idx int, mi *systray.MenuItem) {
|
|
for {
|
|
<-mi.ClickedCh
|
|
cfg := loadConfig()
|
|
cfg.WallpaperType = WPTheme
|
|
cfg.Theme = themeNames[idx].Name
|
|
saveConfig(cfg)
|
|
for _, it := range themeItems {
|
|
it.Uncheck()
|
|
}
|
|
mi.Check()
|
|
log.Printf("主题切换: %s", themeNames[idx].Label)
|
|
reloadWallpaper()
|
|
}
|
|
}(i, item)
|
|
}
|
|
|
|
// 本地图片
|
|
go func() {
|
|
for {
|
|
<-mLocalImage.ClickedCh
|
|
path := openFileDialog(wvHwnd)
|
|
if path == "" {
|
|
continue
|
|
}
|
|
cfg := loadConfig()
|
|
cfg.WallpaperType = WPImage
|
|
cfg.ImagePath = path
|
|
saveConfig(cfg)
|
|
for _, it := range themeItems {
|
|
it.Uncheck()
|
|
}
|
|
log.Printf("本地图片: %s", path)
|
|
reloadWallpaper()
|
|
}
|
|
}()
|
|
|
|
// Bing 启用
|
|
go func() {
|
|
for {
|
|
<-mBingEnable.ClickedCh
|
|
cfg := loadConfig()
|
|
cfg.WallpaperType = WPBing
|
|
saveConfig(cfg)
|
|
for _, it := range themeItems {
|
|
it.Uncheck()
|
|
}
|
|
log.Println("切换 Bing 壁纸")
|
|
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)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// 纯色壁纸
|
|
go func() {
|
|
for {
|
|
<-mSolidColor.ClickedCh
|
|
color := colorPickerDialog(wvHwnd, "")
|
|
if color == "" {
|
|
continue
|
|
}
|
|
cfg := loadConfig()
|
|
cfg.WallpaperType = WPColor
|
|
cfg.Color1 = color
|
|
cfg.ColorGradient = false
|
|
saveConfig(cfg)
|
|
for _, it := range themeItems {
|
|
it.Uncheck()
|
|
}
|
|
log.Printf("纯色壁纸: %s", color)
|
|
reloadWallpaper()
|
|
}
|
|
}()
|
|
|
|
// 渐变壁纸
|
|
go func() {
|
|
for {
|
|
<-mGradientColor.ClickedCh
|
|
c1 := colorPickerDialog(wvHwnd, "")
|
|
if c1 == "" {
|
|
continue
|
|
}
|
|
c2 := colorPickerDialog(wvHwnd, "")
|
|
if c2 == "" {
|
|
c2 = "#16213e"
|
|
}
|
|
cfg := loadConfig()
|
|
cfg.WallpaperType = WPColor
|
|
cfg.Color1 = c1
|
|
cfg.Color2 = c2
|
|
cfg.ColorGradient = true
|
|
saveConfig(cfg)
|
|
for _, it := range themeItems {
|
|
it.Uncheck()
|
|
}
|
|
log.Printf("渐变壁纸: %s -> %s", c1, c2)
|
|
reloadWallpaper()
|
|
}
|
|
}()
|
|
|
|
// 星座选择监听
|
|
for i, item := range zodiacItems {
|
|
go func(idx int, mi *systray.MenuItem) {
|
|
name := zodiacs[idx]
|
|
for {
|
|
<-mi.ClickedCh
|
|
cfg := loadConfig()
|
|
cfg.Zodiac = name
|
|
saveConfig(cfg)
|
|
for _, it := range zodiacItems {
|
|
it.Uncheck()
|
|
}
|
|
mi.Check()
|
|
evalJS(fmt.Sprintf(`window.userZodiac = %q; if(window.updateTime) updateTime();`, name))
|
|
}
|
|
}(i, item)
|
|
}
|
|
|
|
// 城市选择监听
|
|
for i, item := range cityItems {
|
|
go func(idx int, mi *systray.MenuItem) {
|
|
for {
|
|
<-mi.ClickedCh
|
|
city := cities[idx]
|
|
cfg := loadConfig()
|
|
cfg.City = city.ID
|
|
saveConfig(cfg)
|
|
for _, it := range cityItems {
|
|
it.Uncheck()
|
|
}
|
|
mi.Check()
|
|
go fetchAndPushWeather(city)
|
|
}
|
|
}(i, item)
|
|
}
|
|
|
|
// 暂停
|
|
go func() {
|
|
for {
|
|
<-mPause.ClickedCh
|
|
newVal := 1 - atomic.LoadInt32(&paused)
|
|
atomic.StoreInt32(&paused, newVal)
|
|
isPaused := newVal == 1
|
|
if isPaused {
|
|
mPause.SetTitle("继续")
|
|
} else {
|
|
mPause.SetTitle("暂停")
|
|
}
|
|
evalJS("if(window.setPaused) setPaused(" + strconv.FormatBool(isPaused) + ")")
|
|
}
|
|
}()
|
|
|
|
// 退出
|
|
go func() {
|
|
<-mQuit.ClickedCh
|
|
os.Exit(0)
|
|
}()
|
|
|
|
// 重启
|
|
go func() {
|
|
for {
|
|
<-mRestart.ClickedCh
|
|
exe, _ := os.Executable()
|
|
exec.Command(exe).Start()
|
|
os.Exit(0)
|
|
}
|
|
}()
|
|
|
|
go startWebView()
|
|
go weatherLoop()
|
|
go bingWallpaperLoop()
|
|
}
|
|
|
|
func startWebView() {
|
|
runtime.LockOSThread()
|
|
|
|
workerw := findWorkerW()
|
|
if workerw == 0 {
|
|
log.Fatal("WorkerW not found")
|
|
}
|
|
|
|
screenW, screenH := getScreenSize()
|
|
log.Printf("Screen: %dx%d", screenW, screenH)
|
|
|
|
wv = webview2.NewWithOptions(webview2.WebViewOptions{
|
|
AutoFocus: false,
|
|
WindowOptions: webview2.WindowOptions{
|
|
Title: "动态壁纸",
|
|
Width: uint(screenW),
|
|
Height: uint(screenH),
|
|
},
|
|
})
|
|
if wv == nil {
|
|
log.Fatal("WebView2 create failed")
|
|
}
|
|
|
|
wv.Bind("setZodiacFromGo", func(zodiac string) error {
|
|
cfg := loadConfig()
|
|
cfg.Zodiac = zodiac
|
|
return saveConfig(cfg)
|
|
})
|
|
|
|
wv.SetHtml(buildWallpaperHTML(loadConfig()))
|
|
time.Sleep(1 * time.Second)
|
|
|
|
wvHwnd = uintptr(wv.Window())
|
|
procSetWindowLongPtrW.Call(wvHwnd, uintptr(0xFFFFFFF0), uintptr(0x80000000|0x10000000|0x02000000))
|
|
procShowWindow.Call(wvHwnd, 5)
|
|
procMoveWindow.Call(wvHwnd, uintptr(^uint(0)), uintptr(^uint(0)), uintptr(screenW+2), uintptr(screenH+2), 1)
|
|
log.Printf("壁纸已嵌入: HWND=0x%x, %dx%d", wvHwnd, screenW, screenH)
|
|
|
|
go func() {
|
|
time.Sleep(500 * time.Millisecond)
|
|
cfg := loadConfig()
|
|
evalJS(fmt.Sprintf(`window.userZodiac = %q;`, cfg.Zodiac))
|
|
}()
|
|
|
|
go fullscreenMonitor()
|
|
|
|
go func() {
|
|
time.Sleep(3 * time.Second)
|
|
workerw := findWorkerW()
|
|
if workerw != 0 {
|
|
oldParent, _, _ := procSetParent.Call(wvHwnd, workerw)
|
|
log.Printf("SetParent: 0x%x -> 0x%x (old=0x%x)", wvHwnd, workerw, oldParent)
|
|
procMoveWindow.Call(wvHwnd, uintptr(^uint(0)), uintptr(^uint(0)), uintptr(screenW+2), uintptr(screenH+2), 1)
|
|
}
|
|
}()
|
|
|
|
type msg struct {
|
|
hwnd uintptr
|
|
message uint32
|
|
wParam uintptr
|
|
lParam uintptr
|
|
time uint32
|
|
pt struct{ x, y int32 }
|
|
}
|
|
var m msg
|
|
for {
|
|
ret, _, _ := procGetMessageW.Call(
|
|
uintptr(unsafe.Pointer(&m)),
|
|
0, 0, 0,
|
|
)
|
|
if ret == 0 {
|
|
break
|
|
}
|
|
if m.message == wmEvalJS {
|
|
for {
|
|
select {
|
|
case js := <-jsQueue:
|
|
wv.Eval(js)
|
|
default:
|
|
goto nextMsg
|
|
}
|
|
}
|
|
}
|
|
if m.message == wmSetHtml {
|
|
select {
|
|
case html := <-htmlQueue:
|
|
wv.SetHtml(html)
|
|
default:
|
|
}
|
|
goto nextMsg
|
|
}
|
|
nextMsg:
|
|
procTranslateMessage.Call(uintptr(unsafe.Pointer(&m)))
|
|
procDispatchMessageW.Call(uintptr(unsafe.Pointer(&m)))
|
|
}
|
|
}
|
|
|
|
func fullscreenMonitor() {
|
|
type rect struct{ Left, Top, Right, Bottom int32 }
|
|
var lastState string
|
|
for {
|
|
if atomic.LoadInt32(&paused) == 0 && wv != nil {
|
|
fg, _, _ := procGetForegroundWindow.Call()
|
|
if fg != 0 {
|
|
var r rect
|
|
procGetWindowRect.Call(fg, uintptr(unsafe.Pointer(&r)))
|
|
screenW, screenH := getScreenSize()
|
|
isFull := (r.Right-r.Left >= screenW) && (r.Bottom-r.Top >= screenH)
|
|
state := strconv.FormatBool(isFull)
|
|
if state != lastState {
|
|
lastState = state
|
|
evalJS("if(window.setFullscreen) setFullscreen(" + state + ")")
|
|
}
|
|
}
|
|
}
|
|
time.Sleep(2 * time.Second)
|
|
}
|
|
}
|