重构: 拆分main.go为5个文件 + 壁纸HTML移至web目录
- main.go: 入口+单实例互斥锁 - win32.go: Win32 API/embed/evalJS/全局变量 - config.go: 配置读写+图标生成 - weather.go: 天气API/定位/天气循环 - systray.go: 托盘菜单/WebView/全屏监控 - wallpaper.html → web/wallpaper.html
This commit is contained in:
244
systray.go
Normal file
244
systray.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/getlantern/systray"
|
||||
"github.com/jchv/go-webview2"
|
||||
)
|
||||
|
||||
var zodiacItems []*systray.MenuItem
|
||||
var cityItems []*systray.MenuItem
|
||||
|
||||
func onSystrayReady() {
|
||||
systray.SetIcon(generateIcon())
|
||||
systray.SetTooltip("动态壁纸引擎")
|
||||
|
||||
mPause := systray.AddMenuItem("暂停", "暂停/继续")
|
||||
systray.AddSeparator()
|
||||
|
||||
mZodiac := systray.AddMenuItem("星座设置", "")
|
||||
zodiacs := []string{
|
||||
"白羊座", "金牛座", "双子座",
|
||||
"巨蟹座", "狮子座", "处女座",
|
||||
"天秤座", "天蝎座", "射手座",
|
||||
"摩羯座", "水瓶座", "双鱼座",
|
||||
}
|
||||
cfg := loadConfig()
|
||||
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()
|
||||
mQuit := systray.AddMenuItem("退出", "退出程序")
|
||||
|
||||
// 星座选择监听
|
||||
for i, item := range zodiacItems {
|
||||
go func(idx int, mi *systray.MenuItem) {
|
||||
name := zodiacs[idx]
|
||||
log.Printf("星座监听启动: %s", name)
|
||||
for {
|
||||
<-mi.ClickedCh
|
||||
log.Printf("星座点击: %s", name)
|
||||
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) {
|
||||
log.Printf("城市监听启动: %s", cities[idx].Name)
|
||||
for {
|
||||
<-mi.ClickedCh
|
||||
city := cities[idx]
|
||||
log.Printf("城市点击: %s", city.Name)
|
||||
cfg := loadConfig()
|
||||
cfg.City = city.ID
|
||||
saveConfig(cfg)
|
||||
for _, it := range cityItems {
|
||||
it.Uncheck()
|
||||
}
|
||||
mi.Check()
|
||||
go fetchAndPushWeather(city)
|
||||
}
|
||||
}(i, item)
|
||||
}
|
||||
|
||||
// 暂停
|
||||
go func() {
|
||||
log.Println("暂停监听启动")
|
||||
for {
|
||||
<-mPause.ClickedCh
|
||||
newVal := 1 - atomic.LoadInt32(&paused)
|
||||
atomic.StoreInt32(&paused, newVal)
|
||||
isPaused := newVal == 1
|
||||
log.Printf("暂停切换: paused=%v", isPaused)
|
||||
if isPaused {
|
||||
mPause.SetTitle("继续")
|
||||
} else {
|
||||
mPause.SetTitle("暂停")
|
||||
}
|
||||
evalJS("if(window.setPaused) setPaused(" + strconv.FormatBool(isPaused) + ")")
|
||||
}
|
||||
}()
|
||||
|
||||
// 退出
|
||||
go func() {
|
||||
<-mQuit.ClickedCh
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
go startWebView()
|
||||
go weatherLoop()
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
htmlData, err := fs.ReadFile("web/wallpaper.html")
|
||||
if err != nil {
|
||||
log.Fatal("读取 wallpaper.html 失败:", err)
|
||||
}
|
||||
|
||||
wv.Bind("setZodiacFromGo", func(zodiac string) error {
|
||||
cfg := loadConfig()
|
||||
cfg.Zodiac = zodiac
|
||||
return saveConfig(cfg)
|
||||
})
|
||||
|
||||
wv.SetHtml(string(htmlData))
|
||||
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))
|
||||
log.Printf("配置已注入: zodiac=%s", cfg.Zodiac)
|
||||
}()
|
||||
|
||||
go fullscreenMonitor()
|
||||
|
||||
// 延迟嵌入 WorkerW
|
||||
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
|
||||
log.Println("启动自定义消息循环...")
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user