mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
318 lines
8.7 KiB
Go
318 lines
8.7 KiB
Go
package main
|
|
|
|
import (
|
|
"ant-chrome/backend"
|
|
"context"
|
|
"embed"
|
|
"encoding/json"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
goruntime "runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/wailsapp/wails/v2"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
|
"github.com/wailsapp/wails/v2/pkg/options/linux"
|
|
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
)
|
|
|
|
//go:embed all:frontend/dist
|
|
var assets embed.FS
|
|
|
|
//go:embed wails.json
|
|
var wailsConfigJSON []byte
|
|
|
|
//go:embed build/appicon.png
|
|
var linuxAppIcon []byte
|
|
|
|
// appRoot 应用根目录,所有相对路径基于此目录解析。
|
|
// 生产环境 = exe 所在目录;dev 环境 = 项目源码根目录(CWD)。
|
|
var appRoot string
|
|
|
|
// isDevMode 标识当前是否为 wails dev 模式(exe 在临时目录)
|
|
var isDevMode bool
|
|
|
|
type App struct {
|
|
*backend.App
|
|
}
|
|
|
|
type wailsBuildConfig struct {
|
|
Info struct {
|
|
ProductVersion string `json:"productVersion"`
|
|
} `json:"info"`
|
|
}
|
|
|
|
func envFlagEnabled(name string) bool {
|
|
value := strings.TrimSpace(strings.ToLower(os.Getenv(name)))
|
|
switch value {
|
|
case "1", "true", "yes", "on":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func resolveBuildVersion() string {
|
|
var cfg wailsBuildConfig
|
|
if err := json.Unmarshal(wailsConfigJSON, &cfg); err != nil {
|
|
log.Printf("解析 wails.json 版本信息失败: %v", err)
|
|
return "unknown"
|
|
}
|
|
|
|
version := strings.TrimSpace(cfg.Info.ProductVersion)
|
|
if version == "" {
|
|
log.Printf("wails.json 未配置 info.productVersion,回退为 unknown")
|
|
return "unknown"
|
|
}
|
|
|
|
return version
|
|
}
|
|
|
|
func NewApp(appRoot, version string) *App {
|
|
return &App{App: backend.NewApp(appRoot, version)}
|
|
}
|
|
|
|
func (a *App) startup(ctx context.Context) {
|
|
backend.Start(a.App, ctx)
|
|
}
|
|
|
|
func (a *App) shutdown(ctx context.Context) {
|
|
backend.Stop(a.App, ctx)
|
|
}
|
|
|
|
func (a *App) shouldBlockClose(ctx context.Context) bool {
|
|
return backend.ShouldBlockClose(a.App, ctx)
|
|
}
|
|
|
|
func (a *App) BrowserExtensionManualInstallGuide(query string) (backend.BrowserExtensionManualInstallGuide, error) {
|
|
return a.App.BrowserExtensionManualInstallGuide(query)
|
|
}
|
|
|
|
func (a *App) BrowserExtensionOpenManualDownloadDir() error {
|
|
return a.App.BrowserExtensionOpenManualDownloadDir()
|
|
}
|
|
|
|
func (a *App) BrowserExtensionListManualDownloadFiles() ([]backend.BrowserExtensionManualDownloadFile, error) {
|
|
return a.App.BrowserExtensionListManualDownloadFiles()
|
|
}
|
|
|
|
func (a *App) BrowserExtensionInstallManualDownloadFile(fileName string) (backend.BrowserExtension, error) {
|
|
return a.App.BrowserExtensionInstallManualDownloadFile(fileName)
|
|
}
|
|
|
|
func main() {
|
|
// 确定应用根目录:
|
|
// 1. 生产环境:exe 所在目录(快捷方式启动时 CWD 可能不对,需要修正)
|
|
// 2. dev 环境:wails dev 时 exe 可能在 temp 目录或 build/bin 目录,使用当前工作目录
|
|
if exePath, err := os.Executable(); err == nil {
|
|
exeDir := filepath.Dir(exePath)
|
|
tempDir := os.TempDir()
|
|
if resolved, err := filepath.EvalSymlinks(exeDir); err == nil {
|
|
exeDir = resolved
|
|
}
|
|
if resolved, err := filepath.EvalSymlinks(tempDir); err == nil {
|
|
tempDir = resolved
|
|
}
|
|
|
|
exeDirLower := strings.ToLower(exeDir)
|
|
inTemp := strings.HasPrefix(exeDirLower, strings.ToLower(tempDir))
|
|
// wails dev 会把 exe 编译到 build/bin/ 目录
|
|
inBuildBin := strings.HasSuffix(filepath.ToSlash(exeDirLower), "/build/bin")
|
|
|
|
if inTemp || inBuildBin {
|
|
// dev 模式:exe 在临时目录或 build/bin,使用 CWD 作为根目录
|
|
isDevMode = true
|
|
if cwd, err := os.Getwd(); err == nil {
|
|
appRoot = cwd
|
|
} else {
|
|
appRoot = "."
|
|
}
|
|
} else {
|
|
// 生产模式:使用 exe 所在目录
|
|
isDevMode = false
|
|
appRoot = exeDir
|
|
os.Chdir(exeDir)
|
|
}
|
|
} else {
|
|
// 兜底:使用 CWD
|
|
if cwd, err := os.Getwd(); err == nil {
|
|
appRoot = cwd
|
|
} else {
|
|
appRoot = "."
|
|
}
|
|
}
|
|
|
|
startupDebugEnabled := envFlagEnabled("ANT_BROWSER_DEBUG_STARTUP")
|
|
if startupDebugEnabled {
|
|
log.Printf("应用根目录: %s (dev=%v)", appRoot, isDevMode)
|
|
}
|
|
if err := backend.EnsureRuntimeLayout(appRoot); err != nil {
|
|
log.Printf("准备用户数据目录失败: %v", err)
|
|
}
|
|
singleInstance, primaryInstance, err := acquireSingleInstance(appRoot)
|
|
if err != nil {
|
|
log.Printf("单实例检查失败: %v", err)
|
|
}
|
|
if !primaryInstance {
|
|
if startupDebugEnabled {
|
|
log.Printf("检测到已有应用实例,已请求唤醒并退出当前进程")
|
|
}
|
|
return
|
|
}
|
|
defer singleInstance.Close()
|
|
if startupDebugEnabled && backend.RuntimeUsesDetachedState(appRoot) {
|
|
log.Printf("检测到安装目录需要只读运行,状态目录切换到: %s", backend.RuntimeStateRoot(appRoot))
|
|
}
|
|
buildVersion := resolveBuildVersion()
|
|
if startupDebugEnabled {
|
|
log.Printf("应用版本: %s", buildVersion)
|
|
log.Printf(
|
|
"Wails 启动环境: GOOS=%s GOARCH=%s DISPLAY=%q WAYLAND_DISPLAY=%q XDG_SESSION_TYPE=%q XDG_CURRENT_DESKTOP=%q",
|
|
goruntime.GOOS,
|
|
goruntime.GOARCH,
|
|
os.Getenv("DISPLAY"),
|
|
os.Getenv("WAYLAND_DISPLAY"),
|
|
os.Getenv("XDG_SESSION_TYPE"),
|
|
os.Getenv("XDG_CURRENT_DESKTOP"),
|
|
)
|
|
}
|
|
if startupDebugEnabled && goruntime.GOOS == "linux" && strings.TrimSpace(os.Getenv("DISPLAY")) == "" && strings.TrimSpace(os.Getenv("WAYLAND_DISPLAY")) == "" {
|
|
log.Printf("检测到 Linux 图形环境变量为空:DISPLAY / WAYLAND_DISPLAY 都未设置,GUI 窗口大概率无法创建")
|
|
}
|
|
|
|
// 加载配置
|
|
cfg, err := backend.LoadConfig(backend.ResolveRuntimePath(appRoot, "config.yaml"))
|
|
if err != nil {
|
|
log.Printf("加载配置失败,使用默认配置: %v", err)
|
|
cfg = backend.DefaultConfig()
|
|
}
|
|
|
|
// 创建应用实例
|
|
app := NewApp(appRoot, buildVersion)
|
|
|
|
var wailsCtx context.Context
|
|
startupReached := make(chan struct{})
|
|
go func() {
|
|
for activation := range singleInstance.activation {
|
|
if wailsCtx == nil {
|
|
select {
|
|
case <-startupReached:
|
|
case <-time.After(12 * time.Second):
|
|
}
|
|
if wailsCtx == nil {
|
|
close(activation.done)
|
|
continue
|
|
}
|
|
}
|
|
runtime.WindowShow(wailsCtx)
|
|
runtime.WindowUnminimise(wailsCtx)
|
|
runtime.WindowSetAlwaysOnTop(wailsCtx, true)
|
|
runtime.WindowSetAlwaysOnTop(wailsCtx, false)
|
|
activateExistingSingleInstanceWindow(os.Getpid())
|
|
close(activation.done)
|
|
}
|
|
}()
|
|
|
|
if startupDebugEnabled {
|
|
go func() {
|
|
select {
|
|
case <-startupReached:
|
|
return
|
|
case <-time.After(12 * time.Second):
|
|
log.Printf("Wails OnStartup 在 12 秒内未触发。若终端一直转圈但没有窗口,优先检查 Linux 图形环境、libgtk-3、libwebkit2gtk,以及是否运行在 SSH/容器/无桌面会话中")
|
|
}
|
|
}()
|
|
}
|
|
|
|
// 启动应用
|
|
if startupDebugEnabled {
|
|
log.Printf("准备调用 wails.Run 创建 GUI 窗口")
|
|
}
|
|
windowBounds := resolveStartupWindowBounds(startupWindowBounds{
|
|
Width: cfg.App.Window.Width,
|
|
Height: cfg.App.Window.Height,
|
|
MinWidth: cfg.App.Window.MinWidth,
|
|
MinHeight: cfg.App.Window.MinHeight,
|
|
})
|
|
if startupDebugEnabled {
|
|
log.Printf(
|
|
"窗口启动尺寸: width=%d height=%d minWidth=%d minHeight=%d",
|
|
windowBounds.Width,
|
|
windowBounds.Height,
|
|
windowBounds.MinWidth,
|
|
windowBounds.MinHeight,
|
|
)
|
|
}
|
|
err = wails.Run(&options.App{
|
|
Title: cfg.App.Name,
|
|
Width: windowBounds.Width,
|
|
Height: windowBounds.Height,
|
|
MinWidth: windowBounds.MinWidth,
|
|
MinHeight: windowBounds.MinHeight,
|
|
AssetServer: &assetserver.Options{
|
|
Assets: assets,
|
|
},
|
|
BackgroundColour: &options.RGBA{R: 245, G: 247, B: 250, A: 255},
|
|
OnStartup: func(ctx context.Context) {
|
|
close(startupReached)
|
|
if startupDebugEnabled {
|
|
log.Printf("Wails OnStartup 已触发,GUI 宿主已创建")
|
|
}
|
|
wailsCtx = ctx
|
|
runtime.WindowCenter(wailsCtx)
|
|
// 启动系统托盘(非阻塞)
|
|
go backend.RunTray(backend.TrayCallbacks{
|
|
OnShow: func() {
|
|
runtime.WindowShow(wailsCtx)
|
|
runtime.WindowUnminimise(wailsCtx)
|
|
activateExistingSingleInstanceWindow(os.Getpid())
|
|
},
|
|
OnQuitAppOnly: func() {
|
|
app.QuitAppOnly()
|
|
},
|
|
OnQuit: func() {
|
|
app.ForceQuit()
|
|
},
|
|
})
|
|
app.startup(ctx)
|
|
if startupDebugEnabled {
|
|
log.Printf("后端 startup 已完成")
|
|
}
|
|
},
|
|
OnShutdown: func(ctx context.Context) {
|
|
if startupDebugEnabled {
|
|
log.Printf("Wails OnShutdown 已触发")
|
|
}
|
|
backend.QuitTray()
|
|
app.shutdown(ctx)
|
|
},
|
|
// 拦截关闭按钮事件,由前端处理自定义对话框
|
|
OnBeforeClose: func(ctx context.Context) bool {
|
|
return app.shouldBlockClose(ctx)
|
|
},
|
|
Bind: []interface{}{
|
|
app,
|
|
},
|
|
Linux: &linux.Options{
|
|
// Expose a real window icon to Linux desktop environments.
|
|
Icon: linuxAppIcon,
|
|
WebviewGpuPolicy: linux.WebviewGpuPolicyNever,
|
|
},
|
|
Windows: &windows.Options{
|
|
WebviewIsTransparent: false,
|
|
WindowIsTranslucent: false,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
log.Fatal("启动应用失败:", err)
|
|
}
|
|
if startupDebugEnabled {
|
|
log.Printf("wails.Run 已退出")
|
|
}
|
|
}
|