b483360778
- 新增 light/dark 主题调色板与 Windows 注册表系统主题探测 - 偏好设置对话框:主题三选一 + 语言下拉(en/zh-CN/de),即时生效 - 持久化 Language/Theme/AutoLockMinutes/MinimizeToTray 至 YAML 顶层 - 启动时按 config 中保存的语言初始化 i18n - About 对话框展示 version/runtime/项目地址/许可证 - 全部硬编码颜色迁移到 themePalette,为深色模式做准备
118 lines
3.7 KiB
Go
118 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"git.wxccs.org/iceking2nd/winauth-go/internal/config"
|
|
"git.wxccs.org/iceking2nd/winauth-go/internal/global"
|
|
"git.wxccs.org/iceking2nd/winauth-go/internal/i18n"
|
|
"git.wxccs.org/iceking2nd/winauth-go/internal/logging"
|
|
"git.wxccs.org/iceking2nd/winauth-go/internal/ui"
|
|
"git.wxccs.org/iceking2nd/winauth-go/internal/version"
|
|
"git.wxccs.org/iceking2nd/winauth-go/internal/win32"
|
|
)
|
|
|
|
const (
|
|
// singleInstanceMutex is the name used by AcquireInstanceLock. The
|
|
// "Global\" prefix would make the lock cross-session; without it
|
|
// each Windows user sees their own copy, which is what we want
|
|
// (config.yaml is per-user anyway).
|
|
singleInstanceMutex = "winauth-go-single-instance-mutex"
|
|
// windowTitle is what AcquireInstanceLock's duplicate path tries to
|
|
// foreground. Must stay in sync with ui.Run's app.Title option.
|
|
windowTitle = "WinAuth"
|
|
)
|
|
|
|
func main() {
|
|
var (
|
|
console bool
|
|
logLevel string
|
|
logFile string
|
|
configPath string
|
|
)
|
|
|
|
root := &cobra.Command{
|
|
Use: "winauth-go",
|
|
Short: "WinAuth (Go port) — TOTP/HOTP authenticator",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if console {
|
|
logging.AttachConsole()
|
|
}
|
|
|
|
closer, err := logging.Init(logging.Options{
|
|
Level: logLevel,
|
|
File: logFile,
|
|
Console: console,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if closer != nil {
|
|
defer func() { _ = closer.Close() }()
|
|
}
|
|
|
|
const fn = "cmd.winauth.main"
|
|
global.Log.WithField("func", fn).
|
|
WithField("version", version.String()).Info("winauth-go starting")
|
|
|
|
release, alreadyRunning, err := win32.AcquireInstanceLock(singleInstanceMutex)
|
|
if err != nil {
|
|
global.Log.WithField("func", fn).WithError(err).Warn("instance lock failed; continuing without it")
|
|
}
|
|
if alreadyRunning {
|
|
global.Log.WithField("func", fn).Info("another instance already running; activating it")
|
|
if err := win32.ActivateOtherInstance(windowTitle); err != nil {
|
|
global.Log.WithField("func", fn).WithError(err).Warn("could not activate existing window")
|
|
}
|
|
return nil
|
|
}
|
|
if release != nil {
|
|
defer release()
|
|
}
|
|
|
|
// Peek the config (without password) to recover the user's
|
|
// last language choice before initialising i18n. Encrypted
|
|
// configs still expose Language at the YAML top level so we
|
|
// can localise the password prompt itself.
|
|
lang := preferredLanguage(configPath)
|
|
if err := i18n.Init(lang); err != nil {
|
|
global.Log.WithField("func", fn).WithError(err).Warn("i18n init failed; falling back to keys")
|
|
}
|
|
|
|
return ui.Run(configPath)
|
|
},
|
|
}
|
|
|
|
root.Flags().BoolVar(&console, "console", false, "Show console window (Windows GUI builds)")
|
|
root.Flags().StringVar(&logLevel, "log-level", "info",
|
|
"Log level: panic|fatal|error|warn|info|debug|trace (or 0..6)")
|
|
root.Flags().StringVar(&logFile, "log-file", "",
|
|
"If set, also write logs to this file in addition to the console")
|
|
root.Flags().StringVar(&configPath, "config", "",
|
|
"Path to the YAML config file (default: %APPDATA%\\winauth-go\\config.yaml or $XDG_CONFIG_HOME/winauth-go/config.yaml)")
|
|
|
|
if err := root.Execute(); err != nil {
|
|
fmt.Fprintln(os.Stderr, "error:", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// preferredLanguage reads the user's saved language tag out of the YAML
|
|
// config without requiring a passphrase. We resolve the default config
|
|
// path when configPath is blank and silently return "" on any failure;
|
|
// callers fall back to the i18n default in that case. Encrypted configs
|
|
// still expose the top-level Language field so this works for them too.
|
|
func preferredLanguage(configPath string) string {
|
|
if configPath == "" {
|
|
configPath = config.DefaultPath()
|
|
}
|
|
cfg, _ := config.LoadYAML(configPath, nil)
|
|
if cfg == nil {
|
|
return ""
|
|
}
|
|
return cfg.Language
|
|
}
|